feat(recommend): 콘텐츠 조회 이력 저장 어댑터를 추가한다
This commit is contained in:
@@ -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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user