revert(ai-character): 기존 관리자 기능 기반을 제거한다
This commit is contained in:
-276
@@ -1,276 +0,0 @@
|
||||
package kr.co.vividnext.sodalive.chat.original.controller
|
||||
|
||||
import kr.co.vividnext.sodalive.chat.character.ChatCharacter
|
||||
import kr.co.vividnext.sodalive.chat.character.image.CharacterImageRepository
|
||||
import kr.co.vividnext.sodalive.chat.character.translate.AiCharacterTranslationRepository
|
||||
import kr.co.vividnext.sodalive.chat.original.OriginalWork
|
||||
import kr.co.vividnext.sodalive.chat.original.OriginalWorkLink
|
||||
import kr.co.vividnext.sodalive.chat.original.OriginalWorkTag
|
||||
import kr.co.vividnext.sodalive.chat.original.OriginalWorkTagMapping
|
||||
import kr.co.vividnext.sodalive.chat.original.service.OriginalWorkQueryService
|
||||
import kr.co.vividnext.sodalive.chat.original.service.OriginalWorkTranslationService
|
||||
import kr.co.vividnext.sodalive.chat.original.translation.OriginalWorkTranslationRepository
|
||||
import kr.co.vividnext.sodalive.common.CountryContext
|
||||
import kr.co.vividnext.sodalive.configs.SecurityConfig
|
||||
import kr.co.vividnext.sodalive.content.ContentType
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||
import kr.co.vividnext.sodalive.jwt.JwtAccessDeniedHandler
|
||||
import kr.co.vividnext.sodalive.jwt.JwtAuthenticationEntryPoint
|
||||
import kr.co.vividnext.sodalive.jwt.TokenProvider
|
||||
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.assertNull
|
||||
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.web.servlet.WebMvcTest
|
||||
import org.springframework.boot.test.mock.mockito.MockBean
|
||||
import org.springframework.context.annotation.Import
|
||||
import org.springframework.core.MethodParameter
|
||||
import org.springframework.data.domain.PageImpl
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.anonymous
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory
|
||||
import org.springframework.web.context.request.NativeWebRequest
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver
|
||||
import org.springframework.web.method.support.ModelAndViewContainer
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@WebMvcTest(OriginalWorkController::class)
|
||||
@Import(SecurityConfig::class, JwtAuthenticationEntryPoint::class, JwtAccessDeniedHandler::class)
|
||||
class OriginalWorkControllerContractTest @Autowired constructor(
|
||||
private val securityMockMvc: MockMvc
|
||||
) {
|
||||
@MockBean
|
||||
private lateinit var securedQueryService: OriginalWorkQueryService
|
||||
|
||||
@MockBean
|
||||
private lateinit var securedCharacterImageRepository: CharacterImageRepository
|
||||
|
||||
@MockBean
|
||||
private lateinit var securedMemberContentPreferenceService: MemberContentPreferenceService
|
||||
|
||||
@MockBean
|
||||
private lateinit var securedLangContext: LangContext
|
||||
|
||||
@MockBean
|
||||
private lateinit var securedOriginalWorkTranslationService: OriginalWorkTranslationService
|
||||
|
||||
@MockBean
|
||||
private lateinit var securedOriginalWorkTranslationRepository: OriginalWorkTranslationRepository
|
||||
|
||||
@MockBean
|
||||
private lateinit var securedAiCharacterTranslationRepository: AiCharacterTranslationRepository
|
||||
|
||||
@MockBean
|
||||
private lateinit var tokenProvider: TokenProvider
|
||||
|
||||
@MockBean
|
||||
private lateinit var countryContext: CountryContext
|
||||
|
||||
@MockBean
|
||||
private lateinit var sodaMessageSource: SodaMessageSource
|
||||
|
||||
@Test
|
||||
@DisplayName("일반 사용자 원작 API는 기존 base path와 공개 목록 경로를 유지한다")
|
||||
fun shouldKeepConsumerOriginalWorkListRoute() {
|
||||
val classMapping = OriginalWorkController::class.java.getAnnotation(RequestMapping::class.java)
|
||||
val list = OriginalWorkController::class.java.getDeclaredMethod(
|
||||
"list",
|
||||
Int::class.java,
|
||||
Int::class.java,
|
||||
Member::class.java
|
||||
)
|
||||
|
||||
assertEquals("/api/chat/original", classMapping.value.single())
|
||||
assertEquals("/list", list.getAnnotation(GetMapping::class.java).value.single())
|
||||
assertNull(list.getAnnotation(PreAuthorize::class.java))
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("일반 사용자 원작 상세 API는 기존 경로를 유지한다")
|
||||
fun shouldKeepConsumerOriginalWorkDetailRoute() {
|
||||
val detail = OriginalWorkController::class.java.getDeclaredMethod("detail", Long::class.java, Member::class.java)
|
||||
|
||||
assertEquals("/{id}", detail.getAnnotation(GetMapping::class.java).value.single())
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("production security matcher는 익명 원작 목록은 허용하고 상세는 거부한다")
|
||||
fun shouldApplyProductionSecurityMatcherToAnonymousOriginalWorkRequests() {
|
||||
Mockito.`when`(securedQueryService.listForAppPage(false, 0, 20)).thenReturn(PageImpl(emptyList()))
|
||||
|
||||
securityMockMvc.perform(get("/api/chat/original/list").with(anonymous()))
|
||||
.andExpect(status().isOk)
|
||||
|
||||
securityMockMvc.perform(get("/api/chat/original/1").with(anonymous()))
|
||||
.andExpect(status().isUnauthorized)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("일반 사용자 원작 목록은 기존 성공 응답 surface를 유지한다")
|
||||
fun shouldKeepConsumerOriginalWorkListResponseSurface() {
|
||||
val queryService = Mockito.mock(OriginalWorkQueryService::class.java)
|
||||
val originalWork = originalWork()
|
||||
Mockito.`when`(queryService.listForAppPage(false, 0, 20)).thenReturn(PageImpl(listOf(originalWork)))
|
||||
val langContext = Mockito.mock(LangContext::class.java)
|
||||
Mockito.`when`(langContext.lang).thenReturn(Lang.KO)
|
||||
val originalWorkTranslationRepository = Mockito.mock(OriginalWorkTranslationRepository::class.java)
|
||||
Mockito.`when`(originalWorkTranslationRepository.findByOriginalWorkIdInAndLocale(setOf(1L), "ko"))
|
||||
.thenReturn(emptyList())
|
||||
val controller = OriginalWorkController(
|
||||
queryService = queryService,
|
||||
characterImageRepository = Mockito.mock(CharacterImageRepository::class.java),
|
||||
memberContentPreferenceService = Mockito.mock(MemberContentPreferenceService::class.java),
|
||||
langContext = langContext,
|
||||
originalWorkTranslationService = Mockito.mock(OriginalWorkTranslationService::class.java),
|
||||
originalWorkTranslationRepository = originalWorkTranslationRepository,
|
||||
aiCharacterTranslationRepository = Mockito.mock(AiCharacterTranslationRepository::class.java),
|
||||
imageHost = "https://cdn.test"
|
||||
)
|
||||
val mockMvc = MockMvcBuilders.standaloneSetup(controller)
|
||||
.setCustomArgumentResolvers(AnonymousMemberArgumentResolver())
|
||||
.build()
|
||||
|
||||
mockMvc.perform(get("/api/chat/original/list").param("page", "0").param("size", "20"))
|
||||
.andExpect(status().isOk)
|
||||
.andExpect(jsonPath("$.success").value(true))
|
||||
.andExpect(jsonPath("$.data.totalCount").value(1))
|
||||
.andExpect(jsonPath("$.data.content").isArray)
|
||||
.andExpect(jsonPath("$.data.content[0].id").value(1))
|
||||
.andExpect(jsonPath("$.data.content[0].imageUrl").value("https://cdn.test/originals/1.png"))
|
||||
.andExpect(jsonPath("$.data.content[0].title").value("title"))
|
||||
.andExpect(jsonPath("$.data.content[0].contentType").value("webtoon"))
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("일반 사용자 원작 상세는 기존 성공 응답 field를 유지한다")
|
||||
fun shouldKeepConsumerOriginalWorkDetailResponseSurface() {
|
||||
val queryService = Mockito.mock(OriginalWorkQueryService::class.java)
|
||||
val originalWork = originalWork()
|
||||
val character = ChatCharacter(
|
||||
characterUUID = "character-uuid",
|
||||
name = "character",
|
||||
description = "character description",
|
||||
systemPrompt = "prompt"
|
||||
).apply {
|
||||
id = 10L
|
||||
imagePath = "characters/10.png"
|
||||
}
|
||||
Mockito.`when`(queryService.getOriginalWork(1L)).thenReturn(originalWork)
|
||||
Mockito.`when`(queryService.getActiveCharactersPage(1L, 0, 20)).thenReturn(PageImpl(listOf(character)))
|
||||
val characterImageRepository = Mockito.mock(CharacterImageRepository::class.java)
|
||||
Mockito.`when`(
|
||||
characterImageRepository.findCharacterIdsWithRecentImages(
|
||||
Mockito.eq(listOf(10L)) ?: listOf(10L),
|
||||
Mockito.any(LocalDateTime::class.java) ?: LocalDateTime.now()
|
||||
)
|
||||
)
|
||||
.thenReturn(listOf(10L))
|
||||
val memberContentPreferenceService = Mockito.mock(MemberContentPreferenceService::class.java)
|
||||
val member = Member(email = "user@test.com", password = "password", nickname = "user").apply { id = 20L }
|
||||
Mockito.`when`(memberContentPreferenceService.getStoredPreference(member))
|
||||
.thenReturn(ViewerContentPreference("KR", true, ContentType.ALL, true))
|
||||
val langContext = Mockito.mock(LangContext::class.java)
|
||||
Mockito.`when`(langContext.lang).thenReturn(Lang.KO)
|
||||
val originalWorkTranslationService = Mockito.mock(OriginalWorkTranslationService::class.java)
|
||||
Mockito.`when`(originalWorkTranslationService.ensureTranslated(originalWork, "ko")).thenReturn(null)
|
||||
val aiCharacterTranslationRepository = Mockito.mock(AiCharacterTranslationRepository::class.java)
|
||||
Mockito.`when`(aiCharacterTranslationRepository.findByCharacterIdInAndLocale(listOf(10L), "ko"))
|
||||
.thenReturn(emptyList())
|
||||
val controller = OriginalWorkController(
|
||||
queryService = queryService,
|
||||
characterImageRepository = characterImageRepository,
|
||||
memberContentPreferenceService = memberContentPreferenceService,
|
||||
langContext = langContext,
|
||||
originalWorkTranslationService = originalWorkTranslationService,
|
||||
originalWorkTranslationRepository = Mockito.mock(OriginalWorkTranslationRepository::class.java),
|
||||
aiCharacterTranslationRepository = aiCharacterTranslationRepository,
|
||||
imageHost = "https://cdn.test"
|
||||
)
|
||||
val mockMvc = MockMvcBuilders.standaloneSetup(controller)
|
||||
.setCustomArgumentResolvers(MemberArgumentResolver(member))
|
||||
.build()
|
||||
|
||||
mockMvc.perform(get("/api/chat/original/1"))
|
||||
.andExpect(status().isOk)
|
||||
.andExpect(jsonPath("$.success").value(true))
|
||||
.andExpect(jsonPath("$.data.imageUrl").value("https://cdn.test/originals/1.png"))
|
||||
.andExpect(jsonPath("$.data.title").value("title"))
|
||||
.andExpect(jsonPath("$.data.contentType").value("webtoon"))
|
||||
.andExpect(jsonPath("$.data.category").value("romance"))
|
||||
.andExpect(jsonPath("$.data.isAdult").value(false))
|
||||
.andExpect(jsonPath("$.data.description").value("description"))
|
||||
.andExpect(jsonPath("$.data.originalWork").value("source"))
|
||||
.andExpect(jsonPath("$.data.originalLink").value("https://source.test"))
|
||||
.andExpect(jsonPath("$.data.writer").value("writer"))
|
||||
.andExpect(jsonPath("$.data.studio").value("studio"))
|
||||
.andExpect(jsonPath("$.data.originalLinks[0]").value("https://link.test"))
|
||||
.andExpect(jsonPath("$.data.tags[0]").value("tag"))
|
||||
.andExpect(jsonPath("$.data.characters[0].characterId").value(10))
|
||||
.andExpect(jsonPath("$.data.characters[0].name").value("character"))
|
||||
.andExpect(jsonPath("$.data.characters[0].description").value("character description"))
|
||||
.andExpect(jsonPath("$.data.characters[0].imageUrl").value("https://cdn.test/characters/10.png"))
|
||||
.andExpect(jsonPath("$.data.characters[0].isNew").value(true))
|
||||
.andExpect(jsonPath("$.data.translated").doesNotExist())
|
||||
}
|
||||
|
||||
private class AnonymousMemberArgumentResolver : HandlerMethodArgumentResolver {
|
||||
override fun supportsParameter(parameter: MethodParameter): Boolean {
|
||||
return parameter.hasParameterAnnotation(AuthenticationPrincipal::class.java)
|
||||
}
|
||||
|
||||
override fun resolveArgument(
|
||||
parameter: MethodParameter,
|
||||
mavContainer: ModelAndViewContainer?,
|
||||
webRequest: NativeWebRequest,
|
||||
binderFactory: WebDataBinderFactory?
|
||||
): Any? = null
|
||||
}
|
||||
|
||||
private class MemberArgumentResolver(private val member: Member) : HandlerMethodArgumentResolver {
|
||||
override fun supportsParameter(parameter: MethodParameter): Boolean {
|
||||
return parameter.hasParameterAnnotation(AuthenticationPrincipal::class.java)
|
||||
}
|
||||
|
||||
override fun resolveArgument(
|
||||
parameter: MethodParameter,
|
||||
mavContainer: ModelAndViewContainer?,
|
||||
webRequest: NativeWebRequest,
|
||||
binderFactory: WebDataBinderFactory?
|
||||
): Any = member
|
||||
}
|
||||
|
||||
private fun originalWork(): OriginalWork {
|
||||
val originalWork = OriginalWork(
|
||||
title = "title",
|
||||
contentType = "webtoon",
|
||||
category = "romance",
|
||||
isAdult = false,
|
||||
description = "description",
|
||||
originalWork = "source",
|
||||
originalLink = "https://source.test",
|
||||
writer = "writer",
|
||||
studio = "studio"
|
||||
).apply {
|
||||
id = 1L
|
||||
imagePath = "originals/1.png"
|
||||
}
|
||||
originalWork.originalLinks += OriginalWorkLink("https://link.test", originalWork)
|
||||
originalWork.tagMappings += OriginalWorkTagMapping(originalWork, OriginalWorkTag("tag"))
|
||||
return originalWork
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user