From ec890047a5722bbbcdcd0c16460f4cff50bae0fe Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 9 Nov 2026 19:14:19 +0900 Subject: [PATCH] =?UTF-8?q?refactor(i18n):=20=EA=B3=B5=EA=B8=89=EC=9E=90?= =?UTF-8?q?=20=EC=8B=9D=EB=B3=84=EC=9E=90=EB=A5=BC=20enum=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=ED=86=B5=EC=9D=BC=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../content/LanguageDetectionCacheService.kt | 12 +-- .../i18n/translation/DetectedLanguage.kt | 2 +- .../translation/LanguageDetectionProvider.kt | 2 +- .../i18n/translation/LanguageProviderId.kt | 6 ++ .../i18n/translation/LanguageProviderOrder.kt | 4 +- .../translation/LanguageProviderProperties.kt | 16 ++-- .../PapagoLanguageDetectionProvider.kt | 2 +- .../translation/PapagoTranslationService.kt | 2 +- .../translation/ProviderTranslationResult.kt | 2 +- .../translation/TranslationCoordinator.kt | 6 +- .../i18n/translation/TranslationJobWorker.kt | 4 +- .../i18n/translation/TranslationProvider.kt | 2 +- .../CreatorCommunityLanguageDetectTest.kt | 9 ++- .../LanguageDetectionCacheServiceTest.kt | 7 +- .../LanguageDetectionProviderTest.kt | 52 +++++++------ .../LanguageProviderCacheEndToEndTest.kt | 34 +++++---- .../translation/LanguageProviderIdTest.kt | 56 ++++++++++++++ .../LanguageProviderPropertiesTest.kt | 24 +++--- ...LanguageProviderSpringConfigurationTest.kt | 76 ++++++++++--------- .../translation/TranslationCoordinatorTest.kt | 54 ++++++------- .../translation/TranslationJobWorkerTest.kt | 4 +- 21 files changed, 228 insertions(+), 148 deletions(-) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderId.kt create mode 100644 src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderIdTest.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/LanguageDetectionCacheService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/LanguageDetectionCacheService.kt index 57527013..b573c047 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/LanguageDetectionCacheService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/LanguageDetectionCacheService.kt @@ -13,7 +13,7 @@ class LanguageDetectionCacheService( providers: List, properties: LanguageProviderProperties ) { - private val providers = properties.orderedProviders(properties.detectionOrder, providers) { it.providerName } + private val providers = properties.orderedProviders(properties.detectionOrder, providers) { it.providerId } @Transactional fun detectWithCache(query: String): DetectedLanguage? { @@ -24,11 +24,11 @@ class LanguageDetectionCacheService( providers.forEach { provider -> val cached = languageDetectionResultRepository.findBySourceHashAndProviderAndNormalizationVersion( sourceHash = sourceHash, - provider = provider.providerName, + provider = provider.providerId.code, normalizationVersion = SourceTextNormalizer.NORMALIZATION_VERSION ) if (cached != null && cached.detectedLanguage.isNotBlank()) { - return DetectedLanguage(cached.detectedLanguage, provider.providerName) + return DetectedLanguage(cached.detectedLanguage, provider.providerId) } } @@ -42,18 +42,18 @@ class LanguageDetectionCacheService( sourceHash = sourceHash, sourceTextSample = normalizedQuery.take(MAX_SAMPLE_LENGTH), detectedLanguage = detectedLanguage, - provider = provider.providerName, + provider = provider.providerId.code, confidence = null, normalizationVersion = SourceTextNormalizer.NORMALIZATION_VERSION ) val winner = requireNotNull( languageDetectionResultRepository.findDetectedLanguageForUpdate( sourceHash = sourceHash, - provider = provider.providerName, + provider = provider.providerId.code, normalizationVersion = SourceTextNormalizer.NORMALIZATION_VERSION ) ) - return DetectedLanguage(winner, provider.providerName) + return DetectedLanguage(winner, provider.providerId) } return null } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/DetectedLanguage.kt b/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/DetectedLanguage.kt index d7653ceb..2b6dc434 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/DetectedLanguage.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/DetectedLanguage.kt @@ -1,3 +1,3 @@ package kr.co.vividnext.sodalive.i18n.translation -data class DetectedLanguage(val detectedLanguage: String, val providerName: String) +data class DetectedLanguage(val detectedLanguage: String, val providerId: LanguageProviderId) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageDetectionProvider.kt b/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageDetectionProvider.kt index 9ee23e74..9444b6ad 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageDetectionProvider.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageDetectionProvider.kt @@ -1,7 +1,7 @@ package kr.co.vividnext.sodalive.i18n.translation interface LanguageDetectionProvider { - val providerName: String + val providerId: LanguageProviderId fun detect(query: String): String? } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderId.kt b/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderId.kt new file mode 100644 index 00000000..8dffa18d --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderId.kt @@ -0,0 +1,6 @@ +package kr.co.vividnext.sodalive.i18n.translation + +enum class LanguageProviderId(val code: String) { + PAPAGO("papago"), + AWS("aws") +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderOrder.kt b/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderOrder.kt index e1c4b39b..445b4f75 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderOrder.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderOrder.kt @@ -1,6 +1,6 @@ package kr.co.vividnext.sodalive.i18n.translation object LanguageProviderOrder { - val detectionOrder = listOf("papago") - val translationOrder = listOf("papago") + val detectionOrder = listOf(LanguageProviderId.PAPAGO) + val translationOrder = listOf(LanguageProviderId.PAPAGO) } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderProperties.kt b/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderProperties.kt index 9fdb4232..87fe1af1 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderProperties.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderProperties.kt @@ -5,18 +5,18 @@ import org.springframework.stereotype.Component @Component class LanguageProviderProperties( - val detectionOrder: List, - val translationOrder: List + val detectionOrder: List, + val translationOrder: List ) { @Autowired constructor() : this(LanguageProviderOrder.detectionOrder, LanguageProviderOrder.translationOrder) - fun orderedProviders(order: List, providers: List, name: (T) -> String): List { - require(order.isNotEmpty() && order.none { it.isBlank() }) { "language provider order must not be empty" } + fun orderedProviders(order: List, providers: List, id: (T) -> LanguageProviderId): List { + require(order.isNotEmpty()) { "language provider order must not be empty" } require(order.distinct().size == order.size) { "duplicate language provider order" } - val byName = providers.associateBy(name) - require(byName.size == providers.size) { "duplicate registered language provider name" } - require(order.all { it in byName }) { "unknown language provider in order" } - return order.map { byName.getValue(it) } + val byId = providers.associateBy(id) + require(byId.size == providers.size) { "duplicate registered language provider name" } + require(order.all { it in byId }) { "unknown language provider in order" } + return order.map { byId.getValue(it) } } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/PapagoLanguageDetectionProvider.kt b/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/PapagoLanguageDetectionProvider.kt index f0af2808..edbacd66 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/PapagoLanguageDetectionProvider.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/PapagoLanguageDetectionProvider.kt @@ -16,7 +16,7 @@ class PapagoLanguageDetectionProvider( private val papagoClientSecret: String ) : LanguageDetectionProvider { private val restTemplate = RestTemplate() - override val providerName = "papago" + override val providerId = LanguageProviderId.PAPAGO override fun detect(query: String): String? { val headers = HttpHeaders().apply { diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/PapagoTranslationService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/PapagoTranslationService.kt index 75b2a903..24f79539 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/PapagoTranslationService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/PapagoTranslationService.kt @@ -20,7 +20,7 @@ class PapagoTranslationService( private val papagoTranslateUrl = "https://papago.apigw.ntruss.com/nmt/v1/translation" - override val providerName: String = "papago" + override val providerId: LanguageProviderId = LanguageProviderId.PAPAGO override val providerVersion: String = "nmt-v1" diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/ProviderTranslationResult.kt b/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/ProviderTranslationResult.kt index 0e022faa..d3533fe5 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/ProviderTranslationResult.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/ProviderTranslationResult.kt @@ -2,6 +2,6 @@ package kr.co.vividnext.sodalive.i18n.translation data class ProviderTranslationResult( val result: TranslateResult, - val providerName: String, + val providerId: LanguageProviderId, val providerVersion: String ) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/TranslationCoordinator.kt b/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/TranslationCoordinator.kt index 4aeec3fc..d130fd7c 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/TranslationCoordinator.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/TranslationCoordinator.kt @@ -8,7 +8,7 @@ class TranslationCoordinator( properties: LanguageProviderProperties, private val translationMemoryRepository: TranslationMemoryRepository ) { - private val providers = properties.orderedProviders(properties.translationOrder, providers) { it.providerName } + private val providers = properties.orderedProviders(properties.translationOrder, providers) { it.providerId } fun findCached(sourceHash: String, sourceLanguage: String, targetLanguage: String): TranslationMemory? { providers.forEach { provider -> @@ -17,7 +17,7 @@ class TranslationCoordinator( sourceHash, sourceLanguage, targetLanguage, - provider.providerName, + provider.providerId.code, SourceTextNormalizer.NORMALIZATION_VERSION ) if (memory != null && memory.translatedText.isNotBlank()) return memory @@ -42,7 +42,7 @@ class TranslationCoordinator( result != null && result.translatedText.size == request.texts.size && result.translatedText.none { it.isBlank() } ) { - return ProviderTranslationResult(result, provider.providerName, provider.providerVersion) + return ProviderTranslationResult(result, provider.providerId, provider.providerVersion) } } throw IllegalStateException("all translation providers failed") diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/TranslationJobWorker.kt b/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/TranslationJobWorker.kt index 46f3960c..5ea1d74e 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/TranslationJobWorker.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/TranslationJobWorker.kt @@ -87,7 +87,7 @@ class TranslationJobWorker( sourceHash = job.sourceHash, sourceLanguage = job.sourceLanguage, targetLanguage = job.targetLanguage, - provider = response.providerName, + provider = response.providerId.code, normalizationVersion = SourceTextNormalizer.NORMALIZATION_VERSION ) if (memory == null) { @@ -98,7 +98,7 @@ class TranslationJobWorker( sourceLanguage = job.sourceLanguage, targetLanguage = job.targetLanguage, translatedText = translated, - provider = response.providerName, + provider = response.providerId.code, providerVersion = response.providerVersion ) ) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/TranslationProvider.kt b/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/TranslationProvider.kt index ca03fdaf..a35d145d 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/TranslationProvider.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/TranslationProvider.kt @@ -1,7 +1,7 @@ package kr.co.vividnext.sodalive.i18n.translation interface TranslationProvider { - val providerName: String + val providerId: LanguageProviderId val providerVersion: String fun translate(request: TranslateRequest): TranslateResult diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/content/CreatorCommunityLanguageDetectTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/content/CreatorCommunityLanguageDetectTest.kt index fcb2c6d5..662c6ce4 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/content/CreatorCommunityLanguageDetectTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/content/CreatorCommunityLanguageDetectTest.kt @@ -17,6 +17,7 @@ import kr.co.vividnext.sodalive.i18n.LangContext import kr.co.vividnext.sodalive.i18n.SodaMessageSource import kr.co.vividnext.sodalive.i18n.translation.DetectedLanguage import kr.co.vividnext.sodalive.i18n.translation.LanguageDetectionProvider +import kr.co.vividnext.sodalive.i18n.translation.LanguageProviderId import kr.co.vividnext.sodalive.i18n.translation.LanguageProviderProperties import kr.co.vividnext.sodalive.i18n.translation.LanguageTranslationTargetType import kr.co.vividnext.sodalive.i18n.translation.PapagoTranslationService @@ -730,7 +731,7 @@ class ControlledLanguageDetectionCacheService( ) : LanguageDetectionCacheService( languageDetectionResultRepository, listOf(object : LanguageDetectionProvider { - override val providerName = "papago" + override val providerId = LanguageProviderId.PAPAGO override fun detect(query: String): String? = null }), LanguageProviderProperties() @@ -759,15 +760,15 @@ class ControlledLanguageDetectionCacheService( queries.add(query) val concurrentDetection = concurrentDetections[query] if (concurrentDetection != null) { - return DetectedLanguage(concurrentDetection.detect(), "papago") + return DetectedLanguage(concurrentDetection.detect(), LanguageProviderId.PAPAGO) } val blockingDetection = blockingDetections[query] if (blockingDetection != null) { blockingDetection.started.countDown() check(blockingDetection.release.await(5, TimeUnit.SECONDS)) - return DetectedLanguage(blockingDetection.detectedLanguage, "papago") + return DetectedLanguage(blockingDetection.detectedLanguage, LanguageProviderId.PAPAGO) } - return detections[query]?.let { DetectedLanguage(it, "papago") } + return detections[query]?.let { DetectedLanguage(it, LanguageProviderId.PAPAGO) } } } diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/content/LanguageDetectionCacheServiceTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/content/LanguageDetectionCacheServiceTest.kt index 25314e53..c33ab3f9 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/content/LanguageDetectionCacheServiceTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/content/LanguageDetectionCacheServiceTest.kt @@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.content import kr.co.vividnext.sodalive.i18n.translation.DetectedLanguage import kr.co.vividnext.sodalive.i18n.translation.LanguageDetectionProvider +import kr.co.vividnext.sodalive.i18n.translation.LanguageProviderId import kr.co.vividnext.sodalive.i18n.translation.LanguageProviderProperties import kr.co.vividnext.sodalive.i18n.translation.SourceTextNormalizer import org.junit.jupiter.api.Assertions.assertEquals @@ -36,7 +37,7 @@ class LanguageDetectionCacheServiceTest { val detected = service.detectWithCache("Hello world") - assertEquals(DetectedLanguage("en", "papago"), detected) + assertEquals(DetectedLanguage("en", LanguageProviderId.PAPAGO), detected) assertEquals(0, providerCalls) Mockito.verify(repository).findBySourceHashAndProviderAndNormalizationVersion( sourceHash = sourceHash, @@ -80,7 +81,7 @@ class LanguageDetectionCacheServiceTest { val detected = service.detectWithCache("Hello world") - assertEquals(DetectedLanguage("ko", "papago"), detected) + assertEquals(DetectedLanguage("ko", LanguageProviderId.PAPAGO), detected) Mockito.verify(repository).findBySourceHashAndProviderAndNormalizationVersion( sourceHash = sourceHash, provider = "papago", @@ -104,7 +105,7 @@ class LanguageDetectionCacheServiceTest { private fun service(repository: LanguageDetectionResultRepository, detect: () -> String?): LanguageDetectionCacheService { val provider = object : LanguageDetectionProvider { - override val providerName = "papago" + override val providerId = LanguageProviderId.PAPAGO override fun detect(query: String) = detect() } return LanguageDetectionCacheService(repository, listOf(provider), LanguageProviderProperties()) diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageDetectionProviderTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageDetectionProviderTest.kt index 6fdae682..0e889ae1 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageDetectionProviderTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageDetectionProviderTest.kt @@ -3,6 +3,8 @@ package kr.co.vividnext.sodalive.i18n.translation import kr.co.vividnext.sodalive.content.LanguageDetectionCacheService import kr.co.vividnext.sodalive.content.LanguageDetectionResult 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.assertEquals import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertSame @@ -21,39 +23,39 @@ class LanguageDetectionProviderTest { fun shouldFallbackAndReturnCanonicalWinnerWithActualProvider() { val failures: List<() -> String?> = listOf({ throw IllegalStateException("sensitive body") }, { null }, { " " }) failures.forEach { failure -> - val first = RecordingDetectionProvider("first", failure) - val second = RecordingDetectionProvider("second") { "EN" } - Mockito.`when`(repository.findDetectedLanguageForUpdate(hash, "second", "v1")).thenReturn("ko") + val first = RecordingDetectionProvider(PAPAGO, failure) + val second = RecordingDetectionProvider(AWS) { "EN" } + Mockito.`when`(repository.findDetectedLanguageForUpdate(hash, "aws", "v1")).thenReturn("ko") - assertEquals(DetectedLanguage("ko", "second"), service(listOf(first, second)).detectWithCache(query)) + assertEquals(DetectedLanguage("ko", AWS), service(listOf(first, second)).detectWithCache(query)) assertEquals(listOf(query), first.queries) assertEquals(listOf(query), second.queries) } - Mockito.verify(repository, Mockito.times(3)).insertIfAbsent(hash, "original source", "en", "second", null, "v1") + Mockito.verify(repository, Mockito.times(3)).insertIfAbsent(hash, "original source", "en", "aws", null, "v1") } @Test @DisplayName("보조 공급자의 유효 캐시가 있으면 첫 공급자도 호출하지 않는다") fun shouldCheckAllConfiguredCachesBeforeCallingAnyProvider() { - val first = RecordingDetectionProvider("first") { "ko" } - val second = RecordingDetectionProvider("second") { "en" } - Mockito.`when`(repository.findBySourceHashAndProviderAndNormalizationVersion(hash, "second", "v1")) - .thenReturn(LanguageDetectionResult(hash, "sample", "ja", "second", null, "v1")) + val first = RecordingDetectionProvider(PAPAGO) { "ko" } + val second = RecordingDetectionProvider(AWS) { "en" } + Mockito.`when`(repository.findBySourceHashAndProviderAndNormalizationVersion(hash, "aws", "v1")) + .thenReturn(LanguageDetectionResult(hash, "sample", "ja", "aws", null, "v1")) - assertEquals(DetectedLanguage("ja", "second"), service(listOf(first, second)).detectWithCache(query)) + assertEquals(DetectedLanguage("ja", AWS), service(listOf(first, second)).detectWithCache(query)) assertEquals(emptyList(), first.queries + second.queries) } @Test @DisplayName("정상 미지원 언어 감지는 성공이며 후속 공급자를 호출하지 않는다") fun shouldKeepUnsupportedDetectionAsSuccessAndHonorOrder() { - val first = RecordingDetectionProvider("first") { "ko" } - val second = RecordingDetectionProvider("second") { "FR" } - Mockito.`when`(repository.findDetectedLanguageForUpdate(hash, "second", "v1")).thenReturn("fr") + val first = RecordingDetectionProvider(PAPAGO) { "ko" } + val second = RecordingDetectionProvider(AWS) { "FR" } + Mockito.`when`(repository.findDetectedLanguageForUpdate(hash, "aws", "v1")).thenReturn("fr") assertEquals( - DetectedLanguage("fr", "second"), - service(listOf(first, second), listOf("second", "first")).detectWithCache(query) + DetectedLanguage("fr", AWS), + service(listOf(first, second), listOf(AWS, PAPAGO)).detectWithCache(query) ) assertEquals(emptyList(), first.queries) assertEquals(listOf(query), second.queries) @@ -62,8 +64,8 @@ class LanguageDetectionProviderTest { @Test @DisplayName("감지 전체 실패와 공백 입력은 null이고 공백은 저장소와 공급자를 호출하지 않는다") fun shouldReturnNullAfterAllFailuresAndSkipBlankInput() { - val first = RecordingDetectionProvider("first") { null } - val second = RecordingDetectionProvider("second") { null } + val first = RecordingDetectionProvider(PAPAGO) { null } + val second = RecordingDetectionProvider(AWS) { null } val service = service(listOf(first, second)) assertNull(service.detectWithCache(" \n")) Mockito.verifyNoInteractions(repository) @@ -75,28 +77,30 @@ class LanguageDetectionProviderTest { @Test @DisplayName("감지 저장소 조회와 저장 오류는 다음 공급자로 전환하지 않고 전파한다") fun shouldPropagateRepositoryFailuresWithoutFallback() { - val first = RecordingDetectionProvider("first") { "ko" } - val second = RecordingDetectionProvider("second") { "en" } + val first = RecordingDetectionProvider(PAPAGO) { "ko" } + val second = RecordingDetectionProvider(AWS) { "en" } val service = service(listOf(first, second)) val failure = IllegalStateException("database unavailable") - Mockito.`when`(repository.findBySourceHashAndProviderAndNormalizationVersion(hash, "first", "v1")) + Mockito.`when`(repository.findBySourceHashAndProviderAndNormalizationVersion(hash, "papago", "v1")) .thenThrow(failure) assertSame(failure, assertThrows(IllegalStateException::class.java) { service.detectWithCache(query) }) assertEquals(emptyList(), first.queries + second.queries) Mockito.reset(repository) - Mockito.`when`(repository.insertIfAbsent(hash, "original source", "ko", "first", null, "v1")).thenThrow(failure) + Mockito.`when`(repository.insertIfAbsent(hash, "original source", "ko", "papago", null, "v1")).thenThrow(failure) assertSame(failure, assertThrows(IllegalStateException::class.java) { service.detectWithCache(query) }) assertEquals(listOf(query), first.queries) assertEquals(emptyList(), second.queries) } - private fun service(providers: List, order: List = providers.map { it.providerName }) = - LanguageDetectionCacheService(repository, providers, LanguageProviderProperties(order, listOf("papago"))) + private fun service( + providers: List, + order: List = providers.map { it.providerId } + ) = LanguageDetectionCacheService(repository, providers, LanguageProviderProperties(order, listOf(PAPAGO))) } class RecordingDetectionProvider( - override val providerName: String, + override val providerId: LanguageProviderId, private val response: () -> String? ) : LanguageDetectionProvider { val queries = mutableListOf() diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderCacheEndToEndTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderCacheEndToEndTest.kt index 1d5e0203..c2e98f00 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderCacheEndToEndTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderCacheEndToEndTest.kt @@ -6,6 +6,8 @@ import kr.co.vividnext.sodalive.content.LanguageDetectionResultRepository 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.AWS +import kr.co.vividnext.sodalive.i18n.translation.LanguageProviderId.PAPAGO import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.MemberRepository import kr.co.vividnext.sodalive.member.MemberRole @@ -86,16 +88,16 @@ class LanguageProviderCacheEndToEndTest @Autowired constructor( @DisplayName("보조 공급자의 감지를 H2에 귀속 저장하고 외부 호출 없이 캐시를 재사용한다") fun shouldPersistSecondaryDetectionAndReuseCache() { val source = "provider source ${UUID.randomUUID()}" - assertEquals(DetectedLanguage("ko", "secondary"), detector.detectWithCache(source)) - assertEquals(DetectedLanguage("ko", "secondary"), detector.detectWithCache(source)) + assertEquals(DetectedLanguage("ko", AWS), detector.detectWithCache(source)) + assertEquals(DetectedLanguage("ko", AWS), detector.detectWithCache(source)) val detection = transaction.execute { detectionRepository.findBySourceHashAndProviderAndNormalizationVersion( SourceTextNormalizer.hash(source), - "secondary", + "aws", "v1" ) }!! - assertEquals("secondary", detection.provider) + assertEquals("aws", detection.provider) assertEquals("ko", detection.detectedLanguage) assertEquals(1, papagoDetection.queries.size) assertEquals(1, secondaryDetection.queries.size) @@ -113,19 +115,19 @@ class LanguageProviderCacheEndToEndTest @Autowired constructor( SourceTextNormalizer.hash(source), "ko", "en", - "secondary", + "aws", "v1" ) } assertNotNull(memory) - assertEquals("secondary", memory!!.provider) - assertEquals("secondary-v1", memory.providerVersion) - assertEquals("secondary:0", memory.translatedText) + assertEquals("aws", memory!!.provider) + assertEquals("AWS-v1", memory.providerVersion) + assertEquals("AWS:0", memory.translatedText) assertEquals( TranslationJobStatus.COMPLETED, transaction.execute { jobRepository.findAll().single { it.resourceId == postId }.status } ) - assertEquals("secondary:0", displayService.findDisplayContents(listOf(postId), "en")[postId]?.content) + assertEquals("AWS:0", displayService.findDisplayContents(listOf(postId), "en")[postId]?.content) transaction.executeWithoutResult { translations.deleteAll() @@ -137,7 +139,7 @@ class LanguageProviderCacheEndToEndTest @Autowired constructor( assertEquals(1, papagoTranslation.requests.size) assertEquals(1, secondaryTranslation.requests.size) assertEquals(listOf(source), secondaryTranslation.requests.single().texts) - assertEquals("secondary:0", displayService.findDisplayContents(listOf(postId), "en")[postId]?.content) + assertEquals("AWS:0", displayService.findDisplayContents(listOf(postId), "en")[postId]?.content) } private fun schedule(postId: Long, source: String) { @@ -162,20 +164,20 @@ class LanguageProviderCacheEndToEndTest @Autowired constructor( class Providers { @Bean fun languageProviderProperties() = LanguageProviderProperties( - listOf("papago", "secondary"), - listOf("papago", "secondary") + listOf(PAPAGO, AWS), + listOf(PAPAGO, AWS) ) @Bean - fun papagoDetection() = RecordingDetectionProvider("papago") { throw IllegalStateException("sensitive provider body") } + fun papagoDetection() = RecordingDetectionProvider(PAPAGO) { throw IllegalStateException("sensitive provider body") } @Bean - fun secondaryDetection() = RecordingDetectionProvider("secondary") { "ko" } + fun secondaryDetection() = RecordingDetectionProvider(AWS) { "ko" } @Bean - fun papagoTranslation() = RecordingTranslationProvider("papago") { TranslateResult(emptyList()) } + fun papagoTranslation() = RecordingTranslationProvider(PAPAGO) { TranslateResult(emptyList()) } @Bean - fun secondaryTranslation() = RecordingTranslationProvider("secondary") + fun secondaryTranslation() = RecordingTranslationProvider(AWS) } } diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderIdTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderIdTest.kt new file mode 100644 index 00000000..a384b7a2 --- /dev/null +++ b/src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderIdTest.kt @@ -0,0 +1,56 @@ +package kr.co.vividnext.sodalive.i18n.translation + +import org.junit.jupiter.api.Assertions.assertAll +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.function.Executable + +class LanguageProviderIdTest { + @Test + @DisplayName("공급자 인터페이스와 성공 결과의 런타임 식별자는 enum이다") + fun shouldExposeEnumProviderIdentityOnInterfacesAndResults() { + assertAll( + *listOf( + LanguageDetectionProvider::class.java, + TranslationProvider::class.java, + DetectedLanguage::class.java, + ProviderTranslationResult::class.java + ).map { contract -> + Executable { + val getter = contract.methods.firstOrNull { it.name == "getProviderId" } + assertEquals( + "kr.co.vividnext.sodalive.i18n.translation.LanguageProviderId", + getter?.returnType?.name, + "${contract.simpleName}.getProviderId must return LanguageProviderId" + ) + assertTrue(getter?.returnType?.isEnum == true) + assertFalse(contract.methods.any { it.name == "getProviderName" }) + } + }.toTypedArray() + ) + } + + @Test + @DisplayName("공급자의 저장 code는 기존 소문자 값으로 고정한다") + fun shouldKeepStablePersistenceCodes() { + assertEquals("papago", LanguageProviderId.PAPAGO.code) + assertEquals("aws", LanguageProviderId.AWS.code) + assertEquals(listOf(LanguageProviderId.PAPAGO, LanguageProviderId.AWS), LanguageProviderId.values().toList()) + } + + @Test + @DisplayName("production 코드 목록과 실제 Papago adapter는 PAPAGO enum을 사용한다") + 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) + assertEquals(LanguageProviderId.PAPAGO, PapagoLanguageDetectionProvider("test-id", "test-secret").providerId) + val translator = PapagoTranslationService("test-id", "test-secret") + assertEquals(LanguageProviderId.PAPAGO, translator.providerId) + assertEquals("nmt-v1", translator.providerVersion) + } +} diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderPropertiesTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderPropertiesTest.kt index bc639e20..8d5f9744 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderPropertiesTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderPropertiesTest.kt @@ -1,5 +1,7 @@ package kr.co.vividnext.sodalive.i18n.translation +import kr.co.vividnext.sodalive.i18n.translation.LanguageProviderId.AWS +import kr.co.vividnext.sodalive.i18n.translation.LanguageProviderId.PAPAGO import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.DisplayName @@ -9,33 +11,33 @@ class LanguageProviderPropertiesTest { @Test @DisplayName("기능별 목록을 서로 독립된 설정 순서로 정렬한다") fun shouldOrderCapabilitiesIndependently() { - val properties = LanguageProviderProperties(listOf("second", "first"), listOf("first", "second")) + val properties = LanguageProviderProperties(listOf(AWS, PAPAGO), listOf(PAPAGO, AWS)) assertEquals( - listOf("second", "first"), - properties.orderedProviders(properties.detectionOrder, listOf("first", "second")) { it } + listOf(AWS, PAPAGO), + properties.orderedProviders(properties.detectionOrder, listOf(PAPAGO, AWS)) { it } ) assertEquals( - listOf("first", "second"), - properties.orderedProviders(properties.translationOrder, listOf("second", "first")) { it } + listOf(PAPAGO, AWS), + properties.orderedProviders(properties.translationOrder, listOf(AWS, PAPAGO)) { it } ) } @Test - @DisplayName("빈 순서와 빈 이름과 중복 순서와 미등록 공급자는 초기화에서 거부한다") + @DisplayName("빈 순서와 중복 순서와 미등록 AWS 공급자는 초기화에서 거부한다") fun shouldRejectInvalidOrders() { val properties = LanguageProviderProperties() - listOf(emptyList(), listOf(""), listOf("first", "first"), listOf("unknown")).forEach { order -> + listOf(emptyList(), listOf(PAPAGO, PAPAGO), listOf(AWS)).forEach { order -> assertThrows(IllegalArgumentException::class.java) { - properties.orderedProviders(order, listOf("first", "second")) { it } + properties.orderedProviders(order, listOf(PAPAGO)) { it } } } } @Test - @DisplayName("같은 기능 안에서 중복 등록된 공급자 이름은 초기화에서 거부한다") - fun shouldRejectDuplicateRegisteredNamesEvenWhenNotSelected() { + @DisplayName("같은 기능 안에서 선택되지 않은 공급자도 중복 등록을 거부한다") + fun shouldRejectDuplicateRegisteredIdsEvenWhenNotSelected() { assertThrows(IllegalArgumentException::class.java) { - LanguageProviderProperties().orderedProviders(listOf("papago"), listOf("papago", "other", "other")) { it } + LanguageProviderProperties().orderedProviders(listOf(PAPAGO), listOf(PAPAGO, AWS, AWS)) { it } } } } diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderSpringConfigurationTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderSpringConfigurationTest.kt index c127f746..fb11dcfc 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderSpringConfigurationTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderSpringConfigurationTest.kt @@ -2,6 +2,8 @@ package kr.co.vividnext.sodalive.i18n.translation 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.assertEquals import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Assertions.assertNull @@ -16,7 +18,8 @@ import org.springframework.core.env.SystemEnvironmentPropertySource import java.util.function.Supplier class LanguageProviderSpringConfigurationTest { - private val runner = ApplicationContextRunner().withUserConfiguration(Providers::class.java) + private val papagoRunner = ApplicationContextRunner().withUserConfiguration(Providers::class.java) + private val runner = papagoRunner.withUserConfiguration(AwsProviders::class.java) private val defaultRunner = runner.withUserConfiguration(LanguageProviderProperties::class.java) @Test @@ -28,8 +31,8 @@ class LanguageProviderSpringConfigurationTest { ).run { context -> assertNull(context.startupFailure) val properties = context.getBean(LanguageProviderProperties::class.java) - assertEquals(listOf("papago"), properties.detectionOrder) - assertEquals(listOf("papago"), properties.translationOrder) + assertEquals(listOf(PAPAGO), properties.detectionOrder) + assertEquals(listOf(PAPAGO), properties.translationOrder) } } @@ -54,8 +57,8 @@ 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) + assertEquals(listOf(PAPAGO), properties.detectionOrder) + assertEquals(listOf(PAPAGO), properties.translationOrder) } } @@ -63,39 +66,39 @@ class LanguageProviderSpringConfigurationTest { @DisplayName("Spring 테스트 bean의 감지 목록만 바꾸어도 번역 순서는 유지한다") fun shouldInjectIndependentCapabilityOrders() { listOf( - listOf("papago", "secondary") to "papago", - listOf("secondary", "papago") to "secondary" + listOf(PAPAGO, AWS) to PAPAGO, + listOf(AWS, PAPAGO) to AWS ).forEach { (order, expected) -> runner.withBean( LanguageProviderProperties::class.java, - Supplier { LanguageProviderProperties(order, listOf("papago", "secondary")) } + Supplier { LanguageProviderProperties(order, listOf(PAPAGO, AWS)) } ).run { context -> assertNull(context.startupFailure) val detector = context.getBean(LanguageDetectionCacheService::class.java) - assertEquals(expected, detector.detectWithCache("source")?.providerName) + assertEquals(expected, detector.detectWithCache("source")?.providerId) assertEquals( - "papago", + PAPAGO, context.getBean(TranslationCoordinator::class.java) - .translate(TranslateRequest(listOf("source"), "ko", "en")).providerName + .translate(TranslateRequest(listOf("source"), "ko", "en")).providerId ) val secondary = context.getBean("secondaryDetection", RecordingDetectionProvider::class.java) - assertEquals(if (expected == "secondary") 1 else 0, secondary.queries.size) + assertEquals(if (expected == AWS) 1 else 0, secondary.queries.size) assertEquals(0, context.getBean("secondaryTranslation", RecordingTranslationProvider::class.java).requests.size) } } runner.withBean( LanguageProviderProperties::class.java, - Supplier { LanguageProviderProperties(listOf("papago", "secondary"), listOf("secondary", "papago")) } + Supplier { LanguageProviderProperties(listOf(PAPAGO, AWS), listOf(AWS, PAPAGO)) } ).run { context -> assertNull(context.startupFailure) assertEquals( - "papago", - context.getBean(LanguageDetectionCacheService::class.java).detectWithCache("source")?.providerName + PAPAGO, + context.getBean(LanguageDetectionCacheService::class.java).detectWithCache("source")?.providerId ) assertEquals( - "secondary", + AWS, context.getBean(TranslationCoordinator::class.java) - .translate(TranslateRequest(listOf("source"), "ko", "en")).providerName + .translate(TranslateRequest(listOf("source"), "ko", "en")).providerId ) } } @@ -106,29 +109,29 @@ class LanguageProviderSpringConfigurationTest { defaultRunner.run { context -> assertNull(context.startupFailure) val properties = context.getBean(LanguageProviderProperties::class.java) - assertEquals(listOf("papago"), properties.detectionOrder) - assertEquals(listOf("papago"), properties.translationOrder) + assertEquals(listOf(PAPAGO), properties.detectionOrder) + assertEquals(listOf(PAPAGO), properties.translationOrder) assertEquals( - "papago", - context.getBean(LanguageDetectionCacheService::class.java).detectWithCache("source")?.providerName + PAPAGO, + context.getBean(LanguageDetectionCacheService::class.java).detectWithCache("source")?.providerId ) assertEquals( - "papago", + PAPAGO, context.getBean(TranslationCoordinator::class.java) - .translate(TranslateRequest(listOf("source"), "ko", "en")).providerName + .translate(TranslateRequest(listOf("source"), "ko", "en")).providerId ) } } @Test - @DisplayName("Spring 테스트 bean의 빈 목록과 빈 이름과 중복 순서와 미등록 이름은 시작 시 실패한다") + @DisplayName("Spring 테스트 bean의 빈 목록과 중복 순서와 해당 기능의 AWS 구현체 부재는 시작 시 실패한다") fun shouldRejectInvalidOrdersAtStartup() { - listOf(emptyList(), listOf(""), listOf("papago", "papago"), listOf("unknown")).forEach { order -> + listOf(emptyList(), listOf(PAPAGO, PAPAGO), listOf(AWS)).forEach { order -> listOf( - LanguageProviderProperties(order, listOf("papago")), - LanguageProviderProperties(listOf("papago"), order) + LanguageProviderProperties(order, listOf(PAPAGO)), + LanguageProviderProperties(listOf(PAPAGO), order) ).forEach { properties -> - runner.withBean(LanguageProviderProperties::class.java, Supplier { properties }).run { context -> + papagoRunner.withBean(LanguageProviderProperties::class.java, Supplier { properties }).run { context -> assertNotNull(context.startupFailure) } } @@ -144,7 +147,7 @@ class LanguageProviderSpringConfigurationTest { @Bean fun detectionRepository(): LanguageDetectionResultRepository { val repository = Mockito.mock(LanguageDetectionResultRepository::class.java) - listOf("papago", "secondary").forEach { provider -> + listOf("papago", "aws").forEach { provider -> Mockito.`when`(repository.findDetectedLanguageForUpdate(SourceTextNormalizer.hash("source"), provider, "v1")) .thenReturn("ko") } @@ -152,15 +155,18 @@ class LanguageProviderSpringConfigurationTest { } @Bean - fun papagoDetection() = RecordingDetectionProvider("papago") { "ko" } + fun papagoDetection() = RecordingDetectionProvider(PAPAGO) { "ko" } @Bean - fun secondaryDetection() = RecordingDetectionProvider("secondary") { "ko" } + fun papagoTranslation() = RecordingTranslationProvider(PAPAGO) + } + + @TestConfiguration + class AwsProviders { + @Bean + fun secondaryDetection() = RecordingDetectionProvider(AWS) { "ko" } @Bean - fun papagoTranslation() = RecordingTranslationProvider("papago") - - @Bean - fun secondaryTranslation() = RecordingTranslationProvider("secondary") + fun secondaryTranslation() = RecordingTranslationProvider(AWS) } } diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/TranslationCoordinatorTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/TranslationCoordinatorTest.kt index 555ddef2..b23dc76c 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/TranslationCoordinatorTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/TranslationCoordinatorTest.kt @@ -1,5 +1,7 @@ package kr.co.vividnext.sodalive.i18n.translation +import kr.co.vividnext.sodalive.i18n.translation.LanguageProviderId.AWS +import kr.co.vividnext.sodalive.i18n.translation.LanguageProviderId.PAPAGO import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertSame @@ -15,15 +17,15 @@ class TranslationCoordinatorTest { @Test @DisplayName("설정 순서의 첫 성공 공급자와 버전을 반환하고 후속 호출을 멈춘다") fun shouldUseConfiguredOrderAndStopAfterSuccess() { - val first = RecordingTranslationProvider("first") - val second = RecordingTranslationProvider("second") - val coordinator = coordinator(listOf(first, second), listOf("second", "first")) + val first = RecordingTranslationProvider(PAPAGO) + val second = RecordingTranslationProvider(AWS) + val coordinator = coordinator(listOf(first, second), listOf(AWS, PAPAGO)) val result = coordinator.translate(request) - assertEquals("second", result.providerName) - assertEquals("second-v1", result.providerVersion) - assertEquals(listOf("second:0", "second:1"), result.result.translatedText) + assertEquals(AWS, result.providerId) + assertEquals("AWS-v1", result.providerVersion) + assertEquals(listOf("AWS:0", "AWS:1"), result.result.translatedText) assertEquals(listOf(request), second.requests) assertEquals(emptyList(), first.requests) } @@ -37,11 +39,11 @@ class TranslationCoordinatorTest { { TranslateResult(listOf("translated", " ")) } ) failures.forEach { failure -> - val first = RecordingTranslationProvider("first", failure) - val second = RecordingTranslationProvider("second") + val first = RecordingTranslationProvider(PAPAGO, failure) + val second = RecordingTranslationProvider(AWS) val coordinator = coordinator(listOf(first, second)) - assertEquals("second", coordinator.translate(request).providerName) + assertEquals(AWS, coordinator.translate(request).providerId) assertEquals(listOf(request), first.requests) assertEquals(listOf(request), second.requests) } @@ -50,8 +52,8 @@ class TranslationCoordinatorTest { @Test @DisplayName("전체 공급자 실패는 원문이나 원시 예외 원인 없이 고정 메시지를 반환한다") fun shouldSanitizeAllProviderFailures() { - val first = RecordingTranslationProvider("first") { throw IllegalStateException("sensitive response body") } - val second = RecordingTranslationProvider("second") { TranslateResult(emptyList()) } + val first = RecordingTranslationProvider(PAPAGO) { throw IllegalStateException("sensitive response body") } + val second = RecordingTranslationProvider(AWS) { TranslateResult(emptyList()) } val error = assertThrows(IllegalStateException::class.java) { coordinator(listOf(first, second)).translate(request) @@ -66,7 +68,7 @@ class TranslationCoordinatorTest { @Test @DisplayName("빈 목록과 공백과 동일 언어와 미지원 언어는 공급자 호출 전에 거부한다") fun shouldRejectInvalidRequestsBeforeCallingProviders() { - val provider = RecordingTranslationProvider("first") + val provider = RecordingTranslationProvider(PAPAGO) val coordinator = coordinator(listOf(provider)) listOf( request.copy(texts = emptyList()), @@ -85,17 +87,17 @@ class TranslationCoordinatorTest { @Test @DisplayName("모든 설정 공급자 캐시를 호출보다 먼저 조회하고 순서상 첫 유효 캐시를 선택한다") fun shouldFindSecondaryCacheAndPreferConfiguredFirstValidCache() { - val first = RecordingTranslationProvider("first") - val second = RecordingTranslationProvider("second") + val first = RecordingTranslationProvider(PAPAGO) + val second = RecordingTranslationProvider(AWS) val coordinator = coordinator(listOf(first, second)) - val secondary = memory("second", "cached secondary") - givenMemory("second", secondary) + val secondary = memory("aws", "cached secondary") + givenMemory("aws", secondary) assertSame(secondary, coordinator.findCached("hash", "ko", "en")) - givenMemory("first", memory("first", " ")) + givenMemory("papago", memory("papago", " ")) assertSame(secondary, coordinator.findCached("hash", "ko", "en")) - val primary = memory("first", "cached primary") - givenMemory("first", primary) + val primary = memory("papago", "cached primary") + givenMemory("papago", primary) assertSame(primary, coordinator.findCached("hash", "ko", "en")) assertEquals(emptyList(), first.requests + second.requests) } @@ -103,14 +105,14 @@ class TranslationCoordinatorTest { @Test @DisplayName("캐시 저장소 오류는 공급자 실패로 숨기지 않는다") fun shouldPropagateCacheRepositoryFailure() { - val provider = RecordingTranslationProvider("first") + val provider = RecordingTranslationProvider(PAPAGO) val failure = IllegalStateException("database unavailable") Mockito.`when`( repository.findBySourceHashAndSourceLanguageAndTargetLanguageAndProviderAndNormalizationVersion( "hash", "ko", "en", - "first", + "papago", "v1" ) ).thenThrow(failure) @@ -124,8 +126,8 @@ class TranslationCoordinatorTest { private fun coordinator( providers: List, - order: List = providers.map { it.providerName } - ) = TranslationCoordinator(providers, LanguageProviderProperties(listOf("papago"), order), repository) + order: List = providers.map { it.providerId } + ) = TranslationCoordinator(providers, LanguageProviderProperties(listOf(PAPAGO), order), repository) private fun memory(provider: String, text: String) = TranslationMemory( "hash", @@ -151,12 +153,12 @@ class TranslationCoordinatorTest { } class RecordingTranslationProvider( - override val providerName: String, + override val providerId: LanguageProviderId, private val response: (TranslateRequest) -> TranslateResult = { request -> - TranslateResult(request.texts.indices.map { "$providerName:$it" }) + TranslateResult(request.texts.indices.map { "$providerId:$it" }) } ) : TranslationProvider { - override val providerVersion = "$providerName-v1" + override val providerVersion = "$providerId-v1" val requests = mutableListOf() override fun translate(request: TranslateRequest): TranslateResult { diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/TranslationJobWorkerTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/TranslationJobWorkerTest.kt index b20baa7b..d98e1f72 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/TranslationJobWorkerTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/i18n/translation/TranslationJobWorkerTest.kt @@ -236,7 +236,7 @@ class TranslationJobWorkerTest { private fun successfulProvider(): TranslationProvider { return object : TranslationProvider { - override val providerName: String = "papago" + override val providerId: LanguageProviderId = LanguageProviderId.PAPAGO override val providerVersion: String = "nmt-v1" override fun translate(request: TranslateRequest): TranslateResult { @@ -247,7 +247,7 @@ class TranslationJobWorkerTest { private fun failingProvider(): TranslationProvider { return object : TranslationProvider { - override val providerName: String = "papago" + override val providerId: LanguageProviderId = LanguageProviderId.PAPAGO override val providerVersion: String = "nmt-v1" override fun translate(request: TranslateRequest): TranslateResult {