From e05ec958626719c3f3eca8eecc2bac70f2ecf4b6 Mon Sep 17 00:00:00 2001 From: Thorsten Schlathoelter Date: Tue, 14 May 2024 14:58:53 +0200 Subject: [PATCH] feat(#1156): provide test api generator --- src/manual/testapi.adoc | 380 +++++++ .../citrus-test-api-generator-core/README.md | 636 ------------ .../citrus-test-api-generator-core/pom.xml | 43 - .../generator/AbstractTestRequestTest.java | 70 -- .../openapi/generator/GeneratedApiIT.java | 951 +++++++----------- .../openapi/generator/GetPetByIdTest.java | 254 +++++ .../getPetByIdRequestTest.xml | 2 +- ...ge.json => getPetByIdControlMessage1.json} | 0 .../payloads/getPetByIdControlMessage2.json | 6 + .../GeneratedApiTest/postFileTest.xml | 2 +- .../request/MultiparttestControllerApi.java | 4 + .../rest/petstore/request/PetApi.java | 2 + .../rest/petstore/request/StoreApi.java | 8 + .../rest/petstore/request/UserApi.java | 8 + .../OpenApiFromWsdlAbstractTestRequest.java | 1 + .../request/BookServiceSoapApi.java | 2 + .../README.md | 162 --- 17 files changed, 1041 insertions(+), 1490 deletions(-) create mode 100644 src/manual/testapi.adoc delete mode 100644 test-api-generator/citrus-test-api-generator-core/README.md delete mode 100644 test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/AbstractTestRequestTest.java create mode 100644 test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/GetPetByIdTest.java rename test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/{getPetByIdControlMessage.json => getPetByIdControlMessage1.json} (100%) create mode 100644 test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage2.json delete mode 100644 test-api-generator/citrus-test-api-generator-maven-plugin/README.md diff --git a/src/manual/testapi.adoc b/src/manual/testapi.adoc new file mode 100644 index 0000000000..f7d7cc7add --- /dev/null +++ b/src/manual/testapi.adoc @@ -0,0 +1,380 @@ +[[testapi]] += Test API Generation + +== OpenAPI: A Standard for API Description +OpenAPI, formerly known as Swagger, is a widely adopted standard for describing RESTful APIs. It provides a +language-agnostic interface to define the structure of APIs in a human-readable format, typically using YAML +or JSON. OpenAPI specifications serve as a contract for your API, detailing endpoints, request/response formats, +authentication methods, and more. + +OpenAPI specifications are commonly used to generate various artifacts such as client libraries, server stubs, and +documentation. By capturing the structure and behavior of APIs in a machine-readable format, OpenAPI enables +seamless code generation, saving time and ensuring consistency across different implementations. + +== Introducing the Citrus OpenAPI Test API Generator + +Given the widespread adoption of OpenAPI code generation in service implementation, it's natural to extend +this practice to testing. The `Citrus OpenAPI Test API Generator` leverages OpenAPI specifications +to craft a dedicated test API explicitly designed for testing the API under evaluation. This tailored API +seamlessly integrates into Citrus XML or Java tests, ensuring a cohesive testing experience. + +== What does it offer? + +`Citrus OpenAPI Test API Generator` provides the following features: + +* generation of a Test API +** from OpenAPI Specification +** [TODO #1163] from WSDL via an intermediate step that generates a "light" OpenApi specification from a WSDL +* integration into Citrus XML test cases +** integration into XML editors via provided XSD +*** schema validation +*** auto completion +* integration into Citrus Java test cases + +=== How Does it Work? + +The `Citrus OpenAPI Test API Generator` leverages the link:https://github.com/swagger-api/swagger-codegen/tree/master[OpenAPI Code Generator] +to generate code, but provides custom templates tailored for seamless integration within the Citrus framework. + +During a build process, code generation is carried out by a Maven plugin. While the standard generator plugin, +`org.openapitools:openapi-generator-maven-plugin`, can be employed for this purpose, configuring it can be +cumbersome, especially when dealing with multiple APIs. To address this challenge, Citrus offers its adaptation +of this standard generator Maven plugin. This `Citrus OpenAPI Generator Plugin` simplifies the configuration of test API +generation by providing predefined defaults and supporting the generation of multiple APIs. Additionally, +it enhances support for generating Spring integration files (`spring.handlers` and `spring.schemas`), thereby +facilitating the integration of generated APIs into Spring-based applications. Consequently, utilizing the +Citrus Generator Plugin is recommended in most scenarios. + +The following shows the configuration of test api generation for different scenarios: + +.Citrus OpenAPI Generator Plugin - multiple APIs, minimal configuration +[source,xml,indent=0,role="primary"] +---- + + citrus-test-api-generator-maven-plugin + + + + + Multi1 + api/test-api.yml + + + Multi2 + api/test-api.yml + + + Multi3 + api/test-api.yml + + + + + + + create-test-api + + + + + +---- + +.Citrus OpenAPI Generator Plugin - single API full configuration +[source,xml,indent=0,role="secondary"] +---- + + citrus-test-api-generator-maven-plugin + + + + my-generated-sources + my-generated-resources + myschema/xsd + src/main/resources/META-INF + + Full + api/test-api.yml + org.mypackage.%PREFIX%.api + myEndpoint + org.mypackage.%PREFIX%.invoker + org.mypackage.%PREFIX%.model + "http://company/citrus-test-api/myNamespace" + + + + + + + + create-test-api + + + + +---- + +.Standard OpenAPI Generator Plugin +[source,xml,indent=0,role="secondary"] +---- + + + org.openapitools + openapi-generator-maven-plugin + + + + org.citrusframework + citrus-test-api-generator-core + ${project.version} + + + + + REST + generated-test-resources + generated-test-sources + true + + true + + java-citrus + ${project.build.directory} + + + + generate-openapi-petstore-files + compile + + generate + + + ${project.basedir}/src/test/resources/apis/petstore.yaml + + org.citrusframework.openapi.generator.rest.petstore + org.citrusframework.openapi.generator.rest.petstore.request + org.citrusframework.openapi.generator.rest.petstore.model + PetStore + petStoreEndpoint + + + + + generate-openapi-files-for-soap + compile + + generate + + + ${project.basedir}/src/test/resources/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest/BookService-generated.yaml + + SOAP + org.citrusframework.openapi.generator.soap.bookservice + org.citrusframework.openapi.generator.soap.bookservice.request + org.citrusframework.openapi.generator.soap.bookservice.model + SoapSample + OpenApiFromWsdl + soapSampleEndpoint + + + + + +---- + +=== Configuration Options + +Here are the primary elements you can configure in the `` section: + +|=== +| Configuration element | Maven Property | Description | Default Value + +| `schemaFolder` | `citrus.test.api.generator.schema.folder` | Location for the generated XSD schemas | `schema/xsd/%VERSION%` +| `resourceFolder` | `citrus.test.api.generator.resource.folder` | Location to which resources are generated | `generated-resources` +| `sourceFolder` | `citrus.test.api.generator.source.folder` | Location to which sources are generated | `generated-sources` +| `metaInfFolder` | `citrus.test.api.generator.meta.inf.folder` | Location to which spring meta files are generated/updated | `target/generated-test-resources/META-INF` +| `generateSpringIntegrationFiles` | `citrus.test.api.generator.generate.spring.integration.files` | Specifies whether spring integration files should be generated | `true` +| Nested api element | | | +| `prefix` | `citrus.test.api.generator.prefix` | Specifies the prefix used for the test API, typically an acronym | (no default, required) +| `source` | `citrus.test.api.generator.source` | Specifies the source of the test API | (no default, required) +| `version` | `citrus.test.api.generator.version` | Specifies the version of the API, may be null | (none) +| `endpoint` | `citrus.test.api.generator.endpoint` | Specifies the endpoint of the test API | `applicationServiceClient` +| `type` | `citrus.test.api.generator.type` | Specifies the type of the test API | `REST`, other option is `SOAP` +| `useTags` | `citrus.test.api.generator.use.tags` | Specifies whether tags should be used by the generator | `true` +| `invokerPackage` | `citrus.test.api.generator.invoker.package` | Package for the test API classes | `org.citrusframework.automation.%PREFIX%.%VERSION%` +| `apiPackage` | `citrus.test.api.generator.api.package` | Package for the test API interface classes | `org.citrusframework.automation.%PREFIX%.%VERSION%.api` +| `modelPackage` | `citrus.test.api.generator.model.package` | Package for the test API model classes | `org.citrusframework.automation.%PREFIX%.%VERSION%.model` +| `targetXmlnsNamespace` | `citrus.test.api.generator.namespace` | XML namespace used by the API | `http://www.citrusframework.org/schema/%VERSION%/%PREFIX%-api` +|=== + + +Note: `%PREFIX%` and `%VERSION%` are placeholders that will be replaced by their specific values as configured. The plugin performs a conversion to lowercase for `PREFIX` used in package names and in `targetXmlnsNamespace`. + +=== Running the generator + +To run the generator, execute the following command in your project directory: + +[source,bash] +---- +mvn citrus-test-api-generator-maven-plugin:create-test-api +---- + + +This command will generate the classes and XSD files as configured for your APIs in the specified locations. + +=== Spring meta file generation + +The `citrus-test-api-generator-maven-plugin` supports the generation of Spring integration files, specifically `spring.handlers` and `spring.schemas`. +These files are essential for Spring applications utilizing XML configuration, as they provide mapping information for custom XML namespaces. + +==== Purpose + +The generated Spring integration files serve the purpose of mapping custom XML namespaces to their corresponding namespace handler and schema +locations. This mapping allows Spring to properly parse and validate XML configuration files containing custom elements and attributes. + +==== Configuration + +The plugin generates these Spring integration files based on the provided configuration in the `citrus-test-api-generator-maven-plugin` section +of the pom.xml file. For each API specified, the plugin writes entries into the `spring.handlers` and `spring.schemas` files according to the +configured XML namespaces and their corresponding handlers and schemas. + +==== Important Consideration + +When there are other non-generated Spring schemas or handlers present in the `META-INF` folder, it's crucial to ensure that the `metaInfFolder` +configuration points to the existing `META-INF` directory in the main resources, which is usually `src/main/resources/META-INF`. This ensures +that the plugin correctly updates the existing files without overwriting them. + +To identify generated schemas, their namespace should include the following segment `citrus-test-schema`. During updates of the meta files, +the generator filters out lines containing this segment from existing files and then re-adds them, preserving any non-generated content. + +==== Usage + +Once generated, the `spring.handlers` and `spring.schemas` files, along with any existing non-generated content, should be included in the +resources of your Spring application. During runtime, Spring will use these files to resolve custom XML namespaces and handle elements accordingly. +This automatically happens if one of the following folders is chosen: + +- `target/generated-test-resources/META-INF` (default) +- `target/generated-resources/META-INF` for pure testing projects that provide their code on main rather than test +- `src/main/resources/META-INF` - for mixing existing meta files with generated + +=== Configuration of the Test Classpath + +In case you choose to generate the API into `generated-test` folders, the maven build requires further configuration +to add the `generated-test` folders to the classpath. +The link:https://www.mojohaus.org/build-helper-maven-plugin/usage.html[build-helper-maven-plugin] is used to accomplish this configuration step. + +[source,xml] +---- + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-test-sources + generate-test-sources + + add-test-source + + + + ${project.build.directory}/generated-test-sources + + + + + add-test-resource + generate-test-resources + + add-test-resource + + + + + ${project.build.directory}/generated-test-resources + + + + + + + + +---- + +=== Sample usage + +To utilize the test API in XML, it's necessary to import the respective namespace. Once imported, requests +can be directly employed as actions, as illustrated in the sample below. Further examples can be found here +`org.citrusframework.openapi.generator.GeneratedApiIT`. + + + +.XML DSL +[source,xml,indent=0,role="secondary"] +---- + + + + + + + + + + + + +---- + + +To utilize the test API in Java, it's necessary to import the API configuration, that provides the respective +request actions. The request to test can then be autowired, configured and autowired, as illustrated in the sample below. +Further examples can be found here `org.citrusframework.openapi.generator.GetPetByIdTest`. + +.Java DSL +[source,java,indent=0,role="secondary"] +---- +@ExtendWith(CitrusSpringExtension.class) +@SpringBootTest(classes = {PetStoreBeanConfiguration.class, CitrusSpringConfig.class}) +class GetPetByIdTest { + + @Autowired + private ApplicationContext applicationContext; + + @Autowired + private GetPetByIdRequest getPetByIdRequest; + + @Test + @CitrusTest + void testByJsonPath(@CitrusResource TestCaseRunner runner) { + + // Given + getPetByIdRequest.setPetId("1234"); + + // Then + getPetByIdRequest.setResponseStatus(HttpStatus.OK.value()); + getPetByIdRequest.setResponseReasonPhrase(HttpStatus.OK.getReasonPhrase()); + + // Assert body by json path + getPetByIdRequest.setResponseValue(Map.of("$.name", "Snoopy")); + + // When + runner.$(getPetByIdRequest); + } +} + +---- diff --git a/test-api-generator/citrus-test-api-generator-core/README.md b/test-api-generator/citrus-test-api-generator-core/README.md deleted file mode 100644 index e9011945dc..0000000000 --- a/test-api-generator/citrus-test-api-generator-core/README.md +++ /dev/null @@ -1,636 +0,0 @@ -# API Generator - -> Generates a Citrus Test-API for OpenAPI and WSDL specifications. - -The generated API can be used from Citrus XML test cases as well as from Citrus test cases implemented in Java. It provides simple access to implementations of the interface. - -Check out [OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) for additional information about the OpenAPI project, including additional libraries with support for other languages and more. - -## Contents - -- [Features at a glance](#features-at-a-glance) -- [Generator cookbook](#generator-cookbook) - - [Using Maven](#configuring-api-generation-with-maven) - - [Using Gradle](#configuring-api-generation-with-gradle) -- [Integrate the API](#integrate-the-api) -- [Configure an XML Editor](#configure-an-xml-editor) -- [Add Namespace to XML Tests](#add-namespace-to-xml-tests) -- [Use the API](#use-the-api) - -## Features at a glance - -SIR OpenAPI Generator for Citrus provides the following features: - -- generation from OpenAPI Specification -- generation from WSDL via an intermediate step that generates a "light" OpenApi specification from a WSDL -- integration into Citrus XML test cases -- integration into Citrus Java test cases -- integration into XML editors via provided XSD - - schema validation - - auto completion -- full validation of outgoing and incoming messages with respect to OpenAPI specification - - schema validation of the payload against the components specified in the specification - - validation of message parameters with respect to type, constraints, required attributes - - note that for WSDL the validation possibilities are limited. The generation of OpenAPI from WSDL currently only takes WSDL bindings and respective binding operations into account and thus is not able to validate any input/output structure against a specification. Thus the only information that is currently taken into account for validation of WSDL based test APIs are the query paths determined from the WSDL bindings. -- validation of responses by all means of standard Citrus message validation -- possibility to determine API coverage -- if the api is generated from a [SIR 2.0](https://api-catalog.e1-pfnet-a.pnet.ch/) managed Open-Api, the following request header information will automatically be added to each API-request: - - pf-itam-app: the name of the application as provided by SIR 2.0 info.x-pf-itam-app - - pf-api-name: the name of the api as provided by SIR 2.0 info.x-citrus-api-name - - pf-api-version: the api version as provided by the Open-Api used for Test-API generation - -## Generator cookbook - -### Requirements - -You need at least have Maven and Java 17 installed and being accessible through the `PATH` -environment variable on your system. - -### Generation Step by Step - -In short the following steps have to be performed in order to generate and use a Test-API: - -1. [Configure the Maven build to create Test-API](#configure-api-generator-using-maven) -2. [Integrate API into Spring by adjusting the following files](#integrate-the-api) - - `META-INF/spring.schemas` - - `META-INF/spring.handlers` -3. [Configure your XML Editor with the generated Test-API-schema.xsd](#configure-an-xml-editor) -4. [Add Test-API namespace to Citrus XML Test case](#add-namespace-to-xml-tests) -5. [Add a request action from the Test-API to your test case](#use-the-api) - -You can, of course, also [use Gradle](#configure-api-generator-using-gradle) to achieve the same. - -### Generate - -Once the API generator is configured according to the above steps, generation of the API can be triggered using Maven. - -## Configuring API Generation with Maven using `citrus-openapi-generator-maven-plugin` - -The standard procedure to generate source and resource files from an OpenAPI specification, involves utilizing the -`openapi-generator-maven-plugin` for API generation, as decribed [here](#configuring-api-generation-with-maven-using-openapi-generator-maven-plugin-). -However, managing multiple API generations can be cumbersome with this approach. Each API requires a separate Maven execution, necessitating -the duplication of properties. To address this challenge, a custom Maven plugin `citrus-openapi-generator-maven-plugin` has been developed. -It operates seamlessly in the background, invoking the `openapi-generator-maven-plugin`, but provides a more user-friendly configuration experience. -With this plugin, multiple APIs can be defined within a single execution, while default values are automatically applied to most properties. -Only two configuration properties -`source` and `prefix`- require explicit specification. In addition, this plugin supports the generation/update -of spring integration files `spring.handlers` and `spring.schemas`. It is recommended to use this Maven plugin for API generation. For a detailed -explanation, on how to use this Maven plugin, check [here](../citrus-test-api-generator-maven-plugin/README.md). If the recommended way does not -suite your needs, you can still use the `openapi-generator-maven-plugin` directly, as outlined in the next chapter. - -## Configuring API Generation with Maven using `openapi-generator-maven-plugin` - -To generate source and resource files from an OpenAPI specification, we use -the [`openapi-generator-maven-plugin`](https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator-maven-plugin). -You need to configure an execution block for each API you want to generate. Below is an example configuration, -whereas `pf-citrus-api-generator.version` represents one of -the [release versions](https://gitlab.pnet.ch/SIR/sir-citrus/-/releases). `openapi.generator.version` should be replaced with the latest -version of the [`openapi-generator-maven-plugin`](https://mvnrepository.com/artifact/org.openapitools/openapi-generator-maven-plugin). - -```xml - - - - - org.openapitools - openapi-generator-maven-plugin - ${openapi.generator.version} - - - - generate-code-from-api-spec - generate-test-sources - - generate - - - - ${project.build.directory}/generated-resources/specs/ping-api.yml - - java-citrus - ${project.build.directory} - - - generated-test-sources - generated-test-resources - schemas/xsd - org.postfinance.my.app - org.postfinance.my.app.ping.api - org.postfinance.my.app.ping.models - Ping - my/api - customApiEndpoint - http://www.citrusframework.org/schema/my/api/ping - - REST - - false - false - false - false - false - - - - - - org.postfinance.citrusframework - pf-citrus-api-generator - ${pf-citrus-api-generator.version} - - - - - -``` - -**Since the API is solely used in test scope, it is strongly recommended to use `generated-test-*` folders as target for the generated -code.** This clearly separates all testing concerns and ensures that no testing sources will be part of the later assembly. The usage of the -test scoped target folders requires another [configuration](#configuration-of-the-test-classpath) that adds the respective folders to the -test classpath. - -### Configuration Options - -| Name | Default | Description | -| -------------------- | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | -| apiEndpoint | `apiEndpoint` | Which http client should be used for the requests. The endpoints are typically configured in the citrus-context.xml. | -| prefix | `Api` | Add a prefix before the name of the files. First character should be upper case. | -| httpPathPrefix | `api` | Add a prefix to http path for all APIs. | -| targetXmlnsNamespace | `http://www.citrusframework.org/schema/api` | Xmlns namespace of the generated schema. | -| resourceFolder | `src/main/resources` | Where the resource files are emitted. | -| sourceFolder | `src/main/java` | Where the sources are emitted. | -| invokerPackage | | Base package for the generated sources. | -| apiPackage | | Package to which the API requests are generated. | -| modelPackage | | Package to which the component classes are generated. | - -For a detailed description of standard configuration parameters please visit the web page of (outside `configOptions`) -the [`openapi-generator-maven-plugin`](https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator-maven-plugin). - -### Configuration of the Test Classpath - -In case you chose to generate the API into generated-test folders, the build needs further configuration to add the generated-test folders -to the classpath. The [`build-helper-maven-plugin`](https://www.mojohaus.org/build-helper-maven-plugin/usage.html) is used to accomplish -this configuration step. - -```xml - - - - org.codehaus.mojo - build-helper-maven-plugin - - - add-test-sources - generate-test-sources - - add-test-source - - - - ${project.build.directory}/generated-test-sources - - - - - add-test-resource - generate-test-resources - - add-test-resource - - - - - ${project.build.directory}/generated-test-resources - - - - - - -``` - -## Configuring API Generation with Gradle - -Configuration for Gradle-based projects is a little bit different from Maven projects. At the top of the `build.gradle` file, add the -following `buildscript`: - -```groovy -buildscript { - dependencies { - classpath "org.postfinance.citrusframework:pf-citrus-api-generator:${citrusVersion}" - } -} -``` - -Later on, include the [`openapi-generator-gradle-plugin`](https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator-gradle-plugin): - -```groovy -plugins { - id "java" - id "org.flywaydb.flyway" version "${flywayVersion}" - id "org.openapi.generator" version "${openapiPluginVersion}" -} -``` - -That enables you to use the custom `java-citrus` generator: - -```groovy -task openApiGeneratePingApi(type: org.openapitools.generator.gradle.plugin.tasks.GenerateTask) { - generatorName = "java-citrus" - inputSpec = "$rootDir/src/main/resources/specs/ping-api.yml".toString() - outputDir = "$buildDir/openapi".toString() - apiPackage = "org.postfinance.my.app.ping.api" - modelPackage = "org.postfinance.my.app.ping.model" - configOptions = [ - apiEndpoint : "applicationServiceClient", - apiType : "REST", - prefix : "Ping", - targetXmlnsNamespace: "http://www.citrusframework.org/schema/my/api/ping", - useTags : "true" - ] - validateSpec = true -} -compileJava.dependsOn "openApiGeneratePingApi" -``` - -As you would do for Maven, add the generated re- and sources to the test source sets as well. Because this modifies the dependency chain of -your build, you must also reconfigure the test resources processing order, as seen below. - -```groovy -sourceSets { - test { - java { - srcDir 'build/openapi/src/main/java' - } - resources { - srcDir 'build/openapi/src/main/resources' - } - } -} - -processTestResources.mustRunAfter openApiGeneratePingApi -``` - -### Configure WSDL for Test-API generation - -The generator also supports Test-API generation from WSDL. Technically, the WSDL is transformed into a lightweight OpenAPI specification -which can then be processed as shown above. In order to create this intermediate OpenApi from WSDL, the -following [`pf-citrus-maven`](https://gitlab.pnet.ch/SIR/sir-citrus/-/blob/canary/pf-citrus-parent/pf-citrus-tools/pf-citrus-maven)plugin -needs to be configured. - -Make sure to replace `pf-citrus-maven.version` with an actual [release version](https://gitlab.pnet.ch/SIR/sir-citrus/-/releases). - -```xml - - - - - org.postfinance.citrusframework - pf-citrus-maven - ${pf-citrus-maven.version} - - - create-open-api-from-wsdl - generate-test-sources - - create-open-api-from-wsdl - - - - - ${project.build.directory}/generated-resources/wsdl/test/test-service.wsdl - - - ${project.build.directory}/generated-resources/generated-tmp-api/ - - test-api-from-wsdl.yaml - - - - - - -``` - -## Integrate the API - -To integrate the API with the Spring framework for usage with Citrus XML test cases the following integration files have to be created or -the relevant information has to be added if they already exist: - -- `META-INF/spring.schemas`: - Maps the namespace of the generated API to the specific XSD provided by the code generator. The namespace is defined in - the `targetXmlnsNamespace` element of the configOptions of the API generation configuration. The XSD itself can found in the configured - **resourceFolder**: `/schema/xsd/test-api-name.xsd`. -- `META-INF/spring.handlers`: - Specifies the handler that manages bean creation from xml elements. This handler is provided by the code generator. - -Following the previous example with the Ping API, the following lines need to be added to the respective files... - -**spring.handlers** - -``` -http\://www.citrusframework.org/schema/my/api/ping=org.postfinance.my.app.ping.citrus.extension.PingNamespaceHandler -``` - -**spring.schemas** - -``` -http\://www.citrusframework.org/schema/my/api/ping/ping-api.xsd=schema/xsd/ping-api.xsd -``` - -## Configure an XML Editor - -In order to use the generated XSD in your favourite XML Editor, configuration is required to resolve the XSD for a given namespace. If -everything has been set up correctly, IntelliJ should detect the schemas automatically. Make sure you used the correct paths otherwise. - -Following the example above and XMLCatalog entry with the following attributes has to be added: - -**location:** target/generated-resources/schema/my/api/ping/ping-api.xsd - -**key:** http://www.citrusframework.org/schema/my/api/ping/ping-api.xsd=schema/xsd/ping-api.xsd - -Note that instead of specifying a mapping of the type above, the location of the XSD file can be specified as a relative path as shown in -the next chapter. - -## Add Namespace to XML Tests - -In order to use the Test-API in an XML test case the Test-API namespace needs to be added to the test case XML file (Lines (4) and (16)). - -``` -(1) -``` - -The mapping of namespace to the URI of the XSD in line (16) has the same purpose as the `spring.schema` integration file. It tells the XML -consumer (editor, parser,...) where to look for the respective XSD. The [configuration of the XML Editor](#configure-xml-editor) maps the -URI to a real file location. As an alternative for this configuration the XSD location can be specified as a relative path in your -filesystem. - -In this case line (16) could be written as: - -``` -(16) http://www.citrusframework.org/schema/ef/api ../../../../schema/xsd/ords-api.xsd> -``` - -Bear in mind that this relative path may change when the xml header with namespace configuration is copied to a different file in a -different folder. - -## Use the API - -The following chapters show in detail how the API can be accessed from Citrus XML/Java test cases. - -### Example XML Test case - -This is an example of a Citrus XML test case that performs a request from a generated test API: - -``` -(1) -(16) -(17) -(18) -(19) -(20) schlathoeltt -(21) 2021-11-11 -(22) FINAL -(23) 001 -(24) Sample execution of an API request. -(25) -(26) -(27) -(28) -(29) -(30) -(32) -(33) -(34) -(35) -(36) -``` - -Line 4: Definition of the namespace of the API XSD schema. - -Line 14: Definition of the schema location. - -Lines 28-32: Adds a request action of the test API for execution. - -Lines 29-31: Evaluation of the response by making use of a json-path expression. - -Note that several different API calls may be added to the actions element of the Citrus test. Response data from a call may be stored into -variables and used for further processing or as input to other API calls. - -A sample for an XML test case that uses the API is given [here](#example-xml-test-case) and its execution using a Java test class is -shown [here](#example-xml-test-case-execution). - -### Example XML test case execution - -To execute this test case, the following test class can be used: - -``` -(1) package org.postfinance.dab.data.trx.kartentrx.webapp.integration; -(2) -(3) import org.postfinance.citrusframework.automation.config.TaConfiguration; -(4) import org.postfinance.citrusframework.test.simulator.PfCitrusSpringExtension; -(5) import com.consol.citrus.annotations.CitrusXmlTest; -(6) import org.junit.jupiter.api.Test; -(7) import org.junit.jupiter.api.extension.ExtendWith; -(8) import org.springframework.boot.test.context.SpringBootTest; -(9) -(10) @SpringBootTest(classes = {TaConfiguration.class}) -(11) @ExtendWith({PfCitrusSpringExtension.class}) -(12) @SuppressWarnings({"squid:S2699"}) -(13) class DabDataKartentrxWebappXmlIntegrationTests { -(14) -(15) private static final String TEST_PACKAGE_NAME = -(16) "org.postfinance.dab.data.trx.kartentrx.webapp.integration"; -(17) -(18) /** -(19) * A simple xml test executed from a Junit test class using @CitrusXmlTest annotation. -(20) */ -(21) @Test -(22) @CitrusXmlTest(name = "KartenTransaktion2Request-dab004Test", packageName = TEST_PACKAGE_NAME) -(23) void testKartenTransaktion2Request() {} -(24) } -``` - -Line 11: Configure a SpringBootTest with required configuration classes. - -Line 12: Use a specific JUnit 5 extension for execution of the test case. - -Line 13: Suppress a specific Sonar Qube warning related to a test case without any assertion. - -Line 22: JUnit Test annotation. - -Line 23: CitrusXmlTest case annotation that specifies the test case name and package. The PfCitrusSpringExtension will take care of loading the xml and executing the test. - -Line 24: The body of the test case method may stay empty. Any code put into the body will be executed after the test case has been executed by the PfCitrusSpringExtension. - -### Example Java Test case execution - -The following test class shows how to call the test API from a test in Junit5. It makes use of the automatic `*BeanConfiguration` class, so -that API requests derived from the OpenAPI specification are automatically available for autowiring. Note that it requires a separate -configuration of an `HttpClient` named `applicationServiceClient`, according to the `apiEndpoint` property of the generator. -See [`pf-citrus-ta`](https://gitlab.pnet.ch/SIR/sir-citrus/-/tree/canary/pf-citrus-parent/pf-citrus-ta#required-http-client) for an example -configuration of it. - -```java -package org.postfinance.dab.data.trx.kartentrx.webapp.integration; - -import static java.lang.String.format; -import static java.util.Arrays.stream; -import static java.util.stream.Collectors.joining; -import static org.apache.commons.lang3.StringUtils.isNotEmpty; -import static org.junit.jupiter.api.Assertions.fail; - -import org.postfinance.citrusframework.automation.config.TaConfiguration; -import org.postfinance.citrusframework.test.simulator.PfCitrusSpringExtension; -import org.postfinance.dab.data.trx.kartentrx.webapp.citrus.KartentrxAbstractTestRequest; -import org.postfinance.dab.data.trx.kartentrx.webapp.requests.DabDataKartentrxNfControllerApi; -import org.postfinance.dab.data.trx.kartentrx.webapp.requests.DabDataKartentrxNfControllerApi.KartenTransaktion2Request; -import org.postfinance.dab.data.trx.kartentrx.webapp.spring.DabDataKartentrxNfControllerBeanConfiguration; -import java.util.List; -import org.citrusframework.DefaultTestCase; -import org.citrusframework.TestAction; -import org.citrusframework.TestCaseRunner; -import org.citrusframework.actions.AbstractTestAction; -import org.citrusframework.annotations.CitrusResource; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.ApplicationContext; - -// ⏩ Use the PF Citrus JUnit 5 extension for execution of the test case. -@ExtendWith({PfCitrusSpringExtension.class}) -// ⏩ Configure a SpringBootTest with required configuration classes. -@SpringBootTest(classes = {TaConfiguration.class, - DabDataKartentrxNfControllerBeanConfiguration.class}) -class DabDataKartentrxWebappJavaIntegrationTests { - - private static List> ALL_TEST_REQUESTS; - - // ⏩ Register the API requests with the Spring context. - // In a future release this will automatically be performed by PfCitrusSpringExtension. - @SuppressWarnings("unchecked") - @BeforeAll - public static void initApi(ApplicationContext applicationCtx) { - // ⏩ Provide all requests as beans via ApplicationContext - Class[] innerClasses = DabDataKartentrxNfControllerApi.class.getClasses(); - ALL_TEST_REQUESTS = - stream(innerClasses) - .filter(AbstractTestAction.class::isAssignableFrom) - .map(ic -> (Class) ic) - .toList(); - } - - // ⏩ A final test that checks if all requests have been executed during test case execution. - // This is a simple way to ensure full test coverage on the generated API. - @AfterAll - public static void coverageCheck() { - String message = ALL_TEST_REQUESTS.stream() - .map(Class::getSimpleName) - .collect(joining("\n")); - - if (isNotEmpty(message)) { - fail( - format( - "Not all API calls covered by test cases. The following calls have not been called: %n%s", - message) - ); - } - } - - @Test - void testKartenTransaktion2Request(ApplicationContext applicationCtx, - @CitrusResource TestCaseRunner testCaseRunner) { - // When - // ⏩ Load the test API request bean. - var kartenTransaktion2Request = getRequestBean(applicationCtx, - KartenTransaktion2Request.class); - // ⏩ Configure the request. - kartenTransaktion2Request.setOidKey("oidKey"); - kartenTransaktion2Request.setOidTyp("1"); - kartenTransaktion2Request.setAufgabeDatum("2021-01-30"); - - // Then - kartenTransaktion2Request.setResource( - "org/postfinance/dab/data/trx/kartentrx/webapp/integration/payloads/kartenTransaktion2Request-resp.json" - ); - // ⏩ Configure the expected response - kartenTransaktion2Request.setResponseStatus(404); - kartenTransaktion2Request.setResponseReasonPhrase("NOT_FOUND"); - // ⏩ Execute the test - runTest(testCaseRunner, kartenTransaktion2Request); - } - - private T getRequestBean( - ApplicationContext applicationCtx, Class type) { - // ⏩ Get the test API request bean from the Spring context and perform configuration for schema validation. - // In a future release request validation will be active by default - T requestBean = applicationCtx.getBean(type); - - // ⏩ Enable validation for the request - requestBean.setSchemaValidation(true); - requestBean.setSchema("oas3"); - - return requestBean; - } - - // ⏩ Execute the test case and record the execution for the coverage check. - private void runTest(TestCaseRunner testCaseRunner, TestAction action) { - ALL_TEST_REQUESTS.remove(action.getClass()); - - var testCase = new DefaultTestCase(); - testCase.addTestAction(action); - testCaseRunner.run(testCase); - } -} -``` - -### Reserved words - -It may happen, that the Yaml specification contains a reserved word (for example a query parameter name is used in a base class or is part of the Java language). Then this name gets a special handling. - -A parameter that has a resvered word clash gets an underline as prefix in the generated API. For example `abstract` becomes `_abstract` and `continue` becomes `_continue`. While these specific naming becomes visible in the usage of the API, the API call itself will of course use the correct naming. - -See the example below: - -``` - - - -``` - -The request will still looks like this: - -``` -/newsletter?name=headliner -``` diff --git a/test-api-generator/citrus-test-api-generator-core/pom.xml b/test-api-generator/citrus-test-api-generator-core/pom.xml index e0ffa60b01..c6366b98ee 100644 --- a/test-api-generator/citrus-test-api-generator-core/pom.xml +++ b/test-api-generator/citrus-test-api-generator-core/pom.xml @@ -93,49 +93,6 @@ - - org.apache.maven.plugins - maven-enforcer-plugin - 3.4.1 - - - enforce-maven - - enforce - - - - - 2.2.0 - - - - - - - - - org.apache.maven.plugins - maven-jar-plugin - 3.3.0 - - - default-jar - package - - jar - - - - test-jar - package - - test-jar - - - - - org.codehaus.mojo build-helper-maven-plugin diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/AbstractTestRequestTest.java b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/AbstractTestRequestTest.java deleted file mode 100644 index c4e1c15d27..0000000000 --- a/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/AbstractTestRequestTest.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.citrusframework.openapi.generator; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.springframework.test.util.ReflectionTestUtils.setField; - -import org.citrusframework.TestAction; -import org.citrusframework.context.TestContext; -import org.citrusframework.http.client.HttpClient; -import org.citrusframework.http.client.HttpEndpointConfiguration; -import org.citrusframework.message.DefaultMessageStore; -import org.citrusframework.message.Message; -import org.citrusframework.message.MessageStore; -import org.citrusframework.openapi.generator.rest.petstore.citrus.PetStoreAbstractTestRequest; -import org.citrusframework.validation.MessageValidatorRegistry; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - - -@ExtendWith(MockitoExtension.class) -class AbstractTestRequestTest { - - @Mock - private HttpClient httpClientMock; - - @Mock - private TestContext testContextMock; - - private MessageStore messageStoreSpy = spy(new DefaultMessageStore()); - - private PetStoreAbstractTestRequest fixture = new PetStoreAbstractTestRequest() { - @Override - public void sendRequest(TestContext context) { - - } - }; - - @BeforeEach - void beforeEachSetup() { - setField(fixture, "httpClient", httpClientMock, HttpClient.class); - } - - @Test - void testReceiveResponse() { - - doReturn(mock(HttpEndpointConfiguration.class)).when(httpClientMock).getEndpointConfiguration(); - doReturn(httpClientMock).when(httpClientMock).createConsumer(); - - Message receiveMessageMock = mock(Message.class); - doReturn(receiveMessageMock).when(httpClientMock).receive(testContextMock, 0); - - doReturn(messageStoreSpy).when(testContextMock).getMessageStore(); - - doReturn(mock(MessageValidatorRegistry.class)).when(testContextMock).getMessageValidatorRegistry(); - - fixture.receiveResponse(testContextMock); - - verify(testContextMock, times(3)).getMessageStore(); - verify(messageStoreSpy, times(2)).constructMessageName(any(TestAction.class), eq(httpClientMock)); - } -} diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/GeneratedApiIT.java b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/GeneratedApiIT.java index bcef336075..0240db1100 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/GeneratedApiIT.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/GeneratedApiIT.java @@ -21,9 +21,7 @@ import java.util.stream.Stream; import org.assertj.core.api.InstanceOfAssertFactories; import org.citrusframework.Citrus; -import org.citrusframework.CitrusContext; import org.citrusframework.CitrusInstanceManager; -import org.citrusframework.CitrusSpringContextProvider; import org.citrusframework.TestAction; import org.citrusframework.TestCase; import org.citrusframework.actions.SendMessageAction.SendMessageActionBuilder; @@ -38,19 +36,21 @@ import org.citrusframework.http.client.HttpEndpointConfiguration; import org.citrusframework.http.message.HttpMessage; import org.citrusframework.json.schema.SimpleJsonSchema; +import org.citrusframework.junit.jupiter.spring.CitrusSpringExtension; import org.citrusframework.message.DefaultMessage; -import org.citrusframework.message.DefaultMessageStore; import org.citrusframework.message.Message; import org.citrusframework.messaging.Producer; import org.citrusframework.messaging.SelectiveConsumer; -import org.citrusframework.testapi.ApiActionBuilderCustomizerService; -import org.citrusframework.testapi.GeneratedApi; import org.citrusframework.openapi.generator.rest.multiparttest.request.MultiparttestControllerApi.PostFileRequest; import org.citrusframework.openapi.generator.rest.petstore.request.PetApi.AddPetRequest; import org.citrusframework.openapi.generator.rest.petstore.request.PetApi.GetPetByIdRequest; import org.citrusframework.spi.Resources; +import org.citrusframework.testapi.ApiActionBuilderCustomizerService; +import org.citrusframework.testapi.GeneratedApi; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.parallel.Isolated; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -58,6 +58,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatcher; import org.mockito.ArgumentMatchers; +import org.mockito.Mock; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -73,6 +74,7 @@ */ @Isolated @DirtiesContext +@ExtendWith({CitrusSpringExtension.class}) @SpringBootTest(classes = {CitrusSpringConfig.class, GeneratedApiIT.Config.class}) @TestPropertySource( properties = {"applicationServiceClient.basic.username=Max Mustermann", @@ -83,553 +85,404 @@ class GeneratedApiIT { @Autowired private ApplicationContext applicationContext; - private HttpClient httpClient; + @Autowired + private HttpClient httpClientMock; + + @Mock + private Producer producerMock; - private Citrus citrus; + @Mock + private SelectiveConsumer consumerMock; - private CitrusContext citrusContext; + private TestContext testContext; @BeforeEach void beforeEach() { - httpClient = (HttpClient) applicationContext.getBean("applicationServiceClient"); - citrus = CitrusInstanceManager.newInstance( - new CitrusSpringContextProvider(applicationContext)); - citrusContext = citrus.getCitrusContext(); - } - - @Test - void testCustomizer() throws IOException { - ApiActionBuilderCustomizerService customizer = (ApiActionBuilderCustomizerService) applicationContext.getBean( - "customizer"); - - Producer producer = mockProducer(httpClient); - - TestContext testContext = applicationContext.getBean(TestContext.class); - Message receiveMessage = createReceiveMessage( - readToString(Resources.create( - "org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage.json"), - StandardCharsets.UTF_8) - ); - - SelectiveConsumer consumer = mockConsumer(httpClient, testContext, receiveMessage); - - testContext.setMessageStore(new DefaultMessageStore()); - - TestCase testCase = executeTest("getPetByIdRequestTest", testContext); - - TestAction testAction = testCase.getActions().get(0); - assertThat(testAction).isInstanceOf(GetPetByIdRequest.class); - - ArgumentMatcher messageMatcher = message -> { - HttpMessage httpMessage = (HttpMessage) message; - assertThat(httpMessage.getCookies().get(0)) - .hasFieldOrPropertyWithValue("name", "c1") - .hasFieldOrPropertyWithValue("value", "v1"); - assertThat(httpMessage.getCookies().get(1)) - .hasFieldOrPropertyWithValue("name", "c2") - .hasFieldOrPropertyWithValue("value", "v2"); - assertThat(httpMessage.getHeader("citrus_request_path")).isEqualTo("/pet/1234"); - assertThat(httpMessage.getHeader("Authorization")).isEqualTo( - "Basic YWRtaW46dG9wLXNlY3JldA=="); - return true; - }; - verify(producer).send(ArgumentMatchers.argThat(messageMatcher), eq(testContext)); - verify(consumer).receive(testContext, 5000L); - } - - @Test - void testBasicAuthorization() throws IOException { - Producer producer = mockProducer(httpClient); - - TestContext testContext = applicationContext.getBean(TestContext.class); - Message receiveMessage = createReceiveMessage( - readToString(Resources.create( - "org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage.json"), - StandardCharsets.UTF_8) - ); - - SelectiveConsumer consumer = mockConsumer(httpClient, testContext, receiveMessage); - - testContext.setMessageStore(new DefaultMessageStore()); - - TestCase testCase = executeTest("getPetByIdRequestTest", testContext); - - TestAction testAction = testCase.getActions().get(0); - assertThat(testAction).isInstanceOf(GetPetByIdRequest.class); - - ArgumentMatcher messageMatcher = message -> { - HttpMessage httpMessage = (HttpMessage) message; - assertThat(httpMessage.getHeader("Authorization")).isEqualTo( - "Basic YWRtaW46dG9wLXNlY3JldA=="); - return true; - }; - verify(producer).send(ArgumentMatchers.argThat(messageMatcher), eq(testContext)); - verify(consumer).receive(testContext, 5000L); - - } - - @Test - void testRequestPath() throws IOException { - Producer producer = mockProducer(httpClient); - - TestContext testContext = applicationContext.getBean(TestContext.class); - Message receiveMessage = createReceiveMessage( - readToString(Resources.create( - "org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage.json"), - StandardCharsets.UTF_8) - ); - - SelectiveConsumer consumer = mockConsumer(httpClient, testContext, receiveMessage); - - testContext.setMessageStore(new DefaultMessageStore()); - - TestCase testCase = executeTest("getPetByIdRequestTest", testContext); - TestAction testAction = testCase.getActions().get(0); - assertThat(testAction).isInstanceOf(GetPetByIdRequest.class); - - ArgumentMatcher messageMatcher = message -> { - HttpMessage httpMessage = (HttpMessage) message; - assertThat(httpMessage.getHeader("citrus_request_path")).isEqualTo("/pet/1234"); - return true; - }; - verify(producer).send(ArgumentMatchers.argThat(messageMatcher), eq(testContext)); - verify(consumer).receive(testContext, 5000L); + testContext = applicationContext.getBean(TestContext.class); } @Test - void testCookies() throws IOException { - - Producer producer = mockProducer(httpClient); - - TestContext testContext = applicationContext.getBean(TestContext.class); - Message receiveMessage = createReceiveMessage( - readToString(Resources.create( - "org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage.json"), - StandardCharsets.UTF_8) - ); - - SelectiveConsumer consumer = mockConsumer(httpClient, testContext, receiveMessage); - - testContext.setMessageStore(new DefaultMessageStore()); - - TestCase testCase = executeTest("getPetByIdRequestTest", testContext); - TestAction testAction = testCase.getActions().get(0); - assertThat(testAction).isInstanceOf(GetPetByIdRequest.class); - - ArgumentMatcher messageMatcher = message -> { - HttpMessage httpMessage = (HttpMessage) message; - Cookie cookie1 = httpMessage.getCookies().get(0); - Cookie cookie2 = httpMessage.getCookies().get(1); - assertThat(cookie1.getName()).isEqualTo("c1"); - assertThat(cookie1.getValue()).isEqualTo("v1"); - assertThat(cookie2.getName()).isEqualTo("c2"); - assertThat(cookie2.getValue()).isEqualTo("v2"); - return true; - }; - verify(producer).send(ArgumentMatchers.argThat(messageMatcher), eq(testContext)); - verify(consumer).receive(testContext, 5000L); - - } - - @Test - void testJsonPathValidation() throws IOException { - mockProducer(httpClient); - - TestContext testContext = applicationContext.getBean(TestContext.class); - Message receiveMessage = createReceiveMessage( - readToString(Resources.create( - "org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage.json"), - StandardCharsets.UTF_8) - ); - - mockConsumer(httpClient, testContext, receiveMessage); - - testContext.setMessageStore(new DefaultMessageStore()); - - TestCase testCase = executeTest("jsonPathValidationTest", testContext); - - TestAction testAction = testCase.getActions().get(0); - assertThat(testAction).isInstanceOf(GetPetByIdRequest.class); - } - - @Test - void testJsonPathExtraction() throws IOException { - mockProducer(httpClient); - - TestContext testContext = applicationContext.getBean(TestContext.class); - Message receiveMessage = createReceiveMessage( - readToString(Resources.create( - "org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage.json"), - StandardCharsets.UTF_8) - ); - - mockConsumer(httpClient, testContext, receiveMessage); - - testContext.setMessageStore(new DefaultMessageStore()); - TestCase testCase = executeTest("jsonPathExtractionTest", testContext); - TestAction testAction = testCase.getActions().get(0); - assertThat(testAction).isInstanceOf(GetPetByIdRequest.class); - - assertThat(testContext.getVariable("name")).isEqualTo("Snoopy"); - assertThat(testContext.getVariable("id")).isEqualTo("12"); - } - - @Test - void testSendWithBody() { - ArgumentMatcher messageMatcher = message -> { - HttpMessage httpMessage = (HttpMessage) message; - try { - assertThat(httpMessage.getPayload()) - .isEqualTo( - readToString(Resources.create( - "org/citrusframework/openapi/generator/GeneratedApiTest/payloads/addPetMessage.json"), - StandardCharsets.UTF_8) - ); - } catch (IOException e) { - throw new CitrusRuntimeException("Unable to parse file!", e); - } - return true; - }; - - sendAndValidateMessage("sendWithBodyTest", messageMatcher); - } - - @Test - void testSendMultipartFile() { - ArgumentMatcher messageMatcher = message -> { - assertThat(message.getPayload()).isInstanceOf(MultiValueMap.class); - MultiValueMap multiValueMap = (MultiValueMap) message.getPayload(); - List multipartFile = multiValueMap.get("multipartFile"); - try { - assertThat(((Resource) multipartFile.get(0)).getURL().toString()) - .endsWith( - "test-classes/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage.json"); - } catch (IOException e) { - throw new CitrusRuntimeException("Unable to parse file!", e); - } - - return true; - }; - - sendAndValidateMessage("postFileTest", messageMatcher, PostFileRequest.class); - } - - @Test - void testSendMultipartWithFileAttribute() { - TestContext testContext = applicationContext.getBean(TestContext.class); - Message payload = createReceiveMessage("{\"id\": 1}"); - Producer producer = mockProducer(httpClient); - mockConsumer(httpClient, testContext, payload); - executeTest("multipartWithFileAttributesTest", testContext); - ArgumentCaptor messageArgumentCaptor = ArgumentCaptor.forClass(Message.class); - verify(producer).send(messageArgumentCaptor.capture(), eq(testContext)); - Object producedMessagePayload = messageArgumentCaptor.getValue().getPayload(); - assertThat(producedMessagePayload).isInstanceOf(MultiValueMap.class); - - Object templateValue = ((MultiValueMap) producedMessagePayload).get("template"); - assertThat(templateValue) - .asInstanceOf(InstanceOfAssertFactories.LIST) - .element(0) - .hasFieldOrPropertyWithValue("path", - "org/citrusframework/openapi/generator/GeneratedApiTest/payloads/MultipartTemplate.xml"); - - Object additionalDataValue = ((MultiValueMap) producedMessagePayload).get( - "additionalData"); - assertThat(additionalDataValue) - .asInstanceOf(InstanceOfAssertFactories.LIST) - .element(0) - .hasFieldOrPropertyWithValue("path", - "org/citrusframework/openapi/generator/GeneratedApiTest/payloads/AdditionalData.json"); - - Object schemaValue = ((MultiValueMap) producedMessagePayload).get("_schema"); - assertThat(schemaValue) - .asInstanceOf(InstanceOfAssertFactories.LIST) - .element(0) - .hasFieldOrPropertyWithValue("path", - "org/citrusframework/openapi/generator/GeneratedApiTest/payloads/Schema.json"); - } - - @Test - void testSendMultipartWithPlainText() { - TestContext testContext = applicationContext.getBean(TestContext.class); - String expectedPayload = - "{template=[ ], additionalData=[ {\"data1\":\"value1\"} ], _schema=[ {\"schema\":\"mySchema\"} ]}"; - Message payload = createReceiveMessage("{\"id\": 1}"); - Producer producer = mockProducer(httpClient); - mockConsumer(httpClient, testContext, payload); - executeTest("multipartWithPlainTextTest", testContext); - ArgumentCaptor messageArgumentCaptor = ArgumentCaptor.forClass(Message.class); - verify(producer).send(messageArgumentCaptor.capture(), eq(testContext)); - String producedMessagePayload = normalizeWhitespace( - messageArgumentCaptor.getValue().getPayload().toString(), - true, - true - ); - assertThat(producedMessagePayload).isEqualTo(expectedPayload); - } - - @Test - void testSendMultipartWithMultipleDatatypes() { - TestContext testContext = applicationContext.getBean(TestContext.class); - String expectedPayload = "{stringData=[Test], booleanData=[true], integerData=[1]}"; - Message payload = createReceiveMessage("{\"id\": 1}"); - Producer producer = mockProducer(httpClient); - mockConsumer(httpClient, testContext, payload); - executeTest("multipartWithMultipleDatatypesTest", testContext); - ArgumentCaptor messageArgumentCaptor = ArgumentCaptor.forClass(Message.class); - verify(producer).send(messageArgumentCaptor.capture(), eq(testContext)); - String producedMessagePayload = normalizeWhitespace( - messageArgumentCaptor.getValue().getPayload().toString(), - true, - true - ); - assertThat(producedMessagePayload).isEqualTo(expectedPayload); - } - - @Test - void testSendWithBodyLiteral() { - ArgumentMatcher messageMatcher = message -> { - HttpMessage httpMessage = (HttpMessage) message; - assertThat(((String) httpMessage.getPayload()).trim()).isEqualTo("{\"id\": 13}"); - return true; - }; - - sendAndValidateMessage("sendWithBodyLiteralTest", messageMatcher); - } - - @Test - void testSendWithExtraHeaders() { - ArgumentMatcher messageMatcher = message -> { - HttpMessage httpMessage = (HttpMessage) message; - assertThat(httpMessage.getHeader("h1")).isEqualTo("v1"); - assertThat(httpMessage.getHeader("h2")).isEqualTo("v2"); - return true; - }; - - sendAndValidateMessage("sendWithExtraHeaderTest", messageMatcher); + void testValidationFailure() { + mockProducerAndConsumer(createReceiveMessage("{\"some\": \"payload\"}")); + assertThatThrownBy( + () -> executeTest("getPetByIdRequestTest", testContext)).hasCauseExactlyInstanceOf( + ValidationException.class); } - @Test - void testXCitrusApiHeaders() { - ArgumentMatcher messageMatcher = message -> { - HttpMessage httpMessage = (HttpMessage) message; - assertThat(httpMessage.getHeader("x-citrus-api-name")).isEqualTo("petstore"); - assertThat(httpMessage.getHeader("x-citrus-app")).isEqualTo("PETS"); - assertThat(httpMessage.getHeader("x-citrus-api-version")).isEqualTo("1.0.0"); - return true; - }; - - sendAndValidateMessage("sendWithBodyLiteralTest", messageMatcher); - } + @Nested + class WithValidationMatcher { - @Test - void testSendWithBodyLiteralWithVariable() { - ArgumentMatcher messageMatcher = message -> { - HttpMessage httpMessage = (HttpMessage) message; - assertThat(((String) httpMessage.getPayload()).trim()).isEqualTo("{\"id\": 15}"); - return true; - }; - - sendAndValidateMessage("sendWithBodyLiteralWithVariableTest", messageMatcher); - } + @BeforeEach + void beforeEach() { + mockProducerAndConsumer(createReceiveMessage("")); + } - @Test - void testJsonPathValidationFailure() throws IOException { - mockProducer(httpClient); + @Test + void testSendWithBody() { + ArgumentMatcher messageMatcher = message -> { + HttpMessage httpMessage = (HttpMessage) message; + try { + assertThat(httpMessage.getPayload()) + .isEqualTo( + readToString(Resources.create( + "org/citrusframework/openapi/generator/GeneratedApiTest/payloads/addPetMessage.json"), + StandardCharsets.UTF_8) + ); + } catch (IOException e) { + throw new CitrusRuntimeException("Unable to parse file!", e); + } + return true; + }; - TestContext testContext = applicationContext.getBean(TestContext.class); + sendAndValidateMessage("sendWithBodyTest", messageMatcher); + } - Message receiveMessage = createReceiveMessage( - readToString(Resources.create( - "org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage.json"), - StandardCharsets.UTF_8) - ); + @Test + void testSendWithBodyLiteralWithVariable() { + ArgumentMatcher messageMatcher = message -> { + HttpMessage httpMessage = (HttpMessage) message; + assertThat(((String) httpMessage.getPayload()).trim()).isEqualTo("{\"id\": 15}"); + return true; + }; + sendAndValidateMessage("sendWithBodyLiteralWithVariableTest", messageMatcher); + } - mockConsumer(httpClient, testContext, receiveMessage); + @Test + void testXCitrusApiHeaders() { + ArgumentMatcher messageMatcher = message -> { + HttpMessage httpMessage = (HttpMessage) message; + assertThat(httpMessage.getHeader("x-citrus-api-name")).isEqualTo("petstore"); + assertThat(httpMessage.getHeader("x-citrus-app")).isEqualTo("PETS"); + assertThat(httpMessage.getHeader("x-citrus-api-version")).isEqualTo("1.0.0"); + return true; + }; - assertThatThrownBy(() -> executeTest("jsonPathValidationFailureTest", testContext)) - .hasCauseExactlyInstanceOf(ValidationException.class); - } + sendAndValidateMessage("sendWithBodyLiteralTest", messageMatcher); + } - @Test - void testValidationFailure() { - mockProducer(httpClient); + @Test + void testSendWithExtraHeaders() { + ArgumentMatcher messageMatcher = message -> { + HttpMessage httpMessage = (HttpMessage) message; + assertThat(httpMessage.getHeader("h1")).isEqualTo("v1"); + assertThat(httpMessage.getHeader("h2")).isEqualTo("v2"); + return true; + }; - TestContext testContext = applicationContext.getBean(TestContext.class); + sendAndValidateMessage("sendWithExtraHeaderTest", messageMatcher); + } - Message receiveMessage = createReceiveMessage("{\"some\": \"payload\"}"); + @Test + void testSendWithBodyLiteral() { + ArgumentMatcher messageMatcher = message -> { + HttpMessage httpMessage = (HttpMessage) message; + assertThat(((String) httpMessage.getPayload()).trim()).isEqualTo("{\"id\": 13}"); + return true; + }; - mockConsumer(httpClient, testContext, receiveMessage); + sendAndValidateMessage("sendWithBodyLiteralTest", messageMatcher); + } - testContext.setMessageStore(new DefaultMessageStore()); + private void sendAndValidateMessage(String testName, + ArgumentMatcher messageMatcher) { + GeneratedApiIT.this.sendAndValidateMessage(testName, messageMatcher, + AddPetRequest.class); + } - assertThatThrownBy( - () -> executeTest("getPetByIdRequestTest", testContext)).hasCauseExactlyInstanceOf( - ValidationException.class); } - /** - * Test the send message using the given matcher - */ - @Test - void jsonSchemaValidationTest() throws IOException { - mockProducer(httpClient); + @Nested + class WithMultipartMessage { - TestContext testContext = applicationContext.getBean(TestContext.class); + @Test + void testSendMultipartFile() { + mockProducerAndConsumer(createReceiveMessage("")); - Message receiveMessage = createReceiveMessage( - readToString(Resources.create( - "org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage.json"), - StandardCharsets.UTF_8) - ); - - mockConsumer(httpClient, testContext, receiveMessage); + ArgumentMatcher messageMatcher = message -> { + assertThat(message.getPayload()).isInstanceOf(MultiValueMap.class); + MultiValueMap multiValueMap = (MultiValueMap) message.getPayload(); + List multipartFile = multiValueMap.get("multipartFile"); + try { + assertThat(((Resource) multipartFile.get(0)).getURL().toString()) + .endsWith( + "test-classes/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage1.json"); + } catch (IOException e) { + throw new CitrusRuntimeException("Unable to parse file!", e); + } - testContext.setMessageStore(new DefaultMessageStore()); + return true; + }; - SimpleJsonSchema testSchema = (SimpleJsonSchema) applicationContext.getBean("testSchema"); - Mockito.clearInvocations(testSchema, testSchema.getSchema()); + sendAndValidateMessage("postFileTest", messageMatcher, PostFileRequest.class); + } - TestCase testCase = executeTest("jsonSchemaValidationTest", testContext); + @Test + void testSendMultipartWithFileAttribute() { + Message payload = createReceiveMessage("{\"id\": 1}"); + mockProducerAndConsumer(payload); + + executeTest("multipartWithFileAttributesTest", testContext); + ArgumentCaptor messageArgumentCaptor = ArgumentCaptor.forClass(Message.class); + verify(producerMock).send(messageArgumentCaptor.capture(), eq(testContext)); + Object producedMessagePayload = messageArgumentCaptor.getValue().getPayload(); + assertThat(producedMessagePayload).isInstanceOf(MultiValueMap.class); + + Object templateValue = ((MultiValueMap) producedMessagePayload).get("template"); + assertThat(templateValue) + .asInstanceOf(InstanceOfAssertFactories.LIST) + .element(0) + .hasFieldOrPropertyWithValue("path", + "org/citrusframework/openapi/generator/GeneratedApiTest/payloads/MultipartTemplate.xml"); + + Object additionalDataValue = ((MultiValueMap) producedMessagePayload).get( + "additionalData"); + assertThat(additionalDataValue) + .asInstanceOf(InstanceOfAssertFactories.LIST) + .element(0) + .hasFieldOrPropertyWithValue("path", + "org/citrusframework/openapi/generator/GeneratedApiTest/payloads/AdditionalData.json"); + + Object schemaValue = ((MultiValueMap) producedMessagePayload).get("_schema"); + assertThat(schemaValue) + .asInstanceOf(InstanceOfAssertFactories.LIST) + .element(0) + .hasFieldOrPropertyWithValue("path", + "org/citrusframework/openapi/generator/GeneratedApiTest/payloads/Schema.json"); + } - assertTestActionType(testCase, GetPetByIdRequest.class); + @Test + void testSendMultipartWithPlainText() { + mockProducerAndConsumer(createReceiveMessage("{\"id\": 1}")); + executeTest("multipartWithPlainTextTest", testContext); + ArgumentCaptor messageArgumentCaptor = ArgumentCaptor.forClass(Message.class); + verify(producerMock).send(messageArgumentCaptor.capture(), eq(testContext)); + String producedMessagePayload = normalizeWhitespace( + messageArgumentCaptor.getValue().getPayload().toString(), + true, + true + ); + + String expectedPayload = + "{template=[ ], additionalData=[ {\"data1\":\"value1\"} ], _schema=[ {\"schema\":\"mySchema\"} ]}"; + assertThat(producedMessagePayload).isEqualTo(expectedPayload); + } - // Assert that schema validation was called - verify(testSchema).getSchema(); - JsonSchema schema = testSchema.getSchema(); - verify(schema).validate(any()); + @Test + void testSendMultipartWithMultipleDatatypes() { + Message receiveMessage = createReceiveMessage("{\"id\": 1}"); + mockProducerAndConsumer(receiveMessage); + + executeTest("multipartWithMultipleDatatypesTest", testContext); + ArgumentCaptor messageArgumentCaptor = ArgumentCaptor.forClass(Message.class); + verify(producerMock).send(messageArgumentCaptor.capture(), eq(testContext)); + String producedMessagePayload = normalizeWhitespace( + messageArgumentCaptor.getValue().getPayload().toString(), + true, + true + ); + + String expectedPayload = "{stringData=[Test], booleanData=[true], integerData=[1]}"; + assertThat(producedMessagePayload).isEqualTo(expectedPayload); + } } - @Test - void defaultOas3SchemaValidationTest() throws IOException { - mockProducer(httpClient); + @Nested + class WithDefaultReceiveMessage { - TestContext testContext = applicationContext.getBean(TestContext.class); + private Message defaultRecieveMessage; - Message receiveMessage = createReceiveMessage( - readToString(Resources.create( - "org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage.json"), - StandardCharsets.UTF_8) - ); + @BeforeEach + void beforeEach() throws IOException { + defaultRecieveMessage = createReceiveMessage( + readToString(Resources.create( + "org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage1.json"), + StandardCharsets.UTF_8) + ); + mockProducerAndConsumer(defaultRecieveMessage); + } - mockConsumer(httpClient, testContext, receiveMessage); + @Test + void testJsonPathExtraction() { + TestCase testCase = executeTest("jsonPathExtractionTest", testContext); + TestAction testAction = testCase.getActions().get(0); + assertThat(testAction).isInstanceOf(GetPetByIdRequest.class); - testContext.setMessageStore(new DefaultMessageStore()); + assertThat(testContext.getVariable("name")).isEqualTo("Snoopy"); + assertThat(testContext.getVariable("id")).isEqualTo("12"); + } - SimpleJsonSchema testSchema = (SimpleJsonSchema) applicationContext.getBean("oas3"); - Mockito.clearInvocations(testSchema, testSchema.getSchema()); + @Test + void testCustomizer() { + TestCase testCase = executeTest("getPetByIdRequestTest", testContext); - TestCase testCase = executeTest("defaultOas3SchemaValidationTest", testContext); + TestAction testAction = testCase.getActions().get(0); + assertThat(testAction).isInstanceOf(GetPetByIdRequest.class); - assertTestActionType(testCase, GetPetByIdRequest.class); + ArgumentMatcher messageMatcher = message -> { + HttpMessage httpMessage = (HttpMessage) message; + assertThat(httpMessage.getHeader("x-citrus-api-version")).isEqualTo( + "1.0.0"); - // Assert that schema validation was called - verify(testSchema).getSchema(); - JsonSchema schema = testSchema.getSchema(); - verify(schema).validate(any()); - } - - /** - * Test the send message using the given matcher - */ - @Test - void jsonDeactivatedSchemaValidationTest() throws IOException { - mockProducer(httpClient); - - TestContext testContext = applicationContext.getBean(TestContext.class); - - Message receiveMessage = createReceiveMessage( - readToString(Resources.create( - "org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage.json"), - StandardCharsets.UTF_8) - ); - - mockConsumer(httpClient, testContext, receiveMessage); + return true; + }; + verify(producerMock).send(ArgumentMatchers.argThat(messageMatcher), eq(testContext)); + verify(consumerMock).receive(testContext, 5000L); + } - testContext.setMessageStore(new DefaultMessageStore()); + @Test + void testBasicAuthorization() { + TestCase testCase = executeTest("getPetByIdRequestTest", testContext); - SimpleJsonSchema testSchema = (SimpleJsonSchema) applicationContext.getBean("testSchema"); - Mockito.clearInvocations(testSchema, testSchema.getSchema()); + TestAction testAction = testCase.getActions().get(0); + assertThat(testAction).isInstanceOf(GetPetByIdRequest.class); - TestCase testCase = executeTest("jsonDeactivatedSchemaValidationTest", testContext); + ArgumentMatcher messageMatcher = message -> { + HttpMessage httpMessage = (HttpMessage) message; + assertThat(httpMessage.getHeader("Authorization")).isEqualTo( + "Basic YWRtaW46dG9wLXNlY3JldA=="); + return true; + }; + verify(producerMock).send(ArgumentMatchers.argThat(messageMatcher), eq(testContext)); + verify(consumerMock).receive(testContext, 5000L); + } - assertTestActionType(testCase, GetPetByIdRequest.class); + @Test + void testRequestPath() { + TestCase testCase = executeTest("getPetByIdRequestTest", testContext); + TestAction testAction = testCase.getActions().get(0); + assertThat(testAction).isInstanceOf(GetPetByIdRequest.class); - // Assert that schema validation was called - Mockito.verifyNoInteractions(testSchema); - } + ArgumentMatcher messageMatcher = message -> { + HttpMessage httpMessage = (HttpMessage) message; + assertThat(httpMessage.getHeader("citrus_request_path")).isEqualTo("/pet/1234"); + return true; + }; + verify(producerMock).send(ArgumentMatchers.argThat(messageMatcher), eq(testContext)); + verify(consumerMock).receive(testContext, 5000L); + } - /** - * Test the send message using the given matcher - */ - @Test - void jsonSchemaValidationFailureTest() throws IOException { - mockProducer(httpClient); + @Test + void testCookies() { + TestCase testCase = executeTest("getPetByIdRequestTest", testContext); + TestAction testAction = testCase.getActions().get(0); + assertThat(testAction).isInstanceOf(GetPetByIdRequest.class); + + ArgumentMatcher messageMatcher = message -> { + HttpMessage httpMessage = (HttpMessage) message; + Cookie cookie1 = httpMessage.getCookies().get(0); + Cookie cookie2 = httpMessage.getCookies().get(1); + assertThat(cookie1.getName()).isEqualTo("c1"); + assertThat(cookie1.getValue()).isEqualTo("v1"); + assertThat(cookie2.getName()).isEqualTo("c2"); + assertThat(cookie2.getValue()).isEqualTo("v2"); + return true; + }; + verify(producerMock).send(ArgumentMatchers.argThat(messageMatcher), eq(testContext)); + verify(consumerMock).receive(testContext, 5000L); + } - TestContext testContext = applicationContext.getBean(TestContext.class); + @Test + void testJsonPathValidation() { + TestCase testCase = executeTest("jsonPathValidationTest", testContext); + assertTestActionType(testCase, GetPetByIdRequest.class); + } - Message receiveMessage = createReceiveMessage( - readToString(Resources.create( - "org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage.json"), - StandardCharsets.UTF_8) - ); + @Test + void scriptValidationFailureTest() { + TestCase testCase = executeTest("scriptValidationTest", testContext); + assertTestActionType(testCase, GetPetByIdRequest.class); + } - mockConsumer(httpClient, testContext, receiveMessage); + @Test + void jsonSchemaValidationFailureTest() { + assertThatThrownBy(() -> executeTest("jsonSchemaValidationFailureTest", testContext)) + .hasCauseExactlyInstanceOf(ValidationException.class); - testContext.setMessageStore(new DefaultMessageStore()); + SimpleJsonSchema testSchema = (SimpleJsonSchema) applicationContext.getBean( + "failingTestSchema"); - assertThatThrownBy(() -> executeTest("jsonSchemaValidationFailureTest", testContext)) - .hasCauseExactlyInstanceOf(ValidationException.class); + // Assert that schema validation was called + verify(testSchema).getSchema(); + JsonSchema schema = testSchema.getSchema(); + verify(schema).validate(any()); + } - SimpleJsonSchema testSchema = (SimpleJsonSchema) applicationContext.getBean( - "failingTestSchema"); + @Test + void jsonDeactivatedSchemaValidationTest() { + SimpleJsonSchema testSchema = (SimpleJsonSchema) applicationContext.getBean( + "testSchema"); + Mockito.clearInvocations(testSchema, testSchema.getSchema()); - // Assert that schema validation was called - verify(testSchema).getSchema(); - JsonSchema schema = testSchema.getSchema(); - verify(schema).validate(any()); - } + TestCase testCase = executeTest("jsonDeactivatedSchemaValidationTest", testContext); - @Test - void scriptValidationTest() throws IOException { - mockProducer(httpClient); + assertTestActionType(testCase, GetPetByIdRequest.class); - TestContext testContext = applicationContext.getBean(TestContext.class); + // Assert that schema validation was called + Mockito.verifyNoInteractions(testSchema); + } - Message receiveMessage = createReceiveMessage( - readToString(Resources.create( - "org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage.json"), - StandardCharsets.UTF_8) - ); + @Test + void defaultOas3SchemaValidationTest() { + SimpleJsonSchema testSchema = (SimpleJsonSchema) applicationContext.getBean("oas3"); + Mockito.clearInvocations(testSchema, testSchema.getSchema()); - mockConsumer(httpClient, testContext, receiveMessage); + TestCase testCase = executeTest("defaultOas3SchemaValidationTest", testContext); - testContext.setMessageStore(new DefaultMessageStore()); + assertTestActionType(testCase, GetPetByIdRequest.class); - TestCase testCase = executeTest("scriptValidationTest", testContext); + // Assert that schema validation was called + verify(testSchema).getSchema(); + JsonSchema schema = testSchema.getSchema(); + verify(schema).validate(any()); + } - assertTestActionType(testCase, GetPetByIdRequest.class); - } + @Test + void jsonSchemaValidationTest() { + SimpleJsonSchema testSchema = (SimpleJsonSchema) applicationContext.getBean( + "testSchema"); + Mockito.clearInvocations(testSchema, testSchema.getSchema()); - @Test - void scriptValidationFailureTest() throws IOException { - mockProducer(httpClient); + TestCase testCase = executeTest("jsonSchemaValidationTest", testContext); - TestContext testContext = applicationContext.getBean(TestContext.class); + assertTestActionType(testCase, GetPetByIdRequest.class); - Message receiveMessage = createReceiveMessage( - readToString(Resources.create( - "org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage.json"), - StandardCharsets.UTF_8) - ); + // Assert that schema validation was called + verify(testSchema).getSchema(); + JsonSchema schema = testSchema.getSchema(); + verify(schema).validate(any()); + } - mockConsumer(httpClient, testContext, receiveMessage); + @Test + void testJsonPathValidationFailure() { + mockProducerAndConsumer(defaultRecieveMessage); - testContext.setMessageStore(new DefaultMessageStore()); + assertThatThrownBy(() -> executeTest("jsonPathValidationFailureTest", testContext)) + .hasCauseExactlyInstanceOf(ValidationException.class); + } - TestCase testCase = executeTest("scriptValidationTest", testContext); + private static Stream testValidationFailures() { + return Stream.of( + Arguments.of("failOnStatusTest", + "Values not equal for header element 'citrus_http_status_code', expected '201' but was '200'"), + Arguments.of( + "failOnReasonPhraseTest", + "Values not equal for header element 'citrus_http_reason_phrase', expected 'Almost OK' but was 'OK'" + ), + Arguments.of( + "failOnVersionTest", + "Values not equal for header element 'citrus_http_version', expected 'HTTP/1.0' but was 'HTTP/1.1'" + ) + ); + } - assertTestActionType(testCase, GetPetByIdRequest.class); + @ParameterizedTest + @MethodSource + void testValidationFailures(String testName, String expectedErrorMessage) { + assertThatThrownBy(() -> executeTest(testName, testContext)) + .hasCauseExactlyInstanceOf(ValidationException.class) + .message() + .startsWith(expectedErrorMessage); + } } // @Test @@ -651,12 +504,12 @@ void scriptValidationFailureTest() throws IOException { // } // ); // -// TestContext testContext = applicationContext.getBean(TestContext.class); +// // // mockProducer(httpClient); // // Message receiveMessage = createReceiveMessage( -// FileUtils.readToString(Resources.create("org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage.json"), StandardCharsets.UTF_8) +// FileUtils.readToString(Resources.create("org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage1.json"), StandardCharsets.UTF_8) // ); // // mockConsumer(httpClient, testContext, receiveMessage); @@ -666,42 +519,16 @@ void scriptValidationFailureTest() throws IOException { // assertThat(logMessages.get(0)).isEqualTo("getPetById;GET;\"{}\";\"\";\"\""); // } - @ParameterizedTest - @MethodSource - void testValidationFailures(String testName, String expectedErrorMessage) throws IOException { - mockProducer(httpClient); - - TestContext testContext = applicationContext.getBean(TestContext.class); - - Message receiveMessage = createReceiveMessage( - readToString(Resources.create( - "org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage.json"), - StandardCharsets.UTF_8) - ); - - mockConsumer(httpClient, testContext, receiveMessage); - - testContext.setMessageStore(new DefaultMessageStore()); + /** + * Test the send message using the given matcher + */ + private void sendAndValidateMessage(String testName, ArgumentMatcher messageMatcher, + Class apiClass) { - assertThatThrownBy(() -> executeTest(testName, testContext)) - .hasCauseExactlyInstanceOf(ValidationException.class) - .message() - .startsWith(expectedErrorMessage); - } + TestCase testCase = executeTest(testName, testContext); + assertTestActionType(testCase, apiClass); - private static Stream testValidationFailures() { - return Stream.of( - Arguments.of("failOnStatusTest", - "Values not equal for header element 'citrus_http_status_code', expected '201' but was '200'"), - Arguments.of( - "failOnReasonPhraseTest", - "Values not equal for header element 'citrus_http_reason_phrase', expected 'Almost OK' but was 'OK'" - ), - Arguments.of( - "failOnVersionTest", - "Values not equal for header element 'citrus_http_version', expected 'HTTP/1.0' but was 'HTTP/1.1'" - ) - ); + verify(producerMock).send(ArgumentMatchers.argThat(messageMatcher), eq(testContext)); } /** @@ -717,48 +544,18 @@ private void assertTestActionType(TestCase testCase, Class apiClass) { assertThat(testAction).isNotNull(); } - private void sendAndValidateMessage(String testName, ArgumentMatcher messageMatcher) { - this.sendAndValidateMessage(testName, messageMatcher, AddPetRequest.class); - } - - /** - * Test the send message using the given matcher - */ - private void sendAndValidateMessage(String testName, ArgumentMatcher messageMatcher, - Class apiClass) { - Producer producer = mockProducer(httpClient); - - TestContext testContext = applicationContext.getBean(TestContext.class); - - Message receiveMessage = createReceiveMessage(""); - - mockConsumer(httpClient, testContext, receiveMessage); - - testContext.setMessageStore(new DefaultMessageStore()); - - TestCase testCase = executeTest(testName, testContext); - - assertTestActionType(testCase, apiClass); - - verify(producer).send(ArgumentMatchers.argThat(messageMatcher), eq(testContext)); - } - - private Producer mockProducer(HttpClient httpClient) { - Producer producer = mock(Producer.class); - when(httpClient.createProducer()).thenReturn(producer); - return producer; - } - - private SelectiveConsumer mockConsumer(HttpClient httpClient, TestContext testContext, - Message receiveMessage) { - SelectiveConsumer consumer = mock(SelectiveConsumer.class); - when(httpClient.createConsumer()).thenReturn(consumer); - when(consumer.receive(testContext, 5000L)).thenReturn(receiveMessage); - return consumer; + private void mockProducerAndConsumer(Message receiveMessage) { + when(httpClientMock.createProducer()).thenReturn(producerMock); + when(httpClientMock.createConsumer()).thenReturn(consumerMock); + when(consumerMock.receive(testContext, 5000L)).thenReturn(receiveMessage); } private TestCase executeTest(String testName, TestContext testContext) { - TestLoader loader = new SpringXmlTestLoader().citrusContext(citrusContext).citrus(citrus) + assertThat(CitrusInstanceManager.get()).isPresent(); + + Citrus citrus = CitrusInstanceManager.get().get(); + TestLoader loader = new SpringXmlTestLoader().citrusContext(citrus.getCitrusContext()) + .citrus(citrus) .context(testContext); loader.setTestName(testName); loader.setPackageName("org.citrusframework.openapi.generator.GeneratedApiTest"); @@ -780,50 +577,50 @@ public static class Config { @Bean(name = {"applicationServiceClient", "multipartTestEndpoint", "soapSampleStoreEndpoint", "petStoreEndpoint"}) public HttpClient applicationServiceClient() { - HttpClient client = mock(HttpClient.class); - EndpointConfiguration endpointConfiguration = mock(EndpointConfiguration.class); - when(client.getEndpointConfiguration()).thenReturn(new HttpEndpointConfiguration()); - when(endpointConfiguration.getTimeout()).thenReturn(5000L); - return client; + HttpClient clientMock = mock(); + EndpointConfiguration endpointConfigurationMock = mock(); + when(clientMock.getEndpointConfiguration()).thenReturn(new HttpEndpointConfiguration()); + when(endpointConfigurationMock.getTimeout()).thenReturn(5000L); + return clientMock; } @Bean public ApiActionBuilderCustomizerService customizer() { - ApiActionBuilderCustomizerService customizer = new ApiActionBuilderCustomizerService() { + return new ApiActionBuilderCustomizerService() { @Override public > T build( GeneratedApi generatedApi, TestAction action, TestContext context, T builder) { - builder.getMessageBuilderSupport().header("x-citrus-api-version", generatedApi.getApiVersion()); + builder.getMessageBuilderSupport() + .header("x-citrus-api-version", generatedApi.getApiVersion()); return builder; } }; - return customizer; } @Bean({"oas3", "testSchema"}) public SimpleJsonSchema testSchema() { - JsonSchema schema = mock(JsonSchema.class); - SimpleJsonSchema jsonSchema = mock(SimpleJsonSchema.class); + JsonSchema schemaMock = mock(); + SimpleJsonSchema jsonSchemaMock = mock(); - when(jsonSchema.getSchema()).thenReturn(schema); + when(jsonSchemaMock.getSchema()).thenReturn(schemaMock); Set okReport = new HashSet<>(); - when(schema.validate(any())).thenReturn(okReport); - return jsonSchema; + when(schemaMock.validate(any())).thenReturn(okReport); + return jsonSchemaMock; } @Bean public SimpleJsonSchema failingTestSchema() { - JsonSchema schema = mock(JsonSchema.class); - SimpleJsonSchema jsonSchema = mock(SimpleJsonSchema.class); + JsonSchema schemaMock = mock(); + SimpleJsonSchema jsonSchemaMock = mock(); - when(jsonSchema.getSchema()).thenReturn(schema); + when(jsonSchemaMock.getSchema()).thenReturn(schemaMock); Set nokReport = new HashSet<>(); nokReport.add(new ValidationMessage.Builder().customMessage( "This is a simulated validation error message").build()); - when(schema.validate(any())).thenReturn(nokReport); - return jsonSchema; + when(schemaMock.validate(any())).thenReturn(nokReport); + return jsonSchemaMock; } } } diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/GetPetByIdTest.java b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/GetPetByIdTest.java new file mode 100644 index 0000000000..6421cee946 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/GetPetByIdTest.java @@ -0,0 +1,254 @@ +package org.citrusframework.openapi.generator; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.citrusframework.container.Assert.Builder.assertException; +import static org.citrusframework.util.FileUtils.readToString; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import org.citrusframework.TestCaseRunner; +import org.citrusframework.annotations.CitrusResource; +import org.citrusframework.annotations.CitrusTest; +import org.citrusframework.config.CitrusSpringConfig; +import org.citrusframework.context.TestContext; +import org.citrusframework.endpoint.EndpointConfiguration; +import org.citrusframework.http.client.HttpClient; +import org.citrusframework.http.client.HttpEndpointConfiguration; +import org.citrusframework.junit.jupiter.spring.CitrusSpringExtension; +import org.citrusframework.message.DefaultMessage; +import org.citrusframework.message.Message; +import org.citrusframework.messaging.Producer; +import org.citrusframework.messaging.SelectiveConsumer; +import org.citrusframework.openapi.generator.GetPetByIdTest.Config; +import org.citrusframework.openapi.generator.rest.petstore.request.PetApi.GetPetByIdRequest; +import org.citrusframework.openapi.generator.rest.petstore.spring.PetStoreBeanConfiguration; +import org.citrusframework.spi.Resources; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpStatus; + +@ExtendWith(CitrusSpringExtension.class) +@SpringBootTest(classes = {PetStoreBeanConfiguration.class, CitrusSpringConfig.class, Config.class}) +class GetPetByIdTest { + + @Autowired + private GetPetByIdRequest getPetByIdRequest; + + @Autowired + @Qualifier("petStoreEndpoint") + private HttpClient httpClient; + + private String defaultResponse; + + @BeforeEach + public void beforeTest() throws IOException { + defaultResponse = readToString(Resources.create( + "org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage1.json"), + StandardCharsets.UTF_8) ; + + mockProducer(); + mockConsumer(); + } + + /** + * TODO #1161 - Improve with builder pattern + */ + @Test + @CitrusTest + void testByJsonPath(@CitrusResource TestCaseRunner runner) { + + // Given + getPetByIdRequest.setPetId("1234"); + + // Then + getPetByIdRequest.setResponseStatus(HttpStatus.OK.value()); + getPetByIdRequest.setResponseReasonPhrase(HttpStatus.OK.getReasonPhrase()); + + // Assert body by json path + getPetByIdRequest.setResponseValue(Map.of("$.name", "Snoopy")); + + // When + runner.$(getPetByIdRequest); + } + + /** + * TODO #1161 - Improve with builder pattern + */ + @Test + @CitrusTest + void testValidationFailureByJsonPath(@CitrusResource TestCaseRunner runner) { + + // Given + getPetByIdRequest.setPetId("1234"); + + // Then + getPetByIdRequest.setResponseStatus(HttpStatus.OK.value()); + getPetByIdRequest.setResponseReasonPhrase(HttpStatus.OK.getReasonPhrase()); + + // Assert body by json path + getPetByIdRequest.setResponseValue(Map.of("$.name", "Garfield")); + + // When + runner.$(assertException() + .exception(org.citrusframework.exceptions.CitrusRuntimeException.class) + .message("Values not equal for element '$.name', expected 'Garfield' but was 'Snoopy'") + .when( + getPetByIdRequest + ) + ); + // When + + } + + /** + * TODO #1161 - Improve with builder pattern + */ + @Test + @CitrusTest + void testByResource(@CitrusResource TestCaseRunner runner) { + + // Given + getPetByIdRequest.setPetId("1234"); + + // Then + getPetByIdRequest.setResponseStatus(HttpStatus.OK.value()); + getPetByIdRequest.setResponseReasonPhrase(HttpStatus.OK.getReasonPhrase()); + // Assert body by resource + getPetByIdRequest.setResource( + "org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage1.json"); + + // When + runner.$(getPetByIdRequest); + } + + /** + * TODO #1161 - Improve with builder pattern + */ + @Test + @CitrusTest + void testValidationFailureByResource(@CitrusResource TestCaseRunner runner) { + + // Given + getPetByIdRequest.setPetId("1234"); + + // Then + getPetByIdRequest.setResponseStatus(HttpStatus.OK.value()); + getPetByIdRequest.setResponseReasonPhrase(HttpStatus.OK.getReasonPhrase()); + // Assert body by resource + getPetByIdRequest.setResource( + "org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage2.json"); + + // When + runner.$(assertException() + .exception(org.citrusframework.exceptions.CitrusRuntimeException.class) + .message("Values not equal for entry: '$['name']', expected 'Garfield' but was 'Snoopy'") + .when( + getPetByIdRequest + ) + ); + } + + /** + * TODO #1161 - Improve with builder pattern + */ + @Test + @CitrusTest + void validateByVariable(@CitrusResource TestContext testContext, + @CitrusResource TestCaseRunner runner) { + + // Given + getPetByIdRequest.setPetId("1234"); + + // Then + getPetByIdRequest.setResponseStatus(HttpStatus.OK.value()); + getPetByIdRequest.setResponseReasonPhrase(HttpStatus.OK.getReasonPhrase()); + + // Assert load data into variables + getPetByIdRequest.setResponseVariable(Map.of("$", "RESPONSE", "$.id", "ID")); + + // When + runner.$(getPetByIdRequest); + + // Then + assertThat(testContext) + .satisfies( + c -> assertThat(c.getVariable("RESPONSE")) + .isNotNull(), + c -> assertThat(c.getVariable("ID")) + .isNotNull() + .isEqualTo("12") + ); + } + + /** + * TODO #1161 - Improve with builder pattern + */ + @Test + @CitrusTest + void validateReceivedResponse(@CitrusResource TestContext testContext) { + + // Given + getPetByIdRequest.setPetId("1234"); + + // When + getPetByIdRequest.sendRequest(testContext); + + // Then + Message receiveResponse = getPetByIdRequest.receiveResponse(testContext); + assertThat(receiveResponse) + .isNotNull() + .extracting(Message::getPayload) + .asString() + .isEqualToIgnoringWhitespace(defaultResponse); + assertThat(receiveResponse.getHeaders()) + .containsEntry("citrus_http_status_code", 200) + .containsEntry("citrus_http_reason_phrase", "OK"); + } + + private void mockProducer() { + Producer producerMock = mock(); + when(httpClient.createProducer()).thenReturn(producerMock); + } + + private void mockConsumer() { + Message receiveMessage = createReceiveMessage(); + + SelectiveConsumer consumer = mock(SelectiveConsumer.class); + when(httpClient.createConsumer()).thenReturn(consumer); + when(consumer.receive(any(), eq(5000L))).thenReturn(receiveMessage); + } + + private Message createReceiveMessage() { + Message receiveMessage = new DefaultMessage(); + receiveMessage.setPayload(defaultResponse); + receiveMessage.getHeaders().put("citrus_http_reason_phrase", "OK"); + receiveMessage.getHeaders().put("citrus_http_version", "HTTP/1.1"); + receiveMessage.getHeaders().put("Content-Type", 200); + receiveMessage.getHeaders().put("citrus_http_status_code", 200); + return receiveMessage; + } + + @TestConfiguration + public static class Config { + + @Bean(name = {"applicationServiceClient", "petStoreEndpoint"}) + public HttpClient applicationServiceClient() { + HttpClient client = mock(HttpClient.class); + EndpointConfiguration endpointConfiguration = mock(EndpointConfiguration.class); + when(client.getEndpointConfiguration()).thenReturn(new HttpEndpointConfiguration()); + when(endpointConfiguration.getTimeout()).thenReturn(5000L); + return client; + } + } +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/getPetByIdRequestTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/getPetByIdRequestTest.xml index 316401ac05..c6f1afc8b5 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/getPetByIdRequestTest.xml +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/getPetByIdRequestTest.xml @@ -16,7 +16,7 @@ - + diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage.json b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage1.json similarity index 100% rename from test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage.json rename to test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage1.json diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage2.json b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage2.json new file mode 100644 index 0000000000..267e7887a0 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage2.json @@ -0,0 +1,6 @@ +{ + "id": 12, + "name": "Garfield", + "tags": ["comic cat"], + "photoUrls": ["url1", "url2"] +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/postFileTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/postFileTest.xml index fd3734f2cf..a04b808966 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/postFileTest.xml +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/postFileTest.xml @@ -15,7 +15,7 @@ diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/request/MultiparttestControllerApi.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/request/MultiparttestControllerApi.java index b729f170f7..36d2bca5f4 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/request/MultiparttestControllerApi.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/request/MultiparttestControllerApi.java @@ -21,6 +21,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.ClassPathResource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -28,7 +30,9 @@ import org.citrusframework.openapi.generator.rest.multiparttest.citrus.MultipartTestAbstractTestRequest; import java.io.IOException; +import java.util.Base64; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.stream.Collectors; diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/request/PetApi.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/request/PetApi.java index 54dcf3c78c..a014b1c53f 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/request/PetApi.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/request/PetApi.java @@ -21,6 +21,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.ClassPathResource; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.util.LinkedMultiValueMap; @@ -31,6 +32,7 @@ import java.io.IOException; import java.util.Base64; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.stream.Collectors; diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/request/StoreApi.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/request/StoreApi.java index 5bbd0c7e53..406f97f2f9 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/request/StoreApi.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/request/StoreApi.java @@ -20,11 +20,19 @@ import org.citrusframework.util.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.core.io.ClassPathResource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import org.citrusframework.openapi.generator.rest.petstore.citrus.PetStoreAbstractTestRequest; import java.io.IOException; +import java.util.Base64; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.stream.Collectors; diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/request/UserApi.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/request/UserApi.java index 571502f297..01f4558954 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/request/UserApi.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/request/UserApi.java @@ -20,11 +20,19 @@ import org.citrusframework.util.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.core.io.ClassPathResource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import org.citrusframework.openapi.generator.rest.petstore.citrus.PetStoreAbstractTestRequest; import java.io.IOException; +import java.util.Base64; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.stream.Collectors; diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/citrus/OpenApiFromWsdlAbstractTestRequest.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/citrus/OpenApiFromWsdlAbstractTestRequest.java index c95877349d..8acda64ca3 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/citrus/OpenApiFromWsdlAbstractTestRequest.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/citrus/OpenApiFromWsdlAbstractTestRequest.java @@ -11,6 +11,7 @@ import java.util.ServiceLoader; import org.citrusframework.actions.AbstractTestAction; import org.citrusframework.context.TestContext; +import org.citrusframework.http.actions.HttpClientRequestActionBuilder; import org.citrusframework.testapi.ApiActionBuilderCustomizerService; import org.citrusframework.testapi.GeneratedApi; import org.citrusframework.spi.Resources; diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/request/BookServiceSoapApi.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/request/BookServiceSoapApi.java index bdc4df06c5..dd84878034 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/request/BookServiceSoapApi.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/request/BookServiceSoapApi.java @@ -27,6 +27,8 @@ import org.slf4j.LoggerFactory; import org.springframework.util.CollectionUtils; +import org.citrusframework.openapi.generator.soap.bookservice.citrus.OpenApiFromWsdlAbstractTestRequest; + @Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen") public class BookServiceSoapApi implements GeneratedApi { diff --git a/test-api-generator/citrus-test-api-generator-maven-plugin/README.md b/test-api-generator/citrus-test-api-generator-maven-plugin/README.md deleted file mode 100644 index a36c24a80b..0000000000 --- a/test-api-generator/citrus-test-api-generator-maven-plugin/README.md +++ /dev/null @@ -1,162 +0,0 @@ -# Citrus OpenAPI Generator Maven Plugin - -## Overview - -The Citrus OpenAPI Generator Maven Plugin is designed to facilitate the integration of multiple OpenAPI specifications into the Citrus testing framework by automatically generating necessary test classes and XSDs. This plugin wraps the `org.openapitools.codegen.plugin.CodeGenMojo` and extends its functionality to support multiple API configurations. - -## Features - -- **Multiple API Configurations**: Easily configure multiple OpenAPI specifications to generate test APIs with specific prefixes. -- **Citrus Integration**: Generates classes and XSDs tailored for use within the Citrus framework, streamlining the process of creating robust integration tests. - -## Getting Started - -### Prerequisites - -Ensure that Maven is installed on your system. You can verify this by running `mvn -v` in your terminal. If Maven is not installed, please follow the installation instructions provided at [Apache Maven Official Site](https://maven.apache.org). - -### Installation - -To use this plugin, you need to include it as a dependency in your Maven project. Add the following configuration to your `pom.xml` file inside the `` section of the ``: - -#### Minimal configuration for multiple APIS - -```xml - - citrus-test-api-generator-maven-plugin - - - - - Multi1 - api/test-api.yml - - - Multi2 - api/test-api.yml - - - Multi3 - api/test-api.yml - - - - - - - create-test-api - - - - -``` - -#### Full configuration for a single API - -```xml - - - citrus-test-api-generator-maven-plugin - - - - my-generated-sources - my-generated-resources - myschema/xsd - src/main/resources/META-INF - - Full - api/test-api.yml - org.mypackage.%PREFIX%.api - myEndpoint - org.mypackage.%PREFIX%.invoker - org.mypackage.%PREFIX%.model - "http://company/citrus-test-api/myNamespace" - - - - - - - - create-test-api - - - - -``` - -### Usage - -After including the plugin in your project, you can run it by executing: - -```bash -mvn citrus-test-api-generator-maven-plugin:create-test-api -``` - -This command will generate the classes and XSD files as configured for your APIs in the specified locations. - -## Configuration Options - -Here are the primary elements you can configure in the `` section: - -| Configuration element | Maven Property | Description | Default Value | -| -------------------------------- | ------------------------------------------------------------- | ----------------------------------------------------------------- |----------------------------------------------------------------| -| `schemaFolder` | `citrus.test.api.generator.schema.folder` | Location for the generated XSD schemas. | `schema/xsd/%VERSION%` | -| `resourceFolder` | `citrus.test.api.generator.resource.folder` | Location to which resources are generated. | `generated-resources` | -| `sourceFolder` | `citrus.test.api.generator.source.folder` | Location to which sources are generated. | `generated-sources` | -| `metaInfFolder` | `citrus.test.api.generator.meta.inf.folder` | Location to which spring meta files are generated/updated. | `target/generated-test-resources/META-INF` | -| `generateSpringIntegrationFiles` | `citrus.test.api.generator.generate.spring.integration.files` | Specifies whether spring integration files should be generated. | `true` | -| Nested api element | | | | -| `prefix` | `citrus.test.api.generator.prefix` | Specifies the prefix used for the test API, typically an acronym. | (no default, required) | -| `source` | `citrus.test.api.generator.source` | Specifies the source of the test API. | (no default, required) | -| `version` | `citrus.test.api.generator.version` | Specifies the version of the API, may be null. | (none) | -| `endpoint` | `citrus.test.api.generator.endpoint` | Specifies the endpoint of the test API. | `applicationServiceClient` | -| `type` | `citrus.test.api.generator.type` | Specifies the type of the test API. | `REST`, other option is `SOAP` | -| `useTags` | `citrus.test.api.generator.use.tags` | Specifies whether tags should be used by the generator. | `true` | -| `invokerPackage` | `citrus.test.api.generator.invoker.package` | Package for the test API classes. | `org.citrusframework.automation.%PREFIX%.%VERSION%` | -| `apiPackage` | `citrus.test.api.generator.api.package` | Package for the test API interface classes. | `org.citrusframework.automation.%PREFIX%.%VERSION%.api` | -| `modelPackage` | `citrus.test.api.generator.model.package` | Package for the test API model classes. | `org.citrusframework.automation.%PREFIX%.%VERSION%.model` | -| `targetXmlnsNamespace` | `citrus.test.api.generator.namespace` | XML namespace used by the API. | `http://www.citrusframework.org/schema/%VERSION%/%PREFIX%-api` | - -Note: `%PREFIX%` and `%VERSION%` are placeholders that will be replaced by their specific values as configured. The plugin performs a conversion to lowercase for `PREFIX` used in package names and in `targetXmlnsNamespace`. - -## Spring meta file generation - -The `citrus-test-api-generator-maven-plugin` supports the generation of Spring integration files, specifically `spring.handlers` and `spring.schemas`. -These files are essential for Spring applications utilizing XML configuration, as they provide mapping information for custom XML namespaces. - -### Purpose - -The generated Spring integration files serve the purpose of mapping custom XML namespaces to their corresponding namespace handler and schema -locations. This mapping allows Spring to properly parse and validate XML configuration files containing custom elements and attributes. - -### Configuration - -The plugin generates these Spring integration files based on the provided configuration in the `citrus-test-api-generator-maven-plugin` section -of the pom.xml file. For each API specified, the plugin writes entries into the `spring.handlers` and `spring.schemas` files according to the -configured XML namespaces and their corresponding handlers and schemas. - -### Important Consideration - -When there are other non-generated Spring schemas or handlers present in the `META-INF` folder, it's crucial to ensure that the `metaInfFolder` -configuration points to the existing `META-INF` directory in the main resources, which is usually `src/main/resources/META-INF`. This ensures -that the plugin correctly updates the existing files without overwriting them. - -To identify generated schemas, their namespace should include the following segment `citrus-test-schema`. During updates of the meta files, -the generator filters out lines containing this segment from existing files and then re-adds them, preserving any non-generated content. - -### Usage - -Once generated, the `spring.handlers` and `spring.schemas` files, along with any existing non-generated content, should be included in the -resources of your Spring application. During runtime, Spring will use these files to resolve custom XML namespaces and handle elements accordingly. -This should automatically happen if one of the following folders is chosen: - -- `target/generated-test-resources/META-INF` (default) -- `target/generated-resources/META-INF` for pure testing projects that provide their code on main rather than test -- `src/main/resources/META-INF` - for mixing existing meta files with generated - -## Contributing - -Contributions are welcome! If you have suggestions for improving the plugin or have identified issues, please feel free to submit a pull request or open an issue in the project repository.