feat(recommend): 콘텐츠 조회 이력 저장 어댑터를 추가한다

This commit is contained in:
2026-05-31 18:18:50 +09:00
parent 70832a10b9
commit 2ef8e8e489
2 changed files with 135 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
package kr.co.vividnext.sodalive.v2.recommend.adapter.out.persistence
import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.content.QAudioContent.audioContent
import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme.audioContentTheme
import kr.co.vividnext.sodalive.v2.recommend.port.out.CreatorContentViewHistoryPort
import kr.co.vividnext.sodalive.v2.recommend.port.out.CreatorContentViewHistoryRecord
import org.springframework.stereotype.Repository
@Repository
class CreatorContentViewHistoryPersistenceAdapter(
private val repository: CreatorContentViewHistoryRepository,
private val queryFactory: JPAQueryFactory
) : CreatorContentViewHistoryPort {
override fun findGenreIdByContentId(contentId: Long): Long? {
return queryFactory
.select(audioContentTheme.id)
.from(audioContent)
.innerJoin(audioContent.theme, audioContentTheme)
.where(
audioContent.id.eq(contentId),
audioContent.isActive.isTrue,
audioContentTheme.isActive.isTrue
)
.fetchFirst()
}
override fun save(record: CreatorContentViewHistoryRecord) {
repository.save(
CreatorContentViewHistory(
memberId = record.memberId,
contentId = record.contentId,
genreId = record.genreId,
viewedAt = record.viewedAt
)
)
}
}

View File

@@ -0,0 +1,97 @@
package kr.co.vividnext.sodalive.v2.recommend.adapter.out.persistence
import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.configs.QueryDslConfig
import kr.co.vividnext.sodalive.content.AudioContent
import kr.co.vividnext.sodalive.content.theme.AudioContentTheme
import kr.co.vividnext.sodalive.member.Member
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.context.annotation.Import
import java.time.LocalDateTime
import javax.persistence.EntityManager
@DataJpaTest(
properties = [
"spring.cache.type=none",
"spring.datasource.url=jdbc:h2:mem:testdb;MODE=MySQL;NON_KEYWORDS=VALUE"
]
)
@Import(QueryDslConfig::class)
class CreatorContentViewHistoryPersistenceAdapterTest @Autowired constructor(
private val entityManager: EntityManager,
private val repository: CreatorContentViewHistoryRepository,
queryFactory: JPAQueryFactory
) {
private val adapter = CreatorContentViewHistoryPersistenceAdapter(repository, queryFactory)
@Test
@DisplayName("콘텐츠 조회 이력 저장용 genreId는 content_theme id를 조회한다")
fun shouldFindContentThemeIdByContentId() {
val creator = saveMember("history-theme-creator")
val theme = saveTheme("history-theme")
val content = saveAudioContent(creator, theme, isActive = true)
flushAndClear()
val themeId = adapter.findGenreIdByContentId(content.id!!)
assertEquals(theme.id, themeId)
}
@Test
@DisplayName("비활성 콘텐츠 또는 비활성 테마는 조회 이력 저장 대상 테마를 반환하지 않는다")
fun shouldNotFindThemeIdWhenContentOrThemeIsInactive() {
val creator = saveMember("history-inactive-creator")
val activeTheme = saveTheme("history-active-theme")
val inactiveTheme = saveTheme("history-inactive-theme", isActive = false)
val inactiveContent = saveAudioContent(creator, activeTheme, isActive = false)
val inactiveThemeContent = saveAudioContent(creator, inactiveTheme, isActive = true)
flushAndClear()
assertNull(adapter.findGenreIdByContentId(inactiveContent.id!!))
assertNull(adapter.findGenreIdByContentId(inactiveThemeContent.id!!))
}
private fun saveMember(nickname: String): Member {
val member = Member(
email = "$nickname@test.com",
password = "password",
nickname = nickname
)
entityManager.persist(member)
return member
}
private fun saveTheme(name: String, isActive: Boolean = true): AudioContentTheme {
val theme = AudioContentTheme(
theme = name,
image = "$name.png",
isActive = isActive
)
entityManager.persist(theme)
return theme
}
private fun saveAudioContent(creator: Member, theme: AudioContentTheme, isActive: Boolean): AudioContent {
val content = AudioContent(
title = "content-${creator.nickname}-${theme.theme}",
detail = "detail",
languageCode = "ko",
releaseDate = LocalDateTime.of(2026, 5, 30, 10, 0)
)
content.member = creator
content.theme = theme
content.isActive = isActive
entityManager.persist(content)
return content
}
private fun flushAndClear() {
entityManager.flush()
entityManager.clear()
}
}