From 85f104e36856d0ccb147dd497fe5068f6f6fbec4 Mon Sep 17 00:00:00 2001 From: "siva.isikella" <7499500+sivaprakashiv@users.noreply.github.com> Date: Thu, 1 Jul 2021 16:30:14 +0100 Subject: [PATCH 1/8] APID-208 - set from, faultTo defaults similar to replyTo --- .../models/JsonFormats.scala | 4 +- .../models/MessageRequest.scala | 6 +- .../services/OutboundService.scala | 4 +- .../controllers/OutboundControllerSpec.scala | 64 +++++++++++++++---- .../services/OutboundServiceSpec.scala | 17 ++--- 5 files changed, 66 insertions(+), 29 deletions(-) diff --git a/app/uk/gov/hmrc/apiplatformoutboundsoap/models/JsonFormats.scala b/app/uk/gov/hmrc/apiplatformoutboundsoap/models/JsonFormats.scala index 1d51e4c..daa655c 100644 --- a/app/uk/gov/hmrc/apiplatformoutboundsoap/models/JsonFormats.scala +++ b/app/uk/gov/hmrc/apiplatformoutboundsoap/models/JsonFormats.scala @@ -21,10 +21,10 @@ import play.api.libs.json.{JsPath, Json, OFormat, Reads} object JsonFormats { val addressingReads: Reads[Addressing] = ( - (JsPath \ "from").readNullable[String] and + (JsPath \ "from").read[String].orElse(Reads.pure("TBC")) and (JsPath \ "to").read[String] and (JsPath \ "replyTo").read[String].orElse(Reads.pure("TBC")) and - (JsPath \ "faultTo").readNullable[String] and + (JsPath \ "faultTo").read[String].orElse(Reads.pure("TBC")) and (JsPath \ "messageId").read[String] and (JsPath \ "relatesTo").readNullable[String] ) (Addressing.apply _) diff --git a/app/uk/gov/hmrc/apiplatformoutboundsoap/models/MessageRequest.scala b/app/uk/gov/hmrc/apiplatformoutboundsoap/models/MessageRequest.scala index a93b33a..0573d0e 100644 --- a/app/uk/gov/hmrc/apiplatformoutboundsoap/models/MessageRequest.scala +++ b/app/uk/gov/hmrc/apiplatformoutboundsoap/models/MessageRequest.scala @@ -25,13 +25,15 @@ case class MessageRequest(wsdlUrl: String, confirmationOfDelivery: Boolean, notificationUrl: Option[String] = None) -case class Addressing(from: Option[String] = None, +case class Addressing(from: String, to: String, replyTo: String, - faultTo: Option[String] = None, + faultTo: String, messageId: String, relatesTo: Option[String] = None){ validate(to.trim != "", "addressing.to being empty") validate(messageId.trim != "", "addressing.messageId being empty") validate(replyTo.trim != "", "addressing.replyTo being empty") + validate(from.trim != "", "addressing.from being empty") + validate(faultTo.trim != "", "addressing.faultTo being empty") } diff --git a/app/uk/gov/hmrc/apiplatformoutboundsoap/services/OutboundService.scala b/app/uk/gov/hmrc/apiplatformoutboundsoap/services/OutboundService.scala index 177c753..69c11f1 100644 --- a/app/uk/gov/hmrc/apiplatformoutboundsoap/services/OutboundService.scala +++ b/app/uk/gov/hmrc/apiplatformoutboundsoap/services/OutboundService.scala @@ -175,10 +175,10 @@ class OutboundService @Inject()(outboundConnector: OutboundConnector, } private def addOptionalAddressingHeaders(message: MessageRequest, wsaNs: OMNamespace, envelope: SOAPEnvelope): Unit = { - message.addressing.from.foreach(addWithAddressElementChildToSoapHeader(_, WSA_FROM, wsaNs, envelope)) + addWithAddressElementChildToSoapHeader(message.addressing.from, WSA_FROM, wsaNs, envelope) addToSoapHeader(message.addressing.to, WSA_TO, wsaNs, envelope) addWithAddressElementChildToSoapHeader(message.addressing.replyTo, WSA_REPLY_TO, wsaNs, envelope) - message.addressing.faultTo.foreach(addWithAddressElementChildToSoapHeader(_, WSA_FAULT_TO, wsaNs, envelope)) + addWithAddressElementChildToSoapHeader(message.addressing.faultTo, WSA_FAULT_TO, wsaNs, envelope) addToSoapHeader(message.addressing.messageId, WSA_MESSAGE_ID, wsaNs, envelope) message.addressing.relatesTo.foreach(addToSoapHeader(_, WSA_RELATES_TO, wsaNs, envelope)) } diff --git a/test/uk/gov/hmrc/apiplatformoutboundsoap/controllers/OutboundControllerSpec.scala b/test/uk/gov/hmrc/apiplatformoutboundsoap/controllers/OutboundControllerSpec.scala index 222065d..83e97d0 100644 --- a/test/uk/gov/hmrc/apiplatformoutboundsoap/controllers/OutboundControllerSpec.scala +++ b/test/uk/gov/hmrc/apiplatformoutboundsoap/controllers/OutboundControllerSpec.scala @@ -50,7 +50,7 @@ class OutboundControllerSpec extends AnyWordSpec with Matchers with GuiceOneAppP "message" should { val fakeRequest = FakeRequest("POST", "/message") - val addressing = Addressing(messageId = "987", to = "AddressedTo", replyTo = "ReplyTo") + val addressing = Addressing(messageId = "987", to = "AddressedTo", replyTo = "ReplyTo", faultTo = "FaultTo", from = "from") val addressingJson = Json.toJson(addressing) val message = Json.obj("wsdlUrl" -> "http://example.com/wsdl", "wsdlOperation" -> "theOp", "messageBody" -> "example", "addressing" -> addressingJson) @@ -80,6 +80,56 @@ class OutboundControllerSpec extends AnyWordSpec with Matchers with GuiceOneAppP messageCaptor.getValue.confirmationOfDelivery shouldBe false } + "return OK response with defaults when the request json body addressing section has missing from, replyTo, faultTo addressing fields" in new Setup { + val messageCaptor: ArgumentCaptor[MessageRequest] = ArgumentCaptor.forClass(classOf[MessageRequest]) + when(outboundServiceMock.sendMessage(messageCaptor.capture())).thenReturn(successful(outboundSoapMessage)) + + val result: Future[Result] = underTest.message()(fakeRequest.withBody(Json.obj("wsdlUrl" -> "http://example.com/wsdl", + "wsdlOperation" -> "theOp", "messageBody" -> "example", "addressing" -> + Json.obj("messageId" -> "some msg id", "to" -> "who it is to")))) + + status(result) shouldBe OK + messageCaptor.getValue.addressing.from shouldBe "TBC" + messageCaptor.getValue.addressing.replyTo shouldBe "TBC" + messageCaptor.getValue.addressing.faultTo shouldBe "TBC" + } + + "return bad request when the request json body addressing section has empty ReplyTo field" in new Setup { + when(outboundServiceMock.sendMessage(*)).thenReturn(successful(outboundSoapMessage)) + + val result: Future[Result] = underTest.message()(fakeRequest.withBody(Json.obj("wsdlUrl" -> "http://example.com/wsdl", + "wsdlOperation" -> "theOp", "messageBody" -> "example", "addressing" -> + Json.obj("messageId" -> "some msg id", "to" -> "who it is to", "replyTo" -> " ")))) + + status(result) shouldBe BAD_REQUEST + contentAsString(result) shouldBe + "Could not parse body due to addressing.replyTo being empty" + } + + "return bad request when the request json body addressing section has empty from field" in new Setup { + when(outboundServiceMock.sendMessage(*)).thenReturn(successful(outboundSoapMessage)) + + val result: Future[Result] = underTest.message()(fakeRequest.withBody(Json.obj("wsdlUrl" -> "http://example.com/wsdl", + "wsdlOperation" -> "theOp", "messageBody" -> "example", "addressing" -> + Json.obj("messageId" -> "some msg id", "to" -> "who it is to", "from" -> " ")))) + + status(result) shouldBe BAD_REQUEST + contentAsString(result) shouldBe + "Could not parse body due to addressing.from being empty" + } + + "return bad request when the request json body addressing section has empty faultTo field" in new Setup { + when(outboundServiceMock.sendMessage(*)).thenReturn(successful(outboundSoapMessage)) + + val result: Future[Result] = underTest.message()(fakeRequest.withBody(Json.obj("wsdlUrl" -> "http://example.com/wsdl", + "wsdlOperation" -> "theOp", "messageBody" -> "example", "addressing" -> + Json.obj("messageId" -> "some msg id", "to" -> "who it is to", "faultTo" -> " ")))) + + status(result) shouldBe BAD_REQUEST + contentAsString(result) shouldBe + "Could not parse body due to addressing.faultTo being empty" + } + "return bad request when the request json body is missing wsdlUrl field" in new Setup { when(outboundServiceMock.sendMessage(*)).thenReturn(successful(outboundSoapMessage)) @@ -112,18 +162,6 @@ class OutboundControllerSpec extends AnyWordSpec with Matchers with GuiceOneAppP "Invalid MessageRequest payload: List((/addressing/to,List(JsonValidationError(List(error.path.missing),WrappedArray()))))" } - "return bad request when the request json body addressing section has empty ReplyTo field" in new Setup { - when(outboundServiceMock.sendMessage(*)).thenReturn(successful(outboundSoapMessage)) - - val result: Future[Result] = underTest.message()(fakeRequest.withBody(Json.obj("wsdlUrl" -> "http://example.com/wsdl", - "wsdlOperation" -> "theOp", "messageBody" -> "example", "addressing" -> - Json.obj("messageId" -> "some msg id", "to" -> "who it is to", "replyTo" -> "")))) - - status(result) shouldBe BAD_REQUEST - contentAsString(result) shouldBe - "Could not parse body due to addressing.replyTo being empty" - } - "return bad request when the request json body addressing section is missing message ID field" in new Setup { when(outboundServiceMock.sendMessage(*)).thenReturn(successful(outboundSoapMessage)) diff --git a/test/uk/gov/hmrc/apiplatformoutboundsoap/services/OutboundServiceSpec.scala b/test/uk/gov/hmrc/apiplatformoutboundsoap/services/OutboundServiceSpec.scala index 7091707..64743f7 100644 --- a/test/uk/gov/hmrc/apiplatformoutboundsoap/services/OutboundServiceSpec.scala +++ b/test/uk/gov/hmrc/apiplatformoutboundsoap/services/OutboundServiceSpec.scala @@ -80,9 +80,8 @@ class OutboundServiceSpec extends AnyWordSpec with Matchers with GuiceOneAppPerS "sendMessage" should { val messageId = "123" val to = "CCN2" - val from = Some("HMRC") - val addressing = Addressing(from , to , "ReplyTo", Some("FaultTo"), messageId, Some("RelatesTo")) - val addressingOnlyMandatoryFields = Addressing(to= to, replyTo = "ReplyTo", messageId = messageId) + val from = "HMRC" + val addressing = Addressing(from , to , "ReplyTo", "FaultTo", messageId, Some("RelatesTo")) val messageRequestFullAddressing = MessageRequest( "test/resources/definitions/CCN2.Service.Customs.Default.ICS.RiskAnalysisOrchestrationBAS_1.0.0_CCN2_1.0.0.wsdl", "IE4N03notifyERiskAnalysisHit", @@ -110,8 +109,6 @@ class OutboundServiceSpec extends AnyWordSpec with Matchers with GuiceOneAppPerS Some("http://somenotification.url") ) - val messageRequestMinimalAddressing = messageRequestFullAddressing - .copy(addressing = addressingOnlyMandatoryFields) val expectedStatus: Int = OK val allAddressingHeaders = @@ -290,7 +287,7 @@ class OutboundServiceSpec extends AnyWordSpec with Matchers with GuiceOneAppPerS when(outboundConnectorMock.postMessage(*)).thenReturn(successful(expectedStatus)) when(outboundMessageRepositoryMock.persist(*)).thenReturn(Future(InsertOneResult.acknowledged(BsonNumber(1)))) - await(underTest.sendMessage(messageRequestMinimalAddressing)) + await(underTest.sendMessage(messageRequestFullAddressing)) getXmlDiff(messageCaptor.getValue.toString, expectedSoapEnvelope(mandatoryAddressingHeaders)).build().hasDifferences shouldBe false } @@ -301,7 +298,7 @@ class OutboundServiceSpec extends AnyWordSpec with Matchers with GuiceOneAppPerS when(outboundConnectorMock.postMessage(*)).thenReturn(successful(expectedStatus)) when(outboundMessageRepositoryMock.persist(*)).thenReturn(Future(InsertOneResult.acknowledged(BsonNumber(1)))) - await(underTest.sendMessage(messageRequestMinimalAddressing)) + await(underTest.sendMessage(messageRequestFullAddressing)) getXmlDiff(messageCaptor.getValue.toString, expectedSoapEnvelope(mandatoryAddressingHeaders)).build().hasDifferences shouldBe false } @@ -313,7 +310,7 @@ class OutboundServiceSpec extends AnyWordSpec with Matchers with GuiceOneAppPerS when(outboundConnectorMock.postMessage(*)).thenReturn(successful(expectedStatus)) when(outboundMessageRepositoryMock.persist(persistCaptor.capture())).thenReturn(Future(InsertOneResult.acknowledged(BsonNumber(1)))) - await(underTest.sendMessage(messageRequestMinimalAddressing)) + await(underTest.sendMessage(messageRequestFullAddressing)) persistCaptor.getValue.soapMessage shouldBe expectedSoapEnvelope(mandatoryAddressingHeaders) getXmlDiff(messageCaptor.getValue.toString, expectedSoapEnvelope(mandatoryAddressingHeaders)).build().hasDifferences shouldBe false } @@ -324,7 +321,7 @@ class OutboundServiceSpec extends AnyWordSpec with Matchers with GuiceOneAppPerS val messageCaptor: ArgumentCaptor[OutboundSoapMessage] = ArgumentCaptor.forClass(classOf[OutboundSoapMessage]) when(outboundMessageRepositoryMock.persist(messageCaptor.capture())).thenReturn(Future(InsertOneResult.acknowledged(BsonNumber(1)))) - await(underTest.sendMessage(messageRequestMinimalAddressing)) + await(underTest.sendMessage(messageRequestFullAddressing)) messageCaptor.getValue.messageId shouldBe messageId } @@ -336,7 +333,7 @@ class OutboundServiceSpec extends AnyWordSpec with Matchers with GuiceOneAppPerS val messageCaptor: ArgumentCaptor[OutboundSoapMessage] = ArgumentCaptor.forClass(classOf[OutboundSoapMessage]) when(outboundMessageRepositoryMock.persist(messageCaptor.capture())).thenReturn(Future(InsertOneResult.acknowledged(BsonNumber(1)))) - await(underTest.sendMessage(messageRequestMinimalAddressing)) + await(underTest.sendMessage(messageRequestFullAddressing)) messageCaptor.getValue.messageId shouldBe messageId } From 71bf77b05bf1531372678ab9a3ff3deaceb62bc6 Mon Sep 17 00:00:00 2001 From: "siva.isikella" <7499500+sivaprakashiv@users.noreply.github.com> Date: Fri, 2 Jul 2021 15:42:59 +0100 Subject: [PATCH 2/8] APID-208 - Using typesafe config to load properties --- .../models/JsonFormats.scala | 16 ++++++-- .../models/MessageRequest.scala | 2 - .../controllers/OutboundControllerSpec.scala | 38 ++++--------------- 3 files changed, 19 insertions(+), 37 deletions(-) diff --git a/app/uk/gov/hmrc/apiplatformoutboundsoap/models/JsonFormats.scala b/app/uk/gov/hmrc/apiplatformoutboundsoap/models/JsonFormats.scala index daa655c..fcc0b4d 100644 --- a/app/uk/gov/hmrc/apiplatformoutboundsoap/models/JsonFormats.scala +++ b/app/uk/gov/hmrc/apiplatformoutboundsoap/models/JsonFormats.scala @@ -16,15 +16,23 @@ package uk.gov.hmrc.apiplatformoutboundsoap.models +import com.typesafe.config.{Config, ConfigFactory} import play.api.libs.functional.syntax._ import play.api.libs.json.{JsPath, Json, OFormat, Reads} -object JsonFormats { +object JsonFormats { + val config: Config = ConfigFactory.load() + private def getParam(path: String): String = if (config.hasPath(path)) config.getString(path) else "" + + private val addressingFrom = getParam("addressing.from") + private val addressingReplyTo = getParam("addressing.replyTo") + private val addressingFaultTo = getParam("addressing.faultTo") + val addressingReads: Reads[Addressing] = ( - (JsPath \ "from").read[String].orElse(Reads.pure("TBC")) and + (JsPath \ "from").read[String].orElse(Reads.pure(addressingFrom)) and (JsPath \ "to").read[String] and - (JsPath \ "replyTo").read[String].orElse(Reads.pure("TBC")) and - (JsPath \ "faultTo").read[String].orElse(Reads.pure("TBC")) and + (JsPath \ "replyTo").read[String].orElse(Reads.pure(addressingReplyTo)) and + (JsPath \ "faultTo").read[String].orElse(Reads.pure(addressingFaultTo)) and (JsPath \ "messageId").read[String] and (JsPath \ "relatesTo").readNullable[String] ) (Addressing.apply _) diff --git a/app/uk/gov/hmrc/apiplatformoutboundsoap/models/MessageRequest.scala b/app/uk/gov/hmrc/apiplatformoutboundsoap/models/MessageRequest.scala index 0573d0e..5794ecd 100644 --- a/app/uk/gov/hmrc/apiplatformoutboundsoap/models/MessageRequest.scala +++ b/app/uk/gov/hmrc/apiplatformoutboundsoap/models/MessageRequest.scala @@ -33,7 +33,5 @@ case class Addressing(from: String, relatesTo: Option[String] = None){ validate(to.trim != "", "addressing.to being empty") validate(messageId.trim != "", "addressing.messageId being empty") - validate(replyTo.trim != "", "addressing.replyTo being empty") validate(from.trim != "", "addressing.from being empty") - validate(faultTo.trim != "", "addressing.faultTo being empty") } diff --git a/test/uk/gov/hmrc/apiplatformoutboundsoap/controllers/OutboundControllerSpec.scala b/test/uk/gov/hmrc/apiplatformoutboundsoap/controllers/OutboundControllerSpec.scala index 83e97d0..4a17489 100644 --- a/test/uk/gov/hmrc/apiplatformoutboundsoap/controllers/OutboundControllerSpec.scala +++ b/test/uk/gov/hmrc/apiplatformoutboundsoap/controllers/OutboundControllerSpec.scala @@ -20,6 +20,7 @@ import akka.stream.Materializer import org.joda.time.DateTime import org.joda.time.DateTimeZone.UTC import org.mockito.{ArgumentCaptor, ArgumentMatchersSugar, MockitoSugar} +import org.scalatest.BeforeAndAfterEach import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec import org.scalatestplus.play.guice.GuiceOneAppPerSuite @@ -39,7 +40,7 @@ import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future import scala.concurrent.Future.{failed, successful} -class OutboundControllerSpec extends AnyWordSpec with Matchers with GuiceOneAppPerSuite with MockitoSugar with ArgumentMatchersSugar { +class OutboundControllerSpec extends AnyWordSpec with Matchers with GuiceOneAppPerSuite with MockitoSugar with ArgumentMatchersSugar with BeforeAndAfterEach { implicit val mat: Materializer = app.injector.instanceOf[Materializer] trait Setup { @@ -47,7 +48,6 @@ class OutboundControllerSpec extends AnyWordSpec with Matchers with GuiceOneAppP val underTest = new OutboundController(Helpers.stubControllerComponents(), outboundServiceMock) } - "message" should { val fakeRequest = FakeRequest("POST", "/message") val addressing = Addressing(messageId = "987", to = "AddressedTo", replyTo = "ReplyTo", faultTo = "FaultTo", from = "from") @@ -80,30 +80,18 @@ class OutboundControllerSpec extends AnyWordSpec with Matchers with GuiceOneAppP messageCaptor.getValue.confirmationOfDelivery shouldBe false } - "return OK response with defaults when the request json body addressing section has missing from, replyTo, faultTo addressing fields" in new Setup { + "return OK response with defaults when the request json body addressing section has missing replyTo, faultTo addressing fields" in new Setup { val messageCaptor: ArgumentCaptor[MessageRequest] = ArgumentCaptor.forClass(classOf[MessageRequest]) when(outboundServiceMock.sendMessage(messageCaptor.capture())).thenReturn(successful(outboundSoapMessage)) val result: Future[Result] = underTest.message()(fakeRequest.withBody(Json.obj("wsdlUrl" -> "http://example.com/wsdl", "wsdlOperation" -> "theOp", "messageBody" -> "example", "addressing" -> - Json.obj("messageId" -> "some msg id", "to" -> "who it is to")))) + Json.obj("messageId" -> "some msg id", "to" -> "who it is to", "from" -> "from")))) status(result) shouldBe OK - messageCaptor.getValue.addressing.from shouldBe "TBC" - messageCaptor.getValue.addressing.replyTo shouldBe "TBC" - messageCaptor.getValue.addressing.faultTo shouldBe "TBC" - } - - "return bad request when the request json body addressing section has empty ReplyTo field" in new Setup { - when(outboundServiceMock.sendMessage(*)).thenReturn(successful(outboundSoapMessage)) - - val result: Future[Result] = underTest.message()(fakeRequest.withBody(Json.obj("wsdlUrl" -> "http://example.com/wsdl", - "wsdlOperation" -> "theOp", "messageBody" -> "example", "addressing" -> - Json.obj("messageId" -> "some msg id", "to" -> "who it is to", "replyTo" -> " ")))) - - status(result) shouldBe BAD_REQUEST - contentAsString(result) shouldBe - "Could not parse body due to addressing.replyTo being empty" + messageCaptor.getValue.addressing.from shouldBe "from" + messageCaptor.getValue.addressing.replyTo shouldBe "" + messageCaptor.getValue.addressing.faultTo shouldBe "" } "return bad request when the request json body addressing section has empty from field" in new Setup { @@ -118,18 +106,6 @@ class OutboundControllerSpec extends AnyWordSpec with Matchers with GuiceOneAppP "Could not parse body due to addressing.from being empty" } - "return bad request when the request json body addressing section has empty faultTo field" in new Setup { - when(outboundServiceMock.sendMessage(*)).thenReturn(successful(outboundSoapMessage)) - - val result: Future[Result] = underTest.message()(fakeRequest.withBody(Json.obj("wsdlUrl" -> "http://example.com/wsdl", - "wsdlOperation" -> "theOp", "messageBody" -> "example", "addressing" -> - Json.obj("messageId" -> "some msg id", "to" -> "who it is to", "faultTo" -> " ")))) - - status(result) shouldBe BAD_REQUEST - contentAsString(result) shouldBe - "Could not parse body due to addressing.faultTo being empty" - } - "return bad request when the request json body is missing wsdlUrl field" in new Setup { when(outboundServiceMock.sendMessage(*)).thenReturn(successful(outboundSoapMessage)) From 3a6fd5ce9700012ecd0d2739107cc4db11e63742 Mon Sep 17 00:00:00 2001 From: "siva.isikella" <7499500+sivaprakashiv@users.noreply.github.com> Date: Sat, 3 Jul 2021 18:11:52 +0100 Subject: [PATCH 3/8] APID-208 - Injecting AppConfig dependency from built-in play module --- README.md | 8 ++--- .../GlobalContext.scala | 32 +++++++++++++++++++ .../config/AppConfig.scala | 3 ++ .../models/JsonFormats.scala | 18 +++++------ conf/application.conf | 2 ++ .../services/OutboundServiceSpec.scala | 26 +++++++++------ 6 files changed, 66 insertions(+), 23 deletions(-) create mode 100644 app/uk/gov/hmrc/apiplatformoutboundsoap/GlobalContext.scala diff --git a/README.md b/README.md index 063fce0..da45da0 100644 --- a/README.md +++ b/README.md @@ -38,10 +38,10 @@ Send a SOAP message for the given operation | `confirmationOfDelivery` | An optional boolean specifying whether the sender wishes to receive a confirmation of delivery from the target SOAP service. Defaults to false if not provided in the request | | `notificationUrl` | An optional String property which, if provided, will be used to POST a status update when the message is successfully sent, or is marked as failed after retries have been exhausted. The body will be in the same form as [the response below this table](#response) | | `addressing` | The property to provide WS addressing data | -| `addressing.from` | This optional property provides the value for the `From` element in the SOAP header | +| `addressing.from` | This optional property provides the value for the `From` element in the SOAP header. This will default to value in environment specific configuration parameter `addressing.from` if not present in the message. In the absence of configuration this parameter defaults to empty string. If, however, it is present in the message but is an empty string or whitespace then a 400 error will be returned || | `addressing.to` | This required property provides the value for the `To` element in the SOAP header | -| `addressing.replyTo` | This optional property provides the value for the `ReplyTo` element in the SOAP header. This will default to TBC if not present in the message. If, however, it is present in the message but is an empty string or whitespace then a 400 error will be returned | -| `addressing.faultTo` | This optional property provides the value for the `FaultTo` element in the SOAP header | +| `addressing.replyTo` | This optional property provides the value for the `ReplyTo` element in the SOAP header. This will default to value in environment specific configuration parameter `addressing.replyTo` if not present in the message. In the absence of configuration this parameter defaults to empty string. | +| `addressing.faultTo` | This optional property provides the value for the `FaultTo` element in the SOAP header. This will default to value in environment specific configuration parameter `addressing.faultTo` if not present in the message. In the absence of configuration this parameter defaults to empty string. | | `addressing.messageId` | This required property provides the value for the `MessageID` element in the SOAP header | | `addressing.relatesTo` | This optional property provides the value for the `RelatesTo` element in the SOAP header | @@ -68,7 +68,7 @@ HTTP Status: 200 (OK) | --- | --- | | `wsdlUrl`, `wsdlOperation` or `messageBody` missing from request body | `400` | | `to` or `messageId` missing from addressing property | `400` | -| `replyTo` in addressing property is empty or whitespace| `400` | +| `from` in addressing property is empty or whitespace| `400` | | invalid WSDL | `400` | | operation not found in the WSDL | `404` | diff --git a/app/uk/gov/hmrc/apiplatformoutboundsoap/GlobalContext.scala b/app/uk/gov/hmrc/apiplatformoutboundsoap/GlobalContext.scala new file mode 100644 index 0000000..71d448b --- /dev/null +++ b/app/uk/gov/hmrc/apiplatformoutboundsoap/GlobalContext.scala @@ -0,0 +1,32 @@ +/* + * Copyright 2021 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.apiplatformoutboundsoap + +import play.api.inject.Injector + +import javax.inject.{Inject, Singleton} + +@Singleton +class GlobalContext @Inject()(playBuiltinInjector: Injector) { + GlobalContext.injectorRef = playBuiltinInjector +} + +object GlobalContext { + private var injectorRef: Injector = _ + + def injector: Injector = injectorRef +} \ No newline at end of file diff --git a/app/uk/gov/hmrc/apiplatformoutboundsoap/config/AppConfig.scala b/app/uk/gov/hmrc/apiplatformoutboundsoap/config/AppConfig.scala index 238f7fc..fbfed3d 100644 --- a/app/uk/gov/hmrc/apiplatformoutboundsoap/config/AppConfig.scala +++ b/app/uk/gov/hmrc/apiplatformoutboundsoap/config/AppConfig.scala @@ -48,4 +48,7 @@ class AppConfig @Inject()(config: Configuration, servicesConfig: ServicesConfig) val parallelism: Int = config.getOptional[Int]("retry.parallelism").getOrElse(5) val cacheDuration: Duration = Duration(config.getOptional[String]("cache.duration").getOrElse("1 day")) + val addressingFrom: String = config.getOptional[String]("addressing.from").getOrElse("") + val addressingReplyTo: String = config.getOptional[String]("addressing.replyTo").getOrElse("") + val addressingFaultTo: String = config.getOptional[String]("addressing.faultTo").getOrElse("") } diff --git a/app/uk/gov/hmrc/apiplatformoutboundsoap/models/JsonFormats.scala b/app/uk/gov/hmrc/apiplatformoutboundsoap/models/JsonFormats.scala index fcc0b4d..b656c23 100644 --- a/app/uk/gov/hmrc/apiplatformoutboundsoap/models/JsonFormats.scala +++ b/app/uk/gov/hmrc/apiplatformoutboundsoap/models/JsonFormats.scala @@ -16,23 +16,20 @@ package uk.gov.hmrc.apiplatformoutboundsoap.models -import com.typesafe.config.{Config, ConfigFactory} import play.api.libs.functional.syntax._ import play.api.libs.json.{JsPath, Json, OFormat, Reads} +import uk.gov.hmrc.apiplatformoutboundsoap.config.AppConfig object JsonFormats { - val config: Config = ConfigFactory.load() - private def getParam(path: String): String = if (config.hasPath(path)) config.getString(path) else "" - private val addressingFrom = getParam("addressing.from") - private val addressingReplyTo = getParam("addressing.replyTo") - private val addressingFaultTo = getParam("addressing.faultTo") + import uk.gov.hmrc.apiplatformoutboundsoap.GlobalContext.injector + val appConfig: AppConfig = injector.instanceOf[AppConfig] - val addressingReads: Reads[Addressing] = ( - (JsPath \ "from").read[String].orElse(Reads.pure(addressingFrom)) and + def addressingReads: Reads[Addressing] = ( + (JsPath \ "from").read[String].orElse(Reads.pure(appConfig.addressingFrom)) and (JsPath \ "to").read[String] and - (JsPath \ "replyTo").read[String].orElse(Reads.pure(addressingReplyTo)) and - (JsPath \ "faultTo").read[String].orElse(Reads.pure(addressingFaultTo)) and + (JsPath \ "replyTo").read[String].orElse(Reads.pure(appConfig.addressingReplyTo)) and + (JsPath \ "faultTo").read[String].orElse(Reads.pure(appConfig.addressingFaultTo)) and (JsPath \ "messageId").read[String] and (JsPath \ "relatesTo").readNullable[String] ) (Addressing.apply _) @@ -49,3 +46,4 @@ object JsonFormats { ) (MessageRequest.apply _) implicit val messageRequestFormatter: OFormat[MessageRequest] = OFormat(messageRequestReads, Json.writes[MessageRequest]) } + diff --git a/conf/application.conf b/conf/application.conf index af722c3..e691883 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -25,6 +25,8 @@ play.application.loader = "uk.gov.hmrc.play.bootstrap.ApplicationLoader" # Primary entry point for all HTTP requests on Play applications play.http.requestHandler = "uk.gov.hmrc.play.bootstrap.http.RequestHandler" +# Setting this as a first module to allow the other modules to use the play built in injector properly. +play.modules.enabled += "uk.gov.hmrc.apiplatformoutboundsoap.modules.InjectionModule" # Provides an implementation of AuditConnector. Use `uk.gov.hmrc.play.bootstrap.AuditModule` or create your own. # An audit connector must be provided. play.modules.enabled += "uk.gov.hmrc.play.bootstrap.AuditModule" diff --git a/test/uk/gov/hmrc/apiplatformoutboundsoap/services/OutboundServiceSpec.scala b/test/uk/gov/hmrc/apiplatformoutboundsoap/services/OutboundServiceSpec.scala index 64743f7..b6a574b 100644 --- a/test/uk/gov/hmrc/apiplatformoutboundsoap/services/OutboundServiceSpec.scala +++ b/test/uk/gov/hmrc/apiplatformoutboundsoap/services/OutboundServiceSpec.scala @@ -68,6 +68,9 @@ class OutboundServiceSpec extends AnyWordSpec with Matchers with GuiceOneAppPerS when(appConfigMock.cacheDuration).thenReturn(Duration("10 min")) when(appConfigMock.ccn2Host).thenReturn("example.com") when(appConfigMock.ccn2Port).thenReturn(1234) + when(appConfigMock.addressingFrom).thenReturn("HMRC") + when(appConfigMock.addressingReplyTo).thenReturn("ReplyTo") + when(appConfigMock.addressingFaultTo).thenReturn("FaultTo") val underTest: OutboundService = new OutboundService(outboundConnectorMock, wsSecurityServiceMock, outboundMessageRepositoryMock, notificationCallbackConnectorMock, appConfigMock, cacheSpy) { @@ -82,6 +85,7 @@ class OutboundServiceSpec extends AnyWordSpec with Matchers with GuiceOneAppPerS val to = "CCN2" val from = "HMRC" val addressing = Addressing(from , to , "ReplyTo", "FaultTo", messageId, Some("RelatesTo")) + val addressingOnlyMandatoryFields = Addressing(from = from, to = to, replyTo = "ReplyTo", faultTo = "FaultTo", messageId = messageId) val messageRequestFullAddressing = MessageRequest( "test/resources/definitions/CCN2.Service.Customs.Default.ICS.RiskAnalysisOrchestrationBAS_1.0.0_CCN2_1.0.0.wsdl", "IE4N03notifyERiskAnalysisHit", @@ -109,6 +113,8 @@ class OutboundServiceSpec extends AnyWordSpec with Matchers with GuiceOneAppPerS Some("http://somenotification.url") ) + val messageRequestMinimalAddressing = messageRequestFullAddressing + .copy(addressing = addressingOnlyMandatoryFields) val expectedStatus: Int = OK val allAddressingHeaders = @@ -120,8 +126,10 @@ class OutboundServiceSpec extends AnyWordSpec with Matchers with GuiceOneAppPerS |RelatesTo""".stripMargin.replaceAll("\n", "") val mandatoryAddressingHeaders = - """CCN2 + """HMRC + |CCN2 |ReplyTo + |FaultTo |123""".stripMargin.replaceAll("\n", "") def expectedSoapEnvelope(extraHeaders: String = ""): String = @@ -287,7 +295,7 @@ class OutboundServiceSpec extends AnyWordSpec with Matchers with GuiceOneAppPerS when(outboundConnectorMock.postMessage(*)).thenReturn(successful(expectedStatus)) when(outboundMessageRepositoryMock.persist(*)).thenReturn(Future(InsertOneResult.acknowledged(BsonNumber(1)))) - await(underTest.sendMessage(messageRequestFullAddressing)) + await(underTest.sendMessage(messageRequestMinimalAddressing)) getXmlDiff(messageCaptor.getValue.toString, expectedSoapEnvelope(mandatoryAddressingHeaders)).build().hasDifferences shouldBe false } @@ -298,7 +306,7 @@ class OutboundServiceSpec extends AnyWordSpec with Matchers with GuiceOneAppPerS when(outboundConnectorMock.postMessage(*)).thenReturn(successful(expectedStatus)) when(outboundMessageRepositoryMock.persist(*)).thenReturn(Future(InsertOneResult.acknowledged(BsonNumber(1)))) - await(underTest.sendMessage(messageRequestFullAddressing)) + await(underTest.sendMessage(messageRequestMinimalAddressing)) getXmlDiff(messageCaptor.getValue.toString, expectedSoapEnvelope(mandatoryAddressingHeaders)).build().hasDifferences shouldBe false } @@ -310,7 +318,7 @@ class OutboundServiceSpec extends AnyWordSpec with Matchers with GuiceOneAppPerS when(outboundConnectorMock.postMessage(*)).thenReturn(successful(expectedStatus)) when(outboundMessageRepositoryMock.persist(persistCaptor.capture())).thenReturn(Future(InsertOneResult.acknowledged(BsonNumber(1)))) - await(underTest.sendMessage(messageRequestFullAddressing)) + await(underTest.sendMessage(messageRequestMinimalAddressing)) persistCaptor.getValue.soapMessage shouldBe expectedSoapEnvelope(mandatoryAddressingHeaders) getXmlDiff(messageCaptor.getValue.toString, expectedSoapEnvelope(mandatoryAddressingHeaders)).build().hasDifferences shouldBe false } @@ -321,7 +329,7 @@ class OutboundServiceSpec extends AnyWordSpec with Matchers with GuiceOneAppPerS val messageCaptor: ArgumentCaptor[OutboundSoapMessage] = ArgumentCaptor.forClass(classOf[OutboundSoapMessage]) when(outboundMessageRepositoryMock.persist(messageCaptor.capture())).thenReturn(Future(InsertOneResult.acknowledged(BsonNumber(1)))) - await(underTest.sendMessage(messageRequestFullAddressing)) + await(underTest.sendMessage(messageRequestMinimalAddressing)) messageCaptor.getValue.messageId shouldBe messageId } @@ -333,7 +341,7 @@ class OutboundServiceSpec extends AnyWordSpec with Matchers with GuiceOneAppPerS val messageCaptor: ArgumentCaptor[OutboundSoapMessage] = ArgumentCaptor.forClass(classOf[OutboundSoapMessage]) when(outboundMessageRepositoryMock.persist(messageCaptor.capture())).thenReturn(Future(InsertOneResult.acknowledged(BsonNumber(1)))) - await(underTest.sendMessage(messageRequestFullAddressing)) + await(underTest.sendMessage(messageRequestMinimalAddressing)) messageCaptor.getValue.messageId shouldBe messageId } @@ -382,15 +390,15 @@ class OutboundServiceSpec extends AnyWordSpec with Matchers with GuiceOneAppPerS exception.getMessage should include("addressing.messageId being empty") } - "fail when the addressing.replyTo field is empty" in new Setup { + "fail when the addressing.from field is empty" in new Setup { when(wsSecurityServiceMock.addUsernameToken(*)).thenReturn(expectedSoapEnvelope()) when(outboundConnectorMock.postMessage(*)).thenReturn(successful(expectedStatus)) val exception: IllegalArgumentException = intercept[IllegalArgumentException] { - await(underTest.sendMessage(messageRequestFullAddressing.copy(addressing= addressing.copy(replyTo = "")))) + await(underTest.sendMessage(messageRequestFullAddressing.copy(addressing= addressing.copy(from = "")))) } - exception.getMessage should include("addressing.replyTo being empty") + exception.getMessage should include("addressing.from being empty") } } From bd68075270a8ef227586396417ec8f5c77b2405c Mon Sep 17 00:00:00 2001 From: "siva.isikella" <7499500+sivaprakashiv@users.noreply.github.com> Date: Sat, 3 Jul 2021 22:47:51 +0100 Subject: [PATCH 4/8] APID-208 - test confirmationOfDelivery is formatted with fallback defaults --- .../config/AppConfig.scala | 1 + .../models/JsonFormats.scala | 2 +- .../controllers/OutboundControllerSpec.scala | 22 +++++++++++++------ 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/app/uk/gov/hmrc/apiplatformoutboundsoap/config/AppConfig.scala b/app/uk/gov/hmrc/apiplatformoutboundsoap/config/AppConfig.scala index fbfed3d..3d449fd 100644 --- a/app/uk/gov/hmrc/apiplatformoutboundsoap/config/AppConfig.scala +++ b/app/uk/gov/hmrc/apiplatformoutboundsoap/config/AppConfig.scala @@ -51,4 +51,5 @@ class AppConfig @Inject()(config: Configuration, servicesConfig: ServicesConfig) val addressingFrom: String = config.getOptional[String]("addressing.from").getOrElse("") val addressingReplyTo: String = config.getOptional[String]("addressing.replyTo").getOrElse("") val addressingFaultTo: String = config.getOptional[String]("addressing.faultTo").getOrElse("") + val confirmationOfDelivery: Boolean = config.getOptional[Boolean]("confirmationOfDelivery").getOrElse(false) } diff --git a/app/uk/gov/hmrc/apiplatformoutboundsoap/models/JsonFormats.scala b/app/uk/gov/hmrc/apiplatformoutboundsoap/models/JsonFormats.scala index b656c23..1f878c4 100644 --- a/app/uk/gov/hmrc/apiplatformoutboundsoap/models/JsonFormats.scala +++ b/app/uk/gov/hmrc/apiplatformoutboundsoap/models/JsonFormats.scala @@ -41,7 +41,7 @@ object JsonFormats { (JsPath \ "wsdlOperation").read[String] and (JsPath \ "messageBody").read[String] and (JsPath \ "addressing").read[Addressing] and - ((JsPath \ "confirmationOfDelivery").read[Boolean] or Reads.pure(false)) and + ((JsPath \ "confirmationOfDelivery").read[Boolean].orElse(Reads.pure(appConfig.confirmationOfDelivery)) or Reads.pure(false)) and (JsPath \ "notificationUrl").readNullable[String] ) (MessageRequest.apply _) implicit val messageRequestFormatter: OFormat[MessageRequest] = OFormat(messageRequestReads, Json.writes[MessageRequest]) diff --git a/test/uk/gov/hmrc/apiplatformoutboundsoap/controllers/OutboundControllerSpec.scala b/test/uk/gov/hmrc/apiplatformoutboundsoap/controllers/OutboundControllerSpec.scala index 4a17489..241e2f5 100644 --- a/test/uk/gov/hmrc/apiplatformoutboundsoap/controllers/OutboundControllerSpec.scala +++ b/test/uk/gov/hmrc/apiplatformoutboundsoap/controllers/OutboundControllerSpec.scala @@ -19,12 +19,14 @@ package uk.gov.hmrc.apiplatformoutboundsoap.controllers import akka.stream.Materializer import org.joda.time.DateTime import org.joda.time.DateTimeZone.UTC +import org.mockito.scalatest.ResetMocksAfterEachTest import org.mockito.{ArgumentCaptor, ArgumentMatchersSugar, MockitoSugar} -import org.scalatest.BeforeAndAfterEach import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec import org.scalatestplus.play.guice.GuiceOneAppPerSuite +import play.api.{Application, Configuration} import play.api.http.Status.{BAD_REQUEST, OK} +import play.api.inject.guice.GuiceApplicationBuilder import play.api.libs.json.{JsBoolean, Json} import play.api.mvc.Result import play.api.test.Helpers._ @@ -40,14 +42,20 @@ import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future import scala.concurrent.Future.{failed, successful} -class OutboundControllerSpec extends AnyWordSpec with Matchers with GuiceOneAppPerSuite with MockitoSugar with ArgumentMatchersSugar with BeforeAndAfterEach { +class OutboundControllerSpec extends AnyWordSpec with Matchers with MockitoSugar with GuiceOneAppPerSuite + with ArgumentMatchersSugar with ResetMocksAfterEachTest { implicit val mat: Materializer = app.injector.instanceOf[Materializer] - trait Setup { - val outboundServiceMock: OutboundService = mock[OutboundService] + val outboundServiceMock: OutboundService = mock[OutboundService] + + override lazy val app: Application = GuiceApplicationBuilder() + .configure("confirmationOfDelivery" -> true) + .build + trait Setup { val underTest = new OutboundController(Helpers.stubControllerComponents(), outboundServiceMock) } + "message" should { val fakeRequest = FakeRequest("POST", "/message") val addressing = Addressing(messageId = "987", to = "AddressedTo", replyTo = "ReplyTo", faultTo = "FaultTo", from = "from") @@ -77,7 +85,7 @@ class OutboundControllerSpec extends AnyWordSpec with Matchers with GuiceOneAppP messageCaptor.getValue.wsdlOperation shouldBe "theOp" messageCaptor.getValue.messageBody shouldBe "example" messageCaptor.getValue.addressing shouldBe addressing - messageCaptor.getValue.confirmationOfDelivery shouldBe false + messageCaptor.getValue.confirmationOfDelivery shouldBe true } "return OK response with defaults when the request json body addressing section has missing replyTo, faultTo addressing fields" in new Setup { @@ -150,7 +158,7 @@ class OutboundControllerSpec extends AnyWordSpec with Matchers with GuiceOneAppP "Invalid MessageRequest payload: List((/addressing/messageId,List(JsonValidationError(List(error.path.missing),WrappedArray()))))" } - "default confirmation of delivery to false if not present" in new Setup { + "default confirmation of delivery to true if not present in request but its overridden in config" in new Setup { val expectedStatus: Int = OK val messageCaptor: ArgumentCaptor[MessageRequest] = ArgumentCaptor.forClass(classOf[MessageRequest]) when(outboundServiceMock.sendMessage(messageCaptor.capture())).thenReturn(successful(outboundSoapMessage)) @@ -158,7 +166,7 @@ class OutboundControllerSpec extends AnyWordSpec with Matchers with GuiceOneAppP val result: Future[Result] = underTest.message()(fakeRequest.withBody(message)) status(result) shouldBe expectedStatus - messageCaptor.getValue.confirmationOfDelivery shouldBe false + messageCaptor.getValue.confirmationOfDelivery shouldBe true } "confirmation of delivery field is true when true in the request" in new Setup { From 0751e8d1d74e18509305347cf78893bf3dd5dee5 Mon Sep 17 00:00:00 2001 From: "siva.isikella" <7499500+sivaprakashiv@users.noreply.github.com> Date: Mon, 5 Jul 2021 11:28:38 +0100 Subject: [PATCH 5/8] APID-208 - refactored missing module to config package --- .../config/InjectionModule.scala | 29 +++++++++++++++++++ conf/application.conf | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 app/uk/gov/hmrc/apiplatformoutboundsoap/config/InjectionModule.scala diff --git a/app/uk/gov/hmrc/apiplatformoutboundsoap/config/InjectionModule.scala b/app/uk/gov/hmrc/apiplatformoutboundsoap/config/InjectionModule.scala new file mode 100644 index 0000000..30903a0 --- /dev/null +++ b/app/uk/gov/hmrc/apiplatformoutboundsoap/config/InjectionModule.scala @@ -0,0 +1,29 @@ +/* + * Copyright 2021 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.apiplatformoutboundsoap.config + +import com.google.inject.AbstractModule +import uk.gov.hmrc.apiplatformoutboundsoap.GlobalContext + +class InjectionModule extends AbstractModule { + override def configure() = { + // ... + + // Eager initialize Context singleton + bind(classOf[GlobalContext]).asEagerSingleton() + } +} diff --git a/conf/application.conf b/conf/application.conf index e691883..04d9cbd 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -26,7 +26,7 @@ play.application.loader = "uk.gov.hmrc.play.bootstrap.ApplicationLoader" play.http.requestHandler = "uk.gov.hmrc.play.bootstrap.http.RequestHandler" # Setting this as a first module to allow the other modules to use the play built in injector properly. -play.modules.enabled += "uk.gov.hmrc.apiplatformoutboundsoap.modules.InjectionModule" +play.modules.enabled += "uk.gov.hmrc.apiplatformoutboundsoap.config.InjectionModule" # Provides an implementation of AuditConnector. Use `uk.gov.hmrc.play.bootstrap.AuditModule` or create your own. # An audit connector must be provided. play.modules.enabled += "uk.gov.hmrc.play.bootstrap.AuditModule" From bd53681c62da6a72964d1ff38b8f1620e9fea4ee Mon Sep 17 00:00:00 2001 From: "siva.isikella" <7499500+sivaprakashiv@users.noreply.github.com> Date: Tue, 6 Jul 2021 11:32:55 +0100 Subject: [PATCH 6/8] APID-208 - review comments addressed --- .../hmrc/apiplatformoutboundsoap/config/InjectionModule.scala | 1 - .../gov/hmrc/apiplatformoutboundsoap/models/JsonFormats.scala | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/uk/gov/hmrc/apiplatformoutboundsoap/config/InjectionModule.scala b/app/uk/gov/hmrc/apiplatformoutboundsoap/config/InjectionModule.scala index 30903a0..1c70d67 100644 --- a/app/uk/gov/hmrc/apiplatformoutboundsoap/config/InjectionModule.scala +++ b/app/uk/gov/hmrc/apiplatformoutboundsoap/config/InjectionModule.scala @@ -21,7 +21,6 @@ import uk.gov.hmrc.apiplatformoutboundsoap.GlobalContext class InjectionModule extends AbstractModule { override def configure() = { - // ... // Eager initialize Context singleton bind(classOf[GlobalContext]).asEagerSingleton() diff --git a/app/uk/gov/hmrc/apiplatformoutboundsoap/models/JsonFormats.scala b/app/uk/gov/hmrc/apiplatformoutboundsoap/models/JsonFormats.scala index 1f878c4..5a81760 100644 --- a/app/uk/gov/hmrc/apiplatformoutboundsoap/models/JsonFormats.scala +++ b/app/uk/gov/hmrc/apiplatformoutboundsoap/models/JsonFormats.scala @@ -25,7 +25,7 @@ object JsonFormats { import uk.gov.hmrc.apiplatformoutboundsoap.GlobalContext.injector val appConfig: AppConfig = injector.instanceOf[AppConfig] - def addressingReads: Reads[Addressing] = ( + val addressingReads: Reads[Addressing] = ( (JsPath \ "from").read[String].orElse(Reads.pure(appConfig.addressingFrom)) and (JsPath \ "to").read[String] and (JsPath \ "replyTo").read[String].orElse(Reads.pure(appConfig.addressingReplyTo)) and From 3712e6472f8d9dcc173250219d52730d9bec7f78 Mon Sep 17 00:00:00 2001 From: "siva.isikella" <7499500+sivaprakashiv@users.noreply.github.com> Date: Tue, 6 Jul 2021 12:11:21 +0100 Subject: [PATCH 7/8] APID-208 - review comments addressed --- .../services/OutboundServiceSpec.scala | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/test/uk/gov/hmrc/apiplatformoutboundsoap/services/OutboundServiceSpec.scala b/test/uk/gov/hmrc/apiplatformoutboundsoap/services/OutboundServiceSpec.scala index b6a574b..ae16d6e 100644 --- a/test/uk/gov/hmrc/apiplatformoutboundsoap/services/OutboundServiceSpec.scala +++ b/test/uk/gov/hmrc/apiplatformoutboundsoap/services/OutboundServiceSpec.scala @@ -85,7 +85,8 @@ class OutboundServiceSpec extends AnyWordSpec with Matchers with GuiceOneAppPerS val to = "CCN2" val from = "HMRC" val addressing = Addressing(from , to , "ReplyTo", "FaultTo", messageId, Some("RelatesTo")) - val addressingOnlyMandatoryFields = Addressing(from = from, to = to, replyTo = "ReplyTo", faultTo = "FaultTo", messageId = messageId) + // mixin refers to mandatory and default addressing fields + val addressingMixinFields = Addressing(from = from, to = to, replyTo = "ReplyTo", faultTo = "FaultTo", messageId = messageId) val messageRequestFullAddressing = MessageRequest( "test/resources/definitions/CCN2.Service.Customs.Default.ICS.RiskAnalysisOrchestrationBAS_1.0.0_CCN2_1.0.0.wsdl", "IE4N03notifyERiskAnalysisHit", @@ -114,7 +115,7 @@ class OutboundServiceSpec extends AnyWordSpec with Matchers with GuiceOneAppPerS ) val messageRequestMinimalAddressing = messageRequestFullAddressing - .copy(addressing = addressingOnlyMandatoryFields) + .copy(addressing = addressingMixinFields) val expectedStatus: Int = OK val allAddressingHeaders = @@ -125,7 +126,7 @@ class OutboundServiceSpec extends AnyWordSpec with Matchers with GuiceOneAppPerS |123 |RelatesTo""".stripMargin.replaceAll("\n", "") - val mandatoryAddressingHeaders = + val mixinAddressingHeaders = """HMRC |CCN2 |ReplyTo @@ -291,36 +292,36 @@ class OutboundServiceSpec extends AnyWordSpec with Matchers with GuiceOneAppPerS "send the expected SOAP envelope to the security service which adds username token" in new Setup { val messageCaptor: ArgumentCaptor[SOAPEnvelope] = ArgumentCaptor.forClass(classOf[SOAPEnvelope]) - when(wsSecurityServiceMock.addUsernameToken(messageCaptor.capture())).thenReturn(expectedSoapEnvelope(mandatoryAddressingHeaders)) + when(wsSecurityServiceMock.addUsernameToken(messageCaptor.capture())).thenReturn(expectedSoapEnvelope(mixinAddressingHeaders)) when(outboundConnectorMock.postMessage(*)).thenReturn(successful(expectedStatus)) when(outboundMessageRepositoryMock.persist(*)).thenReturn(Future(InsertOneResult.acknowledged(BsonNumber(1)))) await(underTest.sendMessage(messageRequestMinimalAddressing)) - getXmlDiff(messageCaptor.getValue.toString, expectedSoapEnvelope(mandatoryAddressingHeaders)).build().hasDifferences shouldBe false + getXmlDiff(messageCaptor.getValue.toString, expectedSoapEnvelope(mixinAddressingHeaders)).build().hasDifferences shouldBe false } "send the expected SOAP envelope to the security service which adds signature" in new Setup { val messageCaptor: ArgumentCaptor[SOAPEnvelope] = ArgumentCaptor.forClass(classOf[SOAPEnvelope]) when(appConfigMock.enableMessageSigning).thenReturn(true) - when(wsSecurityServiceMock.addSignature(messageCaptor.capture())).thenReturn(expectedSoapEnvelope(mandatoryAddressingHeaders)) + when(wsSecurityServiceMock.addSignature(messageCaptor.capture())).thenReturn(expectedSoapEnvelope(mixinAddressingHeaders)) when(outboundConnectorMock.postMessage(*)).thenReturn(successful(expectedStatus)) when(outboundMessageRepositoryMock.persist(*)).thenReturn(Future(InsertOneResult.acknowledged(BsonNumber(1)))) await(underTest.sendMessage(messageRequestMinimalAddressing)) - getXmlDiff(messageCaptor.getValue.toString, expectedSoapEnvelope(mandatoryAddressingHeaders)).build().hasDifferences shouldBe false + getXmlDiff(messageCaptor.getValue.toString, expectedSoapEnvelope(mixinAddressingHeaders)).build().hasDifferences shouldBe false } "send the optional addressing headers if present in the request" in new Setup { val messageCaptor: ArgumentCaptor[SOAPEnvelope] = ArgumentCaptor.forClass(classOf[SOAPEnvelope]) val persistCaptor: ArgumentCaptor[OutboundSoapMessage] = ArgumentCaptor.forClass(classOf[OutboundSoapMessage]) - when(wsSecurityServiceMock.addUsernameToken(messageCaptor.capture())).thenReturn(expectedSoapEnvelope(mandatoryAddressingHeaders)) + when(wsSecurityServiceMock.addUsernameToken(messageCaptor.capture())).thenReturn(expectedSoapEnvelope(mixinAddressingHeaders)) when(outboundConnectorMock.postMessage(*)).thenReturn(successful(expectedStatus)) when(outboundMessageRepositoryMock.persist(persistCaptor.capture())).thenReturn(Future(InsertOneResult.acknowledged(BsonNumber(1)))) await(underTest.sendMessage(messageRequestMinimalAddressing)) - persistCaptor.getValue.soapMessage shouldBe expectedSoapEnvelope(mandatoryAddressingHeaders) - getXmlDiff(messageCaptor.getValue.toString, expectedSoapEnvelope(mandatoryAddressingHeaders)).build().hasDifferences shouldBe false + persistCaptor.getValue.soapMessage shouldBe expectedSoapEnvelope(mixinAddressingHeaders) + getXmlDiff(messageCaptor.getValue.toString, expectedSoapEnvelope(mixinAddressingHeaders)).build().hasDifferences shouldBe false } "persist message ID if present in the request for success" in new Setup { From 3f25ab21d2b9da55f69933392897b75e70270f35 Mon Sep 17 00:00:00 2001 From: "siva.isikella" <7499500+sivaprakashiv@users.noreply.github.com> Date: Tue, 6 Jul 2021 13:01:14 +0100 Subject: [PATCH 8/8] APID-208 - review comments addressed --- .../gov/hmrc/apiplatformoutboundsoap/models/JsonFormats.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/uk/gov/hmrc/apiplatformoutboundsoap/models/JsonFormats.scala b/app/uk/gov/hmrc/apiplatformoutboundsoap/models/JsonFormats.scala index 5a81760..9213896 100644 --- a/app/uk/gov/hmrc/apiplatformoutboundsoap/models/JsonFormats.scala +++ b/app/uk/gov/hmrc/apiplatformoutboundsoap/models/JsonFormats.scala @@ -41,7 +41,7 @@ object JsonFormats { (JsPath \ "wsdlOperation").read[String] and (JsPath \ "messageBody").read[String] and (JsPath \ "addressing").read[Addressing] and - ((JsPath \ "confirmationOfDelivery").read[Boolean].orElse(Reads.pure(appConfig.confirmationOfDelivery)) or Reads.pure(false)) and + (JsPath \ "confirmationOfDelivery").read[Boolean].orElse(Reads.pure(appConfig.confirmationOfDelivery)) and (JsPath \ "notificationUrl").readNullable[String] ) (MessageRequest.apply _) implicit val messageRequestFormatter: OFormat[MessageRequest] = OFormat(messageRequestReads, Json.writes[MessageRequest])