diff --git a/src/main/kotlin/com/mjucow/eatda/domain/store/service/command/MenuCommandService.kt b/src/main/kotlin/com/mjucow/eatda/domain/store/service/command/MenuCommandService.kt index 216ef0d..867fa26 100644 --- a/src/main/kotlin/com/mjucow/eatda/domain/store/service/command/MenuCommandService.kt +++ b/src/main/kotlin/com/mjucow/eatda/domain/store/service/command/MenuCommandService.kt @@ -14,8 +14,8 @@ class MenuCommandService( private val repository: MenuRepository, private val storeRepository: StoreRepository, ) { - fun create(command: MenuCreateCommand): Long { - val store = storeRepository.getReferenceById(command.storeId) + fun create(storeId: Long, command: MenuCreateCommand): Long { + val store = storeRepository.getReferenceById(storeId) val menu = Menu( name = command.name, @@ -26,8 +26,8 @@ class MenuCommandService( return repository.save(menu).id } - fun update(command: MenuUpdateCommand) { - val menu = repository.getReferenceById(command.id).apply { + fun update(id: Long, command: MenuUpdateCommand) { + val menu = repository.getReferenceById(id).apply { name = command.name price = command.price imageAddress = command.imageAddress diff --git a/src/main/kotlin/com/mjucow/eatda/domain/store/service/command/dto/MenuCreateCommand.kt b/src/main/kotlin/com/mjucow/eatda/domain/store/service/command/dto/MenuCreateCommand.kt index 6bb2978..97d0a5f 100644 --- a/src/main/kotlin/com/mjucow/eatda/domain/store/service/command/dto/MenuCreateCommand.kt +++ b/src/main/kotlin/com/mjucow/eatda/domain/store/service/command/dto/MenuCreateCommand.kt @@ -1,7 +1,6 @@ package com.mjucow.eatda.domain.store.service.command.dto data class MenuCreateCommand( - val storeId: Long, val name: String, val price: Int, val imageAddress: String? = null, diff --git a/src/main/kotlin/com/mjucow/eatda/domain/store/service/command/dto/MenuUpdateCommand.kt b/src/main/kotlin/com/mjucow/eatda/domain/store/service/command/dto/MenuUpdateCommand.kt index 60b3fef..3eba071 100644 --- a/src/main/kotlin/com/mjucow/eatda/domain/store/service/command/dto/MenuUpdateCommand.kt +++ b/src/main/kotlin/com/mjucow/eatda/domain/store/service/command/dto/MenuUpdateCommand.kt @@ -1,7 +1,6 @@ package com.mjucow.eatda.domain.store.service.command.dto data class MenuUpdateCommand( - val id: Long, val name: String, val price: Int, val imageAddress: String? = null, diff --git a/src/main/kotlin/com/mjucow/eatda/presentation/store/StoreController.kt b/src/main/kotlin/com/mjucow/eatda/presentation/store/StoreController.kt index f6f95dc..08e59ec 100644 --- a/src/main/kotlin/com/mjucow/eatda/presentation/store/StoreController.kt +++ b/src/main/kotlin/com/mjucow/eatda/presentation/store/StoreController.kt @@ -1,9 +1,13 @@ package com.mjucow.eatda.presentation.store +import com.mjucow.eatda.domain.store.service.command.MenuCommandService import com.mjucow.eatda.domain.store.service.command.StoreCommandService +import com.mjucow.eatda.domain.store.service.command.dto.MenuCreateCommand import com.mjucow.eatda.domain.store.service.command.dto.StoreCreateCommand import com.mjucow.eatda.domain.store.service.command.dto.StoreUpdateCommand +import com.mjucow.eatda.domain.store.service.query.MenuQueryService import com.mjucow.eatda.domain.store.service.query.StoreQueryService +import com.mjucow.eatda.domain.store.service.query.dto.MenuList import com.mjucow.eatda.domain.store.service.query.dto.StoreDetailDto import com.mjucow.eatda.domain.store.service.query.dto.StoreDto import com.mjucow.eatda.presentation.common.ApiResponse @@ -27,6 +31,8 @@ import org.springframework.web.bind.annotation.RestController class StoreController( val storeQueryService: StoreQueryService, val storeCommandService: StoreCommandService, + val menuQueryService: MenuQueryService, + val menuCommandService: MenuCommandService, ) { @PostMapping @@ -65,4 +71,19 @@ class StoreController( fun deleteById(@PathVariable("storeId") id: Long) { storeCommandService.delete(id) } + + @GetMapping("/{storeId}/menu") + @ResponseStatus(HttpStatus.OK) + fun findAllMenu(@PathVariable("storeId") id: Long): ApiResponse { + return ApiResponse.success(menuQueryService.findAll(id)) + } + + @PostMapping("/{storeId}/menu") + @ResponseStatus(HttpStatus.CREATED) + fun createMenu( + @PathVariable("storeId") id: Long, + @RequestBody menuCreateCommand: MenuCreateCommand, + ): ApiResponse { + return ApiResponse.success(menuCommandService.create(id, menuCreateCommand)) + } } diff --git a/src/main/kotlin/com/mjucow/eatda/presentation/store/menu/MenuController.kt b/src/main/kotlin/com/mjucow/eatda/presentation/store/menu/MenuController.kt new file mode 100644 index 0000000..c2d4b3f --- /dev/null +++ b/src/main/kotlin/com/mjucow/eatda/presentation/store/menu/MenuController.kt @@ -0,0 +1,41 @@ +package com.mjucow.eatda.presentation.store.menu + +import com.mjucow.eatda.domain.store.service.command.MenuCommandService +import com.mjucow.eatda.domain.store.service.command.dto.MenuUpdateCommand +import com.mjucow.eatda.domain.store.service.query.MenuDto +import com.mjucow.eatda.domain.store.service.query.MenuQueryService +import com.mjucow.eatda.presentation.common.ApiResponse +import org.springframework.http.HttpStatus +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.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.ResponseStatus +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/api/v1/menu") +class MenuController( + private val queryService: MenuQueryService, + private val commandService: MenuCommandService, +) { + @GetMapping("/{menuId}") + @ResponseStatus(HttpStatus.OK) + fun findById(@PathVariable("menuId") id: Long): ApiResponse { + return ApiResponse.success(queryService.findById(id)) + } + + @PatchMapping("/{menuId}") + @ResponseStatus(HttpStatus.OK) + fun updateById(@PathVariable("menuId") id: Long, @RequestBody command: MenuUpdateCommand) { + commandService.update(id, command) + } + + @DeleteMapping("/{menuId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + fun deleteById(@PathVariable("menuId") id: Long) { + commandService.delete(id) + } +} diff --git a/src/test/kotlin/com/mjucow/eatda/domain/store/service/command/MenuCommandServiceTest.kt b/src/test/kotlin/com/mjucow/eatda/domain/store/service/command/MenuCommandServiceTest.kt index 7156fcd..4ce4d1e 100644 --- a/src/test/kotlin/com/mjucow/eatda/domain/store/service/command/MenuCommandServiceTest.kt +++ b/src/test/kotlin/com/mjucow/eatda/domain/store/service/command/MenuCommandServiceTest.kt @@ -37,14 +37,14 @@ class MenuCommandServiceTest : AbstractDataTest() { @Test fun test1() { // given + val storeId = Long.MAX_VALUE val command = MenuCreateCommand( - storeId = Long.MAX_VALUE, name = MenuMother.NAME, price = MenuMother.PRICE ) // when - val throwable = catchThrowable { commandService.create(command) } + val throwable = catchThrowable { commandService.create(storeId, command) } // then assertThat(throwable).isNotNull() @@ -56,13 +56,12 @@ class MenuCommandServiceTest : AbstractDataTest() { // given val store = storeRepository.save(StoreMother.create()) val command = MenuCreateCommand( - storeId = store.id, name = MenuMother.NAME, price = MenuMother.PRICE ) // when - val menuId = commandService.create(command) + val menuId = commandService.create(store.id, command) // then assertThat(repository.getReferenceById(menuId)).isNotNull() @@ -72,14 +71,14 @@ class MenuCommandServiceTest : AbstractDataTest() { @Test fun test3() { // given + val id = Long.MAX_VALUE val command = MenuUpdateCommand( - id = 1L, name = MenuMother.NAME, price = MenuMother.PRICE ) // when - val throwable = catchThrowable { commandService.update(command) } + val throwable = catchThrowable { commandService.update(id, command) } // then assertThat(throwable).isNotNull() @@ -93,13 +92,12 @@ class MenuCommandServiceTest : AbstractDataTest() { val updatedName = "updateName" val updatedPrice = MenuMother.PRICE * 2 val command = MenuUpdateCommand( - id = menu.id, name = updatedName, price = updatedPrice ) // when - commandService.update(command) + commandService.update(menu.id, command) // then val updatedMenu = repository.getReferenceById(menu.id) diff --git a/src/test/kotlin/com/mjucow/eatda/presentation/store/StoreControllerMvcTest.kt b/src/test/kotlin/com/mjucow/eatda/presentation/store/StoreControllerMvcTest.kt index 9a677fa..7411ecd 100644 --- a/src/test/kotlin/com/mjucow/eatda/presentation/store/StoreControllerMvcTest.kt +++ b/src/test/kotlin/com/mjucow/eatda/presentation/store/StoreControllerMvcTest.kt @@ -3,11 +3,17 @@ package com.mjucow.eatda.presentation.store import com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper import com.epages.restdocs.apispec.ResourceDocumentation import com.epages.restdocs.apispec.ResourceSnippetParametersBuilder +import com.mjucow.eatda.domain.store.entity.objectmother.MenuMother import com.mjucow.eatda.domain.store.entity.objectmother.StoreMother +import com.mjucow.eatda.domain.store.service.command.MenuCommandService import com.mjucow.eatda.domain.store.service.command.StoreCommandService +import com.mjucow.eatda.domain.store.service.command.dto.MenuCreateCommand import com.mjucow.eatda.domain.store.service.command.dto.StoreCreateCommand import com.mjucow.eatda.domain.store.service.command.dto.StoreUpdateCommand +import com.mjucow.eatda.domain.store.service.query.MenuDto +import com.mjucow.eatda.domain.store.service.query.MenuQueryService import com.mjucow.eatda.domain.store.service.query.StoreQueryService +import com.mjucow.eatda.domain.store.service.query.dto.MenuList import com.mjucow.eatda.domain.store.service.query.dto.StoreDetailDto import com.mjucow.eatda.domain.store.service.query.dto.StoreDto import com.mjucow.eatda.presentation.AbstractMockMvcTest @@ -33,6 +39,12 @@ class StoreControllerMvcTest : AbstractMockMvcTest() { @MockkBean(relaxUnitFun = true) lateinit var storeCommandService: StoreCommandService + @MockkBean(relaxUnitFun = true) + lateinit var menuQueryService: MenuQueryService + + @MockkBean(relaxUnitFun = true) + lateinit var menuCommandService: MenuCommandService + @Test fun create() { // given @@ -232,6 +244,79 @@ class StoreControllerMvcTest : AbstractMockMvcTest() { ) } + @Test + fun createMenu() { + // given + val entityId = 1L + every { menuCommandService.create(any(), any()) } returns entityId + val command = MenuCreateCommand( + name = MenuMother.NAME, + price = MenuMother.PRICE, + imageAddress = MenuMother.IMAGE_ADDRESS + ) + val content = objectMapper.writeValueAsString(command) + + // when & then + mockMvc.perform( + RestDocumentationRequestBuilders.post("$BASE_URI/{storeId}/menu", 1L) + .contentType(MediaType.APPLICATION_JSON) + .content(content) + ) + .andExpect(MockMvcResultMatchers.status().isCreated) + .andExpect(MockMvcResultMatchers.jsonPath("message", `is`(IsNull.nullValue()))) + .andExpect(MockMvcResultMatchers.jsonPath("body", `is`(entityId), Long::class.java)) + .andDo( + MockMvcRestDocumentationWrapper.document( + identifier = "menu-create", + resourceDetails = ResourceSnippetParametersBuilder() + .tag("Store") + .description("가게의 메뉴 생성") + .pathParameters( + ResourceDocumentation.parameterWithName("storeId").description("가게 식별자") + ) + .responseFields( + PayloadDocumentation.fieldWithPath("message").type(JsonFieldType.STRING).description("에러 메세지"), + PayloadDocumentation.fieldWithPath("body").type(JsonFieldType.NUMBER).description("가게 식별자") + ) + ) + ) + } + + @Test + fun findAllMenu() { + // given + val entityId = 1L + val dto = MenuDto.from(MenuMother.createWithId(id = entityId)) + every { menuQueryService.findAll(any()) } returns MenuList(listOf(dto)) + + // when & then + mockMvc.perform( + RestDocumentationRequestBuilders.get("$BASE_URI/{storeId}/menu", 1L) + ) + .andExpect(MockMvcResultMatchers.status().isOk) + .andExpect(MockMvcResultMatchers.jsonPath("message", `is`(IsNull.nullValue()))) + .andExpect(MockMvcResultMatchers.jsonPath("body").exists()) + .andDo( + MockMvcRestDocumentationWrapper.document( + identifier = "store-findAllMenu", + resourceDetails = ResourceSnippetParametersBuilder() + .tag("Store") + .description("가게의 메뉴 전체 조회") + .pathParameters( + ResourceDocumentation.parameterWithName("storeId").description("가게 식별자") + ) + .responseFields( + PayloadDocumentation.fieldWithPath("message").type(JsonFieldType.STRING).description("에러 메세지"), + PayloadDocumentation.fieldWithPath("body").type(JsonFieldType.ARRAY).description("조회된 가게 메뉴 리스트"), + PayloadDocumentation.fieldWithPath("body[0].id").type(JsonFieldType.NUMBER).description("메뉴 식별자"), + PayloadDocumentation.fieldWithPath("body[0].name").type(JsonFieldType.STRING).description("메뉴 이름"), + PayloadDocumentation.fieldWithPath("body[0].price").type(JsonFieldType.NUMBER).description("메뉴 가격"), + PayloadDocumentation.fieldWithPath("body[0].imageAddress").type(JsonFieldType.STRING).description("메뉴 이미지 주소") + ) + ) + ) + } + companion object { const val BASE_URI = "/api/v1/stores" } diff --git a/src/test/kotlin/com/mjucow/eatda/presentation/store/menu/MenuControllerMvcTest.kt b/src/test/kotlin/com/mjucow/eatda/presentation/store/menu/MenuControllerMvcTest.kt new file mode 100644 index 0000000..8772084 --- /dev/null +++ b/src/test/kotlin/com/mjucow/eatda/presentation/store/menu/MenuControllerMvcTest.kt @@ -0,0 +1,121 @@ +package com.mjucow.eatda.presentation.store.menu + +import com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper +import com.epages.restdocs.apispec.ResourceDocumentation +import com.epages.restdocs.apispec.ResourceSnippetParametersBuilder +import com.mjucow.eatda.domain.store.entity.objectmother.MenuMother +import com.mjucow.eatda.domain.store.service.command.MenuCommandService +import com.mjucow.eatda.domain.store.service.command.dto.MenuUpdateCommand +import com.mjucow.eatda.domain.store.service.query.MenuDto +import com.mjucow.eatda.domain.store.service.query.MenuQueryService +import com.mjucow.eatda.presentation.AbstractMockMvcTest +import com.ninjasquad.springmockk.MockkBean +import io.mockk.every +import org.junit.jupiter.api.Test +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest +import org.springframework.http.MediaType +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders +import org.springframework.restdocs.payload.JsonFieldType +import org.springframework.restdocs.payload.PayloadDocumentation +import org.springframework.test.web.servlet.result.MockMvcResultMatchers + +@WebMvcTest(MenuController::class) +class MenuControllerMvcTest : AbstractMockMvcTest() { + @MockkBean(relaxUnitFun = true) + lateinit var queryService: MenuQueryService + + @MockkBean(relaxUnitFun = true) + lateinit var commandService: MenuCommandService + + @Test + fun update() { + // given + val menuId = 1L + val command = MenuUpdateCommand(name = MenuMother.NAME, price = MenuMother.PRICE) + val content = objectMapper.writeValueAsString(command) + + // when & then + mockMvc.perform( + RestDocumentationRequestBuilders.patch("$BASE_URI/{menuId}", menuId) + .contentType(MediaType.APPLICATION_JSON) + .content(content) + ) + .andExpect(MockMvcResultMatchers.status().isOk) + .andDo( + MockMvcRestDocumentationWrapper.document( + identifier = "menu-update", + resourceDetails = ResourceSnippetParametersBuilder() + .tag("Menu") + .description("메뉴 수정") + .pathParameters( + ResourceDocumentation.parameterWithName("menuId").description("메뉴 식별자") + ) + .requestFields( + PayloadDocumentation.fieldWithPath("name").type(JsonFieldType.STRING).description("새로운 메뉴 이름"), + PayloadDocumentation.fieldWithPath("price").type(JsonFieldType.NUMBER).description("새로운 메뉴 가격"), + PayloadDocumentation.fieldWithPath("imageAddress").type(JsonFieldType.STRING).description("새로운 메뉴 이미지 주소").optional() + ) + ) + ) + } + + @Test + fun findById() { + // given + val menuId = 1L + val entity = MenuMother.createWithId(id = menuId) + every { queryService.findById(any()) } returns MenuDto.from(entity) + + // when & then + mockMvc.perform( + RestDocumentationRequestBuilders.get("$BASE_URI/{menuId}", menuId) + ) + .andExpect(MockMvcResultMatchers.status().isOk) + .andDo( + MockMvcRestDocumentationWrapper.document( + identifier = "menu-findById", + resourceDetails = ResourceSnippetParametersBuilder() + .tag("Menu") + .description("메뉴 조회") + .pathParameters( + ResourceDocumentation.parameterWithName("menuId").description("메뉴 식별자") + ) + .responseFields( + PayloadDocumentation.fieldWithPath("message").type(JsonFieldType.STRING).description("에러 메세지"), + PayloadDocumentation.fieldWithPath("body").type(JsonFieldType.OBJECT).description("메뉴"), + PayloadDocumentation.fieldWithPath("body.id").type(JsonFieldType.NUMBER).description("메뉴 식별자"), + PayloadDocumentation.fieldWithPath("body.name").type(JsonFieldType.STRING).description("메뉴 이름"), + PayloadDocumentation.fieldWithPath("body.price").type(JsonFieldType.NUMBER).description("메뉴 가격"), + PayloadDocumentation.fieldWithPath("body.imageAddress").type(JsonFieldType.STRING).description("메뉴 이미지 주소") + ) + ) + ) + } + + @Test + fun deleteById() { + // given + val menuId = 1L + + // when & then + mockMvc.perform( + RestDocumentationRequestBuilders.delete("$BASE_URI/{menuId}", menuId) + ) + .andExpect(MockMvcResultMatchers.status().isNoContent) + .andDo( + MockMvcRestDocumentationWrapper.document( + identifier = "menu-deleteById", + resourceDetails = ResourceSnippetParametersBuilder() + .tag("Menu") + .description("메뉴 삭제") + .pathParameters( + ResourceDocumentation.parameterWithName("menuId").description("메뉴 식별자") + ) + ) + ) + } + + companion object { + const val BASE_URI = "/api/v1/menu" + } +}