Skip to content

Commit

Permalink
Merge pull request #46 from hmrc/APID-208
Browse files Browse the repository at this point in the history
APID-208 - Set defaults to addressing fields
  • Loading branch information
sivaprakashiv authored Jul 6, 2021
2 parents 15d1d18 + 3f25ab2 commit bec5a9d
Show file tree
Hide file tree
Showing 10 changed files with 149 additions and 49 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |

Expand All @@ -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` |

Expand Down
32 changes: 32 additions & 0 deletions app/uk/gov/hmrc/apiplatformoutboundsoap/GlobalContext.scala
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,8 @@ 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("")
val confirmationOfDelivery: Boolean = config.getOptional[Boolean]("confirmationOfDelivery").getOrElse(false)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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()
}
}
16 changes: 11 additions & 5 deletions app/uk/gov/hmrc/apiplatformoutboundsoap/models/JsonFormats.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,18 @@ package uk.gov.hmrc.apiplatformoutboundsoap.models

import play.api.libs.functional.syntax._
import play.api.libs.json.{JsPath, Json, OFormat, Reads}
import uk.gov.hmrc.apiplatformoutboundsoap.config.AppConfig

object JsonFormats {

import uk.gov.hmrc.apiplatformoutboundsoap.GlobalContext.injector
val appConfig: AppConfig = injector.instanceOf[AppConfig]

object JsonFormats {
val addressingReads: Reads[Addressing] = (
(JsPath \ "from").readNullable[String] and
(JsPath \ "from").read[String].orElse(Reads.pure(appConfig.addressingFrom)) and
(JsPath \ "to").read[String] and
(JsPath \ "replyTo").read[String].orElse(Reads.pure("TBC")) and
(JsPath \ "faultTo").readNullable[String] 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 _)
Expand All @@ -36,8 +41,9 @@ 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)) and
(JsPath \ "notificationUrl").readNullable[String]
) (MessageRequest.apply _)
implicit val messageRequestFormatter: OFormat[MessageRequest] = OFormat(messageRequestReads, Json.writes[MessageRequest])
}

Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ 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")
}
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand Down
2 changes: 2 additions & 0 deletions conf/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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.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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +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.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._
Expand All @@ -39,18 +42,23 @@ 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 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")
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" -> "<IE4N03>example</IE4N03>", "addressing" -> addressingJson)
Expand All @@ -77,7 +85,33 @@ class OutboundControllerSpec extends AnyWordSpec with Matchers with GuiceOneAppP
messageCaptor.getValue.wsdlOperation shouldBe "theOp"
messageCaptor.getValue.messageBody shouldBe "<IE4N03>example</IE4N03>"
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 {
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" -> "<IE4N03>example</IE4N03>", "addressing" ->
Json.obj("messageId" -> "some msg id", "to" -> "who it is to", "from" -> "from"))))

status(result) shouldBe OK
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 {
when(outboundServiceMock.sendMessage(*)).thenReturn(successful(outboundSoapMessage))

val result: Future[Result] = underTest.message()(fakeRequest.withBody(Json.obj("wsdlUrl" -> "http://example.com/wsdl",
"wsdlOperation" -> "theOp", "messageBody" -> "<IE4N03>example</IE4N03>", "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 is missing wsdlUrl field" in new Setup {
Expand Down Expand Up @@ -112,18 +146,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" -> "<IE4N03>example</IE4N03>", "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))

Expand All @@ -136,15 +158,15 @@ 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))

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 {
Expand Down
Loading

0 comments on commit bec5a9d

Please sign in to comment.