From f414c25a08c676c42ed448502da20b952c1badbf Mon Sep 17 00:00:00 2001 From: Sim-km Date: Sun, 8 Jun 2025 13:56:29 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=EC=9D=B5=EB=AA=85=20=ED=8E=B8=EC=A7=80=20?= =?UTF-8?q?=EC=A0=84=EC=86=A1=20=EC=8B=9C=20=EB=B0=9C=EC=8B=A0=EC=9E=90=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EC=A7=80=EC=9B=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `AnonymousSendLetterRequest` 및 `AnonymousCommand`에 `senderName` 필드 추가. - 발신자 이름이 없는 경우 기본값 "Anonymous"를 사용하도록 `LetterCommandService` 수정. - `SendLetter` 엔터티와 관련 매퍼 및 JPA 엔터티, DB 테이블에 `senderName` 필드 추가. - `V1_21__add_sender_name_to_send_letter.sql` 마이그레이션 파일 작성. --- .gitignore | 3 +++ .../application/letter/port/in/SendLetterUsecase.kt | 1 + .../letter/service/LetterCommandService.kt | 12 +++++++++--- .../web/letter/controller/LetterController.kt | 1 + .../web/letter/dto/AnonymousSendLetterRequest.kt | 1 + .../com/asap/domain/letter/entity/SendLetter.kt | 4 ++++ .../asap/persistence/jpa/letter/SendLetterMapper.kt | 2 ++ .../jpa/letter/entity/SendLetterEntity.kt | 4 ++++ .../db/V1_21__add_sender_name_to_send_letter.sql | 1 + 9 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 Infrastructure-Module/Persistence/src/main/resources/db/V1_21__add_sender_name_to_send_letter.sql diff --git a/.gitignore b/.gitignore index 5a979af6..ea09a5f2 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,6 @@ out/ ### Kotlin ### .kotlin + +### claude ### +CLAUDE.md diff --git a/Application-Module/src/main/kotlin/com/asap/application/letter/port/in/SendLetterUsecase.kt b/Application-Module/src/main/kotlin/com/asap/application/letter/port/in/SendLetterUsecase.kt index ede1b7dd..ef444e63 100644 --- a/Application-Module/src/main/kotlin/com/asap/application/letter/port/in/SendLetterUsecase.kt +++ b/Application-Module/src/main/kotlin/com/asap/application/letter/port/in/SendLetterUsecase.kt @@ -15,6 +15,7 @@ interface SendLetterUsecase { ) data class AnonymousCommand( + val senderName: String? = null, val receiverName: String, val content: String, val images: List, 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 16646c26..61a912fd 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 @@ -70,6 +70,7 @@ class LetterCommandService( letterCodeGenerator.generateCode( content = command.content, ), + senderName = command.senderName ?: ANONYMOUS_SENDER_NAME, ) sendLetterManagementPort.save(sendLetter) @@ -111,9 +112,10 @@ class LetterCommandService( SenderInfo( senderId = sendLetter.senderId, senderName = - sendLetter.senderId - ?.let { userManagementPort.getUserNotNull(it).username } - .orEmpty(), + sendLetter.senderName + ?: sendLetter.senderId + ?.let { userManagementPort.getUserNotNull(it).username } + .orEmpty(), ), receiver = ReceiverInfo( @@ -273,4 +275,8 @@ class LetterCommandService( ) spaceLetterManagementPort.save(spaceLetter) } + + companion object { + private const val ANONYMOUS_SENDER_NAME = "Anonymous" + } } diff --git a/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/web/letter/controller/LetterController.kt b/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/web/letter/controller/LetterController.kt index 3496ba7e..7b47e77b 100644 --- a/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/web/letter/controller/LetterController.kt +++ b/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/web/letter/controller/LetterController.kt @@ -270,6 +270,7 @@ class LetterController( val response = sendLetterUsecase.sendAnonymous( SendLetterUsecase.AnonymousCommand( + senderName = request.senderName, receiverName = request.receiverName, content = request.content, images = request.images, diff --git a/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/web/letter/dto/AnonymousSendLetterRequest.kt b/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/web/letter/dto/AnonymousSendLetterRequest.kt index 147bf262..180f69b9 100644 --- a/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/web/letter/dto/AnonymousSendLetterRequest.kt +++ b/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/web/letter/dto/AnonymousSendLetterRequest.kt @@ -1,6 +1,7 @@ package com.asap.bootstrap.web.letter.dto data class AnonymousSendLetterRequest( + val senderName: String? = null, val receiverName: String, val content: String, val images: List, 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 0fdf191e..48519493 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 @@ -13,6 +13,7 @@ class SendLetter( id: DomainId, val content: LetterContent, var senderId: DomainId?, + var senderName: String?, var receiverName: String, var letterCode: String?, var status: LetterStatus, @@ -37,6 +38,7 @@ class SendLetter( id = DomainId.generate(), content = content, senderId = senderId, + senderName = null, receiverName = receiverName, letterCode = letterCode, status = status, @@ -51,6 +53,7 @@ class SendLetter( content: LetterContent, receiverName: String, letterCode: String?, + senderName: String, status: LetterStatus = LetterStatus.SENDING, receiverId: DomainId? = null, createdAt: LocalDateTime = LocalDateTime.now(), @@ -60,6 +63,7 @@ class SendLetter( id = DomainId.generate(), content = content, senderId = null, + senderName = senderName, receiverName = receiverName, letterCode = letterCode, status = status, diff --git a/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/letter/SendLetterMapper.kt b/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/letter/SendLetterMapper.kt index 0fa655bb..06adc521 100644 --- a/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/letter/SendLetterMapper.kt +++ b/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/letter/SendLetterMapper.kt @@ -18,6 +18,7 @@ object SendLetterMapper { receiverName = sendLetterEntity.receiverName, letterCode = sendLetterEntity.letterCode ?: "", senderId = sendLetterEntity.senderId?.let { DomainId(it) }, + senderName = sendLetterEntity.senderName, receiverId = sendLetterEntity.receiverId?.let { DomainId(it) }, status = sendLetterEntity.letterStatus, createdAt = sendLetterEntity.createdAt, @@ -32,6 +33,7 @@ object SendLetterMapper { templateType = sendLetter.content.templateType, receiverName = sendLetter.receiverName, senderId = sendLetter.senderId?.value, + senderName = sendLetter.senderName, letterCode = sendLetter.letterCode, receiverId = sendLetter.receiverId?.value, letterStatus = sendLetter.status, diff --git a/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/letter/entity/SendLetterEntity.kt b/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/letter/entity/SendLetterEntity.kt index 8fe740ea..7a70ee5e 100644 --- a/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/letter/entity/SendLetterEntity.kt +++ b/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/letter/entity/SendLetterEntity.kt @@ -27,6 +27,7 @@ class SendLetterEntity( images: List, templateType: Int, senderId: String?, + senderName: String?, letterCode: String?, receiverId: String?, letterStatus: LetterStatus, @@ -52,6 +53,9 @@ class SendLetterEntity( @Column(name = "sender_id") var senderId: String? = senderId + @Column(name = "sender_name") + var senderName: String? = senderName + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn( name = "sender_id", diff --git a/Infrastructure-Module/Persistence/src/main/resources/db/V1_21__add_sender_name_to_send_letter.sql b/Infrastructure-Module/Persistence/src/main/resources/db/V1_21__add_sender_name_to_send_letter.sql new file mode 100644 index 00000000..9afea5f4 --- /dev/null +++ b/Infrastructure-Module/Persistence/src/main/resources/db/V1_21__add_sender_name_to_send_letter.sql @@ -0,0 +1 @@ +ALTER TABLE send_letter ADD COLUMN sender_name VARCHAR(255) NULL; \ No newline at end of file From d225ebdd14f283c9483ebf9abc2d6b25e7cd7e2d Mon Sep 17 00:00:00 2001 From: Sim-km Date: Sun, 8 Jun 2025 14:07:41 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=EC=9D=B5=EB=AA=85=20=ED=8E=B8=EC=A7=80=20?= =?UTF-8?q?=EC=A0=84=EC=86=A1=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BC=80?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EB=B3=B4=EC=99=84=20=EB=B0=8F=20=EB=B0=9C?= =?UTF-8?q?=EC=8B=A0=EC=9E=90=20=EC=9D=B4=EB=A6=84=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `AnonymousSendLetterRequest` 및 테스트에 발신자 이름 지원 테스트 케이스 추가. - 발신자 이름이 null일 경우 기본값 "Anonymous"로 처리 확인. - `LetterCommandServiceTest`, `LetterApiIntegrationTest`, `LetterControllerTest`의 익명 편지 로직 관련 테스트 시나리오 상세화. - 중복 및 누락된 DTO import 최적화. --- .../service/LetterCommandServiceTest.kt | 47 +++++++ .../letter/controller/LetterControllerTest.kt | 80 ++++++++++++ .../letter/LetterApiIntegrationTest.kt | 116 ++++++++++++++---- .../user/UserApiIntegrationTest.kt | 2 +- 4 files changed, 220 insertions(+), 25 deletions(-) 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 0ba460f2..91aae3d4 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 @@ -75,6 +75,52 @@ class LetterCommandServiceTest : verify { mockSendLetterManagementPort.save(any()) } } } + + val commandWithSenderName = + SendLetterUsecase.AnonymousCommand( + senderName = "Test Sender", + receiverName = "receiver-name", + content = "content", + images = emptyList(), + templateType = 1, + ) + `when`("발송자 이름이 제공된 익명 편지 전송 요청을 처리하면") { + val response = letterCommandService.sendAnonymous(commandWithSenderName) + then("편지 코드가 생성되고, 제공된 발송자 이름으로 편지가 저장되어야 한다") { + response.letterCode shouldNotBeNull { + this.isNotBlank() + this.isNotEmpty() + } + verify { + mockSendLetterManagementPort.save(match { sendLetter -> + sendLetter.senderName == "Test Sender" + }) + } + } + } + + val commandWithNullSenderName = + SendLetterUsecase.AnonymousCommand( + senderName = null, + receiverName = "receiver-name", + content = "content", + images = emptyList(), + templateType = 1, + ) + `when`("발송자 이름이 null인 익명 편지 전송 요청을 처리하면") { + val response = letterCommandService.sendAnonymous(commandWithNullSenderName) + then("편지 코드가 생성되고, Anonymous로 편지가 저장되어야 한다") { + response.letterCode shouldNotBeNull { + this.isNotBlank() + this.isNotEmpty() + } + verify { + mockSendLetterManagementPort.save(match { sendLetter -> + sendLetter.senderName == "Anonymous" + }) + } + } + } } given("편지 검증 시에") { @@ -381,6 +427,7 @@ class LetterCommandServiceTest : content = content, receiverName = "receiverName", letterCode = letterCode, + senderName = "Anonymous", ) every { 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 6a6ec16a..93bd6a1d 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 @@ -626,4 +626,84 @@ class LetterControllerTest : LetterAcceptanceSupporter() { } } } + + @Test + fun sendAnonymousLetterWithSenderName() { + // given + val request = + AnonymousSendLetterRequest( + senderName = "Test Sender", + receiverName = "receiverName", + content = "content", + images = listOf("images"), + templateType = 1, + ) + BDDMockito + .given( + sendLetterUsecase.sendAnonymous( + SendLetterUsecase.AnonymousCommand( + senderName = request.senderName, + receiverName = request.receiverName, + content = request.content, + images = request.images, + templateType = request.templateType, + ), + ), + ).willReturn(SendLetterUsecase.Response("letterCode")) + // when + val response = + mockMvc.post("/api/v1/letters/anonymous/send") { + contentType = MediaType.APPLICATION_JSON + content = objectMapper.writeValueAsString(request) + } + // then + response.andExpect { + status { isOk() } + jsonPath("$.letterCode") { + exists() + isString() + isNotEmpty() + } + } + } + + @Test + fun sendAnonymousLetterWithNullSenderName() { + // given + val request = + AnonymousSendLetterRequest( + senderName = null, + receiverName = "receiverName", + content = "content", + images = listOf("images"), + templateType = 1, + ) + BDDMockito + .given( + sendLetterUsecase.sendAnonymous( + SendLetterUsecase.AnonymousCommand( + senderName = null, + receiverName = request.receiverName, + content = request.content, + images = request.images, + templateType = request.templateType, + ), + ), + ).willReturn(SendLetterUsecase.Response("letterCode")) + // when + val response = + mockMvc.post("/api/v1/letters/anonymous/send") { + contentType = MediaType.APPLICATION_JSON + content = objectMapper.writeValueAsString(request) + } + // then + response.andExpect { + status { isOk() } + jsonPath("$.letterCode") { + exists() + isString() + isNotEmpty() + } + } + } } 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 901324c2..58e903f7 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 @@ -3,7 +3,13 @@ package com.asap.bootstrap.integration.letter import com.asap.application.letter.LetterMockManager import com.asap.application.space.SpaceMockManager import com.asap.bootstrap.IntegrationSupporter -import com.asap.bootstrap.web.letter.dto.* +import com.asap.bootstrap.web.letter.dto.AddPhysicalLetterRequest +import com.asap.bootstrap.web.letter.dto.AddVerifiedLetterRequest +import com.asap.bootstrap.web.letter.dto.AnonymousSendLetterRequest +import com.asap.bootstrap.web.letter.dto.DeleteSendLettersRequest +import com.asap.bootstrap.web.letter.dto.LetterVerifyRequest +import com.asap.bootstrap.web.letter.dto.ModifyLetterRequest +import com.asap.bootstrap.web.letter.dto.SendLetterRequest import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test @@ -1082,30 +1088,92 @@ class LetterApiIntegrationTest : IntegrationSupporter() { } } - @Test - @DisplayName("비회원 편지 쓰기") - fun sendAnonymousLetter() { - // given - val request = - AnonymousSendLetterRequest( - receiverName = "receiverName", - content = "content", - images = listOf("images"), - templateType = 1, - ) - // when - val response = - mockMvc.post("/api/v1/letters/anonymous/send") { - contentType = MediaType.APPLICATION_JSON - content = objectMapper.writeValueAsString(request) + @Nested + @DisplayName("익명 편지 전송") + inner class SendAnonymousLetter { + @Test + @DisplayName("발송자 이름 없이 익명 편지 전송") + fun sendAnonymousLetter() { + // given + val request = + AnonymousSendLetterRequest( + receiverName = "receiverName", + content = "content", + images = listOf("images"), + templateType = 1, + ) + // when + val response = + mockMvc.post("/api/v1/letters/anonymous/send") { + contentType = MediaType.APPLICATION_JSON + content = objectMapper.writeValueAsString(request) + } + // then + response.andExpect { + status { isOk() } + jsonPath("$.letterCode") { + exists() + isString() + isNotEmpty() + } } - // then - response.andExpect { - status { isOk() } - jsonPath("$.letterCode") { - exists() - isString() - isNotEmpty() + } + + @Test + @DisplayName("발송자 이름과 함께 익명 편지 전송") + fun sendAnonymousLetterWithSenderName() { + // given + val request = + AnonymousSendLetterRequest( + senderName = "Test Sender", + receiverName = "receiverName", + content = "content", + images = listOf("images"), + templateType = 1, + ) + // when + val response = + mockMvc.post("/api/v1/letters/anonymous/send") { + contentType = MediaType.APPLICATION_JSON + content = objectMapper.writeValueAsString(request) + } + // then + response.andExpect { + status { isOk() } + jsonPath("$.letterCode") { + exists() + isString() + isNotEmpty() + } + } + } + + @Test + @DisplayName("발송자 이름이 null인 익명 편지 전송") + fun sendAnonymousLetterWithNullSenderName() { + // given + val request = + AnonymousSendLetterRequest( + senderName = null, + receiverName = "receiverName", + content = "content", + images = listOf("images"), + templateType = 1, + ) + // when + val response = + mockMvc.post("/api/v1/letters/anonymous/send") { + contentType = MediaType.APPLICATION_JSON + content = objectMapper.writeValueAsString(request) + } + // then + response.andExpect { + status { isOk() } + jsonPath("$.letterCode") { + exists() + isString() + isNotEmpty() + } } } } diff --git a/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/integration/user/UserApiIntegrationTest.kt b/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/integration/user/UserApiIntegrationTest.kt index 6c327024..fdf5df28 100644 --- a/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/integration/user/UserApiIntegrationTest.kt +++ b/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/integration/user/UserApiIntegrationTest.kt @@ -332,7 +332,7 @@ class UserApiIntegrationTest( content = letterContent, receiverName = receiverName, letterCode = "test-letter-code", - status = LetterStatus.SENDING, + senderName = "Anonymous", ) sendLetterManagementPort.save(anonymousSendLetter)