diff --git a/app/uk/gov/hmrc/apiplatformoutboundsoap/services/OutboundService.scala b/app/uk/gov/hmrc/apiplatformoutboundsoap/services/OutboundService.scala index 39286e2..005cec4 100644 --- a/app/uk/gov/hmrc/apiplatformoutboundsoap/services/OutboundService.scala +++ b/app/uk/gov/hmrc/apiplatformoutboundsoap/services/OutboundService.scala @@ -21,7 +21,6 @@ import java.util.UUID import javax.inject.{Inject, Singleton} import javax.wsdl._ import javax.wsdl.extensions.soap12.SOAP12Address -import javax.wsdl.xml.WSDLReader import javax.xml.namespace.QName import scala.concurrent.{ExecutionContext, Future} import scala.jdk.CollectionConverters._ @@ -32,7 +31,6 @@ import org.apache.axiom.om.util.AXIOMUtil.stringToOM import org.apache.axiom.soap.SOAPEnvelope import org.apache.axis2.addressing.AddressingConstants.Final.{WSAW_NAMESPACE, WSA_NAMESPACE} import org.apache.axis2.addressing.AddressingConstants._ -import org.apache.axis2.wsdl.WSDLUtil import org.apache.pekko.Done import org.apache.pekko.stream.Materializer import org.apache.pekko.stream.scaladsl.Sink @@ -55,6 +53,7 @@ class OutboundService @Inject() ( wsSecurityService: WsSecurityService, outboundMessageRepository: OutboundMessageRepository, notificationCallbackConnector: NotificationCallbackConnector, + wsdlParser: WsdlParser, appConfig: AppConfig, cache: AsyncCacheApi )(implicit val ec: ExecutionContext, @@ -188,7 +187,7 @@ class OutboundService @Inject() ( private def buildSoapRequest(message: MessageRequest): Future[SoapRequest] = { cache.getOrElseUpdate[Definition](message.wsdlUrl, appConfig.cacheDuration) { - parseWsdl(message.wsdlUrl) + wsdlParser.parseWsdl(message.wsdlUrl) } map { wsdlDefinition: Definition => val portType = wsdlDefinition.getAllPortTypes.asScala.values.head.asInstanceOf[PortType] val operation: Operation = portType.getOperations.asScala.map(_.asInstanceOf[Operation]) @@ -208,12 +207,6 @@ class OutboundService @Inject() ( } } - private def parseWsdl(wsdlUrl: String): Future[Definition] = { - val reader: WSDLReader = WSDLUtil.newWSDLReaderWithPopulatedExtensionRegistry - reader.setFeature("javax.wsdl.importDocuments", true) - Future.successful(reader.readWSDL(wsdlUrl)) - } - private def addHeaders(message: MessageRequest, operation: Operation, envelope: SOAPEnvelope): Unit = { val wsaNs: OMNamespace = envelope.declareNamespace(WSA_NAMESPACE, "wsa") addSoapAction(operation, wsaNs, envelope) diff --git a/app/uk/gov/hmrc/apiplatformoutboundsoap/services/WsdlParser.scala b/app/uk/gov/hmrc/apiplatformoutboundsoap/services/WsdlParser.scala new file mode 100644 index 0000000..0114b35 --- /dev/null +++ b/app/uk/gov/hmrc/apiplatformoutboundsoap/services/WsdlParser.scala @@ -0,0 +1,37 @@ +/* + * Copyright 2024 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.services + +import javax.wsdl.Definition +import javax.wsdl.xml.WSDLReader +import scala.concurrent.Future + +import org.apache.axis2.wsdl.WSDLUtil + +// Turned off coverage for this as it was just one line and no need to test WSDLUtil code as it is not +// ours. However if this ever expands in future, tests will be needed and scoverage ignore removed. +// $COVERAGE-OFF$ +class WsdlParser { + + def parseWsdl(wsdlUrl: String): Future[Definition] = { + val reader: WSDLReader = WSDLUtil.newWSDLReaderWithPopulatedExtensionRegistry + reader.setFeature("javax.wsdl.importDocuments", true) + Future.successful(reader.readWSDL(wsdlUrl)) + } + +} +// $COVERAGE-ON$ diff --git a/test/uk/gov/hmrc/apiplatformoutboundsoap/services/OutboundServiceSpec.scala b/test/uk/gov/hmrc/apiplatformoutboundsoap/services/OutboundServiceSpec.scala index 13f696f..1370860 100644 --- a/test/uk/gov/hmrc/apiplatformoutboundsoap/services/OutboundServiceSpec.scala +++ b/test/uk/gov/hmrc/apiplatformoutboundsoap/services/OutboundServiceSpec.scala @@ -20,13 +20,15 @@ import java.time.Instant import java.util.UUID import java.util.UUID.randomUUID import javax.wsdl.WSDLException +import javax.wsdl.xml.WSDLReader import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future -import scala.concurrent.Future.successful +import scala.concurrent.Future.{failed, successful} import scala.concurrent.duration.Duration import com.mongodb.client.result.InsertOneResult import org.apache.axiom.soap.SOAPEnvelope +import org.apache.axis2.wsdl.WSDLUtil import org.apache.pekko.stream.Materializer import org.apache.pekko.stream.scaladsl.Source.{fromIterator, single} import org.mockito.{ArgumentCaptor, ArgumentMatchersSugar, MockitoSugar} @@ -60,6 +62,7 @@ class OutboundServiceSpec extends AnyWordSpec with Matchers with GuiceOneAppPerS val outboundMessageRepositoryMock: OutboundMessageRepository = mock[OutboundMessageRepository] val wsSecurityServiceMock: WsSecurityService = mock[WsSecurityService] val notificationCallbackConnectorMock: NotificationCallbackConnector = mock[NotificationCallbackConnector] + val wsdlParser: WsdlParser = mock[WsdlParser] val appConfigMock: AppConfig = mock[AppConfig] val cacheSpy: AsyncCacheApi = spy[AsyncCacheApi](cache) val httpStatus: Int = 200 @@ -75,8 +78,14 @@ class OutboundServiceSpec extends AnyWordSpec with Matchers with GuiceOneAppPerS when(appConfigMock.proxyRequiredForThisEnvironment).thenReturn(false) + val reader: WSDLReader = WSDLUtil.newWSDLReaderWithPopulatedExtensionRegistry + reader.setFeature("javax.wsdl.importDocuments", true) + val definition = reader.readWSDL("test/resources/definitions/CCN2.Service.Customs.Default.ICS.RiskAnalysisOrchestrationBAS_1.0.0_CCN2_1.0.0.wsdl") + val definitionUrlResolver = reader.readWSDL("test/resources/definitions/CCN2.Service.Customs.Default.ICS.RiskAnalysisOrchestrationBAS_1.0.0_CCN2_URLResolver.wsdl") + when(wsdlParser.parseWsdl(*)).thenReturn(successful(definition)) + val underTest: OutboundService = - new OutboundService(outboundConnectorMock, wsSecurityServiceMock, outboundMessageRepositoryMock, notificationCallbackConnectorMock, appConfigMock, cacheSpy) { + new OutboundService(outboundConnectorMock, wsSecurityServiceMock, outboundMessageRepositoryMock, notificationCallbackConnectorMock, wsdlParser, appConfigMock, cacheSpy) { override def now: Instant = expectedInstantNow override def randomUUID: UUID = expectedGlobalId @@ -312,6 +321,7 @@ class OutboundServiceSpec extends AnyWordSpec with Matchers with GuiceOneAppPerS } "resolve destination url not having port when sending the SOAP envelope returned from the security service to the connector" in new Setup { + when(wsdlParser.parseWsdl(*)).thenReturn(successful(definitionUrlResolver)) when(wsSecurityServiceMock.addUsernameToken(*)).thenReturn(expectedSoapEnvelope()) when(outboundMessageRepositoryMock.persist(*)).thenReturn(Future(InsertOneResult.acknowledged(BsonNumber(1)))) val messageCaptor: ArgumentCaptor[SoapRequest] = ArgumentCaptor.forClass(classOf[SoapRequest]) @@ -418,12 +428,13 @@ class OutboundServiceSpec extends AnyWordSpec with Matchers with GuiceOneAppPerS "fail when the given WSDL does not exist" in new Setup { when(wsSecurityServiceMock.addUsernameToken(*)).thenReturn(expectedSoapEnvelope()) when(outboundConnectorMock.postMessage(*, *)).thenReturn(successful(expectedStatus)) + when(wsdlParser.parseWsdl(*)).thenReturn(failed(new WSDLException(WSDLException.INVALID_WSDL, "This file was not found: http://localhost:3001/"))) val exception: WSDLException = intercept[WSDLException] { - await(underTest.sendMessage(messageRequestFullAddressing.copy(wsdlUrl = "https://github.com/hmrc/api-platform-outbound-soap/missing"))) + await(underTest.sendMessage(messageRequestFullAddressing.copy(wsdlUrl = "http://localhost:3001/"))) } - exception.getMessage should include("This file was not found: https://github.com/hmrc/api-platform-outbound-soap/missing") + exception.getMessage should include("This file was not found: http://localhost:3001/") } "fail when the addressing.to field is empty" in new Setup {