Skip to content

Commit

Permalink
Merge pull request #22 from jordanrowe/MIBM-72
Browse files Browse the repository at this point in the history
[MIBM-72][JR] add endpoint to retrieve a list of currencies available…
  • Loading branch information
suryend4u authored Sep 23, 2020
2 parents 21fa4c7 + 84fb17f commit a96c621
Show file tree
Hide file tree
Showing 9 changed files with 408 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,11 @@ class ExchangeRateController @Inject() (
Future.successful(Ok(Json.toJson(rates)))
}
}
}

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)
}
}
}
33 changes: 33 additions & 0 deletions app/uk/gov/hmrc/currencyconversion/models/Currency.scala
Original file line number Diff line number Diff line change
@@ -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]
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,38 +19,41 @@ 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)))

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)))

}
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,7 @@ class ExchangeRateService @Inject()(exchangeRateRepository: ConversionRatePeriod
}
}
}

def getCurrencies(date: LocalDate): Option[CurrencyPeriod] =
exchangeRateRepository.getCurrencyPeriod(date)
}
82 changes: 82 additions & 0 deletions app/uk/gov/hmrc/currencyconversion/utils/CurrencyParsing.scala
Original file line number Diff line number Diff line change
@@ -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
}
}
1 change: 1 addition & 0 deletions conf/app.routes
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
Loading

0 comments on commit a96c621

Please sign in to comment.