From d314d023e5464ff012195c8fb57f72723759dc28 Mon Sep 17 00:00:00 2001 From: Sim-km Date: Thu, 12 Sep 2024 16:30:07 +0900 Subject: [PATCH] =?UTF-8?q?ASAP-110=20feat:=20=ED=8E=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=B4=EB=9E=8C=20=EA=B0=80=EB=8A=A5=EC=97=AC=EB=B6=80=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20api=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../letter/exception/LetterException.kt | 31 ++++ .../port/in/VerifyLetterAccessibleUsecase.kt | 18 +++ .../port/out/SendLetterManagementPort.kt | 36 +++++ .../MemorySendLetterManagementAdapter.kt | 88 ++++++++++- .../letter/service/LetterCommandService.kt | 29 +++- .../service/LetterCommandServiceTest.kt | 90 ++++++++++- .../letter/LetterApplicationConfig.kt | 16 ++ .../application/letter/LetterMockManager.kt | 51 +++++++ .../asap/application/user/UserMockManager.kt | 3 +- .../asap/bootstrap/letter/api/LetterApi.kt | 29 +++- .../letter/controller/LetterController.kt | 19 ++- .../letter/dto/LetterVerifyRequest.kt | 1 - .../letter/controller/LetterControllerTest.kt | 55 +++++++ .../letter/LetterApiIntegrationTest.kt | 144 +++++++++++++++++- .../asap/domain/letter/entity/SendLetter.kt | 4 + 15 files changed, 599 insertions(+), 15 deletions(-) create mode 100644 Application-Module/src/main/kotlin/com/asap/application/letter/exception/LetterException.kt create mode 100644 Application-Module/src/main/kotlin/com/asap/application/letter/port/in/VerifyLetterAccessibleUsecase.kt create mode 100644 Application-Module/src/testFixtures/kotlin/com/asap/application/letter/LetterApplicationConfig.kt create mode 100644 Application-Module/src/testFixtures/kotlin/com/asap/application/letter/LetterMockManager.kt diff --git a/Application-Module/src/main/kotlin/com/asap/application/letter/exception/LetterException.kt b/Application-Module/src/main/kotlin/com/asap/application/letter/exception/LetterException.kt new file mode 100644 index 0000000..020b06b --- /dev/null +++ b/Application-Module/src/main/kotlin/com/asap/application/letter/exception/LetterException.kt @@ -0,0 +1,31 @@ +package com.asap.application.letter.exception + +import com.asap.common.exception.BusinessException + +sealed class LetterException( + codePrefix: String = CODE_PREFIX, + errorCode: Int, + httpStatus: Int = 400, + message: String = DEFAULT_ERROR_MESSAGE +): BusinessException(codePrefix, errorCode, httpStatus, message) { + + class SendLetterNotFoundException( + message: String = "존재하지 않는 편지입니다." + ): LetterException( + errorCode = 1, + message = message + ) + + class InvalidLetterAccessException( + message: String = "편지에 대한 접근 권한이 없습니다." + ): LetterException( + errorCode = 2, + message = message, + httpStatus = 403 + ) + + companion object { + const val CODE_PREFIX = "LETTER" + const val DEFAULT_ERROR_MESSAGE = "편지 관련된 예외가 발생했습니다." + } +} \ No newline at end of file diff --git a/Application-Module/src/main/kotlin/com/asap/application/letter/port/in/VerifyLetterAccessibleUsecase.kt b/Application-Module/src/main/kotlin/com/asap/application/letter/port/in/VerifyLetterAccessibleUsecase.kt new file mode 100644 index 0000000..d157846 --- /dev/null +++ b/Application-Module/src/main/kotlin/com/asap/application/letter/port/in/VerifyLetterAccessibleUsecase.kt @@ -0,0 +1,18 @@ +package com.asap.application.letter.port.`in` + +interface VerifyLetterAccessibleUsecase { + + fun verify( + command: Command + ): Response + + + data class Command( + val letterCode: String, + val userId: String + ) + + data class Response( + val letterId: String + ) +} \ No newline at end of file diff --git a/Application-Module/src/main/kotlin/com/asap/application/letter/port/out/SendLetterManagementPort.kt b/Application-Module/src/main/kotlin/com/asap/application/letter/port/out/SendLetterManagementPort.kt index a805bda..c59a614 100644 --- a/Application-Module/src/main/kotlin/com/asap/application/letter/port/out/SendLetterManagementPort.kt +++ b/Application-Module/src/main/kotlin/com/asap/application/letter/port/out/SendLetterManagementPort.kt @@ -1,5 +1,7 @@ package com.asap.application.letter.port.out +import com.asap.application.letter.exception.LetterException +import com.asap.domain.common.DomainId import com.asap.domain.letter.entity.SendLetter interface SendLetterManagementPort { @@ -7,4 +9,38 @@ interface SendLetterManagementPort { fun save( sendLetter: SendLetter ) + + @Throws( + LetterException.SendLetterNotFoundException::class + ) + fun getLetterNotNull( + letterId: DomainId + ): SendLetter + + @Throws( + LetterException.SendLetterNotFoundException::class + ) + fun getLetterByCodeNotNull( + letterCode: String + ): SendLetter + + @Throws( + LetterException.SendLetterNotFoundException::class + ) + fun getExpiredLetterNotNull( + receiverId: DomainId, + letterCode: String + ): SendLetter + + + fun expireLetter( + receiverId: DomainId, + letterId: DomainId + ) + + fun verifiedLetter( + receiverId: DomainId, + letterCode: String + ): Boolean + } \ No newline at end of file diff --git a/Application-Module/src/main/kotlin/com/asap/application/letter/port/out/memory/MemorySendLetterManagementAdapter.kt b/Application-Module/src/main/kotlin/com/asap/application/letter/port/out/memory/MemorySendLetterManagementAdapter.kt index 6eeaac3..21b5adb 100644 --- a/Application-Module/src/main/kotlin/com/asap/application/letter/port/out/memory/MemorySendLetterManagementAdapter.kt +++ b/Application-Module/src/main/kotlin/com/asap/application/letter/port/out/memory/MemorySendLetterManagementAdapter.kt @@ -1,6 +1,8 @@ package com.asap.application.letter.port.out.memory +import com.asap.application.letter.exception.LetterException import com.asap.application.letter.port.out.SendLetterManagementPort +import com.asap.domain.common.DomainId import com.asap.domain.letter.entity.SendLetter import org.springframework.context.annotation.Primary import org.springframework.stereotype.Component @@ -9,11 +11,91 @@ import org.springframework.stereotype.Component @Primary class MemorySendLetterManagementAdapter( -): SendLetterManagementPort { - private val sendLetters = mutableListOf() +) : SendLetterManagementPort { + private val sendLetters = mutableListOf() override fun save(sendLetter: SendLetter) { - sendLetters.add(sendLetter) + sendLetters.add(SendLetterEntity.fromSendLetter(sendLetter)) + } + + override fun getLetterNotNull(letterId: DomainId): SendLetter { + return matchingNotExpired { this.id == letterId.value }?.toSendLetter() + ?: throw LetterException.SendLetterNotFoundException() + } + + override fun getLetterByCodeNotNull(letterCode: String): SendLetter { + return matchingNotExpired { this.letterCode == letterCode }?.toSendLetter() + ?: throw LetterException.SendLetterNotFoundException() + } + + override fun getExpiredLetterNotNull(receiverId: DomainId, letterCode: String): SendLetter { + return matching { (this.letterCode == letterCode) and (this.receiverId == receiverId.value) }?.toSendLetter() + ?: throw LetterException.SendLetterNotFoundException() + } + + override fun expireLetter(receiverId: DomainId, letterId: DomainId) { + val sendLetter = matchingNotExpired { this.id == letterId.value } + ?: throw LetterException.SendLetterNotFoundException() + sendLetter.expire(receiverId.value) + } + + override fun verifiedLetter(receiverId: DomainId, letterCode: String): Boolean { + return matching { (this.letterCode == letterCode) and (this.receiverId == receiverId.value) }?.isExpired + ?: false + } + + private fun matchingNotExpired(query: SendLetterEntity.() -> Boolean): SendLetterEntity? { + return matching { this.isExpired.not() and query() } + } + + private fun matching(query: SendLetterEntity.() -> Boolean): SendLetterEntity? { + return sendLetters.find { query(it) } + } + + + data class SendLetterEntity( + val id: String, + val receiverName: String, + val content: String, + val images: List, + val templateType: Int, + val senderId: String, + val letterCode: String, + var isExpired: Boolean, + var receiverId: String + ) { + fun toSendLetter(): SendLetter { + return SendLetter( + id = DomainId(id), + receiverName = receiverName, + content = content, + images = images, + templateType = templateType, + senderId = DomainId(senderId), + letterCode = letterCode + ) + } + + companion object { + fun fromSendLetter(sendLetter: SendLetter): SendLetterEntity { + return SendLetterEntity( + id = sendLetter.id.value, + receiverName = sendLetter.receiverName, + content = sendLetter.content, + images = sendLetter.images, + templateType = sendLetter.templateType, + senderId = sendLetter.senderId.value, + letterCode = sendLetter.letterCode, + isExpired = false, + receiverId = "" + ) + } + } + + fun expire(receiverId: String) { + this.isExpired = true + this.receiverId = receiverId + } } } \ No newline at end of file diff --git a/Application-Module/src/main/kotlin/com/asap/application/letter/service/LetterCommandService.kt b/Application-Module/src/main/kotlin/com/asap/application/letter/service/LetterCommandService.kt index 3a40d0a..326d917 100644 --- a/Application-Module/src/main/kotlin/com/asap/application/letter/service/LetterCommandService.kt +++ b/Application-Module/src/main/kotlin/com/asap/application/letter/service/LetterCommandService.kt @@ -1,7 +1,10 @@ package com.asap.application.letter.service +import com.asap.application.letter.exception.LetterException import com.asap.application.letter.port.`in`.SendLetterUsecase +import com.asap.application.letter.port.`in`.VerifyLetterAccessibleUsecase import com.asap.application.letter.port.out.SendLetterManagementPort +import com.asap.application.user.port.out.UserManagementPort import com.asap.domain.common.DomainId import com.asap.domain.letter.entity.SendLetter import com.asap.domain.letter.service.LetterCodeGenerator @@ -9,8 +12,9 @@ import org.springframework.stereotype.Service @Service class LetterCommandService( - private val sendLetterManagementPort: SendLetterManagementPort -) : SendLetterUsecase { + private val sendLetterManagementPort: SendLetterManagementPort, + private val userManagementPort: UserManagementPort +) : SendLetterUsecase, VerifyLetterAccessibleUsecase { private val letterCodeGenerator = LetterCodeGenerator() @@ -35,4 +39,25 @@ class LetterCommandService( return SendLetterUsecase.Response(letterCode = sendLetter.letterCode) } + + override fun verify(command: VerifyLetterAccessibleUsecase.Command): VerifyLetterAccessibleUsecase.Response { + if (sendLetterManagementPort.verifiedLetter(DomainId(command.userId), command.letterCode)) { + val sendLetter = sendLetterManagementPort.getExpiredLetterNotNull( + receiverId = DomainId(command.userId), + command.letterCode + ) + return VerifyLetterAccessibleUsecase.Response(letterId = sendLetter.id.value) + } + + val sendLetter = sendLetterManagementPort.getLetterByCodeNotNull(command.letterCode) + sendLetter.isSameReceiver { + userManagementPort.getUserNotNull(DomainId(command.userId)).username + }.takeIf { it }?.let { + sendLetterManagementPort.expireLetter( + receiverId = DomainId(command.userId), + letterId = sendLetter.id + ) + return VerifyLetterAccessibleUsecase.Response(letterId = sendLetter.id.value) + } ?: throw LetterException.InvalidLetterAccessException() + } } \ No newline at end of file diff --git a/Application-Module/src/test/kotlin/com/asap/application/letter/service/LetterCommandServiceTest.kt b/Application-Module/src/test/kotlin/com/asap/application/letter/service/LetterCommandServiceTest.kt index 01ee23e..a10ab6e 100644 --- a/Application-Module/src/test/kotlin/com/asap/application/letter/service/LetterCommandServiceTest.kt +++ b/Application-Module/src/test/kotlin/com/asap/application/letter/service/LetterCommandServiceTest.kt @@ -1,18 +1,32 @@ package com.asap.application.letter.service +import com.asap.application.letter.exception.LetterException import com.asap.application.letter.port.`in`.SendLetterUsecase +import com.asap.application.letter.port.`in`.VerifyLetterAccessibleUsecase import com.asap.application.letter.port.out.SendLetterManagementPort +import com.asap.application.user.port.out.UserManagementPort +import com.asap.domain.common.DomainId +import com.asap.domain.letter.entity.SendLetter +import com.asap.domain.user.entity.User +import com.asap.domain.user.vo.UserPermission +import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.nulls.shouldNotBeNull +import io.mockk.every import io.mockk.mockk import io.mockk.verify +import java.time.LocalDate class LetterCommandServiceTest:BehaviorSpec({ val mockSendLetterManagementPort = mockk(relaxed=true) + val mockUserManagementPort = mockk(relaxed=true) - val letterCommandService = LetterCommandService(mockSendLetterManagementPort) + val letterCommandService = LetterCommandService( + mockSendLetterManagementPort, + mockUserManagementPort + ) @@ -36,5 +50,79 @@ class LetterCommandServiceTest:BehaviorSpec({ } } } + + given("편지 검증 시에"){ + val letterCode = "letter-code" + val verifyCommand = VerifyLetterAccessibleUsecase.Command( + letterCode = letterCode, + userId = "user-id" + ) + val sendLetter = SendLetter( + receiverName = "receiver-name", + content = "content", + images = emptyList(), + templateType = 1, + senderId = mockk(), + letterCode = letterCode + ) + val mockUser = User( + id = DomainId("user-id"), + username = "receiver-name", + profileImage = "profile-image", + permission = UserPermission(true,true,true), + birthday = LocalDate.now() + ) + every { mockSendLetterManagementPort.verifiedLetter(any(), any()) } returns false + every { mockSendLetterManagementPort.getLetterByCodeNotNull(any()) } returns sendLetter + every { mockUserManagementPort.getUserNotNull(any()) } returns mockUser + `when`("이전에 열람한 적이 없고, 수신자 이름과 같다면"){ + val response = letterCommandService.verify(verifyCommand) + then("편지 코드가 검증되고, 편지 ID가 반환되어야 한다"){ + response.letterId shouldNotBeNull { + this.isNotBlank() + this.isNotEmpty() + } + verify { mockSendLetterManagementPort.expireLetter(mockUser.id, sendLetter.id) } + } + } + + every { mockSendLetterManagementPort.getLetterByCodeNotNull(any()) } throws LetterException.SendLetterNotFoundException() + `when`("코드가 존재하지 않는다면"){ + then("예외가 발생해야 한다"){ + shouldThrow { + letterCommandService.verify(verifyCommand) + } + } + } + + val anotherUser = User( + id = DomainId("user-id"), + username = "another-name", + profileImage = "profile-image", + permission = UserPermission(true,true,true), + birthday = LocalDate.now() + ) + every { mockSendLetterManagementPort.getLetterByCodeNotNull(any()) } returns sendLetter + every { mockUserManagementPort.getUserNotNull(any()) } returns anotherUser + `when`("편지의 수신자 이름과 사용자 이름이 다르면"){ + then("예외가 발생해야 한다"){ + shouldThrow { + letterCommandService.verify(verifyCommand) + } + } + } + + every { mockSendLetterManagementPort.verifiedLetter(any(), any()) } returns true + every { mockSendLetterManagementPort.getExpiredLetterNotNull(any(), any()) } returns sendLetter + `when`("이전에 열람한 적이 있는 사용자라면"){ + val response = letterCommandService.verify(verifyCommand) + then("편지 코드가 검증되고, 편지 ID가 반환되어야 한다"){ + response.letterId shouldNotBeNull { + this.isNotBlank() + this.isNotEmpty() + } + } + } + } }) { } \ No newline at end of file diff --git a/Application-Module/src/testFixtures/kotlin/com/asap/application/letter/LetterApplicationConfig.kt b/Application-Module/src/testFixtures/kotlin/com/asap/application/letter/LetterApplicationConfig.kt new file mode 100644 index 0000000..975e7b2 --- /dev/null +++ b/Application-Module/src/testFixtures/kotlin/com/asap/application/letter/LetterApplicationConfig.kt @@ -0,0 +1,16 @@ +package com.asap.application.letter + +import com.asap.application.letter.port.out.SendLetterManagementPort +import org.springframework.boot.test.context.TestConfiguration +import org.springframework.context.annotation.Bean + +@TestConfiguration +class LetterApplicationConfig( + private val sendLetterManagementPort: SendLetterManagementPort +) { + + @Bean + fun letterMockManager(): LetterMockManager { + return LetterMockManager(sendLetterManagementPort) + } +} \ No newline at end of file diff --git a/Application-Module/src/testFixtures/kotlin/com/asap/application/letter/LetterMockManager.kt b/Application-Module/src/testFixtures/kotlin/com/asap/application/letter/LetterMockManager.kt new file mode 100644 index 0000000..e7e6fc3 --- /dev/null +++ b/Application-Module/src/testFixtures/kotlin/com/asap/application/letter/LetterMockManager.kt @@ -0,0 +1,51 @@ +package com.asap.application.letter + +import com.asap.application.letter.port.out.SendLetterManagementPort +import com.asap.domain.common.DomainId +import com.asap.domain.letter.entity.SendLetter +import com.asap.domain.letter.service.LetterCodeGenerator + +class LetterMockManager( + private val sendLetterManagementPort: SendLetterManagementPort +) { + + private val letterCodeGenerator = LetterCodeGenerator() + + fun generateMockSendLetter( + receiverName: String, + ): String{ + val sendLetter = SendLetter( + receiverName = receiverName, + content = "content", + images = listOf("image1", "image2"), + templateType = 1, + senderId = DomainId.generate(), + letterCode = letterCodeGenerator.generateCode( + content = "content", + ownerId = DomainId.generate().value + ) + ) + sendLetterManagementPort.save(sendLetter) + return sendLetter.letterCode + } + + fun generateMockExpiredSendLetter( + receiverName: String, + receiverId: String, + ): String{ + val sendLetter = SendLetter( + receiverName = receiverName, + content = "content", + images = listOf("image1", "image2"), + templateType = 1, + senderId = DomainId.generate(), + letterCode = letterCodeGenerator.generateCode( + content = "content", + ownerId = DomainId.generate().value + ) + ) + sendLetterManagementPort.save(sendLetter) + sendLetterManagementPort.expireLetter(DomainId(receiverId), sendLetter.id) + return sendLetter.letterCode + } +} \ No newline at end of file diff --git a/Application-Module/src/testFixtures/kotlin/com/asap/application/user/UserMockManager.kt b/Application-Module/src/testFixtures/kotlin/com/asap/application/user/UserMockManager.kt index f67214b..5e6fc63 100644 --- a/Application-Module/src/testFixtures/kotlin/com/asap/application/user/UserMockManager.kt +++ b/Application-Module/src/testFixtures/kotlin/com/asap/application/user/UserMockManager.kt @@ -28,11 +28,12 @@ class UserMockManager( fun settingUser( userId: String = UUID.randomUUID().toString(), + username: String = "nickname", ): String{ val user = userManagementPort.saveUser( User( id = DomainId(userId), - username = "nickname", + username = username, profileImage = "profileImage", permission = UserPermission(true, true, true), birthday = LocalDate.now() diff --git a/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/letter/api/LetterApi.kt b/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/letter/api/LetterApi.kt index 0980309..40f750f 100644 --- a/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/letter/api/LetterApi.kt +++ b/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/letter/api/LetterApi.kt @@ -17,8 +17,35 @@ interface LetterApi { @Operation(summary = "편지 열람 가능 검증") @PutMapping("/verify") + @ApiResponses( + value = [ + ApiResponse( + responseCode = "200", + description = "편지 열람 가능 검증 성공", + content = [ + Content( + mediaType = "application/json", + schema = Schema(implementation = LetterVerifyResponse::class) + ) + ] + ), + ApiResponse( + responseCode = "400", + description = """ + LETTER-001 :편지가 존재하지 않음 + """ + ), + ApiResponse( + responseCode = "403", + description = """ + LETTER-002 :해당 사용자는 편지 열람 권한이 없음 + """ + ) + ] + ) fun verifyLetter( - @RequestBody request: LetterVerifyRequest + @RequestBody request: LetterVerifyRequest, + @AccessUser userId: String ): LetterVerifyResponse diff --git a/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/letter/controller/LetterController.kt b/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/letter/controller/LetterController.kt index 2f7d001..02bead9 100644 --- a/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/letter/controller/LetterController.kt +++ b/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/letter/controller/LetterController.kt @@ -1,16 +1,29 @@ package com.asap.bootstrap.letter.controller import com.asap.application.letter.port.`in`.SendLetterUsecase +import com.asap.application.letter.port.`in`.VerifyLetterAccessibleUsecase import com.asap.bootstrap.letter.api.LetterApi import com.asap.bootstrap.letter.dto.* import org.springframework.web.bind.annotation.RestController @RestController class LetterController( - private val sendLetterUsecase: SendLetterUsecase + private val sendLetterUsecase: SendLetterUsecase, + private val verifyLetterAccessibleUsecase: VerifyLetterAccessibleUsecase ) : LetterApi { - override fun verifyLetter(request: LetterVerifyRequest): LetterVerifyResponse { - TODO("Not yet implemented") + override fun verifyLetter( + request: LetterVerifyRequest, + userId: String + ): LetterVerifyResponse { + val response = verifyLetterAccessibleUsecase.verify( + VerifyLetterAccessibleUsecase.Command( + letterCode = request.letterCode, + userId = userId + ) + ) + return LetterVerifyResponse( + letterId = response.letterId + ) } override fun getReceiveLetter(letterId: String): ReceiveLetterInfoResponse { diff --git a/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/letter/dto/LetterVerifyRequest.kt b/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/letter/dto/LetterVerifyRequest.kt index 2fba834..9097954 100644 --- a/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/letter/dto/LetterVerifyRequest.kt +++ b/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/letter/dto/LetterVerifyRequest.kt @@ -3,6 +3,5 @@ package com.asap.bootstrap.letter.dto data class LetterVerifyRequest( val letterCode: String, - val receiverName: String ) { } \ No newline at end of file diff --git a/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/acceptance/letter/controller/LetterControllerTest.kt b/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/acceptance/letter/controller/LetterControllerTest.kt index fb9e9ff..bfca530 100644 --- a/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/acceptance/letter/controller/LetterControllerTest.kt +++ b/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/acceptance/letter/controller/LetterControllerTest.kt @@ -1,16 +1,59 @@ package com.asap.bootstrap.acceptance.letter.controller +import com.asap.application.letter.port.`in`.SendLetterUsecase +import com.asap.application.letter.port.`in`.VerifyLetterAccessibleUsecase import com.asap.bootstrap.AcceptanceSupporter import com.asap.bootstrap.letter.controller.LetterController +import com.asap.bootstrap.letter.dto.LetterVerifyRequest import com.asap.bootstrap.letter.dto.SendLetterRequest import org.junit.jupiter.api.Test +import org.mockito.BDDMockito import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest +import org.springframework.boot.test.mock.mockito.MockBean import org.springframework.http.MediaType import org.springframework.test.web.servlet.post +import org.springframework.test.web.servlet.put @WebMvcTest(LetterController::class) class LetterControllerTest: AcceptanceSupporter() { + @MockBean + lateinit var verifyLetterAccessibleUsecase: VerifyLetterAccessibleUsecase + + @MockBean + lateinit var sendLetterUsecase: SendLetterUsecase + + + @Test + fun verifyLetter() { + //given + val accessToken = testJwtDataGenerator.generateAccessToken() + val request = LetterVerifyRequest("letterCode") + BDDMockito.given( + verifyLetterAccessibleUsecase.verify( + VerifyLetterAccessibleUsecase.Command( + letterCode = request.letterCode, + userId = "userId" + ) + ) + ).willReturn(VerifyLetterAccessibleUsecase.Response("letterId")) + //when + val response = mockMvc.put("/api/v1/letters/verify") { + contentType = MediaType.APPLICATION_JSON + content = objectMapper.writeValueAsString(request) + header("Authorization", "Bearer $accessToken") + } + //then + response.andExpect { + status { isOk() } + jsonPath("$.letterId") { + exists() + isString() + isNotEmpty() + } + } + } + @Test fun sendLetter(){ //given @@ -22,6 +65,18 @@ class LetterControllerTest: AcceptanceSupporter() { draftId = "draftId" ) val accessToken = testJwtDataGenerator.generateAccessToken() + BDDMockito.given( + sendLetterUsecase.send( + SendLetterUsecase.Command( + userId = "userId", + receiverName = request.receiverName, + content = request.content, + images = request.images, + templateType = request.templateType, + draftId = request.draftId + ) + ) + ).willReturn(SendLetterUsecase.Response("letterCode")) //when val response = mockMvc.post("/api/v1/letters/send") { contentType = MediaType.APPLICATION_JSON diff --git a/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/integration/letter/LetterApiIntegrationTest.kt b/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/integration/letter/LetterApiIntegrationTest.kt index 26a7170..e460294 100644 --- a/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/integration/letter/LetterApiIntegrationTest.kt +++ b/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/integration/letter/LetterApiIntegrationTest.kt @@ -1,20 +1,158 @@ package com.asap.bootstrap.integration.letter +import com.asap.application.letter.LetterMockManager import com.asap.application.user.UserMockManager import com.asap.bootstrap.IntegrationSupporter +import com.asap.bootstrap.letter.dto.LetterVerifyRequest import com.asap.bootstrap.letter.dto.SendLetterRequest +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.http.MediaType import org.springframework.test.web.servlet.post +import org.springframework.test.web.servlet.put -class LetterApiIntegrationTest: IntegrationSupporter() { +class LetterApiIntegrationTest : IntegrationSupporter() { @Autowired lateinit var userMockManager: UserMockManager + @Autowired + lateinit var letterMockManager: LetterMockManager + + + @Nested + inner class LetterVerify { + @Test + @DisplayName("편지 열람 가능 검증 성공") + fun verifyLetter() { + //given + val userId = userMockManager.settingUser(username = "username") + val accessToken = testJwtDataGenerator.generateAccessToken(userId) + userMockManager.settingToken(accessToken) + val letterCode = letterMockManager.generateMockSendLetter("username") + val request = LetterVerifyRequest(letterCode) + //when + val response = mockMvc.put("/api/v1/letters/verify") { + contentType = MediaType.APPLICATION_JSON + content = objectMapper.writeValueAsString(request) + header("Authorization", "Bearer $accessToken") + } + //then + response.andExpect { + status { isOk() } + jsonPath("$.letterId") { + exists() + isString() + isNotEmpty() + } + } + } + + @Test + @DisplayName("편지가 존재하지 않음") + fun verifyLetter_With_InvalidLetterCode() { + //given + val userId = userMockManager.settingUser(username = "username") + val accessToken = testJwtDataGenerator.generateAccessToken(userId) + userMockManager.settingToken(accessToken) + val request = LetterVerifyRequest("invalidLetterCode") + //when + val response = mockMvc.put("/api/v1/letters/verify") { + contentType = MediaType.APPLICATION_JSON + content = objectMapper.writeValueAsString(request) + header("Authorization", "Bearer $accessToken") + } + //then + response.andExpect { + status { isBadRequest() } + jsonPath("$.code") { + value("LETTER-001") + } + } + } + + @Test + @DisplayName("해당 사용자는 편지 열람 권한이 없음") + fun verifyLetter_With_InvalidUser() { + //given + val userId = userMockManager.settingUser(username = "username") + val accessToken = testJwtDataGenerator.generateAccessToken(userId) + userMockManager.settingToken(accessToken) + val letterCode = letterMockManager.generateMockSendLetter("otherUsername") + val request = LetterVerifyRequest(letterCode) + //when + val response = mockMvc.put("/api/v1/letters/verify") { + contentType = MediaType.APPLICATION_JSON + content = objectMapper.writeValueAsString(request) + header("Authorization", "Bearer $accessToken") + } + //then + response.andExpect { + status { isForbidden() } + jsonPath("$.code") { + value("LETTER-002") + } + } + } + + @Test + @DisplayName("다른 사용자가 이미 연람함 편지면 열람 불가") + fun verifyLetter_With_ExpiredLetter() { + //given + val userId = userMockManager.settingUser(username = "username") + val accessToken = testJwtDataGenerator.generateAccessToken(userId) + userMockManager.settingToken(accessToken) + val letterCode = letterMockManager.generateMockExpiredSendLetter("username", "otherUserId") + val request = LetterVerifyRequest(letterCode) + //when + val response = mockMvc.put("/api/v1/letters/verify") { + contentType = MediaType.APPLICATION_JSON + content = objectMapper.writeValueAsString(request) + header("Authorization", "Bearer $accessToken") + } + //then + response.andExpect { + status { isBadRequest() } + jsonPath("$.code") { + value("LETTER-001") + } + } + } + + @Test + @DisplayName("이전에 열람한 적이 있다면 다시 열람 가능") + fun verifyLetter_With_ExpiredLetter_ReAccessible() { + //given + val userId = userMockManager.settingUser(username = "username") + val accessToken = testJwtDataGenerator.generateAccessToken(userId) + userMockManager.settingToken(accessToken) + val letterCode = letterMockManager.generateMockExpiredSendLetter("username", userId) + val request = LetterVerifyRequest(letterCode) + //when + val response = mockMvc.put("/api/v1/letters/verify") { + contentType = MediaType.APPLICATION_JSON + content = objectMapper.writeValueAsString(request) + header("Authorization", "Bearer $accessToken") + } + //then + response.andExpect { + status { isOk() } + jsonPath("$.letterId") { + exists() + isString() + isNotEmpty() + } + } + } + + + } + + @Test - fun sendLetter(){ + fun sendLetter() { //given val request = SendLetterRequest( receiverName = "receiverName", @@ -30,7 +168,7 @@ class LetterApiIntegrationTest: IntegrationSupporter() { val response = mockMvc.post("/api/v1/letters/send") { contentType = MediaType.APPLICATION_JSON content = objectMapper.writeValueAsString(request) - header("Authorization","Bearer $accessToken") + header("Authorization", "Bearer $accessToken") } //then response.andExpect { diff --git a/Domain-Module/src/main/kotlin/com/asap/domain/letter/entity/SendLetter.kt b/Domain-Module/src/main/kotlin/com/asap/domain/letter/entity/SendLetter.kt index 4329079..d9b2182 100644 --- a/Domain-Module/src/main/kotlin/com/asap/domain/letter/entity/SendLetter.kt +++ b/Domain-Module/src/main/kotlin/com/asap/domain/letter/entity/SendLetter.kt @@ -11,4 +11,8 @@ data class SendLetter( val senderId: DomainId, val letterCode: String ) { + + fun isSameReceiver(receiverName: () -> String): Boolean { + return this.receiverName == receiverName() + } } \ No newline at end of file