From 6725f31414f1aac9700ab7474caa993a7001e748 Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Sat, 19 Oct 2024 14:23:59 +0100 Subject: [PATCH] feat(soap): adds additional script method and configuration property to return a fault. --- .../script/MutableResponseBehaviour.kt | 11 ++- .../script/ReadWriteResponseBehaviourImpl.kt | 19 +++-- .../imposter/script/ResponseBehaviour.kt | 11 ++- docs/soap_plugin.md | 12 +++- .../http/OpenApiResponseBehaviourFactory.kt | 2 +- .../imposter/plugin/soap/SoapPluginImpl.kt | 11 +-- .../plugin/soap/config/SoapPluginConfig.kt | 5 +- .../soap/config/SoapPluginResourceConfig.kt | 10 ++- .../plugin/soap/config/SoapResponseConfig.kt | 62 ++++++++++++++++ .../soap/http/SoapResponseBehaviourFactory.kt | 70 +++++++++++++++++++ .../imposter/plugin/soap/FaultExampleTest.kt | 62 +++++++++++++++- .../fault-example/imposter-config.yaml | 21 ++++++ 12 files changed, 277 insertions(+), 19 deletions(-) create mode 100644 mock/soap/src/main/java/io/gatehill/imposter/plugin/soap/config/SoapResponseConfig.kt create mode 100644 mock/soap/src/main/java/io/gatehill/imposter/plugin/soap/http/SoapResponseBehaviourFactory.kt diff --git a/core/api/src/main/java/io/gatehill/imposter/script/MutableResponseBehaviour.kt b/core/api/src/main/java/io/gatehill/imposter/script/MutableResponseBehaviour.kt index bf337c9dd..be7b6b203 100644 --- a/core/api/src/main/java/io/gatehill/imposter/script/MutableResponseBehaviour.kt +++ b/core/api/src/main/java/io/gatehill/imposter/script/MutableResponseBehaviour.kt @@ -54,7 +54,6 @@ interface MutableResponseBehaviour { @Deprecated("Use withContent(String) instead", replaceWith = ReplaceWith("withContent")) fun withData(responseData: String?) = withContent(responseData) fun template(): MutableResponseBehaviour - fun withExampleName(exampleName: String): MutableResponseBehaviour fun usingDefaultBehaviour(): MutableResponseBehaviour fun skipDefaultBehaviour(): MutableResponseBehaviour fun continueToNext(): MutableResponseBehaviour @@ -69,6 +68,16 @@ interface MutableResponseBehaviour { fun withFailure(failureType: String): MutableResponseBehaviour fun withFailureType(failureType: FailureSimulationType?): MutableResponseBehaviour + /** + * Only supported for OpenAPI plugin. + */ + fun withExampleName(exampleName: String): MutableResponseBehaviour + + /** + * Only supported for SOAP plugin. + */ + fun withSoapFault(): MutableResponseBehaviour + @Deprecated("Use skipDefaultBehaviour() instead", ReplaceWith("skipDefaultBehaviour()")) fun immediately(): MutableResponseBehaviour } diff --git a/core/api/src/main/java/io/gatehill/imposter/script/ReadWriteResponseBehaviourImpl.kt b/core/api/src/main/java/io/gatehill/imposter/script/ReadWriteResponseBehaviourImpl.kt index 24d6fd46a..95712689e 100644 --- a/core/api/src/main/java/io/gatehill/imposter/script/ReadWriteResponseBehaviourImpl.kt +++ b/core/api/src/main/java/io/gatehill/imposter/script/ReadWriteResponseBehaviourImpl.kt @@ -51,11 +51,12 @@ open class ReadWriteResponseBehaviourImpl : ReadWriteResponseBehaviour { override var responseFile: String? = null override var content: String? = null override var isTemplate = false - override var exampleName: String? = null override val responseHeaders: MutableMap = mutableMapOf() private var behaviourConfigured = false override var performanceSimulation: PerformanceSimulationConfig? = null override var failureType: FailureSimulationType? = null + override var exampleName: String? = null + override var soapFault: Boolean = false override fun template(): MutableResponseBehaviour { isTemplate = true @@ -108,11 +109,6 @@ open class ReadWriteResponseBehaviourImpl : ReadWriteResponseBehaviour { return this } - override fun withExampleName(exampleName: String): MutableResponseBehaviour { - this.exampleName = exampleName - return this - } - /** * Use the plugin's default behaviour to respond * @@ -205,4 +201,15 @@ open class ReadWriteResponseBehaviourImpl : ReadWriteResponseBehaviour { this.failureType = failureType return this } + + override fun withExampleName(exampleName: String): MutableResponseBehaviour { + this.exampleName = exampleName + return this + } + + override fun withSoapFault(): MutableResponseBehaviour { + this.soapFault = true + withStatusCode(500) + return this + } } diff --git a/core/api/src/main/java/io/gatehill/imposter/script/ResponseBehaviour.kt b/core/api/src/main/java/io/gatehill/imposter/script/ResponseBehaviour.kt index d3c6e797d..67b74102b 100644 --- a/core/api/src/main/java/io/gatehill/imposter/script/ResponseBehaviour.kt +++ b/core/api/src/main/java/io/gatehill/imposter/script/ResponseBehaviour.kt @@ -51,8 +51,17 @@ interface ResponseBehaviour { val responseFile: String? val content: String? val isTemplate: Boolean - val exampleName: String? val behaviourType: ResponseBehaviourType? val performanceSimulation: PerformanceSimulationConfig? val failureType: FailureSimulationType? + + /** + * Only supported for OpenAPI plugin. + */ + val exampleName: String? + + /** + * Only supported for SOAP plugin. + */ + val soapFault: Boolean } diff --git a/docs/soap_plugin.md b/docs/soap_plugin.md index becf3ed78..fb1191ae3 100644 --- a/docs/soap_plugin.md +++ b/docs/soap_plugin.md @@ -225,7 +225,13 @@ HTTP/1.1 400 Bad Request ## Returning fault messages -If your WSDL document defines a `fault`, then Imposter can generate a sample response from its type. To return a fault, set the response status code to 500. +If your WSDL document defines a `fault`, then Imposter can generate a sample response from its type. + +To return a fault you can: + +1. set the response status code to `500`, or +2. set the `response.soapFault` configuration property to `true`, or +3. use the `respond().withSoapFault()` script function ### Example configuration to respond with a fault @@ -241,7 +247,8 @@ resources: ``` > **Tip** -> Use conditional matching with resources, to only return a fault in particular circumstances. +> Use conditional matching with resources, to only return a fault in particular circumstances. +> See [fault-example](https://github.com/outofcoffee/imposter/blob/main/examples/soap/fault-example) for an example of how to do this. ## Scripted responses (advanced) @@ -312,6 +319,7 @@ Now, `example.groovy` can control the responses, such as: - [conditional-example](https://github.com/outofcoffee/imposter/blob/main/examples/soap/conditional-example) - [scripted-example](https://github.com/outofcoffee/imposter/blob/main/examples/soap/scripted-example) +- [fault-example](https://github.com/outofcoffee/imposter/blob/main/examples/soap/fault-example) ### Configuration reference diff --git a/mock/openapi/src/main/java/io/gatehill/imposter/plugin/openapi/http/OpenApiResponseBehaviourFactory.kt b/mock/openapi/src/main/java/io/gatehill/imposter/plugin/openapi/http/OpenApiResponseBehaviourFactory.kt index f94f8a59c..38d229218 100644 --- a/mock/openapi/src/main/java/io/gatehill/imposter/plugin/openapi/http/OpenApiResponseBehaviourFactory.kt +++ b/mock/openapi/src/main/java/io/gatehill/imposter/plugin/openapi/http/OpenApiResponseBehaviourFactory.kt @@ -66,4 +66,4 @@ class OpenApiResponseBehaviourFactory : DefaultResponseBehaviourFactory() { responseBehaviour.withExampleName(configExampleName!!) } } -} \ No newline at end of file +} diff --git a/mock/soap/src/main/java/io/gatehill/imposter/plugin/soap/SoapPluginImpl.kt b/mock/soap/src/main/java/io/gatehill/imposter/plugin/soap/SoapPluginImpl.kt index 198f0d20f..cb6d453e3 100644 --- a/mock/soap/src/main/java/io/gatehill/imposter/plugin/soap/SoapPluginImpl.kt +++ b/mock/soap/src/main/java/io/gatehill/imposter/plugin/soap/SoapPluginImpl.kt @@ -43,7 +43,6 @@ package io.gatehill.imposter.plugin.soap import io.gatehill.imposter.ImposterConfig -import io.gatehill.imposter.http.DefaultResponseBehaviourFactory import io.gatehill.imposter.http.DefaultStatusCodeFactory import io.gatehill.imposter.http.HttpExchange import io.gatehill.imposter.http.HttpMethod @@ -53,6 +52,7 @@ import io.gatehill.imposter.plugin.RequireModules import io.gatehill.imposter.plugin.config.ConfiguredPlugin import io.gatehill.imposter.plugin.config.resource.BasicResourceConfig import io.gatehill.imposter.plugin.soap.config.SoapPluginConfig +import io.gatehill.imposter.plugin.soap.http.SoapResponseBehaviourFactory import io.gatehill.imposter.plugin.soap.model.BindingType import io.gatehill.imposter.plugin.soap.model.MessageBodyHolder import io.gatehill.imposter.plugin.soap.model.OperationMessage @@ -97,6 +97,7 @@ class SoapPluginImpl @Inject constructor( private val responseService: ResponseService, private val responseRoutingService: ResponseRoutingService, private val soapExampleService: SoapExampleService, + private val soapResponseBehaviourFactory: SoapResponseBehaviourFactory, ) : ConfiguredPlugin( vertx, imposterConfig ) { @@ -224,7 +225,6 @@ class SoapPluginImpl @Inject constructor( soapAction: String?, ): CompletableFuture { val statusCodeFactory = DefaultStatusCodeFactory.instance - val responseBehaviourFactory = DefaultResponseBehaviourFactory.instance val resourceConfig = httpExchange.get(ResourceUtil.RESOURCE_CONFIG_KEY) val defaultBehaviourHandler: DefaultBehaviourHandler = { responseBehaviour: ResponseBehaviour -> @@ -290,7 +290,7 @@ class SoapPluginImpl @Inject constructor( httpExchange, context, statusCodeFactory, - responseBehaviourFactory, + soapResponseBehaviourFactory, defaultBehaviourHandler, ) } @@ -299,8 +299,9 @@ class SoapPluginImpl @Inject constructor( responseBehaviour: ResponseBehaviour, operation: WsdlOperation ): OperationMessage? { - return when (responseBehaviour.statusCode) { - HttpUtil.HTTP_INTERNAL_ERROR -> operation.faultRef + return when { + responseBehaviour.soapFault -> operation.faultRef + responseBehaviour.statusCode == HttpUtil.HTTP_INTERNAL_ERROR -> operation.faultRef else -> operation.outputRef } } diff --git a/mock/soap/src/main/java/io/gatehill/imposter/plugin/soap/config/SoapPluginConfig.kt b/mock/soap/src/main/java/io/gatehill/imposter/plugin/soap/config/SoapPluginConfig.kt index ac51f146c..70765d6b5 100644 --- a/mock/soap/src/main/java/io/gatehill/imposter/plugin/soap/config/SoapPluginConfig.kt +++ b/mock/soap/src/main/java/io/gatehill/imposter/plugin/soap/config/SoapPluginConfig.kt @@ -62,7 +62,10 @@ class SoapPluginConfig : CommonPluginConfig(), ResourcesHolder? = null + + @JsonProperty("response") + override val responseConfig = SoapResponseConfig() + @get:JsonIgnore override val resourceId by lazy { UUID.randomUUID().toString() } diff --git a/mock/soap/src/main/java/io/gatehill/imposter/plugin/soap/config/SoapResponseConfig.kt b/mock/soap/src/main/java/io/gatehill/imposter/plugin/soap/config/SoapResponseConfig.kt new file mode 100644 index 000000000..6ec977e4e --- /dev/null +++ b/mock/soap/src/main/java/io/gatehill/imposter/plugin/soap/config/SoapResponseConfig.kt @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2016-2021. + * + * This file is part of Imposter. + * + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, as + * defined below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of rights + * under the License will not include, and the License does not grant to + * you, the right to Sell the Software. + * + * For purposes of the foregoing, "Sell" means practicing any or all of + * the rights granted to you under the License to provide to third parties, + * for a fee or other consideration (including without limitation fees for + * hosting or consulting/support services related to the Software), a + * product or service whose value derives, entirely or substantially, from + * the functionality of the Software. Any license notice or attribution + * required by the License must also include this Commons Clause License + * Condition notice. + * + * Software: Imposter + * + * License: GNU Lesser General Public License version 3 + * + * Licensor: Peter Cornish + * + * Imposter is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Imposter is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Imposter. If not, see . + */ +package io.gatehill.imposter.plugin.soap.config + +import io.gatehill.imposter.plugin.config.resource.ResponseConfig + +/** + * Extends the base response configuration with items specific + * to the OpenAPI plugin. + * + * @author Pete Cornish + */ +class SoapResponseConfig : ResponseConfig() { + val soapFault: Boolean? = null + + override fun hasConfiguration(): Boolean = + super.hasConfiguration() || null != soapFault + + override fun toString(): String { + return "OpenApiResponseConfig(parent=${super.toString()}, soapFault=$soapFault)" + } +} diff --git a/mock/soap/src/main/java/io/gatehill/imposter/plugin/soap/http/SoapResponseBehaviourFactory.kt b/mock/soap/src/main/java/io/gatehill/imposter/plugin/soap/http/SoapResponseBehaviourFactory.kt new file mode 100644 index 000000000..3d3712317 --- /dev/null +++ b/mock/soap/src/main/java/io/gatehill/imposter/plugin/soap/http/SoapResponseBehaviourFactory.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2016-2021. + * + * This file is part of Imposter. + * + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, as + * defined below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of rights + * under the License will not include, and the License does not grant to + * you, the right to Sell the Software. + * + * For purposes of the foregoing, "Sell" means practicing any or all of + * the rights granted to you under the License to provide to third parties, + * for a fee or other consideration (including without limitation fees for + * hosting or consulting/support services related to the Software), a + * product or service whose value derives, entirely or substantially, from + * the functionality of the Software. Any license notice or attribution + * required by the License must also include this Commons Clause License + * Condition notice. + * + * Software: Imposter + * + * License: GNU Lesser General Public License version 3 + * + * Licensor: Peter Cornish + * + * Imposter is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Imposter is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Imposter. If not, see . + */ +package io.gatehill.imposter.plugin.soap.http + +import io.gatehill.imposter.http.DefaultResponseBehaviourFactory +import io.gatehill.imposter.plugin.config.resource.BasicResourceConfig +import io.gatehill.imposter.plugin.soap.config.SoapResponseConfig +import io.gatehill.imposter.script.ReadWriteResponseBehaviour + +/** + * Extends base response behaviour population with specific + * SOAP plugin configuration. + * + * @author Pete Cornish + */ +class SoapResponseBehaviourFactory : DefaultResponseBehaviourFactory() { + override fun populate( + statusCode: Int, + resourceConfig: BasicResourceConfig, + responseBehaviour: ReadWriteResponseBehaviour + ) { + if ((resourceConfig.responseConfig as SoapResponseConfig).soapFault == true) { + responseBehaviour.withSoapFault() + } + + // invoke superclass after calling `withSoapFault` as it + // overrides the status code to 500 + super.populate(statusCode, resourceConfig, responseBehaviour) + } +} diff --git a/mock/soap/src/test/java/io/gatehill/imposter/plugin/soap/FaultExampleTest.kt b/mock/soap/src/test/java/io/gatehill/imposter/plugin/soap/FaultExampleTest.kt index 1085882a9..ec03f6f09 100644 --- a/mock/soap/src/test/java/io/gatehill/imposter/plugin/soap/FaultExampleTest.kt +++ b/mock/soap/src/test/java/io/gatehill/imposter/plugin/soap/FaultExampleTest.kt @@ -70,7 +70,7 @@ class FaultExampleTest : BaseVerticleTest() { } @Test - fun `respond with a fault generated from the schema`() { + fun `respond with a fault generated from the schema if status is 500`() { val getPetByIdEnv = SoapUtil.wrapInEnv( """ @@ -98,4 +98,64 @@ class FaultExampleTest : BaseVerticleTest() { ) ) } + + @Test + fun `respond with a fault generated from the schema if response configuration set`() { + val getPetByIdEnv = SoapUtil.wrapInEnv( + """ + + 99 + +""".trim(), soapEnvNamespace + ) + + RestAssured.given() + .log().ifValidationFails() + .accept(soapContentType) + .contentType(soapContentType) + .`when`() + .body(getPetByIdEnv) + .post("/pets/") + .then() + .log().ifValidationFails() + .statusCode(HttpUtil.HTTP_INTERNAL_ERROR) + .body( + Matchers.allOf( + Matchers.containsString("Envelope"), + Matchers.containsString("getPetFault"), + Matchers.containsString("code"), + Matchers.containsString("description"), + ) + ) + } + + @Test + fun `respond with a fault generated from the schema if script function called`() { + val getPetByIdEnv = SoapUtil.wrapInEnv( + """ + + 100 + +""".trim(), soapEnvNamespace + ) + + RestAssured.given() + .log().ifValidationFails() + .accept(soapContentType) + .contentType(soapContentType) + .`when`() + .body(getPetByIdEnv) + .post("/pets/") + .then() + .log().ifValidationFails() + .statusCode(HttpUtil.HTTP_INTERNAL_ERROR) + .body( + Matchers.allOf( + Matchers.containsString("Envelope"), + Matchers.containsString("getPetFault"), + Matchers.containsString("code"), + Matchers.containsString("description"), + ) + ) + } } diff --git a/mock/soap/src/test/resources/fault-example/imposter-config.yaml b/mock/soap/src/test/resources/fault-example/imposter-config.yaml index 4acb03d90..461e28975 100644 --- a/mock/soap/src/test/resources/fault-example/imposter-config.yaml +++ b/mock/soap/src/test/resources/fault-example/imposter-config.yaml @@ -6,3 +6,24 @@ resources: operation: getPetById response: statusCode: 500 + + - binding: SoapBinding + operation: getPetById + requestBody: + xPath: //pets:id + value: 99 + response: + soapFault: true + + - binding: SoapBinding + operation: getPetById + requestBody: + xPath: //pets:id + value: 100 + steps: + - type: script + code: respond().withSoapFault() + +system: + xmlNamespaces: + pets: "urn:com:example:petstore"