From c6bc36e3b0fa97c8306a7db597e9271023c45289 Mon Sep 17 00:00:00 2001 From: bbortt Date: Thu, 7 Dec 2023 13:27:22 +0100 Subject: [PATCH 1/2] docs(#206): updated simulations and rest support --- .../src/main/java/scenario/HelloScenario.java | 23 +- .../src/main/asciidoc/endpoint-support.adoc | 23 +- .../src/main/asciidoc/installation.adoc | 22 +- .../main/asciidoc/intermediate-messages.adoc | 119 ++++---- .../src/main/asciidoc/jms-support.adoc | 46 +--- .../src/main/asciidoc/rest-support.adoc | 259 ++++++------------ .../src/main/asciidoc/scenarios.adoc | 39 +-- .../src/main/asciidoc/ws-support.adoc | 40 +-- .../async/scenario/FaxCancelledScenario.java | 48 ++-- .../sample/scenario/HelloScenario.java | 16 +- .../sample/scenario/DefaultScenario.java | 10 +- .../sample/scenario/GoodNightScenario.java | 48 ++-- .../sample/scenario/HelloScenario.java | 22 +- .../simulator/scenario/ScenarioRunner.java | 4 +- 14 files changed, 322 insertions(+), 397 deletions(-) diff --git a/simulator-archetypes/archetype-mail/src/main/resources/archetype-resources/src/main/java/scenario/HelloScenario.java b/simulator-archetypes/archetype-mail/src/main/resources/archetype-resources/src/main/java/scenario/HelloScenario.java index 604024485..4277dd385 100644 --- a/simulator-archetypes/archetype-mail/src/main/resources/archetype-resources/src/main/java/scenario/HelloScenario.java +++ b/simulator-archetypes/archetype-mail/src/main/resources/archetype-resources/src/main/java/scenario/HelloScenario.java @@ -28,20 +28,21 @@ public void run(ScenarioRunner scenario) { scenario .receive() .payload("" + - "user@citrusframework.org" + - "citrus@citrusframework.org" + - "" + - "" + - "Hello" + - "" + - "text/plain; charset=utf-8" + - "Say Hello!" + - "" + - ""); + "user@citrusframework.org" + + "citrus@citrusframework.org" + + "" + + "" + + "Hello" + + "" + + "text/plain; charset=utf-8" + + "Say Hello!" + + "" + + ""); scenario .send() - .payload("" + + .payload( + "" + "250" + "OK" + ""); diff --git a/simulator-docs/src/main/asciidoc/endpoint-support.adoc b/simulator-docs/src/main/asciidoc/endpoint-support.adoc index 84aefe993..1c598c9d7 100644 --- a/simulator-docs/src/main/asciidoc/endpoint-support.adoc +++ b/simulator-docs/src/main/asciidoc/endpoint-support.adoc @@ -115,20 +115,21 @@ public class HelloScenario extends AbstractSimulatorScenario { scenario .receive() .payload("" + - "user@citrusframework.org" + - "citrus@citrusframework.org" + - "" + - "" + - "Hello" + - "" + - "text/plain; charset=utf-8" + - "Say Hello!" + - "" + - ""); + "user@citrusframework.org" + + "citrus@citrusframework.org" + + "" + + "" + + "Hello" + + "" + + "text/plain; charset=utf-8" + + "Say Hello!" + + "" + + ""); scenario .send() - .payload("" + + .payload( + "" + "250" + "OK" + ""); diff --git a/simulator-docs/src/main/asciidoc/installation.adoc b/simulator-docs/src/main/asciidoc/installation.adoc index 273987cf9..caf778adc 100644 --- a/simulator-docs/src/main/asciidoc/installation.adoc +++ b/simulator-docs/src/main/asciidoc/installation.adoc @@ -79,20 +79,24 @@ Additionally, we'll implement a default scenario that will be triggered by incom ---- package org.citrusframework.simulator; -import org.citrusframework.simulator.scenario.*; +import org.citrusframework.simulator.scenario.AbstractSimulatorScenario; +import org.citrusframework.simulator.scenario.Scenario; +import org.citrusframework.simulator.scenario.ScenarioRunner; import org.springframework.http.HttpStatus; -import org.citrusframework.http.message.HttpMessage; -@Scenario("DEFAULT_SCENARIO") +@Scenario("Default") public class DefaultScenario extends AbstractSimulatorScenario { @Override - public void run(ScenarioDesigner designer) { - designer.echo("Default scenario executed!"); - - designer.send() - .message(new HttpMessage("Welcome to the Citrus simulator") - .status(HttpStatus.OK)); + public void run(ScenarioRunner scenario) { + scenario.$(scenario.http() + .receive().post()); + + scenario.$(scenario.http() + .send() + .response(HttpStatus.OK) + .message() + .body("This is a default response!")); } } ---- diff --git a/simulator-docs/src/main/asciidoc/intermediate-messages.adoc b/simulator-docs/src/main/asciidoc/intermediate-messages.adoc index 67bb88ded..575b2b949 100644 --- a/simulator-docs/src/main/asciidoc/intermediate-messages.adoc +++ b/simulator-docs/src/main/asciidoc/intermediate-messages.adoc @@ -17,31 +17,44 @@ To explain this concept, consider the following simple example. @Scenario("GoodNight") public class GoodNightScenario extends AbstractSimulatorScenario { + private static final String CORRELATION_ID = "x-correlationid"; + @Override public void run(ScenarioRunner scenario) { - scenario + scenario.$(scenario.http() .receive() - .payload("" + - "Go to sleep!" + - "") - .extractFromHeader("X-CorrelationId", "correlationId"); - - scenario.correlation().start() - .onHeader("X-CorrelationId", "${correlationId}"); - - scenario + .post() + .message() + .body("" + + "Go to sleep!" + + "") + .extract(fromHeaders().header(CORRELATION_ID, "correlationId") + )); + + scenario.$(correlation().start() + .onHeader(CORRELATION_ID, "${correlationId}") + ); + + scenario.$(scenario.http() .send() - .payload("" + - "Good Night!" + - ""); + .response(HttpStatus.OK) + .message() + .body("" + + "Good Night!" + + "")); - scenario + scenario.$(scenario.http() .receive() - .payload("In between!"); + .post() + .selector("x-correlationid = '1${correlationId}'") + .message() + .body("In between!")); - scenario + scenario.$(scenario.http() .send() - .payload("In between!"); + .response(HttpStatus.OK) + .message() + .body("In between!")); } } ---- @@ -59,39 +72,51 @@ public class FaxCancelledScenario extends AbstractFaxScenario { public static final String ROOT_ELEMENT_XPATH = "string:local-name(/*)"; public static final String REFERENCE_ID_XPATH = "//fax:referenceId"; + public static final String REFERENCE_ID_VAR = "referenceId"; + public static final String REFERENCE_ID_PH = "${referenceId}"; @Override public void run(ScenarioRunner scenario) { - scenario - .receive() - .xpath(ROOT_ELEMENT_XPATH, "SendFaxMessage") - .extractFromPayload(REFERENCE_ID_XPATH, "referenceId"); - - scenario.correlation().start() - .onPayload(REFERENCE_ID_XPATH, "${referenceId}"); - - scenario - .send(getStatusEndpoint()) - .payload( - getPayloadHelper().generateFaxStatusMessage("${referenceId}", - FaxStatusEnumType.QUEUED, - "The fax message has been queued and will be sent shortly"), - getPayloadHelper().getMarshaller() - ); - - scenario - .receive() - .xpath(ROOT_ELEMENT_XPATH, "CancelFaxMessage") - .xpath(REFERENCE_ID_XPATH, "${referenceId}"); - - scenario - .send(getStatusEndpoint()) - .payload( - getPayloadHelper().generateFaxStatusMessage("${referenceId}", - FaxStatusEnumType.CANCELLED, - "The fax message has been cancelled"), - getPayloadHelper().getMarshaller() - ); + scenario.$(scenario.receive() + .message() + .validate(xpath().expression(ROOT_ELEMENT_XPATH, "SendFaxMessage")) + .extract( + fromBody().expression(REFERENCE_ID_XPATH, REFERENCE_ID_VAR))); + + scenario.$(correlation().start() + .onPayload(REFERENCE_ID_XPATH, REFERENCE_ID_PH)); + + scenario.$(send() + .endpoint(getStatusEndpoint()) + .message() + .body( + new MarshallingPayloadBuilder( + getPayloadHelper().generateFaxStatusMessage( + REFERENCE_ID_PH, + "QUEUED", + "The fax message has been queued and will be send shortly" + ), + getPayloadHelper().getMarshaller()) + )); + + scenario.$(scenario.receive() + .message() + .validate(xpath() + .expression(ROOT_ELEMENT_XPATH, "CancelFaxMessage") + .expression(REFERENCE_ID_XPATH, REFERENCE_ID_PH))); + + scenario.$(send() + .endpoint(getStatusEndpoint()) + .message() + .body( + new MarshallingPayloadBuilder( + getPayloadHelper().generateFaxStatusMessage( + REFERENCE_ID_PH, + "CANCELLED", + "The fax message has been cancelled" + ), + getPayloadHelper().getMarshaller()) + )); } } ---- diff --git a/simulator-docs/src/main/asciidoc/jms-support.adoc b/simulator-docs/src/main/asciidoc/jms-support.adoc index 1fa800d91..d7512818f 100644 --- a/simulator-docs/src/main/asciidoc/jms-support.adoc +++ b/simulator-docs/src/main/asciidoc/jms-support.adoc @@ -190,18 +190,17 @@ public class HelloJmsScenario extends AbstractSimulatorScenario { @Override public void run(ScenarioRunner scenario) { - scenario - .receive() - .payload("" + - "@ignore@" + - "") - .extractFromPayload("/Hello/user", "userName"); - - scenario - .send(replyEndpoint) - .payload("" + - "Hi there ${userName}!" + - ""); + scenario.$(scenario.receive() + .message() + .body("" + + "Say Hello!" + + "")); + + scenario.$(scenario.send() + .message() + .body("" + + "Hi there!" + + "")); } } ---- @@ -218,29 +217,6 @@ When dealing with synchronous communication the message producer waits for a rep within the simulator. So when we have synchronous communication we simply send back a response message using the scenario endpoint. The simulator makes sure that the response is provided to the waiting producer on the reply destination. -[source,java] ----- -@Scenario("Hello") -public class HelloJmsScenario extends AbstractSimulatorScenario { - - @Override - public void run(ScenarioRunner scenario) { - scenario - .receive() - .payload("" + - "@ignore@" + - "") - .extractFromPayload("/Hello/user", "userName"); - - scenario - .send() - .payload("" + - "Hi there ${userName}!" + - ""); - } -} ----- - The synchronous JMS communication needs to be enabled on the JMS simulator adapter. [source,java] diff --git a/simulator-docs/src/main/asciidoc/rest-support.adoc b/simulator-docs/src/main/asciidoc/rest-support.adoc index b9a07711f..3a927b8d9 100644 --- a/simulator-docs/src/main/asciidoc/rest-support.adoc +++ b/simulator-docs/src/main/asciidoc/rest-support.adoc @@ -1,11 +1,9 @@ [[rest]] -= REST support += REST Support -The simulator is able to provide Http REST APIs as a server. Clients can call the simulator on request paths using methods such as -Http GET, POST, PUT, DELETE and so on. +The Citrus simulator can serve as an Http REST API server, handling client requests using HTTP methods such as GET, POST, PUT, DELETE, etc. -The generic rest support is activated by setting the property *citrus.simulator.rest.enabled=true*. You can do so in the basic `application.properties` -file or via system property or environment variable setting. +Enable generic REST support by setting the property `citrus.simulator.rest.enabled=true` in the `application.properties` file or via system property or environment variable. [source,java] ---- @@ -20,16 +18,12 @@ public class Simulator { } ---- -The *citrus.simulator.rest.enabled* property performs some auto configuration steps and loads required beans for the Spring application context -in the Spring boot application. - -After that we are ready to handle incoming REST API calls on the simulator. +Setting `citrus.simulator.rest.enabled` triggers autoconfiguration steps and loads the required beans into the Spring application context. [[rest-config]] == Configuration -Once the REST support is enabled on the simulator we have different configuration options. The most comfortable way is to -add a *SimulatorRestAdapter* implementation to the classpath. The adapter provides several configuration methods. +With REST support enabled, various configuration options are available, typically via a `SimulatorRestAdapter` implementation: [source,java] ---- @@ -52,25 +46,21 @@ public abstract class SimulatorRestAdapter implements SimulatorRestConfigurer { } ---- -The adapter defines methods that configure the simulator REST handling. For instance we can add another scenario mapper implementation or -add handler interceptors to the REST API call handling. +The adapter allows customization of REST handling, such as implementing different scenario mappers or adding handler interceptors. -*Note* -The REST support is using the default scenario mapper *HttpRequestAnnotationScenarioMapper* that searches for *@RequestMapping* annotations -on scenario classes. Read more about that in link:#rest-request-mapping[rest-request-mapping]. +*Note*: By default, the REST support uses the `HttpRequestAnnotationScenarioMapper` to search for `@RequestMapping` annotations on scenario classes. -The *urlMapping* defines how clients can access the simulator REST API. Assuming the Spring boot simulator application is running on port 8080 the -REST API would be accessible on this URI: +The `urlMapping` method defines the access path to the simulator's REST API. +Assuming the Spring Boot application runs on port 8080, the API would be accessible at: [source] ---- http://localhost:8080/services/rest/* ---- -The clients can send GET, POST, DELETE and other calls to that endpoint URI then. The simulator will respond with respective responses based on the called -scenario. +Clients can send requests like GET, POST, DELETE to this endpoint, and the simulator will respond based on the executed scenario. -You can simply extend the adapter in a custom class for adding customizations. +Customize the simulator REST support by extending `SimulatorRestAdapter` in a custom class: [source,java] ---- @@ -84,15 +74,10 @@ public class MySimulatorRestAdapter extends SimulatorRestAdapter { } ---- -As you can see the class is annotated with *@Component* annotation. This is because the adapter should be recognized by Spring in order to overwrite the default -REST adapter behavior. The custom adapter just overwrites the *urlMapping* method so the REST simulator API will be accessible for clients under this endpoint URI: - -[source] ----- -http://localhost:8080/my-rest-service/* ----- +Annotate your custom class with `@Component` to override the default REST adapter behavior. +Now, the REST API will be accessible at `http://localhost:8080/my-rest-service/*`. -This is the simplest way to customize the simulator REST support. We can also use the adapter extension directly on the Spring boot main application class: +Extend the adapter directly in the main application class for further customizations: [source,java] ---- @@ -122,9 +107,9 @@ public class Simulator extends SimulatorRestAdapter { ---- [[rest-customization]] -== Advanced customizations +== Advanced Customizations -For a more advanced configuration option we can extend the *SimulatorRestSupport* implementation. +For more advanced configurations, extend `SimulatorRestSupport`: [source,java] ---- @@ -173,16 +158,13 @@ public class Simulator extends SimulatorRestAutoConfiguration { } ---- -With that configuration option we can overwrite REST support auto configuration features on the simulator such as the *requestCachingFilter* or the *handlerMapping*. -We extend the *SimulatorRestAutoConfiguration* implementation directly. +This approach allows you to override auto-configuration features like `requestCachingFilter` or `handlerMapping`. [[rest-request-mapping]] -== Request mapping - -By default the simulator will map incoming requests to scenarios using so called mapping keys that are evaluated on the incoming request. When using REST support on -the simulator we can also use *@RequestMapping* annotations on scenarios in order to map incoming requests. +== Request Mapping -This looks like follows: +By default, the simulator maps incoming requests to scenarios using mapping keys evaluated from the requests. +When utilizing REST support, `@RequestMapping` annotations on scenarios can also be used: [source,java] ---- @@ -192,35 +174,32 @@ public class HelloScenario extends AbstractSimulatorScenario { @Override public void run(ScenarioRunner scenario) { - scenario - .receive() - .payload("" + + scenario.$(scenario.http() + .receive() + .post() + .message() + .body("" + "Say Hello!" + - ""); + "")); - scenario - .send() - .payload("" + + scenario.$(scenario.http() + .send() + .response(HttpStatus.OK) + .message() + .body("" + "Hi there!" + - ""); + "")); } } ---- -As you can see the example above uses *@RequestMapping* annotation in addition to the *@Scenario* annotation. -All requests on the request path */services/rest/simulator/hello* of method *POST* that include the query -parameter *user* will be mapped to the scenario. With this strategy the simulator is able to map requests based -on methods, request paths and query parameters. - -The mapping strategy requires a special scenario mapper implementation that is used by default. This scenario mapper automatically scans for scenarios with *@RequestMapping* annotations. -The *HttpRequestAnnotationScenarioMapper* is active by default when enabling REST support on the simulator. Of course you can use traditional scenario mappers, too when using REST. -So in case you need to apply different mapping strategies you can overwrite the scenario mapper implementation in the configuration adapter. +In the above example, any POST request to `/services/rest/simulator/hello` with the `user` query parameter will be mapped to the `HelloScenario`. [[rest-status-code]] -== Http responses +== HTTP Responses -As Http is a synchronous messaging transport by its nature we can provide response messages to the calling client. In Http REST APIs this should include some Http status code. -You can specify the Http status code very easy when using the Citrus Java DSL methods as shown in the next example. +HTTP responses in REST APIs should include appropriate status codes. +This can be easily specified using Citrus's Java DSL: [source,java] ---- @@ -230,36 +209,36 @@ public class HelloScenario extends AbstractSimulatorScenario { @Override public void run(ScenarioRunner scenario) { - scenario - .http() - .receive() - .post() - .payload("" + - "Say Hello!" + - ""); - - scenario - .http() - .send() - .response(HttpStatus.OK) - .payload("" + - "Hi there!" + - ""); + scenario.$(scenario.http() + .receive() + .post() + .message() + .body("" + + "Say Hello!" + + "")); + + scenario.$(scenario.http() + .send() + .response(HttpStatus.OK) + .message() + .payload("" + + "Hi there!" + + "")); } } ---- -The Http Java DSL extension in Citrus provides easy access to Http related identities such as request methods, query parameters and status codes. Please -see the official Citrus documentation for more details how to use this Http specific Java fluent API. +Citrus's HTTP Java DSL simplifies setting request methods, query parameters, and status codes. +Refer to the Citrus documentation for more details on using this API. [[rest-swagger]] -== Swagger support +== Swagger Support -The simulator application is able to read link:https://swagger.io/[Swagger] link:https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md[Open API V3.0] specifications for auto -generating simulator scenarios for each operation. The Open API specification defines available REST request paths, supported methods (GET, POST, PUT, DELETE, ...) and their outcome when clients -call that API operations. The simulator generates basic scenarios for these specification information. +The simulator is equipped to interpret Swagger (OpenAPI V3.0) specifications, using them to automatically generate scenarios for each defined operation. +This feature streamlines the process of creating a simulator that mirrors real-world API behavior based on the Swagger documentation. -See the following sample how to do that: +To utilize this feature, the Swagger API file should be configured within the simulator's settings. +Below is an example of how to set up Swagger support: [source,java] ---- @@ -299,15 +278,15 @@ public class Simulator extends SimulatorRestAdapter { } ---- -The listing above adds a `HttpScenarioGenerator` as Spring bean to the simulator application. The generator receives the swagger api file location `swagger/petstore-api.json` and the -context path for this API. In addition to that we need to set a special scenario mapper implementation `HttpRequestPathScenarioMapper` that is aware of generated REST scenarios. +In the above configuration, the `HttpScenarioGenerator` bean is defined with the location of the Swagger API file (`swagger/petstore-api.json`) and the context path for the API. +The `HttpRequestPathScenarioMapper` is set to handle the REST scenarios generated from the Swagger specification. -Also we set a custom fallback endpoint adapter. This one is used when no scenario matches the incoming request or when the scenario itself did not produce a proper response because of -some validation error. +Additionally, a custom fallback endpoint adapter is defined for handling unmatched requests or validation errors. -On startup the generator dynamically generates a scenario for each operation defined in that swagger api file. You can review all generated scenarios in the user interface. +Upon startup, the simulator dynamically generates scenarios for each operation in the Swagger API file. +These scenarios are available for review in the simulator's user interface. -Let's have a look at a sample operation in that *petstore* swagger api file: +Consider the following sample operation from the Swagger API file: [source,json] ---- @@ -365,87 +344,30 @@ Let's have a look at a sample operation in that *petstore* swagger api file: } ] } -} ---- -The REST operation above defines a *GET* method on */pet/findByStatus*. The required query parameter *status* is defined to filter the returned list of pets. As a response -the API defines *200 OK* with an array of *Pet* objects. In addition to that *400* response is defined when the *status* parameter is not within its restriction enumeration *available, pending, sold*. +This operation would prompt the simulator to generate scenarios that validate requests against the defined criteria and provide appropriate responses, including handling different HTTP methods and query parameters. -*IMPORTANT:* _The simulator will always generate the success case exclusively. Here this would be the *200 OK* response. Other response variations are not generated up to now!_ +*Important*: The current implementation primarily focuses on generating scenarios for successful cases, like `200 OK` responses. +Other variations, such as error responses, are not automatically generated but can be manually added. -The generated scenario for this operation verifies that the request is using *GET* method on request path */pet/findByStatus*. Also the scenario verifies the existence of the *status* query -parameter and that the value is within the enumeration boundaries. - -Only in case these verification steps are performed successfully the simulator scenario generates a proper response *200 OK* that contains a dynamic array of pet objects. - -Let's have a look at the communication on that scenario: - -.Request -[source] ----- -GET http://localhost:8080/petstore/v2/pet/findByStatus?status=pending -Accept:application/json -Content-Type:text/plain;charset=UTF-8 -Content-Length:0 ----- - -.Response -[source] ----- -HTTP/1.1 200 -X-Application-Context:application -Content-Type:application/json -Content-Length:193 -Date:Wed, 13 Sep 2017 08:13:52 GMT - -[{"id": 5243024128,"category": {"id": 5032916791,"name": "hneBENfFDq"},"name": "JjZhcsvSRA","photoUrls": ["GwSVIBOhsi"],"tags": [{"id": 8074462757,"name": "DYwotNekKc"}],"status": "available"}] ----- +.Request and Response Examples +The simulator's response to requests is based on the generated scenarios. +For a valid request, it would provide a response as defined in the Swagger specification. +Conversely, for an invalid request (e.g., missing required parameters), the simulator would respond with an error, such as `404 NOT_FOUND`. -The request did match all verification steps on the simulator for this operation. Following from that we receive a generated response message with some sample data as array of pet objects. The simulator -is able to generate dynamic identifier such as *id*, *category* and *name* values. According to the field type the simulator generates dynamic number of string values. When there is a enumeration value restriction as seen in *status* -the simulator generates a dynamic enumeration value. - -This is how we always get a proper generated response from the simulator API. The petstore swagger Open API specification defines the returned objects and how to validate the incoming requests. - -Just in case we sent an invalid request according to the Open API specification we do not get a proper response. - -.Request -[source] ----- -GET http://localhost:8080/petstore/v2/pet/findByStatus -Accept:application/json -Content-Type:text/plain;charset=UTF-8 -Content-Length:0 ----- - -.Response -[source] ----- -HTTP/1.1 404 -X-Application-Context:application -Content-Type:text/plain;charset=UTF-8 -Content-Length:0 -Date:Wed, 13 Sep 2017 08:42:56 GMT ----- - -The sample request above is missing the required *status* query parameter on the *findByStatus* operation. As a result we get a *404 NOT_FOUND* response from the fallback endpoint adapter -as the scenario did not complete because of validation errors. You will see the failed scenario activity with proper error message on that missing *status* parameter in the user interface then. - -[rest-swagger-properties] -=== Swagger system properties - -The simulator Swagger API auto generate scenario feature can also be activated using pure property settings on the Spring boot application. Instead of adding the Spring bean `HttpScenarioGenerator` in your -simulator configuration you could just set the following properties on the simulator application: +.Additional Configuration Options +Swagger support can also be configured using system properties or environment variables, providing an alternative to programmatically setting up the `HttpScenarioGenerator`. [source, properties] ---- -# Enable swagger api support +# Example system properties for enabling Swagger support citrus.simulator.rest.swagger.enabled=true citrus.simulator.rest.swagger.api=classpath:swagger/petstore-api.json citrus.simulator.rest.swagger.contextPath=/petstore ---- -Of course you can also use environment variables. +Of course, the same can be achieved using environment variables. [source, properties] ---- @@ -454,32 +376,17 @@ CITRUS_SIMULATOR_REST_SWAGGER_API=classpath:swagger/petstore-api.json CITRUS_SIMULATOR_REST_SWAGGER_CONTEXT_PATH=/petstore ---- -We just add the api file location and everything else is auto configuration done in the simulator application. - -[rest-swagger-data-dictionary] -=== Data dictionaries - -Data dictionaries enable us to centralize data manipulation via JsonPath expressions in order to have more dynamic message values in generated request and response message. -When a scenario receives and sends messages the data dictionary is asked for available translations for message elements. This means that -data dictionaries are able to manipulate message content before they are processed. - -The auto generated scenario references both inbound and outbound data dictionaries. We simply need to enable those in the Spring boot `application.properties` file: - -[source, properties] ----- -citrus.simulator.inbound.json.dictionary.enabled=true -citrus.simulator.inboundJsonDictionary=classpath:dictionary/inbound_mappings.properties -citrus.simulator.outbound.json.dictionary.enabled=true -citrus.simulator.outboundJsonDictionary=classpath:dictionary/outbound_mappings.properties ----- - -As you can see you have the possibility to define mapping files that map JsonPath expression evaluation with pre defined values in the dictionary: +.Data Dictionary Integration +To further enhance dynamic message handling, data dictionaries can be used. +These dictionaries allow for centralized manipulation of message content via JsonPath expressions, making the interaction with the simulator more dynamic and adaptable. -Now we have added some mapping files for inbound and outbound dictionaries. The mapping file can look like this: +.Defining Data Dictionaries +Data dictionaries are defined in property files, with mappings that dictate how message content should be manipulated: .inbound mappings [source, properties] ---- +# Example inbound data dictionary mappings $.category.name=@assertThat(anyOf(is(dog),is(cat)))@ $.status=@matches(available|pending|sold|placed)@ $.quantity=@greaterThan(0)@ @@ -488,9 +395,9 @@ $.quantity=@greaterThan(0)@ .outbound mappings [source, properties] ---- +# Example outbound data dictionary mappings $.category.name=citrus:randomEnumValue('dog', 'cat') $.name=citrus:randomEnumValue('hasso', 'cutie', 'fluffy') ---- -The inbound and outbound mapping files defines several JsonPath expressions that should set predefined values before incoming and outgoing messages are validated respectively sent out. -As you can see we can use Citrus validation matchers and functions in order to get more complex value generation. +These mappings apply to both incoming and outgoing messages, ensuring that the simulator's responses are dynamic and contextually relevant, adhering to the constraints and possibilities defined in the Swagger specification. diff --git a/simulator-docs/src/main/asciidoc/scenarios.adoc b/simulator-docs/src/main/asciidoc/scenarios.adoc index 0608985ca..12e063427 100644 --- a/simulator-docs/src/main/asciidoc/scenarios.adoc +++ b/simulator-docs/src/main/asciidoc/scenarios.adoc @@ -31,17 +31,20 @@ public class HelloScenario extends AbstractSimulatorScenario { @Override public void run(ScenarioRunner scenario) { - scenario + scenario.$(scenario.soap() .receive() - .payload("" + - "Say Hello!" + - ""); + .message() + .body("" + + "Say Hello!" + + "") + .soapAction("Hello")); - scenario + scenario.$(scenario.soap() .send() - .payload("" + - "Hi there!" + - ""); + .message() + .body("" + + "Hi there!" + + "")); } } ---- @@ -59,18 +62,20 @@ public class HelloScenario extends AbstractSimulatorScenario { @Override public void run(ScenarioRunner scenario) { - scenario + scenario.$(scenario.soap() .receive() - .payload("" + - "@ignore@" + - "") - .extractFromPayload("/Hello/user", "userName"); + .message() + .body("" + + "@ignore@" + + "") + .extract(fromBody().expression("/Hello/user", "userName"))); - scenario + scenario.$(scenario.soap() .send() - .payload("" + - "Hi there ${userName}!" + - ""); + .message() + .body("" + + "Hi there ${userName}!" + + "")); } } ---- diff --git a/simulator-docs/src/main/asciidoc/ws-support.adoc b/simulator-docs/src/main/asciidoc/ws-support.adoc index f9ea6910c..59cdddba0 100644 --- a/simulator-docs/src/main/asciidoc/ws-support.adoc +++ b/simulator-docs/src/main/asciidoc/ws-support.adoc @@ -178,20 +178,20 @@ public class HelloScenario extends AbstractSimulatorScenario { @Override public void run(ScenarioRunner scenario) { - scenario - .soap() + scenario.$(scenario.soap() .receive() - .payload("" + - "Say Hello!" + - "") - .soapAction("Hello"); + .message() + .body("" + + "Say Hello!" + + "") + .soapAction("Hello")); - scenario - .soap() + scenario.$(scenario.soap() .send() - .payload("" + - "Hi there!" + - ""); + .message() + .body("" + + "Hi there!" + + "")); } } ---- @@ -205,7 +205,7 @@ When using SOAP message protocols we may need to send SOAP faults as response me == SOAP faults The simulator is in charge of sending proper response messages to the calling client. When using SOAP we might also want to send -back a SOAP fault message. Therefore the default Web Service scenario implementation also provides fault responses as scenario result. +back a SOAP fault message. Therefore, the default Web Service scenario implementation also provides fault responses as scenario result. [source,java] ---- @@ -214,17 +214,19 @@ public class GoodNightScenario extends AbstractSimulatorScenario { @Override protected void configure() { - scenario + scenario.$(scenario.soap() .receive() - .payload("" + - "Go to sleep!" + - "") - .header(SoapMessageHeaders.SOAP_ACTION, "GoodNight"); + .message() + .body("" + + "Go to sleep!" + + "") + .soapAction("GoodNight")); - scenario + scenario.$(scenario.soap() .sendFault() + .message() .faultCode("{http://citrusframework.org}CITRUS:SIM-1001") - .faultString("No sleep for me!"); + .faultString("No sleep for me!")); } } ---- diff --git a/simulator-samples/sample-jms-fax/src/main/java/org/citrusframework/simulator/sample/jms/async/scenario/FaxCancelledScenario.java b/simulator-samples/sample-jms-fax/src/main/java/org/citrusframework/simulator/sample/jms/async/scenario/FaxCancelledScenario.java index bbe0b27b9..64b285380 100644 --- a/simulator-samples/sample-jms-fax/src/main/java/org/citrusframework/simulator/sample/jms/async/scenario/FaxCancelledScenario.java +++ b/simulator-samples/sample-jms-fax/src/main/java/org/citrusframework/simulator/sample/jms/async/scenario/FaxCancelledScenario.java @@ -35,37 +35,43 @@ public class FaxCancelledScenario extends AbstractFaxScenario { @Override public void run(ScenarioRunner scenario) { scenario.$(scenario.receive() - .message() - .validate(xpath().expression(Variables.ROOT_ELEMENT_XPATH, "SendFaxMessage")) - .extract(fromBody().expression(Variables.REFERENCE_ID_XPATH, Variables.REFERENCE_ID_VAR))); + .message() + .validate(xpath().expression(Variables.ROOT_ELEMENT_XPATH, "SendFaxMessage")) + .extract( + fromBody().expression(Variables.REFERENCE_ID_XPATH, Variables.REFERENCE_ID_VAR))); scenario.$(correlation().start() - .onPayload(Variables.REFERENCE_ID_XPATH, Variables.REFERENCE_ID_PH)); + .onPayload(Variables.REFERENCE_ID_XPATH, Variables.REFERENCE_ID_PH)); scenario.$(send() - .endpoint(getStatusEndpoint()) - .message() - .body(new MarshallingPayloadBuilder( - getPayloadHelper().generateFaxStatusMessage(Variables.REFERENCE_ID_PH, - FaxStatusEnumType.QUEUED, - "The fax message has been queued and will be send shortly"), + .endpoint(getStatusEndpoint()) + .message() + .body( + new MarshallingPayloadBuilder( + getPayloadHelper().generateFaxStatusMessage( + Variables.REFERENCE_ID_PH, + FaxStatusEnumType.QUEUED, + "The fax message has been queued and will be send shortly" + ), getPayloadHelper().getMarshaller()) )); scenario.$(scenario.receive() - .message() - .validate(xpath() - .expression(Variables.ROOT_ELEMENT_XPATH, "CancelFaxMessage") - .expression(Variables.REFERENCE_ID_XPATH, Variables.REFERENCE_ID_PH)) - ); + .message() + .validate(xpath() + .expression(Variables.ROOT_ELEMENT_XPATH, "CancelFaxMessage") + .expression(Variables.REFERENCE_ID_XPATH, Variables.REFERENCE_ID_PH))); scenario.$(send() - .endpoint(getStatusEndpoint()) - .message() - .body(new MarshallingPayloadBuilder( - getPayloadHelper().generateFaxStatusMessage(Variables.REFERENCE_ID_PH, - FaxStatusEnumType.CANCELLED, - "The fax message has been cancelled"), + .endpoint(getStatusEndpoint()) + .message() + .body( + new MarshallingPayloadBuilder( + getPayloadHelper().generateFaxStatusMessage( + Variables.REFERENCE_ID_PH, + FaxStatusEnumType.CANCELLED, + "The fax message has been cancelled" + ), getPayloadHelper().getMarshaller()) )); } diff --git a/simulator-samples/sample-jms/src/main/java/org/citrusframework/simulator/sample/scenario/HelloScenario.java b/simulator-samples/sample-jms/src/main/java/org/citrusframework/simulator/sample/scenario/HelloScenario.java index d09650c7f..145d20017 100644 --- a/simulator-samples/sample-jms/src/main/java/org/citrusframework/simulator/sample/scenario/HelloScenario.java +++ b/simulator-samples/sample-jms/src/main/java/org/citrusframework/simulator/sample/scenario/HelloScenario.java @@ -33,15 +33,15 @@ public void run(ScenarioRunner scenario) { scenario.$(echo("Simulator: ${simulator.name}")); scenario.$(scenario.receive() - .message() - .body("" + - "Say Hello!" + - "")); + .message() + .body("" + + "Say Hello!" + + "")); scenario.$(scenario.send() - .message() - .body("" + - "Hi there!" + - "")); + .message() + .body("" + + "Hi there!" + + "")); } } diff --git a/simulator-samples/sample-rest/src/main/java/org/citrusframework/simulator/sample/scenario/DefaultScenario.java b/simulator-samples/sample-rest/src/main/java/org/citrusframework/simulator/sample/scenario/DefaultScenario.java index c051be8da..da6165642 100644 --- a/simulator-samples/sample-rest/src/main/java/org/citrusframework/simulator/sample/scenario/DefaultScenario.java +++ b/simulator-samples/sample-rest/src/main/java/org/citrusframework/simulator/sample/scenario/DefaultScenario.java @@ -30,12 +30,12 @@ public class DefaultScenario extends AbstractSimulatorScenario { @Override public void run(ScenarioRunner scenario) { scenario.$(scenario.http() - .receive().post()); + .receive().post()); scenario.$(scenario.http() - .send() - .response(HttpStatus.OK) - .message() - .body("This is a default response!")); + .send() + .response(HttpStatus.OK) + .message() + .body("This is a default response!")); } } diff --git a/simulator-samples/sample-rest/src/main/java/org/citrusframework/simulator/sample/scenario/GoodNightScenario.java b/simulator-samples/sample-rest/src/main/java/org/citrusframework/simulator/sample/scenario/GoodNightScenario.java index 51004ab3c..c67adcf23 100644 --- a/simulator-samples/sample-rest/src/main/java/org/citrusframework/simulator/sample/scenario/GoodNightScenario.java +++ b/simulator-samples/sample-rest/src/main/java/org/citrusframework/simulator/sample/scenario/GoodNightScenario.java @@ -37,40 +37,38 @@ public class GoodNightScenario extends AbstractSimulatorScenario { @Override public void run(ScenarioRunner scenario) { scenario.$(scenario.http() - .receive() - .post() - .message() - .body("" + - "Go to sleep!" + - "") - .extract(fromHeaders().header(CORRELATION_ID, "correlationId") + .receive() + .post() + .message() + .body("" + + "Go to sleep!" + + "") + .extract(fromHeaders().header(CORRELATION_ID, "correlationId") )); scenario.$(correlation().start() - .onHeader(CORRELATION_ID, "${correlationId}") + .onHeader(CORRELATION_ID, "${correlationId}") ); scenario.$(scenario.http() - .send() - .response(HttpStatus.OK) - .message() - .body("" + - "Good Night!" + - "")); + .send() + .response(HttpStatus.OK) + .message() + .body("" + + "Good Night!" + + "")); scenario.$(scenario.http() - .receive() - .post() - .selector("x-correlationid = '1${correlationId}'") - .message() - .body("In between!") - ); + .receive() + .post() + .selector("x-correlationid = '1${correlationId}'") + .message() + .body("In between!")); scenario.$(scenario.http() - .send() - .response(HttpStatus.OK) - .message() - .body("In between!") - ); + .send() + .response(HttpStatus.OK) + .message() + .body("In between!")); } } diff --git a/simulator-samples/sample-ws/src/main/java/org/citrusframework/simulator/sample/scenario/HelloScenario.java b/simulator-samples/sample-ws/src/main/java/org/citrusframework/simulator/sample/scenario/HelloScenario.java index fc51d75e9..de0a6ba20 100644 --- a/simulator-samples/sample-ws/src/main/java/org/citrusframework/simulator/sample/scenario/HelloScenario.java +++ b/simulator-samples/sample-ws/src/main/java/org/citrusframework/simulator/sample/scenario/HelloScenario.java @@ -29,18 +29,18 @@ public class HelloScenario extends AbstractSimulatorScenario { @Override public void run(ScenarioRunner scenario) { scenario.$(scenario.soap() - .receive() - .message() - .body("" + - "Say Hello!" + - "") - .soapAction("Hello")); + .receive() + .message() + .body("" + + "Say Hello!" + + "") + .soapAction("Hello")); scenario.$(scenario.soap() - .send() - .message() - .body("" + - "Hi there!" + - "")); + .send() + .message() + .body("" + + "Hi there!" + + "")); } } diff --git a/simulator-spring-boot/src/main/java/org/citrusframework/simulator/scenario/ScenarioRunner.java b/simulator-spring-boot/src/main/java/org/citrusframework/simulator/scenario/ScenarioRunner.java index 5c8e5d662..275bb63b4 100644 --- a/simulator-spring-boot/src/main/java/org/citrusframework/simulator/scenario/ScenarioRunner.java +++ b/simulator-spring-boot/src/main/java/org/citrusframework/simulator/scenario/ScenarioRunner.java @@ -88,8 +88,8 @@ public SoapScenarioActionBuilder soap() { @Override public T run(TestActionBuilder builder) { - if (builder instanceof CorrelationHandlerBuilder) { - ((CorrelationHandlerBuilder) builder).setApplicationContext(applicationContext); + if (builder instanceof CorrelationHandlerBuilder correlationHandlerBuilder) { + correlationHandlerBuilder.setApplicationContext(applicationContext); delegate.run(doFinally().actions(((CorrelationHandlerBuilder) builder).stop())); } From 6b62e8b72c1f0c06503ec4981b233acf7b8e095b Mon Sep 17 00:00:00 2001 From: bbortt Date: Thu, 7 Dec 2023 14:55:49 +0100 Subject: [PATCH 2/2] docs(#206): ws support --- .../src/main/asciidoc/ws-support.adoc | 329 +++++------------- 1 file changed, 84 insertions(+), 245 deletions(-) diff --git a/simulator-docs/src/main/asciidoc/ws-support.adoc b/simulator-docs/src/main/asciidoc/ws-support.adoc index 59cdddba0..41e080290 100644 --- a/simulator-docs/src/main/asciidoc/ws-support.adoc +++ b/simulator-docs/src/main/asciidoc/ws-support.adoc @@ -1,10 +1,8 @@ [[web-service]] -= Web Service support += Web Service Support -The simulator is able to handle SOAP Web Service calls as a server. - -The generic SOAP web service support is activated by setting the property *citrus.simulator.ws.enabled=true*. You can do so in the basic `application.properties` -file or via system property or environment variable setting. +The simulator is capable of handling SOAP Web Service calls as a server. +To activate generic SOAP web service support, set the property `citrus.simulator.ws.enabled=true` in the `application.properties` file, or through a system property or environment variable. [source,java] ---- @@ -19,10 +17,10 @@ public class Simulator { } ---- -The *citrus.simulator.ws.enabled* property performs some auto configuration steps and loads required beans for the Spring application context -in the Spring boot application. +Setting `citrus.simulator.ws.enabled` triggers auto-configuration steps and loads the required beans for the Spring application context. -As SOAP web service support is not included by default in the simulator we need to add some Citrus dependencies to our project. In Maven we simply add the following dependency to the project POM. +SOAP web service support is not included by default, so you must add Citrus dependencies to your project. +In Maven, add the following dependency: [source, xml] ---- @@ -34,21 +32,23 @@ As SOAP web service support is not included by default in the simulator we need ---- -After that we are ready to handle incoming SOAP Web Service calls on the simulator. When SOAP web service handling is enabled on the simulator -the SOAP envelope handling is done automatically. This means we do not have to deal with that SOAP envelope in the scenario receive and send operations. Also -the scenario receive operation has access to the SOAP action of the incoming request call. Besides that we can also link:#ws-soap-faults[return a SOAP fault] message as scenario outcome. +With these configurations, the simulator is ready to handle incoming SOAP Web Service calls. +When SOAP web service handling is enabled, the simulator manages the SOAP envelope automatically, meaning you don't have to deal with the SOAP envelope in scenario operations. +The scenario receive operation can access the SOAP action of the incoming request, and it is also possible to return a SOAP fault message as a scenario outcome. -Let's move on with having a look at the SOAP related configuration options as described in the following sections. +Let's proceed to review the SOAP-related configuration options as described in the following sections. [[web-service-config]] == Configuration -Once the SOAP support is enabled on the simulator we have different configuration options. The most comfortable way is to -add a *SimulatorWebServiceAdapter* implementation to the classpath. The adapter provides several configuration methods. +Once SOAP support is enabled in the simulator, various configuration options become available. +The most straightforward method is to add a `SimulatorWebServiceAdapter` implementation to the classpath. +This adapter provides several configuration methods. [source,java] ---- public abstract class SimulatorWebServiceAdapter implements SimulatorWebServiceConfigurer { + @Override public String servletMapping() { return "/services/ws/*"; @@ -66,21 +66,17 @@ public abstract class SimulatorWebServiceAdapter implements SimulatorWebServiceC } ---- -The adapter defines methods that configure the simulator SOAP message handling. For instance we can add another mapping key extractor implementation or -add endpoint interceptors to the SOAP service call handling. - -The *servletMapping* defines how clients can access the simulator SOAP service. Assuming the Spring boot simulator application is running on port 8080 the -SOAP service would be accessible on this URI: +This adapter defines methods to configure the simulator's SOAP message handling, such as adding different scenario mapper implementations or endpoint interceptors. +The `servletMapping` method defines client access to the simulator's SOAP service. +For example, if the Spring Boot application runs on port 8080, the SOAP service would be accessible at: [source] ---- http://localhost:8080/services/ws/* ---- -The clients can send SOAP calls to that endpoint URI then. The simulator will respond with respective SOAP responses based on the called -scenario. - -You can simply extend the adapter in a custom class for adding customizations. +Clients can send SOAP calls to this endpoint, and the simulator will respond with appropriate SOAP responses based on the executed scenario. +You can customize the simulator's SOAP support by extending `SimulatorWebServiceAdapter` in a custom class: [source,java] ---- @@ -94,15 +90,15 @@ public class MySimulatorWebServiceAdapter extends SimulatorWebServiceAdapter { } ---- -As you can see the class is annotated with *@Component* annotation. This is because the adapter should be recognized by Spring in order to overwrite the default -SOAP adapter behavior. The custom adapter just overwrites the *servletMapping* method so the SOAP simulator API will be accessible for clients under this endpoint URI: +The class is annotated with `@Component` so that Spring recognizes it and overrides the default SOAP adapter behavior. +By customizing the `servletMapping` method, the SOAP simulator API will be accessible under a new endpoint URI: [source] ---- http://localhost:8080/my-soap-service/* ---- -This is the simplest way to customize the simulator SOAP support. We can also use the adapter extension directly on the Spring boot main application class: +For direct integration with the Spring Boot main application class, extend `SimulatorWebServiceAdapter` as shown: [source,java] ---- @@ -130,9 +126,9 @@ public class Simulator extends SimulatorWebServiceAdapter { ---- [[web-service-customization]] -== Advanced customizations +== Advanced Customizations -For a more advanced configuration option we can extend the *SimulatorWebServiceSupport* implementation. +For more advanced configurations, extend `SimulatorWebServiceSupport`: [source,java] ---- @@ -162,14 +158,13 @@ public class Simulator extends SimulatorWebServiceAutoConfiguration { } ---- -With that configuration option we can overwrite SOAP support auto configuration features on the simulator such as the *messageDispatcherServlet*. -We extend the *SimulatorWebServiceAutoConfiguration* implementation directly. +This configuration allows overriding features like the `messageDispatcherServlet` in the SOAP support auto-configuration. [[web-service-response]] -== SOAP response +== SOAP Response -When using Http SOAP services we may want to respond to the calling client with a synchronous SOAP response message. As the SOAP communication is automatically handled -within the simulator we can simply send back a response message in the scenario. +When using SOAP services, you might want to respond synchronously with a SOAP message. +Since the simulator handles SOAP communication automatically, you can define the response message directly in the scenario. [source,java] ---- @@ -196,16 +191,17 @@ public class HelloScenario extends AbstractSimulatorScenario { } ---- -As you can see the Citrus Java DSL provides special SOAP related methods that specify the SOAP request and response data. Once again the SOAP envelope is automatically handled -so we do not have to add this here. The receive operation is able to verify the SOAP action header value. In addition to that we are able to specify the synchronous SOAP response message. +The Citrus Java DSL provides SOAP-specific methods for specifying request and response data. +The SOAP envelope is handled automatically, so there's no need to include it here. +The receive operation verifies the SOAP action header value, and you can specify the synchronous SOAP response message. -When using SOAP message protocols we may need to send SOAP faults as response message. This is handled in the next section. +Next, we will discuss how to send SOAP faults as response messages. [[web-service-faults]] -== SOAP faults +== SOAP Faults -The simulator is in charge of sending proper response messages to the calling client. When using SOAP we might also want to send -back a SOAP fault message. Therefore, the default Web Service scenario implementation also provides fault responses as scenario result. +When using SOAP, you may need to send back a SOAP fault message. +The default Web Service scenario implementation allows sending fault responses. [source,java] ---- @@ -231,17 +227,16 @@ public class GoodNightScenario extends AbstractSimulatorScenario { } ---- -The example above shows a simple fault generating SOAP scenario. The base class *SimulatorWebServiceScenario* provides -the *sendFault()* method in order to create proper SOAP fault messages. The simulator automatically add SOAP envelope and SOAP fault -message details for you. So we can decide wheather to provide a success response or SOAP fault. +In this example, the `sendFault()` method is used to create a SOAP fault message. +The simulator adds the SOAP envelope and fault details, allowing you to choose between a success response or a SOAP fault. [[web-service-wsdl]] -== WSDL support +== WSDL Support -The simulator is able to read your WSDL web service specifications for auto generating simulator scenarios. The WSDL defines multiple operations with request and response message data. -The simulator reads the WSDL information and generates basic scenarios for these operations. +The simulator can read WSDL specifications to auto-generate scenarios for each defined operation. +These operations include request and response message data, which the simulator uses to generate basic scenarios. -See the following sample how to do that: +To set up WSDL support, see the following example: [source,java] ---- @@ -259,15 +254,7 @@ public class Simulator extends SimulatorWebServiceAdapter { @Override public EndpointAdapter fallbackEndpointAdapter() { - return new StaticEndpointAdapter() { - @Override - protected Message handleMessageInternal(Message message) { - return new SoapFault() - .faultActor("SERVER") - .faultCode("{http://localhost:8080/HelloService/v1}HELLO:ERROR-1001") - .faultString("Internal server error"); - } - }; + // Implementation details... } @Bean @@ -278,218 +265,69 @@ public class Simulator extends SimulatorWebServiceAdapter { } ---- -The listing above uses a `WsdlScenarioGenerator` as Spring bean. The generator requires the WSDL file location `xsd/Hello.wsdl` and the -servlet mapping path for this API. - -Also we set a custom fallback endpoint adapter. This one is used when no scenario matches the incoming request or when the scenario itself did not produce a proper response because of -some validation error. The fallback endpoint adapter sends a default SOAP fault message with *"Internal server error"*. +In the configuration above, a `WsdlScenarioGenerator` bean is set up with the WSDL file location `xsd/Hello.wsdl`. +A custom fallback endpoint adapter is also defined for handling unmatched requests or validation errors. -On startup the generator dynamically generates a scenario for each operation defined in that WSDL file. You can review all generated scenarios in the user interface. +Upon startup, the generator creates scenarios for each operation in the WSDL file. -Let's have a look at the sample WSDL file: +Consider the following WSDL file sample: [source,xml] ---- - - - - Version 1.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ----- - -The WSDL above defines a *hello* operation with *Hello* as input and *HelloResponse* as output. The SOAP action is defined as *Hello*. - -The generated scenario for this operation verifies that the request is a valid *Hello* request according to the XSD schema definition in the WSDL. Also the scenario verifies the basic XML structure of that message. - -Only in case these verification steps are performed successfully the simulator scenario generates a proper response *HelloResponse*. The generated scenario is able to create dynamic values in the response according to the XSD schema in the WSDL. We will cover this feature in -more detail on later in this chapter. - -Let's have a look at the communication that the generated scenario is going to perform: + +---- + +This WSDL defines operations like *hello*, with request and response message structures. + +Generated scenarios validate requests against the XSD schema in the WSDL and generate appropriate responses. +Dynamic values in responses adhere to the schema rules. + +Communication in generated scenarios follows this pattern: .Request [source] ---- - - - - Say Hello! - - + ---- .Response [source] ---- - - - - GL29HT - - ----- - -The *Hello* SOAP request matches all verification steps on the simulator for this operation. Following from that we receive a generated *HelloResponse* response message with some sample data. The simulator -is able to generate dynamic values such as *GL29HT* which is according to the WSDL schema rules a string value. - -This is how we always get a proper generated response from the simulator API. The *HelloService* WSDL specification defines the returned objects and how to validate the incoming requests. - -Just in case we sent an invalid request to the simulator we do not get a proper response. For instance if we sent a wrong SOAP action we receive following fault response: - -.Fault response -[source] ----- - - - - - HELLO:ERROR-1001 - Internal server error - SERVER - - - + ---- -As a result we get a SOAP fault message with fault code *ERROR-1001* and message *"Internal server error"* as defined in the fallback endpoint adapter. -You will also see the failed scenario activity with proper error message in the user interface then. +For invalid requests, such as those with incorrect SOAP actions, the simulator responds with a default SOAP fault, as defined in the fallback endpoint adapter. -[web-service-wsdl-properties] -=== WSDL system properties +[[web-service-wsdl-properties]] +=== WSDL System Properties -The simulator WSDL auto generate scenario feature can also be activated using pure property settings on the Spring boot application. Instead of adding the Spring bean `WsdlScenarioGenerator` in your -simulator configuration you could just set the following properties on the simulator application: +The WSDL auto-generation feature can be activated using system properties in the Spring Boot application, providing an alternative to programmatically setting up the `WsdlScenarioGenerator`. [source, properties] ---- -# Enable SOAP web service support +# System properties for enabling WSDL support citrus.simulator.ws.wsdl.enabled=true citrus.simulator.ws.wsdl.location=classpath:xsd/Hello.wsdl ---- -Of course you can also use environment variables. +Environment variables can also be used for configuration. [source, properties] ---- +# Environment variables for enabling WSDL support CITRUS_SIMULATOR_WS_WSDL_ENABLED=true CITRUS_SIMULATOR_WS_WSDL_LOCATION=classpath:xsd/Hello.wsdl ---- -We just add the WSDL location and everything else is auto configuration done in the simulator application. - [web-service-data-dictionary] === Data dictionaries -The auto generated WSDL scenarios make us of so called data dictionaries in order to create dynamic values both in request and response messages. The data dictionaries -are a well known Citrus functionality that enable us to centralize data manipulation via XPath expressions for example. Each XML message construction will consult the data dictionary -for some translation of elements and attributes. +Auto-generated WSDL scenarios utilize data dictionaries to create dynamic values in both request and response messages. +Data dictionaries are a well-known Citrus functionality that centralizes data manipulation, often using XPath expressions. +In XML message processing, each construction step consults the data dictionary for potential modifications to elements and attributes. -The auto generated scenario references both inbound and outbound data dictionaries. We simply need to enable those in the Spring boot `application.properties` file: +Auto-generated scenarios reference both inbound and outbound data dictionaries. +To enable these dictionaries, activate them in the Spring Boot `application.properties` file: [source, properties] ---- @@ -497,10 +335,11 @@ citrus.simulator.inbound.xml.dictionary.enabled=true citrus.simulator.outbound.xml.dictionary.enabled=true ---- -These property settings automatically activate the data dictionaries and you will get random numbers and strings in all generated WSDL messages. For incoming requests the dictionary makes sure -that elements and attributes are ignored in validation by default. This is a good idea as we can not know all data that is sent to the simulator. +Activating these settings automatically enables data dictionaries, generating random numbers and strings in all auto-generated WSDL messages. +For incoming requests, the dictionary ensures elements and attributes are ignored by default during validation. +This approach is beneficial, as it's impossible to predict all data sent to the simulator. -Fortunately you have the possibility to define mapping files that map XPath expression evaluation with pre defined values in the dictionary: +You can define specific mappings in the dictionaries using XPath expressions: [source, properties] ---- @@ -510,7 +349,8 @@ citrus.simulator.outbound.xml.dictionary.enabled=true citrus.simulator.outboundXmlDictionary=classpath:dictionary/outbound_mappings.xml ---- -Now we have added some mapping files for inbound and outbound dictionaries. The mapping file can look like this: +Inbound and outbound mapping files are specified for the dictionaries. +For example, an inbound mapping file could look like this: [source, xml] ---- @@ -521,11 +361,12 @@ Now we have added some mapping files for inbound and outbound dictionaries. The ---- -The inbound mapping file defines two XPath expressions that should set predefined values before incoming request are validated. So in this case we set `Say Hello!` as string element value -to the element `` in the request. When dealing with XML and XPath we need to take care on proper namespace handling. In the XPath expression above we make use of the namespace prefix `sim:`. This prefix resoves to a -proper namespace in the WSDL schema for `Hello` messages and is defined in a global namespace context within the Spring application. +The inbound mappings define XPath expressions to set pre-defined values for incoming requests. +For instance, the above mappings set specific string values for `` and `` elements. +When using XPath in XML, proper namespace handling is crucial. +In the provided XPath expressions, the `sim:` prefix corresponds to a namespace in the WSDL schema for `Hello` messages. -You can add that namespace context as Spring bean for instance. +You can define a global namespace context in your Spring application to facilitate namespace handling: [source, java] ---- @@ -533,14 +374,13 @@ You can add that namespace context as Spring bean for instance. public NamespaceContextBuilder namespaceContextBuilder() { NamespaceContextBuilder namespaceContextBuilder = new NamespaceContextBuilder(); namespaceContextBuilder.getNamespaceMappings().put("sim", "http://citrusframework.org/schemas/hello"); - return namespaceContextBuilder; } ---- -After that we are able to use the global `sim` namespace prefix in all XPath expressions. The XPath expression evaluation will take care on proper namespace handling then. +After setting up this namespace context, the `sim` prefix can be globally used in XPath expressions. -Of course we can also add outbound bindings for creating special response element values. +Outbound mappings can also be specified to create specific response values. [source, xml] ---- @@ -551,7 +391,8 @@ Of course we can also add outbound bindings for creating special response elemen ---- -Now the auto generated response for `HelloResponse` messages will always use `Hello!` as value. in combination with Citrus functions we are able to define more complex response element values in auto generated messages. +For instance, the above outbound mappings ensure that `HelloResponse` messages always contain "Hello!". +Citrus functions can be used to define more complex values in auto-generated messages. [source, xml] ---- @@ -561,5 +402,3 @@ Now the auto generated response for `HelloResponse` messages will always use `He citrus:randomEnumValue('GoodBye!', 'SeeYaLater!', 'ByeBye!') ---- - -