From 428c527f29d75f22525afab60df244a431b6b36a Mon Sep 17 00:00:00 2001 From: Samir Benzenine Date: Mon, 1 Aug 2022 18:25:48 +0100 Subject: [PATCH] ITSASU-1446: Create status-determination-service Connector Signed-off-by: Samir Benzenine --- app/config/MicroserviceAppConfig.scala | 3 + app/connectors/MandationStatusConnector.scala | 41 +++++++++++ app/parsers/MandationStatusParser.scala | 39 ++++++++++ conf/application.conf | 5 ++ .../MandationStatusConnectorISpec.scala | 71 +++++++++++++++++++ it/helpers/ComponentSpecBase.scala | 4 +- .../servicemocks/GetMandationStatusStub.scala | 38 ++++++++++ .../MandationStatusConnectorSpec.scala | 59 +++++++++++++++ test/parsers/MandationStatusParserSpec.scala | 68 ++++++++++++++++++ 9 files changed, 327 insertions(+), 1 deletion(-) create mode 100644 app/connectors/MandationStatusConnector.scala create mode 100644 app/parsers/MandationStatusParser.scala create mode 100644 it/connectors/MandationStatusConnectorISpec.scala create mode 100644 it/helpers/servicemocks/GetMandationStatusStub.scala create mode 100644 test/connectors/MandationStatusConnectorSpec.scala create mode 100644 test/parsers/MandationStatusParserSpec.scala diff --git a/app/config/MicroserviceAppConfig.scala b/app/config/MicroserviceAppConfig.scala index 8eb044b5..d15e1d4b 100644 --- a/app/config/MicroserviceAppConfig.scala +++ b/app/config/MicroserviceAppConfig.scala @@ -43,6 +43,7 @@ trait AppConfig { val desAuthorisationToken: String val desEnvironmentHeader: (String, String) + def statusDeterminationServiceURL: String } @Singleton @@ -55,6 +56,8 @@ class MicroserviceAppConfig @Inject()(servicesConfig: ServicesConfig, val config override lazy val ggURL: String = servicesConfig.baseUrl("government-gateway") override lazy val ggAdminURL: String = servicesConfig.baseUrl("gg-admin") + override lazy val statusDeterminationServiceURL = servicesConfig.baseUrl("status-determination-service") + private def desBase = if (FeatureSwitching.isEnabled(featureswitch.StubDESFeature, configuration)) "microservice.services.stub-des" else "microservice.services.des" diff --git a/app/connectors/MandationStatusConnector.scala b/app/connectors/MandationStatusConnector.scala new file mode 100644 index 00000000..3dc7e7b7 --- /dev/null +++ b/app/connectors/MandationStatusConnector.scala @@ -0,0 +1,41 @@ +/* + * Copyright 2022 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 connectors + +import config.AppConfig +import models.status.MandationStatusRequest +import parsers.MandationStatusParser.{PostMandationStatusResponse, mandationStatusResponseHttpReads} +import uk.gov.hmrc.http.{HeaderCarrier, HttpClient} + +import javax.inject.{Inject, Singleton} +import scala.concurrent.{ExecutionContext, Future} + +@Singleton +class MandationStatusConnector @Inject()(httpClient: HttpClient, + appConfig: AppConfig)(implicit ec: ExecutionContext) { + def getMandationStatus(nino: String, utr: String) + (implicit hc: HeaderCarrier): Future[PostMandationStatusResponse] = { + val requestBody = MandationStatusRequest(nino, utr) + + httpClient.POST[MandationStatusRequest, PostMandationStatusResponse]( + url = getMandationStatusUrl, + body = requestBody + ) + } + + private val getMandationStatusUrl = s"${appConfig.statusDeterminationServiceURL}/itsa-status" +} diff --git a/app/parsers/MandationStatusParser.scala b/app/parsers/MandationStatusParser.scala new file mode 100644 index 00000000..560dd5b9 --- /dev/null +++ b/app/parsers/MandationStatusParser.scala @@ -0,0 +1,39 @@ +/* + * Copyright 2022 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 parsers + +import models.ErrorModel +import models.status.MandationStatusResponse +import play.api.http.Status.OK +import play.api.libs.json.{JsError, JsSuccess} +import uk.gov.hmrc.http.{HttpReads, HttpResponse} + +object MandationStatusParser { + type PostMandationStatusResponse = Either[ErrorModel, MandationStatusResponse] + + implicit val mandationStatusResponseHttpReads: HttpReads[PostMandationStatusResponse] = + (_: String, _: String, response: HttpResponse) => { + response.status match { + case OK => response.json.validate[MandationStatusResponse] match { + case JsSuccess(value, _) => Right(value) + case JsError(errors) => + Left(ErrorModel(OK, s"Invalid Json for mandationStatusResponseHttpReads: $errors")) + } + case status => Left(ErrorModel(status, response.body)) + } + } +} diff --git a/conf/application.conf b/conf/application.conf index 6fdc85fd..09a58c40 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -180,6 +180,11 @@ microservice { port = 9602 } + status-determination-service { + host = localhost + port = 9562 + } + des { url = "http://localhost:9562" environment = "dev" diff --git a/it/connectors/MandationStatusConnectorISpec.scala b/it/connectors/MandationStatusConnectorISpec.scala new file mode 100644 index 00000000..213933e0 --- /dev/null +++ b/it/connectors/MandationStatusConnectorISpec.scala @@ -0,0 +1,71 @@ +/* + * 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 connectors + +import helpers.ComponentSpecBase +import helpers.IntegrationTestConstants.failureResponse +import helpers.servicemocks.GetMandationStatusStub +import models.ErrorModel +import models.status.MandationStatus.Voluntary +import models.status.{MandationStatusRequest, MandationStatusResponse} +import play.api.http.Status.{INTERNAL_SERVER_ERROR, OK} +import play.api.libs.json.Json +import play.api.mvc.Request +import play.api.test.FakeRequest + +class MandationStatusConnectorISpec extends ComponentSpecBase { + private lazy val connector: MandationStatusConnector = app.injector.instanceOf[MandationStatusConnector] + implicit val request: Request[_] = FakeRequest() + + "MandationStatusConnector" should { + "return MandationStatusResponse" when { + "the status determination service returns OK and a valid JSON response body" in { + GetMandationStatusStub.stub( + Json.toJson(MandationStatusRequest("test-nino", "test-utr")) + )(OK, Json.toJson(MandationStatusResponse(currentYearStatus = Voluntary, nextYearStatus = Voluntary))) + + val result = connector.getMandationStatus("test-nino", "test-utr") + + result.futureValue shouldBe Right(MandationStatusResponse(currentYearStatus = Voluntary, nextYearStatus = Voluntary)) + } + } + + "return an exception" when { + "the status determination service returns OK and an invalid JSON response body" in { + GetMandationStatusStub.stubInvalidResponse( + Json.toJson(MandationStatusRequest("test-nino", "test-utr")) + )(OK, "{ currentYearStatus") + + val result = connector.getMandationStatus("test-nino", "test-utr") + + result.futureValue shouldBe Left(ErrorModel(OK, "Invalid Json for mandationStatusResponseHttpReads: List((,List(JsonValidationError(List(error.expected.jsobject),WrappedArray()))))")) + } + } + + "return the status and error received" when { + "the status determination service returns a failure" in { + GetMandationStatusStub.stub( + Json.toJson(MandationStatusRequest("test-nino", "test-utr")) + )(INTERNAL_SERVER_ERROR, failureResponse("code", "reason")) + + val result = connector.getMandationStatus("test-nino", "test-utr") + + result.futureValue shouldBe Left(ErrorModel(INTERNAL_SERVER_ERROR, """{"code":"code","reason":"reason"}""")) + } + } + } +} diff --git a/it/helpers/ComponentSpecBase.scala b/it/helpers/ComponentSpecBase.scala index ff47ebc5..94f93a6c 100644 --- a/it/helpers/ComponentSpecBase.scala +++ b/it/helpers/ComponentSpecBase.scala @@ -62,7 +62,9 @@ trait ComponentSpecBase extends AnyWordSpecLike "microservice.services.throttle-threshold" -> "2", "throttle.testThrottle.max"-> "10", "throttle.zeroTestThrottle.max"-> "0", - "throttle.oneTestThrottle.max" -> "1" + "throttle.oneTestThrottle.max" -> "1", + "microservice.services.status-determination-service.host" -> mockHost, + "microservice.services.status-determination-service.port" -> mockPort, ) ++ overriddenConfig() def overriddenConfig(): Map[String, String] = Map.empty diff --git a/it/helpers/servicemocks/GetMandationStatusStub.scala b/it/helpers/servicemocks/GetMandationStatusStub.scala new file mode 100644 index 00000000..14fa8f08 --- /dev/null +++ b/it/helpers/servicemocks/GetMandationStatusStub.scala @@ -0,0 +1,38 @@ +/* + * 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 helpers.servicemocks + +import com.github.tomakehurst.wiremock.stubbing.StubMapping +import play.api.libs.json.JsValue + +object GetMandationStatusStub extends WireMockMethods { + def stub(expectedBody: JsValue)(status: Int, body: JsValue): StubMapping = { + when( + method = POST, + uri = "/itsa-status", + body = expectedBody + ).thenReturn(status, body) + } + + def stubInvalidResponse(expectedBody: JsValue)(status: Int, body: String): StubMapping = { + when( + method = POST, + uri = "/itsa-status", + body = expectedBody + ).thenReturn(status, body) + } +} diff --git a/test/connectors/MandationStatusConnectorSpec.scala b/test/connectors/MandationStatusConnectorSpec.scala new file mode 100644 index 00000000..4d5930a3 --- /dev/null +++ b/test/connectors/MandationStatusConnectorSpec.scala @@ -0,0 +1,59 @@ +/* + * Copyright 2022 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 connectors + +import common.CommonSpec +import config.AppConfig +import connectors.mocks.MockHttp +import models.status.MandationStatus.Voluntary +import models.status.{MandationStatusRequest, MandationStatusResponse} +import org.mockito.ArgumentMatchers +import org.mockito.Mockito.when +import org.scalatestplus.play.guice.GuiceOneAppPerSuite +import parsers.MandationStatusParser.PostMandationStatusResponse +import play.api.mvc.Request +import play.api.test.FakeRequest +import play.api.test.Helpers.{await, defaultAwaitTimeout} +import uk.gov.hmrc.http.HeaderCarrier + +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.Future + +class MandationStatusConnectorSpec extends CommonSpec with MockHttp with GuiceOneAppPerSuite { + implicit val hc = HeaderCarrier() + implicit val request: Request[_] = FakeRequest() + + val appConfig: AppConfig = app.injector.instanceOf[AppConfig] + val connector = new MandationStatusConnector(mockHttpClient, appConfig) + + val headers = Seq() + + "MandationStatusConnector" should { + "retrieve the user mandation status" in { + when(mockHttpClient.POST[MandationStatusRequest, PostMandationStatusResponse]( + ArgumentMatchers.eq(s"${appConfig.statusDeterminationServiceURL}/itsa-status"), + ArgumentMatchers.eq(MandationStatusRequest("test-nino", "test-utr")), + ArgumentMatchers.any() + )(ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any())) + .thenReturn(Future.successful(Right(MandationStatusResponse(currentYearStatus = Voluntary, nextYearStatus = Voluntary)))) + + await( + connector.getMandationStatus("test-nino", "test-utr") + ) shouldBe Right(MandationStatusResponse(currentYearStatus = Voluntary, nextYearStatus = Voluntary)) + } + } +} diff --git a/test/parsers/MandationStatusParserSpec.scala b/test/parsers/MandationStatusParserSpec.scala new file mode 100644 index 00000000..d0ba9947 --- /dev/null +++ b/test/parsers/MandationStatusParserSpec.scala @@ -0,0 +1,68 @@ +/* + * Copyright 2022 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 parsers + +import common.CommonSpec +import models.ErrorModel +import models.status.MandationStatus.Voluntary +import models.status.MandationStatusResponse +import play.api.http.Status.{INTERNAL_SERVER_ERROR, OK} +import play.api.libs.json.Json +import uk.gov.hmrc.http.HttpResponse + +class MandationStatusParserSpec extends CommonSpec { + "MandationStatusParser" should { + "return MandationStatusResponse" when { + "supplied with an OK response and valid JSON" in { + val response = HttpResponse( + OK, + body = Json.toJson(MandationStatusResponse(currentYearStatus = Voluntary, nextYearStatus = Voluntary)).toString() + ) + + MandationStatusParser.mandationStatusResponseHttpReads.read("POST", "test-url", response) shouldBe + Right(MandationStatusResponse(currentYearStatus = Voluntary, nextYearStatus = Voluntary)) + } + } + + "return an error" when { + "supplied with an OK response and invalid JSON" in { + val response = HttpResponse(OK, body = + """ + |{ + | "invalid" : "json" + |} + """.stripMargin) + + val expectedError = "Invalid Json for mandationStatusResponseHttpReads: " + + "List(" + + "(/currentYearStatus,List(JsonValidationError(List(error.path.missing),WrappedArray()))), " + + "(/nextYearStatus,List(JsonValidationError(List(error.path.missing),WrappedArray())))" + + ")" + + MandationStatusParser.mandationStatusResponseHttpReads.read("POST", "test-url", response) shouldBe + Left(ErrorModel(OK, expectedError)) + } + + "supplied with a failed response" in { + val response = HttpResponse(INTERNAL_SERVER_ERROR, body = "Error body") + + MandationStatusParser.mandationStatusResponseHttpReads.read("POST", "test-url", response) shouldBe + Left(ErrorModel(INTERNAL_SERVER_ERROR, "Error body")) + } + } + } +}