diff --git a/build.gradle b/build.gradle index 0617184..1794334 100644 --- a/build.gradle +++ b/build.gradle @@ -29,8 +29,19 @@ dependencies { compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' + implementation 'org.projectlombok:lombok' + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' + + + // test testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + testImplementation 'org.springframework.security:spring-security-test' + testImplementation 'org.awaitility:awaitility:4.3.0' + testImplementation 'com.h2database:h2' + + } tasks.named('test') { diff --git a/src/main/java/com/example/gtable/GTableApplication.java b/src/main/java/com/example/gtable/GTableApplication.java index 700b9fc..bacdc50 100644 --- a/src/main/java/com/example/gtable/GTableApplication.java +++ b/src/main/java/com/example/gtable/GTableApplication.java @@ -2,12 +2,14 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +@EnableJpaAuditing @SpringBootApplication public class GTableApplication { - public static void main(String[] args) { - SpringApplication.run(GTableApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(GTableApplication.class, args); + } } diff --git a/src/main/java/com/example/gtable/TODO/TodoController.java b/src/main/java/com/example/gtable/TODO/TodoController.java deleted file mode 100644 index bf74bd9..0000000 --- a/src/main/java/com/example/gtable/TODO/TodoController.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.example.gtable.TODO; - -import com.example.gtable.global.api.ApiUtils; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -public class TodoController { - private TodoService todoService; - @GetMapping("TODO") - public ResponseEntity createBoard() { - return ResponseEntity - .status(HttpStatus.CREATED) - .body( - ApiUtils.success( - todoService.add() - ) - ); - } -} \ No newline at end of file diff --git a/src/main/java/com/example/gtable/TODO/TodoRepository.java b/src/main/java/com/example/gtable/TODO/TodoRepository.java deleted file mode 100644 index 5b44108..0000000 --- a/src/main/java/com/example/gtable/TODO/TodoRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.example.gtable.TODO; - -import org.springframework.stereotype.Repository; - -@Repository -public interface TodoRepository { -} diff --git a/src/main/java/com/example/gtable/TODO/TodoService.java b/src/main/java/com/example/gtable/TODO/TodoService.java deleted file mode 100644 index 61a198b..0000000 --- a/src/main/java/com/example/gtable/TODO/TodoService.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.example.gtable.TODO; - -import org.springframework.stereotype.Service; - -@Service -public class TodoService { - private final TodoRepository todoRepository; - - public TodoService(TodoRepository todoRepository) { - this.todoRepository = todoRepository; - } - - public String add() { - String todo = "TODO"; - String todoList = "TODO LIST"; - return todo; - } -} diff --git a/src/main/java/com/example/gtable/global/entity/BaseTimeEntity.java b/src/main/java/com/example/gtable/global/entity/BaseTimeEntity.java new file mode 100644 index 0000000..3f7ca32 --- /dev/null +++ b/src/main/java/com/example/gtable/global/entity/BaseTimeEntity.java @@ -0,0 +1,39 @@ +package com.example.gtable.global.entity; + +import java.time.LocalDateTime; + +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Getter +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +@SuperBuilder +@NoArgsConstructor +@Schema(description = "시간 관련 VO") +public abstract class BaseTimeEntity { + + @CreatedDate + @Column(updatable = false, name = "created_at") + @JsonSerialize(using = LocalDateTimeSerializer.class) + @JsonDeserialize(using = LocalDateTimeDeserializer.class) + private LocalDateTime createdAt; + + public BaseTimeEntity(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + +} diff --git a/src/main/java/com/example/gtable/store/controller/StoreController.java b/src/main/java/com/example/gtable/store/controller/StoreController.java new file mode 100644 index 0000000..d0594e4 --- /dev/null +++ b/src/main/java/com/example/gtable/store/controller/StoreController.java @@ -0,0 +1,89 @@ +package com.example.gtable.store.controller; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.example.gtable.global.api.ApiUtils; +import com.example.gtable.store.dto.StoreCreateRequest; +import com.example.gtable.store.dto.StoreCreateResponse; +import com.example.gtable.store.dto.StoreUpdateRequest; +import com.example.gtable.store.service.StoreService; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/stores") +@RequiredArgsConstructor +public class StoreController { + + private final StoreService storeService; + + @PostMapping + public ResponseEntity createStore(@Valid @RequestBody StoreCreateRequest request) { + StoreCreateResponse response = storeService.createStore(request); + + return ResponseEntity + .status(HttpStatus.CREATED) + .body( + ApiUtils.success( + response + ) + ); + } + + @GetMapping("/all-stores") + public ResponseEntity getAllStores() { + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + storeService.getAllStores() + ) + ); + } + + @GetMapping("/{storeId}") + public ResponseEntity getStoreById(@PathVariable Long storeId) { + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + storeService.getStoreByStoreId(storeId) + ) + ); + } + + @PatchMapping("/{storeId}") + public ResponseEntity updateStore( + @PathVariable Long storeId, + @Valid @RequestBody StoreUpdateRequest request + ) { + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + storeService.updateStore(storeId, request) + ) + ); + } + + @DeleteMapping("/{storeId}") + public ResponseEntity deleteStore(@PathVariable Long storeId) { + return ResponseEntity + .ok() + .body( + ApiUtils.success( + storeService.deleteStore(storeId) + ) + ); + } +} diff --git a/src/main/java/com/example/gtable/store/dto/StoreCreateRequest.java b/src/main/java/com/example/gtable/store/dto/StoreCreateRequest.java new file mode 100644 index 0000000..e400dd3 --- /dev/null +++ b/src/main/java/com/example/gtable/store/dto/StoreCreateRequest.java @@ -0,0 +1,39 @@ +package com.example.gtable.store.dto; + +import com.example.gtable.store.model.Store; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class StoreCreateRequest { + + @NotNull + private Long departmentId; + + @NotBlank + private String name; + + private String location; + + private String description; + + private String storeImageUrl; + + public Store toEntity() { + return Store.builder() + .departmentId(departmentId) + .name(name) + .location(location) + .description(description) + .storeImageUrl(storeImageUrl) + .isActive(false) + .deleted(false) + .build(); + } +} diff --git a/src/main/java/com/example/gtable/store/dto/StoreCreateResponse.java b/src/main/java/com/example/gtable/store/dto/StoreCreateResponse.java new file mode 100644 index 0000000..caeeaf4 --- /dev/null +++ b/src/main/java/com/example/gtable/store/dto/StoreCreateResponse.java @@ -0,0 +1,39 @@ +package com.example.gtable.store.dto; + +import java.time.LocalDateTime; + +import com.example.gtable.store.model.Store; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Builder +public class StoreCreateResponse { + + private Long storeId; + private Long departmentId; + private String name; + private String location; + private String description; + private String storeImageUrl; + private Boolean isActive; + private Boolean deleted; + private LocalDateTime createdAt; + + public static StoreCreateResponse fromEntity(Store store) { + return StoreCreateResponse.builder() + .createdAt(store.getCreatedAt()) + .storeId(store.getStoreId()) + .departmentId(store.getDepartmentId()) + .name(store.getName()) + .location(store.getLocation()) + .description(store.getDescription()) + .storeImageUrl(store.getStoreImageUrl()) + .isActive(store.getIsActive()) + .deleted(store.getDeleted()) + .build(); + } +} diff --git a/src/main/java/com/example/gtable/store/dto/StoreReadDto.java b/src/main/java/com/example/gtable/store/dto/StoreReadDto.java new file mode 100644 index 0000000..610f176 --- /dev/null +++ b/src/main/java/com/example/gtable/store/dto/StoreReadDto.java @@ -0,0 +1,38 @@ +package com.example.gtable.store.dto; + +import java.time.LocalDateTime; + +import com.example.gtable.store.model.Store; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Builder +public class StoreReadDto { + private Long storeId; + private Long departmentId; + private String name; + private String location; + private String description; + private String storeImageUrl; + private Boolean isActive; + private Boolean deleted; + private LocalDateTime createdAt; + + public static StoreReadDto fromEntity(Store store) { + return StoreReadDto.builder() + .createdAt(store.getCreatedAt()) + .storeId(store.getStoreId()) + .departmentId(store.getDepartmentId()) + .name(store.getName()) + .location(store.getLocation()) + .description(store.getDescription()) + .storeImageUrl(store.getStoreImageUrl()) + .isActive(store.getIsActive()) + .deleted(store.getDeleted()) + .build(); + } +} diff --git a/src/main/java/com/example/gtable/store/dto/StoreReadResponse.java b/src/main/java/com/example/gtable/store/dto/StoreReadResponse.java new file mode 100644 index 0000000..061206f --- /dev/null +++ b/src/main/java/com/example/gtable/store/dto/StoreReadResponse.java @@ -0,0 +1,23 @@ +package com.example.gtable.store.dto; + +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Builder +public class StoreReadResponse { + + private List storeReadDtos; + private boolean hasNext; + + public static StoreReadResponse fromEntity(List storeReadDtos, boolean hasNext) { + return StoreReadResponse.builder() + .storeReadDtos(storeReadDtos) + .hasNext(hasNext) + .build(); + } +} diff --git a/src/main/java/com/example/gtable/store/dto/StoreUpdateRequest.java b/src/main/java/com/example/gtable/store/dto/StoreUpdateRequest.java new file mode 100644 index 0000000..033b8ee --- /dev/null +++ b/src/main/java/com/example/gtable/store/dto/StoreUpdateRequest.java @@ -0,0 +1,18 @@ +package com.example.gtable.store.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class StoreUpdateRequest { + private String name; + private String location; + private String description; + private String storeImageUrl; + private Boolean isActive; +} diff --git a/src/main/java/com/example/gtable/store/model/Store.java b/src/main/java/com/example/gtable/store/model/Store.java new file mode 100644 index 0000000..cefa782 --- /dev/null +++ b/src/main/java/com/example/gtable/store/model/Store.java @@ -0,0 +1,83 @@ +package com.example.gtable.store.model; + +import java.time.LocalDateTime; + +import com.example.gtable.global.entity.BaseTimeEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Entity +@Table(name = "stores") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@SuperBuilder +public class Store extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long storeId; + + private Long departmentId; + + private String name; + + private String location; + + private String description; + + private String storeImageUrl; + + @Column(name = "is_active", nullable = false) + private Boolean isActive = false; + + @Column + private Boolean deleted = false; + + public Store(LocalDateTime createdAt, Long storeId, Long departmentId, String name, String location, + String description, String storeImageUrl, Boolean isActive, Boolean deleted) { + super(createdAt); + this.storeId = storeId; + this.departmentId = departmentId; + this.name = name; + this.location = location; + this.description = description; + this.storeImageUrl = storeImageUrl; + this.isActive = isActive; + this.deleted = deleted; + } + + public void setName(String name) { + this.name = name; + } + + public void setLocation(String location) { + this.location = location; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setStoreImageUrl(String url) { + this.storeImageUrl = url; + } + + public void setIsActive(Boolean isActive) { + this.isActive = isActive; + } + + public void setDeleted(Boolean deleted) { + this.deleted = deleted; + } +} diff --git a/src/main/java/com/example/gtable/store/repository/StoreRepository.java b/src/main/java/com/example/gtable/store/repository/StoreRepository.java new file mode 100644 index 0000000..e0ba279 --- /dev/null +++ b/src/main/java/com/example/gtable/store/repository/StoreRepository.java @@ -0,0 +1,17 @@ +package com.example.gtable.store.repository; + +import java.util.List; +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import com.example.gtable.store.model.Store; + +@Repository +public interface StoreRepository extends JpaRepository { + + List findAllByDeletedFalse(); + + Optional findByStoreIdAndDeletedFalse(Long storeId); +} diff --git a/src/main/java/com/example/gtable/store/service/StoreService.java b/src/main/java/com/example/gtable/store/service/StoreService.java new file mode 100644 index 0000000..ae7f46d --- /dev/null +++ b/src/main/java/com/example/gtable/store/service/StoreService.java @@ -0,0 +1,21 @@ +package com.example.gtable.store.service; + +import com.example.gtable.store.dto.StoreCreateRequest; +import com.example.gtable.store.dto.StoreCreateResponse; +import com.example.gtable.store.dto.StoreReadDto; +import com.example.gtable.store.dto.StoreReadResponse; +import com.example.gtable.store.dto.StoreUpdateRequest; + +public interface StoreService { + + StoreCreateResponse createStore(StoreCreateRequest request); + + StoreReadResponse getAllStores(); + + StoreReadDto getStoreByStoreId(Long storeId); + + StoreReadDto updateStore(Long storeId, StoreUpdateRequest request); + + String deleteStore(Long storeId); + +} diff --git a/src/main/java/com/example/gtable/store/service/StoreServiceImpl.java b/src/main/java/com/example/gtable/store/service/StoreServiceImpl.java new file mode 100644 index 0000000..165f3e6 --- /dev/null +++ b/src/main/java/com/example/gtable/store/service/StoreServiceImpl.java @@ -0,0 +1,94 @@ +package com.example.gtable.store.service; + +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.example.gtable.store.dto.StoreCreateRequest; +import com.example.gtable.store.dto.StoreCreateResponse; +import com.example.gtable.store.dto.StoreReadDto; +import com.example.gtable.store.dto.StoreReadResponse; +import com.example.gtable.store.dto.StoreUpdateRequest; +import com.example.gtable.store.model.Store; +import com.example.gtable.store.repository.StoreRepository; + +import jakarta.persistence.EntityNotFoundException; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class StoreServiceImpl implements StoreService { + + private final StoreRepository storeRepository; + + @Override + @Transactional + public StoreCreateResponse createStore(StoreCreateRequest request) { + Store toSave = request.toEntity(); + + Store saved = storeRepository.save(toSave); + + return StoreCreateResponse.fromEntity(saved); + } + + @Override + @Transactional(readOnly = true) + public StoreReadResponse getAllStores() { + List stores = storeRepository.findAllByDeletedFalse(); + + List storeRead = stores.stream() + .map(StoreReadDto::fromEntity) + .toList(); + + boolean hasNext = false; + + return StoreReadResponse.fromEntity( + storeRead, + hasNext + ); + } + + @Override + @Transactional(readOnly = true) + public StoreReadDto getStoreByStoreId(Long storeId) { + Store store = storeRepository.findByStoreIdAndDeletedFalse(storeId) + .orElseThrow(() -> new EntityNotFoundException(storeId + " store not found.")); + + return StoreReadDto.fromEntity(store); + } + + @Override + @Transactional + public StoreReadDto updateStore(Long storeId, StoreUpdateRequest request) { + Store store = storeRepository.findByStoreIdAndDeletedFalse(storeId) + .orElseThrow(() -> new EntityNotFoundException(storeId + " store not found.")); + + if (request.getName() != null) + store.setName(request.getName()); + if (request.getLocation() != null) + store.setLocation(request.getLocation()); + if (request.getDescription() != null) + store.setDescription(request.getDescription()); + if (request.getStoreImageUrl() != null) + store.setStoreImageUrl(request.getStoreImageUrl()); + if (request.getIsActive() != null) + store.setIsActive(request.getIsActive()); + + Store updatedStore = storeRepository.save(store); + + return StoreReadDto.fromEntity(updatedStore); + } + + @Override + @Transactional + public String deleteStore(Long storeId) { + Store store = storeRepository.findByStoreIdAndDeletedFalse(storeId) + .orElseThrow(() -> new EntityNotFoundException(storeId + " store not found.")); + + store.setDeleted(true); + storeRepository.save(store); + + return "Store ID " + storeId + " 삭제되었습니다."; + } +} diff --git a/src/test/java/com/example/gtable/store/controller/StoreControllerTest.java b/src/test/java/com/example/gtable/store/controller/StoreControllerTest.java new file mode 100644 index 0000000..18f655f --- /dev/null +++ b/src/test/java/com/example/gtable/store/controller/StoreControllerTest.java @@ -0,0 +1,84 @@ +package com.example.gtable.store.controller; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.time.LocalDateTime; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import com.example.gtable.store.dto.StoreCreateRequest; +import com.example.gtable.store.dto.StoreCreateResponse; +import com.example.gtable.store.service.StoreService; +import com.fasterxml.jackson.databind.ObjectMapper; + +class StoreControllerTest { + + private MockMvc mockMvc; + + @Mock + private StoreService storeService; + + @InjectMocks + private StoreController storeController; + + private ObjectMapper objectMapper; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + mockMvc = MockMvcBuilders.standaloneSetup(storeController).build(); + objectMapper = new ObjectMapper(); + } + + @Test + @DisplayName("매장 생성 요청이 유효하면 201Created 반환") + void success_return_201_Created() throws Exception { + // given + StoreCreateRequest request = new StoreCreateRequest(1L, "Test Store", "Seoul", "Desc", "http://img"); + LocalDateTime now = LocalDateTime.now(); + StoreCreateResponse responseDto = StoreCreateResponse.builder() + .storeId(10L) + .departmentId(1L) + .name("Test Store") + .location("Seoul") + .description("Desc") + .storeImageUrl("http://img") + .isActive(true) + .createdAt(now) + .build(); + + when(storeService.createStore(any(StoreCreateRequest.class))).thenReturn(responseDto); + + // when + ResultActions result = mockMvc.perform( + post("/stores").contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(request))); + + // then + result.andExpect(status().isCreated()); + } + + @Test + @DisplayName("필수 필드 누락 시 400 Bad Request 반환") + void success_return_400_Bad() throws Exception { + // given + String json = "{\"name\":\"Test Store\",\"location\":\"Seoul\",\"description\":\"Desc\",\"storeImageUrl\":\"http://img\"}"; + + // when + ResultActions result = mockMvc.perform(post("/stores").contentType(MediaType.APPLICATION_JSON).content(json)); + + // then + result.andExpect(status().isBadRequest()); + } +} diff --git a/src/test/java/com/example/gtable/store/repository/StoreRepositoryTest.java b/src/test/java/com/example/gtable/store/repository/StoreRepositoryTest.java new file mode 100644 index 0000000..5abc060 --- /dev/null +++ b/src/test/java/com/example/gtable/store/repository/StoreRepositoryTest.java @@ -0,0 +1,46 @@ +package com.example.gtable.store.repository; + +import static org.assertj.core.api.Assertions.*; + +import java.util.Optional; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import com.example.gtable.store.model.Store; + +@DataJpaTest +public class StoreRepositoryTest { + + @Autowired + private StoreRepository storeRepository; + + @Test + @DisplayName("매장 저장 및 조회") + void save_and_find() { + // given + Store store = Store.builder() + .departmentId(1L) + .name("Test Store") + .location("Seoul") + .description("설명") + .storeImageUrl("http://img") + .isActive(true) + .build(); + + // when + Store saved = storeRepository.save(store); + Optional found = storeRepository.findById(saved.getStoreId()); + + // then + assertThat(found).isPresent(); + assertThat(found.get().getDepartmentId()).isEqualTo(1L); + assertThat(found.get().getName()).isEqualTo("Test Store"); + assertThat(found.get().getLocation()).isEqualTo("Seoul"); + assertThat(found.get().getDescription()).isEqualTo("설명"); + assertThat(found.get().getStoreImageUrl()).isEqualTo("http://img"); + assertThat(found.get().getIsActive()).isTrue(); + } +} diff --git a/src/test/java/com/example/gtable/store/service/StoreServiceTest.java b/src/test/java/com/example/gtable/store/service/StoreServiceTest.java new file mode 100644 index 0000000..69a6c1c --- /dev/null +++ b/src/test/java/com/example/gtable/store/service/StoreServiceTest.java @@ -0,0 +1,71 @@ +package com.example.gtable.store.service; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.time.LocalDateTime; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.example.gtable.store.dto.StoreCreateRequest; +import com.example.gtable.store.dto.StoreCreateResponse; +import com.example.gtable.store.model.Store; +import com.example.gtable.store.repository.StoreRepository; + +@ExtendWith(MockitoExtension.class) +public class StoreServiceTest { + + @InjectMocks + private StoreServiceImpl storeService; + + @Mock + private StoreRepository storeRepository; + + @Test + @DisplayName("매장 생성 요청이 유효하면 StoreCreateResponse 반환한다") + void createStore_success() { + + // given + StoreCreateRequest request = new StoreCreateRequest( + 1L, "Test Store", "Seoul", "Info", "http://img" + ); + + LocalDateTime now = LocalDateTime.now(); + Store initial = request.toEntity(); + + Store savedEntity = Store.builder() + .storeId(10L) + .departmentId(initial.getDepartmentId()) + .name(initial.getName()) + .location(initial.getLocation()) + .description(initial.getDescription()) + .storeImageUrl(initial.getStoreImageUrl()) + .isActive(true) + .createdAt(now) + .build(); + + when(storeRepository.save(any(Store.class))).thenReturn(savedEntity); + + // when + StoreCreateResponse response = storeService.createStore(request); + + // then + assertThat(response).isNotNull(); + assertThat(response.getStoreId()).isEqualTo(10L); + assertThat(response.getDepartmentId()).isEqualTo(1L); + assertThat(response.getName()).isEqualTo("Test Store"); + assertThat(response.getLocation()).isEqualTo("Seoul"); + assertThat(response.getDescription()).isEqualTo("Info"); + assertThat(response.getStoreImageUrl()).isEqualTo("http://img"); + assertThat(response.getIsActive()).isTrue(); + assertThat(response.getCreatedAt()).isEqualTo(now); + + verify(storeRepository, times(1)).save(any(Store.class)); + } + +}