Skip to content

Commit

Permalink
Merge pull request #33 from hmrc/API-7262
Browse files Browse the repository at this point in the history
API-7262 - Implement Acknowledge forwarding
  • Loading branch information
mi-akram authored Sep 27, 2024
2 parents 432a63a + 2bd2e0b commit fa7632c
Show file tree
Hide file tree
Showing 15 changed files with 474 additions and 28 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2023 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.apiplatforminboundsoap.config

import javax.inject.{Inject, Provider, Singleton}

import play.api.inject.{Binding, Module}
import play.api.{Configuration, Environment}
import uk.gov.hmrc.play.bootstrap.config.ServicesConfig

import uk.gov.hmrc.apiplatforminboundsoap.connectors.ApiPlatformOutboundSoapConnector

class ConfigurationModule extends Module {

override def bindings(environment: Environment, configuration: Configuration): List[Binding[_]] = {

List(
bind[ApiPlatformOutboundSoapConnector.Config].toProvider[ApiPlatformOutboundSoapConnectorConfigProvider]
)
}
}

@Singleton
class ApiPlatformOutboundSoapConnectorConfigProvider @Inject() (val configuration: Configuration)
extends ServicesConfig(configuration)
with Provider[ApiPlatformOutboundSoapConnector.Config] {

override def get(): ApiPlatformOutboundSoapConnector.Config = {
val url = baseUrl("api-platform-outbound-soap")
ApiPlatformOutboundSoapConnector.Config(url)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright 2023 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.apiplatforminboundsoap.connectors

import java.net.URL
import javax.inject.{Inject, Singleton}
import scala.concurrent.{ExecutionContext, Future}
import scala.util.control.NonFatal
import scala.xml.NodeSeq

import play.api.Logging
import play.api.http.Status
import uk.gov.hmrc.http.HttpReads.Implicits._
import uk.gov.hmrc.http.client.HttpClientV2
import uk.gov.hmrc.http.{HeaderCarrier, HttpResponse, UpstreamErrorResponse}

import uk.gov.hmrc.apiplatforminboundsoap.models.{SendFail, SendResult, SendSuccess}
import uk.gov.hmrc.apiplatforminboundsoap.xml.XmlHelper

object ApiPlatformOutboundSoapConnector {
case class Config(baseUrl: String)
}

@Singleton
class ApiPlatformOutboundSoapConnector @Inject() (httpClientV2: HttpClientV2, appConfig: ApiPlatformOutboundSoapConnector.Config)(implicit ec: ExecutionContext)
extends Logging with XmlHelper {

def postMessage(soapRequest: NodeSeq)(implicit hc: HeaderCarrier): Future[SendResult] = {

val newHeaders: Seq[(String, String)] = List("x-soap-action" -> getSoapAction(soapRequest).getOrElse(""))

postHttpRequest(soapRequest, appConfig.baseUrl, newHeaders).map {
case Right(_) => SendSuccess
case Left(UpstreamErrorResponse(_, statusCode, _, _)) =>
logger.warn(s"Sending message failed with status code $statusCode")
SendFail(statusCode)
}
.recoverWith {
case NonFatal(e) =>
logger.warn(s"NonFatal error ${e.getMessage} while forwarding message", e)
Future.successful(SendFail(Status.INTERNAL_SERVER_ERROR))
}
}

private def postHttpRequest(soapEnvelope: NodeSeq, baseUrl: String, headers: Seq[(String, String)])(implicit hc: HeaderCarrier): Future[Either[UpstreamErrorResponse, HttpResponse]] = {
httpClientV2.post(new URL(baseUrl))
.withBody(soapEnvelope)
.transform(_.addHttpHeaders(headers: _*))
.execute[Either[UpstreamErrorResponse, HttpResponse]]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import uk.gov.hmrc.apiplatforminboundsoap.config.AppConfig
import uk.gov.hmrc.apiplatforminboundsoap.models.{SendFail, SendResult, SendSuccess, SoapRequest}

@Singleton
class InboundConnector @Inject() (httpClientV2: HttpClientV2, appConfig: AppConfig)(implicit ec: ExecutionContext) extends Logging {
class ImportControlInboundSoapConnector @Inject() (httpClientV2: HttpClientV2, appConfig: AppConfig)(implicit ec: ExecutionContext) extends Logging {

def postMessage(soapRequest: SoapRequest, headers: Seq[(String, String)])(implicit hc: HeaderCarrier): Future[SendResult] = {
postHttpRequest(soapRequest, headers).map {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,30 @@
package uk.gov.hmrc.apiplatforminboundsoap.controllers

import javax.inject.{Inject, Singleton}
import scala.concurrent.Future
import scala.concurrent.{ExecutionContext, Future}
import scala.xml.NodeSeq

import play.api.mvc.{Action, AnyContent, ControllerComponents}
import play.api.mvc.{Action, ControllerComponents}
import uk.gov.hmrc.play.bootstrap.backend.controller.BackendController

import uk.gov.hmrc.apiplatforminboundsoap.connectors.ApiPlatformOutboundSoapConnector
import uk.gov.hmrc.apiplatforminboundsoap.controllers.actionBuilders.VerifyJwtTokenAction
import uk.gov.hmrc.apiplatforminboundsoap.models.{SendFail, SendSuccess}

@Singleton()
class ConfirmationController @Inject() (cc: ControllerComponents, verifyJwtTokenAction: VerifyJwtTokenAction)
extends BackendController(cc) {
class ConfirmationController @Inject() (
apiPlatformOutboundSoapConnector: ApiPlatformOutboundSoapConnector,
cc: ControllerComponents,
verifyJwtTokenAction: VerifyJwtTokenAction
)(implicit ec: ExecutionContext
) extends BackendController(cc) {

def message(): Action[AnyContent] = (Action andThen verifyJwtTokenAction).async { implicit request =>
Future.successful(Ok)
def message(): Action[NodeSeq] = (Action andThen verifyJwtTokenAction).async(parse.xml) { implicit request =>
apiPlatformOutboundSoapConnector.postMessage(request.body) flatMap {
case SendSuccess =>
Future.successful(Ok.as("application/soap+xml"))
case SendFail(status) =>
Future.successful(new Status(status).as("application/soap+xml"))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@ import play.api.Logging
import uk.gov.hmrc.http.HeaderCarrier

import uk.gov.hmrc.apiplatforminboundsoap.config.AppConfig
import uk.gov.hmrc.apiplatforminboundsoap.connectors.InboundConnector
import uk.gov.hmrc.apiplatforminboundsoap.connectors.ImportControlInboundSoapConnector
import uk.gov.hmrc.apiplatforminboundsoap.models.{SendResult, SoapRequest}
import uk.gov.hmrc.apiplatforminboundsoap.xml.XmlHelper

@Singleton
class InboundMessageService @Inject() (appConfig: AppConfig, inboundConnector: InboundConnector) extends Logging with XmlHelper {
class InboundMessageService @Inject() (appConfig: AppConfig, importControlInboundSoapConnector: ImportControlInboundSoapConnector)
extends Logging with XmlHelper {

def processInboundMessage(soapRequest: NodeSeq, isTest: Boolean = false)(implicit hc: HeaderCarrier): Future[SendResult] = {
val newHeaders: Seq[(String, String)] = List(
Expand All @@ -41,6 +42,6 @@ class InboundMessageService @Inject() (appConfig: AppConfig, inboundConnector: I
)
val forwardUrl = if (isTest) appConfig.testForwardMessageUrl else appConfig.forwardMessageUrl

inboundConnector.postMessage(SoapRequest(soapRequest.text, forwardUrl), newHeaders)
importControlInboundSoapConnector.postMessage(SoapRequest(soapRequest.text, forwardUrl), newHeaders)
}
}
5 changes: 5 additions & 0 deletions conf/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ play.http.errorHandler = "uk.gov.hmrc.play.bootstrap.backend.http.JsonErrorHandl
# Play Modules
play.modules.enabled += "uk.gov.hmrc.apiplatforminboundsoap.config.InjectionModule"
play.modules.enabled += "uk.gov.hmrc.apiplatforminboundsoap.config.Module"
play.modules.enabled += "uk.gov.hmrc.apiplatforminboundsoap.config.ConfigurationModule"


# The application languages
Expand Down Expand Up @@ -64,6 +65,10 @@ microservice {
host = localhost
port = 8500
}
api-platform-outbound-soap {
host = localhost
port = 6703
}
}
}

17 changes: 17 additions & 0 deletions it/test/resources/acknowledgement-requests/cod_request.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:ccn2="http://ccn2.ec.eu/CCN2.Service.Platform.Acknowledgement.Schema">
<soap:Header xmlns:wsa="http://www.w3.org/2005/08/addressing">
<wsa:Action>CCN2.Service.Platform.AcknowledgementService/CoD</wsa:Action>
<wsa:From>
<wsa:Address>theEU</wsa:Address>
</wsa:From>
<wsa:RelatesTo RelationshipType="http://ccn2.ec.eu/addressing/ack">5d3a71e3-2a1a-43f1-a246-831dff41e9a1
</wsa:RelatesTo>
<wsa:MessageID>0316250e-0873-49bc-a74e-f6f5efa892c7</wsa:MessageID>
<wsa:To>HMRC</wsa:To>
</soap:Header>
<soap:Body>
<ccn2:CoD>
<ccn2:EventTimestamp>2021-03-10T09:30:10Z</ccn2:EventTimestamp>
</ccn2:CoD>
</soap:Body>
</soap:Envelope>
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright 2023 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.apiplatforminboundsoap.connectors

import scala.io.Source
import scala.xml.{Elem, XML}

import com.github.tomakehurst.wiremock.http.Fault
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import org.scalatestplus.play.guice.GuiceOneAppPerSuite

import play.api.Application
import play.api.inject.guice.GuiceApplicationBuilder
import play.api.test.Helpers._
import uk.gov.hmrc.http.HeaderCarrier
import uk.gov.hmrc.http.test.ExternalWireMockSupport

import uk.gov.hmrc.apiplatforminboundsoap.models.{SendFail, SendResult, SendSuccess}
import uk.gov.hmrc.apiplatforminboundsoap.stubs.ApiPlatformOutboundSoapStub
import uk.gov.hmrc.apiplatforminboundsoap.xml.XmlHelper

class ApiPlatformOutboundSoapConnectorISpec extends AnyWordSpec with Matchers with GuiceOneAppPerSuite
with ExternalWireMockSupport with ApiPlatformOutboundSoapStub with XmlHelper {
override implicit lazy val app: Application = appBuilder.build()
implicit val hc: HeaderCarrier = HeaderCarrier()

protected def appBuilder: GuiceApplicationBuilder =
new GuiceApplicationBuilder()
.configure(
"metrics.enabled" -> false,
"auditing.enabled" -> false,
"microservice.services.api-platform-outbound-soap.host" -> externalWireMockHost,
"microservice.services.api-platform-outbound-soap.port" -> externalWireMockPort
)

trait Setup {
val underTest: ApiPlatformOutboundSoapConnector = app.injector.instanceOf[ApiPlatformOutboundSoapConnector]

def readFromFile(fileName: String) = {
XML.load(Source.fromResource(fileName).bufferedReader())
}

val codRequestBody: Elem = readFromFile("acknowledgement-requests/cod_request.xml")

def forwardedHeaders(xmlBody: Elem) = Seq[(String, String)]("x-soap-action" -> getSoapAction(xmlBody).getOrElse(""))
val expectedHeaders = forwardedHeaders(codRequestBody)

}

"postMessage" should {

"return success status (for COD) when returned by the outbound soap service" in new Setup {
primeStubForSuccess(codRequestBody, OK)

val result: SendResult = await(underTest.postMessage(codRequestBody))

result shouldBe SendSuccess
verifyRequestBody(codRequestBody)
verifyHeader(expectedHeaders.head._1, expectedHeaders.head._2)
}

"return error status returned by the outbound soap service" in new Setup {
val expectedStatus: Int = INTERNAL_SERVER_ERROR
primeStubForSuccess(codRequestBody, expectedStatus)

val result: SendResult = await(underTest.postMessage(codRequestBody))

result shouldBe SendFail(expectedStatus)
verifyHeader(expectedHeaders.head._1, expectedHeaders.head._2)
}

"return error status when soap fault is returned by the outbound soap service" in new Setup {
val responseBody = "<Envelope><Body>foobar</Body></Envelope>"
Seq(Fault.CONNECTION_RESET_BY_PEER, Fault.EMPTY_RESPONSE, Fault.MALFORMED_RESPONSE_CHUNK, Fault.RANDOM_DATA_THEN_CLOSE) foreach { fault =>
primeStubForFault(codRequestBody, responseBody, fault)

val result: SendResult = await(underTest.postMessage(codRequestBody))

result shouldBe SendFail(INTERNAL_SERVER_ERROR)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import uk.gov.hmrc.http.test.ExternalWireMockSupport
import uk.gov.hmrc.apiplatforminboundsoap.models.{SendFail, SendResult, SendSuccess, SoapRequest}
import uk.gov.hmrc.apiplatforminboundsoap.support.ExternalServiceStub

class InboundConnectorISpec extends AnyWordSpec with Matchers with GuiceOneAppPerSuite with ExternalWireMockSupport with ExternalServiceStub {
class ImportControlInboundSoapConnectorISpec extends AnyWordSpec with Matchers with GuiceOneAppPerSuite with ExternalWireMockSupport with ExternalServiceStub {
override implicit lazy val app: Application = appBuilder.build()
implicit val hc: HeaderCarrier = HeaderCarrier()

Expand All @@ -42,8 +42,8 @@ class InboundConnectorISpec extends AnyWordSpec with Matchers with GuiceOneAppPe
)

trait Setup {
val headers: Seq[(String, String)] = List("Authorization" -> "Bearer value")
val underTest: InboundConnector = app.injector.instanceOf[InboundConnector]
val headers: Seq[(String, String)] = List("Authorization" -> "Bearer value")
val underTest: ImportControlInboundSoapConnector = app.injector.instanceOf[ImportControlInboundSoapConnector]
}

"postMessage" should {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ import uk.gov.hmrc.http.test.{ExternalWireMockSupport, HttpClientV2Support}

import uk.gov.hmrc.apiplatforminboundsoap.support.ExternalServiceStub

class PassThroughControllerISpec extends AnyWordSpecLike with Matchers with HttpClientV2Support with ExternalWireMockSupport with GuiceOneAppPerSuite with ExternalServiceStub {
class PassThroughControllerISpec extends AnyWordSpecLike with Matchers
with HttpClientV2Support with ExternalWireMockSupport with GuiceOneAppPerSuite with ExternalServiceStub {

override implicit lazy val app: Application = new GuiceApplicationBuilder()
.configure(
Expand Down
Loading

0 comments on commit fa7632c

Please sign in to comment.