test(aicharacter): 크리에이터 회원 연결 검증을 보강한다

This commit is contained in:
2026-06-12 11:39:50 +09:00
parent 268ed751c3
commit 5cf1f7d909
2 changed files with 172 additions and 104 deletions

View File

@@ -0,0 +1,171 @@
package kr.co.vividnext.sodalive.chat.character.service
import kr.co.vividnext.sodalive.chat.character.ChatCharacter
import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterRepository
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 org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertThrows
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 ChatCharacterCreatorMemberServiceIntegrationTest @Autowired constructor(
private val creatorMemberService: ChatCharacterCreatorMemberService,
private val memberRepository: MemberRepository,
private val chatCharacterRepository: ChatCharacterRepository,
private val entityManager: EntityManager
) {
@Test
fun `ChatCharacter creatorMember 관계와 repository 메서드를 사용할 수 있다`() {
val member = memberRepository.save(createMember(memberKind = MemberKind.AI_CHARACTER))
val chatCharacter = chatCharacterRepository.save(createCharacter().apply { creatorMember = member })
entityManager.flush()
entityManager.clear()
val found = chatCharacterRepository.findByCreatorMemberId(member.id!!)
assertNotNull(found)
assertEquals(chatCharacter.id, found!!.id)
assertEquals(true, chatCharacterRepository.existsByCreatorMemberId(member.id!!))
}
@Test
fun `AI 캐릭터용 Member를 생성하고 표시 정보를 복사한다`() {
val chatCharacter = createCharacter(
name = "소다",
description = "AI 캐릭터 설명",
imagePath = "characters/1/profile.png"
)
val member = creatorMemberService.ensureAiCharacterCreatorMember(chatCharacter)
chatCharacterRepository.save(chatCharacter)
entityManager.flush()
entityManager.clear()
val savedMember = memberRepository.findById(member.id!!).orElseThrow()
val savedCharacter = chatCharacterRepository.findByCreatorMemberId(member.id!!)
assertNotNull(savedCharacter)
assertNull(savedMember.email)
assertEquals("", savedMember.password)
assertEquals(MemberRole.CREATOR, savedMember.role)
assertEquals(MemberKind.AI_CHARACTER, savedMember.memberKind)
assertEquals("소다", savedMember.nickname)
assertEquals("characters/1/profile.png", savedMember.profileImage)
assertEquals("AI 캐릭터 설명", savedMember.introduce)
}
@Test
fun `AI 캐릭터용 Member 표시 정보를 동기화한다`() {
val member = memberRepository.save(
createMember(memberKind = MemberKind.AI_CHARACTER).apply {
nickname = "old-name"
profileImage = "old/profile.png"
introduce = "old-description"
}
)
val chatCharacter = chatCharacterRepository.save(
createCharacter(
name = "new-name",
description = "new-description",
imagePath = "new/profile.png"
).apply { creatorMember = member }
)
entityManager.flush()
entityManager.clear()
val savedCharacter = chatCharacterRepository.findById(chatCharacter.id!!).orElseThrow()
creatorMemberService.syncAiCharacterCreatorMemberDisplayFields(savedCharacter)
entityManager.flush()
entityManager.clear()
val savedMember = memberRepository.findById(member.id!!).orElseThrow()
assertEquals("new-name", savedMember.nickname)
assertEquals("new/profile.png", savedMember.profileImage)
assertEquals("new-description", savedMember.introduce)
}
@Test
fun `동기화 대상 creatorMember가 없으면 저장 실패를 위해 예외를 던진다`() {
val chatCharacter = createCharacter(
id = 1L,
name = "missing-creator-member",
description = "description"
)
val exception = assertThrows(SodaException::class.java) {
creatorMemberService.syncAiCharacterCreatorMemberDisplayFields(chatCharacter)
}
assertEquals("common.error.invalid_request", exception.messageKey)
}
@Test
fun `사람 크리에이터 Member 표시 정보는 덮어쓰지 않는다`() {
val member = memberRepository.save(
createMember(memberKind = MemberKind.HUMAN).apply {
nickname = "human-name"
profileImage = "human/profile.png"
introduce = "human-description"
}
)
val chatCharacter = chatCharacterRepository.save(
createCharacter(
name = "ai-name",
description = "ai-description",
imagePath = "ai/profile.png"
).apply { creatorMember = member }
)
entityManager.flush()
entityManager.clear()
val savedCharacter = chatCharacterRepository.findById(chatCharacter.id!!).orElseThrow()
creatorMemberService.syncAiCharacterCreatorMemberDisplayFields(savedCharacter)
entityManager.flush()
entityManager.clear()
val savedMember = memberRepository.findById(member.id!!).orElseThrow()
assertEquals("human-name", savedMember.nickname)
assertEquals("human/profile.png", savedMember.profileImage)
assertEquals("human-description", savedMember.introduce)
}
private fun createCharacter(
id: Long? = null,
name: String = "character-name",
description: String = "character-description",
imagePath: String? = null
): ChatCharacter {
val character = ChatCharacter(
characterUUID = "character-uuid-$name",
name = name,
description = description,
systemPrompt = "system-prompt"
)
character.id = id
character.imagePath = imagePath
return character
}
private fun createMember(memberKind: MemberKind): Member {
return Member(
email = if (memberKind == MemberKind.HUMAN) "human-${System.nanoTime()}@example.com" else null,
password = if (memberKind == MemberKind.HUMAN) "password" else "",
nickname = "member-name",
role = MemberRole.CREATOR,
memberKind = memberKind
)
}
}

View File

@@ -7,125 +7,23 @@ import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterHobbyRepo
import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterRepository
import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterTagRepository
import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterValueRepository
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 org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertSame
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.Mockito
import java.util.Optional
class ChatCharacterCreatorMemberServiceTest {
private lateinit var memberRepository: MemberRepository
private lateinit var chatCharacterRepository: ChatCharacterRepository
private lateinit var creatorMemberService: ChatCharacterCreatorMemberService
@BeforeEach
fun setUp() {
memberRepository = Mockito.mock(MemberRepository::class.java)
chatCharacterRepository = Mockito.mock(ChatCharacterRepository::class.java)
creatorMemberService = ChatCharacterCreatorMemberService(memberRepository)
}
@Test
fun `ChatCharacter creatorMember 관계와 repository 메서드를 사용할 수 있다`() {
val member = createMember(memberKind = MemberKind.AI_CHARACTER)
val chatCharacter = createCharacter().apply { creatorMember = member }
Mockito.`when`(chatCharacterRepository.findByCreatorMemberId(1L)).thenReturn(chatCharacter)
Mockito.`when`(chatCharacterRepository.existsByCreatorMemberId(1L)).thenReturn(true)
assertSame(member, chatCharacter.creatorMember)
assertSame(chatCharacter, chatCharacterRepository.findByCreatorMemberId(1L))
assertEquals(true, chatCharacterRepository.existsByCreatorMemberId(1L))
}
@Test
fun `AI 캐릭터용 Member를 생성하고 표시 정보를 복사한다`() {
val chatCharacter = createCharacter(
name = "소다",
description = "AI 캐릭터 설명",
imagePath = "characters/1/profile.png"
)
Mockito.`when`(memberRepository.save(Mockito.any(Member::class.java))).thenAnswer { invocation ->
(invocation.arguments[0] as Member).apply { id = 10L }
}
val member = creatorMemberService.ensureAiCharacterCreatorMember(chatCharacter)
assertSame(member, chatCharacter.creatorMember)
assertNull(member.email)
assertEquals("", member.password)
assertEquals(MemberRole.CREATOR, member.role)
assertEquals(MemberKind.AI_CHARACTER, member.memberKind)
assertEquals("소다", member.nickname)
assertEquals("characters/1/profile.png", member.profileImage)
assertEquals("AI 캐릭터 설명", member.introduce)
Mockito.verify(memberRepository).save(member)
}
@Test
fun `AI 캐릭터용 Member 표시 정보를 동기화한다`() {
val member = createMember(memberKind = MemberKind.AI_CHARACTER).apply {
nickname = "old-name"
profileImage = "old/profile.png"
introduce = "old-description"
}
val chatCharacter = createCharacter(
name = "new-name",
description = "new-description",
imagePath = "new/profile.png"
).apply { creatorMember = member }
creatorMemberService.syncAiCharacterCreatorMemberDisplayFields(chatCharacter)
assertEquals("new-name", member.nickname)
assertEquals("new/profile.png", member.profileImage)
assertEquals("new-description", member.introduce)
Mockito.verify(memberRepository).save(member)
}
@Test
fun `동기화 대상 creatorMember가 없으면 저장 실패를 위해 예외를 던진다`() {
val chatCharacter = createCharacter(
id = 1L,
name = "missing-creator-member",
description = "description"
)
val exception = assertThrows(SodaException::class.java) {
creatorMemberService.syncAiCharacterCreatorMemberDisplayFields(chatCharacter)
}
assertEquals("common.error.invalid_request", exception.messageKey)
Mockito.verifyNoInteractions(memberRepository)
}
@Test
fun `사람 크리에이터 Member 표시 정보는 덮어쓰지 않는다`() {
val member = createMember(memberKind = MemberKind.HUMAN).apply {
nickname = "human-name"
profileImage = "human/profile.png"
introduce = "human-description"
}
val chatCharacter = createCharacter(
name = "ai-name",
description = "ai-description",
imagePath = "ai/profile.png"
).apply { creatorMember = member }
creatorMemberService.syncAiCharacterCreatorMemberDisplayFields(chatCharacter)
assertEquals("human-name", member.nickname)
assertEquals("human/profile.png", member.profileImage)
assertEquals("human-description", member.introduce)
Mockito.verifyNoInteractions(memberRepository)
creatorMemberService = Mockito.mock(ChatCharacterCreatorMemberService::class.java)
}
@Test
@@ -172,7 +70,6 @@ class ChatCharacterCreatorMemberServiceTest {
}
private fun createChatCharacterService(): ChatCharacterService {
creatorMemberService = Mockito.mock(ChatCharacterCreatorMemberService::class.java)
return ChatCharacterService(
chatCharacterRepository = chatCharacterRepository,
tagRepository = Mockito.mock(ChatCharacterTagRepository::class.java),