test #426

Merged
klaus merged 415 commits from test into main 2026-06-27 00:35:30 +00:00
7 changed files with 532 additions and 14 deletions
Showing only changes of commit 37ad325cc2 - Show all commits

View File

@@ -196,8 +196,12 @@ class AdminOriginalWorkService(
/** 원작 상세 조회 (소프트 삭제 제외) */
@Transactional(readOnly = true)
fun getOriginalWork(id: Long): OriginalWork {
return originalWorkRepository.findByIdAndIsDeletedFalse(id)
val originalWork = originalWorkRepository.findByIdAndIsDeletedFalse(id)
.orElseThrow { SodaException(messageKey = "admin.chat.original.not_found") }
initializeResponseRelations(originalWork)
return originalWork
}
/** 원작 페이징 조회 */
@@ -210,7 +214,9 @@ class AdminOriginalWorkService(
else -> size
}
val pageable = PageRequest.of(safePage, safeSize, Sort.by("createdAt").descending())
return originalWorkRepository.findByIsDeletedFalse(pageable)
val originalWorks = originalWorkRepository.findByIsDeletedFalse(pageable)
originalWorks.content.forEach { initializeResponseRelations(it) }
return originalWorks
}
/** 지정 원작에 속한 활성 캐릭터 페이징 조회 (최신순) */
@@ -233,7 +239,14 @@ class AdminOriginalWorkService(
/** 원작 검색 (제목/콘텐츠타입/카테고리, 소프트 삭제 제외) - 무페이징 */
@Transactional(readOnly = true)
fun searchOriginalWorksAll(searchTerm: String): List<OriginalWork> {
return originalWorkRepository.searchNoPaging(searchTerm)
val originalWorks = originalWorkRepository.searchNoPaging(searchTerm)
originalWorks.forEach { initializeResponseRelations(it) }
return originalWorks
}
private fun initializeResponseRelations(originalWork: OriginalWork) {
originalWork.originalLinks.forEach { it.url }
originalWork.tagMappings.forEach { it.tag.tag }
}
/** 원작에 기존 캐릭터들을 배정 */

View File

@@ -74,6 +74,28 @@ interface ChatCharacterRepository : JpaRepository<ChatCharacter, Long> {
pageable: Pageable
): List<ChatCharacter>
/**
* 특정 캐릭터와 태그를 공유하는 다른 캐릭터 ID를 무작위로 조회 (현재 캐릭터 제외)
*/
@Query(
"""
SELECT c.id FROM ChatCharacter c
JOIN c.tagMappings tm
JOIN tm.tag t
WHERE c.isActive = true
AND c.id <> :characterId
AND t.id IN (
SELECT t2.id FROM ChatCharacterTagMapping tm2 JOIN tm2.tag t2 WHERE tm2.chatCharacter.id = :characterId
)
GROUP BY c.id
ORDER BY function('RAND')
"""
)
fun findRandomIdsBySharedTags(
@Param("characterId") characterId: Long,
pageable: Pageable
): List<Long>
/**
* 활성 캐릭터 무작위 조회
*/
@@ -99,6 +121,27 @@ interface ChatCharacterRepository : JpaRepository<ChatCharacter, Long> {
fun findRandomActiveExcluding(@Param("excludeIds") excludeIds: List<Long>, pageable: Pageable): List<ChatCharacter>
fun findByIdInAndIsActiveTrue(ids: List<Long>): List<ChatCharacter>
@Query(
"""
SELECT DISTINCT c FROM ChatCharacter c
LEFT JOIN FETCH c.tagMappings tm
LEFT JOIN FETCH tm.tag
WHERE c.id = :id
"""
)
fun findByIdWithTagMappings(@Param("id") id: Long): ChatCharacter?
@Query(
"""
SELECT DISTINCT c FROM ChatCharacter c
LEFT JOIN FETCH c.tagMappings tm
LEFT JOIN FETCH tm.tag
WHERE c.id IN :ids
"""
)
fun findByIdInWithTagMappings(@Param("ids") ids: List<Long>): List<ChatCharacter>
fun findByCreatorMemberId(creatorMemberId: Long): ChatCharacter?
fun existsByCreatorMemberId(creatorMemberId: Long): Boolean
}

View File

@@ -210,13 +210,15 @@ class ChatCharacterService(
*/
@Transactional(readOnly = true)
fun getOtherCharactersBySharedTags(characterId: Long, limit: Int = 10): List<ChatCharacter> {
val others = chatCharacterRepository.findRandomBySharedTags(
val ids = chatCharacterRepository.findRandomIdsBySharedTags(
characterId,
PageRequest.of(0, limit)
)
// 태그 초기화 (지연 로딩 문제 방지)
others.forEach { it.tagMappings.size }
return others
).distinct()
if (ids.isEmpty()) return emptyList()
val charactersById = chatCharacterRepository.findByIdInWithTagMappings(ids)
.associateBy { it.id }
return ids.mapNotNull { charactersById[it] }
}
/**
@@ -555,13 +557,12 @@ class ChatCharacterService(
*/
@Transactional(readOnly = true)
fun getCharacterDetail(id: Long): ChatCharacter? {
val character = findById(id) ?: return null
val character = chatCharacterRepository.findByIdWithTagMappings(id) ?: return null
// 지연 로딩된 관계 데이터 초기화
character.tagMappings.size
character.valueMappings.size
character.hobbyMappings.size
character.goalMappings.size
character.valueMappings.forEach { it.value.value }
character.hobbyMappings.forEach { it.hobby.hobby }
character.goalMappings.forEach { it.goal.goal }
character.memories.size
character.personalities.size
character.backgrounds.size

View File

@@ -43,8 +43,13 @@ class OriginalWorkQueryService(
*/
@Transactional(readOnly = true)
fun getOriginalWork(id: Long): OriginalWork {
return originalWorkRepository.findByIdAndIsDeletedFalse(id)
val originalWork = originalWorkRepository.findByIdAndIsDeletedFalse(id)
.orElseThrow { SodaException(messageKey = "chat.original.not_found") }
originalWork.originalLinks.forEach { it.url }
originalWork.tagMappings.forEach { it.tag.tag }
return originalWork
}
/**

View File

@@ -2,12 +2,14 @@ package kr.co.vividnext.sodalive.i18n.translation
import kr.co.vividnext.sodalive.i18n.translation.PapagoTranslationService.Companion.getTranslatableLanguageCodes
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
@Service
class ResourceTranslationJobScheduler(
private val sourceExtractor: TranslationSourceExtractor,
private val translationJobScheduler: TranslationJobScheduler
) {
@Transactional
fun scheduleResourceTranslations(resourceType: LanguageTranslationTargetType, resourceId: Long) {
val source = sourceExtractor.extract(resourceType, resourceId) ?: return
getTranslatableLanguageCodes(source.sourceLanguage).forEach { targetLanguage ->
@@ -15,6 +17,7 @@ class ResourceTranslationJobScheduler(
}
}
@Transactional
fun scheduleResourceTranslation(
resourceType: LanguageTranslationTargetType,
resourceId: Long,

View File

@@ -23,6 +23,8 @@ import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberRole
import kr.co.vividnext.sodalive.member.QMember.member
import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember
import kr.co.vividnext.sodalive.member.tag.QCreatorTag.creatorTag
import kr.co.vividnext.sodalive.member.tag.QMemberCreatorTag.memberCreatorTag
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Repository
import java.time.LocalDateTime
@@ -53,6 +55,8 @@ class RankingRepository(
.select(member)
.from(creatorRanking)
.innerJoin(creatorRanking.member, member)
.leftJoin(member.tags, memberCreatorTag).fetchJoin()
.leftJoin(memberCreatorTag.tag, creatorTag).fetchJoin()
if (memberId != null) {
select = select.leftJoin(blockMember).on(blockMemberCondition)
@@ -65,6 +69,7 @@ class RankingRepository(
return select
.orderBy(creatorRanking.ranking.asc())
.fetch()
.distinctBy { it.id }
}
fun getAudioContentRanking(

View File

@@ -0,0 +1,448 @@
package kr.co.vividnext.sodalive.osiv
import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.admin.chat.original.dto.OriginalWorkResponse
import kr.co.vividnext.sodalive.admin.chat.original.service.AdminOriginalWorkService
import kr.co.vividnext.sodalive.chat.character.ChatCharacter
import kr.co.vividnext.sodalive.chat.character.image.CharacterImageRepository
import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterGoalRepository
import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterHobbyRepository
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.chat.character.service.ChatCharacterCreatorMemberService
import kr.co.vividnext.sodalive.chat.character.service.ChatCharacterService
import kr.co.vividnext.sodalive.chat.character.service.PopularCharacterQuery
import kr.co.vividnext.sodalive.chat.original.OriginalWork
import kr.co.vividnext.sodalive.chat.original.OriginalWorkLink
import kr.co.vividnext.sodalive.chat.original.OriginalWorkRepository
import kr.co.vividnext.sodalive.chat.original.OriginalWorkTag
import kr.co.vividnext.sodalive.chat.original.OriginalWorkTagMapping
import kr.co.vividnext.sodalive.chat.original.dto.OriginalWorkDetailResponse
import kr.co.vividnext.sodalive.chat.original.repository.OriginalWorkTagRepository
import kr.co.vividnext.sodalive.chat.original.service.OriginalWorkQueryService
import kr.co.vividnext.sodalive.configs.QueryDslConfig
import kr.co.vividnext.sodalive.content.theme.AudioContentThemeQueryRepository
import kr.co.vividnext.sodalive.explorer.CreatorRanking
import kr.co.vividnext.sodalive.i18n.translation.LanguageTranslationTargetType
import kr.co.vividnext.sodalive.i18n.translation.ResourceTranslationJobScheduler
import kr.co.vividnext.sodalive.i18n.translation.TranslationJobRepository
import kr.co.vividnext.sodalive.i18n.translation.TranslationJobScheduler
import kr.co.vividnext.sodalive.i18n.translation.TranslationSourceExtractor
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberRole
import kr.co.vividnext.sodalive.member.tag.CreatorTag
import kr.co.vividnext.sodalive.member.tag.MemberCreatorTag
import kr.co.vividnext.sodalive.rank.RankingRepository
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.mockito.Mockito
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.context.ApplicationEventPublisher
import org.springframework.context.annotation.Import
import org.springframework.data.domain.PageRequest
import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional
import org.springframework.transaction.support.TransactionTemplate
import javax.persistence.EntityManager
@DataJpaTest(
properties = [
"spring.cache.type=none",
"spring.datasource.url=jdbc:h2:mem:osiv-regression;MODE=MySQL;NON_KEYWORDS=VALUE;DB_CLOSE_ON_EXIT=FALSE"
]
)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Import(
QueryDslConfig::class,
ResourceTranslationJobScheduler::class,
TranslationSourceExtractor::class,
TranslationJobScheduler::class,
AudioContentThemeQueryRepository::class
)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class OsivLazyLoadingRegressionTest @Autowired constructor(
private val chatCharacterRepository: ChatCharacterRepository,
private val tagRepository: ChatCharacterTagRepository,
private val valueRepository: ChatCharacterValueRepository,
private val hobbyRepository: ChatCharacterHobbyRepository,
private val goalRepository: ChatCharacterGoalRepository,
private val originalWorkRepository: OriginalWorkRepository,
private val resourceTranslationJobScheduler: ResourceTranslationJobScheduler,
private val translationJobRepository: TranslationJobRepository,
private val queryFactory: JPAQueryFactory,
private val transactionTemplate: TransactionTemplate,
private val entityManager: EntityManager
) {
private val chatCharacterService = ChatCharacterService(
chatCharacterRepository = chatCharacterRepository,
tagRepository = tagRepository,
valueRepository = valueRepository,
hobbyRepository = hobbyRepository,
goalRepository = goalRepository,
popularCharacterQuery = Mockito.mock(PopularCharacterQuery::class.java),
imageRepository = Mockito.mock(CharacterImageRepository::class.java),
creatorMemberService = Mockito.mock(ChatCharacterCreatorMemberService::class.java),
imageHost = "https://cdn.test"
)
private val rankingRepository = RankingRepository(queryFactory, "https://cdn.test")
private val originalWorkQueryService = OriginalWorkQueryService(
originalWorkRepository = originalWorkRepository,
chatCharacterRepository = chatCharacterRepository
)
private val adminOriginalWorkService = AdminOriginalWorkService(
originalWorkRepository = originalWorkRepository,
chatCharacterRepository = chatCharacterRepository,
originalWorkTagRepository = Mockito.mock(OriginalWorkTagRepository::class.java),
applicationEventPublisher = Mockito.mock(ApplicationEventPublisher::class.java)
)
@Test
@DisplayName("캐릭터 상세 조회 결과는 트랜잭션 밖에서도 태그 이름을 읽을 수 있다")
fun shouldLoadCharacterDetailTagOutsideTransaction() {
val characterId = saveCharacterWithTag("detail")
val character = transactionTemplate.execute {
chatCharacterService.getCharacterDetail(characterId)
}!!
val tags = character.tagMappings.map { it.tag.tag }
assertEquals(listOf("detail-tag"), tags)
}
@Test
@DisplayName("캐릭터 상세 조회 결과는 트랜잭션 밖에서도 가치관, 취미, 목표 이름을 읽을 수 있다")
fun shouldLoadCharacterDetailMappingTargetsOutsideTransaction() {
val characterId = saveCharacterWithDetailMappings("detail-target")
val character = transactionTemplate.execute {
chatCharacterService.getCharacterDetail(characterId)
}!!
assertEquals(listOf("detail-target-value"), character.valueMappings.map { it.value.value })
assertEquals(listOf("detail-target-hobby"), character.hobbyMappings.map { it.hobby.hobby })
assertEquals(listOf("detail-target-goal"), character.goalMappings.map { it.goal.goal })
}
@Test
@DisplayName("공유 태그 기반 다른 캐릭터 조회 결과는 트랜잭션 밖에서도 태그 이름을 읽을 수 있다")
fun shouldLoadSharedTagCharactersOutsideTransaction() {
val characterId = saveTwoCharactersWithSharedTag()
val characters = transactionTemplate.execute {
chatCharacterService.getOtherCharactersBySharedTags(characterId, 10)
}!!
val tags = characters.single().tagMappings.map { it.tag.tag }
assertEquals(listOf("shared-tag"), tags)
}
@Test
@DisplayName("공유 태그 기반 다른 캐릭터 ID 조회 결과는 태그 조인 중복을 제거한다")
fun shouldReturnDistinctSharedTagCharacterIds() {
val characterId = saveCharactersWithDuplicateSharedTags()
val ids = transactionTemplate.execute {
chatCharacterRepository.findRandomIdsBySharedTags(
characterId,
PageRequest.of(0, 10)
)
}!!
assertEquals(ids.distinct(), ids)
assertEquals(2, ids.size)
}
@Test
@DisplayName("원작 상세 조회 결과는 트랜잭션 밖에서도 원작 링크와 태그 이름을 DTO로 변환할 수 있다")
fun shouldLoadOriginalWorkDetailRelationsOutsideTransaction() {
val originalWorkId = saveOriginalWorkWithLinkAndTag()
val originalWork = transactionTemplate.execute {
originalWorkQueryService.getOriginalWork(originalWorkId)
}!!
val response = OriginalWorkDetailResponse.from(
entity = originalWork,
characters = emptyList(),
translated = null
)
assertEquals(listOf("https://original.test/original"), response.originalLinks)
assertEquals(listOf("original-tag"), response.tags)
}
@Test
@DisplayName("관리자 원작 상세 조회 결과는 트랜잭션 밖에서도 DTO로 변환할 수 있다")
fun shouldLoadAdminOriginalWorkDetailRelationsOutsideTransaction() {
val originalWorkId = saveOriginalWorkWithLinkAndTag("admin-detail")
val originalWork = transactionTemplate.execute {
adminOriginalWorkService.getOriginalWork(originalWorkId)
}!!
val response = OriginalWorkResponse.from(originalWork)
assertEquals(listOf("https://original.test/admin-detail"), response.originalLinks)
assertEquals(listOf("admin-detail-tag"), response.tags)
}
@Test
@DisplayName("관리자 원작 목록 조회 결과는 트랜잭션 밖에서도 DTO로 변환할 수 있다")
fun shouldLoadAdminOriginalWorkPageRelationsOutsideTransaction() {
saveOriginalWorkWithLinkAndTag("admin-page")
val page = transactionTemplate.execute {
adminOriginalWorkService.getOriginalWorkPage(page = 0, size = 20)
}!!
val response = OriginalWorkResponse.from(page.content.single())
assertEquals(listOf("https://original.test/admin-page"), response.originalLinks)
assertEquals(listOf("admin-page-tag"), response.tags)
}
@Test
@DisplayName("관리자 원작 검색 결과는 트랜잭션 밖에서도 DTO로 변환할 수 있다")
fun shouldLoadAdminOriginalWorkSearchRelationsOutsideTransaction() {
saveOriginalWorkWithLinkAndTag("admin-search")
val originalWorks = transactionTemplate.execute {
adminOriginalWorkService.searchOriginalWorksAll("admin-search-title")
}!!
val response = OriginalWorkResponse.from(originalWorks.single())
assertEquals(listOf("https://original.test/admin-search"), response.originalLinks)
assertEquals(listOf("admin-search-tag"), response.tags)
}
@Test
@DisplayName("캐릭터 번역 job 스케줄러는 트랜잭션 밖 호출에서도 lazy 관계를 추출할 수 있다")
fun shouldScheduleCharacterTranslationOutsideTransaction() {
val characterId = saveCharacterForTranslation("translation")
resourceTranslationJobScheduler.scheduleResourceTranslation(
resourceType = LanguageTranslationTargetType.CHARACTER,
resourceId = characterId,
targetLanguage = "en"
)
val jobs = translationJobRepository.findAll()
assertEquals(
listOf(
"name",
"description",
"gender",
"personalityTrait",
"personalityDescription",
"backgroundTopic",
"backgroundDescription",
"tags"
),
jobs.map { it.fieldKey }.sortedBy { expectedCharacterTranslationFieldOrder().indexOf(it) }
)
}
@Test
@DisplayName("크리에이터 랭킹 조회 결과는 트랜잭션 밖에서도 explorer creator DTO로 변환할 수 있다")
fun shouldLoadCreatorRankingTagsOutsideTransaction() {
saveCreatorRankingWithTag()
val creators = rankingRepository.getCreatorRankings(memberId = null)
val response = creators.single().toExplorerSectionCreator("https://cdn.test")
assertEquals("#creator-tag", response.tags)
}
private fun saveCharacterWithTag(seed: String): Long {
return transactionTemplate.execute {
val tag = kr.co.vividnext.sodalive.chat.character.ChatCharacterTag("$seed-tag")
entityManager.persist(tag)
val character = ChatCharacter(
characterUUID = "$seed-character-uuid",
name = "$seed-character",
description = "$seed-description",
systemPrompt = "$seed-system-prompt"
)
character.creatorMember = persistCreator("$seed-character-creator")
character.addTag(tag)
chatCharacterRepository.save(character).id!!
}!!
}
private fun saveTwoCharactersWithSharedTag(): Long {
return transactionTemplate.execute {
val tag = kr.co.vividnext.sodalive.chat.character.ChatCharacterTag("shared-tag")
entityManager.persist(tag)
val current = ChatCharacter(
characterUUID = "shared-current-uuid",
name = "shared-current",
description = "shared-current-description",
systemPrompt = "shared-current-system-prompt"
)
current.creatorMember = persistCreator("shared-current-creator")
current.addTag(tag)
chatCharacterRepository.save(current)
val other = ChatCharacter(
characterUUID = "shared-other-uuid",
name = "shared-other",
description = "shared-other-description",
systemPrompt = "shared-other-system-prompt"
)
other.creatorMember = persistCreator("shared-other-creator")
other.addTag(tag)
chatCharacterRepository.save(other)
current.id!!
}!!
}
private fun saveCharacterWithDetailMappings(seed: String): Long {
return transactionTemplate.execute {
val value = kr.co.vividnext.sodalive.chat.character.ChatCharacterValue("$seed-value")
val hobby = kr.co.vividnext.sodalive.chat.character.ChatCharacterHobby("$seed-hobby")
val goal = kr.co.vividnext.sodalive.chat.character.ChatCharacterGoal("$seed-goal")
entityManager.persist(value)
entityManager.persist(hobby)
entityManager.persist(goal)
val character = ChatCharacter(
characterUUID = "$seed-character-uuid",
name = "$seed-character",
description = "$seed-description",
systemPrompt = "$seed-system-prompt"
)
character.creatorMember = persistCreator("$seed-character-creator")
character.addValue(value)
character.addHobby(hobby)
character.addGoal(goal)
chatCharacterRepository.save(character).id!!
}!!
}
private fun saveCharacterForTranslation(seed: String): Long {
return transactionTemplate.execute {
val tag = kr.co.vividnext.sodalive.chat.character.ChatCharacterTag("$seed-tag")
entityManager.persist(tag)
val character = ChatCharacter(
characterUUID = "$seed-character-uuid",
name = "$seed-character",
description = "$seed-description",
languageCode = "ko",
systemPrompt = "$seed-system-prompt",
gender = "female"
)
character.creatorMember = persistCreator("$seed-character-creator")
character.addTag(tag)
character.addPersonality("$seed-trait", "$seed-personality-description")
character.addBackground("$seed-topic", "$seed-background-description")
chatCharacterRepository.save(character).id!!
}!!
}
private fun expectedCharacterTranslationFieldOrder(): List<String> {
return listOf(
"name",
"description",
"gender",
"personalityTrait",
"personalityDescription",
"backgroundTopic",
"backgroundDescription",
"tags"
)
}
private fun saveCharactersWithDuplicateSharedTags(): Long {
return transactionTemplate.execute {
val tagA = kr.co.vividnext.sodalive.chat.character.ChatCharacterTag("duplicate-shared-a")
val tagB = kr.co.vividnext.sodalive.chat.character.ChatCharacterTag("duplicate-shared-b")
entityManager.persist(tagA)
entityManager.persist(tagB)
val current = ChatCharacter(
characterUUID = "duplicate-current-uuid",
name = "duplicate-current",
description = "duplicate-current-description",
systemPrompt = "duplicate-current-system-prompt"
)
current.creatorMember = persistCreator("duplicate-current-creator")
current.addTag(tagA)
current.addTag(tagB)
chatCharacterRepository.save(current)
val otherWithTwoSharedTags = ChatCharacter(
characterUUID = "duplicate-other-two-uuid",
name = "duplicate-other-two",
description = "duplicate-other-two-description",
systemPrompt = "duplicate-other-two-system-prompt"
)
otherWithTwoSharedTags.creatorMember = persistCreator("duplicate-other-two-creator")
otherWithTwoSharedTags.addTag(tagA)
otherWithTwoSharedTags.addTag(tagB)
chatCharacterRepository.save(otherWithTwoSharedTags)
val otherWithOneSharedTag = ChatCharacter(
characterUUID = "duplicate-other-one-uuid",
name = "duplicate-other-one",
description = "duplicate-other-one-description",
systemPrompt = "duplicate-other-one-system-prompt"
)
otherWithOneSharedTag.creatorMember = persistCreator("duplicate-other-one-creator")
otherWithOneSharedTag.addTag(tagA)
chatCharacterRepository.save(otherWithOneSharedTag)
current.id!!
}!!
}
private fun saveOriginalWorkWithLinkAndTag(seed: String = "original"): Long {
return transactionTemplate.execute {
val originalWork = OriginalWork(
title = "$seed-title",
contentType = "webtoon",
category = "romance",
description = "$seed-description"
)
val link = OriginalWorkLink(
url = "https://original.test/$seed",
originalWork = originalWork
)
val tag = OriginalWorkTag("$seed-tag")
val tagMapping = OriginalWorkTagMapping(originalWork, tag)
entityManager.persist(tag)
originalWork.originalLinks.add(link)
originalWork.tagMappings.add(tagMapping)
originalWorkRepository.save(originalWork).id!!
}!!
}
private fun persistCreator(seed: String): Member {
val creator = Member(
email = "$seed@test.com",
password = "password",
nickname = seed,
role = MemberRole.CREATOR
)
entityManager.persist(creator)
return creator
}
private fun saveCreatorRankingWithTag() {
transactionTemplate.executeWithoutResult {
val creator = persistCreator("creator-osiv")
val tag = CreatorTag(tag = "creator-tag")
entityManager.persist(tag)
entityManager.persist(MemberCreatorTag(member = creator, tag = tag))
val ranking = CreatorRanking(ranking = 1)
ranking.member = creator
entityManager.persist(ranking)
}
}
}