Compare commits
3 Commits
ee14389786
...
7f1606a8aa
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f1606a8aa | |||
| 3c32559c5d | |||
| d3b1f4bcd4 |
41
docs/20260402_audio_content_banner_lang_ddl.sql
Normal file
41
docs/20260402_audio_content_banner_lang_ddl.sql
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
SET @schema_name := DATABASE();
|
||||||
|
|
||||||
|
SET @lang_column_exists := (
|
||||||
|
SELECT COUNT(1)
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_schema = @schema_name
|
||||||
|
AND table_name = 'content_banner'
|
||||||
|
AND column_name = 'lang'
|
||||||
|
);
|
||||||
|
|
||||||
|
SET @add_lang_column_sql := IF(
|
||||||
|
@lang_column_exists = 0,
|
||||||
|
'ALTER TABLE content_banner ADD COLUMN lang VARCHAR(10) NULL COMMENT ''배너 노출 언어'' AFTER type',
|
||||||
|
'SELECT ''content_banner.lang already exists'' AS message'
|
||||||
|
);
|
||||||
|
|
||||||
|
PREPARE add_lang_column_stmt FROM @add_lang_column_sql;
|
||||||
|
EXECUTE add_lang_column_stmt;
|
||||||
|
DEALLOCATE PREPARE add_lang_column_stmt;
|
||||||
|
|
||||||
|
UPDATE content_banner
|
||||||
|
SET lang = 'KO'
|
||||||
|
WHERE lang IS NULL;
|
||||||
|
|
||||||
|
SET @lang_column_nullable := (
|
||||||
|
SELECT IS_NULLABLE
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_schema = @schema_name
|
||||||
|
AND table_name = 'content_banner'
|
||||||
|
AND column_name = 'lang'
|
||||||
|
);
|
||||||
|
|
||||||
|
SET @alter_lang_column_sql := IF(
|
||||||
|
@lang_column_nullable = 'YES',
|
||||||
|
'ALTER TABLE content_banner MODIFY COLUMN lang VARCHAR(10) NOT NULL DEFAULT ''KO'' COMMENT ''배너 노출 언어 (KO 기본, EN/JA 추가 가능)''',
|
||||||
|
'SELECT ''content_banner.lang already normalized'' AS message'
|
||||||
|
);
|
||||||
|
|
||||||
|
PREPARE alter_lang_column_stmt FROM @alter_lang_column_sql;
|
||||||
|
EXECUTE alter_lang_column_stmt;
|
||||||
|
DEALLOCATE PREPARE alter_lang_column_stmt;
|
||||||
10
docs/20260402_관리자채팅배너목록언어표기추가.md
Normal file
10
docs/20260402_관리자채팅배너목록언어표기추가.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
- [x] 배너 목록 조회 응답 생성 경로와 언어 정보 위치를 확인한다.
|
||||||
|
- [x] 배너 목록 응답의 연결 캐릭터 이름에 배너 등록 언어를 `(언어)` 형식으로 추가한다.
|
||||||
|
- [x] 변경 파일 기준으로 검증을 수행하고 결과를 기록한다.
|
||||||
|
|
||||||
|
## 검증 기록
|
||||||
|
|
||||||
|
### 1차 구현
|
||||||
|
- 무엇을: 관리자 배너 목록 조회 응답에서 연결 캐릭터 이름 뒤에 배너 등록 언어를 `(언어)` 형식으로 붙이도록 수정했다.
|
||||||
|
- 왜: 같은 이름과 같은 이미지의 배너라도 등록 언어가 다르면 관리자 페이지에서 즉시 구분할 수 있어야 하기 때문이다.
|
||||||
|
- 어떻게: `./gradlew test --tests "kr.co.vividnext.sodalive.admin.chat.AdminChatBannerControllerTest"` 실행으로 컨트롤러 테스트를 검증했고, 새 테스트에서 목록 조회 응답 이름이 `character-12 (일본어)`로 반환되는 것을 확인했다. 결과는 `BUILD SUCCESSFUL`이다.
|
||||||
10
docs/20260402_시리즈배너언어별조회적용.md
Normal file
10
docs/20260402_시리즈배너언어별조회적용.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
- [x] 시리즈 배너 등록·조회 경로와 언어 처리 기준을 확인한다.
|
||||||
|
- [x] 배너 등록 시 언어를 저장하고 관리자 목록에서 시리즈 제목에 `(언어)` 표기를 추가한다.
|
||||||
|
- [x] 사용자 시리즈 메인 조회에서 요청 언어와 일치하는 배너만 반환하도록 수정하고 검증 결과를 기록한다.
|
||||||
|
|
||||||
|
## 검증 기록
|
||||||
|
|
||||||
|
### 1차 구현
|
||||||
|
- 무엇을: 시리즈 배너 등록 요청에 `lang`을 추가하고, 관리자 목록에서는 `seriesTitle (언어)` 형태로 응답하며, 사용자 시리즈 메인에서는 `LangContext`와 일치하는 언어 배너만 조회하도록 수정했다.
|
||||||
|
- 왜: 관리자 화면에서는 같은 시리즈명의 다국어 배너를 구분할 수 있어야 하고, 사용자 화면에서는 요청 언어와 맞는 배너만 노출되어야 하기 때문이다.
|
||||||
|
- 어떻게: Kotlin LSP가 없어 정적 진단은 Gradle 컴파일로 대체했고, `./gradlew test --tests "kr.co.vividnext.sodalive.content.series.main.banner.ContentSeriesBannerServiceTest" --tests "kr.co.vividnext.sodalive.admin.content.series.banner.AdminContentSeriesBannerControllerTest" --tests "kr.co.vividnext.sodalive.content.series.main.SeriesMainControllerTest"`를 실행해 등록 언어 저장, 관리자 목록 언어 표기, 사용자 언어별 배너 조회를 검증했다. 결과는 `BUILD SUCCESSFUL`이다.
|
||||||
11
docs/20260402_오디오콘텐츠배너언어적용.md
Normal file
11
docs/20260402_오디오콘텐츠배너언어적용.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
- [x] 오디오 콘텐츠 배너 등록·조회 경로와 언어 처리 기준을 확인한다.
|
||||||
|
- [x] 배너 등록 API에 `lang` 파라미터를 추가하고 지원 언어를 `Lang` 기준으로 저장하도록 수정한다.
|
||||||
|
- [x] 관리자 배너 목록은 전체 언어 배너를 유지하고, HomeService `fetchData`는 사용자 언어와 일치하는 배너만 조회하도록 수정한다.
|
||||||
|
- [x] 변경 파일 기준으로 검증을 수행하고 결과를 기록한다.
|
||||||
|
|
||||||
|
## 검증 기록
|
||||||
|
|
||||||
|
### 1차 구현
|
||||||
|
- 무엇을: 오디오 콘텐츠 배너 엔티티와 등록 요청에 `lang`을 추가하고, 홈 `fetchData`에서 현재 사용자 언어를 넘겨 해당 언어 배너만 조회하도록 수정했다. 운영 반영용으로 `content_banner.lang` 컬럼 DDL도 추가했다.
|
||||||
|
- 왜: 관리자 등록 시 언어별 배너를 구분해 저장해야 하고, 홈에서는 사용자 언어와 맞는 배너만 노출되어야 하기 때문이다. 관리자 목록 API는 기존처럼 언어 전체 배너를 그대로 조회해야 한다.
|
||||||
|
- 어떻게: Kotlin LSP가 없어 정적 진단은 Gradle 컴파일/테스트로 대체했고, `./gradlew test --tests "kr.co.vividnext.sodalive.admin.content.banner.AdminContentBannerServiceTest" --tests "kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerRepositoryTest" --tests "kr.co.vividnext.sodalive.api.home.HomeServiceTest"`로 등록 언어 저장, 언어별 배너 조회, 홈 언어 전달을 검증했다. 이어서 `./gradlew ktlintCheck`를 실행해 스타일 검증까지 확인했고 두 명령 모두 `BUILD SUCCESSFUL`이다.
|
||||||
@@ -62,7 +62,13 @@ class AdminChatBannerController(
|
|||||||
val banners = bannerService.getActiveBanners(pageable)
|
val banners = bannerService.getActiveBanners(pageable)
|
||||||
val response = ChatCharacterBannerListPageResponse(
|
val response = ChatCharacterBannerListPageResponse(
|
||||||
totalCount = banners.totalElements,
|
totalCount = banners.totalElements,
|
||||||
content = banners.content.map { ChatCharacterBannerResponse.from(it, imageHost) }
|
content = banners.content.map {
|
||||||
|
ChatCharacterBannerResponse.from(
|
||||||
|
banner = it,
|
||||||
|
imageHost = imageHost,
|
||||||
|
appendLanguageToCharacterName = true
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
ApiResponse.ok(response)
|
ApiResponse.ok(response)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package kr.co.vividnext.sodalive.admin.chat.dto
|
package kr.co.vividnext.sodalive.admin.chat.dto
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.chat.character.ChatCharacterBanner
|
import kr.co.vividnext.sodalive.chat.character.ChatCharacterBanner
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 캐릭터 배너 응답 DTO
|
* 캐릭터 배너 응답 DTO
|
||||||
@@ -12,14 +13,30 @@ data class ChatCharacterBannerResponse(
|
|||||||
val characterName: String
|
val characterName: String
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
fun from(banner: ChatCharacterBanner, imageHost: String): ChatCharacterBannerResponse {
|
fun from(
|
||||||
|
banner: ChatCharacterBanner,
|
||||||
|
imageHost: String,
|
||||||
|
appendLanguageToCharacterName: Boolean = false
|
||||||
|
): ChatCharacterBannerResponse {
|
||||||
return ChatCharacterBannerResponse(
|
return ChatCharacterBannerResponse(
|
||||||
id = banner.id!!,
|
id = banner.id!!,
|
||||||
imagePath = "$imageHost/${banner.imagePath}",
|
imagePath = "$imageHost/${banner.imagePath}",
|
||||||
characterId = banner.chatCharacter.id!!,
|
characterId = banner.chatCharacter.id!!,
|
||||||
characterName = banner.chatCharacter.name
|
characterName = if (appendLanguageToCharacterName) {
|
||||||
|
"${banner.chatCharacter.name} (${getLanguageLabel(banner.lang)})"
|
||||||
|
} else {
|
||||||
|
banner.chatCharacter.name
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getLanguageLabel(lang: Lang): String {
|
||||||
|
return when (lang) {
|
||||||
|
Lang.KO -> "한국어"
|
||||||
|
Lang.EN -> "영어"
|
||||||
|
Lang.JA -> "일본어"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ class AdminContentBannerService(
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
val audioContentBanner = AudioContentBanner(type = request.type)
|
val audioContentBanner = AudioContentBanner(type = request.type, lang = request.lang)
|
||||||
audioContentBanner.link = request.link
|
audioContentBanner.link = request.link
|
||||||
audioContentBanner.isAdult = request.isAdult
|
audioContentBanner.isAdult = request.isAdult
|
||||||
audioContentBanner.event = event
|
audioContentBanner.event = event
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package kr.co.vividnext.sodalive.admin.content.banner
|
package kr.co.vividnext.sodalive.admin.content.banner
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerType
|
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerType
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
|
|
||||||
data class CreateContentBannerRequest(
|
data class CreateContentBannerRequest(
|
||||||
val type: AudioContentBannerType,
|
val type: AudioContentBannerType,
|
||||||
|
val lang: Lang,
|
||||||
val tabId: Long?,
|
val tabId: Long?,
|
||||||
val eventId: Long?,
|
val eventId: Long?,
|
||||||
val creatorId: Long?,
|
val creatorId: Long?,
|
||||||
|
|||||||
@@ -56,7 +56,9 @@ class AdminContentSeriesBannerController(
|
|||||||
val banners = bannerService.getActiveBanners(pageable)
|
val banners = bannerService.getActiveBanners(pageable)
|
||||||
val response = SeriesBannerListPageResponse(
|
val response = SeriesBannerListPageResponse(
|
||||||
totalCount = banners.totalElements,
|
totalCount = banners.totalElements,
|
||||||
content = banners.content.map { SeriesBannerResponse.from(it, imageHost) }
|
content = banners.content.map {
|
||||||
|
SeriesBannerResponse.from(it, imageHost, appendLanguageToSeriesTitle = true)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
ApiResponse.ok(response)
|
ApiResponse.ok(response)
|
||||||
}
|
}
|
||||||
@@ -82,7 +84,7 @@ class AdminContentSeriesBannerController(
|
|||||||
val objectMapper = ObjectMapper()
|
val objectMapper = ObjectMapper()
|
||||||
val request = objectMapper.readValue(requestString, SeriesBannerRegisterRequest::class.java)
|
val request = objectMapper.readValue(requestString, SeriesBannerRegisterRequest::class.java)
|
||||||
|
|
||||||
val banner = bannerService.registerBanner(seriesId = request.seriesId, imagePath = "")
|
val banner = bannerService.registerBanner(seriesId = request.seriesId, imagePath = "", lang = request.lang)
|
||||||
val imagePath = saveImage(banner.id!!, image)
|
val imagePath = saveImage(banner.id!!, image)
|
||||||
val updatedBanner = bannerService.updateBanner(banner.id!!, imagePath)
|
val updatedBanner = bannerService.updateBanner(banner.id!!, imagePath)
|
||||||
val response = SeriesBannerResponse.from(updatedBanner, imageHost)
|
val response = SeriesBannerResponse.from(updatedBanner, imageHost)
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ package kr.co.vividnext.sodalive.admin.content.series.banner.dto
|
|||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import kr.co.vividnext.sodalive.content.series.main.banner.SeriesBanner
|
import kr.co.vividnext.sodalive.content.series.main.banner.SeriesBanner
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
|
|
||||||
// 시리즈 배너 등록 요청 DTO
|
// 시리즈 배너 등록 요청 DTO
|
||||||
data class SeriesBannerRegisterRequest(
|
data class SeriesBannerRegisterRequest(
|
||||||
@JsonProperty("seriesId") val seriesId: Long
|
@JsonProperty("seriesId") val seriesId: Long,
|
||||||
|
@JsonProperty("lang") val lang: Lang? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
// 시리즈 배너 수정 요청 DTO
|
// 시리즈 배너 수정 요청 DTO
|
||||||
@@ -22,14 +24,30 @@ data class SeriesBannerResponse(
|
|||||||
val seriesTitle: String
|
val seriesTitle: String
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
fun from(banner: SeriesBanner, imageHost: String): SeriesBannerResponse {
|
fun from(
|
||||||
|
banner: SeriesBanner,
|
||||||
|
imageHost: String,
|
||||||
|
appendLanguageToSeriesTitle: Boolean = false
|
||||||
|
): SeriesBannerResponse {
|
||||||
return SeriesBannerResponse(
|
return SeriesBannerResponse(
|
||||||
id = banner.id!!,
|
id = banner.id!!,
|
||||||
imagePath = "$imageHost/${banner.imagePath}",
|
imagePath = "$imageHost/${banner.imagePath}",
|
||||||
seriesId = banner.series.id!!,
|
seriesId = banner.series.id!!,
|
||||||
seriesTitle = banner.series.title
|
seriesTitle = if (appendLanguageToSeriesTitle) {
|
||||||
|
"${banner.series.title} (${getLanguageLabel(banner.lang)})"
|
||||||
|
} else {
|
||||||
|
banner.series.title
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getLanguageLabel(lang: Lang): String {
|
||||||
|
return when (lang) {
|
||||||
|
Lang.KO -> "한국어"
|
||||||
|
Lang.EN -> "영어"
|
||||||
|
Lang.JA -> "일본어"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -124,7 +124,8 @@ class HomeService(
|
|||||||
val bannerList = bannerService.getBannerList(
|
val bannerList = bannerService.getBannerList(
|
||||||
tabId = 1,
|
tabId = 1,
|
||||||
memberId = member?.id,
|
memberId = member?.id,
|
||||||
isAdult = isAdult
|
isAdult = isAdult,
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
|
|
||||||
val originalAudioDramaList = seriesService.getOriginalAudioDramaList(
|
val originalAudioDramaList = seriesService.getOriginalAudioDramaList(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import kr.co.vividnext.sodalive.common.BaseEntity
|
|||||||
import kr.co.vividnext.sodalive.content.main.tab.AudioContentMainTab
|
import kr.co.vividnext.sodalive.content.main.tab.AudioContentMainTab
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.Series
|
import kr.co.vividnext.sodalive.creator.admin.content.series.Series
|
||||||
import kr.co.vividnext.sodalive.event.Event
|
import kr.co.vividnext.sodalive.event.Event
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import javax.persistence.Column
|
import javax.persistence.Column
|
||||||
import javax.persistence.Entity
|
import javax.persistence.Entity
|
||||||
@@ -22,6 +23,9 @@ data class AudioContentBanner(
|
|||||||
@Enumerated(value = EnumType.STRING)
|
@Enumerated(value = EnumType.STRING)
|
||||||
var type: AudioContentBannerType,
|
var type: AudioContentBannerType,
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
|
@Enumerated(value = EnumType.STRING)
|
||||||
|
var lang: Lang = Lang.KO,
|
||||||
|
@Column(nullable = false)
|
||||||
var isAdult: Boolean = false,
|
var isAdult: Boolean = false,
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
var isActive: Boolean = true,
|
var isActive: Boolean = true,
|
||||||
|
|||||||
@@ -4,19 +4,20 @@ import com.querydsl.jpa.impl.JPAQueryFactory
|
|||||||
import kr.co.vividnext.sodalive.content.main.banner.QAudioContentBanner.audioContentBanner
|
import kr.co.vividnext.sodalive.content.main.banner.QAudioContentBanner.audioContentBanner
|
||||||
import kr.co.vividnext.sodalive.content.main.tab.QAudioContentMainTab.audioContentMainTab
|
import kr.co.vividnext.sodalive.content.main.tab.QAudioContentMainTab.audioContentMainTab
|
||||||
import kr.co.vividnext.sodalive.event.QEvent.event
|
import kr.co.vividnext.sodalive.event.QEvent.event
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.QMember.member
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
|
||||||
interface AudioContentBannerRepository : JpaRepository<AudioContentBanner, Long>, AudioContentBannerQueryRepository
|
interface AudioContentBannerRepository : JpaRepository<AudioContentBanner, Long>, AudioContentBannerQueryRepository
|
||||||
|
|
||||||
interface AudioContentBannerQueryRepository {
|
interface AudioContentBannerQueryRepository {
|
||||||
fun getAudioContentMainBannerList(tabId: Long, isAdult: Boolean): List<AudioContentBanner>
|
fun getAudioContentMainBannerList(tabId: Long, isAdult: Boolean, lang: Lang? = null): List<AudioContentBanner>
|
||||||
}
|
}
|
||||||
|
|
||||||
class AudioContentBannerQueryRepositoryImpl(
|
class AudioContentBannerQueryRepositoryImpl(
|
||||||
private val queryFactory: JPAQueryFactory
|
private val queryFactory: JPAQueryFactory
|
||||||
) : AudioContentBannerQueryRepository {
|
) : AudioContentBannerQueryRepository {
|
||||||
override fun getAudioContentMainBannerList(tabId: Long, isAdult: Boolean): List<AudioContentBanner> {
|
override fun getAudioContentMainBannerList(tabId: Long, isAdult: Boolean, lang: Lang?): List<AudioContentBanner> {
|
||||||
var where = audioContentBanner.isActive.isTrue
|
var where = audioContentBanner.isActive.isTrue
|
||||||
|
|
||||||
where = if (tabId == 1L) {
|
where = if (tabId == 1L) {
|
||||||
@@ -29,6 +30,10 @@ class AudioContentBannerQueryRepositoryImpl(
|
|||||||
where = where.and(audioContentBanner.isAdult.isFalse)
|
where = where.and(audioContentBanner.isAdult.isFalse)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (lang != null) {
|
||||||
|
where = where.and(audioContentBanner.lang.eq(lang))
|
||||||
|
}
|
||||||
|
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.selectFrom(audioContentBanner)
|
.selectFrom(audioContentBanner)
|
||||||
.leftJoin(audioContentBanner.tab, audioContentMainTab)
|
.leftJoin(audioContentBanner.tab, audioContentMainTab)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package kr.co.vividnext.sodalive.content.main.banner
|
package kr.co.vividnext.sodalive.content.main.banner
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.event.EventItem
|
import kr.co.vividnext.sodalive.event.EventItem
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
@@ -13,8 +14,8 @@ class AudioContentBannerService(
|
|||||||
@Value("\${cloud.aws.cloud-front.host}")
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
private val imageHost: String
|
private val imageHost: String
|
||||||
) {
|
) {
|
||||||
fun getBannerList(tabId: Long, memberId: Long?, isAdult: Boolean): List<GetAudioContentBannerResponse> {
|
fun getBannerList(tabId: Long, memberId: Long?, isAdult: Boolean, lang: Lang? = null): List<GetAudioContentBannerResponse> {
|
||||||
return repository.getAudioContentMainBannerList(tabId, isAdult)
|
return repository.getAudioContentMainBannerList(tabId, isAdult, lang)
|
||||||
.filter {
|
.filter {
|
||||||
if (it.type == AudioContentBannerType.CREATOR && it.creator != null && memberId != null) {
|
if (it.type == AudioContentBannerType.CREATOR && it.creator != null && memberId != null) {
|
||||||
!isBlockedBetweenMembers(memberId = memberId, creatorId = it.creator!!.id!!)
|
!isBlockedBetweenMembers(memberId = memberId, creatorId = it.creator!!.id!!)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import kr.co.vividnext.sodalive.common.SodaException
|
|||||||
import kr.co.vividnext.sodalive.content.series.ContentSeriesService
|
import kr.co.vividnext.sodalive.content.series.ContentSeriesService
|
||||||
import kr.co.vividnext.sodalive.content.series.main.banner.ContentSeriesBannerService
|
import kr.co.vividnext.sodalive.content.series.main.banner.ContentSeriesBannerService
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
|
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
@@ -21,6 +22,7 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
class SeriesMainController(
|
class SeriesMainController(
|
||||||
private val contentSeriesService: ContentSeriesService,
|
private val contentSeriesService: ContentSeriesService,
|
||||||
private val bannerService: ContentSeriesBannerService,
|
private val bannerService: ContentSeriesBannerService,
|
||||||
|
private val langContext: LangContext,
|
||||||
private val memberContentPreferenceService: MemberContentPreferenceService,
|
private val memberContentPreferenceService: MemberContentPreferenceService,
|
||||||
|
|
||||||
@Value("\${cloud.aws.cloud-front.host}")
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
@@ -33,7 +35,7 @@ class SeriesMainController(
|
|||||||
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
|
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
|
||||||
val preference = resolvePreference(member)
|
val preference = resolvePreference(member)
|
||||||
|
|
||||||
val banners = bannerService.getActiveBanners(PageRequest.of(0, 10))
|
val banners = bannerService.getDisplayBanners(PageRequest.of(0, 10), langContext.lang)
|
||||||
.content
|
.content
|
||||||
.map {
|
.map {
|
||||||
SeriesBannerResponse.from(it, imageHost)
|
SeriesBannerResponse.from(it, imageHost)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.content.series.main.banner
|
|||||||
|
|
||||||
import kr.co.vividnext.sodalive.admin.content.series.AdminContentSeriesRepository
|
import kr.co.vividnext.sodalive.admin.content.series.AdminContentSeriesRepository
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import org.springframework.data.domain.Page
|
import org.springframework.data.domain.Page
|
||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
@@ -16,22 +17,28 @@ class ContentSeriesBannerService(
|
|||||||
return bannerRepository.findByIsActiveTrueOrderBySortOrderAsc(pageable)
|
return bannerRepository.findByIsActiveTrueOrderBySortOrderAsc(pageable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getDisplayBanners(pageable: Pageable, lang: Lang): Page<SeriesBanner> {
|
||||||
|
return bannerRepository.findByIsActiveTrueAndLangOrderBySortOrderAsc(lang, pageable)
|
||||||
|
}
|
||||||
|
|
||||||
fun getBannerById(bannerId: Long): SeriesBanner {
|
fun getBannerById(bannerId: Long): SeriesBanner {
|
||||||
return bannerRepository.findById(bannerId)
|
return bannerRepository.findById(bannerId)
|
||||||
.orElseThrow { SodaException(messageKey = "series.banner.error.not_found") }
|
.orElseThrow { SodaException(messageKey = "series.banner.error.not_found") }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun registerBanner(seriesId: Long, imagePath: String): SeriesBanner {
|
fun registerBanner(seriesId: Long, imagePath: String, lang: Lang? = null): SeriesBanner {
|
||||||
val series = seriesRepository.findByIdAndActiveTrue(seriesId)
|
val series = seriesRepository.findByIdAndActiveTrue(seriesId)
|
||||||
?: throw SodaException(messageKey = "series.banner.error.series_not_found")
|
?: throw SodaException(messageKey = "series.banner.error.series_not_found")
|
||||||
|
|
||||||
val finalSortOrder = (bannerRepository.findMaxSortOrder() ?: 0) + 1
|
val finalSortOrder = (bannerRepository.findMaxSortOrder() ?: 0) + 1
|
||||||
|
val finalLang = lang ?: Lang.KO
|
||||||
|
|
||||||
val banner = SeriesBanner(
|
val banner = SeriesBanner(
|
||||||
imagePath = imagePath,
|
imagePath = imagePath,
|
||||||
series = series,
|
series = series,
|
||||||
sortOrder = finalSortOrder
|
sortOrder = finalSortOrder,
|
||||||
|
lang = finalLang
|
||||||
)
|
)
|
||||||
return bannerRepository.save(banner)
|
return bannerRepository.save(banner)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,11 @@ package kr.co.vividnext.sodalive.content.series.main.banner
|
|||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.BaseEntity
|
import kr.co.vividnext.sodalive.common.BaseEntity
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.Series
|
import kr.co.vividnext.sodalive.creator.admin.content.series.Series
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
|
import javax.persistence.Column
|
||||||
import javax.persistence.Entity
|
import javax.persistence.Entity
|
||||||
|
import javax.persistence.EnumType
|
||||||
|
import javax.persistence.Enumerated
|
||||||
import javax.persistence.FetchType
|
import javax.persistence.FetchType
|
||||||
import javax.persistence.JoinColumn
|
import javax.persistence.JoinColumn
|
||||||
import javax.persistence.ManyToOne
|
import javax.persistence.ManyToOne
|
||||||
@@ -25,6 +29,10 @@ class SeriesBanner(
|
|||||||
// 정렬 순서 (낮을수록 먼저 표시)
|
// 정렬 순서 (낮을수록 먼저 표시)
|
||||||
var sortOrder: Int = 0,
|
var sortOrder: Int = 0,
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
var lang: Lang = Lang.KO,
|
||||||
|
|
||||||
// 활성화 여부 (소프트 삭제용)
|
// 활성화 여부 (소프트 삭제용)
|
||||||
var isActive: Boolean = true
|
var isActive: Boolean = true
|
||||||
) : BaseEntity()
|
) : BaseEntity()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.content.series.main.banner
|
package kr.co.vividnext.sodalive.content.series.main.banner
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import org.springframework.data.domain.Page
|
import org.springframework.data.domain.Page
|
||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
@@ -10,6 +11,8 @@ import org.springframework.stereotype.Repository
|
|||||||
interface SeriesBannerRepository : JpaRepository<SeriesBanner, Long> {
|
interface SeriesBannerRepository : JpaRepository<SeriesBanner, Long> {
|
||||||
fun findByIsActiveTrueOrderBySortOrderAsc(pageable: Pageable): Page<SeriesBanner>
|
fun findByIsActiveTrueOrderBySortOrderAsc(pageable: Pageable): Page<SeriesBanner>
|
||||||
|
|
||||||
|
fun findByIsActiveTrueAndLangOrderBySortOrderAsc(lang: Lang, pageable: Pageable): Page<SeriesBanner>
|
||||||
|
|
||||||
@Query("SELECT MAX(b.sortOrder) FROM SeriesBanner b WHERE b.isActive = true")
|
@Query("SELECT MAX(b.sortOrder) FROM SeriesBanner b WHERE b.isActive = true")
|
||||||
fun findMaxSortOrder(): Int?
|
fun findMaxSortOrder(): Int?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import org.junit.jupiter.api.Assertions.assertEquals
|
|||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.mockito.Mockito
|
import org.mockito.Mockito
|
||||||
|
import org.springframework.data.domain.PageImpl
|
||||||
|
import org.springframework.data.domain.PageRequest
|
||||||
import org.springframework.mock.web.MockMultipartFile
|
import org.springframework.mock.web.MockMultipartFile
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
@@ -107,6 +109,21 @@ class AdminChatBannerControllerTest {
|
|||||||
Mockito.verify(bannerService).registerBanner(2L, "", null)
|
Mockito.verify(bannerService).registerBanner(2L, "", null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldAppendBannerLanguageToCharacterNameInBannerList() {
|
||||||
|
val pageable = PageRequest.of(0, 20)
|
||||||
|
val japaneseBanner = createBanner(id = 12L, lang = Lang.JA, imagePath = "banner/jp.png")
|
||||||
|
|
||||||
|
Mockito.`when`(adminCharacterService.createDefaultPageRequest(0, 20)).thenReturn(pageable)
|
||||||
|
Mockito.`when`(bannerService.getActiveBanners(pageable))
|
||||||
|
.thenReturn(PageImpl(listOf(japaneseBanner), pageable, 1))
|
||||||
|
|
||||||
|
val response = controller.getBannerList(page = 0, size = 20)
|
||||||
|
|
||||||
|
assertTrue(response.success)
|
||||||
|
assertEquals("character-12 (일본어)", response.data?.content?.first()?.characterName)
|
||||||
|
}
|
||||||
|
|
||||||
private fun createBanner(id: Long, lang: Lang, imagePath: String): ChatCharacterBanner {
|
private fun createBanner(id: Long, lang: Lang, imagePath: String): ChatCharacterBanner {
|
||||||
val character = ChatCharacter(
|
val character = ChatCharacter(
|
||||||
characterUUID = "character-$id",
|
characterUUID = "character-$id",
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package kr.co.vividnext.sodalive.admin.content.banner
|
||||||
|
|
||||||
|
import com.amazonaws.services.s3.AmazonS3Client
|
||||||
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
|
import kr.co.vividnext.sodalive.admin.content.series.AdminContentSeriesRepository
|
||||||
|
import kr.co.vividnext.sodalive.admin.content.tab.AdminContentMainTabRepository
|
||||||
|
import kr.co.vividnext.sodalive.aws.s3.S3Uploader
|
||||||
|
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBanner
|
||||||
|
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerType
|
||||||
|
import kr.co.vividnext.sodalive.event.EventRepository
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.ArgumentCaptor
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.springframework.mock.web.MockMultipartFile
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
|
class AdminContentBannerServiceTest {
|
||||||
|
private lateinit var amazonS3Client: AmazonS3Client
|
||||||
|
private lateinit var s3Uploader: S3Uploader
|
||||||
|
private lateinit var repository: AdminContentBannerRepository
|
||||||
|
private lateinit var memberRepository: MemberRepository
|
||||||
|
private lateinit var seriesRepository: AdminContentSeriesRepository
|
||||||
|
private lateinit var eventRepository: EventRepository
|
||||||
|
private lateinit var contentMainTabRepository: AdminContentMainTabRepository
|
||||||
|
private lateinit var service: AdminContentBannerService
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setUp() {
|
||||||
|
amazonS3Client = Mockito.mock(AmazonS3Client::class.java)
|
||||||
|
s3Uploader = S3Uploader(amazonS3Client)
|
||||||
|
repository = Mockito.mock(AdminContentBannerRepository::class.java)
|
||||||
|
memberRepository = Mockito.mock(MemberRepository::class.java)
|
||||||
|
seriesRepository = Mockito.mock(AdminContentSeriesRepository::class.java)
|
||||||
|
eventRepository = Mockito.mock(EventRepository::class.java)
|
||||||
|
contentMainTabRepository = Mockito.mock(AdminContentMainTabRepository::class.java)
|
||||||
|
service = AdminContentBannerService(
|
||||||
|
s3Uploader = s3Uploader,
|
||||||
|
repository = repository,
|
||||||
|
memberRepository = memberRepository,
|
||||||
|
seriesRepository = seriesRepository,
|
||||||
|
eventRepository = eventRepository,
|
||||||
|
contentMainTabRepository = contentMainTabRepository,
|
||||||
|
objectMapper = jacksonObjectMapper(),
|
||||||
|
bucket = "test-bucket"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("배너 등록 요청의 lang 값을 저장한다")
|
||||||
|
fun shouldSaveRequestedLangWhenCreatingBanner() {
|
||||||
|
val image = MockMultipartFile("image", "banner.png", "image/png", "image".toByteArray())
|
||||||
|
val requestString = """
|
||||||
|
{"type":"LINK","lang":"ja","tabId":null,"eventId":null,"creatorId":null,"seriesId":null,"link":"https://example.com","isAdult":false}
|
||||||
|
""".trimIndent()
|
||||||
|
val savedBannerCaptor = ArgumentCaptor.forClass(AudioContentBanner::class.java)
|
||||||
|
|
||||||
|
Mockito.`when`(repository.save(Mockito.any(AudioContentBanner::class.java)))
|
||||||
|
.thenAnswer {
|
||||||
|
(it.arguments[0] as AudioContentBanner).also { banner ->
|
||||||
|
banner.id = 1L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Mockito.doAnswer { URL("https://cdn.test/${it.arguments[1]}") }
|
||||||
|
.`when`(amazonS3Client)
|
||||||
|
.getUrl(Mockito.eq("test-bucket"), Mockito.anyString())
|
||||||
|
|
||||||
|
service.createAudioContentMainBanner(image, requestString)
|
||||||
|
|
||||||
|
Mockito.verify(repository).save(savedBannerCaptor.capture())
|
||||||
|
assertEquals(AudioContentBannerType.LINK, savedBannerCaptor.value.type)
|
||||||
|
assertEquals(Lang.JA, savedBannerCaptor.value.lang)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
package kr.co.vividnext.sodalive.admin.content.series.banner
|
||||||
|
|
||||||
|
import com.amazonaws.services.s3.AmazonS3Client
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import kr.co.vividnext.sodalive.aws.s3.S3Uploader
|
||||||
|
import kr.co.vividnext.sodalive.content.series.main.banner.ContentSeriesBannerService
|
||||||
|
import kr.co.vividnext.sodalive.content.series.main.banner.SeriesBanner
|
||||||
|
import kr.co.vividnext.sodalive.creator.admin.content.series.Series
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
|
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.springframework.data.domain.PageImpl
|
||||||
|
import org.springframework.data.domain.PageRequest
|
||||||
|
import org.springframework.mock.web.MockMultipartFile
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
|
class AdminContentSeriesBannerControllerTest {
|
||||||
|
private val bannerService = Mockito.mock(ContentSeriesBannerService::class.java)
|
||||||
|
private val amazonS3Client = Mockito.mock(AmazonS3Client::class.java)
|
||||||
|
private val s3Uploader = S3Uploader(amazonS3Client)
|
||||||
|
private val controller = AdminContentSeriesBannerController(
|
||||||
|
bannerService = bannerService,
|
||||||
|
s3Uploader = s3Uploader,
|
||||||
|
langContext = LangContext(),
|
||||||
|
messageSource = SodaMessageSource(),
|
||||||
|
s3Bucket = "test-bucket",
|
||||||
|
imageHost = "https://cdn.test"
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldRegisterJapaneseBannerThroughAdminApi() {
|
||||||
|
val image = MockMultipartFile("image", "banner.png", "image/png", "image".toByteArray())
|
||||||
|
val registeredBanner = createBanner(id = 10L, lang = Lang.JA, imagePath = "")
|
||||||
|
val updatedBanner = createBanner(id = 10L, lang = Lang.JA, imagePath = "")
|
||||||
|
|
||||||
|
Mockito.`when`(amazonS3Client.getUrl(Mockito.eq("test-bucket"), Mockito.anyString()))
|
||||||
|
.thenAnswer { URL("https://cdn.test/${it.arguments[1]}") }
|
||||||
|
|
||||||
|
Mockito.`when`(
|
||||||
|
bannerService.registerBanner(
|
||||||
|
seriesId = 1L,
|
||||||
|
imagePath = "",
|
||||||
|
lang = Lang.JA
|
||||||
|
)
|
||||||
|
).thenReturn(registeredBanner)
|
||||||
|
Mockito.doAnswer {
|
||||||
|
updatedBanner.apply {
|
||||||
|
imagePath = it.arguments[1] as String
|
||||||
|
}
|
||||||
|
}.`when`(bannerService).updateBanner(Mockito.eq(10L), Mockito.anyString(), Mockito.isNull())
|
||||||
|
|
||||||
|
val response = controller.registerBanner(
|
||||||
|
image = image,
|
||||||
|
requestString = "{\"seriesId\":1,\"lang\":\"ja\"}"
|
||||||
|
)
|
||||||
|
|
||||||
|
assertTrue(response.success)
|
||||||
|
assertEquals(10L, response.data?.id)
|
||||||
|
assertTrue(response.data?.imagePath?.startsWith("https://cdn.test/series_banner/10/") == true)
|
||||||
|
Mockito.verify(bannerService).registerBanner(1L, "", Lang.JA)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldDeserializeIso639LanguageCodeToLangEnum() {
|
||||||
|
val request = ObjectMapper().readValue(
|
||||||
|
"{\"seriesId\":1,\"lang\":\"en\"}",
|
||||||
|
kr.co.vividnext.sodalive.admin.content.series.banner.dto.SeriesBannerRegisterRequest::class.java
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(Lang.EN, request.lang)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldAppendBannerLanguageToSeriesTitleInBannerList() {
|
||||||
|
val pageable = PageRequest.of(0, 20)
|
||||||
|
val japaneseBanner = createBanner(id = 12L, lang = Lang.JA, imagePath = "banner/jp.png")
|
||||||
|
|
||||||
|
Mockito.`when`(bannerService.getActiveBanners(pageable))
|
||||||
|
.thenReturn(PageImpl(listOf(japaneseBanner), pageable, 1))
|
||||||
|
|
||||||
|
val response = controller.getBannerList(page = 0, size = 20)
|
||||||
|
|
||||||
|
assertTrue(response.success)
|
||||||
|
assertEquals("series-12 (일본어)", response.data?.content?.first()?.seriesTitle)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createBanner(id: Long, lang: Lang, imagePath: String): SeriesBanner {
|
||||||
|
val series = Series(
|
||||||
|
title = "series-$id",
|
||||||
|
introduction = "introduction-$id",
|
||||||
|
languageCode = "ko"
|
||||||
|
)
|
||||||
|
series.id = id
|
||||||
|
|
||||||
|
return SeriesBanner(
|
||||||
|
imagePath = imagePath,
|
||||||
|
series = series,
|
||||||
|
sortOrder = 1,
|
||||||
|
lang = lang
|
||||||
|
).also {
|
||||||
|
it.id = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,314 @@
|
|||||||
|
package kr.co.vividnext.sodalive.api.home
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.audition.AuditionService
|
||||||
|
import kr.co.vividnext.sodalive.chat.character.service.ChatCharacterService
|
||||||
|
import kr.co.vividnext.sodalive.content.AudioContentService
|
||||||
|
import kr.co.vividnext.sodalive.content.ContentType
|
||||||
|
import kr.co.vividnext.sodalive.content.SortType
|
||||||
|
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService
|
||||||
|
import kr.co.vividnext.sodalive.content.series.ContentSeriesService
|
||||||
|
import kr.co.vividnext.sodalive.content.theme.AudioContentThemeService
|
||||||
|
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
|
||||||
|
import kr.co.vividnext.sodalive.explorer.ExplorerQueryRepository
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
|
import kr.co.vividnext.sodalive.live.room.LiveRoomService
|
||||||
|
import kr.co.vividnext.sodalive.live.room.LiveRoomStatus
|
||||||
|
import kr.co.vividnext.sodalive.query.recommend.RecommendChannelQueryService
|
||||||
|
import kr.co.vividnext.sodalive.rank.ContentRankingSortType
|
||||||
|
import kr.co.vividnext.sodalive.rank.RankingRepository
|
||||||
|
import kr.co.vividnext.sodalive.rank.RankingService
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.springframework.data.domain.Pageable
|
||||||
|
import java.time.DayOfWeek
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.ZoneId
|
||||||
|
|
||||||
|
class HomeServiceTest {
|
||||||
|
private lateinit var liveRoomService: LiveRoomService
|
||||||
|
private lateinit var auditionService: AuditionService
|
||||||
|
private lateinit var seriesService: ContentSeriesService
|
||||||
|
private lateinit var contentService: AudioContentService
|
||||||
|
private lateinit var bannerService: AudioContentBannerService
|
||||||
|
private lateinit var contentThemeService: AudioContentThemeService
|
||||||
|
private lateinit var recommendChannelService: RecommendChannelQueryService
|
||||||
|
private lateinit var characterService: ChatCharacterService
|
||||||
|
private lateinit var rankingService: RankingService
|
||||||
|
private lateinit var rankingRepository: RankingRepository
|
||||||
|
private lateinit var explorerQueryRepository: ExplorerQueryRepository
|
||||||
|
private lateinit var service: HomeService
|
||||||
|
private val timezone = "Asia/Seoul"
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setUp() {
|
||||||
|
liveRoomService = Mockito.mock(LiveRoomService::class.java)
|
||||||
|
auditionService = Mockito.mock(AuditionService::class.java)
|
||||||
|
seriesService = Mockito.mock(ContentSeriesService::class.java)
|
||||||
|
contentService = Mockito.mock(AudioContentService::class.java)
|
||||||
|
bannerService = Mockito.mock(AudioContentBannerService::class.java)
|
||||||
|
contentThemeService = Mockito.mock(AudioContentThemeService::class.java)
|
||||||
|
recommendChannelService = Mockito.mock(RecommendChannelQueryService::class.java)
|
||||||
|
characterService = Mockito.mock(ChatCharacterService::class.java)
|
||||||
|
rankingService = Mockito.mock(RankingService::class.java)
|
||||||
|
rankingRepository = Mockito.mock(RankingRepository::class.java)
|
||||||
|
explorerQueryRepository = Mockito.mock(ExplorerQueryRepository::class.java)
|
||||||
|
service = HomeService(
|
||||||
|
liveRoomService = liveRoomService,
|
||||||
|
auditionService = auditionService,
|
||||||
|
seriesService = seriesService,
|
||||||
|
contentService = contentService,
|
||||||
|
bannerService = bannerService,
|
||||||
|
contentThemeService = contentThemeService,
|
||||||
|
recommendChannelService = recommendChannelService,
|
||||||
|
characterService = characterService,
|
||||||
|
rankingService = rankingService,
|
||||||
|
rankingRepository = rankingRepository,
|
||||||
|
explorerQueryRepository = explorerQueryRepository,
|
||||||
|
langContext = LangContext().apply { setLang(Lang.JA) },
|
||||||
|
memberContentPreferenceService = Mockito.mock(
|
||||||
|
kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService::class.java
|
||||||
|
),
|
||||||
|
imageHost = "https://cdn.test"
|
||||||
|
)
|
||||||
|
|
||||||
|
val systemTime = LocalDateTime.now()
|
||||||
|
val zonedDateTime = systemTime
|
||||||
|
.atZone(ZoneId.systemDefault())
|
||||||
|
.withZoneSameInstant(ZoneId.of(timezone))
|
||||||
|
val expectedDayOfWeek = when (zonedDateTime.dayOfWeek) {
|
||||||
|
DayOfWeek.MONDAY -> SeriesPublishedDaysOfWeek.MON
|
||||||
|
DayOfWeek.TUESDAY -> SeriesPublishedDaysOfWeek.TUE
|
||||||
|
DayOfWeek.WEDNESDAY -> SeriesPublishedDaysOfWeek.WED
|
||||||
|
DayOfWeek.THURSDAY -> SeriesPublishedDaysOfWeek.THU
|
||||||
|
DayOfWeek.FRIDAY -> SeriesPublishedDaysOfWeek.FRI
|
||||||
|
DayOfWeek.SATURDAY -> SeriesPublishedDaysOfWeek.SAT
|
||||||
|
DayOfWeek.SUNDAY -> SeriesPublishedDaysOfWeek.SUN
|
||||||
|
null -> SeriesPublishedDaysOfWeek.RANDOM
|
||||||
|
}
|
||||||
|
val currentDateTime = LocalDateTime.now()
|
||||||
|
val rankingStartDate = currentDateTime
|
||||||
|
.withHour(15)
|
||||||
|
.withMinute(0)
|
||||||
|
.withSecond(0)
|
||||||
|
.minusWeeks(1)
|
||||||
|
.with(java.time.temporal.TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))
|
||||||
|
val rankingEndDate = rankingStartDate.plusDays(6)
|
||||||
|
|
||||||
|
Mockito.doReturn(emptyList<Any>()).`when`(liveRoomService)
|
||||||
|
.getRoomList(null, LiveRoomStatus.NOW, Pageable.ofSize(10), null, timezone)
|
||||||
|
Mockito.`when`(rankingRepository.getCreatorRankings(null)).thenReturn(emptyList())
|
||||||
|
Mockito.`when`(
|
||||||
|
contentThemeService.getActiveThemeOfContent(
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
ContentType.ALL,
|
||||||
|
listOf("다시듣기")
|
||||||
|
)
|
||||||
|
).thenReturn(emptyList())
|
||||||
|
Mockito.`when`(
|
||||||
|
contentThemeService.getActiveThemeOfContent(
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
ContentType.ALL,
|
||||||
|
emptyList()
|
||||||
|
)
|
||||||
|
).thenReturn(emptyList())
|
||||||
|
Mockito.`when`(
|
||||||
|
contentService.getLatestContentByTheme(
|
||||||
|
null,
|
||||||
|
emptyList(),
|
||||||
|
ContentType.ALL,
|
||||||
|
0,
|
||||||
|
20,
|
||||||
|
SortType.NEWEST,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
emptyList()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.thenReturn(emptyList())
|
||||||
|
Mockito.`when`(
|
||||||
|
contentService.getLatestContentByTheme(
|
||||||
|
null,
|
||||||
|
emptyList(),
|
||||||
|
ContentType.ALL,
|
||||||
|
0,
|
||||||
|
50,
|
||||||
|
SortType.NEWEST,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
emptyList()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.thenReturn(emptyList())
|
||||||
|
Mockito.`when`(
|
||||||
|
contentService.getLatestContentByTheme(
|
||||||
|
null,
|
||||||
|
emptyList(),
|
||||||
|
ContentType.ALL,
|
||||||
|
50,
|
||||||
|
100,
|
||||||
|
SortType.NEWEST,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
emptyList()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.thenReturn(emptyList())
|
||||||
|
Mockito.`when`(
|
||||||
|
contentService.getLatestContentByTheme(
|
||||||
|
null,
|
||||||
|
emptyList(),
|
||||||
|
ContentType.ALL,
|
||||||
|
150,
|
||||||
|
150,
|
||||||
|
SortType.NEWEST,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
emptyList()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.thenReturn(emptyList())
|
||||||
|
Mockito.`when`(
|
||||||
|
contentService.getLatestContentByTheme(
|
||||||
|
null,
|
||||||
|
emptyList(),
|
||||||
|
ContentType.ALL,
|
||||||
|
0,
|
||||||
|
50,
|
||||||
|
SortType.NEWEST,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
emptyList()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.thenReturn(emptyList())
|
||||||
|
Mockito.`when`(
|
||||||
|
contentService.getLatestContentByTheme(
|
||||||
|
null,
|
||||||
|
emptyList(),
|
||||||
|
ContentType.ALL,
|
||||||
|
50,
|
||||||
|
100,
|
||||||
|
SortType.NEWEST,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
emptyList()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.thenReturn(emptyList())
|
||||||
|
Mockito.`when`(
|
||||||
|
contentService.getLatestContentByTheme(
|
||||||
|
null,
|
||||||
|
emptyList(),
|
||||||
|
ContentType.ALL,
|
||||||
|
150,
|
||||||
|
150,
|
||||||
|
SortType.NEWEST,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
emptyList()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.thenReturn(emptyList())
|
||||||
|
Mockito.`when`(
|
||||||
|
contentService.getLatestContentByTheme(
|
||||||
|
null,
|
||||||
|
emptyList(),
|
||||||
|
ContentType.ALL,
|
||||||
|
0,
|
||||||
|
50,
|
||||||
|
SortType.NEWEST,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
emptyList()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.thenReturn(emptyList())
|
||||||
|
Mockito.`when`(
|
||||||
|
contentService.getLatestContentByTheme(
|
||||||
|
null,
|
||||||
|
emptyList(),
|
||||||
|
ContentType.ALL,
|
||||||
|
50,
|
||||||
|
100,
|
||||||
|
SortType.NEWEST,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
emptyList()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.thenReturn(emptyList())
|
||||||
|
Mockito.`when`(
|
||||||
|
contentService.getLatestContentByTheme(
|
||||||
|
null,
|
||||||
|
emptyList(),
|
||||||
|
ContentType.ALL,
|
||||||
|
150,
|
||||||
|
150,
|
||||||
|
SortType.NEWEST,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
emptyList()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.thenReturn(emptyList())
|
||||||
|
Mockito.`when`(bannerService.getBannerList(1L, null, false, Lang.JA)).thenReturn(emptyList())
|
||||||
|
Mockito.`when`(
|
||||||
|
seriesService.getOriginalAudioDramaList(null, false, ContentType.ALL, 0, 20)
|
||||||
|
).thenReturn(emptyList())
|
||||||
|
Mockito.`when`(auditionService.getInProgressAuditionList(false)).thenReturn(emptyList())
|
||||||
|
Mockito.`when`(
|
||||||
|
seriesService.getDayOfWeekSeriesList(null, false, ContentType.ALL, expectedDayOfWeek, 0, 10)
|
||||||
|
).thenReturn(emptyList())
|
||||||
|
Mockito.`when`(characterService.getPopularCharacters("ja", 20)).thenReturn(emptyList())
|
||||||
|
Mockito.`when`(
|
||||||
|
rankingService.getContentRanking(
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
ContentType.ALL,
|
||||||
|
rankingStartDate.minusDays(1),
|
||||||
|
rankingEndDate,
|
||||||
|
0,
|
||||||
|
12,
|
||||||
|
ContentRankingSortType.REVENUE,
|
||||||
|
""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.thenReturn(emptyList())
|
||||||
|
Mockito.`when`(recommendChannelService.getRecommendChannel(null, false, ContentType.ALL)).thenReturn(emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("홈 fetchData는 현재 요청 언어를 배너 조회에 전달한다")
|
||||||
|
fun shouldPassCurrentLangToBannerServiceWhenFetchingHome() {
|
||||||
|
service.fetchData(timezone = timezone, member = null)
|
||||||
|
|
||||||
|
Mockito.verify(bannerService).getBannerList(1L, null, false, Lang.JA)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package kr.co.vividnext.sodalive.content.main.banner
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.configs.QueryDslConfig
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
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
|
||||||
|
|
||||||
|
@DataJpaTest
|
||||||
|
@Import(QueryDslConfig::class)
|
||||||
|
class AudioContentBannerRepositoryTest @Autowired constructor(
|
||||||
|
private val repository: AudioContentBannerRepository
|
||||||
|
) {
|
||||||
|
@Test
|
||||||
|
@DisplayName("사용자 배너 조회는 요청 언어와 일치하는 배너만 반환한다")
|
||||||
|
fun shouldReturnOnlyRequestedLanguageBanners() {
|
||||||
|
repository.saveAndFlush(
|
||||||
|
AudioContentBanner(
|
||||||
|
thumbnailImage = "banner/ko.png",
|
||||||
|
type = AudioContentBannerType.LINK,
|
||||||
|
lang = Lang.KO
|
||||||
|
).apply {
|
||||||
|
link = "https://ko.example.com"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
repository.saveAndFlush(
|
||||||
|
AudioContentBanner(
|
||||||
|
thumbnailImage = "banner/ja.png",
|
||||||
|
type = AudioContentBannerType.LINK,
|
||||||
|
lang = Lang.JA
|
||||||
|
).apply {
|
||||||
|
link = "https://ja.example.com"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
val banners = repository.getAudioContentMainBannerList(tabId = 1L, isAdult = false, lang = Lang.JA)
|
||||||
|
|
||||||
|
assertEquals(1, banners.size)
|
||||||
|
assertEquals(Lang.JA, banners.first().lang)
|
||||||
|
assertEquals("https://ja.example.com", banners.first().link)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package kr.co.vividnext.sodalive.content.series.main
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.content.ContentType
|
||||||
|
import kr.co.vividnext.sodalive.content.series.ContentSeriesService
|
||||||
|
import kr.co.vividnext.sodalive.content.series.GetSeriesListResponse
|
||||||
|
import kr.co.vividnext.sodalive.content.series.main.banner.ContentSeriesBannerService
|
||||||
|
import kr.co.vividnext.sodalive.content.series.main.banner.SeriesBanner
|
||||||
|
import kr.co.vividnext.sodalive.creator.admin.content.series.Series
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||||
|
import kr.co.vividnext.sodalive.member.contentpreference.ViewerContentPreference
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.springframework.data.domain.PageImpl
|
||||||
|
import org.springframework.data.domain.PageRequest
|
||||||
|
|
||||||
|
class SeriesMainControllerTest {
|
||||||
|
private val contentSeriesService = Mockito.mock(ContentSeriesService::class.java)
|
||||||
|
private val bannerService = Mockito.mock(ContentSeriesBannerService::class.java)
|
||||||
|
private val langContext = LangContext().apply { setLang(Lang.JA) }
|
||||||
|
private val memberContentPreferenceService = Mockito.mock(MemberContentPreferenceService::class.java)
|
||||||
|
private val controller = SeriesMainController(
|
||||||
|
contentSeriesService = contentSeriesService,
|
||||||
|
bannerService = bannerService,
|
||||||
|
langContext = langContext,
|
||||||
|
memberContentPreferenceService = memberContentPreferenceService,
|
||||||
|
imageHost = "https://cdn.test"
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldFetchOnlyRequestedLanguageBanners() {
|
||||||
|
val member = createMember(id = 1L)
|
||||||
|
val preference = ViewerContentPreference(
|
||||||
|
countryCode = "KR",
|
||||||
|
isAdultContentVisible = true,
|
||||||
|
contentType = ContentType.ALL,
|
||||||
|
isAdult = true
|
||||||
|
)
|
||||||
|
val pageable = PageRequest.of(0, 10)
|
||||||
|
val japaneseBanner = SeriesBanner(
|
||||||
|
imagePath = "banner/jp.png",
|
||||||
|
series = createSeries(id = 10L),
|
||||||
|
sortOrder = 1,
|
||||||
|
lang = Lang.JA
|
||||||
|
).also {
|
||||||
|
it.id = 100L
|
||||||
|
}
|
||||||
|
|
||||||
|
Mockito.`when`(memberContentPreferenceService.resolveForQuery(member)).thenReturn(preference)
|
||||||
|
Mockito.`when`(bannerService.getDisplayBanners(pageable, Lang.JA))
|
||||||
|
.thenReturn(PageImpl(listOf(japaneseBanner), pageable, 1))
|
||||||
|
Mockito.`when`(
|
||||||
|
contentSeriesService.getSeriesList(
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
ContentType.ALL,
|
||||||
|
member,
|
||||||
|
0,
|
||||||
|
20
|
||||||
|
)
|
||||||
|
).thenReturn(GetSeriesListResponse(totalCount = 0, items = emptyList()))
|
||||||
|
Mockito.`when`(
|
||||||
|
contentSeriesService.getRecommendSeriesList(
|
||||||
|
true,
|
||||||
|
ContentType.ALL,
|
||||||
|
member
|
||||||
|
)
|
||||||
|
).thenReturn(emptyList())
|
||||||
|
|
||||||
|
val response = controller.fetchData(member)
|
||||||
|
|
||||||
|
assertTrue(response.success)
|
||||||
|
assertEquals(1, response.data?.banners?.size)
|
||||||
|
assertEquals("series-10", response.data?.banners?.first()?.seriesTitle)
|
||||||
|
Mockito.verify(bannerService).getDisplayBanners(pageable, Lang.JA)
|
||||||
|
Mockito.verify(bannerService, Mockito.never()).getActiveBanners(pageable)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createMember(id: Long): Member {
|
||||||
|
return Member(
|
||||||
|
email = "member-$id@test.com",
|
||||||
|
password = "password",
|
||||||
|
nickname = "member-$id"
|
||||||
|
).also {
|
||||||
|
it.id = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createSeries(id: Long): Series {
|
||||||
|
return Series(
|
||||||
|
title = "series-$id",
|
||||||
|
introduction = "introduction-$id",
|
||||||
|
languageCode = "ja"
|
||||||
|
).also {
|
||||||
|
it.id = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package kr.co.vividnext.sodalive.content.series.main.banner
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.admin.content.series.AdminContentSeriesRepository
|
||||||
|
import kr.co.vividnext.sodalive.creator.admin.content.series.Series
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.springframework.data.domain.PageImpl
|
||||||
|
import org.springframework.data.domain.PageRequest
|
||||||
|
|
||||||
|
class ContentSeriesBannerServiceTest {
|
||||||
|
private lateinit var bannerRepository: SeriesBannerRepository
|
||||||
|
private lateinit var seriesRepository: AdminContentSeriesRepository
|
||||||
|
private lateinit var service: ContentSeriesBannerService
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setUp() {
|
||||||
|
bannerRepository = Mockito.mock(SeriesBannerRepository::class.java)
|
||||||
|
seriesRepository = Mockito.mock(AdminContentSeriesRepository::class.java)
|
||||||
|
service = ContentSeriesBannerService(
|
||||||
|
bannerRepository = bannerRepository,
|
||||||
|
seriesRepository = seriesRepository
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("일본어 배너 등록 요청은 JA 언어값으로 저장한다")
|
||||||
|
fun shouldRegisterJapaneseBanner() {
|
||||||
|
val series = createSeries(id = 1L)
|
||||||
|
|
||||||
|
Mockito.`when`(seriesRepository.findByIdAndActiveTrue(1L)).thenReturn(series)
|
||||||
|
Mockito.`when`(bannerRepository.findMaxSortOrder()).thenReturn(2)
|
||||||
|
Mockito.`when`(bannerRepository.save(Mockito.any(SeriesBanner::class.java))).thenAnswer { it.arguments[0] }
|
||||||
|
|
||||||
|
val banner = service.registerBanner(
|
||||||
|
seriesId = 1L,
|
||||||
|
imagePath = "banner/jp.png",
|
||||||
|
lang = Lang.JA
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(Lang.JA, banner.lang)
|
||||||
|
assertEquals(3, banner.sortOrder)
|
||||||
|
assertEquals("banner/jp.png", banner.imagePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("언어가 없는 배너 등록 요청은 KO로 저장한다")
|
||||||
|
fun shouldRegisterKoreanBannerWhenLangIsMissing() {
|
||||||
|
val series = createSeries(id = 2L)
|
||||||
|
|
||||||
|
Mockito.`when`(seriesRepository.findByIdAndActiveTrue(2L)).thenReturn(series)
|
||||||
|
Mockito.`when`(bannerRepository.findMaxSortOrder()).thenReturn(null)
|
||||||
|
Mockito.`when`(bannerRepository.save(Mockito.any(SeriesBanner::class.java))).thenAnswer { it.arguments[0] }
|
||||||
|
|
||||||
|
val banner = service.registerBanner(
|
||||||
|
seriesId = 2L,
|
||||||
|
imagePath = "banner/default.png",
|
||||||
|
lang = null
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(Lang.KO, banner.lang)
|
||||||
|
assertEquals(1, banner.sortOrder)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("일본어 사용자는 일본어 배너만 조회한다")
|
||||||
|
fun shouldReturnJapaneseBannersForJapaneseUser() {
|
||||||
|
val pageable = PageRequest.of(0, 10)
|
||||||
|
val japaneseBanner = SeriesBanner(
|
||||||
|
imagePath = "banner/jp.png",
|
||||||
|
series = createSeries(id = 3L),
|
||||||
|
sortOrder = 1,
|
||||||
|
lang = Lang.JA
|
||||||
|
)
|
||||||
|
val expectedPage = PageImpl(listOf(japaneseBanner), pageable, 1)
|
||||||
|
|
||||||
|
Mockito.`when`(bannerRepository.findByIsActiveTrueAndLangOrderBySortOrderAsc(Lang.JA, pageable))
|
||||||
|
.thenReturn(expectedPage)
|
||||||
|
|
||||||
|
val actual = service.getDisplayBanners(pageable, Lang.JA)
|
||||||
|
|
||||||
|
assertEquals(expectedPage, actual)
|
||||||
|
Mockito.verify(bannerRepository).findByIsActiveTrueAndLangOrderBySortOrderAsc(Lang.JA, pageable)
|
||||||
|
Mockito.verify(bannerRepository, Mockito.never())
|
||||||
|
.findByIsActiveTrueOrderBySortOrderAsc(pageable)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createSeries(id: Long): Series {
|
||||||
|
return Series(
|
||||||
|
title = "series-$id",
|
||||||
|
introduction = "introduction-$id",
|
||||||
|
languageCode = "ko"
|
||||||
|
).also {
|
||||||
|
it.id = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user