From 5c132c984d8f110c96c5facb351bfb1f00dfc966 Mon Sep 17 00:00:00 2001 From: Klaus Date: Fri, 12 Jun 2026 11:40:10 +0900 Subject: [PATCH] =?UTF-8?q?feat(usercreatorchat):=20AI=20=EC=BA=90?= =?UTF-8?q?=EB=A6=AD=ED=84=B0=20=ED=9A=8C=EC=9B=90=20DM=EC=9D=84=20?= =?UTF-8?q?=EC=B0=A8=EB=8B=A8=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/UserCreatorChatService.kt | 4 + .../UserCreatorChatServiceIntegrationTest.kt | 86 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 src/test/kotlin/kr/co/vividnext/sodalive/v2/usercreatorchat/UserCreatorChatServiceIntegrationTest.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/usercreatorchat/service/UserCreatorChatService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/usercreatorchat/service/UserCreatorChatService.kt index 06fcdcdf..a3fe8629 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/v2/usercreatorchat/service/UserCreatorChatService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/usercreatorchat/service/UserCreatorChatService.kt @@ -8,6 +8,7 @@ import kr.co.vividnext.sodalive.fcm.FcmEvent import kr.co.vividnext.sodalive.fcm.FcmEventType import kr.co.vividnext.sodalive.fcm.notification.PushNotificationCategory import kr.co.vividnext.sodalive.member.Member +import kr.co.vividnext.sodalive.member.MemberKind import kr.co.vividnext.sodalive.member.MemberRepository import kr.co.vividnext.sodalive.member.block.BlockMemberRepository import kr.co.vividnext.sodalive.utils.generateFileName @@ -209,6 +210,9 @@ class UserCreatorChatService( private fun validateRecipient(sender: Member, recipient: Member) { if (!recipient.isActive) throw SodaException(messageKey = "message.error.recipient_inactive") + if (recipient.memberKind == MemberKind.AI_CHARACTER) { + throw SodaException(messageKey = "message.error.recipient_not_found") + } if (sender.id == recipient.id) throw SodaException(messageKey = "common.error.invalid_request") if (blockMemberRepository.isBlocked(blockedMemberId = sender.id!!, memberId = recipient.id!!)) { throw SodaException(messageKey = "message.error.blocked_by_recipient") diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/usercreatorchat/UserCreatorChatServiceIntegrationTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/usercreatorchat/UserCreatorChatServiceIntegrationTest.kt new file mode 100644 index 00000000..057a6db4 --- /dev/null +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/usercreatorchat/UserCreatorChatServiceIntegrationTest.kt @@ -0,0 +1,86 @@ +package kr.co.vividnext.sodalive.v2.usercreatorchat + +import kr.co.vividnext.sodalive.common.SodaException +import kr.co.vividnext.sodalive.member.Member +import kr.co.vividnext.sodalive.member.MemberKind +import kr.co.vividnext.sodalive.member.MemberRepository +import kr.co.vividnext.sodalive.member.MemberRole +import kr.co.vividnext.sodalive.support.EmbeddedRedisInitializer +import kr.co.vividnext.sodalive.v2.usercreatorchat.dto.SendUserCreatorTextMessageRequest +import kr.co.vividnext.sodalive.v2.usercreatorchat.repository.UserCreatorChatMessageRepository +import kr.co.vividnext.sodalive.v2.usercreatorchat.repository.UserCreatorChatParticipantRepository +import kr.co.vividnext.sodalive.v2.usercreatorchat.repository.UserCreatorChatRoomRepository +import kr.co.vividnext.sodalive.v2.usercreatorchat.service.UserCreatorChatService +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ContextConfiguration +import org.springframework.transaction.annotation.Transactional +import javax.persistence.EntityManager + +@SpringBootTest +@Transactional +@ContextConfiguration(initializers = [EmbeddedRedisInitializer::class]) +class UserCreatorChatServiceIntegrationTest @Autowired constructor( + private val service: UserCreatorChatService, + private val memberRepository: MemberRepository, + private val roomRepository: UserCreatorChatRoomRepository, + private val participantRepository: UserCreatorChatParticipantRepository, + private val messageRepository: UserCreatorChatMessageRepository, + private val entityManager: EntityManager +) { + @Test + @DisplayName("AI 캐릭터용 Member와는 유저-크리에이터 DM 방을 생성할 수 없다") + fun shouldRejectCreateRoomWhenCreatorIsAiCharacterMember() { + val user = memberRepository.save(Member(email = "dm-user@test.com", password = "pw", nickname = "user")) + val creator = memberRepository.save( + Member( + email = null, + password = "", + nickname = "ai-character", + role = MemberRole.CREATOR, + memberKind = MemberKind.AI_CHARACTER + ) + ) + entityManager.flush() + entityManager.clear() + + val exception = assertThrows(SodaException::class.java) { + service.createOrGetRoom(user, creator.id!!) + } + + assertEquals("message.error.recipient_not_found", exception.messageKey) + assertEquals(0, roomRepository.findAll().size) + assertEquals(0, participantRepository.findAll().size) + } + + @Test + @DisplayName("AI 캐릭터용 Member가 참여한 기존 DM 방에는 메시지를 보낼 수 없다") + fun shouldRejectSendTextMessageWhenOpponentIsAiCharacterMember() { + val user = memberRepository.save(Member(email = "dm-message-user@test.com", password = "pw", nickname = "user")) + val creator = memberRepository.save( + Member( + email = null, + password = "", + nickname = "ai-character-message", + role = MemberRole.CREATOR, + memberKind = MemberKind.AI_CHARACTER + ) + ) + val room = roomRepository.save(UserCreatorChatRoom()) + participantRepository.save(UserCreatorChatParticipant(room, user)) + participantRepository.save(UserCreatorChatParticipant(room, creator)) + entityManager.flush() + entityManager.clear() + + val exception = assertThrows(SodaException::class.java) { + service.sendTextMessage(user, room.id!!, SendUserCreatorTextMessageRequest("hello")) + } + + assertEquals("message.error.recipient_not_found", exception.messageKey) + assertEquals(0, messageRepository.findAll().size) + } +}