feat(i18n): Papago 다음 순서에 AWS 공급자를 등록한다

This commit is contained in:
2026-11-10 10:24:06 +09:00
parent 7cac09e39c
commit 992844df66
8 changed files with 188 additions and 25 deletions
@@ -1,6 +1,6 @@
package kr.co.vividnext.sodalive.i18n.translation
object LanguageProviderOrder {
val detectionOrder = listOf(LanguageProviderId.PAPAGO)
val translationOrder = listOf(LanguageProviderId.PAPAGO)
val detectionOrder = listOf(LanguageProviderId.PAPAGO, LanguageProviderId.AWS)
val translationOrder = listOf(LanguageProviderId.PAPAGO, LanguageProviderId.AWS)
}
@@ -90,7 +90,6 @@ import javax.persistence.EntityManager
TranslationSourceExtractor::class,
TranslationReadModelMaterializer::class,
TranslationCoordinator::class,
LanguageProviderProperties::class,
PapagoTranslationService::class,
ResourceTranslationJobScheduler::class,
TranslationJobScheduler::class,
@@ -678,6 +677,12 @@ class CreatorCommunityLanguageDetectTest @Autowired constructor(
@TestConfiguration
@EnableAsync
class AsyncConfiguration {
@Bean
fun languageProviderProperties() = LanguageProviderProperties(
listOf(LanguageProviderId.PAPAGO),
listOf(LanguageProviderId.PAPAGO)
)
@Bean(name = ["taskExecutor"])
fun taskExecutor(): DetailPipelineTaskExecutor {
return DetailPipelineTaskExecutor()
@@ -734,7 +739,7 @@ class ControlledLanguageDetectionCacheService(
override val providerId = LanguageProviderId.PAPAGO
override fun detect(query: String): String? = null
}),
LanguageProviderProperties()
LanguageProviderProperties(listOf(LanguageProviderId.PAPAGO), listOf(LanguageProviderId.PAPAGO))
) {
val detections = ConcurrentHashMap<String, String>()
val queries = ConcurrentLinkedQueue<String>()
@@ -4,6 +4,7 @@ import kr.co.vividnext.sodalive.configs.QueryDslConfig
import kr.co.vividnext.sodalive.content.theme.AudioContentThemeQueryRepository
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunity
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunityRepository
import kr.co.vividnext.sodalive.i18n.translation.LanguageProviderId.PAPAGO
import kr.co.vividnext.sodalive.i18n.translation.LanguageProviderProperties
import kr.co.vividnext.sodalive.i18n.translation.LanguageTranslationTargetType
import kr.co.vividnext.sodalive.i18n.translation.PapagoLanguageDetectionProvider
@@ -29,6 +30,8 @@ import org.springframework.aop.support.AopUtils
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.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Import
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpMethod
@@ -71,7 +74,7 @@ import java.util.concurrent.atomic.AtomicInteger
QueryDslConfig::class,
AudioContentThemeQueryRepository::class,
LanguageDetectionCacheService::class,
LanguageProviderProperties::class,
CreatorCommunityLanguageDetectionCacheConcurrencyTest.PapagoConfiguration::class,
PapagoLanguageDetectionProvider::class,
PapagoTranslationService::class,
TranslationCoordinator::class,
@@ -98,6 +101,12 @@ class CreatorCommunityLanguageDetectionCacheConcurrencyTest @Autowired construct
}
private lateinit var papagoRequests: ControlledPapagoRequestFactory
@TestConfiguration
class PapagoConfiguration {
@Bean
fun languageProviderProperties() = LanguageProviderProperties(listOf(PAPAGO), listOf(PAPAGO))
}
@BeforeEach
fun setUp() {
papagoRequests = ControlledPapagoRequestFactory()
@@ -108,6 +108,10 @@ class LanguageDetectionCacheServiceTest {
override val providerId = LanguageProviderId.PAPAGO
override fun detect(query: String) = detect()
}
return LanguageDetectionCacheService(repository, listOf(provider), LanguageProviderProperties())
return LanguageDetectionCacheService(
repository,
listOf(provider),
LanguageProviderProperties(listOf(LanguageProviderId.PAPAGO), listOf(LanguageProviderId.PAPAGO))
)
}
}
@@ -42,12 +42,24 @@ class LanguageProviderIdTest {
}
@Test
@DisplayName("production 코드 목록과 실제 Papago adapter는 PAPAGO enum을 사용한다")
@DisplayName("production 기본 순서는 PAPAGO 다음 AWS이며 실제 Papago adapter 식별자는 유지한다")
fun shouldUsePapagoEnumForProductionDefaultsAndAdapters() {
assertEquals(listOf(LanguageProviderId.PAPAGO), LanguageProviderOrder.detectionOrder)
assertEquals(listOf(LanguageProviderId.PAPAGO), LanguageProviderOrder.translationOrder)
assertEquals(listOf(LanguageProviderId.PAPAGO), LanguageProviderProperties().detectionOrder)
assertEquals(listOf(LanguageProviderId.PAPAGO), LanguageProviderProperties().translationOrder)
assertAll(
{ assertEquals(listOf(LanguageProviderId.PAPAGO, LanguageProviderId.AWS), LanguageProviderOrder.detectionOrder) },
{ assertEquals(listOf(LanguageProviderId.PAPAGO, LanguageProviderId.AWS), LanguageProviderOrder.translationOrder) },
{
assertEquals(
listOf(LanguageProviderId.PAPAGO, LanguageProviderId.AWS),
LanguageProviderProperties().detectionOrder
)
},
{
assertEquals(
listOf(LanguageProviderId.PAPAGO, LanguageProviderId.AWS),
LanguageProviderProperties().translationOrder
)
}
)
assertEquals(LanguageProviderId.PAPAGO, PapagoLanguageDetectionProvider("test-id", "test-secret").providerId)
val translator = PapagoTranslationService("test-id", "test-secret")
assertEquals(LanguageProviderId.PAPAGO, translator.providerId)
@@ -4,6 +4,7 @@ import kr.co.vividnext.sodalive.content.LanguageDetectionCacheService
import kr.co.vividnext.sodalive.content.LanguageDetectionResultRepository
import kr.co.vividnext.sodalive.i18n.translation.LanguageProviderId.AWS
import kr.co.vividnext.sodalive.i18n.translation.LanguageProviderId.PAPAGO
import org.junit.jupiter.api.Assertions.assertAll
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertNull
@@ -15,12 +16,20 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Import
import org.springframework.core.env.SystemEnvironmentPropertySource
import software.amazon.awssdk.services.comprehend.ComprehendClient
import software.amazon.awssdk.services.comprehend.model.DetectDominantLanguageRequest
import software.amazon.awssdk.services.comprehend.model.DetectDominantLanguageResponse
import software.amazon.awssdk.services.comprehend.model.DominantLanguage
import software.amazon.awssdk.services.translate.TranslateClient
import software.amazon.awssdk.services.translate.model.TranslateTextRequest
import software.amazon.awssdk.services.translate.model.TranslateTextResponse
import java.util.function.Supplier
class LanguageProviderSpringConfigurationTest {
private val papagoRunner = ApplicationContextRunner().withUserConfiguration(Providers::class.java)
private val runner = papagoRunner.withUserConfiguration(AwsProviders::class.java)
private val defaultRunner = runner.withUserConfiguration(LanguageProviderProperties::class.java)
private val actualRunner = papagoRunner.withUserConfiguration(ActualAwsProviders::class.java)
@Test
@DisplayName("과거 Spring 우선순위 property가 있어도 production 기본 순서는 바뀌지 않는다")
@@ -31,8 +40,18 @@ class LanguageProviderSpringConfigurationTest {
).run { context ->
assertNull(context.startupFailure)
val properties = context.getBean(LanguageProviderProperties::class.java)
assertEquals(listOf(PAPAGO), properties.detectionOrder)
assertEquals(listOf(PAPAGO), properties.translationOrder)
assertAll(
{ assertEquals(listOf(PAPAGO, AWS), properties.detectionOrder) },
{ assertEquals(listOf(PAPAGO, AWS), properties.translationOrder) }
)
assertEquals(PAPAGO, context.getBean(LanguageDetectionCacheService::class.java).detectWithCache("source")?.providerId)
assertEquals(
PAPAGO,
context.getBean(TranslationCoordinator::class.java)
.translate(TranslateRequest(listOf("source"), "ko", "en")).providerId
)
assertEquals(0, context.getBean("secondaryDetection", RecordingDetectionProvider::class.java).queries.size)
assertEquals(0, context.getBean("secondaryTranslation", RecordingTranslationProvider::class.java).requests.size)
}
}
@@ -57,8 +76,18 @@ class LanguageProviderSpringConfigurationTest {
assertEquals("secondary,papago", context.environment.getProperty("sodalive.language-providers.detection-order"))
assertEquals("secondary,papago", context.environment.getProperty("sodalive.language-providers.translation-order"))
val properties = context.getBean(LanguageProviderProperties::class.java)
assertEquals(listOf(PAPAGO), properties.detectionOrder)
assertEquals(listOf(PAPAGO), properties.translationOrder)
assertAll(
{ assertEquals(listOf(PAPAGO, AWS), properties.detectionOrder) },
{ assertEquals(listOf(PAPAGO, AWS), properties.translationOrder) }
)
assertEquals(PAPAGO, context.getBean(LanguageDetectionCacheService::class.java).detectWithCache("source")?.providerId)
assertEquals(
PAPAGO,
context.getBean(TranslationCoordinator::class.java)
.translate(TranslateRequest(listOf("source"), "ko", "en")).providerId
)
assertEquals(0, context.getBean("secondaryDetection", RecordingDetectionProvider::class.java).queries.size)
assertEquals(0, context.getBean("secondaryTranslation", RecordingTranslationProvider::class.java).requests.size)
}
}
@@ -104,13 +133,15 @@ class LanguageProviderSpringConfigurationTest {
}
@Test
@DisplayName("실제 production component의 Spring 기본 생성자는 두 기능 모두 파파고를 사용한다")
@DisplayName("Spring 기본 생성자는 두 기능 모두 PAPAGO 다음 AWS를 등록하고 첫 PAPAGO 성공 뒤 멈춘다")
fun shouldDefaultToPapago() {
defaultRunner.run { context ->
assertNull(context.startupFailure)
val properties = context.getBean(LanguageProviderProperties::class.java)
assertEquals(listOf(PAPAGO), properties.detectionOrder)
assertEquals(listOf(PAPAGO), properties.translationOrder)
assertAll(
{ assertEquals(listOf(PAPAGO, AWS), properties.detectionOrder) },
{ assertEquals(listOf(PAPAGO, AWS), properties.translationOrder) }
)
assertEquals(
PAPAGO,
context.getBean(LanguageDetectionCacheService::class.java).detectWithCache("source")?.providerId
@@ -120,6 +151,72 @@ class LanguageProviderSpringConfigurationTest {
context.getBean(TranslationCoordinator::class.java)
.translate(TranslateRequest(listOf("source"), "ko", "en")).providerId
)
assertEquals(listOf("source"), context.getBean("papagoDetection", RecordingDetectionProvider::class.java).queries)
assertEquals(1, context.getBean("papagoTranslation", RecordingTranslationProvider::class.java).requests.size)
assertEquals(0, context.getBean("secondaryDetection", RecordingDetectionProvider::class.java).queries.size)
assertEquals(0, context.getBean("secondaryTranslation", RecordingTranslationProvider::class.java).requests.size)
}
}
@Test
@DisplayName("실제 AWS adapter와 SDK client만 등록한 context도 기본 순서에서 PAPAGO 첫 성공 뒤 SDK를 호출하지 않는다")
fun shouldWireActualAwsAdaptersWithoutDuplicateIds() {
actualRunner.withUserConfiguration(LanguageProviderProperties::class.java).run { context ->
assertNull(context.startupFailure)
assertEquals(2, context.getBeansOfType(LanguageDetectionProvider::class.java).size)
assertEquals(2, context.getBeansOfType(TranslationProvider::class.java).size)
assertEquals(AWS, context.getBean(AwsLanguageDetectionProvider::class.java).providerId)
val translator = context.getBean(AwsTranslationService::class.java)
assertEquals(AWS, translator.providerId)
assertEquals("translate-2017-07-01", translator.providerVersion)
val properties = context.getBean(LanguageProviderProperties::class.java)
assertAll(
{ assertEquals(listOf(PAPAGO, AWS), properties.detectionOrder) },
{ assertEquals(listOf(PAPAGO, AWS), properties.translationOrder) }
)
assertEquals(PAPAGO, context.getBean(LanguageDetectionCacheService::class.java).detectWithCache("source")?.providerId)
assertEquals(
PAPAGO,
context.getBean(TranslationCoordinator::class.java)
.translate(TranslateRequest(listOf("source"), "ko", "en")).providerId
)
Mockito.verifyNoInteractions(
context.getBean(ComprehendClient::class.java),
context.getBean(TranslateClient::class.java)
)
}
}
@Test
@DisplayName("실제 AWS adapter는 별도 context의 명시 AWS 우선 순서로 SDK 응답을 반환한다")
fun shouldInvokeActualAwsAdaptersThroughSpringCoordinators() {
actualRunner.withBean(
LanguageProviderProperties::class.java,
Supplier { LanguageProviderProperties(listOf(AWS, PAPAGO), listOf(AWS, PAPAGO)) }
).run { context ->
assertNull(context.startupFailure)
val detectionRequest = DetectDominantLanguageRequest.builder().text("source").build()
val translationRequest = TranslateTextRequest.builder()
.text("source").sourceLanguageCode("ko").targetLanguageCode("en").build()
val detector = context.getBean(ComprehendClient::class.java)
val translator = context.getBean(TranslateClient::class.java)
Mockito.`when`(detector.detectDominantLanguage(detectionRequest)).thenReturn(
DetectDominantLanguageResponse.builder()
.languages(DominantLanguage.builder().languageCode("ko").build()).build()
)
Mockito.`when`(translator.translateText(translationRequest))
.thenReturn(TranslateTextResponse.builder().translatedText("translated").build())
assertEquals(AWS, context.getBean(LanguageDetectionCacheService::class.java).detectWithCache("source")?.providerId)
val result = context.getBean(TranslationCoordinator::class.java)
.translate(TranslateRequest(listOf("source"), "ko", "en"))
assertEquals(AWS, result.providerId)
assertEquals("translate-2017-07-01", result.providerVersion)
assertEquals(listOf("translated"), result.result.translatedText)
Mockito.verify(detector).detectDominantLanguage(detectionRequest)
Mockito.verify(translator).translateText(translationRequest)
assertEquals(0, context.getBean("papagoDetection", RecordingDetectionProvider::class.java).queries.size)
assertEquals(0, context.getBean("papagoTranslation", RecordingTranslationProvider::class.java).requests.size)
}
}
@@ -169,4 +266,14 @@ class LanguageProviderSpringConfigurationTest {
@Bean
fun secondaryTranslation() = RecordingTranslationProvider(AWS)
}
@TestConfiguration
@Import(AwsLanguageDetectionProvider::class, AwsTranslationService::class)
class ActualAwsProviders {
@Bean
fun comprehendClient(): ComprehendClient = Mockito.mock(ComprehendClient::class.java)
@Bean
fun translateClient(): TranslateClient = Mockito.mock(TranslateClient::class.java)
}
}
@@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.i18n.translation
import kr.co.vividnext.sodalive.i18n.translation.LanguageProviderId.PAPAGO
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
@@ -30,7 +31,11 @@ class TranslationJobWorkerTest {
val worker = TranslationJobWorker(
translationJobRepository = jobRepository,
translationMemoryRepository = memoryRepository,
translationCoordinator = TranslationCoordinator(listOf(provider), LanguageProviderProperties(), memoryRepository),
translationCoordinator = TranslationCoordinator(
listOf(provider),
LanguageProviderProperties(listOf(PAPAGO), listOf(PAPAGO)),
memoryRepository
),
materializer = materializer,
transactionManager = TestTransactionManager()
)
@@ -54,7 +59,11 @@ class TranslationJobWorkerTest {
val worker = TranslationJobWorker(
translationJobRepository = jobRepository,
translationMemoryRepository = memoryRepository,
translationCoordinator = TranslationCoordinator(listOf(provider), LanguageProviderProperties(), memoryRepository),
translationCoordinator = TranslationCoordinator(
listOf(provider),
LanguageProviderProperties(listOf(PAPAGO), listOf(PAPAGO)),
memoryRepository
),
materializer = materializer,
transactionManager = TestTransactionManager()
)
@@ -84,7 +93,11 @@ class TranslationJobWorkerTest {
val worker = TranslationJobWorker(
translationJobRepository = jobRepository,
translationMemoryRepository = memoryRepository,
translationCoordinator = TranslationCoordinator(listOf(provider), LanguageProviderProperties(), memoryRepository),
translationCoordinator = TranslationCoordinator(
listOf(provider),
LanguageProviderProperties(listOf(PAPAGO), listOf(PAPAGO)),
memoryRepository
),
materializer = materializer,
transactionManager = TestTransactionManager()
)
@@ -129,7 +142,11 @@ class TranslationJobWorkerTest {
val worker = TranslationJobWorker(
translationJobRepository = jobRepository,
translationMemoryRepository = memoryRepository,
translationCoordinator = TranslationCoordinator(listOf(provider), LanguageProviderProperties(), memoryRepository),
translationCoordinator = TranslationCoordinator(
listOf(provider),
LanguageProviderProperties(listOf(PAPAGO), listOf(PAPAGO)),
memoryRepository
),
materializer = materializer,
transactionManager = TestTransactionManager()
)
@@ -165,7 +182,11 @@ class TranslationJobWorkerTest {
val worker = TranslationJobWorker(
translationJobRepository = jobRepository,
translationMemoryRepository = memoryRepository,
translationCoordinator = TranslationCoordinator(listOf(provider), LanguageProviderProperties(), memoryRepository),
translationCoordinator = TranslationCoordinator(
listOf(provider),
LanguageProviderProperties(listOf(PAPAGO), listOf(PAPAGO)),
memoryRepository
),
materializer = materializer,
transactionManager = TestTransactionManager()
)
@@ -198,7 +219,11 @@ class TranslationJobWorkerTest {
val worker = TranslationJobWorker(
translationJobRepository = jobRepository,
translationMemoryRepository = memoryRepository,
translationCoordinator = TranslationCoordinator(listOf(provider), LanguageProviderProperties(), memoryRepository),
translationCoordinator = TranslationCoordinator(
listOf(provider),
LanguageProviderProperties(listOf(PAPAGO), listOf(PAPAGO)),
memoryRepository
),
materializer = materializer,
transactionManager = TestTransactionManager()
)
@@ -19,6 +19,7 @@ import kr.co.vividnext.sodalive.content.theme.translation.ContentThemeTranslatio
import kr.co.vividnext.sodalive.content.translation.ContentTranslationRepository
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunity
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunityRepository
import kr.co.vividnext.sodalive.i18n.translation.LanguageProviderId.PAPAGO
import kr.co.vividnext.sodalive.i18n.translation.LanguageProviderProperties
import kr.co.vividnext.sodalive.i18n.translation.LanguageTranslationTargetType
import kr.co.vividnext.sodalive.i18n.translation.PapagoTranslationService
@@ -673,7 +674,7 @@ class CreatorCommunityTranslationServiceTest @Autowired constructor(
sourceExtractor = sourceExtractor,
translationCoordinator = TranslationCoordinator(
listOf(PapagoTranslationService("test-id", "test-secret")),
LanguageProviderProperties(),
LanguageProviderProperties(listOf(PAPAGO), listOf(PAPAGO)),
memoryRepository
),
contentTranslationRepository = Mockito.mock(ContentTranslationRepository::class.java),