diff --git a/app/uk/gov/hmrc/currencyconversion/controllers/ExchangeRateController.scala b/app/uk/gov/hmrc/currencyconversion/controllers/ExchangeRateController.scala
index 48f1598..37a418a 100644
--- a/app/uk/gov/hmrc/currencyconversion/controllers/ExchangeRateController.scala
+++ b/app/uk/gov/hmrc/currencyconversion/controllers/ExchangeRateController.scala
@@ -50,4 +50,11 @@ class ExchangeRateController @Inject() (
Future.successful(Ok(Json.toJson(rates)))
}
}
-}
\ No newline at end of file
+
+ def getCurrenciesByDate(date: LocalDate): Action[AnyContent] = Action.async { implicit request =>
+ exchangeRatesService.getCurrencies(date) match {
+ case Some(cp) => Future.successful(Ok(Json.toJson(cp)))
+ case None => Future.successful(NotFound)
+ }
+ }
+}
diff --git a/app/uk/gov/hmrc/currencyconversion/models/Currency.scala b/app/uk/gov/hmrc/currencyconversion/models/Currency.scala
new file mode 100644
index 0000000..7df8668
--- /dev/null
+++ b/app/uk/gov/hmrc/currencyconversion/models/Currency.scala
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2020 HM Revenue & Customs
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package uk.gov.hmrc.currencyconversion.models
+
+import java.time.LocalDate
+
+import play.api.libs.json.{Json, OFormat}
+
+case class Currency(countryName: String, currencyName: String, currencyCode: String)
+
+object Currency {
+ implicit val format: OFormat[Currency] = Json.format[Currency]
+}
+
+case class CurrencyPeriod(start: LocalDate, end: LocalDate, currencies: Seq[Currency])
+
+object CurrencyPeriod {
+ implicit val format: OFormat[CurrencyPeriod] = Json.format[CurrencyPeriod]
+}
diff --git a/app/uk/gov/hmrc/currencyconversion/repositories/ConversionRatePeriodRepository.scala b/app/uk/gov/hmrc/currencyconversion/repositories/ConversionRatePeriodRepository.scala
index 3b38366..5723371 100644
--- a/app/uk/gov/hmrc/currencyconversion/repositories/ConversionRatePeriodRepository.scala
+++ b/app/uk/gov/hmrc/currencyconversion/repositories/ConversionRatePeriodRepository.scala
@@ -19,33 +19,32 @@ package uk.gov.hmrc.currencyconversion.repositories
import java.io.InputStream
import java.time.LocalDate
-import uk.gov.hmrc.currencyconversion.models.{ConversionRatePeriod, ExchangeRateOldFileResult, ExchangeRateResult, ExchangeRateSuccessResult}
+import uk.gov.hmrc.currencyconversion.models.{ConversionRatePeriod, CurrencyPeriod}
+import uk.gov.hmrc.currencyconversion.utils.{CurrencyParsing, ExchangeRateParsing}
-import uk.gov.hmrc.currencyconversion.utils.ExchangeRateParsing
-
-import scala.xml.{Elem, XML}
+import scala.xml.XML
class ConversionRatePeriodRepository {
-
- lazy val conversionRatePeriods: Seq[ConversionRatePeriod] = {
- def xmlStreams(month: Int = 9, year: Int = 19): Stream[InputStream] = {
- val nextMonth = if (month == 12) 1 else month + 1
- val nextYear = if (month == 12) year + 1 else year
+ private def xmlStreams(month: Int = 9, year: Int = 19): Stream[InputStream] = {
+ val nextMonth = if (month == 12) 1 else month + 1
+ val nextYear = if (month == 12) year + 1 else year
- val file = "/resources/xml/exrates-monthly-" + "%02d".format(month) + year + ".xml"
+ val file = "/resources/xml/exrates-monthly-" + "%02d".format(month) + year + ".xml"
- val inputStream: Option[InputStream] = Option(getClass.getResourceAsStream(file))
+ val inputStream: Option[InputStream] = Option(getClass.getResourceAsStream(file))
- inputStream match {
- case Some(resource) => resource #:: xmlStreams(nextMonth, nextYear)
- case None => Stream.empty
- }
+ inputStream match {
+ case Some(resource) => resource #:: xmlStreams(nextMonth, nextYear)
+ case None => Stream.empty
}
+ }
+ lazy val conversionRatePeriods: Seq[ConversionRatePeriod] =
xmlStreams().map(XML.load).flatMap(ExchangeRateParsing.ratesFromXml).reverse
- }
+ lazy val currencyPeriods: Seq[CurrencyPeriod] =
+ xmlStreams().map(XML.load).flatMap(CurrencyParsing.currenciesFromXml).reverse
def getConversionRatePeriod(date: LocalDate): Option[ConversionRatePeriod] =
conversionRatePeriods.find(crp => (crp.startDate.isBefore(date) || crp.startDate.isEqual(date)) && (crp.endDate.isAfter(date) || crp.endDate.isEqual(date)))
@@ -53,4 +52,8 @@ class ConversionRatePeriodRepository {
def getLatestConversionRatePeriod: ConversionRatePeriod =
conversionRatePeriods.head
+ def getCurrencyPeriod(date: LocalDate): Option[CurrencyPeriod] =
+ currencyPeriods.find(cp =>
+ (cp.start.isBefore(date) || cp.start.isEqual(date)) && (cp.end.isAfter(date) || cp.end.isEqual(date)))
+
}
diff --git a/app/uk/gov/hmrc/currencyconversion/services/ExchangeRateService.scala b/app/uk/gov/hmrc/currencyconversion/services/ExchangeRateService.scala
index d1d2531..2e3d069 100644
--- a/app/uk/gov/hmrc/currencyconversion/services/ExchangeRateService.scala
+++ b/app/uk/gov/hmrc/currencyconversion/services/ExchangeRateService.scala
@@ -46,4 +46,7 @@ class ExchangeRateService @Inject()(exchangeRateRepository: ConversionRatePeriod
}
}
}
+
+ def getCurrencies(date: LocalDate): Option[CurrencyPeriod] =
+ exchangeRateRepository.getCurrencyPeriod(date)
}
diff --git a/app/uk/gov/hmrc/currencyconversion/utils/CurrencyParsing.scala b/app/uk/gov/hmrc/currencyconversion/utils/CurrencyParsing.scala
new file mode 100644
index 0000000..26a9790
--- /dev/null
+++ b/app/uk/gov/hmrc/currencyconversion/utils/CurrencyParsing.scala
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2020 HM Revenue & Customs
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package uk.gov.hmrc.currencyconversion.utils
+
+import java.time.LocalDate
+import java.time.format.DateTimeFormatter
+
+import play.api.Logger
+import uk.gov.hmrc.currencyconversion.models.{Currency, CurrencyPeriod}
+
+import scala.xml.Elem
+
+object CurrencyParsing {
+
+ private val formatter = DateTimeFormatter.ofPattern("dd/MMM/yyyy")
+
+ def currenciesFromXml(exchangeRatesRoot: Elem): Option[CurrencyPeriod] = {
+ val periodAttr = (exchangeRatesRoot \ "@Period").text
+
+ val currencies = (exchangeRatesRoot \ "_").map { node =>
+ val countryName: String = (node \ "countryName").text
+ val currencyName: String = (node \ "currencyName").text
+ val currencyCode: String = (node \ "currencyCode").text
+
+ Currency(countryName, currencyName, currencyCode)
+ }
+
+ def parseDates(dateRange: String): Option[(LocalDate, LocalDate)] = dateRange.split("to").map(_.trim) match {
+ case Array(start, end) => Some((LocalDate.parse(start, formatter), LocalDate.parse(end, formatter)))
+ case _ => None
+ }
+
+ val parsedDates = parseDates(periodAttr)
+
+ parsedDates match {
+ case Some((s, e)) => Some(CurrencyPeriod(s, e, currencies))
+ case None =>
+ Logger.warn("Unable to parse dates from xml Element")
+ None
+ }
+ }
+
+ def isValidXmlElem(elem: Elem): Boolean = {
+ val validatePeriodAttribute = {
+ val periodRegex = "\\d{2}\\/[a-zA-Z]{3}\\/\\d{4}\\s+to\\s+\\d{2}\\/[a-zA-Z]{3}\\/\\d{4}"
+ val periodAttr = (elem \ "@Period").toList.map(_.text)
+ periodAttr.forall(p => p.matches(periodRegex))
+ }
+
+ val validateEssentialElems = {
+ // get all the nodes and node labels that actually have values
+ val exchangeRateNodes = elem.child.toList.filterNot(_.isAtom)
+ val exchangeRateLabels = exchangeRateNodes.map(exchangeRate => exchangeRate.child.filterNot(_.isAtom).map(childNode => childNode.label).toList)
+
+ val hasCorrectNodes = exchangeRateLabels.exists(x => x.contains("countryName")
+ && x.contains("currencyName")
+ && x.contains("currencyCode"))
+
+ val nodesHaveContent = exchangeRateNodes.forall(x => (x \ "countryName").text.nonEmpty
+ && (x \ "currencyName").text.nonEmpty
+ && (x \ "currencyCode").text.nonEmpty)
+
+ hasCorrectNodes && nodesHaveContent
+ }
+
+ validatePeriodAttribute && validateEssentialElems
+ }
+}
diff --git a/conf/app.routes b/conf/app.routes
index 9543b4c..11aa89d 100644
--- a/conf/app.routes
+++ b/conf/app.routes
@@ -1,3 +1,4 @@
# microservice specific routes
GET /rates/:date uk.gov.hmrc.currencyconversion.controllers.ExchangeRateController.getRatesByCurrencyCode(cc: List[String], date: LocalDate)
+GET /currencies/:date uk.gov.hmrc.currencyconversion.controllers.ExchangeRateController.getCurrenciesByDate(date: LocalDate)
diff --git a/test/uk/gov/hmrc/currencyconversion/controllers/ExchangeRateControllerSpec.scala b/test/uk/gov/hmrc/currencyconversion/controllers/ExchangeRateControllerSpec.scala
index bdb40be..bb1a18f 100644
--- a/test/uk/gov/hmrc/currencyconversion/controllers/ExchangeRateControllerSpec.scala
+++ b/test/uk/gov/hmrc/currencyconversion/controllers/ExchangeRateControllerSpec.scala
@@ -16,20 +16,16 @@
package uk.gov.hmrc.currencyconversion.controllers
-import java.time.ZonedDateTime
-import java.time.format.DateTimeFormatter
-
-import org.mockito.Mockito
-import org.scalatest.mockito.MockitoSugar
+import org.scalatestplus.mockito.MockitoSugar
import org.scalatestplus.play.guice.GuiceOneAppPerSuite
import play.api.http.Status
+import play.api.inject.bind
import play.api.inject.guice.GuiceApplicationBuilder
import play.api.libs.json.{JsArray, JsObject, Json}
import play.api.test.FakeRequest
import play.api.test.Helpers._
-import uk.gov.hmrc.play.test.UnitSpec
-import play.api.inject.bind
import uk.gov.hmrc.play.audit.http.connector.AuditConnector
+import uk.gov.hmrc.play.test.UnitSpec
class ExchangeRateControllerSpec extends UnitSpec with GuiceOneAppPerSuite {
@@ -158,4 +154,36 @@ class ExchangeRateControllerSpec extends UnitSpec with GuiceOneAppPerSuite {
}
}
+
+ "Getting currencies for a valid date" should {
+
+ "return 200 and the correct json" in {
+
+ val result = route(app, FakeRequest("GET", "/currency-conversion/currencies/2019-09-01")).get
+
+ status(result) shouldBe Status.OK
+
+ contentAsJson(result).as[JsObject].keys shouldBe Set("start", "end", "currencies")
+ }
+ }
+
+ "Getting currencies for an invalid date" should {
+
+ "return 400" in {
+
+ val result = route(app, FakeRequest("GET", "/currency-conversion/currencies/INVALID-DATE")).get
+
+ status(result) shouldBe Status.BAD_REQUEST
+ }
+ }
+
+ "Getting currencies for a date which does not exist" should {
+
+ "return 404" in {
+
+ val result = route(app, FakeRequest("GET", "/currency-conversion/currencies/2019-01-01")).get
+
+ status(result) shouldBe Status.NOT_FOUND
+ }
+ }
}
diff --git a/test/uk/gov/hmrc/currencyconversion/repositories/ExchangeRateRepositorySpec.scala b/test/uk/gov/hmrc/currencyconversion/repositories/ExchangeRateRepositorySpec.scala
index 688e3db..eee025e 100644
--- a/test/uk/gov/hmrc/currencyconversion/repositories/ExchangeRateRepositorySpec.scala
+++ b/test/uk/gov/hmrc/currencyconversion/repositories/ExchangeRateRepositorySpec.scala
@@ -17,7 +17,6 @@
package uk.gov.hmrc.currencyconversion.repositories
import java.io.File
-import java.time.LocalDate
import org.scalatest.Inspectors._
import org.scalatest.{Matchers, WordSpec}
diff --git a/test/uk/gov/hmrc/currencyconversion/utils/CurrencyParsingSpec.scala b/test/uk/gov/hmrc/currencyconversion/utils/CurrencyParsingSpec.scala
new file mode 100644
index 0000000..f9db0b0
--- /dev/null
+++ b/test/uk/gov/hmrc/currencyconversion/utils/CurrencyParsingSpec.scala
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2020 HM Revenue & Customs
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package uk.gov.hmrc.currencyconversion.utils
+
+import java.time.LocalDate
+
+import org.scalatest.{Matchers, WordSpec}
+import uk.gov.hmrc.currencyconversion.models.Currency
+
+class CurrencyParsingSpec extends WordSpec with Matchers {
+
+ "currenciesFromXml" should {
+
+ "parse xml representing currencies" in {
+
+ val xml =
+
+
+ Argentina
+ AR
+ Peso
+ ARS
+ 28.67
+
+
+ Australia
+ AU
+ Dollar
+ AUD
+ 1.782
+
+
+
+ val optionOfSample = CurrencyParsing.currenciesFromXml(xml)
+
+ val resultOfSample = optionOfSample match {
+ case Some(x) => x
+ case None => fail("Result returned as None")
+ }
+
+ resultOfSample.start shouldBe LocalDate.parse("2018-05-01")
+ resultOfSample.end shouldBe LocalDate.parse("2018-05-31")
+ resultOfSample.currencies shouldBe Seq(Currency("Argentina", "Peso", "ARS"), Currency("Australia", "Dollar", "AUD"))
+ }
+
+ "isValidXml" should {
+
+ "return true for valid xml" in {
+
+ val xml =
+
+
+ Argentina
+ AR
+ Peso
+ ARS
+ 28.5
+
+
+ Australia
+ AU
+ Dollar
+ AUD
+ 1.782
+
+
+ Brazil
+ BR
+ Real
+ BRL
+ 4.5523
+
+
+
+ CurrencyParsing.isValidXmlElem(xml) shouldBe true
+ }
+
+ "validate the period attribute of an xml element" in {
+
+ val invalidPeriodXml =
+
+
+ Argentina
+ AR
+ Peso
+ ARS
+
+
+
+ CurrencyParsing.isValidXmlElem(invalidPeriodXml) shouldBe false
+ }
+
+ "validate the existence of essential child elements" in {
+
+ val xmlMissingCurrencyCode =
+
+
+ Argentina
+ AR
+ Peso
+ 28.5
+
+
+ Hong Kong
+ HK
+ Dollar
+ HKD
+ 10.93
+
+
+
+ val xmlMissingCountryName =
+
+
+ Argentina
+ AR
+ Peso
+ ARS
+ 28.5
+
+
+ HK
+ Dollar
+ HKD
+ 10.93
+
+
+
+ val xmlMissingCurrencyName =
+
+
+ Argentina
+ AR
+ Peso
+ ARS
+ 28.5
+
+
+ Hong Kong
+ HK
+ HKD
+ 10.93
+
+
+
+ CurrencyParsing.isValidXmlElem(xmlMissingCurrencyCode) shouldBe false
+ CurrencyParsing.isValidXmlElem(xmlMissingCountryName) shouldBe false
+ CurrencyParsing.isValidXmlElem(xmlMissingCurrencyName) shouldBe false
+ }
+
+ "validate essential elements have content" in {
+
+ val xmlEmptyCountryName =
+
+
+
+ AR
+ Peso
+ 10.93
+ HKD
+
+
+ Hong Kong
+ HK
+ Dollar
+ HKD
+ 10.93
+
+
+
+ val xmlEmptyCurrencyCode =
+
+
+ Argentina
+ AR
+ Peso
+ 28.25
+ HKD
+
+
+ Hong Kong
+ HK
+ Dollar
+
+ 10.93
+
+
+
+ val xmlEmptyCurrencyName =
+
+
+ Argentina
+ AR
+
+ 10.93
+ HKD
+
+
+ Hong Kong
+ HK
+ Dollar
+ HKD
+ 10.93
+
+
+
+ CurrencyParsing.isValidXmlElem(xmlEmptyCountryName) shouldBe false
+ CurrencyParsing.isValidXmlElem(xmlEmptyCurrencyCode) shouldBe false
+ CurrencyParsing.isValidXmlElem(xmlEmptyCurrencyName) shouldBe false
+ }
+ }
+ }
+}