Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(soap): adds support for returning SOAP faults #650

Merged
merged 5 commits into from
Oct 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, String> = 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
Expand Down Expand Up @@ -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
*
Expand Down Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
2 changes: 1 addition & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ response:
X-Custom-Header: foo
```
A few things to call out:
Some highlights:
- This endpoint will only be accessible via the `POST` HTTP method
- We've indicated that status code 201 should be returned
Expand Down
2 changes: 1 addition & 1 deletion docs/openapi_plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ definitions:
type: "string"
```

A few things to call out:
Some highlights:

* We’ve defined the endpoint `/pets` as expecting an HTTP GET request
* We’ve said it will produce JSON responses
Expand Down
2 changes: 1 addition & 1 deletion docs/rest_plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ We can configure different responses at multiple paths as follows:
response:
file: dogs.json

A few things to call out:
Some highlights:

* We’ve defined the endpoint `/cats` to return the contents of our sample JSON file; in other words an array of cats.
* We’ve also said that, because the response file is a JSON array, we want to allow querying of individual items by their ID, under the `/cats/{id}` endpoint.
Expand Down
80 changes: 58 additions & 22 deletions docs/soap_plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ This plugin will match the operation using a combination of:
* matching SOAPAction (if required)
* matching XML schema type of the root element within the request SOAP envelope body

Imposter will return the first response found that matches the above criteria. You can, of course, override the behaviour by setting the response body (see below).
Imposter will return the first response found that matches the above criteria. You can, of course, override the behaviour by setting the response body (see below) or status code.

Typically, you will use the configuration file `<something>-config.yaml` to customise the response, however, you can use the in-built script engine to gain further control of the response data, headers etc. (see below).
Typically, you will use the configuration file `<something>-config.yaml` to customise the response, however, you can use the in-built script engine to gain further control of the response data, headers etc.

## Example

Expand Down Expand Up @@ -88,7 +88,7 @@ In this example, we are using a WSDL file (`petstore.wsdl`) containing the follo
</description>
```

A few things to call out:
Some highlights:

* We’ve defined the service `PetService` at the SOAP endpoint `/pets/`
* We’ve said it has one operation: `getPetById`
Expand Down Expand Up @@ -223,6 +223,33 @@ $ curl -v -X POST http://localhost:8080/pets/ -H 'SOAPAction: invalid-pet-action
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 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

```yaml
plugin: soap
wsdlFile: service.wsdl

resources:
- binding: SoapBinding
operation: getPetById
response:
statusCode: 500
```

> **Tip**
> 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)

For more advanced scenarios, you can also control Imposter's responses using JavaScript or Groovy scripts.
Expand Down Expand Up @@ -258,32 +285,41 @@ response:

Now, `example.groovy` can control the responses, such as:

1. the content of a file to return
1. **the content of a file to return**

```groovy
respond().withFile('some-file.xml')
```
```groovy
respond().withFile('some-file.xml')
```

2. a literal string to return
2. **a literal string to return**

```groovy
respond().withContent('''<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://www.w3.org/2001/12/soap-envelope">
<env:Header/>
<env:Body>
<getPetByIdResponse xmlns="urn:com:example:petstore">
<id>3</id>
<name>Custom pet name</name>
</getPetByIdResponse>
</env:Body>
</env:Envelope>
''')
```
```groovy
respond().withContent('''<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://www.w3.org/2001/12/soap-envelope">
<env:Header/>
<env:Body>
<getPetByIdResponse xmlns="urn:com:example:petstore">
<id>3</id>
<name>Custom pet name</name>
</getPetByIdResponse>
</env:Body>
</env:Envelope>
''')
```

3. **a specific HTTP status code**

Setting the status code to 500 will trigger a fault message to be returned if one is defined within the WSDL document.

```groovy
respond().withStatusCode(500)
```

#### Examples
#### Scripting examples

- [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

Expand Down
68 changes: 68 additions & 0 deletions examples/soap/fault-example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Return a SOAP fault

This example returns a SOAP fault if the `id` in the request message is `10`.

Start mock server:

```bash
imposter up
```

Send request:

```bash
curl --data '<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
<env:Header/>
<env:Body>
<getPetByIdRequest xmlns="urn:com:example:petstore">
<id>10</id>
</getPetByIdRequest>
</env:Body>
</env:Envelope>' http://localhost:8080/pets/
```

Response:

```xml
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
<env:Header/>
<env:Body>
<urn:getPetFault xmlns:urn="urn:com:example:petstore">
<code>3</code>
<description>string</description>
</urn:getPetFault>
</env:Body>
</env:Envelope>
```

## Notes

This example uses conditional matching with resources to only return a fault under specific conditions.

If a non-matching `id` is sent in the request, the default response message is generated:

```bash
curl --data '<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
<env:Header/>
<env:Body>
<getPetByIdRequest xmlns="urn:com:example:petstore">
<id>3</id>
</getPetByIdRequest>
</env:Body>
</env:Envelope>' http://localhost:8080/pets/
```

Response:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
<env:Header/>
<env:Body>
<urn:getPetByIdResponse xmlns:urn="urn:com:example:petstore">
<id>3</id>
<name>string</name>
</urn:getPetByIdResponse>
</env:Body>
</env:Envelope>
```
14 changes: 14 additions & 0 deletions examples/soap/fault-example/imposter-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
plugin: soap
wsdlFile: service.wsdl

resources:
- binding: SoapBinding
operation: getPetById
requestBody:
xPath: "/env:Envelope/env:Body/pets:getPetByIdRequest/pets:id"
value: "10"
xmlNamespaces:
env: "http://www.w3.org/2003/05/soap-envelope"
pets: "urn:com:example:petstore"
response:
statusCode: 500
84 changes: 84 additions & 0 deletions examples/soap/fault-example/service.wsdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>

<definitions name="PetService" xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:tns="urn:com:example:petstore"
xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"
xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/"
targetNamespace="urn:com:example:petstore">

<documentation>
A pet store service with a single operation and SOAP binding.
</documentation>

<types>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns="urn:com:example:petstore"
targetNamespace="urn:com:example:petstore">

<xs:complexType name="petType">
<xs:all>
<xs:element name="id" type="xs:int"/>
<xs:element name="name" type="xs:string"/>
</xs:all>
</xs:complexType>

<xs:complexType name="getPetByIdRequest">
<xs:all>
<xs:element name="id" type="xs:int"/>
</xs:all>
</xs:complexType>

<xs:complexType name="fault">
<xs:all>
<xs:element name="code" type="xs:int" />
<xs:element name="description" type="xs:string" />
</xs:all>
</xs:complexType>

<xs:element name="getPetByIdRequest" type="getPetByIdRequest"/>
<xs:element name="getPetByIdResponse" type="petType"/>
<xs:element name="getPetFault" type="fault"/>
</xs:schema>
</types>

<message name="getPetByIdRequest">
<part element="tns:getPetByIdRequest" name="parameters"/>
</message>
<message name="getPetByIdResponse">
<part element="tns:getPetByIdResponse" name="parameters"/>
</message>
<message name="getPetFault">
<part element="tns:getPetFault" name="parameters"/>
</message>

<portType name="PetPortType">
<operation name="getPetById">
<input message="tns:getPetByIdRequest" name="getPetByIdRequest"/>
<output message="tns:getPetByIdResponse" name="getPetByIdResponse"/>
<fault message="tns:getPetFault" name="getPetFault" />
</operation>
</portType>

<binding name="SoapBinding" type="tns:PetPortType">
<soap12:binding style="document" transport="http://schemas.xmlsoap.org/soap/soap"/>

<operation name="getPetById">
<soap12:operation soapAction="getPetById" style="document"/>
<input name="getPetByIdRequest">
<soap12:body use="literal"/>
</input>
<output name="getPetByIdResponse">
<soap12:body use="literal"/>
</output>
<fault name="getPetFault">
<soap12:body use="literal"/>
</fault>
</operation>
</binding>

<service name="PetService">
<port name="SoapEndpoint" binding="tns:SoapBinding">
<soap12:address location="http://www.example.com/pets/"/>
</port>
</service>
</definitions>
Loading