diff --git a/app/uk/gov/hmrc/currencyconversion/connectors/HODConnector.scala b/app/uk/gov/hmrc/currencyconversion/connectors/HODConnector.scala index 0312fd4..4f5fd5d 100644 --- a/app/uk/gov/hmrc/currencyconversion/connectors/HODConnector.scala +++ b/app/uk/gov/hmrc/currencyconversion/connectors/HODConnector.scala @@ -17,14 +17,15 @@ package uk.gov.hmrc.currencyconversion.connectors import akka.pattern.CircuitBreaker -import com.google.inject.name.Named import com.google.inject.Inject -import play.api.http.{ContentTypes, HeaderNames} +import com.google.inject.name.Named import play.api.Configuration -import play.api.http.Status.SERVICE_UNAVAILABLE +import play.api.http.Status.{OK, SERVICE_UNAVAILABLE} +import play.api.http.{ContentTypes, HeaderNames} import play.api.libs.json.{JsValue, Json} -import uk.gov.hmrc.http.{HeaderCarrier, HttpClient, HttpResponse} import uk.gov.hmrc.currencyconversion.models.Service +import uk.gov.hmrc.http.HttpReads.Implicits._ +import uk.gov.hmrc.http.{HeaderCarrier, HttpClient, HttpResponse} import java.util.UUID import javax.inject.Singleton @@ -60,7 +61,11 @@ class HODConnector @Inject() ( ) def call(implicit hc: HeaderCarrier): Future[HttpResponse] = - http.POST[JsValue, HttpResponse](s"$baseUrl$xrsEndPoint", Json.parse("""{}""")) + http.POST[JsValue, HttpResponse](s"$baseUrl$xrsEndPoint", Json.parse("""{}""")).map { response => + (response.status: @unchecked) match { + case OK => HttpResponse(OK, response.body) + } + } circuitBreaker .withCircuitBreaker(call) diff --git a/build.sbt b/build.sbt index 8651f79..2a8c2de 100644 --- a/build.sbt +++ b/build.sbt @@ -12,10 +12,9 @@ lazy val microservice = Project(appName, file(".")) "-language:implicitConversions", "-language:reflectiveCalls", "-language:postfixOps" - ), - retrieveManaged := true + ) ) - .settings(scalaVersion := "2.13.10") + .settings(scalaVersion := "2.13.12") .settings(majorVersion := 1) .settings( routesImport ++= Seq("uk.gov.hmrc.currencyconversion.binders.DateBinder._", "java.time._") @@ -27,5 +26,5 @@ lazy val microservice = Project(appName, file(".")) ) .settings(PlayKeys.playDefaultPort := 9016) -addCommandAlias("scalafmtAll", "all scalafmtSbt scalafmt test:scalafmt") -addCommandAlias("scalastyleAll", "all scalastyle test:scalastyle") +addCommandAlias("scalafmtAll", "all scalafmtSbt scalafmt Test/scalafmt") +addCommandAlias("scalastyleAll", "all scalastyle Test/scalastyle") diff --git a/conf/application.conf b/conf/application.conf index 8cc3706..e60a7d9 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -14,21 +14,8 @@ include "backend.conf" -# This is the main configuration file for the application. -# ~~~~~ - appName=currency-conversion -# Provides an implementation of AuditConnector. Use `uk.gov.hmrc.play.audit.AuditModule` or create your own. -# An audit connector must be provided. -play.modules.enabled += "uk.gov.hmrc.play.audit.AuditModule" - -# Provides an implementation of MetricsFilter. Use `uk.gov.hmrc.play.graphite.GraphiteMetricsModule` or create your own. -# A metric filter must be provided -play.modules.enabled += "uk.gov.hmrc.play.bootstrap.graphite.GraphiteMetricsModule" - -# Provides an implementation and configures all filters required by a Platform frontend microservice. -play.modules.enabled += "uk.gov.hmrc.play.bootstrap.backend.BackendModule" play.modules.enabled += "uk.gov.hmrc.play.bootstrap.HttpClientModule" play.modules.enabled += "uk.gov.hmrc.mongo.play.PlayMongoModule" play.modules.enabled += "uk.gov.hmrc.currencyconversion.config.HmrcModule" @@ -45,51 +32,9 @@ application.session.secure=false # ~~~~~ play.i18n.langs = ["en"] -# Router -# ~~~~~ -# Define the Router object to use for this application. -# This router will be looked up first when the application is starting up, -# so make sure this is the entry point. -# Furthermore, it's assumed your route file is named properly. -# So for an application router like `my.application.Router`, -# you may need to define a router file `conf/my.application.routes`. -# Default to Routes in the root package (and conf/routes) # !!!WARNING!!! DO NOT CHANGE THIS ROUTER play.http.router=prod.Routes - -# Controller -# ~~~~~ -# By default all controllers will have authorisation, logging and -# auditing (transaction monitoring) enabled. -# The below controllers are the default exceptions to this rule. - -controllers { - - # 300 is the default, you may need to change this according to your needs - confidenceLevel = 300 - - uk.gov.hmrc.currencyconversion.controllers.MicroserviceHelloWorld = { - needsAuth = false - needsLogging = false - needsAuditing = false - } - -} - -# Logger -# ~~~~~ -# You can also configure logback (http://logback.qos.ch/), by providing a logger.xml file in the conf directory . - -# Root logger: -logger.root=ERROR - -# Logger used by the framework: -logger.play=INFO - -# Logger provided to your application: -logger.application=DEBUG - # Microservice specific config workers { xrs-exchange-rate { diff --git a/conf/logback-test.xml b/conf/logback-test.xml deleted file mode 100644 index 8f3454e..0000000 --- a/conf/logback-test.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - logs/currency-conversion.log - - %date{ISO8601} level=[%level] logger=[%logger] thread=[%thread] message=[%message] %replace(exception=[%xException]){'^exception=\[\]$',''}%n - - - - - - %date{ISO8601} level=[%level] logger=[%logger] thread=[%thread] rid=[%X{X-Request-ID}] user=[%X{Authorization}] message=[%message] %replace(exception=[%xException]){'^exception=\[\]$',''}%n - - - - - - %date{ISO8601} level=[%level] logger=[%logger] thread=[%thread] rid=[not-available] user=[not-available] message=[%message] %replace(exception=[%xException]){'^exception=\[\]$',''}%n - - - - - logs/access.log - - %message%n - - - - - logs/connector.log - - %message%n - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/project/AppDependencies.scala b/project/AppDependencies.scala index 9ba6be1..8790038 100644 --- a/project/AppDependencies.scala +++ b/project/AppDependencies.scala @@ -2,7 +2,7 @@ import sbt.* object AppDependencies { - private lazy val bootstrapPlayVersion = "7.19.0" + private lazy val bootstrapPlayVersion = "7.23.0" private lazy val hmrcMongoVersion = "1.3.0" private lazy val compile: Seq[ModuleID] = Seq( @@ -11,12 +11,11 @@ object AppDependencies { ) private lazy val test: Seq[ModuleID] = Seq( - "uk.gov.hmrc" %% "bootstrap-test-play-28" % bootstrapPlayVersion, - "uk.gov.hmrc.mongo" %% "hmrc-mongo-test-play-28" % hmrcMongoVersion, - "org.scalatest" %% "scalatest" % "3.2.16", - "org.mockito" %% "mockito-scala-scalatest" % "1.17.14", - "com.github.tomakehurst" % "wiremock-standalone" % "2.27.2", - "com.vladsch.flexmark" % "flexmark-all" % "0.64.8" + "uk.gov.hmrc" %% "bootstrap-test-play-28" % bootstrapPlayVersion, + "uk.gov.hmrc.mongo" %% "hmrc-mongo-test-play-28" % hmrcMongoVersion, + "org.scalatest" %% "scalatest" % "3.2.17", + "org.mockito" %% "mockito-scala-scalatest" % "1.17.30", + "com.vladsch.flexmark" % "flexmark-all" % "0.64.8" ).map(_ % Test) def apply(): Seq[ModuleID] = compile ++ test diff --git a/project/plugins.sbt b/project/plugins.sbt index 6b03d57..85a70b9 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -7,10 +7,10 @@ resolvers += Resolver.url("HMRC-open-artefacts-ivy2", url("https://open.artefact // Try to remove when sbt 1.8.0+ and scoverage is 2.0.7+ ThisBuild / libraryDependencySchemes += "org.scala-lang.modules" %% "scala-xml" % VersionScheme.Always -addSbtPlugin("uk.gov.hmrc" % "sbt-auto-build" % "3.14.0") -addSbtPlugin("uk.gov.hmrc" % "sbt-distributables" % "2.2.0") -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.19") -addSbtPlugin("com.beautiful-scala" % "sbt-scalastyle" % "1.5.1") -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.7") -addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.3") -addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0") +addSbtPlugin("uk.gov.hmrc" % "sbt-auto-build" % "3.15.0") +addSbtPlugin("uk.gov.hmrc" % "sbt-distributables" % "2.2.0") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.21") +addSbtPlugin("org.scalastyle" % "scalastyle-sbt-plugin" % "1.0.0") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.9") +addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.3") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2") diff --git a/test/uk/gov/hmrc/currencyconversion/connectors/HODConnectorSpec.scala b/test/uk/gov/hmrc/currencyconversion/connectors/HODConnectorSpec.scala index 8133145..3df6905 100644 --- a/test/uk/gov/hmrc/currencyconversion/connectors/HODConnectorSpec.scala +++ b/test/uk/gov/hmrc/currencyconversion/connectors/HODConnectorSpec.scala @@ -16,19 +16,17 @@ package uk.gov.hmrc.currencyconversion.connectors -import com.codahale.metrics.SharedMetricRegistries import com.github.tomakehurst.wiremock.client.MappingBuilder import com.github.tomakehurst.wiremock.client.WireMock._ -import com.github.tomakehurst.wiremock.http.Fault import org.scalatest.BeforeAndAfterEach import org.scalatest.concurrent.{IntegrationPatience, ScalaFutures} import org.scalatest.freespec.AnyFreeSpec import org.scalatest.matchers.must.Matchers import org.scalatestplus.play.guice.GuiceOneAppPerSuite import play.api.Application +import play.api.http.Status import play.api.inject.guice.GuiceApplicationBuilder import play.api.test.Helpers._ -import play.api.test.Injecting import uk.gov.hmrc.currencyconversion.utils.WireMockHelper class HODConnectorSpec @@ -38,12 +36,8 @@ class HODConnectorSpec with WireMockHelper with ScalaFutures with IntegrationPatience - with Injecting with BeforeAndAfterEach { - override def beforeEach(): Unit = - SharedMetricRegistries.clear() - override lazy val app: Application = new GuiceApplicationBuilder() .configure( @@ -53,15 +47,14 @@ class HODConnectorSpec ) .build() - private lazy val connector: HODConnector = inject[HODConnector] + private lazy val connector: HODConnector = app.injector.instanceOf[HODConnector] private def stubCall: MappingBuilder = post(urlEqualTo("/passengers/exchangerequest/xrs/getexchangerate/v1")) - "hod connector" - { + "HODConnector" - { "must call the HOD when xrs worker thread is started" in { - server.stubFor( stubCall .willReturn(aResponse().withStatus(OK)) @@ -69,22 +62,42 @@ class HODConnectorSpec connector.submit().futureValue.status mustBe OK } - "must fall back to a 503 (SERVICE_UNAVAILABLE) when the downstream call errors" in { - server.stubFor( - stubCall - .willReturn(aResponse().withFault(Fault.RANDOM_DATA_THEN_CLOSE)) - .willReturn(aResponse().withStatus(SERVICE_UNAVAILABLE)) - ) - connector.submit().futureValue.status mustBe SERVICE_UNAVAILABLE - } + "must fall back to a 503 (SERVICE_UNAVAILABLE) when the circuit breaker handles" - { + def test(status: Int): Unit = + s"2xx statuses and above with an invalid status $status" in { + server.stubFor( + stubCall + .willReturn(aResponse().withStatus(status)) + ) - "must fail fast while the circuit breaker is open when Xrs call is triggered" in { - server.stubFor( - stubCall - .willReturn(aResponse().withFault(Fault.RANDOM_DATA_THEN_CLOSE)) - .willReturn(aResponse().withStatus(INSUFFICIENT_STORAGE)) - ) - connector.submit().futureValue.status mustBe SERVICE_UNAVAILABLE + connector.submit().futureValue.status mustBe SERVICE_UNAVAILABLE + } + + val invalidStatusResponses: Seq[Int] = Status.getClass.getDeclaredFields.toSeq + .map { field => + field.setAccessible(true) + field.get(field.getName) + } + .flatMap { + case fieldValue: java.lang.Integer + if fieldValue != CONTINUE && fieldValue != SWITCHING_PROTOCOLS && fieldValue != OK => + Some(fieldValue.toInt) + case _ => None + } + + invalidStatusResponses.foreach(status => test(status)) + + Seq(CONTINUE, SWITCHING_PROTOCOLS).foreach { status => + s"1xx statuses with an invalid status $status" in { + server.stubFor( + stubCall + .willReturn(aResponse().withStatus(status)) + ) + + connector.submit().futureValue.status mustBe SERVICE_UNAVAILABLE + } + } } } + }