AWS 번역 공급자 추가 #469

Merged
klaus merged 12 commits from test into main 2026-11-10 05:25:25 +00:00
8 changed files with 585 additions and 2 deletions
Showing only changes of commit 7cac09e39c - Show all commits
+6
View File
@@ -16,6 +16,8 @@ plugins {
group = "kr.co.vividnext"
version = "0.0.1-SNAPSHOT"
val querydslVersion = "5.0.0"
extra["httpclient5.version"] = "5.6.4"
extra["httpcore5.version"] = "5.4.3"
java {
sourceCompatibility = JavaVersion.VERSION_17
@@ -56,6 +58,10 @@ dependencies {
implementation("com.amazonaws:aws-java-sdk-s3:1.12.380")
implementation("com.amazonaws:aws-java-sdk-cloudfront:1.12.380")
implementation(platform("software.amazon.awssdk:bom:2.57.11"))
implementation("software.amazon.awssdk:comprehend")
implementation("software.amazon.awssdk:translate")
// bootpay
implementation("io.github.bootpay:backend:2.2.1")
@@ -0,0 +1,27 @@
package kr.co.vividnext.sodalive.i18n.translation
import org.springframework.stereotype.Component
import software.amazon.awssdk.services.comprehend.ComprehendClient
import software.amazon.awssdk.services.comprehend.model.DetectDominantLanguageRequest
@Component
class AwsLanguageDetectionProvider(private val client: ComprehendClient) : LanguageDetectionProvider {
override val providerId = LanguageProviderId.AWS
override fun detect(query: String): String? {
if (query.isBlank()) return null
return try {
// shortcut: 감지는 앞 100000 UTF-8 바이트만 판정하므로 전체 본문과 결과가 다를 수 있음, 전체 본문 기준 품질이 요구되면 별도 감지 정책 승인 후 업그레이드
val text = takeTextPrefixByCodePoint(query, MAX_REQUEST_TEXT_BYTES, ::utf8CodePointSize)
val request = DetectDominantLanguageRequest.builder().text(text).build()
client.detectDominantLanguage(request).languages().firstOrNull()?.languageCode()?.takeIf { it.isNotBlank() }
} catch (_: Exception) {
null
}
}
companion object {
private const val MAX_REQUEST_TEXT_BYTES = 100000
}
}
@@ -0,0 +1,32 @@
package kr.co.vividnext.sodalive.i18n.translation
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider
import software.amazon.awssdk.regions.Region
import software.amazon.awssdk.services.comprehend.ComprehendClient
import software.amazon.awssdk.services.translate.TranslateClient
@Configuration
class AwsLanguageProviderConfiguration(
@Value("\${cloud.aws.credentials.access-key}") accessKey: String,
@Value("\${cloud.aws.credentials.secret-key}") secretKey: String,
@Value("\${cloud.aws.region.static}") region: String
) {
private val credentialsProvider = StaticCredentialsProvider.create(AwsBasicCredentials.create(accessKey, secretKey))
private val region = Region.of(region)
@Bean(destroyMethod = "close")
fun comprehendClient(): ComprehendClient = ComprehendClient.builder()
.credentialsProvider(credentialsProvider)
.region(region)
.build()
@Bean(destroyMethod = "close")
fun translateClient(): TranslateClient = TranslateClient.builder()
.credentialsProvider(credentialsProvider)
.region(region)
.build()
}
@@ -0,0 +1,48 @@
package kr.co.vividnext.sodalive.i18n.translation
import org.springframework.stereotype.Service
import software.amazon.awssdk.services.translate.TranslateClient
import software.amazon.awssdk.services.translate.model.TranslateTextRequest
@Service
class AwsTranslationService(private val client: TranslateClient) : TranslationProvider {
override val providerId = LanguageProviderId.AWS
override val providerVersion = "translate-2017-07-01"
override fun translate(request: TranslateRequest): TranslateResult {
if (request.texts.isEmpty() || request.sourceLanguage == request.targetLanguage) {
return TranslateResult(emptyList())
}
if (
!TranslationLanguages.isSupported(request.sourceLanguage) ||
!TranslationLanguages.isSupported(request.targetLanguage)
) {
return TranslateResult(emptyList())
}
val translatedTexts = mutableListOf<String>()
try {
for (text in request.texts) {
val translatedText = StringBuilder()
for (chunk in splitTextByCodePoint(text, MAX_REQUEST_TEXT_BYTES, ::utf8CodePointSize)) {
val sdkRequest = TranslateTextRequest.builder()
.sourceLanguageCode(request.sourceLanguage)
.targetLanguageCode(request.targetLanguage)
.text(chunk)
.build()
val translated = client.translateText(sdkRequest)?.translatedText()
if (translated.isNullOrBlank()) return TranslateResult(emptyList())
translatedText.append(translated)
}
translatedTexts.add(translatedText.toString())
}
} catch (_: Exception) {
return TranslateResult(emptyList())
}
return TranslateResult(translatedTexts)
}
companion object {
private const val MAX_REQUEST_TEXT_BYTES = 10000
}
}
@@ -0,0 +1,134 @@
package kr.co.vividnext.sodalive.i18n.translation
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource
import org.junit.jupiter.params.provider.NullAndEmptySource
import org.junit.jupiter.params.provider.ValueSource
import org.mockito.ArgumentCaptor
import org.mockito.Mockito
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.boot.test.system.CapturedOutput
import org.springframework.boot.test.system.OutputCaptureExtension
import software.amazon.awssdk.core.exception.SdkClientException
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 java.util.stream.Stream
@SpringBootTest(classes = [AwsLanguageDetectionProvider::class], webEnvironment = SpringBootTest.WebEnvironment.NONE)
@ExtendWith(OutputCaptureExtension::class)
class AwsLanguageDetectionProviderTest @Autowired constructor(
private val provider: AwsLanguageDetectionProvider
) {
@MockBean
private lateinit var client: ComprehendClient
@ParameterizedTest
@ValueSource(strings = ["hi", " 안녕\t\n", "\uD83D\uDE00"])
@DisplayName("짧은 원문도 공백 변경 없이 감지하고 첫 후보를 반환한다")
fun shouldSendShortTextUnchangedAndUseFirstCandidate(query: String) {
respond(language("ja", 0.1f), language("en", 0.9f))
assertEquals("ja", provider.detect(query))
assertEquals(LanguageProviderId.AWS, provider.providerId)
assertRequestedText(query)
}
@ParameterizedTest
@ValueSource(strings = ["", " ", "\t\n"])
@DisplayName("빈 감지 입력과 공백은 SDK를 호출하지 않는다")
fun shouldSkipBlankText(query: String) {
assertNull(provider.detect(query))
Mockito.verifyNoInteractions(client)
}
@ParameterizedTest
@MethodSource("prefixCases")
@DisplayName("감지 입력을 100000 UTF-8 바이트 이내의 정확한 code point prefix로 제한한다")
fun shouldSendExactCodePointPrefix(query: String, expected: String) {
respond(language("ko", 0.8f))
assertEquals("ko", provider.detect(query))
assertRequestedText(expected)
}
@Test
@DisplayName("낮은 confidence의 미지원 첫 언어 코드도 그대로 반환한다")
fun shouldAcceptUnsupportedFirstLanguageWithoutConfidenceThreshold() {
respond(language("fr", 0.001f), language("en", 0.999f))
assertEquals("fr", provider.detect("bonjour"))
assertRequestedText("bonjour")
}
@ParameterizedTest
@NullAndEmptySource
@ValueSource(strings = [" \t\n"])
@DisplayName("첫 후보 코드가 없거나 공백이면 유효한 후속 후보도 선택하지 않는다")
fun shouldRejectInvalidFirstCandidateWithoutScanningLaterCandidates(code: String?) {
respond(language(code, 0.9f), language("en", 0.8f))
assertNull(provider.detect("source"))
assertRequestedText("source")
}
@Test
@DisplayName("감지 후보 목록이 없으면 null을 반환한다")
fun shouldReturnNullForMissingCandidates() {
respond()
assertNull(provider.detect("source"))
assertRequestedText("source")
}
@Test
@DisplayName("SDK 실패는 원문과 오류를 로그에 노출하지 않고 null로 변환한다")
fun shouldReturnNullWithoutLeakingSdkFailure(output: CapturedOutput) {
Mockito.`when`(client.detectDominantLanguage(Mockito.any(DetectDominantLanguageRequest::class.java)))
.thenThrow(SdkClientException.create("sensitive-sdk-marker"))
assertNull(provider.detect("sensitive-source-marker"))
assertRequestedText("sensitive-source-marker")
assertFalse(output.all.contains("sensitive-sdk-marker"))
assertFalse(output.all.contains("sensitive-source-marker"))
}
private fun respond(vararg languages: DominantLanguage) {
Mockito.`when`(client.detectDominantLanguage(Mockito.any(DetectDominantLanguageRequest::class.java)))
.thenReturn(DetectDominantLanguageResponse.builder().languages(*languages).build())
}
private fun language(code: String?, score: Float): DominantLanguage {
return DominantLanguage.builder().languageCode(code).score(score).build()
}
private fun assertRequestedText(expected: String) {
val captor = ArgumentCaptor.forClass(DetectDominantLanguageRequest::class.java)
Mockito.verify(client).detectDominantLanguage(captor.capture())
assertEquals(expected, captor.value.text())
Mockito.verifyNoMoreInteractions(client)
}
companion object {
@JvmStatic
fun prefixCases(): Stream<Arguments> = Stream.of(
Arguments.of("a".repeat(100000), "a".repeat(100000)),
Arguments.of("가".repeat(33333) + "x", "가".repeat(33333) + "x"),
Arguments.of("\uD83D\uDE00".repeat(25000), "\uD83D\uDE00".repeat(25000)),
Arguments.of("a".repeat(100000) + "excluded-tail", "a".repeat(100000)),
Arguments.of("a".repeat(99999) + "가tail", "a".repeat(99999)),
Arguments.of("a".repeat(99998) + "\uD83D\uDE00tail", "a".repeat(99998)),
Arguments.of("a".repeat(99996) + "\uD83D\uDE00tail", "a".repeat(99996) + "\uD83D\uDE00")
)
}
}
@@ -0,0 +1,127 @@
package kr.co.vividnext.sodalive.i18n.translation
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.CsvSource
import org.junit.jupiter.params.provider.ValueSource
import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer
import org.springframework.boot.test.context.runner.ApplicationContextRunner
import org.springframework.test.util.ReflectionTestUtils
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider
import software.amazon.awssdk.awscore.AwsClient
import software.amazon.awssdk.core.client.config.SdkClientConfiguration
import software.amazon.awssdk.core.client.config.SdkClientOption
import software.amazon.awssdk.regions.Region
import software.amazon.awssdk.services.comprehend.ComprehendClient
import software.amazon.awssdk.services.translate.TranslateClient
class AwsLanguageProviderConfigurationTest {
@Test
@DisplayName("공용 테스트 YAML의 합성 키와 서울 리전으로 실제 AWS clients와 providers를 생성한다")
fun shouldLoadRealClientsAndProvidersFromSharedTestConfiguration() {
ApplicationContextRunner()
.withInitializer(ConfigDataApplicationContextInitializer())
.withUserConfiguration(
AwsLanguageProviderConfiguration::class.java,
AwsLanguageDetectionProvider::class.java,
AwsTranslationService::class.java
)
.run { context ->
assertNull(context.startupFailure)
assertEquals(1, context.getBeansOfType(ComprehendClient::class.java).size)
assertEquals(1, context.getBeansOfType(TranslateClient::class.java).size)
assertEquals(1, context.getBeansOfType(LanguageDetectionProvider::class.java).size)
assertEquals(1, context.getBeansOfType(TranslationProvider::class.java).size)
assertEquals(LanguageProviderId.AWS, context.getBean(AwsLanguageDetectionProvider::class.java).providerId)
assertEquals(LanguageProviderId.AWS, context.getBean(AwsTranslationService::class.java).providerId)
val clients = listOf(
context.getBean(ComprehendClient::class.java),
context.getBean(TranslateClient::class.java)
)
clients.forEach { client ->
val configuration = client.serviceClientConfiguration()
assertEquals(Region.AP_NORTHEAST_2, configuration.region())
assertTrue(configuration.credentialsProvider() is StaticCredentialsProvider)
val credentials = (configuration.credentialsProvider() as StaticCredentialsProvider).resolveCredentials()
assertEquals("test-access-key", credentials.accessKeyId())
assertEquals("test-secret-key", credentials.secretAccessKey())
}
}
}
@ParameterizedTest
@CsvSource(
"'',synthetic-secret-key,Access key ID cannot be blank.",
"synthetic-access-key,'',Secret access key cannot be blank."
)
@DisplayName("운영 AWS 설정은 access key 또는 secret key가 비어 있으면 시작을 거부한다")
fun shouldRejectBlankCredentialsAtStartup(accessKey: String, secretKey: String, expectedMessage: String) {
ApplicationContextRunner()
.withUserConfiguration(AwsLanguageProviderConfiguration::class.java)
.withPropertyValues(
"cloud.aws.credentials.access-key=$accessKey",
"cloud.aws.credentials.secret-key=$secretKey",
"cloud.aws.region.static=ap-northeast-2"
)
.run { context ->
assertNotNull(context.startupFailure)
val rootCause = generateSequence(context.startupFailure) { it.cause }.last()
assertTrue(rootCause is NullPointerException)
assertEquals(expectedMessage, rootCause.message)
}
}
@ParameterizedTest
@ValueSource(strings = ["ap-northeast-2", "eu-west-1"])
@DisplayName("기존 명시 설정으로 실제 동기 SDK clients를 생성하고 Spring 종료 시 둘 다 닫는다")
fun shouldConfigureRealClientsAndCloseThemAtShutdown(region: String) {
val poolClosedChecks = mutableListOf<() -> Boolean>()
ApplicationContextRunner()
.withUserConfiguration(AwsLanguageProviderConfiguration::class.java)
.withPropertyValues(
"cloud.aws.credentials.access-key=synthetic-access-key",
"cloud.aws.credentials.secret-key=synthetic-secret-key",
"cloud.aws.region.static=$region"
)
.run { context ->
assertNull(context.startupFailure)
assertEquals(1, context.getBeansOfType(ComprehendClient::class.java).size)
assertEquals(1, context.getBeansOfType(TranslateClient::class.java).size)
val clients = listOf(
context.getBean(ComprehendClient::class.java),
context.getBean(TranslateClient::class.java)
)
clients.forEach { client ->
val configuration = client.serviceClientConfiguration()
assertEquals(Region.of(region), configuration.region())
assertTrue(configuration.credentialsProvider() is StaticCredentialsProvider)
val credentials = (configuration.credentialsProvider() as StaticCredentialsProvider).resolveCredentials()
assertEquals("synthetic-access-key", credentials.accessKeyId())
assertEquals("synthetic-secret-key", credentials.secretAccessKey())
val isClosed = connectionPoolClosedCheck(client)
assertFalse(isClosed())
poolClosedChecks.add(isClosed)
}
}
assertEquals(2, poolClosedChecks.size)
poolClosedChecks.forEach { assertTrue(it()) }
}
private fun connectionPoolClosedCheck(client: AwsClient): () -> Boolean {
val configuration = ReflectionTestUtils.getField(client, "clientConfiguration") as SdkClientConfiguration
val httpClient = configuration.option(SdkClientOption.SYNC_HTTP_CLIENT)
val apacheClient = requireNotNull(ReflectionTestUtils.getField(httpClient, "httpClient"))
val connectionManager = requireNotNull(
ReflectionTestUtils.invokeMethod<Any>(apacheClient, "getHttpClientConnectionManager")
)
return {
requireNotNull(ReflectionTestUtils.invokeMethod<Boolean>(connectionManager, "isClosed"))
}
}
}
@@ -0,0 +1,209 @@
package kr.co.vividnext.sodalive.i18n.translation
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.CsvSource
import org.junit.jupiter.params.provider.ValueSource
import org.mockito.Mockito
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.boot.test.system.CapturedOutput
import org.springframework.boot.test.system.OutputCaptureExtension
import software.amazon.awssdk.core.exception.SdkClientException
import software.amazon.awssdk.services.translate.TranslateClient
import software.amazon.awssdk.services.translate.model.TranslateTextRequest
import software.amazon.awssdk.services.translate.model.TranslateTextResponse
@SpringBootTest(classes = [AwsTranslationService::class], webEnvironment = SpringBootTest.WebEnvironment.NONE)
@ExtendWith(OutputCaptureExtension::class)
class AwsTranslationServiceTest @Autowired constructor(
private val service: AwsTranslationService
) {
@MockBean
private lateinit var client: TranslateClient
private val requests = mutableListOf<TranslateTextRequest>()
@BeforeEach
fun setUp() {
requests.clear()
}
@ParameterizedTest
@ValueSource(ints = [12, 10000])
@DisplayName("10000 UTF-8 바이트 이하 원문은 한 번 요청하고 AWS 결과를 반환한다")
fun shouldTranslateTextWithinByteLimit(length: Int) {
respondWith { response("translated") }
val source = "a".repeat(length)
assertEquals(listOf("translated"), translate(source).translatedText)
assertEquals(LanguageProviderId.AWS, service.providerId)
assertEquals("translate-2017-07-01", service.providerVersion)
assertRequests(source)
}
@Test
@DisplayName("10001바이트 원문을 순서대로 나누고 구분자 없이 결합한다")
fun shouldSplitTextOverByteLimitAndJoinWithoutSeparator() {
val first = "a".repeat(10000)
respondWith { response(if (it.text() == first) "first " else "tail") }
assertEquals(listOf("first tail"), translate(first + "b").translatedText)
assertRequests(first, "b")
}
@Test
@DisplayName("한글 code point와 공백을 UTF-8 한도에 맞춰 손실 없이 분할한다")
fun shouldPreserveKoreanAndWhitespaceAcrossChunks() {
val first = "가".repeat(3333) + "\n"
val second = " 나\t"
respondWith { response(if (it.text() == first) "first" else "second") }
assertEquals(listOf("firstsecond"), translate(first + second).translatedText)
assertRequests(first, second)
}
@Test
@DisplayName("경계에 걸친 보조 평면 문자를 다음 조각에 함께 보존한다")
fun shouldKeepSupplementaryCodePointTogetherAtBoundary() {
val first = "a".repeat(9998)
val second = "\uD83D\uDE00b"
respondWith { response(if (it.text() == first) "first" else "second") }
assertEquals(listOf("firstsecond"), translate(first + second).translatedText)
assertRequests(first, second)
}
@Test
@DisplayName("정확히 10000바이트인 보조 평면 원문은 분할하지 않는다")
fun shouldKeepExactSupplementaryByteLimitInOneRequest() {
val source = "\uD83D\uDE00".repeat(2500)
respondWith { response("translated") }
assertEquals(listOf("translated"), translate(source).translatedText)
assertRequests(source)
}
@Test
@DisplayName("여러 원문의 조각을 순차 호출하고 입력 순서대로 원문당 결과 하나를 반환한다")
fun shouldReturnOneJoinedOutputPerOriginalInOrder() {
val first = "a".repeat(10000)
val second = "b".repeat(10000)
val translated = listOf("first-", "tail", "second-", "tail")
respondWith { response(translated[requests.size - 1]) }
assertEquals(listOf("first-tail", "second-tail"), translate(first + "a", second + "b").translatedText)
assertRequests(first, "a", second, "b")
}
@ParameterizedTest
@CsvSource("ko,en", "en,ja", "ja,ko")
@DisplayName("기존 지원 언어의 source와 target을 SDK 요청에 명시한다")
fun shouldSendExplicitSupportedLanguages(source: String, target: String) {
respondWith { response("translated") }
assertEquals(listOf("translated"), service.translate(TranslateRequest(listOf("source"), source, target)).translatedText)
assertEquals(1, requests.size)
assertEquals("source", requests.single().text())
assertEquals(source, requests.single().sourceLanguageCode())
assertEquals(target, requests.single().targetLanguageCode())
}
@ParameterizedTest
@CsvSource(
"middle,exception", "middle,null-response", "middle,missing-text", "middle,empty", "middle,blank",
"later,exception", "later,null-response", "later,missing-text", "later,empty", "later,blank"
)
@DisplayName("중간 조각이나 다음 원문의 실패는 앞선 성공을 폐기하고 후속 호출을 중단한다")
fun shouldDiscardEntireRequestAndStopAfterAnyFailure(position: String, failure: String, output: CapturedOutput) {
val first = "a".repeat(10000)
val broken = "b".repeat(10000)
val failingText = if (position == "middle") broken else "sensitive-source-marker"
respondWith {
if (it.text() != failingText) {
response("partial success")
} else {
when (failure) {
"exception" -> throw SdkClientException.create("sensitive-sdk-marker")
"null-response" -> null
"missing-text" -> response(null)
"empty" -> response("")
else -> response(" \t\n")
}
}
}
val texts = if (position == "middle") {
listOf("earlier", first + broken + "c", "later")
} else {
listOf("earlier", failingText, "later")
}
assertEquals(emptyList<String>(), service.translate(TranslateRequest(texts, "ko", "en")).translatedText)
if (position == "middle") assertRequests("earlier", first, broken) else assertRequests("earlier", failingText)
assertFalse(output.all.contains("sensitive-sdk-marker"))
assertFalse(output.all.contains("sensitive-source-marker"))
}
@ParameterizedTest
@ValueSource(strings = ["", " \t\n"])
@DisplayName("빈 raw 문자열과 공백도 원문 그대로 SDK에 전달한다")
fun shouldPreserveEmptyAndWhitespaceRawText(source: String) {
respondWith { response("translated") }
assertEquals(listOf("translated"), translate(source).translatedText)
assertRequests(source)
}
@Test
@DisplayName("빈 원문 목록이면 SDK를 호출하지 않는다")
fun shouldSkipEmptyList() {
assertEquals(emptyList<String>(), translate().translatedText)
Mockito.verifyNoInteractions(client)
}
@Test
@DisplayName("같은 언어이면 SDK를 호출하지 않는다")
fun shouldSkipSameLanguage() {
assertEquals(emptyList<String>(), service.translate(TranslateRequest(listOf("source"), "ko", "ko")).translatedText)
Mockito.verifyNoInteractions(client)
}
@ParameterizedTest
@CsvSource("fr,en", "ko,fr", "auto,en")
@DisplayName("미지원 source 또는 target이면 SDK를 호출하지 않는다")
fun shouldSkipUnsupportedLanguage(source: String, target: String) {
assertEquals(emptyList<String>(), service.translate(TranslateRequest(listOf("source"), source, target)).translatedText)
Mockito.verifyNoInteractions(client)
}
private fun translate(vararg texts: String): TranslateResult {
return service.translate(TranslateRequest(texts.toList(), "ko", "en"))
}
private fun respondWith(handler: (TranslateTextRequest) -> TranslateTextResponse?) {
Mockito.`when`(client.translateText(Mockito.any(TranslateTextRequest::class.java))).thenAnswer {
val request = it.getArgument<TranslateTextRequest>(0)
requests.add(request)
handler(request)
}
}
private fun response(text: String?): TranslateTextResponse {
return TranslateTextResponse.builder().sourceLanguageCode("ko").targetLanguageCode("en").translatedText(text).build()
}
private fun assertRequests(vararg texts: String) {
assertEquals(texts.toList(), requests.map { it.text() })
requests.forEach {
assertEquals("ko", it.sourceLanguageCode())
assertEquals("en", it.targetLanguageCode())
}
Mockito.verify(client, Mockito.times(texts.size)).translateText(Mockito.any(TranslateTextRequest::class.java))
Mockito.verifyNoMoreInteractions(client)
}
}
+2 -2
View File
@@ -78,8 +78,8 @@ cloud:
aws:
credentials:
accessKey: ""
secretKey: ""
accessKey: "test-access-key"
secretKey: "test-secret-key"
s3:
contentBucket: ""
bucket: ""