diff --git a/src/main/kotlin/dev/vality/rateboss/client/nbaz/NbazApiClient.kt b/src/main/kotlin/dev/vality/rateboss/client/nbaz/NbazApiClient.kt new file mode 100644 index 0000000..b1cce66 --- /dev/null +++ b/src/main/kotlin/dev/vality/rateboss/client/nbaz/NbazApiClient.kt @@ -0,0 +1,27 @@ +package dev.vality.rateboss.client.nbaz + +import dev.vality.rateboss.config.properties.RatesProperties +import org.springframework.http.HttpEntity +import org.springframework.http.HttpMethod +import org.springframework.stereotype.Component +import org.springframework.web.client.RestTemplate +import org.springframework.web.client.exchange +import java.time.LocalDate +import java.time.format.DateTimeFormatter + +@Component +class NbazApiClient( + private val restTemplate: RestTemplate, + private val ratesProperties: RatesProperties, +) { + fun getExchangeRates(date: LocalDate): String { + val url = buildUrl(date) + return restTemplate.exchange(url, HttpMethod.GET, HttpEntity.EMPTY).body!! + } + + private fun buildUrl(date: LocalDate): String { + val rootUrl = ratesProperties.source.nbaz.rootUrl + val dateFormat = ratesProperties.source.nbaz.dateFormat + return "${rootUrl}${date.format(DateTimeFormatter.ofPattern(dateFormat))}.xml" + } +} diff --git a/src/main/kotlin/dev/vality/rateboss/config/JobConfig.kt b/src/main/kotlin/dev/vality/rateboss/config/JobConfig.kt index 50dd2b0..7eb4386 100644 --- a/src/main/kotlin/dev/vality/rateboss/config/JobConfig.kt +++ b/src/main/kotlin/dev/vality/rateboss/config/JobConfig.kt @@ -3,6 +3,7 @@ package dev.vality.rateboss.config import dev.vality.rateboss.config.properties.RatesProperties import dev.vality.rateboss.job.CbrExchangeGrabberMasterJob import dev.vality.rateboss.job.FixerExchangeGrabberMasterJob +import dev.vality.rateboss.job.NbazExchangeGrabberMasterJob import dev.vality.rateboss.job.NbkrExchangeGrabberMasterJob import dev.vality.rateboss.job.NbkzExchangeGrabberMasterJob import dev.vality.rateboss.job.NbuzExchangeGrabberMasterJob @@ -76,6 +77,16 @@ class JobConfig { schedulerFactoryBean.triggerJob(JobKey(ratesProperties.nbuzJob.jobKey)) } } + val nbazJobTriggerName = ratesProperties.nbazJob.jobTriggerName + if (nbazJobTriggerName.isNotEmpty()) { + schedulerFactoryBean.addJob(nbazExchangeRateGrabberMasterJob(), true, true) + if (!schedulerFactoryBean.checkExists(TriggerKey(ratesProperties.nbazJob.jobTriggerName))) { + schedulerFactoryBean.scheduleJob(nbazExchangeRateGrabberMasterTrigger()) + } + if (runOnStartup) { + schedulerFactoryBean.triggerJob(JobKey(ratesProperties.nbazJob.jobKey)) + } + } } fun fixerExchangeRateGrabberMasterJob(): JobDetailImpl { @@ -152,4 +163,19 @@ class JobConfig { .withIdentity(ratesProperties.nbuzJob.jobTriggerName) .withSchedule(CronScheduleBuilder.cronSchedule(ratesProperties.nbuzJob.jobCron)) .build() + + fun nbazExchangeRateGrabberMasterJob(): JobDetailImpl { + val jobDetail = JobDetailImpl() + jobDetail.key = JobKey(ratesProperties.nbazJob.jobKey) + jobDetail.jobClass = NbazExchangeGrabberMasterJob::class.java + return jobDetail + } + + fun nbazExchangeRateGrabberMasterTrigger(): CronTrigger = + TriggerBuilder + .newTrigger() + .forJob(nbazExchangeRateGrabberMasterJob()) + .withIdentity(ratesProperties.nbazJob.jobTriggerName) + .withSchedule(CronScheduleBuilder.cronSchedule(ratesProperties.nbazJob.jobCron)) + .build() } diff --git a/src/main/kotlin/dev/vality/rateboss/config/properties/RatesProperties.kt b/src/main/kotlin/dev/vality/rateboss/config/properties/RatesProperties.kt index ed93f86..90a60d8 100644 --- a/src/main/kotlin/dev/vality/rateboss/config/properties/RatesProperties.kt +++ b/src/main/kotlin/dev/vality/rateboss/config/properties/RatesProperties.kt @@ -12,6 +12,7 @@ data class RatesProperties( val nbkzJob: JobDescription, val nbkrJob: JobDescription, val nbuzJob: JobDescription, + val nbazJob: JobDescription, val source: RatesSourceProperties, ) @@ -33,6 +34,7 @@ data class RatesSourceProperties( val nbkz: NbkzProperties, val nbkr: NbkrProperties, val nbuz: NbuzProperties, + val nbaz: NbazProperties, ) data class FixerProperties( @@ -61,3 +63,9 @@ data class NbuzProperties( val locale: String, val timeZone: ZoneId, ) + +data class NbazProperties( + val rootUrl: String, + val dateFormat: String, + val timeZone: ZoneId, +) diff --git a/src/main/kotlin/dev/vality/rateboss/job/NbazExchangeGrabberJob.kt b/src/main/kotlin/dev/vality/rateboss/job/NbazExchangeGrabberJob.kt new file mode 100644 index 0000000..e59d9c6 --- /dev/null +++ b/src/main/kotlin/dev/vality/rateboss/job/NbazExchangeGrabberJob.kt @@ -0,0 +1,37 @@ +package dev.vality.rateboss.job + +import dev.vality.rateboss.extensions.getApplicationContext +import dev.vality.rateboss.source.ExchangeRateSource +import dev.vality.rateboss.source.ExchangeRateSourceException +import dev.vality.rateboss.source.impl.NbazExchangeRateSource +import dev.vality.rateboss.source.model.ExchangeRates +import mu.KotlinLogging +import org.quartz.JobExecutionContext +import org.springframework.context.ApplicationContext +import org.springframework.retry.support.RetryTemplate + +private val log = KotlinLogging.logger {} + +class NbazExchangeGrabberJob : AbstractExchangeGrabberJob() { + override fun executeInternal(context: JobExecutionContext) { + val applicationContext = context.getApplicationContext() + val currencySymbolCode = context.jobDetail.jobDataMap["currencySymbolCode"] as String + val currencyExponent = context.jobDetail.jobDataMap["currencyExponent"] as Int + val exchangeRateSource = applicationContext.getBean(NbazExchangeRateSource::class.java) + val sourceId = exchangeRateSource.getSourceId() + log.info { "Process NbazExchangeGrabberJob for $sourceId" } + val exchangeRates = getExchangeRates(applicationContext, exchangeRateSource, currencySymbolCode) + saveExchangeRates(applicationContext, currencySymbolCode, currencyExponent, exchangeRates, sourceId) + } + + private fun getExchangeRates( + applicationContext: ApplicationContext, + exchangeRateSource: ExchangeRateSource, + currencySymbolCode: String, + ): ExchangeRates { + val retryTemplate = applicationContext.getBean(RetryTemplate::class.java) + return retryTemplate.execute { + exchangeRateSource.getExchangeRate(currencySymbolCode) + } + } +} diff --git a/src/main/kotlin/dev/vality/rateboss/job/NbazExchangeGrabberMasterJob.kt b/src/main/kotlin/dev/vality/rateboss/job/NbazExchangeGrabberMasterJob.kt new file mode 100644 index 0000000..ffec72a --- /dev/null +++ b/src/main/kotlin/dev/vality/rateboss/job/NbazExchangeGrabberMasterJob.kt @@ -0,0 +1,18 @@ +package dev.vality.rateboss.job + +import dev.vality.rateboss.config.properties.RatesProperties +import dev.vality.rateboss.extensions.getApplicationContext +import org.quartz.JobExecutionContext +import org.quartz.Scheduler + +class NbazExchangeGrabberMasterJob : AbstractExchangeGrabberMasterJob() { + override fun executeInternal(context: JobExecutionContext) { + val applicationContext = context.getApplicationContext() + val ratesProperties = applicationContext.getBean(RatesProperties::class.java) + val currencies = ratesProperties.nbazJob.currencies + val schedulerFactoryBean = applicationContext.getBean(Scheduler::class.java) + launchJob(currencies, schedulerFactoryBean, NbazExchangeGrabberJob::class.java, getJobName()) + } + + override fun getJobName(): String = "nbazJob" +} diff --git a/src/main/kotlin/dev/vality/rateboss/job/constant/ExRateSources.kt b/src/main/kotlin/dev/vality/rateboss/job/constant/ExRateSources.kt index 90f0216..0874393 100644 --- a/src/main/kotlin/dev/vality/rateboss/job/constant/ExRateSources.kt +++ b/src/main/kotlin/dev/vality/rateboss/job/constant/ExRateSources.kt @@ -6,4 +6,5 @@ object ExRateSources { const val NBKZ = "nbkz" const val NBKR = "nbkr" const val NBUZ = "nbuz" + const val NBAZ = "nbaz" } diff --git a/src/main/kotlin/dev/vality/rateboss/model/NbazDailyRatesXml.kt b/src/main/kotlin/dev/vality/rateboss/model/NbazDailyRatesXml.kt new file mode 100644 index 0000000..4c20f57 --- /dev/null +++ b/src/main/kotlin/dev/vality/rateboss/model/NbazDailyRatesXml.kt @@ -0,0 +1,35 @@ +package dev.vality.rateboss.model + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement + +@JacksonXmlRootElement(localName = "ValCurs") +@JsonIgnoreProperties(ignoreUnknown = true) +data class NbazDailyRatesXml( + @field:JacksonXmlProperty(isAttribute = true, localName = "Date") + val date: String? = null, + @field:JacksonXmlElementWrapper(useWrapping = false) + @field:JacksonXmlProperty(localName = "ValType") + val valTypes: List? = null, +) + +@JsonIgnoreProperties(ignoreUnknown = true) +data class NbazValTypeXml( + @field:JacksonXmlProperty(isAttribute = true, localName = "Type") + val type: String? = null, + @field:JacksonXmlElementWrapper(useWrapping = false) + @field:JacksonXmlProperty(localName = "Valute") + val valutes: List? = null, +) + +@JsonIgnoreProperties(ignoreUnknown = true) +data class NbazValuteXml( + @field:JacksonXmlProperty(isAttribute = true, localName = "Code") + val code: String? = null, + @field:JacksonXmlProperty(localName = "Nominal") + val nominal: String? = null, + @field:JacksonXmlProperty(localName = "Value") + val value: String? = null, +) diff --git a/src/main/kotlin/dev/vality/rateboss/source/impl/NbazExchangeRateSource.kt b/src/main/kotlin/dev/vality/rateboss/source/impl/NbazExchangeRateSource.kt new file mode 100644 index 0000000..7ce0c7d --- /dev/null +++ b/src/main/kotlin/dev/vality/rateboss/source/impl/NbazExchangeRateSource.kt @@ -0,0 +1,85 @@ +package dev.vality.rateboss.source.impl + +import com.fasterxml.jackson.dataformat.xml.XmlMapper +import dev.vality.rateboss.client.nbaz.NbazApiClient +import dev.vality.rateboss.config.properties.RatesProperties +import dev.vality.rateboss.job.constant.ExRateSources +import dev.vality.rateboss.model.NbazDailyRatesXml +import dev.vality.rateboss.source.ExchangeRateSource +import dev.vality.rateboss.source.ExchangeRateSourceException +import dev.vality.rateboss.source.model.ExchangeRates +import mu.KotlinLogging +import org.springframework.stereotype.Component +import java.math.BigDecimal +import java.time.LocalDate +import java.time.ZoneOffset +import java.time.format.DateTimeFormatter + +private val log = KotlinLogging.logger {} + +@Component +class NbazExchangeRateSource( + private val nbazApiClient: NbazApiClient, + private val ratesProperties: RatesProperties, + private val xmlMapper: XmlMapper, +) : ExchangeRateSource { + override fun getExchangeRate(currencySymbolCode: String): ExchangeRates { + val timeZone = ratesProperties.source.nbaz.timeZone + val date = LocalDate.now(timeZone) + log.info { "Trying to get exchange rates from nbaz for currency=$currencySymbolCode, date=$date" } + val parsed = fetchDailyRates(date) + val rates = buildRatesMap(parsed) + if (rates.isEmpty()) { + throw ExchangeRateSourceException("Unsuccessful response from NbazApi") + } + val dayTimestamp = date.atStartOfDay().toEpochSecond(ZoneOffset.UTC) + log.info { + "Exchange rates from nbaz have been retrieved, date=$date, " + + "exchangeRates=$rates, targetTimestamp=$dayTimestamp" + } + return ExchangeRates( + rates = rates, + timestamp = dayTimestamp, + ) + } + + override fun getSourceId(): String = ExRateSources.NBAZ + + private fun fetchDailyRates(date: LocalDate): NbazDailyRatesXml = + try { + xmlMapper.readValue(nbazApiClient.getExchangeRates(date), NbazDailyRatesXml::class.java) + } catch (e: Exception) { + throw ExchangeRateSourceException("Failed to get daily rates", e) + } + + private fun buildRatesMap(root: NbazDailyRatesXml): Map { + val rates = mutableMapOf() + val currencies = + root.valTypes + .orEmpty() + .firstOrNull { it.type?.trim { ch -> ch <= ' ' } == FOREIGN_CURRENCIES_TYPE } + ?.valutes + .orEmpty() + for (item in currencies) { + val code = item.code?.trim { it <= ' ' }.orEmpty() + val nominal = item.nominal?.extractDecimal()?.toBigDecimalOrNull() + val value = item.value?.extractDecimal()?.toBigDecimalOrNull() + if (code.isBlank() || nominal == null || value == null || nominal.compareTo(BigDecimal.ZERO) == 0) { + log.debug { "Skip malformed NBAZ currency record: $item" } + continue + } + rates[code] = value.divide(nominal) + } + return rates + } + + private fun String.extractDecimal(): String = + trim() + .replace(",", ".") + .substringBefore(' ') + + companion object { + private const val FOREIGN_CURRENCIES_TYPE = "Xarici valyutalar" + private val DATE_FORMATTER: DateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy") + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 9aabf65..51c5f07 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -111,6 +111,13 @@ rates: currencies: - symbolCode: "UZS" exponent: 2 + nbazJob: + jobCron: '0 0 0/1 * * ?' + jobKey: 'nbaz-exchange-rate-grabber-master-job' + jobTriggerName: 'nbaz-exchange-rate-grabber-master-job-trigger' + currencies: + - symbolCode: "AZN" + exponent: 2 source: fixer: rootUrl: https://api.apilayer.com/fixer/ @@ -129,3 +136,7 @@ rates: rootUrl: https://nbu.uz/api/collections/individuals_exchange_rates/entries locale: ru timeZone: Asia/Tashkent + nbaz: + rootUrl: https://www.cbar.az/currencies/ + dateFormat: dd.MM.yyyy + timeZone: Asia/Baku diff --git a/src/test/kotlin/dev/vality/rateboss/client/nbaz/NbazApiClientTest.kt b/src/test/kotlin/dev/vality/rateboss/client/nbaz/NbazApiClientTest.kt new file mode 100644 index 0000000..10e78dc --- /dev/null +++ b/src/test/kotlin/dev/vality/rateboss/client/nbaz/NbazApiClientTest.kt @@ -0,0 +1,43 @@ +package dev.vality.rateboss.client.nbaz + +import dev.vality.rateboss.config.TestConfig +import dev.vality.rateboss.source.ExchangeRateSource +import dev.vality.rateboss.source.impl.NbazExchangeRateSource +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.context.annotation.Import +import org.springframework.test.context.ContextConfiguration +import org.springframework.test.context.junit.jupiter.SpringExtension +import java.time.LocalDate + +@Disabled("integration test") +@ExtendWith(SpringExtension::class) +@ContextConfiguration(classes = [NbazApiClient::class, NbazExchangeRateSource::class]) +@Import(TestConfig::class) +class NbazApiClientTest { + @Autowired + lateinit var nbazApiClient: NbazApiClient + + @Autowired + lateinit var nbazExchangeRateSource: ExchangeRateSource + + @Test + fun getExchangeRates() { + val response = nbazApiClient.getExchangeRates(LocalDate.now()) + + assertNotNull(response) + assertTrue(response.isNotBlank()) + } + + @Test + fun getExchangeRatesViaSource() { + val exchangeRates = nbazExchangeRateSource.getExchangeRate("AZN") + + assertNotNull(exchangeRates) + assertTrue(exchangeRates.rates.isNotEmpty()) + } +} diff --git a/src/test/kotlin/dev/vality/rateboss/config/TestConfig.kt b/src/test/kotlin/dev/vality/rateboss/config/TestConfig.kt index e375a41..ff7490b 100644 --- a/src/test/kotlin/dev/vality/rateboss/config/TestConfig.kt +++ b/src/test/kotlin/dev/vality/rateboss/config/TestConfig.kt @@ -59,6 +59,12 @@ class TestConfig { "nbuz-name", listOf(CurrencyProperties("UZS", 2)), ), + JobDescription( + "nbaz-cron", + "nbaz-key", + "nbaz-name", + listOf(CurrencyProperties("AZN", 2)), + ), RatesSourceProperties( FixerProperties("url", "key"), CbrProperties("https://www.cbr.ru/scripts/XML_daily.asp", ZoneId.of("Europe/Moscow")), @@ -69,6 +75,11 @@ class TestConfig { "ru", ZoneId.of("Asia/Tashkent"), ), + NbazProperties( + "https://www.cbar.az/currencies/", + "dd.MM.yyyy", + ZoneId.of("Asia/Baku"), + ), ), ) } diff --git a/src/test/kotlin/dev/vality/rateboss/job/CbrExchangeGrabberJobTest.kt b/src/test/kotlin/dev/vality/rateboss/job/CbrExchangeGrabberJobTest.kt index d6ea7fd..11e9e97 100644 --- a/src/test/kotlin/dev/vality/rateboss/job/CbrExchangeGrabberJobTest.kt +++ b/src/test/kotlin/dev/vality/rateboss/job/CbrExchangeGrabberJobTest.kt @@ -51,6 +51,7 @@ class CbrExchangeGrabberJobTest : ContainerConfiguration() { scheduler.unscheduleJob(TriggerKey(ratesProperties.nbkzJob.jobTriggerName)) scheduler.unscheduleJob(TriggerKey(ratesProperties.nbkrJob.jobTriggerName)) scheduler.unscheduleJob(TriggerKey(ratesProperties.nbuzJob.jobTriggerName)) + scheduler.unscheduleJob(TriggerKey(ratesProperties.nbazJob.jobTriggerName)) } @Test diff --git a/src/test/kotlin/dev/vality/rateboss/job/FixerExchangeGrabberJobTest.kt b/src/test/kotlin/dev/vality/rateboss/job/FixerExchangeGrabberJobTest.kt index 493e89f..826113d 100644 --- a/src/test/kotlin/dev/vality/rateboss/job/FixerExchangeGrabberJobTest.kt +++ b/src/test/kotlin/dev/vality/rateboss/job/FixerExchangeGrabberJobTest.kt @@ -62,6 +62,7 @@ class FixerExchangeGrabberJobTest : ContainerConfiguration() { scheduler.unscheduleJob(TriggerKey(ratesProperties.nbkzJob.jobTriggerName)) scheduler.unscheduleJob(TriggerKey(ratesProperties.nbkrJob.jobTriggerName)) scheduler.unscheduleJob(TriggerKey(ratesProperties.nbuzJob.jobTriggerName)) + scheduler.unscheduleJob(TriggerKey(ratesProperties.nbazJob.jobTriggerName)) } @Test diff --git a/src/test/kotlin/dev/vality/rateboss/job/NbazExchangeGrabberJobTest.kt b/src/test/kotlin/dev/vality/rateboss/job/NbazExchangeGrabberJobTest.kt new file mode 100644 index 0000000..79a61e3 --- /dev/null +++ b/src/test/kotlin/dev/vality/rateboss/job/NbazExchangeGrabberJobTest.kt @@ -0,0 +1,75 @@ +package dev.vality.rateboss.job + +import dev.vality.rateboss.ContainerConfiguration +import dev.vality.rateboss.config.properties.RatesProperties +import dev.vality.rateboss.service.ExchangeDaoService +import dev.vality.rateboss.source.impl.NbazExchangeRateSource +import dev.vality.rateboss.source.model.ExchangeRates +import org.awaitility.Awaitility.await +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.atLeastOnce +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import org.quartz.JobKey +import org.quartz.Scheduler +import org.quartz.TriggerKey +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.bean.override.mockito.MockitoBean +import org.springframework.test.context.bean.override.mockito.MockitoSpyBean +import java.math.BigDecimal +import java.time.Instant +import java.util.concurrent.TimeUnit + +@SpringBootTest( + properties = [ + "rates.nbaz-job.jobCron=0/5 * * * * ?", + "rates.nbaz-job.currencies.[0].symbolCode=AZN", + "rates.nbaz-job.currencies.[0].exponent=2", + ], +) +class NbazExchangeGrabberJobTest : ContainerConfiguration() { + @MockitoSpyBean + lateinit var exchangeDaoService: ExchangeDaoService + + @MockitoBean + lateinit var nbazExchangeRateSource: NbazExchangeRateSource + + @Autowired + lateinit var scheduler: Scheduler + + @Autowired + @Qualifier("rates-dev.vality.rateboss.config.properties.RatesProperties") + lateinit var ratesProperties: RatesProperties + + @BeforeEach + fun setUp() { + scheduler.unscheduleJob(TriggerKey(ratesProperties.fixerJob.jobTriggerName)) + scheduler.unscheduleJob(TriggerKey(ratesProperties.cbrJob.jobTriggerName)) + scheduler.unscheduleJob(TriggerKey(ratesProperties.nbkzJob.jobTriggerName)) + scheduler.unscheduleJob(TriggerKey(ratesProperties.nbkrJob.jobTriggerName)) + scheduler.unscheduleJob(TriggerKey(ratesProperties.nbuzJob.jobTriggerName)) + } + + @Test + fun `test grabber job`() { + whenever(nbazExchangeRateSource.getSourceId()).thenReturn("sourceId") + whenever(nbazExchangeRateSource.getExchangeRate(any())).then { + ExchangeRates( + rates = + mapOf( + "USD" to BigDecimal.valueOf(1.7), + "EUR" to BigDecimal.valueOf(1.977), + ), + timestamp = Instant.now().epochSecond, + ) + } + scheduler.triggerJob(JobKey(ratesProperties.nbazJob.jobKey)) + await().atMost(30, TimeUnit.SECONDS).untilAsserted { + verify(exchangeDaoService, atLeastOnce()).saveExchangeRates(any()) + } + } +} diff --git a/src/test/kotlin/dev/vality/rateboss/job/NbkrExchangeGrabberJobTest.kt b/src/test/kotlin/dev/vality/rateboss/job/NbkrExchangeGrabberJobTest.kt index a9d4cb9..4e22fd2 100644 --- a/src/test/kotlin/dev/vality/rateboss/job/NbkrExchangeGrabberJobTest.kt +++ b/src/test/kotlin/dev/vality/rateboss/job/NbkrExchangeGrabberJobTest.kt @@ -51,6 +51,7 @@ class NbkrExchangeGrabberJobTest : ContainerConfiguration() { scheduler.unscheduleJob(TriggerKey(ratesProperties.cbrJob.jobTriggerName)) scheduler.unscheduleJob(TriggerKey(ratesProperties.nbkzJob.jobTriggerName)) scheduler.unscheduleJob(TriggerKey(ratesProperties.nbuzJob.jobTriggerName)) + scheduler.unscheduleJob(TriggerKey(ratesProperties.nbazJob.jobTriggerName)) } @Test diff --git a/src/test/kotlin/dev/vality/rateboss/job/NbkzExchangeGrabberJobTest.kt b/src/test/kotlin/dev/vality/rateboss/job/NbkzExchangeGrabberJobTest.kt index 26ce530..36dd2a9 100644 --- a/src/test/kotlin/dev/vality/rateboss/job/NbkzExchangeGrabberJobTest.kt +++ b/src/test/kotlin/dev/vality/rateboss/job/NbkzExchangeGrabberJobTest.kt @@ -51,6 +51,7 @@ class NbkzExchangeGrabberJobTest : ContainerConfiguration() { scheduler.unscheduleJob(TriggerKey(ratesProperties.cbrJob.jobTriggerName)) scheduler.unscheduleJob(TriggerKey(ratesProperties.nbkrJob.jobTriggerName)) scheduler.unscheduleJob(TriggerKey(ratesProperties.nbuzJob.jobTriggerName)) + scheduler.unscheduleJob(TriggerKey(ratesProperties.nbazJob.jobTriggerName)) } @Test diff --git a/src/test/kotlin/dev/vality/rateboss/job/NbuzExchangeGrabberJobTest.kt b/src/test/kotlin/dev/vality/rateboss/job/NbuzExchangeGrabberJobTest.kt index f21f302..4d48e92 100644 --- a/src/test/kotlin/dev/vality/rateboss/job/NbuzExchangeGrabberJobTest.kt +++ b/src/test/kotlin/dev/vality/rateboss/job/NbuzExchangeGrabberJobTest.kt @@ -51,6 +51,7 @@ class NbuzExchangeGrabberJobTest : ContainerConfiguration() { scheduler.unscheduleJob(TriggerKey(ratesProperties.cbrJob.jobTriggerName)) scheduler.unscheduleJob(TriggerKey(ratesProperties.nbkzJob.jobTriggerName)) scheduler.unscheduleJob(TriggerKey(ratesProperties.nbkrJob.jobTriggerName)) + scheduler.unscheduleJob(TriggerKey(ratesProperties.nbazJob.jobTriggerName)) } @Test diff --git a/src/test/kotlin/dev/vality/rateboss/source/NbazExchangeRateSourceTest.kt b/src/test/kotlin/dev/vality/rateboss/source/NbazExchangeRateSourceTest.kt new file mode 100644 index 0000000..e8de780 --- /dev/null +++ b/src/test/kotlin/dev/vality/rateboss/source/NbazExchangeRateSourceTest.kt @@ -0,0 +1,123 @@ +package dev.vality.rateboss.source + +import dev.vality.rateboss.client.nbaz.NbazApiClient +import dev.vality.rateboss.config.TestConfig +import dev.vality.rateboss.source.impl.NbazExchangeRateSource +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.kotlin.any +import org.mockito.kotlin.whenever +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.context.annotation.Import +import org.springframework.test.context.ContextConfiguration +import org.springframework.test.context.bean.override.mockito.MockitoBean +import org.springframework.test.context.junit.jupiter.SpringExtension +import org.springframework.web.client.ResourceAccessException +import java.math.BigDecimal + +@ExtendWith(SpringExtension::class) +@ContextConfiguration(classes = [NbazApiClient::class, NbazExchangeRateSource::class]) +@Import(TestConfig::class) +class NbazExchangeRateSourceTest { + @Autowired + lateinit var exchangeRateSource: ExchangeRateSource + + @MockitoBean + lateinit var nbazApiClient: NbazApiClient + + @Test + fun getFailedExchangeRate() { + val currencySymbolCode = "AZN" + whenever(nbazApiClient.getExchangeRates(any())).thenThrow(ResourceAccessException("Error")) + + val exception = + org.junit.jupiter.api.assertThrows { + exchangeRateSource.getExchangeRate(currencySymbolCode) + } + + assertEquals("Failed to get daily rates", exception.message) + } + + @Test + fun getEmptyExchangeRate() { + val currencySymbolCode = "AZN" + whenever(nbazApiClient.getExchangeRates(any())).thenReturn( + """ + + + + """.trimIndent(), + ) + + val exception = + org.junit.jupiter.api.assertThrows { + exchangeRateSource.getExchangeRate(currencySymbolCode) + } + + assertEquals("Unsuccessful response from NbazApi", exception.message) + } + + @Test + fun getSuccessExchangeRate() { + val currencySymbolCode = "AZN" + whenever(nbazApiClient.getExchangeRates(any())).thenReturn( + """ + + + + 1 t.u. + 7693.044 + + + + + 1 + 1.7 + + + 100 + 0.1128 + + + + """.trimIndent(), + ) + + val exchangeRate = exchangeRateSource.getExchangeRate(currencySymbolCode) + + assertNotNull(exchangeRate) + assertTrue(exchangeRate.rates.isNotEmpty()) + assertEquals(BigDecimal("1.7"), exchangeRate.rates["USD"]) + assertEquals(BigDecimal("0.001128"), exchangeRate.rates["KRW"]) + assertTrue(!exchangeRate.rates.containsKey("XAU")) + } + + @Test + fun getSuccessExchangeRateWithMalformedCurrencyRecord() { + val currencySymbolCode = "AZN" + whenever(nbazApiClient.getExchangeRates(any())).thenReturn( + """ + + + + 1 + 1.7 + + + 0 + 1.0 + + + + """.trimIndent(), + ) + + val exchangeRate = exchangeRateSource.getExchangeRate(currencySymbolCode) + + assertEquals(BigDecimal("1.7"), exchangeRate.rates["USD"]) + assertTrue(!exchangeRate.rates.containsKey("BROKEN")) + } +}