diff --git a/core/citrus-api/src/main/java/org/citrusframework/message/MessagePayloadUtils.java b/core/citrus-api/src/main/java/org/citrusframework/message/MessagePayloadUtils.java index 09145650a7..4c2a8824c8 100644 --- a/core/citrus-api/src/main/java/org/citrusframework/message/MessagePayloadUtils.java +++ b/core/citrus-api/src/main/java/org/citrusframework/message/MessagePayloadUtils.java @@ -212,4 +212,38 @@ public static String prettyPrintJson(String payload) { } return sb.toString(); } + + /** + * Normalizes the given text by replacing all whitespace characters (identified by {@link Character#isWhitespace) by a single space + * and replacing windows style line endings with unix style line endings. + */ + public static String normalizeWhitespace(String text, boolean normalizeWhitespace, boolean normalizeLineEndingsToUnix) { + if (text == null || text.isEmpty()) { + return text; + } + + if (normalizeWhitespace) { + StringBuilder result = new StringBuilder(); + boolean lastWasSpace = true; + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (Character.isWhitespace(c)) { + if (!lastWasSpace) { + result.append(' '); + } + lastWasSpace = true; + } else { + result.append(c); + lastWasSpace = false; + } + } + return result.toString().trim(); + } + + if (normalizeLineEndingsToUnix) { + return text.replaceAll("\\r(\\n)?", "\n"); + } + + return text; + } } diff --git a/core/citrus-api/src/main/java/org/citrusframework/testapi/GeneratedApi.java b/core/citrus-api/src/main/java/org/citrusframework/testapi/GeneratedApi.java new file mode 100644 index 0000000000..a3d6bfdad2 --- /dev/null +++ b/core/citrus-api/src/main/java/org/citrusframework/testapi/GeneratedApi.java @@ -0,0 +1,45 @@ +package org.citrusframework.testapi; + +import java.util.Map; + +/** + * Interface representing a generated API from an OpenAPI specification. + * Provides methods to retrieve metadata about the API such as title, version, + * prefix, and information extensions. + */ +public interface GeneratedApi { + + /** + * Retrieves the title of the OpenAPI specification, as specified in the info section of the API. + * + * @return the title of the OpenAPI specification + */ + String getApiTitle(); + + /** + * Retrieves the version of the OpenAPI specification, as specified in the info section of the API. + * + * @return the version of the OpenAPI specification + */ + String getApiVersion(); + + /** + * Retrieves the prefix used for the API, as specified in the API generation configuration. + * + * @return the prefix used for the API + */ + String getApiPrefix(); + + /** + * Retrieves the specification extensions of the OpenAPI defined in the "info" section. + *

+ * Specification extensions, also known as vendor extensions, are custom key-value pairs used to describe extra + * functionality not covered by the standard OpenAPI Specification. These properties start with "x-". + * This method collects only the extensions defined in the "info" section of the API. + *

+ * + * @return a map containing the specification extensions defined in the "info" section of the API, + * where keys are extension names and values are extension values + */ + Map getApiInfoExtensions(); +} \ No newline at end of file diff --git a/core/citrus-api/src/main/java/org/citrusframework/testapi/GeneratedApiRequest.java b/core/citrus-api/src/main/java/org/citrusframework/testapi/GeneratedApiRequest.java new file mode 100644 index 0000000000..5b519ac118 --- /dev/null +++ b/core/citrus-api/src/main/java/org/citrusframework/testapi/GeneratedApiRequest.java @@ -0,0 +1,29 @@ +package org.citrusframework.testapi; + +/** + * Interface representing a generated API request corresponding to an operation in an OpenAPI specification. + * Provides methods to retrieve metadata about the request such as operation name, HTTP method, and path. + */ +public interface GeneratedApiRequest { + + /** + * Retrieves the name of the OpenAPI operation associated with the request. + * + * @return the name of the OpenAPI operation + */ + String getOperationName(); + + /** + * Retrieves the HTTP method used for the request. + * + * @return the HTTP method used for the request (e.g., GET, POST) + */ + String getMethod(); + + /** + * Retrieves the path used for the request. + * + * @return the path used for the request + */ + String getPath(); +} diff --git a/core/citrus-api/src/test/java/org/citrusframework/message/MessagePayloadUtilsTest.java b/core/citrus-api/src/test/java/org/citrusframework/message/MessagePayloadUtilsTest.java index 369385adc4..d89f582038 100644 --- a/core/citrus-api/src/test/java/org/citrusframework/message/MessagePayloadUtilsTest.java +++ b/core/citrus-api/src/test/java/org/citrusframework/message/MessagePayloadUtilsTest.java @@ -16,56 +16,79 @@ package org.citrusframework.message; +import static org.testng.Assert.assertEquals; + import org.testng.Assert; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class MessagePayloadUtilsTest { @Test public void shouldPrettyPrintJson() { - Assert.assertEquals(MessagePayloadUtils.prettyPrint(""), ""); - Assert.assertEquals(MessagePayloadUtils.prettyPrint("{}"), "{}"); - Assert.assertEquals(MessagePayloadUtils.prettyPrint("[]"), "[]"); - Assert.assertEquals(MessagePayloadUtils.prettyPrint("{\"user\":\"citrus\"}"), + assertEquals(MessagePayloadUtils.prettyPrint(""), ""); + assertEquals(MessagePayloadUtils.prettyPrint("{}"), "{}"); + assertEquals(MessagePayloadUtils.prettyPrint("[]"), "[]"); + assertEquals(MessagePayloadUtils.prettyPrint("{\"user\":\"citrus\"}"), String.format("{%n \"user\": \"citrus\"%n}")); - Assert.assertEquals(MessagePayloadUtils.prettyPrint("{\"text\":\"\"}"), + assertEquals(MessagePayloadUtils.prettyPrint("{\"text\":\"\"}"), String.format("{%n \"text\": \"\"%n}")); - Assert.assertEquals(MessagePayloadUtils.prettyPrint(String.format("%n%n { \"user\":%n%n \"citrus\" }")), + assertEquals(MessagePayloadUtils.prettyPrint(String.format("%n%n { \"user\":%n%n \"citrus\" }")), String.format("{%n \"user\": \"citrus\"%n}")); - Assert.assertEquals(MessagePayloadUtils.prettyPrint("{\"user\":\"citrus\",\"age\": 32}"), + assertEquals(MessagePayloadUtils.prettyPrint("{\"user\":\"citrus\",\"age\": 32}"), String.format("{%n \"user\": \"citrus\",%n \"age\": 32%n}")); - Assert.assertEquals(MessagePayloadUtils.prettyPrint("[22, 32]"), + assertEquals(MessagePayloadUtils.prettyPrint("[22, 32]"), String.format("[%n22,%n32%n]")); - Assert.assertEquals(MessagePayloadUtils.prettyPrint("[{\"user\":\"citrus\",\"age\": 32}]"), + assertEquals(MessagePayloadUtils.prettyPrint("[{\"user\":\"citrus\",\"age\": 32}]"), String.format("[%n {%n \"user\": \"citrus\",%n \"age\": 32%n }%n]")); - Assert.assertEquals(MessagePayloadUtils.prettyPrint("[{\"user\":\"citrus\",\"age\": 32}, {\"user\":\"foo\",\"age\": 99}]"), + assertEquals(MessagePayloadUtils.prettyPrint("[{\"user\":\"citrus\",\"age\": 32}, {\"user\":\"foo\",\"age\": 99}]"), String.format("[%n {%n \"user\": \"citrus\",%n \"age\": 32%n },%n {%n \"user\": \"foo\",%n \"age\": 99%n }%n]")); - Assert.assertEquals(MessagePayloadUtils.prettyPrint("{\"user\":\"citrus\",\"age\": 32,\"pet\":{\"name\": \"fluffy\", \"age\": 4}}"), + assertEquals(MessagePayloadUtils.prettyPrint("{\"user\":\"citrus\",\"age\": 32,\"pet\":{\"name\": \"fluffy\", \"age\": 4}}"), String.format("{%n \"user\": \"citrus\",%n \"age\": 32,%n \"pet\": {%n \"name\": \"fluffy\",%n \"age\": 4%n }%n}")); - Assert.assertEquals(MessagePayloadUtils.prettyPrint("{\"user\":\"citrus\",\"age\": 32,\"pets\":[\"fluffy\",\"hasso\"]}"), + assertEquals(MessagePayloadUtils.prettyPrint("{\"user\":\"citrus\",\"age\": 32,\"pets\":[\"fluffy\",\"hasso\"]}"), String.format("{%n \"user\": \"citrus\",%n \"age\": 32,%n \"pets\": [%n \"fluffy\",%n \"hasso\"%n ]%n}")); - Assert.assertEquals(MessagePayloadUtils.prettyPrint("{\"user\":\"citrus\",\"pets\":[\"fluffy\",\"hasso\"],\"age\": 32}"), + assertEquals(MessagePayloadUtils.prettyPrint("{\"user\":\"citrus\",\"pets\":[\"fluffy\",\"hasso\"],\"age\": 32}"), String.format("{%n \"user\": \"citrus\",%n \"pets\": [%n \"fluffy\",%n \"hasso\"%n ],%n \"age\": 32%n}")); - Assert.assertEquals(MessagePayloadUtils.prettyPrint("{\"user\":\"citrus\",\"pets\":[{\"name\": \"fluffy\", \"age\": 4},{\"name\": \"hasso\", \"age\": 2}],\"age\": 32}"), + assertEquals(MessagePayloadUtils.prettyPrint("{\"user\":\"citrus\",\"pets\":[{\"name\": \"fluffy\", \"age\": 4},{\"name\": \"hasso\", \"age\": 2}],\"age\": 32}"), String.format("{%n \"user\": \"citrus\",%n \"pets\": [%n {%n \"name\": \"fluffy\",%n \"age\": 4%n },%n {%n \"name\": \"hasso\",%n \"age\": 2%n }%n ],%n \"age\": 32%n}")); } @Test public void shouldPrettyPrintXml() { - Assert.assertEquals(MessagePayloadUtils.prettyPrint(""), ""); - Assert.assertEquals(MessagePayloadUtils.prettyPrint(""), + assertEquals(MessagePayloadUtils.prettyPrint(""), ""); + assertEquals(MessagePayloadUtils.prettyPrint(""), String.format("%n%n")); - Assert.assertEquals(MessagePayloadUtils.prettyPrint("Citrus rocks!"), + assertEquals(MessagePayloadUtils.prettyPrint("Citrus rocks!"), String.format("%n Citrus rocks!%n%n")); - Assert.assertEquals(MessagePayloadUtils.prettyPrint("Citrus rocks!"), + assertEquals(MessagePayloadUtils.prettyPrint("Citrus rocks!"), String.format("%n%n Citrus rocks!%n%n")); - Assert.assertEquals(MessagePayloadUtils.prettyPrint(String.format("%n%nCitrus rocks!%n%n")), + assertEquals(MessagePayloadUtils.prettyPrint(String.format("%n%nCitrus rocks!%n%n")), String.format("%n Citrus rocks!%n%n")); - Assert.assertEquals(MessagePayloadUtils.prettyPrint(String.format("%n %nCitrus rocks!%n %n")), + assertEquals(MessagePayloadUtils.prettyPrint(String.format("%n %nCitrus rocks!%n %n")), String.format("%n Citrus rocks!%n%n")); - Assert.assertEquals(MessagePayloadUtils.prettyPrint(String.format("%n%n ")), + assertEquals(MessagePayloadUtils.prettyPrint(String.format("%n%n ")), String.format("%n %n %n %n%n")); - Assert.assertEquals(MessagePayloadUtils.prettyPrint(String.format("")), + assertEquals(MessagePayloadUtils.prettyPrint(String.format("")), String.format("%n %n %n %n%n")); } + + @DataProvider + public Object[][] normalizeWhitespaceProvider() { + return new Object[][] { + // Test data: payload, ignoreWhitespace, ignoreNewLineType, expected result + {"Hello \t\r\nWorld\r\n", true, true, "Hello World"}, + {"Hello \t\r\nWorld\r\n", true, false, "Hello World"}, + {"Hello \t\r\nWorld\r\n", false, true, "Hello \t\nWorld\n"}, + {"Hello \t\r\nWorld\r\n", false, false, "Hello \t\r\nWorld\r\n"}, + {"", true, true, ""}, + {"", false, false, ""}, + {null, true, true, null}, + {null, false, false, null} + }; + } + + @Test(dataProvider = "normalizeWhitespaceProvider") + public void normalizeWhitespace(String text, boolean normalizeWhitespace, boolean normalizeLineEndingsToUnix, String expected) { + assertEquals(MessagePayloadUtils.normalizeWhitespace(text, normalizeWhitespace, normalizeLineEndingsToUnix), expected); + } } diff --git a/core/citrus-base/src/main/java/org/citrusframework/testapi/ApiActionBuilderCustomizerService.java b/core/citrus-base/src/main/java/org/citrusframework/testapi/ApiActionBuilderCustomizerService.java new file mode 100644 index 0000000000..2b77d97f7b --- /dev/null +++ b/core/citrus-base/src/main/java/org/citrusframework/testapi/ApiActionBuilderCustomizerService.java @@ -0,0 +1,13 @@ +package org.citrusframework.testapi; + +import org.citrusframework.TestAction; +import org.citrusframework.actions.SendMessageAction.SendMessageActionBuilder; +import org.citrusframework.context.TestContext; + +/** + * Implementors of this interface are used to customize the SendMessageActionBuilder with application specific information. E.g. cookies + * or transactionIds. + */ +public interface ApiActionBuilderCustomizerService { + > T build(GeneratedApi generatedApi, TestAction action, TestContext context, T builder); +} diff --git a/pom.xml b/pom.xml index 26fbca512b..bede18edff 100644 --- a/pom.xml +++ b/pom.xml @@ -175,6 +175,7 @@ 1.6.13 3.9.0 3.13.1 + 3.3.0 3.0.1 3.3.1 1.11.2 @@ -246,6 +247,7 @@ 3.2.0 4.1.105.Final 4.12.0 + 7.5.0 4.7.6 3.0.4 4.21.0 @@ -511,6 +513,13 @@ ${wsdl4j.version} + + org.openapitools + openapi-generator + ${openapi-generator-maven-plugin} + provided + + org.springframework spring-test @@ -859,6 +868,11 @@ jackson-databind ${jackson.version} + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + ${jackson.version} + com.fasterxml.jackson.module jackson-module-jaxb-annotations diff --git a/src/manual/connector-openapi.adoc b/src/manual/connector-openapi.adoc index 53333ff1f0..b89f3889c2 100644 --- a/src/manual/connector-openapi.adoc +++ b/src/manual/connector-openapi.adoc @@ -309,3 +309,401 @@ Also, the server will verify the HTTP request method, the Content-Type header as The given HTTP status code defines the response that should be sent by the server. The server will generate a proper response according to the OpenAPI specification. This also includes a potential response message body (e.g. pet object). + +[[openapi-server]] +=== OpenAPI Test API Generator + +For an even deeper integration with a given OpenAPI, Citrus offers the possibility to generate a dedicated Test API which provides test actions tailored to the specific operations of the OpenAPI under evaluation. +These actions can be used in XML or Java DSL. +This functionality is provided by the `Citrus OpenAPI Test API Generator` which 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. + +The 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 generated XSD +*** schema validation +*** auto completion +* integration into Citrus Java test cases via Java DSL [TODO #1161] + +The following directory structure/table specifies the files, which are generated by the generator. +Note that the `Prefix` is a configuration parameter which should uniquely identify a generated API. +It is specified in the build configuration for the Test API. +``` +target/ +├───generated-test-resources/ +│ ├───META-INF/ +│ │ ├───spring.handlers +│ │ └───spring.schemas +│ └───schema/ +│ └───xsd/ +│ └───prefix-api.xsd +└───generated-test-sources/ + └───org/ + └───citrusframework/ + └───automation/ + └───prefix/ + ├───api/ + │ └───MyApi.java + ├───citrus/ + │ ├───extension/ + │ │ └───PrefixNamespaceHandler.java + │ ├───PrefixAbstractTestRequest.java + │ └───PrefixBeanDefinitionParser.java + ├───model/ + │ ├───MyReqTypeA.java + │ └───MyReqTypeB.java + └───spring/ + └───PrefixBeanConfiguration.java +``` + +|=== +| File | Content + +| `spring.handlers` | Spring namespace handler configuration, that contains all NamespaceHandlers for all generated APIs. +| `spring.schemas` | Spring schema definitions, with mappings of namespaces to schemas for all generated APIs. +| `prefix-api.xsd` | XSD schema for the integration of the Test API into XML. +| `PrefixNamespaceHandler.java` | A Spring class, that registers bean definition parsers for Test API XML elements. +| `PrefixAbstractTestRequest.java` | Abstract superclass of all Test API actions. +| `PrefixBeanDefinitionParser.java` | Spring bean definition parser, responsible for parsing Test API XML elements into test actions. +| `MyReqTypeA.java, MyReqTypeB.java` | Model files generated with respect to the schema definition of the OpenAPI. +| `PrefixBeanConfiguration.java` | A Spring @Configuration class, that registers all Test API actions as Spring beans. +|=== + +==== Configuration of Test API generation + +Code generation is typically performed during the build process. +For the Citrus Test API Generator, it 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 + + + + + +---- + +These 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 maven 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/src/manual/index.adoc b/src/manual/index.adoc index 9ae7fccb18..9d4c2ec4a0 100644 --- a/src/manual/index.adoc +++ b/src/manual/index.adoc @@ -62,6 +62,8 @@ include::endpoint-restdocs.adoc[] include::endpoint-component.adoc[] include::endpoint-adapter.adoc[] +include::testapi.adoc[] + include::connectors.adoc[] include::functions.adoc[] diff --git a/test-api-generator/citrus-test-api-generator-core/pom.xml b/test-api-generator/citrus-test-api-generator-core/pom.xml new file mode 100644 index 0000000000..c74f91249a --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/pom.xml @@ -0,0 +1,202 @@ + + 4.0.0 + + citrus-test-api-generator + org.citrusframework + 4.3.0-SNAPSHOT + ../pom.xml + + + citrus-test-api-generator-core + jar + + Citrus :: Test API Generator :: Core + Generates a Citrus Test-API for OpenAPI and WSDL specifications. + + + + + org.citrusframework + citrus-api + ${project.version} + + + org.citrusframework + citrus-http + ${project.version} + + + org.citrusframework + citrus-spring + ${project.version} + test + + + org.citrusframework + citrus-ws + ${project.version} + + + + org.assertj + assertj-core + ${assertj.version} + test + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + + + org.openapitools + openapi-generator + + + wsdl4j + wsdl4j + + + + + org.citrusframework + citrus-junit5 + ${project.version} + test + + + org.springframework.boot + spring-boot-test + ${spring.boot.test.version} + test + + + org.citrusframework + citrus-validation-json + ${project.version} + test + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + ${maven.helper.plugin.version} + + + add-generated-specs + generate-test-resources + + add-test-resource + + + + + ${project.build.directory}/generated-test-resources + ${project.build.outputDirectory} + + + + + + add-generated-classes + generate-test-sources + + add-test-source + + + + ${project.build.directory}/generated-test-sources + + + + + + + + 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 + + + + + + + generate-openapi-multiparttest-files + compile + + generate + + + ${project.basedir}/src/test/resources/apis/multiparttest-rest-resource.yaml + + org.citrusframework.openapi.generator.rest.multiparttest + org.citrusframework.openapi.generator.rest.multiparttest.request + org.citrusframework.openapi.generator.rest.multiparttest.model + MultipartTest + multipartTestEndpoint + + + + + + + + diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/java/org/citrusframework/openapi/generator/JavaCitrusCodegen.java b/test-api-generator/citrus-test-api-generator-core/src/main/java/org/citrusframework/openapi/generator/JavaCitrusCodegen.java new file mode 100644 index 0000000000..7aa18331b5 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/main/java/org/citrusframework/openapi/generator/JavaCitrusCodegen.java @@ -0,0 +1,345 @@ +package org.citrusframework.openapi.generator; + +import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toMap; +import static org.openapitools.codegen.CliOption.newString; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import java.io.File; +import java.util.ArrayList; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import org.openapitools.codegen.CodegenType; +import org.openapitools.codegen.SupportingFile; +import org.openapitools.codegen.languages.AbstractJavaCodegen; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Thorsten Schlathoelter + */ +public class JavaCitrusCodegen extends AbstractJavaCodegen { + + private static final Logger logger = LoggerFactory.getLogger(JavaCitrusCodegen.class); + + private static final String ABSTRACT_TEST_REQUEST_JAVA = "AbstractTestRequest.java"; + private static final String API_TYPE_REST = "REST"; + private static final String API_TYPE_SOAP = "SOAP"; + public static final String CODEGEN_NAME = "java-citrus"; + + // possible optional parameters + public static final String API_ENDPOINT = "apiEndpoint"; + public static final String API_TYPE = "apiType"; + public static final String GENERATED_SCHEMA_FOLDER = "generatedSchemaFolder"; + public static final String HTTP_PATH_PREFIX = "httpPathPrefix"; + public static final String OPENAPI_SCHEMA = "openapiSchema"; + public static final String PREFIX = "prefix"; + public static final String RESOURCE_FOLDER = "resourceFolder"; + public static final String SOURCE_FOLDER = "sourceFolder"; + public static final String TARGET_XMLNS_NAMESPACE = "targetXmlnsNamespace"; + + // default values for optional parameters + protected String apiPrefix = "Api"; + + protected String httpClient = API_ENDPOINT; + protected String httpPathPrefix = "api"; + protected String openapiSchema = "oas3"; + protected String resourceFolder = + "src" + File.separator + "main" + File.separator + "resources"; + protected String generatedSchemaFolder = "schema" + File.separator + "xsd"; + protected String targetXmlnsNamespace; + + protected String apiVersion = "1.0.0"; + + public JavaCitrusCodegen() { + super(); + // the root folder where all files are emitted + outputFolder = "generated-code" + File.separator + "java"; + + // this is the location which templates will be read from in the - resources - directory + templateDir = CODEGEN_NAME; + + // register additional properties which will be available in the templates + additionalProperties.put("apiVersion", apiVersion); + + // set default + additionalProperties.put(API_TYPE, API_TYPE_REST); + + // add additional reserved words used in CitrusAbstractTestRequest and its base class to prevent name collisions + Set reservedWordsTemp = reservedWords(); + reservedWordsTemp.addAll( + asList( + "name", + "description", + "actor", + "httpClient", + "dataSource", + "schemaValidation", + "schema", + "headerContentType", + "headerAccept", + "bodyFile", + "responseType", + "responseStatus", + "responseReasonPhrase", + "responseVersion", + "resource", + "responseVariable", + "responseValue", + "cookies", + "script", + "type" + ) + ); + setReservedWordsLowerCase(new ArrayList<>(reservedWordsTemp)); + + // add posibility to set a new value for the properties + cliOptions.add(newString(API_ENDPOINT, + "Which http client should be used (default " + httpClient + ").")); + cliOptions.add( + newString( + API_TYPE, + "Specifies the type of API to be generated specify SOAP to generate a SOAP API. By default a REST API will be generated" + ) + ); + cliOptions.add( + newString(GENERATED_SCHEMA_FOLDER, + "The schema output directory (default " + generatedSchemaFolder + ").") + ); + cliOptions.add(newString(HTTP_PATH_PREFIX, + "Add a prefix to http path for all APIs (default " + httpPathPrefix + ").")); + cliOptions.add(newString(OPENAPI_SCHEMA, + "Which OpenAPI schema should be used (default " + openapiSchema + ").")); + cliOptions.add( + newString( + PREFIX, + "Add a prefix before the name of the files. First character should be upper case (default " + + apiPrefix + ")." + ) + ); + cliOptions.add(newString(PREFIX, "The api prefix (default " + apiPrefix + ").")); + cliOptions.add(newString(RESOURCE_FOLDER, + "Where the resource files are emitted (default " + resourceFolder + ").")); + cliOptions.add( + newString(TARGET_XMLNS_NAMESPACE, + "Xmlns namespace of the schema (default " + targetXmlnsNamespace + ").") + ); + } + + /** + * Returns human-friendly help for the generator. Provide the consumer with help tips, + * parameters here + * + * @return A string value for the help message + */ + @Override + public String getHelp() { + return "Generates citrus api requests."; + } + + /** + * Configures a friendly name for the generator. This will be used by the generator to select + * the library with the -g flag. + * + * @return the friendly name for the generator + */ + @Override + public String getName() { + return CODEGEN_NAME; + } + + /** + * Configures the type of generator. + * + * @return the CodegenType for this generator + * @see org.openapitools.codegen.CodegenType + */ + @Override + public CodegenType getTag() { + return CodegenType.CLIENT; + } + + @Override + public void processOpts() { + super.processOpts(); + + if (additionalProperties.containsKey(API_ENDPOINT)) { + this.setHttpClient(additionalProperties.get(API_ENDPOINT).toString()); + } + additionalProperties.put(API_ENDPOINT, httpClient); + + if (additionalProperties.containsKey(GENERATED_SCHEMA_FOLDER)) { + this.setGeneratedSchemaFolder( + additionalProperties.get(GENERATED_SCHEMA_FOLDER).toString()); + } + additionalProperties.put(GENERATED_SCHEMA_FOLDER, generatedSchemaFolder); + + if (additionalProperties.containsKey(HTTP_PATH_PREFIX)) { + this.setHttpPathPrefix(additionalProperties.get(HTTP_PATH_PREFIX).toString()); + additionalProperties.put(HTTP_PATH_PREFIX, httpPathPrefix); + } else { + logger.warn( + "Using empty http-path-prefix for code generation. A http-path-prefix can be configured using \"{}\" property.", + HTTP_PATH_PREFIX + ); + httpPathPrefix = ""; + } + + if (additionalProperties.containsKey(OPENAPI_SCHEMA)) { + this.setOpenapiSchema(additionalProperties.get(OPENAPI_SCHEMA).toString()); + } + additionalProperties.put(OPENAPI_SCHEMA, openapiSchema); + + if (additionalProperties.containsKey(PREFIX)) { + this.setApiPrefix(additionalProperties.get(PREFIX).toString()); + additionalProperties.put(PREFIX, apiPrefix); + additionalProperties.put(PREFIX + "LowerCase", apiPrefix.toLowerCase()); + } else { + logger.warn( + "Using empty prefix for code generation. A prefix can be configured using \"{}\" property.", + PREFIX); + apiPrefix = ""; + } + + if (additionalProperties.containsKey(RESOURCE_FOLDER)) { + this.setResourceFolder(additionalProperties.get(RESOURCE_FOLDER).toString()); + } + additionalProperties.put(RESOURCE_FOLDER, resourceFolder); + + if (additionalProperties.containsKey(TARGET_XMLNS_NAMESPACE)) { + this.setTargetXmlnsNamespace( + additionalProperties.get(TARGET_XMLNS_NAMESPACE).toString()); + } else { + this.targetXmlnsNamespace = String.format( + "http://www.citrusframework.org/citrus-test-schema/%s-api", apiPrefix.toLowerCase()); + } + additionalProperties.put(TARGET_XMLNS_NAMESPACE, targetXmlnsNamespace); + + // define different folders where the files will be emitted + final String invokerFolder = (sourceFolder + File.separator + invokerPackage).replace(".", + File.separator); + final String citrusFolder = invokerFolder + File.separator + "citrus"; + final String extensionFolder = citrusFolder + File.separator + "extension"; + final String springFolder = invokerFolder + File.separator + "spring"; + final String schemaFolder = resourceFolder + File.separator + generatedSchemaFolder; + + Object apiType = additionalProperties.get(API_TYPE); + if (API_TYPE_REST.equals(apiType)) { + addRestSupportingFiles(citrusFolder, schemaFolder); + } else if (API_TYPE_SOAP.equals(apiType)) { + addSoapSupportingFiles(citrusFolder, schemaFolder); + } else { + throw new IllegalArgumentException(String.format("Unknown API_TYPE: '%s'", apiType)); + } + + addDefaultSupportingFiles(citrusFolder, extensionFolder, springFolder); + } + + @Override + public void preprocessOpenAPI(OpenAPI openAPI) { + super.preprocessOpenAPI(openAPI); + + Info info = openAPI.getInfo(); + Map extensions = info.getExtensions(); + if (extensions != null) { + additionalProperties.putAll(extensions); + + Map infoExtensions = extensions.entrySet().stream() + .filter(entry -> entry.getKey().toUpperCase( + ).startsWith("X-")) + .collect(toMap(Entry::getKey, Entry::getValue)); + additionalProperties.put("infoExtensions", infoExtensions); + } + } + + public void setApiPrefix(String apiPrefix) { + this.apiPrefix = apiPrefix; + } + + public String getHttpClient() { + return httpClient; + } + + public void setHttpClient(String httpClient) { + this.httpClient = httpClient; + } + + public String getHttpPathPrefix() { + return httpPathPrefix; + } + + public void setHttpPathPrefix(String httpPathPrefix) { + this.httpPathPrefix = httpPathPrefix; + } + + public String getOpenapiSchema() { + return openapiSchema; + } + + public void setOpenapiSchema(String openapiSchema) { + this.openapiSchema = openapiSchema; + } + + public String getResourceFolder() { + return resourceFolder; + } + + public void setResourceFolder(String resourceFolder) { + this.resourceFolder = resourceFolder; + } + + public String getGeneratedSchemaFolder() { + return generatedSchemaFolder; + } + + public void setGeneratedSchemaFolder(String generatedSchemaFolder) { + this.generatedSchemaFolder = generatedSchemaFolder; + } + + public String getTargetXmlnsNamespace() { + return targetXmlnsNamespace; + } + + public void setTargetXmlnsNamespace(String targetXmlnsNamespace) { + this.targetXmlnsNamespace = targetXmlnsNamespace; + } + + public String getApiPrefix() { + return apiPrefix; + } + + private void addRestSupportingFiles(final String citrusFolder, String schemaFolder) { + supportingFiles.add(new SupportingFile("schema.mustache", schemaFolder, + apiPrefix.toLowerCase() + "-api.xsd")); + supportingFiles.add(new SupportingFile("test_base.mustache", citrusFolder, + apiPrefix + ABSTRACT_TEST_REQUEST_JAVA)); + } + + private void addSoapSupportingFiles(final String citrusFolder, String schemaFolder) { + // Remove the default api template file + apiTemplateFiles().remove("api.mustache"); + apiTemplateFiles().put("api_soap.mustache", ".java"); + + supportingFiles.add(new SupportingFile("schema_soap.mustache", schemaFolder, + apiPrefix.toLowerCase() + "-api.xsd")); + supportingFiles.add(new SupportingFile("api_soap.mustache", citrusFolder, + apiPrefix + ABSTRACT_TEST_REQUEST_JAVA)); + supportingFiles.add(new SupportingFile("test_base_soap.mustache", citrusFolder, + apiPrefix + ABSTRACT_TEST_REQUEST_JAVA)); + } + + private void addDefaultSupportingFiles(final String citrusFolder, final String extensionFolder, + final String springFolder) { + supportingFiles.add(new SupportingFile("bean_configuration.mustache", springFolder, + apiPrefix + "BeanConfiguration.java")); + supportingFiles.add(new SupportingFile("bean_definition_parser.mustache", citrusFolder, + apiPrefix + "BeanDefinitionParser.java")); + supportingFiles.add(new SupportingFile("namespace_handler.mustache", extensionFolder, + apiPrefix + "NamespaceHandler.java")); + supportingFiles.add(new SupportingFile("api-model.mustache", resourceFolder, + apiPrefix.toLowerCase() + "-api-model.csv")); + } + +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/java/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformer.java b/test-api-generator/citrus-test-api-generator-core/src/main/java/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformer.java new file mode 100644 index 0000000000..68d7bf3579 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/main/java/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformer.java @@ -0,0 +1,187 @@ +package org.citrusframework.openapi.generator; + +import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL; +import static com.fasterxml.jackson.databind.MapperFeature.SORT_PROPERTIES_ALPHABETICALLY; +import static java.lang.String.format; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.Paths; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.responses.ApiResponses; +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import javax.wsdl.Binding; +import javax.wsdl.BindingOperation; +import javax.wsdl.Definition; +import javax.wsdl.WSDLException; +import javax.wsdl.factory.WSDLFactory; +import javax.wsdl.xml.WSDLReader; +import javax.xml.namespace.QName; +import org.citrusframework.openapi.generator.exception.WsdlToOpenApiTransformationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Element; + +/** + * Transforms a wsdl specification into a simple OpenApi specification for usage with the OpenApiGenerator. + *

+ * + * Note that this transformer only transforms bindings from the wsdl into operations in the OpenApi. + */ +public class SimpleWsdlToOpenApiTransformer { + + private static final Logger logger = LoggerFactory.getLogger(SimpleWsdlToOpenApiTransformer.class); + + private static final YAMLMapper yamlMapper = (YAMLMapper) YAMLMapper.builder() + .enable(SORT_PROPERTIES_ALPHABETICALLY) + .build() + .setSerializationInclusion(NON_NULL); + + private final URI wsdlUri; + + public SimpleWsdlToOpenApiTransformer(URI wsdlUri) { + this.wsdlUri = wsdlUri; + } + + /** + * Transforms the wsdl of this transfromer into a OpenApi yaml representation. + * + * @return the OpenApi yaml + * @throws WsdlToOpenApiTransformationException if the parsing fails + */ + public String transformToOpenApi() throws WsdlToOpenApiTransformationException { + try { + Definition wsdlDefinition = readWSDL(); + Map bindings = wsdlDefinition.getBindings(); + OpenAPI openAPI = transformToOpenApi(bindings); + return convertToYaml(openAPI); + } catch (Exception e) { + throw new WsdlToOpenApiTransformationException("Unable to parse wsdl", e); + } + } + + /** + * Performs the actual transformation from bindings into OpenApi operations. + * + * @param bindings + * @return + */ + private OpenAPI transformToOpenApi(Map bindings) { + OpenAPI openAPI = new OpenAPI(); + openAPI.setInfo(createInfo()); + + Paths paths = new Paths(); + openAPI.setPaths(paths); + for (Entry entry : bindings.entrySet()) { + Object key = entry.getKey(); + Object value = entry.getValue(); + + if (key instanceof QName && value instanceof Binding) { + addOperations(openAPI, (QName) key, (Binding) value); + } + } + return openAPI; + } + + private Definition readWSDL() throws WSDLException { + logger.debug("Reading wsdl file from path: {}", wsdlUri); + + WSDLReader reader = WSDLFactory.newInstance().newWSDLReader(); + + // switch off the verbose mode + reader.setFeature("javax.wsdl.verbose", false); + reader.setFeature("javax.wsdl.importDocuments", true); + + if (logger.isDebugEnabled()) { + logger.debug("Reading the WSDL. Base uri is {}", wsdlUri); + } + + return reader.readWSDL(wsdlUri.toString()); + } + + private Info createInfo() { + Info info = new Info(); + info.setTitle("Generated api from wsdl"); + + info.setDescription( + format( + "This api has been generated from the following wsdl '%s'. It's purpose is solely to serve as input for SOAP API generation. Note that only operations are extracted from the WSDL. No schema information whatsoever is generated!", + java.nio.file.Paths.get(wsdlUri).getFileName() + ) + ); + info.setVersion("1.0.0"); + + Contact contact = new Contact(); + contact.setName("org.citrusframework.openapi.generator.SimpleWsdlToOpenApiTransformer"); + info.setContact(contact); + return info; + } + + private void addOperations(OpenAPI openApi, QName qName, Binding binding) { + String localPart = qName.getLocalPart(); + + String bindingApiName; + if (localPart.endsWith("SoapBinding")) { + bindingApiName = localPart.substring(0, localPart.length() - "SoapBinding".length()); + } else { + bindingApiName = localPart; + } + + List bindingOperations = binding.getBindingOperations(); + for (Object operation : bindingOperations) { + if (operation instanceof BindingOperation bindingOperation) { + addOperation( + openApi.getPaths(), + bindingOperation.getName(), + retrieveOperationDescription(bindingOperation), + bindingApiName + ); + } + } + } + + private void addOperation(Paths paths, String name, String description, String tag) { + Operation postOperation = new Operation(); + + logger.debug("Adding operation to spec: {}", name); + + postOperation.setOperationId(name); + postOperation.setDescription(description); + postOperation.tags(Collections.singletonList(tag)); + ApiResponses responses = new ApiResponses(); + postOperation.responses(responses); + + PathItem pi = new PathItem(); + pi.setPost(postOperation); + + paths.addPathItem("/" + name, pi); + } + + /** + * Retrieve the description of the bindingOperation via the documentation of the associated operation. + */ + private String retrieveOperationDescription(BindingOperation bindingOperation) { + String description = ""; + javax.wsdl.Operation soapOperation = bindingOperation.getOperation(); + if (soapOperation != null) { + Element documentationElement = soapOperation.getDocumentationElement(); + if (documentationElement != null) { + description = documentationElement.getTextContent(); + } + } + + return description; + } + + private String convertToYaml(OpenAPI openAPI) throws JsonProcessingException { + return yamlMapper.writeValueAsString(openAPI); + } +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/java/org/citrusframework/openapi/generator/exception/WsdlToOpenApiTransformationException.java b/test-api-generator/citrus-test-api-generator-core/src/main/java/org/citrusframework/openapi/generator/exception/WsdlToOpenApiTransformationException.java new file mode 100644 index 0000000000..fd845194bf --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/main/java/org/citrusframework/openapi/generator/exception/WsdlToOpenApiTransformationException.java @@ -0,0 +1,8 @@ +package org.citrusframework.openapi.generator.exception; + +public class WsdlToOpenApiTransformationException extends Exception { + + public WsdlToOpenApiTransformationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig b/test-api-generator/citrus-test-api-generator-core/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig new file mode 100644 index 0000000000..33a85c5059 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig @@ -0,0 +1 @@ +org.citrusframework.openapi.generator.JavaCitrusCodegen \ No newline at end of file diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api-model.mustache b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api-model.mustache new file mode 100644 index 0000000000..ae513df561 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api-model.mustache @@ -0,0 +1,2 @@ +OperationId;Path;Method;Parameters{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}} +{{operationId}};{{path}};{{httpMethod}};{{#allParams}}{{paramName}}:{{{dataType}}}{{^-last}},{{/-last}}{{/allParams}}{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api.mustache b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api.mustache new file mode 100644 index 0000000000..5d90d5f80c --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api.mustache @@ -0,0 +1,265 @@ +/** + * ================================================== + * GENERATED CLASS, ALL CHANGES WILL BE LOST + * ================================================== + */ + +package {{package}}; + +import jakarta.annotation.Generated; +import org.citrusframework.testapi.GeneratedApi; +import org.citrusframework.testapi.GeneratedApiRequest; +import jakarta.servlet.http.Cookie; +import org.apache.commons.lang3.StringUtils; +import org.citrusframework.context.TestContext; +import org.citrusframework.exceptions.CitrusRuntimeException; +import org.citrusframework.spi.Resources; +import org.citrusframework.http.actions.HttpActionBuilder; +import org.citrusframework.http.actions.HttpClientRequestActionBuilder; +import org.citrusframework.http.actions.HttpClientRequestActionBuilder.HttpMessageBuilderSupport; +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 {{invokerPackage}}.citrus.{{prefix}}AbstractTestRequest; + +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; + + +@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen") +public class {{classname}} implements GeneratedApi +{ + + public static final {{classname}} INSTANCE = new {{classname}}(); + + public String getApiTitle() { + return "{{appName}}"; + } + + public String getApiVersion() { + return "{{appVersion}}"; + } + + public String getApiPrefix() { + return "{{prefix}}"; + } + + public Map getApiInfoExtensions() { + Map infoExtensionMap = new HashMap<>(); + {{#infoExtensions}} + {{#entrySet}} + infoExtensionMap.put("{{key}}", "{{value}}"); + {{/entrySet}} + {{/infoExtensions}} + return infoExtensionMap; + } + + {{#operations}} + {{#operation}} + /** {{operationId}} ({{httpMethod}} {{httpPathPrefix}}{{{path}}}) + {{summary}} + {{description}} + **/ + public static class {{operationIdCamelCase}}Request extends {{prefix}}AbstractTestRequest implements GeneratedApiRequest { + + private static final String ENDPOINT = "{{httpPathPrefix}}{{{path}}}"; + private final Logger coverageLogger = LoggerFactory.getLogger({{operationIdCamelCase}}Request.class); + + {{#queryParams}} + private String {{paramName}}; + + {{/queryParams}} + {{#pathParams}} + private String {{paramName}}; + + {{/pathParams}} + {{#isMultipart}} + {{#formParams}} + private String {{paramName}}; + + {{/formParams}} + {{/isMultipart}} + {{#authMethods}}{{#isBasic}} + @Value("${" + "{{apiEndpoint}}.basic.username:#{null}}") + private String basicUsername; + @Value("${" + "{{apiEndpoint}}.basic.password:#{null}}") + private String basicPassword; + + {{/isBasic}} + {{/authMethods}} + + public {{operationIdCamelCase}}Request() { + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("{{prefix}}".toLowerCase() + ":{{operationId}}RequestType"); + } + + public String getOperationName() { + return "{{operationId}}"; + } + + public String getMethod() { + return "{{httpMethod}}"; + } + + public String getPath() { + return "{{path}}"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send() + .{{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}}(replacePathParams(ENDPOINT)); + + HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport(); + messageBuilderSupport.accept(responseAcceptType); + + if (cookies != null) { + cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + } + + if (headers != null) { + headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + headers.forEach(messageBuilderSupport::header); + } + + String bodyLog = ""; + {{#isMultipart}} + MultiValueMap multiValues = new LinkedMultiValueMap<>(); + {{#formParams}} + {{#required}} + if(StringUtils.isBlank({{paramName}})) { + throw new CitrusRuntimeException(String.format("Required attribute '%s' is not specified", "{{paramName}}")); + } + {{/required}} + {{#isBinary}} + if (StringUtils.isNotBlank({{paramName}})) { + multiValues.add("{{paramName}}", new ClassPathResource({{paramName}})); + bodyLog += {{paramName}}.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") +","; + } + {{/isBinary}} + {{^isBinary}} + if (StringUtils.isNotBlank({{paramName}})) { + // first try to load from resource + ClassPathResource resource = null; + try { + resource = new ClassPathResource({{paramName}}); + } + catch(Exception ignore) { + // Use plain text instead of resource + } + + if(resource != null && resource.exists()){ + multiValues.add("{{paramName}}", resource); + } else { + multiValues.add("{{paramName}}", {{paramName}}); + } + bodyLog += {{paramName}}.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") +","; + } + {{/isBinary}} + {{/formParams}} + + bodyLog += "\";\"" + MediaType.MULTIPART_FORM_DATA_VALUE + "\""; + messageBuilderSupport.contentType(MediaType.MULTIPART_FORM_DATA_VALUE) + .body(multiValues); + + {{/isMultipart}} + {{^isMultipart}} + String payload = null; + String payloadType = null; + if (StringUtils.isNotBlank(this.bodyFile)) { + try { + payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset()); + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read payload resource", e); + } + payloadType = this.bodyContentType; + } else if (StringUtils.isNotBlank(this.bodyLiteral)) { + payload = this.bodyLiteral; + payloadType = this.bodyLiteralContentType; + } + String body = ""; + String bodyType = ""; + if(payload != null && payloadType != null) { + messageBuilderSupport.body(payload).contentType(payloadType); + body = context.replaceDynamicContentInString(payload); + bodyType = context.replaceDynamicContentInString(payloadType); + } + + bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\""; + {{/isMultipart}} + + Map queryParams = new HashMap<>(); + {{#allParams}}{{#isQueryParam}} + + if (StringUtils.isNotBlank(this.{{paramName}})) { + queryParams.put("{{baseName}}", context.replaceDynamicContentInString(this.{{paramName}})); + httpClientRequestActionBuilder.queryParam("{{baseName}}", this.{{paramName}}); + } + {{/isQueryParam}}{{/allParams}} + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + {{#authMethods}}{{#isBasic}} + + if(basicUsername != null && basicPassword != null){ + messageBuilderSupport.header("Authorization", "Basic " + Base64.getEncoder().encodeToString((context.replaceDynamicContentInString(basicUsername)+":"+context.replaceDynamicContentInString(basicPassword)).getBytes())); + } + {{/isBasic}}{{/authMethods}} + httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver()); + httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder); + + httpClientRequestActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "{{operationId}};{{#lambda.uppercase}}{{httpMethod}}{{/lambda.uppercase}};\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + bodyLog); + } + {{#queryParams}} + + public void set{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}}(String {{paramName}}) { + this.{{paramName}} = {{paramName}}; + } + {{/queryParams}} + {{#pathParams}} + + public void set{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}}(String {{paramName}}) { + this.{{paramName}} = {{paramName}}; + } + {{/pathParams}} + {{#isMultipart}} + {{#formParams}} + + public void set{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}}(String {{paramName}}) { + this.{{paramName}} = {{paramName}}; + } + {{/formParams}} + {{/isMultipart}} + {{#authMethods}}{{#isBasic}} + + public void setBasicUsername(String basicUsername) { + this.basicUsername = basicUsername; + } + + public void setBasicPassword(String basicPassword) { + this.basicPassword = basicPassword; + } + {{/isBasic}}{{/authMethods}} + private String replacePathParams(String endpoint) { + {{#pathParams}}endpoint = endpoint.replace("{" + "{{baseName}}" + "}", {{paramName}});{{/pathParams}} + return endpoint; + } + } + {{/operation}} + {{/operations}} +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api_doc.mustache b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api_doc.mustache new file mode 100644 index 0000000000..f8737ed4d9 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api_doc.mustache @@ -0,0 +1 @@ +# not in use diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api_soap.mustache b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api_soap.mustache new file mode 100644 index 0000000000..d39d1aff12 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api_soap.mustache @@ -0,0 +1,165 @@ +/** + * ================================================== + * GENERATED CLASS, ALL CHANGES WILL BE LOST + * ================================================== + */ + +package {{package}}; + +import jakarta.annotation.Generated; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; +import org.citrusframework.context.TestContext; +import org.citrusframework.exceptions.CitrusRuntimeException; +import org.citrusframework.testapi.GeneratedApi; +import org.citrusframework.testapi.GeneratedApiRequest; +import org.citrusframework.openapi.generator.soap.bookservice.citrus.OpenApiFromWsdlAbstractTestRequest; +import org.citrusframework.spi.Resources; +import org.citrusframework.util.FileUtils; +import org.citrusframework.ws.actions.SendSoapMessageAction; +import org.citrusframework.ws.actions.SendSoapMessageAction.Builder.SendSoapMessageBuilderSupport; +import org.citrusframework.ws.actions.SoapActionBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.CollectionUtils; + +import {{invokerPackage}}.citrus.{{prefix}}AbstractTestRequest; + +@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen") +public class {{classname}} implements GeneratedApi +{ + public static final {{classname}} INSTANCE = new {{classname}}(); + + public String getApiTitle() { + return "{{appName}}"; + } + + public String getApiVersion() { + return "{{appVersion}}"; + } + + public String getApiPrefix() { + return "{{prefix}}"; + } + + public Map getApiInfoExtensions() { + Map infoExtensionMap = new HashMap<>(); + {{#infoExtensions}} + {{#entrySet}} + infoExtensionMap.put("{{key}}", "{{value}}"); + {{/entrySet}} + {{/infoExtensions}} + return infoExtensionMap; + } + + {{#operations}} + {{#operation}} + /** + {{operationId}} ({{httpMethod}} {{httpPathPrefix}}{{{path}}}) + {{summary}} + {{description}} + **/ + public static class {{operationIdCamelCase}}Request extends {{prefix}}AbstractTestRequest implements GeneratedApiRequest { + + private final Logger coverageLogger = LoggerFactory.getLogger({{operationIdCamelCase}}Request.class); + + // Query params + {{#allParams}}{{#isQueryParam}}private String {{paramName}}; + {{/isQueryParam}}{{/allParams}} + + public {{operationIdCamelCase}}Request(){ + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("{{prefix}}".toLowerCase() + ":{{operationId}}RequestType"); + } + + public String getOperationName() { + return "{{operationId}}"; + } + + public String getMethod() { + return "{{httpMethod}}"; + } + + public String getPath() { + return "{{path}}"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + + SendSoapMessageAction.Builder soapSendMessageActionBuilder = new SoapActionBuilder().client(wsClient).send(); + SendSoapMessageBuilderSupport messageBuilderSupport = soapSendMessageActionBuilder.getMessageBuilderSupport(); + + messageBuilderSupport.soapAction("{{operationId}}"); + + String payload = null; + String payloadType = null; + + if (StringUtils.isNotBlank(this.bodyFile)) { + try { + payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset()); + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read payload resource", e); + } + payloadType = this.bodyContentType; + } else if (StringUtils.isNotBlank(this.bodyLiteral)) { + payload = this.bodyLiteral; + payloadType = this.bodyLiteralContentType; + } + + String body = ""; + String bodyType = ""; + if(payload != null && payloadType != null) { + messageBuilderSupport.body(payload).contentType(payloadType); + body = context.replaceDynamicContentInString(payload); + bodyType = context.replaceDynamicContentInString(payloadType); + } + + if (!CollectionUtils.isEmpty(soapHeaders)) { + for (Entry entry : soapHeaders.entrySet()) { + messageBuilderSupport = messageBuilderSupport.header(entry.getKey(), + entry.getValue()); + } + } + + if (!CollectionUtils.isEmpty(mimeHeaders)) { + for (Entry entry : mimeHeaders.entrySet()) { + messageBuilderSupport = messageBuilderSupport.header("citrus_http_" + entry.getKey(), + entry.getValue()); + } + } + + Map queryParams = new HashMap<>(); + {{#allParams}}{{#isQueryParam}} + if (StringUtils.isNotBlank(this.{{paramName}})) { + queryParams.put("{{baseName}}", context.replaceDynamicContentInString(this.{{paramName}})); + sendSoapMessageActionBuilder.queryParam("{{baseName}}", this.{{paramName}}); + } + {{/isQueryParam}}{{/allParams}} + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + soapSendMessageActionBuilder.withReferenceResolver(context.getReferenceResolver()); + soapSendMessageActionBuilder = customizeBuilder(INSTANCE, context, soapSendMessageActionBuilder); + + soapSendMessageActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "{{operationId}};{{#lambda.uppercase}}{{httpMethod}}{{/lambda.uppercase}};\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\""); + } + + {{#allParams}}{{#isQueryParam}} + public void set{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}}(String {{paramName}}) { + this.{{paramName}} = {{paramName}}; + } + {{/isQueryParam}}{{/allParams}} + } + {{/operation}} + {{/operations}} +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api_test.mustache b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api_test.mustache new file mode 100644 index 0000000000..d5341fea2c --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api_test.mustache @@ -0,0 +1 @@ +// not in use diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/bean_configuration.mustache b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/bean_configuration.mustache new file mode 100644 index 0000000000..d4b8dd2e01 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/bean_configuration.mustache @@ -0,0 +1,38 @@ +/** + * ================================================== + * GENERATED CLASS, ALL CHANGES WILL BE LOST + * ================================================== + */ + +package {{invokerPackage}}.spring; + +import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE; + +{{#apiInfo}} +{{#apis}} +import {{package}}.{{classname}}; +{{/apis}} +{{/apiInfo}} +import javax.annotation.processing.Generated; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; + +@Configuration +@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen") +public class {{prefix}}BeanConfiguration { +{{#apiInfo}} + {{#apis}} + {{#operations}} + {{#operation}} + + @Bean + @Scope(SCOPE_PROTOTYPE) + public {{classname}}.{{operationIdCamelCase}}Request {{operationId}}Request() { + return new {{classname}}.{{operationIdCamelCase}}Request(); + } + {{/operation}} + {{/operations}} + {{/apis}} +{{/apiInfo}} +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/bean_definition_parser.mustache b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/bean_definition_parser.mustache new file mode 100644 index 0000000000..4e0957f1af --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/bean_definition_parser.mustache @@ -0,0 +1,215 @@ +/** + * ================================================== + * GENERATED CLASS, ALL CHANGES WILL BE LOST + * ================================================== + */ + +package {{invokerPackage}}.citrus; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.annotation.processing.Generated; + +import org.citrusframework.exceptions.CitrusRuntimeException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.core.Conventions; +import org.springframework.util.Assert; +import org.apache.commons.lang3.StringUtils; +import org.springframework.util.xml.DomUtils; +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + + +@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen") +public class {{prefix}}BeanDefinitionParser implements BeanDefinitionParser { + + private static final String COOKIE = "cookie"; + private static final String HEADER = "header"; + private static final String SOAP_HEADER = "soapHeader"; + private static final String MIME_HEADER = "mimeHeader"; + private static final String NAME = "name"; + private static final String REQUEST_BODY = "body"; + private static final String REQUEST_BODY_LITERAL = "bodyLiteral"; + private static final String MULTIPART_BODY = "multipartBody"; + private static final String RESPONSE = "response"; + private static final String RESPONSE_JSONPATH = "json-path"; + private static final String RESPONSE_XPATH = "xpath"; + private static final String EXPRESSION = "expression"; + private static final String VALUE = "value"; + private static final String RESPONSE_RESOURCE = "resource"; + private static final String FILE = "file"; + private static final String RESPONSE_VARIABLE = "responseVariable"; + private static final String RESPONSE_VALUE = "responseValue"; + private static final String SCRIPT = "script"; + private static final String TYPE = "type"; + private static final String SQL = "sql"; + private static final String COLUMN = "column"; + private static final String VARIABLE = "variable"; + // new + private static final String SCHEMA = "schema"; + // new + private static final String SCHEMA_VALIDATION = "schemaValidation"; + + private final Class beanClass; + + public {{prefix}}BeanDefinitionParser(Class beanClass) { + this.beanClass = beanClass; + } + + public BeanDefinition parse(Element element) { + return parse(element, null); + } + + /** + * Note: The {@link {{prefix}}BeanDefinitionParser#parse(Element element)} allows access direct + * access without the {@link org.springframework.beans.factory.xml.ParserContext} for convenience. + */ + @Override + public BeanDefinition parse(Element element, ParserContext parserContext) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(beanClass); + retrieveRootNodeAttributes(element, builder); + retrieveOptionalNodeAttributes(element, REQUEST_BODY, builder); + retrieveTextContentAndNodeAttributes(element, REQUEST_BODY_LITERAL, builder); + retrieveOptionalNodeAttributes(element, RESPONSE, builder); + retrieveParamNodeData(element, builder, COOKIE); + retrieveParamNodeData(element, builder, HEADER); + retrieveParamNodeData(element, builder, SOAP_HEADER); + retrieveParamNodeData(element, builder, MIME_HEADER); + retrieveOptionalNodeAttributes(element, SCHEMA, builder); + retrieveOptionalNodeAttributes(element, SCHEMA_VALIDATION, builder); + retrieveOptionalMultipartElements(element, builder); + retrieveResponseNodeData(element, builder); + builder.addPropertyValue("name", element.getTagName()); + return builder.getBeanDefinition(); + } + + private void retrieveOptionalMultipartElements(Element element, BeanDefinitionBuilder builder) { + var multipartBodyElement = DomUtils.getChildElementByTagName(element, MULTIPART_BODY); + if (multipartBodyElement != null) { + var multipartBodyChildElements = DomUtils.getChildElements(multipartBodyElement); + for(int i = 0; i < multipartBodyChildElements.size(); i++){ + var multipartBodyChildElement = multipartBodyChildElements.get(i); + String propertyName = Conventions.attributeNameToPropertyName(multipartBodyChildElement.getLocalName()); + builder.addPropertyValue(propertyName, multipartBodyChildElement.getTextContent()); + } + } + } + + private void retrieveRootNodeAttributes(Element element, BeanDefinitionBuilder builder) { + NamedNodeMap attributes = element.getAttributes(); + for (int x = 0; x < attributes.getLength(); x++) { + Attr attribute = (Attr) attributes.item(x); + String propertyName = Conventions.attributeNameToPropertyName(attribute.getLocalName()); + Assert.state(StringUtils.isNotBlank(propertyName), "Illegal property name returned, it must not be null or empty."); + builder.addPropertyValue(propertyName, attribute.getValue()); + } + } + + private void retrieveOptionalNodeAttributes(Element element, String elementName, BeanDefinitionBuilder builder) { + if (!DomUtils.getChildElementsByTagName(element, elementName).isEmpty()) { + Element el = DomUtils.getChildElementsByTagName(element, elementName).get(0); + NamedNodeMap attributes = el.getAttributes(); + for (int x = 0; x < attributes.getLength(); x++) { + Attr attribute = (Attr) attributes.item(x); + String propertyName = Conventions.attributeNameToPropertyName(attribute.getLocalName()); + Assert.state(StringUtils.isNotBlank(propertyName), "Illegal property name returned, it must not be null or empty."); + String variableName = el.getLocalName() + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1); + builder.addPropertyValue(variableName, attribute.getValue()); + } + } + } + + private void retrieveTextContentAndNodeAttributes(Element element, String elementName, BeanDefinitionBuilder builder) { + if (!DomUtils.getChildElementsByTagName(element, elementName).isEmpty()) { + Element el1 = DomUtils.getChildElementsByTagName(element, elementName).get(0); + NamedNodeMap attributes = el1.getAttributes(); + for (int x = 0; x < attributes.getLength(); x++) { + Attr attribute = (Attr) attributes.item(x); + String propertyName1 = Conventions.attributeNameToPropertyName(attribute.getLocalName()); + Assert.state(StringUtils.isNotBlank(propertyName1), "Illegal property name returned, it must not be null or empty."); + String variableName = el1.getLocalName() + propertyName1.substring(0, 1).toUpperCase() + propertyName1.substring(1); + builder.addPropertyValue(variableName, attribute.getValue()); + } + Element el = DomUtils.getChildElementsByTagName(element, elementName).get(0); + builder.addPropertyValue(elementName, el.getTextContent()); + } + } + + private void retrieveParamNodeData(Element element, BeanDefinitionBuilder builder, String paramType) { + if (!DomUtils.getChildElementsByTagName(element, paramType).isEmpty()) { + Map params = new HashMap<>(); + List elements = DomUtils.getChildElementsByTagName(element, paramType); + elements.forEach(e -> { + String name = e.getAttribute(NAME); + String value = e.getAttribute(VALUE); + + Assert.state(StringUtils.isNotBlank(name), "Illegal attribute value returned. The 'name' attribute must not be null or empty."); + Assert.state(StringUtils.isNotBlank(value), "Illegal attribute value returned. The 'value' attribute must not be null or empty."); + + params.put(name, value); + }); + builder.addPropertyValue(paramType, params); + } + } + + private void retrieveResponseNodeData(Element element, BeanDefinitionBuilder builder) { + + if (!DomUtils.getChildElementsByTagName(element, RESPONSE).isEmpty()) { + Element response = DomUtils.getChildElementsByTagName(element, RESPONSE).get(0); + List elements = DomUtils.getChildElements(response); + + Map responseVariable = new HashMap<>(); + Map responseValue = new HashMap<>(); + + for (int i = 0; i < elements.size(); i++) { + Element e = elements.get(i); + + if (e.getTagName().contains(RESPONSE_JSONPATH) || e.getTagName().contains(RESPONSE_XPATH)) { + String expression = e.getAttribute(EXPRESSION); + String value = e.getAttribute(VALUE); + + Assert.state(StringUtils.isNotBlank(expression), "Illegal attribute value returned. The 'expression' attribute must not be null or empty."); + Assert.state(StringUtils.isNotBlank(value), "Illegal attribute value returned. The 'value' attribute must not be null or empty."); + + // variable to save @variable('ebid')@ else value to validate + if (value.matches("\\@variable\\('.*'\\)\\@")) { + Matcher match = Pattern.compile("\\'(.*?)\\'").matcher(value); + if (match.find()) { + responseVariable.put(expression, value.substring(match.start() + 1, match.end() - 1)); + } + } else { + responseValue.put(expression, value); + } + } else if (e.getTagName().contains(SCRIPT)) { + String script = e.getTextContent(); + Assert.state(StringUtils.isNotBlank(script), "Illegal attribute value returned. The 'script' attribute must not be null or empty."); + builder.addPropertyValue(SCRIPT, script); + + if (!e.getAttribute(TYPE).isEmpty()) { + String type = e.getAttribute(TYPE); + Assert.state(StringUtils.isNotBlank(type), "Illegal attribute value returned. The 'type' attribute must not be null or empty."); + builder.addPropertyValue(TYPE, type); + } + } else if (e.getTagName().contains(RESPONSE_RESOURCE)) { + String filePath = e.getAttribute(FILE); + Assert.state(StringUtils.isNotBlank(filePath), "Illegal attribute value returned. The 'file' attribute must not be null or empty."); + builder.addPropertyValue(RESPONSE_RESOURCE, filePath); + } + + } + + builder.addPropertyValue(RESPONSE_VARIABLE, responseVariable); + builder.addPropertyValue(RESPONSE_VALUE, responseValue); + } + } +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/model.mustache b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/model.mustache new file mode 100644 index 0000000000..d5341fea2c --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/model.mustache @@ -0,0 +1 @@ +// not in use diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/model_doc.mustache b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/model_doc.mustache new file mode 100644 index 0000000000..f8737ed4d9 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/model_doc.mustache @@ -0,0 +1 @@ +# not in use diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/namespace_handler.mustache b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/namespace_handler.mustache new file mode 100644 index 0000000000..ad260e35ba --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/namespace_handler.mustache @@ -0,0 +1,36 @@ +/** + * ================================================== + * GENERATED CLASS, ALL CHANGES WILL BE LOST + * ================================================== + */ + +package {{invokerPackage}}.citrus.extension; + +{{#apiInfo}} +{{#apis}} +import {{package}}.{{classname}}; +{{/apis}} +{{/apiInfo}} +import {{invokerPackage}}.citrus.{{prefix}}BeanDefinitionParser; + +import javax.annotation.processing.Generated; + +import org.springframework.beans.factory.xml.NamespaceHandlerSupport; + + +@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen") +public class {{prefix}}NamespaceHandler extends NamespaceHandlerSupport { + + @Override + public void init() { + {{#apiInfo}} + {{#apis}} + {{#operations}} + {{#operation}} + registerBeanDefinitionParser("{{operationId}}Request", new {{prefix}}BeanDefinitionParser({{classname}}.{{operationIdCamelCase}}Request.class)); + {{/operation}} + {{/operations}} + {{/apis}} + {{/apiInfo}} + } +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/schema.mustache b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/schema.mustache new file mode 100644 index 0000000000..faf2807dd1 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/schema.mustache @@ -0,0 +1,217 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{#apiInfo}} + {{#apis}} + {{#operations}} + {{#operation}} + + {{#isMultipart}} + + + {{#formParams}} + + + + {{^required}}Optional {{/required}}{{#required}}Required{{/required}} - must either be set as attribute or element: {{#description}} +

{{description}}

{{/description}} + + + + {{/formParams}} + + + {{/isMultipart}} + + + + + {{operationId}} +

{{httpMethod}} {{httpPathPrefix}}{{{path}}}

+
    + {{#queryParams}} +
  • {{paramName}} {{description}}
  • + {{/queryParams}} + {{#pathParams}} +
  • {{baseName}} {{description}}
  • + {{/pathParams}} + {{#bodyParams}} +
  • Body: {{description}}
  • + {{/bodyParams}} + {{#authMethods}}{{#isBasic}} +
  • basicUsername http basic authentication username
  • +
  • basicPassword http basic authentication password
  • + {{/isBasic}}{{/authMethods}} + {{#isMultipart}} + {{#formParams}} +
  • {{paramName}} {{description}}
  • + {{/formParams}} + {{/isMultipart}} +
+
+
+ + + + {{#isMultipart}} + + {{/isMultipart}} + {{^isMultipart}} + {{#bodyParams}} + + + + {{^required}}Optional {{/required}}Body - {{summary}}{{#description}} +

{{description}}

{{/description}} +
+
+
+ {{/bodyParams}} + {{/isMultipart}} + +
+ {{#queryParams}} + + + {{description}} + + + {{/queryParams}} + {{#pathParams}} + + + {{description}} + + + {{/pathParams}} + {{#isMultipart}} + {{#formParams}} + + + + The filename of the {{paramName}} to upload + + + {{/formParams}} + {{/isMultipart}} + {{#authMethods}}{{#isBasic}} + + + http basic authentication username + + + + + http basic authentication password + + + {{/isBasic}}{{/authMethods}} +
+
+
+ {{/operation}} + {{/operations}} + {{/apis}} + {{#apis}} + {{#operations}} + {{#operation}} + + + {{/operation}} + {{/operations}} + {{/apis}} + {{/apiInfo}} + diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/schema_soap.mustache b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/schema_soap.mustache new file mode 100644 index 0000000000..877bdc1884 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/schema_soap.mustache @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{#apiInfo}} + {{#apis}} + {{#operations}} + {{#operation}} + + + + {{operationId}} +

{{{path}}}

+
  • + Body: {{description}}
  • +
+
+
+ + + + + + + {{^required}}SOAP {{/required}}Body{{summary}}{{#description}} +

{{description}}

{{/description}} +
+
+
+ +
+
+
+
+ + {{/operation}} + {{/operations}} + {{/apis}} + {{#apis}} + {{#operations}} + {{#operation}} + + + {{/operation}} + {{/operations}} + {{/apis}} + {{/apiInfo}} +
diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/test_base.mustache b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/test_base.mustache new file mode 100644 index 0000000000..f3566c6716 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/test_base.mustache @@ -0,0 +1,245 @@ +/** + * ================================================== + * GENERATED CLASS, ALL CHANGES WILL BE LOST + * ================================================== + */ + +package {{invokerPackage}}.citrus; + + +import static org.springframework.util.CollectionUtils.isEmpty; + +import jakarta.annotation.Generated; +import jakarta.annotation.Nullable; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import javax.sql.DataSource; +import org.citrusframework.actions.AbstractTestAction; +import org.citrusframework.actions.ReceiveMessageAction; +import org.citrusframework.context.TestContext; +import org.citrusframework.http.actions.HttpActionBuilder; +import org.citrusframework.http.actions.HttpClientRequestActionBuilder; +import org.citrusframework.http.actions.HttpClientResponseActionBuilder; +import org.citrusframework.http.actions.HttpClientResponseActionBuilder.HttpMessageBuilderSupport; +import org.citrusframework.http.client.HttpClient; +import org.citrusframework.message.Message; +import org.citrusframework.testapi.ApiActionBuilderCustomizerService; +import org.citrusframework.testapi.GeneratedApi; +import org.citrusframework.spi.Resources; +import org.citrusframework.validation.DelegatingPayloadVariableExtractor; +import org.citrusframework.validation.PathExpressionValidationContext; +import org.citrusframework.validation.json.JsonMessageValidationContext; +import org.citrusframework.validation.script.ScriptValidationContext; +import org.slf4j.Marker; +import org.slf4j.MarkerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen") +public abstract class {{prefix}}AbstractTestRequest extends AbstractTestAction { + + protected final Marker coverageMarker = MarkerFactory.getMarker("{{#lambda.uppercase}}{{prefix}}{{/lambda.uppercase}}-API-COVERAGE"); + + @Autowired + @Qualifier("{{apiEndpoint}}") + protected HttpClient httpClient; + + @Autowired(required = false) + protected DataSource dataSource; + + @Autowired(required = false) + private List actionBuilderCustomizerServices; + + // attributes of differentNodes + protected boolean schemaValidation; + protected String schema; + protected String bodyContentType; + protected String bodyLiteralContentType; + protected String bodyFile; + protected String bodyLiteral; + protected String responseAcceptType = "*/*"; + protected String responseType = "json"; + protected int responseStatus = 200; + protected String responseReasonPhrase = "OK"; + protected String responseVersion = "HTTP/1.1"; + + // children of response element + protected String resource; + protected Map responseVariable; // Contains the 'JSON-PATH' as key and the 'VARIABLE NAME' as value + protected Map responseValue; // Contains the 'JSON-PATH' as key and the 'VALUE TO BE VALIDATED' as value + protected Map cookies; + protected Map headers; + protected String script; + protected String type; // default script type is groovy - supported types see com.consol.citrus.script.ScriptTypes + + @Override + public void doExecute(TestContext context) { + sendRequest(context); + recieveResponse(context); + } + + /** + * This method receives the HTTP-Response. + * + * @deprecated use {@link {{prefix}}AbstractTestRequest#receiveResponse(TestContext)} instead. + */ + public ReceiveMessageAction recieveResponse(TestContext context) { + + HttpClientResponseActionBuilder httpClientResponseActionBuilder = new HttpActionBuilder().client(httpClient).receive().response(); + HttpMessageBuilderSupport messageBuilderSupport = httpClientResponseActionBuilder.getMessageBuilderSupport(); + + messageBuilderSupport + .statusCode(responseStatus) + .reasonPhrase(responseReasonPhrase) + .version(responseVersion) + .validate(new JsonMessageValidationContext.Builder().schemaValidation(schemaValidation).schema(schema)); + + if (resource != null) { + messageBuilderSupport.body(Resources.create(resource)); + } + + if (!isEmpty(responseVariable)) { + DelegatingPayloadVariableExtractor.Builder extractorBuilder = new DelegatingPayloadVariableExtractor.Builder(); + responseVariable.forEach(extractorBuilder::expression); + messageBuilderSupport.extract(extractorBuilder); + } + + if (!isEmpty(responseValue)) { + PathExpressionValidationContext.Builder validationContextBuilder = new PathExpressionValidationContext.Builder(); + responseValue.forEach(validationContextBuilder::expression); + messageBuilderSupport.validate(validationContextBuilder); + } + + if (script != null) { + ScriptValidationContext.Builder scriptValidationContextBuilder = new ScriptValidationContext.Builder(); + if (type != null) { + scriptValidationContextBuilder.scriptType(type); + } + scriptValidationContextBuilder.script(script); + messageBuilderSupport.validate(scriptValidationContextBuilder); + } + + messageBuilderSupport.type(responseType); + httpClientResponseActionBuilder.withReferenceResolver(context.getReferenceResolver()); + var responseAction = httpClientResponseActionBuilder.build(); + + responseAction.execute(context); + + return responseAction; + } + + public @Nullable Message receiveResponse(TestContext context) { + var responseAction = recieveResponse(context); + + var messageStore = context.getMessageStore(); + return messageStore.getMessage(messageStore.constructMessageName(responseAction, httpClient)); + } + + public abstract void sendRequest(TestContext context); + + public void setSchemaValidation(boolean schemaValidation) { + this.schemaValidation = schemaValidation; + } + + public void setSchema(String schema) { + this.schema = schema; + } + + public void setBodyLiteral(String bodyLiteral) { + this.bodyLiteral = bodyLiteral; + } + + public void setBodyContentType(String bodyContentType) { + this.bodyContentType = bodyContentType; + } + + public void setBodyLiteralContentType(String bodyLiteralContentType) { + this.bodyLiteralContentType = bodyLiteralContentType; + } + + public void setResponseAcceptType(String responseAcceptType) { + this.responseAcceptType = responseAcceptType; + } + + public void setCookie(Map cookies) { + this.cookies = cookies; + } + + public void setHeader(Map headers) { + this.headers = headers; + } + + public void setBodyFile(String bodyFile) { + this.bodyFile = bodyFile; + } + + public void setResponseType(String responseType) { + this.responseType = responseType; + } + + public void setResponseStatus(int responseStatus) { + this.responseStatus = responseStatus; + } + + public void setResponseReasonPhrase(String responseReasonPhrase) { + this.responseReasonPhrase = responseReasonPhrase; + } + + public void setResponseVersion(String responseVersion) { + this.responseVersion = responseVersion; + } + + public void setResource(String resource) { + this.resource = resource; + } + + public void setResponseVariable(Map responseVariable) { + this.responseVariable = responseVariable; + } + + public void setResponseValue(Map responseValue) { + this.responseValue = responseValue; + } + + public void setScript(String script) { + this.script = script; + } + + public void setType(String type) { + this.type = type; + } + + protected HttpClientRequestActionBuilder customizeBuilder(GeneratedApi generatedApi, + TestContext context, HttpClientRequestActionBuilder httpClientRequestActionBuilder) { + + httpClientRequestActionBuilder = customizeByBeans(generatedApi, context, + httpClientRequestActionBuilder); + + httpClientRequestActionBuilder = customizeBySpi(generatedApi, context, httpClientRequestActionBuilder); + + return httpClientRequestActionBuilder; + } + + private HttpClientRequestActionBuilder customizeBySpi(GeneratedApi generatedApi, TestContext context, + HttpClientRequestActionBuilder httpClientRequestActionBuilder) { + ServiceLoader serviceLoader = ServiceLoader.load( + ApiActionBuilderCustomizerService.class, ApiActionBuilderCustomizerService.class.getClassLoader()); + for (ApiActionBuilderCustomizerService service :serviceLoader) { + httpClientRequestActionBuilder = service.build(generatedApi, this, context, httpClientRequestActionBuilder); + } + return httpClientRequestActionBuilder; + } + + private HttpClientRequestActionBuilder customizeByBeans( + GeneratedApi generatedApi, TestContext context, + HttpClientRequestActionBuilder httpClientRequestActionBuilder) { + if (actionBuilderCustomizerServices != null) { + for (ApiActionBuilderCustomizerService apiActionBuilderCustomizer : actionBuilderCustomizerServices) { + httpClientRequestActionBuilder = apiActionBuilderCustomizer.build(generatedApi, this, + context, httpClientRequestActionBuilder); + } + } + return httpClientRequestActionBuilder; + } +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/test_base_soap.mustache b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/test_base_soap.mustache new file mode 100644 index 0000000000..051485041e --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/test_base_soap.mustache @@ -0,0 +1,187 @@ +/** + * ================================================== + * GENERATED CLASS, ALL CHANGES WILL BE LOST + * ================================================== + */ + +package {{invokerPackage}}.citrus; + +import jakarta.annotation.Generated; +import java.util.List; +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; +import org.citrusframework.validation.DelegatingPayloadVariableExtractor; +import org.citrusframework.validation.PathExpressionValidationContext; +import org.citrusframework.validation.script.ScriptValidationContext; +import org.citrusframework.ws.actions.ReceiveSoapMessageAction; +import org.citrusframework.ws.actions.ReceiveSoapMessageAction.SoapMessageBuilderSupport; +import org.citrusframework.ws.actions.SendSoapMessageAction; +import org.citrusframework.ws.actions.SoapActionBuilder; +import org.citrusframework.ws.client.WebServiceClient; +import org.slf4j.Marker; +import org.slf4j.MarkerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.util.CollectionUtils; + +import javax.sql.DataSource; +import java.util.Map; + +@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen") +public abstract class {{prefix}}AbstractTestRequest extends AbstractTestAction { + + protected final Marker coverageMarker = MarkerFactory.getMarker("{{#lambda.uppercase}}{{prefix}}{{/lambda.uppercase}}-API-COVERAGE"); + + @Autowired + @Qualifier("{{apiEndpoint}}") + protected WebServiceClient wsClient; + + @Autowired(required = false) + protected DataSource dataSource; + + @Autowired(required = false) + private List actionBuilderCustomizerServices; + + // attributes of differentNodes + protected String bodyContentType; + protected String bodyLiteralContentType; + protected String bodyFile; + protected String bodyLiteral; + + // children of response element + protected String resource; + protected Map responseVariable; // Contains the 'XPATH' as key and the 'VARIABLE NAME' as value + protected Map responseValue; // Contains the 'XPATH' as key and the 'VALUE TO BE VALIDATED' as value + protected String script; + protected String type; // default script type is groovy - supported types see com.consol.citrus.script.ScriptTypes + protected Map soapHeaders; + protected Map mimeHeaders; + + @Override + public void doExecute(TestContext context) { + sendRequest(context); + receiveResponse(context); + } + + /** + * This method receives the HTTP-Response + */ + public void receiveResponse(TestContext context) { + + ReceiveSoapMessageAction.Builder soapReceiveMessageActionBuilder = new SoapActionBuilder().client(wsClient).receive(); + SoapMessageBuilderSupport messageBuilderSupport = soapReceiveMessageActionBuilder.getMessageBuilderSupport(); + + if (resource != null) { + messageBuilderSupport.body(Resources.create(resource)); + } + + if (!CollectionUtils.isEmpty(responseVariable)) { + DelegatingPayloadVariableExtractor.Builder extractorBuilder = new DelegatingPayloadVariableExtractor.Builder(); + responseVariable.forEach(extractorBuilder::expression); + messageBuilderSupport.extract(extractorBuilder); + } + + if (!CollectionUtils.isEmpty(responseValue)) { + PathExpressionValidationContext.Builder validationContextBuilder = new PathExpressionValidationContext.Builder(); + responseValue.forEach(validationContextBuilder::expression); + messageBuilderSupport.validate(validationContextBuilder); + } + + if (script != null) { + ScriptValidationContext.Builder scriptValidationContextBuilder = new ScriptValidationContext.Builder(); + if (type != null) { + scriptValidationContextBuilder.scriptType(type); + } + scriptValidationContextBuilder.script(script); + messageBuilderSupport.validate(scriptValidationContextBuilder); + } + + soapReceiveMessageActionBuilder.withReferenceResolver(context.getReferenceResolver()); + soapReceiveMessageActionBuilder.build().execute(context); + } + + public abstract void sendRequest(TestContext context); + + public void setBodyLiteral(String bodyLiteral) { + this.bodyLiteral = bodyLiteral; + } + public void setBodyContentType(String bodyContentType) { + this.bodyContentType = bodyContentType; + } + + public void setBodyLiteralContentType(String bodyLiteralContentType) { + this.bodyLiteralContentType = bodyLiteralContentType; + } + + public void setBodyFile(String bodyFile) { + this.bodyFile = bodyFile; + } + + public void setResource(String resource) { + this.resource = resource; + } + + public void setResponseVariable(Map responseVariable) { + this.responseVariable = responseVariable; + } + + public void setResponseValue(Map responseValue) { + this.responseValue = responseValue; + } + + public void setScript(String script) { + this.script = script; + } + + public void setType(String type) { + this.type = type; + } + + public void setSoapHeader(Map soapHeaders) { + this.soapHeaders = soapHeaders; + } + + public void setMimeHeader(Map mimeHeaders) { + this.mimeHeaders = mimeHeaders; + } + + protected SendSoapMessageAction.Builder customizeBuilder(GeneratedApi generatedApi, + TestContext context, SendSoapMessageAction.Builder sendSoapMessageActionBuilder) { + + sendSoapMessageActionBuilder = customizeByBeans(generatedApi, context, sendSoapMessageActionBuilder); + + sendSoapMessageActionBuilder = customizeBySpi(generatedApi, context, sendSoapMessageActionBuilder); + + return sendSoapMessageActionBuilder; + } + + private SendSoapMessageAction.Builder customizeBySpi(GeneratedApi generatedApi, TestContext context, + SendSoapMessageAction.Builder sendSoapMessageActionBuilder) { + + ServiceLoader serviceLoader = ServiceLoader.load( + ApiActionBuilderCustomizerService.class, ApiActionBuilderCustomizerService.class.getClassLoader()); + for (ApiActionBuilderCustomizerService service :serviceLoader) { + sendSoapMessageActionBuilder = service.build(generatedApi, this, context, sendSoapMessageActionBuilder); + } + + return sendSoapMessageActionBuilder; + } + + private SendSoapMessageAction.Builder customizeByBeans( + GeneratedApi generatedApi, TestContext context, SendSoapMessageAction.Builder sendSoapMessageActionBuilder) { + + if (actionBuilderCustomizerServices != null) { + for (ApiActionBuilderCustomizerService apiActionBuilderCustomizer : actionBuilderCustomizerServices) { + sendSoapMessageActionBuilder = apiActionBuilderCustomizer.build(generatedApi, this, + context, sendSoapMessageActionBuilder); + } + } + + return sendSoapMessageActionBuilder; + } +} 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 new file mode 100644 index 0000000000..0240db1100 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/GeneratedApiIT.java @@ -0,0 +1,626 @@ +package org.citrusframework.openapi.generator; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.citrusframework.message.MessagePayloadUtils.normalizeWhitespace; +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.verify; +import static org.mockito.Mockito.when; + +import com.networknt.schema.JsonSchema; +import com.networknt.schema.ValidationMessage; +import jakarta.servlet.http.Cookie; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; +import org.assertj.core.api.InstanceOfAssertFactories; +import org.citrusframework.Citrus; +import org.citrusframework.CitrusInstanceManager; +import org.citrusframework.TestAction; +import org.citrusframework.TestCase; +import org.citrusframework.actions.SendMessageAction.SendMessageActionBuilder; +import org.citrusframework.common.SpringXmlTestLoader; +import org.citrusframework.common.TestLoader; +import org.citrusframework.config.CitrusSpringConfig; +import org.citrusframework.context.TestContext; +import org.citrusframework.endpoint.EndpointConfiguration; +import org.citrusframework.exceptions.CitrusRuntimeException; +import org.citrusframework.exceptions.ValidationException; +import org.citrusframework.http.client.HttpClient; +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.Message; +import org.citrusframework.messaging.Producer; +import org.citrusframework.messaging.SelectiveConsumer; +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; +import org.junit.jupiter.params.provider.MethodSource; +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; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.core.io.Resource; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.TestPropertySource; +import org.springframework.util.MultiValueMap; + +/** + * This test tests the generated API + */ +@Isolated +@DirtiesContext +@ExtendWith({CitrusSpringExtension.class}) +@SpringBootTest(classes = {CitrusSpringConfig.class, GeneratedApiIT.Config.class}) +@TestPropertySource( + properties = {"applicationServiceClient.basic.username=Max Mustermann", + "applicationServiceClient.basic.password=Top secret"} +) +class GeneratedApiIT { + + @Autowired + private ApplicationContext applicationContext; + + @Autowired + private HttpClient httpClientMock; + + @Mock + private Producer producerMock; + + @Mock + private SelectiveConsumer consumerMock; + + private TestContext testContext; + + @BeforeEach + void beforeEach() { + testContext = applicationContext.getBean(TestContext.class); + } + + @Test + void testValidationFailure() { + mockProducerAndConsumer(createReceiveMessage("{\"some\": \"payload\"}")); + assertThatThrownBy( + () -> executeTest("getPetByIdRequestTest", testContext)).hasCauseExactlyInstanceOf( + ValidationException.class); + } + + @Nested + class WithValidationMatcher { + + @BeforeEach + void beforeEach() { + mockProducerAndConsumer(createReceiveMessage("")); + } + + @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 testSendWithBodyLiteralWithVariable() { + ArgumentMatcher messageMatcher = message -> { + HttpMessage httpMessage = (HttpMessage) message; + assertThat(((String) httpMessage.getPayload()).trim()).isEqualTo("{\"id\": 15}"); + return true; + }; + sendAndValidateMessage("sendWithBodyLiteralWithVariableTest", messageMatcher); + } + + @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); + } + + @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); + } + + @Test + void testSendWithBodyLiteral() { + ArgumentMatcher messageMatcher = message -> { + HttpMessage httpMessage = (HttpMessage) message; + assertThat(((String) httpMessage.getPayload()).trim()).isEqualTo("{\"id\": 13}"); + return true; + }; + + sendAndValidateMessage("sendWithBodyLiteralTest", messageMatcher); + } + + private void sendAndValidateMessage(String testName, + ArgumentMatcher messageMatcher) { + GeneratedApiIT.this.sendAndValidateMessage(testName, messageMatcher, + AddPetRequest.class); + } + + } + + @Nested + class WithMultipartMessage { + + @Test + void testSendMultipartFile() { + mockProducerAndConsumer(createReceiveMessage("")); + + 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); + } + + return true; + }; + + sendAndValidateMessage("postFileTest", messageMatcher, PostFileRequest.class); + } + + @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"); + } + + @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); + } + + @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); + } + } + + @Nested + class WithDefaultReceiveMessage { + + private Message defaultRecieveMessage; + + @BeforeEach + void beforeEach() throws IOException { + defaultRecieveMessage = createReceiveMessage( + readToString(Resources.create( + "org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage1.json"), + StandardCharsets.UTF_8) + ); + mockProducerAndConsumer(defaultRecieveMessage); + } + + @Test + void testJsonPathExtraction() { + 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 testCustomizer() { + 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("x-citrus-api-version")).isEqualTo( + "1.0.0"); + + return true; + }; + verify(producerMock).send(ArgumentMatchers.argThat(messageMatcher), eq(testContext)); + verify(consumerMock).receive(testContext, 5000L); + } + + @Test + void testBasicAuthorization() { + 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(producerMock).send(ArgumentMatchers.argThat(messageMatcher), eq(testContext)); + verify(consumerMock).receive(testContext, 5000L); + } + + @Test + void testRequestPath() { + 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(producerMock).send(ArgumentMatchers.argThat(messageMatcher), eq(testContext)); + verify(consumerMock).receive(testContext, 5000L); + } + + @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); + } + + @Test + void testJsonPathValidation() { + TestCase testCase = executeTest("jsonPathValidationTest", testContext); + assertTestActionType(testCase, GetPetByIdRequest.class); + } + + @Test + void scriptValidationFailureTest() { + TestCase testCase = executeTest("scriptValidationTest", testContext); + assertTestActionType(testCase, GetPetByIdRequest.class); + } + + @Test + void jsonSchemaValidationFailureTest() { + assertThatThrownBy(() -> executeTest("jsonSchemaValidationFailureTest", testContext)) + .hasCauseExactlyInstanceOf(ValidationException.class); + + SimpleJsonSchema testSchema = (SimpleJsonSchema) applicationContext.getBean( + "failingTestSchema"); + + // Assert that schema validation was called + verify(testSchema).getSchema(); + JsonSchema schema = testSchema.getSchema(); + verify(schema).validate(any()); + } + + @Test + void jsonDeactivatedSchemaValidationTest() { + SimpleJsonSchema testSchema = (SimpleJsonSchema) applicationContext.getBean( + "testSchema"); + Mockito.clearInvocations(testSchema, testSchema.getSchema()); + + TestCase testCase = executeTest("jsonDeactivatedSchemaValidationTest", testContext); + + assertTestActionType(testCase, GetPetByIdRequest.class); + + // Assert that schema validation was called + Mockito.verifyNoInteractions(testSchema); + } + + @Test + void defaultOas3SchemaValidationTest() { + SimpleJsonSchema testSchema = (SimpleJsonSchema) applicationContext.getBean("oas3"); + Mockito.clearInvocations(testSchema, testSchema.getSchema()); + + TestCase testCase = executeTest("defaultOas3SchemaValidationTest", testContext); + + assertTestActionType(testCase, GetPetByIdRequest.class); + + // Assert that schema validation was called + verify(testSchema).getSchema(); + JsonSchema schema = testSchema.getSchema(); + verify(schema).validate(any()); + } + + @Test + void jsonSchemaValidationTest() { + SimpleJsonSchema testSchema = (SimpleJsonSchema) applicationContext.getBean( + "testSchema"); + Mockito.clearInvocations(testSchema, testSchema.getSchema()); + + TestCase testCase = executeTest("jsonSchemaValidationTest", testContext); + + assertTestActionType(testCase, GetPetByIdRequest.class); + + // Assert that schema validation was called + verify(testSchema).getSchema(); + JsonSchema schema = testSchema.getSchema(); + verify(schema).validate(any()); + } + + @Test + void testJsonPathValidationFailure() { + mockProducerAndConsumer(defaultRecieveMessage); + + assertThatThrownBy(() -> executeTest("jsonPathValidationFailureTest", testContext)) + .hasCauseExactlyInstanceOf(ValidationException.class); + } + + 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'" + ) + ); + } + + @ParameterizedTest + @MethodSource + void testValidationFailures(String testName, String expectedErrorMessage) { + assertThatThrownBy(() -> executeTest(testName, testContext)) + .hasCauseExactlyInstanceOf(ValidationException.class) + .message() + .startsWith(expectedErrorMessage); + } + } + +// @Test +// void testCoverageLogger() throws IOException { +// List logMessages = new ArrayList<>(); +// Logger logger = LoggerFactory.getLogger(GetPetByIdRequest.class); +// org.qos.logback.classic.Logger l = (org.qos.logback.classic.Logger) logger; +// l.setLevel(Level.TRACE); +// l.addAppender( +// new AppenderBase<>() { +// @Override +// protected void append(ILoggingEvent eventObject) {} +// +// @Override +// public synchronized void doAppend(ILoggingEvent eventObject) { +// logMessages.add(eventObject.getMessage()); +// super.doAppend(eventObject); +// } +// } +// ); +// +// +// +// mockProducer(httpClient); +// +// Message receiveMessage = createReceiveMessage( +// FileUtils.readToString(Resources.create("org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage1.json"), StandardCharsets.UTF_8) +// ); +// +// mockConsumer(httpClient, testContext, receiveMessage); +// +// executeTest("getPetByIdRequestTest", testContext); +// +// assertThat(logMessages.get(0)).isEqualTo("getPetById;GET;\"{}\";\"\";\"\""); +// } + + /** + * Test the send message using the given matcher + */ + private void sendAndValidateMessage(String testName, ArgumentMatcher messageMatcher, + Class apiClass) { + + TestCase testCase = executeTest(testName, testContext); + assertTestActionType(testCase, apiClass); + + verify(producerMock).send(ArgumentMatchers.argThat(messageMatcher), eq(testContext)); + } + + /** + * Assert that an action of type 'apiClass' is contained in the list of test actions + */ + private void assertTestActionType(TestCase testCase, Class apiClass) { + TestAction testAction = testCase + .getActions() + .stream() + .filter(action -> apiClass.isAssignableFrom(action.getClass())) + .findAny() + .orElse(null); + assertThat(testAction).isNotNull(); + } + + 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) { + 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"); + loader.load(); + return loader.getTestCase(); + } + + private Message createReceiveMessage(String payload) { + Message receiveMessage = new DefaultMessage(); + receiveMessage.setPayload(payload); + receiveMessage.getHeaders().put("citrus_http_reason_phrase", "OK"); + receiveMessage.getHeaders().put("citrus_http_version", "HTTP/1.1"); + receiveMessage.getHeaders().put("citrus_http_status_code", 200); + return receiveMessage; + } + + public static class Config { + + @Bean(name = {"applicationServiceClient", "multipartTestEndpoint", + "soapSampleStoreEndpoint", "petStoreEndpoint"}) + public HttpClient applicationServiceClient() { + HttpClient clientMock = mock(); + EndpointConfiguration endpointConfigurationMock = mock(); + when(clientMock.getEndpointConfiguration()).thenReturn(new HttpEndpointConfiguration()); + when(endpointConfigurationMock.getTimeout()).thenReturn(5000L); + return clientMock; + } + + @Bean + public ApiActionBuilderCustomizerService customizer() { + return new ApiActionBuilderCustomizerService() { + @Override + public > T build( + GeneratedApi generatedApi, TestAction action, TestContext context, T builder) { + builder.getMessageBuilderSupport() + .header("x-citrus-api-version", generatedApi.getApiVersion()); + return builder; + } + }; + } + + @Bean({"oas3", "testSchema"}) + public SimpleJsonSchema testSchema() { + JsonSchema schemaMock = mock(); + SimpleJsonSchema jsonSchemaMock = mock(); + + when(jsonSchemaMock.getSchema()).thenReturn(schemaMock); + + Set okReport = new HashSet<>(); + when(schemaMock.validate(any())).thenReturn(okReport); + return jsonSchemaMock; + } + + @Bean + public SimpleJsonSchema failingTestSchema() { + JsonSchema schemaMock = mock(); + SimpleJsonSchema jsonSchemaMock = mock(); + + when(jsonSchemaMock.getSchema()).thenReturn(schemaMock); + + Set nokReport = new HashSet<>(); + nokReport.add(new ValidationMessage.Builder().customMessage( + "This is a simulated validation error message").build()); + 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/java/org/citrusframework/openapi/generator/JavaCitrusCodegenIT.java b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/JavaCitrusCodegenIT.java new file mode 100644 index 0000000000..dd4f52ece7 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/JavaCitrusCodegenIT.java @@ -0,0 +1,93 @@ +package org.citrusframework.openapi.generator; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.stream.Stream; +import org.apache.commons.lang3.stream.Streams; +import org.citrusframework.exceptions.CitrusRuntimeException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; + +/** + * This test case is designed to validate the consistency of the code generation process and detect + * any discrepancies between the generated API files and the reference files stored in + * '/JavaCitrusCodegenIntegrationTest/expectedgen/'. It compares the results of API generation + * against the reference files, and a failure indicates potential changes in mustache templates or + * code generation logic. + *

+ * If this test fails, it is essential to review the code generation process and underlying + * templates carefully. If the changes are intentional and verified, update the reference files by + * copying the generated API sources to the '/JavaCitrusCodegenIntegrationTest/expectedgen/' + * directory. To ensure accurate copying, without unwanted code formatting, use a simple File + * Explorer instead of relying on IDE-based operations. + */ +class JavaCitrusCodegenIT { + + static Stream getResourcesForRest() throws IOException { + return geClassResourcesIgnoringInnerClasses("org/citrusframework/openapi/generator/rest"); + } + + @ParameterizedTest + @MethodSource("getResourcesForRest") + void testGeneratedFiles(Resource resource) throws IOException { + File classFile = resource.getFile(); + String absolutePath = classFile.getAbsolutePath(); + String javaFilePath = absolutePath.replace("test-classes", "generated-test-sources") + .replace(".class", ".java"); + + assertFileContent(new File(javaFilePath), "rest"); + } + + static Stream getResourcesForSoap() throws IOException { + return geClassResourcesIgnoringInnerClasses( + "org/citrusframework/openapi/generator/soap/bookservice"); + } + + @ParameterizedTest + @MethodSource("getResourcesForSoap") + void testGeneratedSoapFiles(Resource resource) throws IOException { + File classFile = resource.getFile(); + String absolutePath = classFile.getAbsolutePath(); + + String javaFilePath = absolutePath.replace("test-classes", "generated-test-sources") + .replace(".class", ".java"); + + assertFileContent(new File(javaFilePath), "soap"); + } + + private static Stream geClassResourcesIgnoringInnerClasses(String path) + throws IOException { + return Streams.of(new PathMatchingResourcePatternResolver().getResources( + path + "/**/*.class")).filter(resource -> { + try { + return !resource.getURI().toString().contains("$"); + } catch (Exception e) { + throw new CitrusRuntimeException("Unable to retrieve URL from resource!"); + } + }).map(Arguments::arguments); + } + + private void assertFileContent(File file, String apiDir) throws IOException { + assertThat(file).exists(); + String expectedFilePath = + "org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/" + + file.getAbsolutePath().substring(file.getAbsolutePath().indexOf(apiDir)); + + ClassPathResource classPathResource = new ClassPathResource(expectedFilePath); + + /* + * NOTE: when changes have been performed to mustache templates, the expected files need to be updated. + * Be aware that file content may change according to IDE formatting rules if the files are copied via IDE. + * Files should therefore be copied using a file explorer which ensures that content of files does not change. + */ + assertThat(file).hasSameTextualContentAs(classPathResource.getFile()); + } +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/JavaCitrusCodegenTest.java b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/JavaCitrusCodegenTest.java new file mode 100644 index 0000000000..57f49cd861 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/JavaCitrusCodegenTest.java @@ -0,0 +1,166 @@ +package org.citrusframework.openapi.generator; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.citrusframework.openapi.generator.JavaCitrusCodegen.CODEGEN_NAME; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.junit.jupiter.api.Test; +import org.openapitools.codegen.ClientOptInput; +import org.openapitools.codegen.CodegenConfigLoader; +import org.openapitools.codegen.DefaultGenerator; +import org.openapitools.codegen.config.CodegenConfigurator; + +/** + * This test validates the code generation process. + *

+ * It may also serve as an entry point for debugging the code generation process. When executed in debug mode, it allows you to + * step through the generation process and inspect the resulting output in the specified output directory. + *

+ * To debug the code generator: + *

    + *
  1. Set a breakpoint in the {@code postProcessOperationsWithModels()} method of {@code JavaCitrusCodegen.java}.
  2. + *
  3. In your IDE, launch this test by right-clicking and selecting Debug As > JUnit Test.
  4. + *
+ */ + +class JavaCitrusCodegenTest { + + @Test + void retrieveGeneratorBsSpi() { + JavaCitrusCodegen codegen = (JavaCitrusCodegen) CodegenConfigLoader.forName("java-citrus"); + assertThat(codegen).isNotNull(); + } + + @Test + void arePredefinedValuesNotEmptyTest() { + JavaCitrusCodegen codegen = new JavaCitrusCodegen(); + + assertThat(codegen.getName()).isEqualTo(CODEGEN_NAME); + assertThat(codegen.getHelp()).isNotEmpty(); + assertThat(codegen.getHttpClient()).isNotEmpty(); + assertThat(codegen.getOpenapiSchema()).isNotEmpty(); + assertThat(codegen.getApiPrefix()).isNotEmpty(); + assertThat(codegen.getHttpPathPrefix()).isNotEmpty(); + assertThat(codegen.getTargetXmlnsNamespace()).isNull(); + assertThat(codegen.getGeneratedSchemaFolder()).isNotEmpty(); + } + + @Test + void areAdditionalPropertiesProcessedTest() { + final String httpClient = "myTestEndpoint"; + final String openapiSchema = "testSchema"; + final String prefix = "testPrefix"; + final String httpPathPrefix = "test/path"; + final String targetXmlnsNamespace = "http://www.citrusframework.org/schema/test/extension"; + final String generatedSchemaFolder = "generatedResourceFolder"; + + Map properties = new HashMap<>(); + properties.put(JavaCitrusCodegen.API_ENDPOINT, httpClient); + properties.put(JavaCitrusCodegen.GENERATED_SCHEMA_FOLDER, generatedSchemaFolder); + properties.put(JavaCitrusCodegen.HTTP_PATH_PREFIX, httpPathPrefix); + properties.put(JavaCitrusCodegen.OPENAPI_SCHEMA, openapiSchema); + properties.put(JavaCitrusCodegen.PREFIX, prefix); + properties.put(JavaCitrusCodegen.TARGET_XMLNS_NAMESPACE, targetXmlnsNamespace); + + JavaCitrusCodegen codegen = new JavaCitrusCodegen(); + codegen.additionalProperties().putAll(properties); + codegen.processOpts(); + + assertThat(codegen.getApiPrefix()).isEqualTo(prefix); + assertThat(codegen.getGeneratedSchemaFolder()).isEqualTo(generatedSchemaFolder); + assertThat(codegen.getHttpClient()).isEqualTo(httpClient); + assertThat(codegen.getHttpPathPrefix()).isEqualTo(httpPathPrefix); + assertThat(codegen.getOpenapiSchema()).isEqualTo(openapiSchema); + assertThat(codegen.getTargetXmlnsNamespace()).isEqualTo(targetXmlnsNamespace); + } + + @Test + void areReservedWordsEscapedTest() throws IOException { + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName(CODEGEN_NAME) + .setInputSpec("src/test/resources/apis/petstore_reservedWords.yaml") + .setOutputDir("target/JavaCitrusCodegenTest/petstore_escapedWords"); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + List outputFiles = generator.opts(clientOptInput).generate(); + + Optional file = outputFiles.stream().filter(x -> "PetApi.java".equals(x.getName())) + .findFirst(); + + assertThat(file).isPresent(); + + List lines = Files.readAllLines(file.get().toPath(), StandardCharsets.UTF_8); + + // "name" is a reserved word, so it should be escaped with an underline for the second parameter + assertThat(lines.stream().filter(x -> x.contains("\"name\", this._name")).count()).isEqualTo(1L); + } + + @Test + void arePathParamsFieldsPresent() throws IOException { + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName(CODEGEN_NAME) + .setInputSpec("src/test/resources/apis/petstore.yaml") + .setOutputDir("target/JavaCitrusCodegenTest/petstore"); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + List outputFiles = generator.opts(clientOptInput).generate(); + + Optional file = outputFiles.stream().filter(x -> "PetApi.java".equals(x.getName())) + .findFirst(); + + assertThat(file).isPresent(); + + List lines = Files.readAllLines(file.get().toPath(), StandardCharsets.UTF_8); + + // "name" is a reserved word, so it should be escaped with an underline for the second parameter + assertThat(lines.stream().filter(x -> x.contains("private String petId;")).count()).isEqualTo(4L); + assertThat(lines.stream().filter( + x -> x.contains("endpoint = endpoint.replace(\"{\" + \"petId\" + \"}\", petId);")) + .count()).isEqualTo(4L); + } + + @Test + void areBasicAuthFieldsPresent() throws IOException { + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName(CODEGEN_NAME) + .setInputSpec("src/test/resources/apis/petstore.yaml") + .setOutputDir("target/JavaCitrusCodegenTest/petstore"); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + List outputFiles = generator.opts(clientOptInput).generate(); + + Optional file = outputFiles.stream().filter(x -> "PetApi.java".equals(x.getName())) + .findFirst(); + + assertThat(file).isPresent(); + + List lines = Files.readAllLines(file.get().toPath(), StandardCharsets.UTF_8); + + // "name" is a reserved word, so it should be escaped with an underline for the second parameter + assertThat(lines.stream() + .filter(x -> x.contains("@Value(\"${\" + \"apiEndpoint.basic.username:#{null}}\")")) + .count()).isEqualTo(1L); + assertThat( + lines.stream().filter(x -> x.contains("private String basicUsername;")).count()).isEqualTo(1L); + assertThat( + lines + .stream() + .filter(x -> + x.contains( + "messageBuilderSupport.header(\"Authorization\", \"Basic \" + Base64.getEncoder().encodeToString((context.replaceDynamicContentInString(basicUsername)+\":\"+context.replaceDynamicContentInString(basicPassword)).getBytes()));" + ) + ) + .count() + ).isEqualTo(1L); + } +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/ServiceLoaderTest.java b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/ServiceLoaderTest.java new file mode 100644 index 0000000000..6b17f01f13 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/ServiceLoaderTest.java @@ -0,0 +1,24 @@ +package org.citrusframework.openapi.generator; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.ServiceLoader; +import java.util.ServiceLoader.Provider; +import org.citrusframework.testapi.ApiActionBuilderCustomizerService; +import org.citrusframework.openapi.generator.util.TestApiActionBuilderCustomizer; +import org.junit.jupiter.api.Test; + +class ServiceLoaderTest { + + @Test + void test() { + ServiceLoader serviceLoader = ServiceLoader.load( + ApiActionBuilderCustomizerService.class, ApiActionBuilderCustomizerService.class.getClassLoader()); + List> list = serviceLoader.stream().toList(); + assertThat(list).hasSize(1); + ApiActionBuilderCustomizerService apiActionBuilderCustomizerService = list.iterator().next() + .get(); + assertThat(apiActionBuilderCustomizerService).isInstanceOf(TestApiActionBuilderCustomizer.class); + } +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest.java b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest.java new file mode 100644 index 0000000000..1ae66986fd --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest.java @@ -0,0 +1,29 @@ +package org.citrusframework.openapi.generator; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.citrusframework.util.FileUtils.readToString; + +import java.io.IOException; +import org.citrusframework.openapi.generator.exception.WsdlToOpenApiTransformationException; +import org.citrusframework.spi.Resource; +import org.citrusframework.spi.Resources.ClasspathResource; +import org.junit.jupiter.api.Test; +import org.springframework.core.io.ClassPathResource; + +class SimpleWsdlToOpenApiTransformerTest { + + @Test + void testTransform() throws WsdlToOpenApiTransformationException, IOException { + ClassPathResource wsdlResource = new ClassPathResource( + "/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest/BookService.wsdl"); + + SimpleWsdlToOpenApiTransformer simpleWsdlToOpenApiTransformer = new SimpleWsdlToOpenApiTransformer(wsdlResource.getURI()); + String generatedYaml = simpleWsdlToOpenApiTransformer.transformToOpenApi(); + + Resource expectedYamlResource = new ClasspathResource( + "/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest/BookService-generated.yaml"); + + String expectedYaml = readToString(expectedYamlResource); + assertThat(generatedYaml).isEqualToIgnoringWhitespace(expectedYaml); + } +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/SpringBeanConfigurationIT.java b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/SpringBeanConfigurationIT.java new file mode 100644 index 0000000000..2f5dbf7179 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/SpringBeanConfigurationIT.java @@ -0,0 +1,55 @@ +package org.citrusframework.openapi.generator; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.citrusframework.annotations.CitrusResource; +import org.citrusframework.annotations.CitrusTest; +import org.citrusframework.config.CitrusSpringConfig; +import org.citrusframework.context.TestContext; +import org.citrusframework.http.client.HttpClient; +import org.citrusframework.http.client.HttpEndpointConfiguration; +import org.citrusframework.junit.jupiter.spring.CitrusSpringSupport; +import org.citrusframework.openapi.generator.rest.petstore.request.PetApi.AddPetRequest; +import org.citrusframework.openapi.generator.rest.petstore.spring.PetStoreBeanConfiguration; +import org.junit.jupiter.api.Test; +import org.citrusframework.openapi.generator.SpringBeanConfigurationIT.ClientConfiguration; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; + +@CitrusSpringSupport +@ContextConfiguration(classes = {CitrusSpringConfig.class, ClientConfiguration.class, PetStoreBeanConfiguration.class}) +class SpringBeanConfigurationIT { + + @Autowired + private ApplicationContext applicationContext; + + @Test + @CitrusTest + void fromReferenceResolverIsPrototypeScoped(@CitrusResource TestContext testContext) { + var addPetRequest = testContext.getReferenceResolver().resolve(AddPetRequest.class); + assertThat(addPetRequest) + .isNotNull() + .isNotEqualTo(testContext.getReferenceResolver().resolve(AddPetRequest.class)); + } + + @Test + void fromSpringApplicationContextIsPrototypeScoped() { + assertThat(applicationContext.getBean(AddPetRequest.class)) + .isNotNull() + .isNotEqualTo(applicationContext.getBean(AddPetRequest.class)); + } + + @TestConfiguration + public static class ClientConfiguration { + + @Bean(name= {"applicationServiceClient", "petStoreEndpoint"}) + public HttpClient applicationServiceClient() { + var config = new HttpEndpointConfiguration(); + config.setRequestUrl("http://localhost:9000"); + return new HttpClient(config); + } + } +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/util/TestApiActionBuilderCustomizer.java b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/util/TestApiActionBuilderCustomizer.java new file mode 100644 index 0000000000..0aaa9761ab --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/util/TestApiActionBuilderCustomizer.java @@ -0,0 +1,22 @@ +package org.citrusframework.openapi.generator.util; + +import org.citrusframework.TestAction; +import org.citrusframework.TestActionBuilder; +import org.citrusframework.actions.SendMessageAction.SendMessageActionBuilder; +import org.citrusframework.context.TestContext; +import org.citrusframework.testapi.ApiActionBuilderCustomizerService; +import org.citrusframework.testapi.GeneratedApi; + +public class TestApiActionBuilderCustomizer implements ApiActionBuilderCustomizerService { + + @Override + public > T build(GeneratedApi generatedApi, TestAction action, + TestContext context, T builder) { + + generatedApi.getApiInfoExtensions().forEach((key, value) -> { + builder.getMessageBuilderSupport().header(key, value); + }); + + return builder; + } +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/META-INF/services/org.citrusframework.testapi.ApiActionBuilderCustomizerService b/test-api-generator/citrus-test-api-generator-core/src/test/resources/META-INF/services/org.citrusframework.testapi.ApiActionBuilderCustomizerService new file mode 100644 index 0000000000..ba96f521f6 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/META-INF/services/org.citrusframework.testapi.ApiActionBuilderCustomizerService @@ -0,0 +1 @@ +org.citrusframework.openapi.generator.util.TestApiActionBuilderCustomizer \ No newline at end of file diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/META-INF/spring.handlers b/test-api-generator/citrus-test-api-generator-core/src/test/resources/META-INF/spring.handlers new file mode 100644 index 0000000000..1f0c4bdb95 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/META-INF/spring.handlers @@ -0,0 +1,3 @@ +http\://www.citrusframework.org/citrus-test-schema/multiparttest-api=org.citrusframework.openapi.generator.rest.multiparttest.citrus.extension.MultipartTestNamespaceHandler +http\://www.citrusframework.org/citrus-test-schema/openapifromwsdl-api=org.citrusframework.openapi.generator.soap.bookservice.citrus.extension.OpenApiFromWsdlNamespaceHandler +http\://www.citrusframework.org/citrus-test-schema/petstore-api=org.citrusframework.openapi.generator.rest.petstore.citrus.extension.PetStoreNamespaceHandler diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/META-INF/spring.schemas b/test-api-generator/citrus-test-api-generator-core/src/test/resources/META-INF/spring.schemas new file mode 100644 index 0000000000..0050010472 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/META-INF/spring.schemas @@ -0,0 +1,3 @@ +http\://www.citrusframework.org/citrus-test-schema/multiparttest-api/multiparttest-api.xsd=schema/xsd/multiparttest-api.xsd +http\://www.citrusframework.org/citrus-test-schema/openapifromwsdl-api/openapifromwsdl-api.xsd=schema/xsd/openapifromwsdl-api.xsd +http\://www.citrusframework.org/citrus-test-schema/petstore-api/petstore-api.xsd=schema/xsd/petstore-api.xsd diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/apis/multiparttest-rest-resource.yaml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/apis/multiparttest-rest-resource.yaml new file mode 100644 index 0000000000..b77b55c4d2 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/apis/multiparttest-rest-resource.yaml @@ -0,0 +1,249 @@ +openapi: 3.0.3 +info: + title: multiparttest API + version: 2.0.0 + description: | + The service to test mutlipart + x-citrus-app: MPT + x-citrus-api-name: multiparttest-rest-resource + contact: + name: IT-Services-CI TAuBE + email: IT-Serv-CI-ETAdl@post.ch + url: https://confluence.pnet.ch/pages/viewpage.action?pageId=314828825 +tags: + - name: multiparttest-controller +paths: + /api/v2/multitest-file/{bucket}/{filename}/random: + post: + tags: + - multiparttest-controller + operationId: postRandom + summary: Uploads random file. + parameters: + - name: bucket + description: The name of an existing s3 bucket. + in: path + required: true + schema: + type: string + - name: filename + description: The name under which to store the random file. + in: path + required: true + schema: + type: string + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PutObjectResult' + 500: + description: Internal Server Error + /api/v2/multitest-file/{bucket}/{filename}: + post: + tags: + - multiparttest-controller + operationId: postFile + summary: Uploads file. + parameters: + - name: bucket + description: The name of an existing s3 bucket. + in: path + required: true + schema: + type: string + - name: filename + description: The name of the file which should be uploaded. It may override any existing file with the same name. + in: path + required: true + schema: + type: string + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + multipartFile: + type: string + format: binary + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PutObjectResult' + 500: + description: Internal Server Error + delete: + tags: + - multiparttest-controller + operationId: deleteObject + summary: Delete file. + parameters: + - name: bucket + in: path + required: true + schema: + type: string + description: The name of an existing s3 bucket. + - name: filename + in: path + required: true + schema: + type: string + description: The name of the file which should be deleted. + responses: + 200: + description: OK + content: + application/json: + schema: + type: boolean + 500: + description: Internal Server Error + /api/v2/multitest-reportgeneration: + post: + tags: + - multiparttest-controller + operationId: generateReport + summary: summary + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + required: ['template'] + properties: + template: + description: | + Content of the template. + type: string + additionalData: + $ref: '#/components/schemas/AdditionalData' + schema: + description: | + An optional JSON schema to validate the created report against. + type: string + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PutObjectResult' + 500: + description: Internal Server Error + /api/v2/multitest-multipledatatypes: + post: + tags: + - multiparttest-controller + operationId: multipleDatatypes + summary: summary + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + stringData: + type: string + booleanData: + type: boolean + integerData: + type: integer + + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PutObjectResult' + 500: + description: Internal Server Error + /api/v2/multitest-file/{bucket}/{filename}/exists: + get: + tags: + - multiparttest-controller + operationId: fileExists + summary: Checks if file exist. + parameters: + - name: bucket + description: The name of an existing s3 bucket. + in: path + required: true + schema: + type: string + - name: filename + description: The name of the file on which the status should be checked. + in: path + required: true + schema: + type: string + responses: + 200: + description: OK + content: + application/json: + schema: + type: boolean + 500: + description: Internal Server Error +components: + schemas: + Metadata: + type: object + properties: + userMetadata: + type: object + additionalProperties: + type: string + rawMetadata: + type: object + additionalProperties: + type: string + httpExpiresDate: + type: string + format: date-time + expirationTime: + type: string + format: date-time + expirationTimeRuleId: + type: string + ongoingRestore: + type: boolean + restoreExpirationTime: + type: string + format: date-time + bucketKeyEnabled: + type: boolean + PutObjectResult: + type: object + properties: + versionId: + type: string + eTag: + type: string + expirationTime: + type: string + format: date-time + expirationTimeRuleId: + type: string + contentMd5: + type: string + metadata: + $ref: '#/components/schemas/Metadata' + isRequesterCharged: + type: boolean + AdditionalData: + description: | + Additional data provided to the report. For each dataset requested, provide a json + object with the name of the dataset. + type: string diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/apis/petstore.yaml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/apis/petstore.yaml new file mode 100644 index 0000000000..79249f26ed --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/apis/petstore.yaml @@ -0,0 +1,700 @@ +swagger: '2.0' +info: + description: 'This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.' + version: 1.0.0 + x-citrus-app: PETS + x-citrus-api-name: petstore + title: OpenAPI Petstore + license: + name: Apache-2.0 + url: 'https://www.apache.org/licenses/LICENSE-2.0.html' +host: petstore.swagger.io +basePath: /v2 +tags: + - name: pet + description: Everything about your Pets + - name: store + description: Access to Petstore orders + - name: user + description: Operations about user +schemes: + - http +paths: + /pet: + post: + tags: + - pet + summary: Add a new pet to the store + description: '' + operationId: addPet + consumes: + - application/json + - application/xml + produces: + - application/xml + - application/json + parameters: + - in: body + name: body + description: Pet object that needs to be added to the store + required: true + schema: + $ref: '#/definitions/Pet' + responses: + '405': + description: Invalid input + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + put: + tags: + - pet + summary: Update an existing pet + description: '' + operationId: updatePet + consumes: + - application/json + - application/xml + produces: + - application/xml + - application/json + parameters: + - in: body + name: body + description: Pet object that needs to be added to the store + required: true + schema: + $ref: '#/definitions/Pet' + responses: + '400': + description: Invalid ID supplied + '404': + description: Pet not found + '405': + description: Validation exception + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + produces: + - application/xml + - application/json + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: true + type: array + items: + type: string + enum: + - available + - pending + - sold + default: available + collectionFormat: csv + responses: + '200': + description: successful operation + schema: + type: array + items: + $ref: '#/definitions/Pet' + '400': + description: Invalid status value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags + description: 'Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.' + operationId: findPetsByTags + produces: + - application/xml + - application/json + parameters: + - name: tags + in: query + description: Tags to filter by + required: true + type: array + items: + type: string + collectionFormat: csv + responses: + '200': + description: successful operation + schema: + type: array + items: + $ref: '#/definitions/Pet' + '400': + description: Invalid tag value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + deprecated: true + '/pet/{petId}': + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + produces: + - application/xml + - application/json + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + type: integer + format: int64 + responses: + '200': + description: successful operation + schema: + $ref: '#/definitions/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + security: + - api_key: [] + - basicAuth: [] + post: + tags: + - pet + summary: Updates a pet in the store with form data + description: '' + operationId: updatePetWithForm + consumes: + - application/x-www-form-urlencoded + produces: + - application/xml + - application/json + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + type: integer + format: int64 + - name: name + in: formData + description: Updated name of the pet + required: false + type: string + - name: status + in: formData + description: Updated status of the pet + required: false + type: string + responses: + '405': + description: Invalid input + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + delete: + tags: + - pet + summary: Deletes a pet + description: '' + operationId: deletePet + produces: + - application/xml + - application/json + parameters: + - name: api_key + in: header + required: false + type: string + - name: petId + in: path + description: Pet id to delete + required: true + type: integer + format: int64 + responses: + '400': + description: Invalid pet value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + '/pet/{petId}/uploadImage': + post: + tags: + - pet + summary: uploads an image + description: '' + operationId: uploadFile + consumes: + - multipart/form-data + produces: + - application/json + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + type: integer + format: int64 + - name: additionalMetadata + in: formData + description: Additional data to pass to server + required: false + type: string + - name: file + in: formData + description: file to upload + required: false + type: file + responses: + '200': + description: successful operation + schema: + $ref: '#/definitions/ApiResponse' + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + /store/inventory: + get: + tags: + - store + summary: Returns pet inventories by status + description: Returns a map of status codes to quantities + operationId: getInventory + produces: + - application/json + parameters: [] + responses: + '200': + description: successful operation + schema: + type: object + additionalProperties: + type: integer + format: int32 + security: + - api_key: [] + /store/order: + post: + tags: + - store + summary: Place an order for a pet + description: '' + operationId: placeOrder + produces: + - application/xml + - application/json + parameters: + - in: body + name: body + description: order placed for purchasing the pet + required: true + schema: + $ref: '#/definitions/Order' + responses: + '200': + description: successful operation + schema: + $ref: '#/definitions/Order' + '400': + description: Invalid Order + '/store/order/{order_id}': + get: + tags: + - store + summary: Find purchase order by ID + description: 'For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions' + operationId: getOrderById + produces: + - application/xml + - application/json + parameters: + - name: order_id + in: path + description: ID of pet that needs to be fetched + required: true + type: integer + maximum: 5 + minimum: 1 + format: int64 + responses: + '200': + description: successful operation + schema: + $ref: '#/definitions/Order' + '400': + description: Invalid ID supplied + '404': + description: Order not found + delete: + tags: + - store + summary: Delete purchase order by ID + description: For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors + operationId: deleteOrder + produces: + - application/xml + - application/json + parameters: + - name: order_id + in: path + description: ID of the order that needs to be deleted + required: true + type: string + responses: + '400': + description: Invalid ID supplied + '404': + description: Order not found + /user: + post: + tags: + - user + summary: Create user + description: This can only be done by the logged in user. + operationId: createUser + produces: + - application/xml + - application/json + parameters: + - in: body + name: body + description: Created user object + required: true + schema: + $ref: '#/definitions/User' + responses: + default: + description: successful operation + /user/createWithArray: + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithArrayInput + produces: + - application/xml + - application/json + parameters: + - in: body + name: body + description: List of user object + required: true + schema: + type: array + items: + $ref: '#/definitions/User' + responses: + default: + description: successful operation + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithListInput + produces: + - application/xml + - application/json + parameters: + - in: body + name: body + description: List of user object + required: true + schema: + type: array + items: + $ref: '#/definitions/User' + responses: + default: + description: successful operation + /user/login: + get: + tags: + - user + summary: Logs user into the system + description: '' + operationId: loginUser + produces: + - application/xml + - application/json + parameters: + - name: username + in: query + description: The user name for login + required: true + type: string + - name: password + in: query + description: The password for login in clear text + required: true + type: string + responses: + '200': + description: successful operation + schema: + type: string + headers: + X-Rate-Limit: + type: integer + format: int32 + description: calls per hour allowed by the user + X-Expires-After: + type: string + format: date-time + description: date in UTC when toekn expires + '400': + description: Invalid username/password supplied + /user/logout: + get: + tags: + - user + summary: Logs out current logged in user session + description: '' + operationId: logoutUser + produces: + - application/xml + - application/json + parameters: [] + responses: + default: + description: successful operation + '/user/{username}': + get: + tags: + - user + summary: Get user by user name + description: '' + operationId: getUserByName + produces: + - application/xml + - application/json + parameters: + - name: username + in: path + description: 'The name that needs to be fetched. Use user1 for testing.' + required: true + type: string + responses: + '200': + description: successful operation + schema: + $ref: '#/definitions/User' + '400': + description: Invalid username supplied + '404': + description: User not found + put: + tags: + - user + summary: Updated user + description: This can only be done by the logged in user. + operationId: updateUser + produces: + - application/xml + - application/json + parameters: + - name: username + in: path + description: name that need to be deleted + required: true + type: string + - in: body + name: body + description: Updated user object + required: true + schema: + $ref: '#/definitions/User' + responses: + '400': + description: Invalid user supplied + '404': + description: User not found + delete: + tags: + - user + summary: Delete user + description: This can only be done by the logged in user. + operationId: deleteUser + produces: + - application/xml + - application/json + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + type: string + responses: + '400': + description: Invalid username supplied + '404': + description: User not found +securityDefinitions: + petstore_auth: + type: oauth2 + authorizationUrl: 'http://petstore.swagger.io/api/oauth/dialog' + flow: implicit + scopes: + 'write:pets': modify pets in your account + 'read:pets': read your pets + api_key: + type: apiKey + name: api_key + in: header + basicAuth: + type: basic +definitions: + Order: + title: Pet Order + description: An order for a pets from the pet store + type: object + properties: + id: + type: integer + format: int64 + petId: + type: integer + format: int64 + quantity: + type: integer + format: int32 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + enum: + - placed + - approved + - delivered + complete: + type: boolean + default: false + xml: + name: Order + Category: + title: Pet category + description: A category for a pet + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Category + User: + title: a User + description: A User who is purchasing from the pet store + type: object + properties: + id: + type: integer + format: int64 + username: + type: string + firstName: + type: string + lastName: + type: string + email: + type: string + password: + type: string + phone: + type: string + userStatus: + type: integer + format: int32 + description: User Status + xml: + name: User + Tag: + title: Pet Tag + description: A tag for a pet + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Tag + Pet: + title: a Pet + description: A pet for sale in the pet store + type: object + required: + - name + - photoUrls + properties: + id: + type: integer + format: int64 + category: + $ref: '#/definitions/Category' + name: + type: string + example: doggie + photoUrls: + type: array + xml: + name: photoUrl + wrapped: true + items: + type: string + tags: + type: array + xml: + name: tag + wrapped: true + items: + $ref: '#/definitions/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: Pet + ApiResponse: + title: An uploaded response + description: Describes the result of uploading an image resource + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/apis/petstore_reservedWords.yaml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/apis/petstore_reservedWords.yaml new file mode 100644 index 0000000000..7175b75f0e --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/apis/petstore_reservedWords.yaml @@ -0,0 +1,120 @@ +swagger: '2.0' +info: + description: 'This is a modified Petstore server, that uses the reserved word "name" as parameter name. This should be renamed to "_name" in the generated code.' + version: 1.0.0 + title: OpenAPI Petstore + license: + name: Apache-2.0 + url: 'https://www.apache.org/licenses/LICENSE-2.0.html' +host: petstore.swagger.io +basePath: /v2 +tags: + - name: pet + description: Everything about your Pets +schemes: + - http +paths: + /pet/findByName: + get: + tags: + - pet + summary: Finds Pet by name + description: Name can be any text + operationId: findPetByName + produces: + - application/xml + - application/json + parameters: + # name is a reserved word and should be masked with an '_' in the generated api + - name: name + in: query + description: Name of the pet + required: true + type: string + responses: + '200': + description: successful operation + schema: + $ref: '#/definitions/Pet' + '400': + description: Invalid name value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' +securityDefinitions: + petstore_auth: + type: oauth2 + authorizationUrl: 'http://petstore.swagger.io/api/oauth/dialog' + flow: implicit + scopes: + 'write:pets': modify pets in your account + 'read:pets': read your pets + api_key: + type: apiKey + name: api_key + in: header +definitions: + Category: + title: Pet category + description: A category for a pet + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Category + Tag: + title: Pet Tag + description: A tag for a pet + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Tag + Pet: + title: a Pet + description: A pet for sale in the pet store + type: object + required: + - name + - photoUrls + properties: + id: + type: integer + format: int64 + category: + $ref: '#/definitions/Category' + name: + type: string + example: doggie + photoUrls: + type: array + xml: + name: photoUrl + wrapped: true + items: + type: string + tags: + type: array + xml: + name: tag + wrapped: true + items: + $ref: '#/definitions/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: Pet diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/citrus-context.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/citrus-context.xml new file mode 100644 index 0000000000..3f2a783fca --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/citrus-context.xml @@ -0,0 +1,7 @@ + diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/defaultOas3SchemaValidationTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/defaultOas3SchemaValidationTest.xml new file mode 100644 index 0000000000..1784d89782 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/defaultOas3SchemaValidationTest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/failOnReasonPhraseTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/failOnReasonPhraseTest.xml new file mode 100644 index 0000000000..60f8dc25d0 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/failOnReasonPhraseTest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/failOnStatusTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/failOnStatusTest.xml new file mode 100644 index 0000000000..ca47b78103 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/failOnStatusTest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/failOnVersionTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/failOnVersionTest.xml new file mode 100644 index 0000000000..5047fc38a0 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/failOnVersionTest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + 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 new file mode 100644 index 0000000000..c6f1afc8b5 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/getPetByIdRequestTest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonDeactivatedSchemaValidationTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonDeactivatedSchemaValidationTest.xml new file mode 100644 index 0000000000..7f4b46ed43 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonDeactivatedSchemaValidationTest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonPathExtractTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonPathExtractTest.xml new file mode 100644 index 0000000000..efb6b3e5a8 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonPathExtractTest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonPathExtractionTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonPathExtractionTest.xml new file mode 100644 index 0000000000..2ead5c459b --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonPathExtractionTest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonPathValidationFailureTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonPathValidationFailureTest.xml new file mode 100644 index 0000000000..4d4bff102f --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonPathValidationFailureTest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonPathValidationTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonPathValidationTest.xml new file mode 100644 index 0000000000..320e32fcd9 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonPathValidationTest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonSchemaValidationFailureTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonSchemaValidationFailureTest.xml new file mode 100644 index 0000000000..3aeb456f25 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonSchemaValidationFailureTest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonSchemaValidationTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonSchemaValidationTest.xml new file mode 100644 index 0000000000..50f146bc18 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/jsonSchemaValidationTest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/multipartWithFileAttributesTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/multipartWithFileAttributesTest.xml new file mode 100644 index 0000000000..4e24ad3e06 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/multipartWithFileAttributesTest.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/multipartWithMultipleDatatypesTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/multipartWithMultipleDatatypesTest.xml new file mode 100644 index 0000000000..a727b3bd06 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/multipartWithMultipleDatatypesTest.xml @@ -0,0 +1,24 @@ + + + + + + Test + true + 1 + + + + + diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/multipartWithPlainTextTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/multipartWithPlainTextTest.xml new file mode 100644 index 0000000000..a3082ee857 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/multipartWithPlainTextTest.xml @@ -0,0 +1,32 @@ + + + + + + + + ]]> + + + {"data1":"value1"} + + + {"schema":"mySchema"} + + + + + + diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/AdditionalData.json b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/AdditionalData.json new file mode 100644 index 0000000000..a921a4b0c2 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/AdditionalData.json @@ -0,0 +1,6 @@ +{ + "Konto": { + "iban": "DE43100500000920018963", + "amount": 1234 + } +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/MultipartTemplate.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/MultipartTemplate.xml new file mode 100644 index 0000000000..a3dd52a043 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/MultipartTemplate.xml @@ -0,0 +1,4 @@ + + diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/Schema.json b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/Schema.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/Schema.json @@ -0,0 +1 @@ +{} diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/addPetMessage.json b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/addPetMessage.json new file mode 100644 index 0000000000..b68ed32a5e --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/addPetMessage.json @@ -0,0 +1,6 @@ +{ + "id": 12, + "name": "Snoopy", + "tags": ["comic dog"], + "photoUrls": ["url1", "url2"] +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage1.json b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage1.json new file mode 100644 index 0000000000..b68ed32a5e --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/payloads/getPetByIdControlMessage1.json @@ -0,0 +1,6 @@ +{ + "id": 12, + "name": "Snoopy", + "tags": ["comic dog"], + "photoUrls": ["url1", "url2"] +} 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 new file mode 100644 index 0000000000..a04b808966 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/postFileTest.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/scriptValidationTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/scriptValidationTest.xml new file mode 100644 index 0000000000..39efdef351 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/scriptValidationTest.xml @@ -0,0 +1,25 @@ + + + + + + + assert json.id == 12 + assert json.name == 'Snoopy' + + + + + + diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/sendWithBodyLiteralTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/sendWithBodyLiteralTest.xml new file mode 100644 index 0000000000..3daad673c3 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/sendWithBodyLiteralTest.xml @@ -0,0 +1,20 @@ + + + + + {"id": 13} + + + + diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/sendWithBodyLiteralWithVariableTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/sendWithBodyLiteralWithVariableTest.xml new file mode 100644 index 0000000000..11aacdf5a0 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/sendWithBodyLiteralWithVariableTest.xml @@ -0,0 +1,23 @@ + + + + + + + + {"id": ${id}} + + + + diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/sendWithBodyTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/sendWithBodyTest.xml new file mode 100644 index 0000000000..8cb689bf3f --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/sendWithBodyTest.xml @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/sendWithExtraHeaderTest.xml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/sendWithExtraHeaderTest.xml new file mode 100644 index 0000000000..b2b002aa24 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/GeneratedApiTest/sendWithExtraHeaderTest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/citrus/MultipartTestAbstractTestRequest.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/citrus/MultipartTestAbstractTestRequest.java new file mode 100644 index 0000000000..d35d8934bf --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/citrus/MultipartTestAbstractTestRequest.java @@ -0,0 +1,245 @@ +/** + * ================================================== + * GENERATED CLASS, ALL CHANGES WILL BE LOST + * ================================================== + */ + +package org.citrusframework.openapi.generator.rest.multiparttest.citrus; + + +import static org.springframework.util.CollectionUtils.isEmpty; + +import jakarta.annotation.Generated; +import jakarta.annotation.Nullable; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import javax.sql.DataSource; +import org.citrusframework.actions.AbstractTestAction; +import org.citrusframework.actions.ReceiveMessageAction; +import org.citrusframework.context.TestContext; +import org.citrusframework.http.actions.HttpActionBuilder; +import org.citrusframework.http.actions.HttpClientRequestActionBuilder; +import org.citrusframework.http.actions.HttpClientResponseActionBuilder; +import org.citrusframework.http.actions.HttpClientResponseActionBuilder.HttpMessageBuilderSupport; +import org.citrusframework.http.client.HttpClient; +import org.citrusframework.message.Message; +import org.citrusframework.testapi.ApiActionBuilderCustomizerService; +import org.citrusframework.testapi.GeneratedApi; +import org.citrusframework.spi.Resources; +import org.citrusframework.validation.DelegatingPayloadVariableExtractor; +import org.citrusframework.validation.PathExpressionValidationContext; +import org.citrusframework.validation.json.JsonMessageValidationContext; +import org.citrusframework.validation.script.ScriptValidationContext; +import org.slf4j.Marker; +import org.slf4j.MarkerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen") +public abstract class MultipartTestAbstractTestRequest extends AbstractTestAction { + + protected final Marker coverageMarker = MarkerFactory.getMarker("MULTIPARTTEST-API-COVERAGE"); + + @Autowired + @Qualifier("multipartTestEndpoint") + protected HttpClient httpClient; + + @Autowired(required = false) + protected DataSource dataSource; + + @Autowired(required = false) + private List actionBuilderCustomizerServices; + + // attributes of differentNodes + protected boolean schemaValidation; + protected String schema; + protected String bodyContentType; + protected String bodyLiteralContentType; + protected String bodyFile; + protected String bodyLiteral; + protected String responseAcceptType = "*/*"; + protected String responseType = "json"; + protected int responseStatus = 200; + protected String responseReasonPhrase = "OK"; + protected String responseVersion = "HTTP/1.1"; + + // children of response element + protected String resource; + protected Map responseVariable; // Contains the 'JSON-PATH' as key and the 'VARIABLE NAME' as value + protected Map responseValue; // Contains the 'JSON-PATH' as key and the 'VALUE TO BE VALIDATED' as value + protected Map cookies; + protected Map headers; + protected String script; + protected String type; // default script type is groovy - supported types see com.consol.citrus.script.ScriptTypes + + @Override + public void doExecute(TestContext context) { + sendRequest(context); + recieveResponse(context); + } + + /** + * This method receives the HTTP-Response. + * + * @deprecated use {@link MultipartTestAbstractTestRequest#receiveResponse(TestContext)} instead. + */ + public ReceiveMessageAction recieveResponse(TestContext context) { + + HttpClientResponseActionBuilder httpClientResponseActionBuilder = new HttpActionBuilder().client(httpClient).receive().response(); + HttpMessageBuilderSupport messageBuilderSupport = httpClientResponseActionBuilder.getMessageBuilderSupport(); + + messageBuilderSupport + .statusCode(responseStatus) + .reasonPhrase(responseReasonPhrase) + .version(responseVersion) + .validate(new JsonMessageValidationContext.Builder().schemaValidation(schemaValidation).schema(schema)); + + if (resource != null) { + messageBuilderSupport.body(Resources.create(resource)); + } + + if (!isEmpty(responseVariable)) { + DelegatingPayloadVariableExtractor.Builder extractorBuilder = new DelegatingPayloadVariableExtractor.Builder(); + responseVariable.forEach(extractorBuilder::expression); + messageBuilderSupport.extract(extractorBuilder); + } + + if (!isEmpty(responseValue)) { + PathExpressionValidationContext.Builder validationContextBuilder = new PathExpressionValidationContext.Builder(); + responseValue.forEach(validationContextBuilder::expression); + messageBuilderSupport.validate(validationContextBuilder); + } + + if (script != null) { + ScriptValidationContext.Builder scriptValidationContextBuilder = new ScriptValidationContext.Builder(); + if (type != null) { + scriptValidationContextBuilder.scriptType(type); + } + scriptValidationContextBuilder.script(script); + messageBuilderSupport.validate(scriptValidationContextBuilder); + } + + messageBuilderSupport.type(responseType); + httpClientResponseActionBuilder.withReferenceResolver(context.getReferenceResolver()); + var responseAction = httpClientResponseActionBuilder.build(); + + responseAction.execute(context); + + return responseAction; + } + + public @Nullable Message receiveResponse(TestContext context) { + var responseAction = recieveResponse(context); + + var messageStore = context.getMessageStore(); + return messageStore.getMessage(messageStore.constructMessageName(responseAction, httpClient)); + } + + public abstract void sendRequest(TestContext context); + + public void setSchemaValidation(boolean schemaValidation) { + this.schemaValidation = schemaValidation; + } + + public void setSchema(String schema) { + this.schema = schema; + } + + public void setBodyLiteral(String bodyLiteral) { + this.bodyLiteral = bodyLiteral; + } + + public void setBodyContentType(String bodyContentType) { + this.bodyContentType = bodyContentType; + } + + public void setBodyLiteralContentType(String bodyLiteralContentType) { + this.bodyLiteralContentType = bodyLiteralContentType; + } + + public void setResponseAcceptType(String responseAcceptType) { + this.responseAcceptType = responseAcceptType; + } + + public void setCookie(Map cookies) { + this.cookies = cookies; + } + + public void setHeader(Map headers) { + this.headers = headers; + } + + public void setBodyFile(String bodyFile) { + this.bodyFile = bodyFile; + } + + public void setResponseType(String responseType) { + this.responseType = responseType; + } + + public void setResponseStatus(int responseStatus) { + this.responseStatus = responseStatus; + } + + public void setResponseReasonPhrase(String responseReasonPhrase) { + this.responseReasonPhrase = responseReasonPhrase; + } + + public void setResponseVersion(String responseVersion) { + this.responseVersion = responseVersion; + } + + public void setResource(String resource) { + this.resource = resource; + } + + public void setResponseVariable(Map responseVariable) { + this.responseVariable = responseVariable; + } + + public void setResponseValue(Map responseValue) { + this.responseValue = responseValue; + } + + public void setScript(String script) { + this.script = script; + } + + public void setType(String type) { + this.type = type; + } + + protected HttpClientRequestActionBuilder customizeBuilder(GeneratedApi generatedApi, + TestContext context, HttpClientRequestActionBuilder httpClientRequestActionBuilder) { + + httpClientRequestActionBuilder = customizeByBeans(generatedApi, context, + httpClientRequestActionBuilder); + + httpClientRequestActionBuilder = customizeBySpi(generatedApi, context, httpClientRequestActionBuilder); + + return httpClientRequestActionBuilder; + } + + private HttpClientRequestActionBuilder customizeBySpi(GeneratedApi generatedApi, TestContext context, + HttpClientRequestActionBuilder httpClientRequestActionBuilder) { + ServiceLoader serviceLoader = ServiceLoader.load( + ApiActionBuilderCustomizerService.class, ApiActionBuilderCustomizerService.class.getClassLoader()); + for (ApiActionBuilderCustomizerService service :serviceLoader) { + httpClientRequestActionBuilder = service.build(generatedApi, this, context, httpClientRequestActionBuilder); + } + return httpClientRequestActionBuilder; + } + + private HttpClientRequestActionBuilder customizeByBeans( + GeneratedApi generatedApi, TestContext context, + HttpClientRequestActionBuilder httpClientRequestActionBuilder) { + if (actionBuilderCustomizerServices != null) { + for (ApiActionBuilderCustomizerService apiActionBuilderCustomizer : actionBuilderCustomizerServices) { + httpClientRequestActionBuilder = apiActionBuilderCustomizer.build(generatedApi, this, + context, httpClientRequestActionBuilder); + } + } + return httpClientRequestActionBuilder; + } +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/citrus/MultipartTestBeanDefinitionParser.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/citrus/MultipartTestBeanDefinitionParser.java new file mode 100644 index 0000000000..d9730d0d42 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/citrus/MultipartTestBeanDefinitionParser.java @@ -0,0 +1,215 @@ +/** + * ================================================== + * GENERATED CLASS, ALL CHANGES WILL BE LOST + * ================================================== + */ + +package org.citrusframework.openapi.generator.rest.multiparttest.citrus; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.annotation.processing.Generated; + +import org.citrusframework.exceptions.CitrusRuntimeException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.core.Conventions; +import org.springframework.util.Assert; +import org.apache.commons.lang3.StringUtils; +import org.springframework.util.xml.DomUtils; +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + + +@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen") +public class MultipartTestBeanDefinitionParser implements BeanDefinitionParser { + + private static final String COOKIE = "cookie"; + private static final String HEADER = "header"; + private static final String SOAP_HEADER = "soapHeader"; + private static final String MIME_HEADER = "mimeHeader"; + private static final String NAME = "name"; + private static final String REQUEST_BODY = "body"; + private static final String REQUEST_BODY_LITERAL = "bodyLiteral"; + private static final String MULTIPART_BODY = "multipartBody"; + private static final String RESPONSE = "response"; + private static final String RESPONSE_JSONPATH = "json-path"; + private static final String RESPONSE_XPATH = "xpath"; + private static final String EXPRESSION = "expression"; + private static final String VALUE = "value"; + private static final String RESPONSE_RESOURCE = "resource"; + private static final String FILE = "file"; + private static final String RESPONSE_VARIABLE = "responseVariable"; + private static final String RESPONSE_VALUE = "responseValue"; + private static final String SCRIPT = "script"; + private static final String TYPE = "type"; + private static final String SQL = "sql"; + private static final String COLUMN = "column"; + private static final String VARIABLE = "variable"; + // new + private static final String SCHEMA = "schema"; + // new + private static final String SCHEMA_VALIDATION = "schemaValidation"; + + private final Class beanClass; + + public MultipartTestBeanDefinitionParser(Class beanClass) { + this.beanClass = beanClass; + } + + public BeanDefinition parse(Element element) { + return parse(element, null); + } + + /** + * Note: The {@link MultipartTestBeanDefinitionParser#parse(Element element)} allows access direct + * access without the {@link org.springframework.beans.factory.xml.ParserContext} for convenience. + */ + @Override + public BeanDefinition parse(Element element, ParserContext parserContext) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(beanClass); + retrieveRootNodeAttributes(element, builder); + retrieveOptionalNodeAttributes(element, REQUEST_BODY, builder); + retrieveTextContentAndNodeAttributes(element, REQUEST_BODY_LITERAL, builder); + retrieveOptionalNodeAttributes(element, RESPONSE, builder); + retrieveParamNodeData(element, builder, COOKIE); + retrieveParamNodeData(element, builder, HEADER); + retrieveParamNodeData(element, builder, SOAP_HEADER); + retrieveParamNodeData(element, builder, MIME_HEADER); + retrieveOptionalNodeAttributes(element, SCHEMA, builder); + retrieveOptionalNodeAttributes(element, SCHEMA_VALIDATION, builder); + retrieveOptionalMultipartElements(element, builder); + retrieveResponseNodeData(element, builder); + builder.addPropertyValue("name", element.getTagName()); + return builder.getBeanDefinition(); + } + + private void retrieveOptionalMultipartElements(Element element, BeanDefinitionBuilder builder) { + var multipartBodyElement = DomUtils.getChildElementByTagName(element, MULTIPART_BODY); + if (multipartBodyElement != null) { + var multipartBodyChildElements = DomUtils.getChildElements(multipartBodyElement); + for(int i = 0; i < multipartBodyChildElements.size(); i++){ + var multipartBodyChildElement = multipartBodyChildElements.get(i); + String propertyName = Conventions.attributeNameToPropertyName(multipartBodyChildElement.getLocalName()); + builder.addPropertyValue(propertyName, multipartBodyChildElement.getTextContent()); + } + } + } + + private void retrieveRootNodeAttributes(Element element, BeanDefinitionBuilder builder) { + NamedNodeMap attributes = element.getAttributes(); + for (int x = 0; x < attributes.getLength(); x++) { + Attr attribute = (Attr) attributes.item(x); + String propertyName = Conventions.attributeNameToPropertyName(attribute.getLocalName()); + Assert.state(StringUtils.isNotBlank(propertyName), "Illegal property name returned, it must not be null or empty."); + builder.addPropertyValue(propertyName, attribute.getValue()); + } + } + + private void retrieveOptionalNodeAttributes(Element element, String elementName, BeanDefinitionBuilder builder) { + if (!DomUtils.getChildElementsByTagName(element, elementName).isEmpty()) { + Element el = DomUtils.getChildElementsByTagName(element, elementName).get(0); + NamedNodeMap attributes = el.getAttributes(); + for (int x = 0; x < attributes.getLength(); x++) { + Attr attribute = (Attr) attributes.item(x); + String propertyName = Conventions.attributeNameToPropertyName(attribute.getLocalName()); + Assert.state(StringUtils.isNotBlank(propertyName), "Illegal property name returned, it must not be null or empty."); + String variableName = el.getLocalName() + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1); + builder.addPropertyValue(variableName, attribute.getValue()); + } + } + } + + private void retrieveTextContentAndNodeAttributes(Element element, String elementName, BeanDefinitionBuilder builder) { + if (!DomUtils.getChildElementsByTagName(element, elementName).isEmpty()) { + Element el1 = DomUtils.getChildElementsByTagName(element, elementName).get(0); + NamedNodeMap attributes = el1.getAttributes(); + for (int x = 0; x < attributes.getLength(); x++) { + Attr attribute = (Attr) attributes.item(x); + String propertyName1 = Conventions.attributeNameToPropertyName(attribute.getLocalName()); + Assert.state(StringUtils.isNotBlank(propertyName1), "Illegal property name returned, it must not be null or empty."); + String variableName = el1.getLocalName() + propertyName1.substring(0, 1).toUpperCase() + propertyName1.substring(1); + builder.addPropertyValue(variableName, attribute.getValue()); + } + Element el = DomUtils.getChildElementsByTagName(element, elementName).get(0); + builder.addPropertyValue(elementName, el.getTextContent()); + } + } + + private void retrieveParamNodeData(Element element, BeanDefinitionBuilder builder, String paramType) { + if (!DomUtils.getChildElementsByTagName(element, paramType).isEmpty()) { + Map params = new HashMap<>(); + List elements = DomUtils.getChildElementsByTagName(element, paramType); + elements.forEach(e -> { + String name = e.getAttribute(NAME); + String value = e.getAttribute(VALUE); + + Assert.state(StringUtils.isNotBlank(name), "Illegal attribute value returned. The 'name' attribute must not be null or empty."); + Assert.state(StringUtils.isNotBlank(value), "Illegal attribute value returned. The 'value' attribute must not be null or empty."); + + params.put(name, value); + }); + builder.addPropertyValue(paramType, params); + } + } + + private void retrieveResponseNodeData(Element element, BeanDefinitionBuilder builder) { + + if (!DomUtils.getChildElementsByTagName(element, RESPONSE).isEmpty()) { + Element response = DomUtils.getChildElementsByTagName(element, RESPONSE).get(0); + List elements = DomUtils.getChildElements(response); + + Map responseVariable = new HashMap<>(); + Map responseValue = new HashMap<>(); + + for (int i = 0; i < elements.size(); i++) { + Element e = elements.get(i); + + if (e.getTagName().contains(RESPONSE_JSONPATH) || e.getTagName().contains(RESPONSE_XPATH)) { + String expression = e.getAttribute(EXPRESSION); + String value = e.getAttribute(VALUE); + + Assert.state(StringUtils.isNotBlank(expression), "Illegal attribute value returned. The 'expression' attribute must not be null or empty."); + Assert.state(StringUtils.isNotBlank(value), "Illegal attribute value returned. The 'value' attribute must not be null or empty."); + + // variable to save @variable('ebid')@ else value to validate + if (value.matches("\\@variable\\('.*'\\)\\@")) { + Matcher match = Pattern.compile("\\'(.*?)\\'").matcher(value); + if (match.find()) { + responseVariable.put(expression, value.substring(match.start() + 1, match.end() - 1)); + } + } else { + responseValue.put(expression, value); + } + } else if (e.getTagName().contains(SCRIPT)) { + String script = e.getTextContent(); + Assert.state(StringUtils.isNotBlank(script), "Illegal attribute value returned. The 'script' attribute must not be null or empty."); + builder.addPropertyValue(SCRIPT, script); + + if (!e.getAttribute(TYPE).isEmpty()) { + String type = e.getAttribute(TYPE); + Assert.state(StringUtils.isNotBlank(type), "Illegal attribute value returned. The 'type' attribute must not be null or empty."); + builder.addPropertyValue(TYPE, type); + } + } else if (e.getTagName().contains(RESPONSE_RESOURCE)) { + String filePath = e.getAttribute(FILE); + Assert.state(StringUtils.isNotBlank(filePath), "Illegal attribute value returned. The 'file' attribute must not be null or empty."); + builder.addPropertyValue(RESPONSE_RESOURCE, filePath); + } + + } + + builder.addPropertyValue(RESPONSE_VARIABLE, responseVariable); + builder.addPropertyValue(RESPONSE_VALUE, responseValue); + } + } +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/citrus/extension/MultipartTestNamespaceHandler.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/citrus/extension/MultipartTestNamespaceHandler.java new file mode 100644 index 0000000000..0d81b2d521 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/citrus/extension/MultipartTestNamespaceHandler.java @@ -0,0 +1,29 @@ +/** + * ================================================== + * GENERATED CLASS, ALL CHANGES WILL BE LOST + * ================================================== + */ + +package org.citrusframework.openapi.generator.rest.multiparttest.citrus.extension; + +import org.citrusframework.openapi.generator.rest.multiparttest.request.MultiparttestControllerApi; +import org.citrusframework.openapi.generator.rest.multiparttest.citrus.MultipartTestBeanDefinitionParser; + +import javax.annotation.processing.Generated; + +import org.springframework.beans.factory.xml.NamespaceHandlerSupport; + + +@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen") +public class MultipartTestNamespaceHandler extends NamespaceHandlerSupport { + + @Override + public void init() { + registerBeanDefinitionParser("deleteObjectRequest", new MultipartTestBeanDefinitionParser(MultiparttestControllerApi.DeleteObjectRequest.class)); + registerBeanDefinitionParser("fileExistsRequest", new MultipartTestBeanDefinitionParser(MultiparttestControllerApi.FileExistsRequest.class)); + registerBeanDefinitionParser("generateReportRequest", new MultipartTestBeanDefinitionParser(MultiparttestControllerApi.GenerateReportRequest.class)); + registerBeanDefinitionParser("multipleDatatypesRequest", new MultipartTestBeanDefinitionParser(MultiparttestControllerApi.MultipleDatatypesRequest.class)); + registerBeanDefinitionParser("postFileRequest", new MultipartTestBeanDefinitionParser(MultiparttestControllerApi.PostFileRequest.class)); + registerBeanDefinitionParser("postRandomRequest", new MultipartTestBeanDefinitionParser(MultiparttestControllerApi.PostRandomRequest.class)); + } +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/model/Metadata.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/model/Metadata.java new file mode 100644 index 0000000000..d5341fea2c --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/model/Metadata.java @@ -0,0 +1 @@ +// not in use diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/model/PutObjectResult.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/model/PutObjectResult.java new file mode 100644 index 0000000000..d5341fea2c --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/model/PutObjectResult.java @@ -0,0 +1 @@ +// not in use 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 new file mode 100644 index 0000000000..36d2bca5f4 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/request/MultiparttestControllerApi.java @@ -0,0 +1,750 @@ +/** + * ================================================== + * GENERATED CLASS, ALL CHANGES WILL BE LOST + * ================================================== + */ + +package org.citrusframework.openapi.generator.rest.multiparttest.request; + +import jakarta.annotation.Generated; +import org.citrusframework.testapi.GeneratedApi; +import org.citrusframework.testapi.GeneratedApiRequest; +import jakarta.servlet.http.Cookie; +import org.apache.commons.lang3.StringUtils; +import org.citrusframework.context.TestContext; +import org.citrusframework.exceptions.CitrusRuntimeException; +import org.citrusframework.spi.Resources; +import org.citrusframework.http.actions.HttpActionBuilder; +import org.citrusframework.http.actions.HttpClientRequestActionBuilder; +import org.citrusframework.http.actions.HttpClientRequestActionBuilder.HttpMessageBuilderSupport; +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.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; + + +@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen") +public class MultiparttestControllerApi implements GeneratedApi +{ + + public static final MultiparttestControllerApi INSTANCE = new MultiparttestControllerApi(); + + public String getApiTitle() { + return "multiparttest API"; + } + + public String getApiVersion() { + return "2.0.0"; + } + + public String getApiPrefix() { + return "MultipartTest"; + } + + public Map getApiInfoExtensions() { + Map infoExtensionMap = new HashMap<>(); + infoExtensionMap.put("x-citrus-api-name", "multiparttest-rest-resource"); + infoExtensionMap.put("x-citrus-app", "MPT"); + return infoExtensionMap; + } + + /** deleteObject (DELETE /api/v2/multitest-file/{bucket}/{filename}) + Delete file. + + **/ + public static class DeleteObjectRequest extends MultipartTestAbstractTestRequest implements GeneratedApiRequest { + + private static final String ENDPOINT = "/api/v2/multitest-file/{bucket}/{filename}"; + private final Logger coverageLogger = LoggerFactory.getLogger(DeleteObjectRequest.class); + + private String bucket; + + private String filename; + + + public DeleteObjectRequest() { + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("MultipartTest".toLowerCase() + ":deleteObjectRequestType"); + } + + public String getOperationName() { + return "deleteObject"; + } + + public String getMethod() { + return "DELETE"; + } + + public String getPath() { + return "/api/v2/multitest-file/{bucket}/{filename}"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send() + .delete(replacePathParams(ENDPOINT)); + + HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport(); + messageBuilderSupport.accept(responseAcceptType); + + if (cookies != null) { + cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + } + + if (headers != null) { + headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + headers.forEach(messageBuilderSupport::header); + } + + String bodyLog = ""; + String payload = null; + String payloadType = null; + if (StringUtils.isNotBlank(this.bodyFile)) { + try { + payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset()); + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read payload resource", e); + } + payloadType = this.bodyContentType; + } else if (StringUtils.isNotBlank(this.bodyLiteral)) { + payload = this.bodyLiteral; + payloadType = this.bodyLiteralContentType; + } + String body = ""; + String bodyType = ""; + if(payload != null && payloadType != null) { + messageBuilderSupport.body(payload).contentType(payloadType); + body = context.replaceDynamicContentInString(payload); + bodyType = context.replaceDynamicContentInString(payloadType); + } + + bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\""; + + Map queryParams = new HashMap<>(); + + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver()); + httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder); + + httpClientRequestActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "deleteObject;DELETE;\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + bodyLog); + } + + public void setBucket(String bucket) { + this.bucket = bucket; + } + + public void setFilename(String filename) { + this.filename = filename; + } + + private String replacePathParams(String endpoint) { + endpoint = endpoint.replace("{" + "bucket" + "}", bucket);endpoint = endpoint.replace("{" + "filename" + "}", filename); + return endpoint; + } + } + /** fileExists (GET /api/v2/multitest-file/{bucket}/{filename}/exists) + Checks if file exist. + + **/ + public static class FileExistsRequest extends MultipartTestAbstractTestRequest implements GeneratedApiRequest { + + private static final String ENDPOINT = "/api/v2/multitest-file/{bucket}/{filename}/exists"; + private final Logger coverageLogger = LoggerFactory.getLogger(FileExistsRequest.class); + + private String bucket; + + private String filename; + + + public FileExistsRequest() { + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("MultipartTest".toLowerCase() + ":fileExistsRequestType"); + } + + public String getOperationName() { + return "fileExists"; + } + + public String getMethod() { + return "GET"; + } + + public String getPath() { + return "/api/v2/multitest-file/{bucket}/{filename}/exists"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send() + .get(replacePathParams(ENDPOINT)); + + HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport(); + messageBuilderSupport.accept(responseAcceptType); + + if (cookies != null) { + cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + } + + if (headers != null) { + headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + headers.forEach(messageBuilderSupport::header); + } + + String bodyLog = ""; + String payload = null; + String payloadType = null; + if (StringUtils.isNotBlank(this.bodyFile)) { + try { + payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset()); + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read payload resource", e); + } + payloadType = this.bodyContentType; + } else if (StringUtils.isNotBlank(this.bodyLiteral)) { + payload = this.bodyLiteral; + payloadType = this.bodyLiteralContentType; + } + String body = ""; + String bodyType = ""; + if(payload != null && payloadType != null) { + messageBuilderSupport.body(payload).contentType(payloadType); + body = context.replaceDynamicContentInString(payload); + bodyType = context.replaceDynamicContentInString(payloadType); + } + + bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\""; + + Map queryParams = new HashMap<>(); + + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver()); + httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder); + + httpClientRequestActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "fileExists;GET;\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + bodyLog); + } + + public void setBucket(String bucket) { + this.bucket = bucket; + } + + public void setFilename(String filename) { + this.filename = filename; + } + + private String replacePathParams(String endpoint) { + endpoint = endpoint.replace("{" + "bucket" + "}", bucket);endpoint = endpoint.replace("{" + "filename" + "}", filename); + return endpoint; + } + } + /** generateReport (POST /api/v2/multitest-reportgeneration) + summary + + **/ + public static class GenerateReportRequest extends MultipartTestAbstractTestRequest implements GeneratedApiRequest { + + private static final String ENDPOINT = "/api/v2/multitest-reportgeneration"; + private final Logger coverageLogger = LoggerFactory.getLogger(GenerateReportRequest.class); + + private String template; + + private String additionalData; + + private String _schema; + + + public GenerateReportRequest() { + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("MultipartTest".toLowerCase() + ":generateReportRequestType"); + } + + public String getOperationName() { + return "generateReport"; + } + + public String getMethod() { + return "POST"; + } + + public String getPath() { + return "/api/v2/multitest-reportgeneration"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send() + .post(replacePathParams(ENDPOINT)); + + HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport(); + messageBuilderSupport.accept(responseAcceptType); + + if (cookies != null) { + cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + } + + if (headers != null) { + headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + headers.forEach(messageBuilderSupport::header); + } + + String bodyLog = ""; + MultiValueMap multiValues = new LinkedMultiValueMap<>(); + if(StringUtils.isBlank(template)) { + throw new CitrusRuntimeException(String.format("Required attribute '%s' is not specified", "template")); + } + if (StringUtils.isNotBlank(template)) { + // first try to load from resource + ClassPathResource resource = null; + try { + resource = new ClassPathResource(template); + } + catch(Exception ignore) { + // Use plain text instead of resource + } + + if(resource != null && resource.exists()){ + multiValues.add("template", resource); + } else { + multiValues.add("template", template); + } + bodyLog += template.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") +","; + } + if (StringUtils.isNotBlank(additionalData)) { + // first try to load from resource + ClassPathResource resource = null; + try { + resource = new ClassPathResource(additionalData); + } + catch(Exception ignore) { + // Use plain text instead of resource + } + + if(resource != null && resource.exists()){ + multiValues.add("additionalData", resource); + } else { + multiValues.add("additionalData", additionalData); + } + bodyLog += additionalData.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") +","; + } + if (StringUtils.isNotBlank(_schema)) { + // first try to load from resource + ClassPathResource resource = null; + try { + resource = new ClassPathResource(_schema); + } + catch(Exception ignore) { + // Use plain text instead of resource + } + + if(resource != null && resource.exists()){ + multiValues.add("_schema", resource); + } else { + multiValues.add("_schema", _schema); + } + bodyLog += _schema.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") +","; + } + + bodyLog += "\";\"" + MediaType.MULTIPART_FORM_DATA_VALUE + "\""; + messageBuilderSupport.contentType(MediaType.MULTIPART_FORM_DATA_VALUE) + .body(multiValues); + + + Map queryParams = new HashMap<>(); + + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver()); + httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder); + + httpClientRequestActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "generateReport;POST;\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + bodyLog); + } + + public void setTemplate(String template) { + this.template = template; + } + + public void setAdditionalData(String additionalData) { + this.additionalData = additionalData; + } + + public void set_schema(String _schema) { + this._schema = _schema; + } + + private String replacePathParams(String endpoint) { + + return endpoint; + } + } + /** multipleDatatypes (POST /api/v2/multitest-multipledatatypes) + summary + + **/ + public static class MultipleDatatypesRequest extends MultipartTestAbstractTestRequest implements GeneratedApiRequest { + + private static final String ENDPOINT = "/api/v2/multitest-multipledatatypes"; + private final Logger coverageLogger = LoggerFactory.getLogger(MultipleDatatypesRequest.class); + + private String stringData; + + private String booleanData; + + private String integerData; + + + public MultipleDatatypesRequest() { + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("MultipartTest".toLowerCase() + ":multipleDatatypesRequestType"); + } + + public String getOperationName() { + return "multipleDatatypes"; + } + + public String getMethod() { + return "POST"; + } + + public String getPath() { + return "/api/v2/multitest-multipledatatypes"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send() + .post(replacePathParams(ENDPOINT)); + + HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport(); + messageBuilderSupport.accept(responseAcceptType); + + if (cookies != null) { + cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + } + + if (headers != null) { + headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + headers.forEach(messageBuilderSupport::header); + } + + String bodyLog = ""; + MultiValueMap multiValues = new LinkedMultiValueMap<>(); + if (StringUtils.isNotBlank(stringData)) { + // first try to load from resource + ClassPathResource resource = null; + try { + resource = new ClassPathResource(stringData); + } + catch(Exception ignore) { + // Use plain text instead of resource + } + + if(resource != null && resource.exists()){ + multiValues.add("stringData", resource); + } else { + multiValues.add("stringData", stringData); + } + bodyLog += stringData.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") +","; + } + if (StringUtils.isNotBlank(booleanData)) { + // first try to load from resource + ClassPathResource resource = null; + try { + resource = new ClassPathResource(booleanData); + } + catch(Exception ignore) { + // Use plain text instead of resource + } + + if(resource != null && resource.exists()){ + multiValues.add("booleanData", resource); + } else { + multiValues.add("booleanData", booleanData); + } + bodyLog += booleanData.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") +","; + } + if (StringUtils.isNotBlank(integerData)) { + // first try to load from resource + ClassPathResource resource = null; + try { + resource = new ClassPathResource(integerData); + } + catch(Exception ignore) { + // Use plain text instead of resource + } + + if(resource != null && resource.exists()){ + multiValues.add("integerData", resource); + } else { + multiValues.add("integerData", integerData); + } + bodyLog += integerData.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") +","; + } + + bodyLog += "\";\"" + MediaType.MULTIPART_FORM_DATA_VALUE + "\""; + messageBuilderSupport.contentType(MediaType.MULTIPART_FORM_DATA_VALUE) + .body(multiValues); + + + Map queryParams = new HashMap<>(); + + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver()); + httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder); + + httpClientRequestActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "multipleDatatypes;POST;\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + bodyLog); + } + + public void setStringData(String stringData) { + this.stringData = stringData; + } + + public void setBooleanData(String booleanData) { + this.booleanData = booleanData; + } + + public void setIntegerData(String integerData) { + this.integerData = integerData; + } + + private String replacePathParams(String endpoint) { + + return endpoint; + } + } + /** postFile (POST /api/v2/multitest-file/{bucket}/{filename}) + Uploads file. + + **/ + public static class PostFileRequest extends MultipartTestAbstractTestRequest implements GeneratedApiRequest { + + private static final String ENDPOINT = "/api/v2/multitest-file/{bucket}/{filename}"; + private final Logger coverageLogger = LoggerFactory.getLogger(PostFileRequest.class); + + private String bucket; + + private String filename; + + private String multipartFile; + + + public PostFileRequest() { + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("MultipartTest".toLowerCase() + ":postFileRequestType"); + } + + public String getOperationName() { + return "postFile"; + } + + public String getMethod() { + return "POST"; + } + + public String getPath() { + return "/api/v2/multitest-file/{bucket}/{filename}"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send() + .post(replacePathParams(ENDPOINT)); + + HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport(); + messageBuilderSupport.accept(responseAcceptType); + + if (cookies != null) { + cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + } + + if (headers != null) { + headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + headers.forEach(messageBuilderSupport::header); + } + + String bodyLog = ""; + MultiValueMap multiValues = new LinkedMultiValueMap<>(); + if (StringUtils.isNotBlank(multipartFile)) { + multiValues.add("multipartFile", new ClassPathResource(multipartFile)); + bodyLog += multipartFile.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") +","; + } + + bodyLog += "\";\"" + MediaType.MULTIPART_FORM_DATA_VALUE + "\""; + messageBuilderSupport.contentType(MediaType.MULTIPART_FORM_DATA_VALUE) + .body(multiValues); + + + Map queryParams = new HashMap<>(); + + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver()); + httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder); + + httpClientRequestActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "postFile;POST;\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + bodyLog); + } + + public void setBucket(String bucket) { + this.bucket = bucket; + } + + public void setFilename(String filename) { + this.filename = filename; + } + + public void setMultipartFile(String multipartFile) { + this.multipartFile = multipartFile; + } + + private String replacePathParams(String endpoint) { + endpoint = endpoint.replace("{" + "bucket" + "}", bucket);endpoint = endpoint.replace("{" + "filename" + "}", filename); + return endpoint; + } + } + /** postRandom (POST /api/v2/multitest-file/{bucket}/{filename}/random) + Uploads random file. + + **/ + public static class PostRandomRequest extends MultipartTestAbstractTestRequest implements GeneratedApiRequest { + + private static final String ENDPOINT = "/api/v2/multitest-file/{bucket}/{filename}/random"; + private final Logger coverageLogger = LoggerFactory.getLogger(PostRandomRequest.class); + + private String bucket; + + private String filename; + + + public PostRandomRequest() { + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("MultipartTest".toLowerCase() + ":postRandomRequestType"); + } + + public String getOperationName() { + return "postRandom"; + } + + public String getMethod() { + return "POST"; + } + + public String getPath() { + return "/api/v2/multitest-file/{bucket}/{filename}/random"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send() + .post(replacePathParams(ENDPOINT)); + + HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport(); + messageBuilderSupport.accept(responseAcceptType); + + if (cookies != null) { + cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + } + + if (headers != null) { + headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + headers.forEach(messageBuilderSupport::header); + } + + String bodyLog = ""; + String payload = null; + String payloadType = null; + if (StringUtils.isNotBlank(this.bodyFile)) { + try { + payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset()); + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read payload resource", e); + } + payloadType = this.bodyContentType; + } else if (StringUtils.isNotBlank(this.bodyLiteral)) { + payload = this.bodyLiteral; + payloadType = this.bodyLiteralContentType; + } + String body = ""; + String bodyType = ""; + if(payload != null && payloadType != null) { + messageBuilderSupport.body(payload).contentType(payloadType); + body = context.replaceDynamicContentInString(payload); + bodyType = context.replaceDynamicContentInString(payloadType); + } + + bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\""; + + Map queryParams = new HashMap<>(); + + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver()); + httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder); + + httpClientRequestActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "postRandom;POST;\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + bodyLog); + } + + public void setBucket(String bucket) { + this.bucket = bucket; + } + + public void setFilename(String filename) { + this.filename = filename; + } + + private String replacePathParams(String endpoint) { + endpoint = endpoint.replace("{" + "bucket" + "}", bucket);endpoint = endpoint.replace("{" + "filename" + "}", filename); + return endpoint; + } + } +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/spring/MultipartTestBeanConfiguration.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/spring/MultipartTestBeanConfiguration.java new file mode 100644 index 0000000000..090a9bfb7e --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/multiparttest/spring/MultipartTestBeanConfiguration.java @@ -0,0 +1,56 @@ +/** + * ================================================== + * GENERATED CLASS, ALL CHANGES WILL BE LOST + * ================================================== + */ + +package org.citrusframework.openapi.generator.rest.multiparttest.spring; + +import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE; + +import org.citrusframework.openapi.generator.rest.multiparttest.request.MultiparttestControllerApi; +import javax.annotation.processing.Generated; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; + +@Configuration +@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen") +public class MultipartTestBeanConfiguration { + + @Bean + @Scope(SCOPE_PROTOTYPE) + public MultiparttestControllerApi.DeleteObjectRequest deleteObjectRequest() { + return new MultiparttestControllerApi.DeleteObjectRequest(); + } + + @Bean + @Scope(SCOPE_PROTOTYPE) + public MultiparttestControllerApi.FileExistsRequest fileExistsRequest() { + return new MultiparttestControllerApi.FileExistsRequest(); + } + + @Bean + @Scope(SCOPE_PROTOTYPE) + public MultiparttestControllerApi.GenerateReportRequest generateReportRequest() { + return new MultiparttestControllerApi.GenerateReportRequest(); + } + + @Bean + @Scope(SCOPE_PROTOTYPE) + public MultiparttestControllerApi.MultipleDatatypesRequest multipleDatatypesRequest() { + return new MultiparttestControllerApi.MultipleDatatypesRequest(); + } + + @Bean + @Scope(SCOPE_PROTOTYPE) + public MultiparttestControllerApi.PostFileRequest postFileRequest() { + return new MultiparttestControllerApi.PostFileRequest(); + } + + @Bean + @Scope(SCOPE_PROTOTYPE) + public MultiparttestControllerApi.PostRandomRequest postRandomRequest() { + return new MultiparttestControllerApi.PostRandomRequest(); + } +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/citrus/PetStoreAbstractTestRequest.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/citrus/PetStoreAbstractTestRequest.java new file mode 100644 index 0000000000..9ff2150d21 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/citrus/PetStoreAbstractTestRequest.java @@ -0,0 +1,245 @@ +/** + * ================================================== + * GENERATED CLASS, ALL CHANGES WILL BE LOST + * ================================================== + */ + +package org.citrusframework.openapi.generator.rest.petstore.citrus; + + +import static org.springframework.util.CollectionUtils.isEmpty; + +import jakarta.annotation.Generated; +import jakarta.annotation.Nullable; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import javax.sql.DataSource; +import org.citrusframework.actions.AbstractTestAction; +import org.citrusframework.actions.ReceiveMessageAction; +import org.citrusframework.context.TestContext; +import org.citrusframework.http.actions.HttpActionBuilder; +import org.citrusframework.http.actions.HttpClientRequestActionBuilder; +import org.citrusframework.http.actions.HttpClientResponseActionBuilder; +import org.citrusframework.http.actions.HttpClientResponseActionBuilder.HttpMessageBuilderSupport; +import org.citrusframework.http.client.HttpClient; +import org.citrusframework.message.Message; +import org.citrusframework.testapi.ApiActionBuilderCustomizerService; +import org.citrusframework.testapi.GeneratedApi; +import org.citrusframework.spi.Resources; +import org.citrusframework.validation.DelegatingPayloadVariableExtractor; +import org.citrusframework.validation.PathExpressionValidationContext; +import org.citrusframework.validation.json.JsonMessageValidationContext; +import org.citrusframework.validation.script.ScriptValidationContext; +import org.slf4j.Marker; +import org.slf4j.MarkerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen") +public abstract class PetStoreAbstractTestRequest extends AbstractTestAction { + + protected final Marker coverageMarker = MarkerFactory.getMarker("PETSTORE-API-COVERAGE"); + + @Autowired + @Qualifier("petStoreEndpoint") + protected HttpClient httpClient; + + @Autowired(required = false) + protected DataSource dataSource; + + @Autowired(required = false) + private List actionBuilderCustomizerServices; + + // attributes of differentNodes + protected boolean schemaValidation; + protected String schema; + protected String bodyContentType; + protected String bodyLiteralContentType; + protected String bodyFile; + protected String bodyLiteral; + protected String responseAcceptType = "*/*"; + protected String responseType = "json"; + protected int responseStatus = 200; + protected String responseReasonPhrase = "OK"; + protected String responseVersion = "HTTP/1.1"; + + // children of response element + protected String resource; + protected Map responseVariable; // Contains the 'JSON-PATH' as key and the 'VARIABLE NAME' as value + protected Map responseValue; // Contains the 'JSON-PATH' as key and the 'VALUE TO BE VALIDATED' as value + protected Map cookies; + protected Map headers; + protected String script; + protected String type; // default script type is groovy - supported types see com.consol.citrus.script.ScriptTypes + + @Override + public void doExecute(TestContext context) { + sendRequest(context); + recieveResponse(context); + } + + /** + * This method receives the HTTP-Response. + * + * @deprecated use {@link PetStoreAbstractTestRequest#receiveResponse(TestContext)} instead. + */ + public ReceiveMessageAction recieveResponse(TestContext context) { + + HttpClientResponseActionBuilder httpClientResponseActionBuilder = new HttpActionBuilder().client(httpClient).receive().response(); + HttpMessageBuilderSupport messageBuilderSupport = httpClientResponseActionBuilder.getMessageBuilderSupport(); + + messageBuilderSupport + .statusCode(responseStatus) + .reasonPhrase(responseReasonPhrase) + .version(responseVersion) + .validate(new JsonMessageValidationContext.Builder().schemaValidation(schemaValidation).schema(schema)); + + if (resource != null) { + messageBuilderSupport.body(Resources.create(resource)); + } + + if (!isEmpty(responseVariable)) { + DelegatingPayloadVariableExtractor.Builder extractorBuilder = new DelegatingPayloadVariableExtractor.Builder(); + responseVariable.forEach(extractorBuilder::expression); + messageBuilderSupport.extract(extractorBuilder); + } + + if (!isEmpty(responseValue)) { + PathExpressionValidationContext.Builder validationContextBuilder = new PathExpressionValidationContext.Builder(); + responseValue.forEach(validationContextBuilder::expression); + messageBuilderSupport.validate(validationContextBuilder); + } + + if (script != null) { + ScriptValidationContext.Builder scriptValidationContextBuilder = new ScriptValidationContext.Builder(); + if (type != null) { + scriptValidationContextBuilder.scriptType(type); + } + scriptValidationContextBuilder.script(script); + messageBuilderSupport.validate(scriptValidationContextBuilder); + } + + messageBuilderSupport.type(responseType); + httpClientResponseActionBuilder.withReferenceResolver(context.getReferenceResolver()); + var responseAction = httpClientResponseActionBuilder.build(); + + responseAction.execute(context); + + return responseAction; + } + + public @Nullable Message receiveResponse(TestContext context) { + var responseAction = recieveResponse(context); + + var messageStore = context.getMessageStore(); + return messageStore.getMessage(messageStore.constructMessageName(responseAction, httpClient)); + } + + public abstract void sendRequest(TestContext context); + + public void setSchemaValidation(boolean schemaValidation) { + this.schemaValidation = schemaValidation; + } + + public void setSchema(String schema) { + this.schema = schema; + } + + public void setBodyLiteral(String bodyLiteral) { + this.bodyLiteral = bodyLiteral; + } + + public void setBodyContentType(String bodyContentType) { + this.bodyContentType = bodyContentType; + } + + public void setBodyLiteralContentType(String bodyLiteralContentType) { + this.bodyLiteralContentType = bodyLiteralContentType; + } + + public void setResponseAcceptType(String responseAcceptType) { + this.responseAcceptType = responseAcceptType; + } + + public void setCookie(Map cookies) { + this.cookies = cookies; + } + + public void setHeader(Map headers) { + this.headers = headers; + } + + public void setBodyFile(String bodyFile) { + this.bodyFile = bodyFile; + } + + public void setResponseType(String responseType) { + this.responseType = responseType; + } + + public void setResponseStatus(int responseStatus) { + this.responseStatus = responseStatus; + } + + public void setResponseReasonPhrase(String responseReasonPhrase) { + this.responseReasonPhrase = responseReasonPhrase; + } + + public void setResponseVersion(String responseVersion) { + this.responseVersion = responseVersion; + } + + public void setResource(String resource) { + this.resource = resource; + } + + public void setResponseVariable(Map responseVariable) { + this.responseVariable = responseVariable; + } + + public void setResponseValue(Map responseValue) { + this.responseValue = responseValue; + } + + public void setScript(String script) { + this.script = script; + } + + public void setType(String type) { + this.type = type; + } + + protected HttpClientRequestActionBuilder customizeBuilder(GeneratedApi generatedApi, + TestContext context, HttpClientRequestActionBuilder httpClientRequestActionBuilder) { + + httpClientRequestActionBuilder = customizeByBeans(generatedApi, context, + httpClientRequestActionBuilder); + + httpClientRequestActionBuilder = customizeBySpi(generatedApi, context, httpClientRequestActionBuilder); + + return httpClientRequestActionBuilder; + } + + private HttpClientRequestActionBuilder customizeBySpi(GeneratedApi generatedApi, TestContext context, + HttpClientRequestActionBuilder httpClientRequestActionBuilder) { + ServiceLoader serviceLoader = ServiceLoader.load( + ApiActionBuilderCustomizerService.class, ApiActionBuilderCustomizerService.class.getClassLoader()); + for (ApiActionBuilderCustomizerService service :serviceLoader) { + httpClientRequestActionBuilder = service.build(generatedApi, this, context, httpClientRequestActionBuilder); + } + return httpClientRequestActionBuilder; + } + + private HttpClientRequestActionBuilder customizeByBeans( + GeneratedApi generatedApi, TestContext context, + HttpClientRequestActionBuilder httpClientRequestActionBuilder) { + if (actionBuilderCustomizerServices != null) { + for (ApiActionBuilderCustomizerService apiActionBuilderCustomizer : actionBuilderCustomizerServices) { + httpClientRequestActionBuilder = apiActionBuilderCustomizer.build(generatedApi, this, + context, httpClientRequestActionBuilder); + } + } + return httpClientRequestActionBuilder; + } +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/citrus/PetStoreBeanDefinitionParser.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/citrus/PetStoreBeanDefinitionParser.java new file mode 100644 index 0000000000..32920fb8ef --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/citrus/PetStoreBeanDefinitionParser.java @@ -0,0 +1,215 @@ +/** + * ================================================== + * GENERATED CLASS, ALL CHANGES WILL BE LOST + * ================================================== + */ + +package org.citrusframework.openapi.generator.rest.petstore.citrus; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.annotation.processing.Generated; + +import org.citrusframework.exceptions.CitrusRuntimeException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.core.Conventions; +import org.springframework.util.Assert; +import org.apache.commons.lang3.StringUtils; +import org.springframework.util.xml.DomUtils; +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + + +@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen") +public class PetStoreBeanDefinitionParser implements BeanDefinitionParser { + + private static final String COOKIE = "cookie"; + private static final String HEADER = "header"; + private static final String SOAP_HEADER = "soapHeader"; + private static final String MIME_HEADER = "mimeHeader"; + private static final String NAME = "name"; + private static final String REQUEST_BODY = "body"; + private static final String REQUEST_BODY_LITERAL = "bodyLiteral"; + private static final String MULTIPART_BODY = "multipartBody"; + private static final String RESPONSE = "response"; + private static final String RESPONSE_JSONPATH = "json-path"; + private static final String RESPONSE_XPATH = "xpath"; + private static final String EXPRESSION = "expression"; + private static final String VALUE = "value"; + private static final String RESPONSE_RESOURCE = "resource"; + private static final String FILE = "file"; + private static final String RESPONSE_VARIABLE = "responseVariable"; + private static final String RESPONSE_VALUE = "responseValue"; + private static final String SCRIPT = "script"; + private static final String TYPE = "type"; + private static final String SQL = "sql"; + private static final String COLUMN = "column"; + private static final String VARIABLE = "variable"; + // new + private static final String SCHEMA = "schema"; + // new + private static final String SCHEMA_VALIDATION = "schemaValidation"; + + private final Class beanClass; + + public PetStoreBeanDefinitionParser(Class beanClass) { + this.beanClass = beanClass; + } + + public BeanDefinition parse(Element element) { + return parse(element, null); + } + + /** + * Note: The {@link PetStoreBeanDefinitionParser#parse(Element element)} allows access direct + * access without the {@link org.springframework.beans.factory.xml.ParserContext} for convenience. + */ + @Override + public BeanDefinition parse(Element element, ParserContext parserContext) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(beanClass); + retrieveRootNodeAttributes(element, builder); + retrieveOptionalNodeAttributes(element, REQUEST_BODY, builder); + retrieveTextContentAndNodeAttributes(element, REQUEST_BODY_LITERAL, builder); + retrieveOptionalNodeAttributes(element, RESPONSE, builder); + retrieveParamNodeData(element, builder, COOKIE); + retrieveParamNodeData(element, builder, HEADER); + retrieveParamNodeData(element, builder, SOAP_HEADER); + retrieveParamNodeData(element, builder, MIME_HEADER); + retrieveOptionalNodeAttributes(element, SCHEMA, builder); + retrieveOptionalNodeAttributes(element, SCHEMA_VALIDATION, builder); + retrieveOptionalMultipartElements(element, builder); + retrieveResponseNodeData(element, builder); + builder.addPropertyValue("name", element.getTagName()); + return builder.getBeanDefinition(); + } + + private void retrieveOptionalMultipartElements(Element element, BeanDefinitionBuilder builder) { + var multipartBodyElement = DomUtils.getChildElementByTagName(element, MULTIPART_BODY); + if (multipartBodyElement != null) { + var multipartBodyChildElements = DomUtils.getChildElements(multipartBodyElement); + for(int i = 0; i < multipartBodyChildElements.size(); i++){ + var multipartBodyChildElement = multipartBodyChildElements.get(i); + String propertyName = Conventions.attributeNameToPropertyName(multipartBodyChildElement.getLocalName()); + builder.addPropertyValue(propertyName, multipartBodyChildElement.getTextContent()); + } + } + } + + private void retrieveRootNodeAttributes(Element element, BeanDefinitionBuilder builder) { + NamedNodeMap attributes = element.getAttributes(); + for (int x = 0; x < attributes.getLength(); x++) { + Attr attribute = (Attr) attributes.item(x); + String propertyName = Conventions.attributeNameToPropertyName(attribute.getLocalName()); + Assert.state(StringUtils.isNotBlank(propertyName), "Illegal property name returned, it must not be null or empty."); + builder.addPropertyValue(propertyName, attribute.getValue()); + } + } + + private void retrieveOptionalNodeAttributes(Element element, String elementName, BeanDefinitionBuilder builder) { + if (!DomUtils.getChildElementsByTagName(element, elementName).isEmpty()) { + Element el = DomUtils.getChildElementsByTagName(element, elementName).get(0); + NamedNodeMap attributes = el.getAttributes(); + for (int x = 0; x < attributes.getLength(); x++) { + Attr attribute = (Attr) attributes.item(x); + String propertyName = Conventions.attributeNameToPropertyName(attribute.getLocalName()); + Assert.state(StringUtils.isNotBlank(propertyName), "Illegal property name returned, it must not be null or empty."); + String variableName = el.getLocalName() + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1); + builder.addPropertyValue(variableName, attribute.getValue()); + } + } + } + + private void retrieveTextContentAndNodeAttributes(Element element, String elementName, BeanDefinitionBuilder builder) { + if (!DomUtils.getChildElementsByTagName(element, elementName).isEmpty()) { + Element el1 = DomUtils.getChildElementsByTagName(element, elementName).get(0); + NamedNodeMap attributes = el1.getAttributes(); + for (int x = 0; x < attributes.getLength(); x++) { + Attr attribute = (Attr) attributes.item(x); + String propertyName1 = Conventions.attributeNameToPropertyName(attribute.getLocalName()); + Assert.state(StringUtils.isNotBlank(propertyName1), "Illegal property name returned, it must not be null or empty."); + String variableName = el1.getLocalName() + propertyName1.substring(0, 1).toUpperCase() + propertyName1.substring(1); + builder.addPropertyValue(variableName, attribute.getValue()); + } + Element el = DomUtils.getChildElementsByTagName(element, elementName).get(0); + builder.addPropertyValue(elementName, el.getTextContent()); + } + } + + private void retrieveParamNodeData(Element element, BeanDefinitionBuilder builder, String paramType) { + if (!DomUtils.getChildElementsByTagName(element, paramType).isEmpty()) { + Map params = new HashMap<>(); + List elements = DomUtils.getChildElementsByTagName(element, paramType); + elements.forEach(e -> { + String name = e.getAttribute(NAME); + String value = e.getAttribute(VALUE); + + Assert.state(StringUtils.isNotBlank(name), "Illegal attribute value returned. The 'name' attribute must not be null or empty."); + Assert.state(StringUtils.isNotBlank(value), "Illegal attribute value returned. The 'value' attribute must not be null or empty."); + + params.put(name, value); + }); + builder.addPropertyValue(paramType, params); + } + } + + private void retrieveResponseNodeData(Element element, BeanDefinitionBuilder builder) { + + if (!DomUtils.getChildElementsByTagName(element, RESPONSE).isEmpty()) { + Element response = DomUtils.getChildElementsByTagName(element, RESPONSE).get(0); + List elements = DomUtils.getChildElements(response); + + Map responseVariable = new HashMap<>(); + Map responseValue = new HashMap<>(); + + for (int i = 0; i < elements.size(); i++) { + Element e = elements.get(i); + + if (e.getTagName().contains(RESPONSE_JSONPATH) || e.getTagName().contains(RESPONSE_XPATH)) { + String expression = e.getAttribute(EXPRESSION); + String value = e.getAttribute(VALUE); + + Assert.state(StringUtils.isNotBlank(expression), "Illegal attribute value returned. The 'expression' attribute must not be null or empty."); + Assert.state(StringUtils.isNotBlank(value), "Illegal attribute value returned. The 'value' attribute must not be null or empty."); + + // variable to save @variable('ebid')@ else value to validate + if (value.matches("\\@variable\\('.*'\\)\\@")) { + Matcher match = Pattern.compile("\\'(.*?)\\'").matcher(value); + if (match.find()) { + responseVariable.put(expression, value.substring(match.start() + 1, match.end() - 1)); + } + } else { + responseValue.put(expression, value); + } + } else if (e.getTagName().contains(SCRIPT)) { + String script = e.getTextContent(); + Assert.state(StringUtils.isNotBlank(script), "Illegal attribute value returned. The 'script' attribute must not be null or empty."); + builder.addPropertyValue(SCRIPT, script); + + if (!e.getAttribute(TYPE).isEmpty()) { + String type = e.getAttribute(TYPE); + Assert.state(StringUtils.isNotBlank(type), "Illegal attribute value returned. The 'type' attribute must not be null or empty."); + builder.addPropertyValue(TYPE, type); + } + } else if (e.getTagName().contains(RESPONSE_RESOURCE)) { + String filePath = e.getAttribute(FILE); + Assert.state(StringUtils.isNotBlank(filePath), "Illegal attribute value returned. The 'file' attribute must not be null or empty."); + builder.addPropertyValue(RESPONSE_RESOURCE, filePath); + } + + } + + builder.addPropertyValue(RESPONSE_VARIABLE, responseVariable); + builder.addPropertyValue(RESPONSE_VALUE, responseValue); + } + } +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/citrus/extension/PetStoreNamespaceHandler.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/citrus/extension/PetStoreNamespaceHandler.java new file mode 100644 index 0000000000..af5b731084 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/citrus/extension/PetStoreNamespaceHandler.java @@ -0,0 +1,45 @@ +/** + * ================================================== + * GENERATED CLASS, ALL CHANGES WILL BE LOST + * ================================================== + */ + +package org.citrusframework.openapi.generator.rest.petstore.citrus.extension; + +import org.citrusframework.openapi.generator.rest.petstore.request.PetApi; +import org.citrusframework.openapi.generator.rest.petstore.request.StoreApi; +import org.citrusframework.openapi.generator.rest.petstore.request.UserApi; +import org.citrusframework.openapi.generator.rest.petstore.citrus.PetStoreBeanDefinitionParser; + +import javax.annotation.processing.Generated; + +import org.springframework.beans.factory.xml.NamespaceHandlerSupport; + + +@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen") +public class PetStoreNamespaceHandler extends NamespaceHandlerSupport { + + @Override + public void init() { + registerBeanDefinitionParser("addPetRequest", new PetStoreBeanDefinitionParser(PetApi.AddPetRequest.class)); + registerBeanDefinitionParser("deletePetRequest", new PetStoreBeanDefinitionParser(PetApi.DeletePetRequest.class)); + registerBeanDefinitionParser("findPetsByStatusRequest", new PetStoreBeanDefinitionParser(PetApi.FindPetsByStatusRequest.class)); + registerBeanDefinitionParser("findPetsByTagsRequest", new PetStoreBeanDefinitionParser(PetApi.FindPetsByTagsRequest.class)); + registerBeanDefinitionParser("getPetByIdRequest", new PetStoreBeanDefinitionParser(PetApi.GetPetByIdRequest.class)); + registerBeanDefinitionParser("updatePetRequest", new PetStoreBeanDefinitionParser(PetApi.UpdatePetRequest.class)); + registerBeanDefinitionParser("updatePetWithFormRequest", new PetStoreBeanDefinitionParser(PetApi.UpdatePetWithFormRequest.class)); + registerBeanDefinitionParser("uploadFileRequest", new PetStoreBeanDefinitionParser(PetApi.UploadFileRequest.class)); + registerBeanDefinitionParser("deleteOrderRequest", new PetStoreBeanDefinitionParser(StoreApi.DeleteOrderRequest.class)); + registerBeanDefinitionParser("getInventoryRequest", new PetStoreBeanDefinitionParser(StoreApi.GetInventoryRequest.class)); + registerBeanDefinitionParser("getOrderByIdRequest", new PetStoreBeanDefinitionParser(StoreApi.GetOrderByIdRequest.class)); + registerBeanDefinitionParser("placeOrderRequest", new PetStoreBeanDefinitionParser(StoreApi.PlaceOrderRequest.class)); + registerBeanDefinitionParser("createUserRequest", new PetStoreBeanDefinitionParser(UserApi.CreateUserRequest.class)); + registerBeanDefinitionParser("createUsersWithArrayInputRequest", new PetStoreBeanDefinitionParser(UserApi.CreateUsersWithArrayInputRequest.class)); + registerBeanDefinitionParser("createUsersWithListInputRequest", new PetStoreBeanDefinitionParser(UserApi.CreateUsersWithListInputRequest.class)); + registerBeanDefinitionParser("deleteUserRequest", new PetStoreBeanDefinitionParser(UserApi.DeleteUserRequest.class)); + registerBeanDefinitionParser("getUserByNameRequest", new PetStoreBeanDefinitionParser(UserApi.GetUserByNameRequest.class)); + registerBeanDefinitionParser("loginUserRequest", new PetStoreBeanDefinitionParser(UserApi.LoginUserRequest.class)); + registerBeanDefinitionParser("logoutUserRequest", new PetStoreBeanDefinitionParser(UserApi.LogoutUserRequest.class)); + registerBeanDefinitionParser("updateUserRequest", new PetStoreBeanDefinitionParser(UserApi.UpdateUserRequest.class)); + } +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/Category.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/Category.java new file mode 100644 index 0000000000..d5341fea2c --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/Category.java @@ -0,0 +1 @@ +// not in use diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/ModelApiResponse.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/ModelApiResponse.java new file mode 100644 index 0000000000..d5341fea2c --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/ModelApiResponse.java @@ -0,0 +1 @@ +// not in use diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/Order.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/Order.java new file mode 100644 index 0000000000..d5341fea2c --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/Order.java @@ -0,0 +1 @@ +// not in use diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/Pet.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/Pet.java new file mode 100644 index 0000000000..d5341fea2c --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/Pet.java @@ -0,0 +1 @@ +// not in use diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/Tag.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/Tag.java new file mode 100644 index 0000000000..d5341fea2c --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/Tag.java @@ -0,0 +1 @@ +// not in use diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/User.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/User.java new file mode 100644 index 0000000000..d5341fea2c --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/model/User.java @@ -0,0 +1 @@ +// not in use 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 new file mode 100644 index 0000000000..a014b1c53f --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/request/PetApi.java @@ -0,0 +1,862 @@ +/** + * ================================================== + * GENERATED CLASS, ALL CHANGES WILL BE LOST + * ================================================== + */ + +package org.citrusframework.openapi.generator.rest.petstore.request; + +import jakarta.annotation.Generated; +import org.citrusframework.testapi.GeneratedApi; +import org.citrusframework.testapi.GeneratedApiRequest; +import jakarta.servlet.http.Cookie; +import org.apache.commons.lang3.StringUtils; +import org.citrusframework.context.TestContext; +import org.citrusframework.exceptions.CitrusRuntimeException; +import org.citrusframework.spi.Resources; +import org.citrusframework.http.actions.HttpActionBuilder; +import org.citrusframework.http.actions.HttpClientRequestActionBuilder; +import org.citrusframework.http.actions.HttpClientRequestActionBuilder.HttpMessageBuilderSupport; +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; + + +@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen") +public class PetApi implements GeneratedApi +{ + + public static final PetApi INSTANCE = new PetApi(); + + public String getApiTitle() { + return "OpenAPI Petstore"; + } + + public String getApiVersion() { + return "1.0.0"; + } + + public String getApiPrefix() { + return "PetStore"; + } + + public Map getApiInfoExtensions() { + Map infoExtensionMap = new HashMap<>(); + infoExtensionMap.put("x-citrus-api-name", "petstore"); + infoExtensionMap.put("x-citrus-app", "PETS"); + return infoExtensionMap; + } + + /** addPet (POST /pet) + Add a new pet to the store + + **/ + public static class AddPetRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest { + + private static final String ENDPOINT = "/pet"; + private final Logger coverageLogger = LoggerFactory.getLogger(AddPetRequest.class); + + + public AddPetRequest() { + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("PetStore".toLowerCase() + ":addPetRequestType"); + } + + public String getOperationName() { + return "addPet"; + } + + public String getMethod() { + return "POST"; + } + + public String getPath() { + return "/pet"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send() + .post(replacePathParams(ENDPOINT)); + + HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport(); + messageBuilderSupport.accept(responseAcceptType); + + if (cookies != null) { + cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + } + + if (headers != null) { + headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + headers.forEach(messageBuilderSupport::header); + } + + String bodyLog = ""; + String payload = null; + String payloadType = null; + if (StringUtils.isNotBlank(this.bodyFile)) { + try { + payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset()); + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read payload resource", e); + } + payloadType = this.bodyContentType; + } else if (StringUtils.isNotBlank(this.bodyLiteral)) { + payload = this.bodyLiteral; + payloadType = this.bodyLiteralContentType; + } + String body = ""; + String bodyType = ""; + if(payload != null && payloadType != null) { + messageBuilderSupport.body(payload).contentType(payloadType); + body = context.replaceDynamicContentInString(payload); + bodyType = context.replaceDynamicContentInString(payloadType); + } + + bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\""; + + Map queryParams = new HashMap<>(); + + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver()); + httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder); + + httpClientRequestActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "addPet;POST;\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + bodyLog); + } + + private String replacePathParams(String endpoint) { + + return endpoint; + } + } + /** deletePet (DELETE /pet/{petId}) + Deletes a pet + + **/ + public static class DeletePetRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest { + + private static final String ENDPOINT = "/pet/{petId}"; + private final Logger coverageLogger = LoggerFactory.getLogger(DeletePetRequest.class); + + private String petId; + + + public DeletePetRequest() { + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("PetStore".toLowerCase() + ":deletePetRequestType"); + } + + public String getOperationName() { + return "deletePet"; + } + + public String getMethod() { + return "DELETE"; + } + + public String getPath() { + return "/pet/{petId}"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send() + .delete(replacePathParams(ENDPOINT)); + + HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport(); + messageBuilderSupport.accept(responseAcceptType); + + if (cookies != null) { + cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + } + + if (headers != null) { + headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + headers.forEach(messageBuilderSupport::header); + } + + String bodyLog = ""; + String payload = null; + String payloadType = null; + if (StringUtils.isNotBlank(this.bodyFile)) { + try { + payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset()); + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read payload resource", e); + } + payloadType = this.bodyContentType; + } else if (StringUtils.isNotBlank(this.bodyLiteral)) { + payload = this.bodyLiteral; + payloadType = this.bodyLiteralContentType; + } + String body = ""; + String bodyType = ""; + if(payload != null && payloadType != null) { + messageBuilderSupport.body(payload).contentType(payloadType); + body = context.replaceDynamicContentInString(payload); + bodyType = context.replaceDynamicContentInString(payloadType); + } + + bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\""; + + Map queryParams = new HashMap<>(); + + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver()); + httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder); + + httpClientRequestActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "deletePet;DELETE;\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + bodyLog); + } + + public void setPetId(String petId) { + this.petId = petId; + } + + private String replacePathParams(String endpoint) { + endpoint = endpoint.replace("{" + "petId" + "}", petId); + return endpoint; + } + } + /** findPetsByStatus (GET /pet/findByStatus) + Finds Pets by status + + **/ + public static class FindPetsByStatusRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest { + + private static final String ENDPOINT = "/pet/findByStatus"; + private final Logger coverageLogger = LoggerFactory.getLogger(FindPetsByStatusRequest.class); + + private String status; + + + public FindPetsByStatusRequest() { + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("PetStore".toLowerCase() + ":findPetsByStatusRequestType"); + } + + public String getOperationName() { + return "findPetsByStatus"; + } + + public String getMethod() { + return "GET"; + } + + public String getPath() { + return "/pet/findByStatus"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send() + .get(replacePathParams(ENDPOINT)); + + HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport(); + messageBuilderSupport.accept(responseAcceptType); + + if (cookies != null) { + cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + } + + if (headers != null) { + headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + headers.forEach(messageBuilderSupport::header); + } + + String bodyLog = ""; + String payload = null; + String payloadType = null; + if (StringUtils.isNotBlank(this.bodyFile)) { + try { + payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset()); + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read payload resource", e); + } + payloadType = this.bodyContentType; + } else if (StringUtils.isNotBlank(this.bodyLiteral)) { + payload = this.bodyLiteral; + payloadType = this.bodyLiteralContentType; + } + String body = ""; + String bodyType = ""; + if(payload != null && payloadType != null) { + messageBuilderSupport.body(payload).contentType(payloadType); + body = context.replaceDynamicContentInString(payload); + bodyType = context.replaceDynamicContentInString(payloadType); + } + + bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\""; + + Map queryParams = new HashMap<>(); + + + if (StringUtils.isNotBlank(this.status)) { + queryParams.put("status", context.replaceDynamicContentInString(this.status)); + httpClientRequestActionBuilder.queryParam("status", this.status); + } + + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver()); + httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder); + + httpClientRequestActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "findPetsByStatus;GET;\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + bodyLog); + } + + public void setStatus(String status) { + this.status = status; + } + + private String replacePathParams(String endpoint) { + + return endpoint; + } + } + /** findPetsByTags (GET /pet/findByTags) + Finds Pets by tags + + **/ + public static class FindPetsByTagsRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest { + + private static final String ENDPOINT = "/pet/findByTags"; + private final Logger coverageLogger = LoggerFactory.getLogger(FindPetsByTagsRequest.class); + + private String tags; + + + public FindPetsByTagsRequest() { + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("PetStore".toLowerCase() + ":findPetsByTagsRequestType"); + } + + public String getOperationName() { + return "findPetsByTags"; + } + + public String getMethod() { + return "GET"; + } + + public String getPath() { + return "/pet/findByTags"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send() + .get(replacePathParams(ENDPOINT)); + + HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport(); + messageBuilderSupport.accept(responseAcceptType); + + if (cookies != null) { + cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + } + + if (headers != null) { + headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + headers.forEach(messageBuilderSupport::header); + } + + String bodyLog = ""; + String payload = null; + String payloadType = null; + if (StringUtils.isNotBlank(this.bodyFile)) { + try { + payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset()); + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read payload resource", e); + } + payloadType = this.bodyContentType; + } else if (StringUtils.isNotBlank(this.bodyLiteral)) { + payload = this.bodyLiteral; + payloadType = this.bodyLiteralContentType; + } + String body = ""; + String bodyType = ""; + if(payload != null && payloadType != null) { + messageBuilderSupport.body(payload).contentType(payloadType); + body = context.replaceDynamicContentInString(payload); + bodyType = context.replaceDynamicContentInString(payloadType); + } + + bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\""; + + Map queryParams = new HashMap<>(); + + + if (StringUtils.isNotBlank(this.tags)) { + queryParams.put("tags", context.replaceDynamicContentInString(this.tags)); + httpClientRequestActionBuilder.queryParam("tags", this.tags); + } + + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver()); + httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder); + + httpClientRequestActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "findPetsByTags;GET;\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + bodyLog); + } + + public void setTags(String tags) { + this.tags = tags; + } + + private String replacePathParams(String endpoint) { + + return endpoint; + } + } + /** getPetById (GET /pet/{petId}) + Find pet by ID + + **/ + public static class GetPetByIdRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest { + + private static final String ENDPOINT = "/pet/{petId}"; + private final Logger coverageLogger = LoggerFactory.getLogger(GetPetByIdRequest.class); + + private String petId; + + + @Value("${" + "petStoreEndpoint.basic.username:#{null}}") + private String basicUsername; + @Value("${" + "petStoreEndpoint.basic.password:#{null}}") + private String basicPassword; + + + public GetPetByIdRequest() { + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("PetStore".toLowerCase() + ":getPetByIdRequestType"); + } + + public String getOperationName() { + return "getPetById"; + } + + public String getMethod() { + return "GET"; + } + + public String getPath() { + return "/pet/{petId}"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send() + .get(replacePathParams(ENDPOINT)); + + HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport(); + messageBuilderSupport.accept(responseAcceptType); + + if (cookies != null) { + cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + } + + if (headers != null) { + headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + headers.forEach(messageBuilderSupport::header); + } + + String bodyLog = ""; + String payload = null; + String payloadType = null; + if (StringUtils.isNotBlank(this.bodyFile)) { + try { + payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset()); + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read payload resource", e); + } + payloadType = this.bodyContentType; + } else if (StringUtils.isNotBlank(this.bodyLiteral)) { + payload = this.bodyLiteral; + payloadType = this.bodyLiteralContentType; + } + String body = ""; + String bodyType = ""; + if(payload != null && payloadType != null) { + messageBuilderSupport.body(payload).contentType(payloadType); + body = context.replaceDynamicContentInString(payload); + bodyType = context.replaceDynamicContentInString(payloadType); + } + + bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\""; + + Map queryParams = new HashMap<>(); + + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + + if(basicUsername != null && basicPassword != null){ + messageBuilderSupport.header("Authorization", "Basic " + Base64.getEncoder().encodeToString((context.replaceDynamicContentInString(basicUsername)+":"+context.replaceDynamicContentInString(basicPassword)).getBytes())); + } + + httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver()); + httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder); + + httpClientRequestActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "getPetById;GET;\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + bodyLog); + } + + public void setPetId(String petId) { + this.petId = petId; + } + + + public void setBasicUsername(String basicUsername) { + this.basicUsername = basicUsername; + } + + public void setBasicPassword(String basicPassword) { + this.basicPassword = basicPassword; + } + + private String replacePathParams(String endpoint) { + endpoint = endpoint.replace("{" + "petId" + "}", petId); + return endpoint; + } + } + /** updatePet (PUT /pet) + Update an existing pet + + **/ + public static class UpdatePetRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest { + + private static final String ENDPOINT = "/pet"; + private final Logger coverageLogger = LoggerFactory.getLogger(UpdatePetRequest.class); + + + public UpdatePetRequest() { + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("PetStore".toLowerCase() + ":updatePetRequestType"); + } + + public String getOperationName() { + return "updatePet"; + } + + public String getMethod() { + return "PUT"; + } + + public String getPath() { + return "/pet"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send() + .put(replacePathParams(ENDPOINT)); + + HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport(); + messageBuilderSupport.accept(responseAcceptType); + + if (cookies != null) { + cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + } + + if (headers != null) { + headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + headers.forEach(messageBuilderSupport::header); + } + + String bodyLog = ""; + String payload = null; + String payloadType = null; + if (StringUtils.isNotBlank(this.bodyFile)) { + try { + payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset()); + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read payload resource", e); + } + payloadType = this.bodyContentType; + } else if (StringUtils.isNotBlank(this.bodyLiteral)) { + payload = this.bodyLiteral; + payloadType = this.bodyLiteralContentType; + } + String body = ""; + String bodyType = ""; + if(payload != null && payloadType != null) { + messageBuilderSupport.body(payload).contentType(payloadType); + body = context.replaceDynamicContentInString(payload); + bodyType = context.replaceDynamicContentInString(payloadType); + } + + bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\""; + + Map queryParams = new HashMap<>(); + + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver()); + httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder); + + httpClientRequestActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "updatePet;PUT;\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + bodyLog); + } + + private String replacePathParams(String endpoint) { + + return endpoint; + } + } + /** updatePetWithForm (POST /pet/{petId}) + Updates a pet in the store with form data + + **/ + public static class UpdatePetWithFormRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest { + + private static final String ENDPOINT = "/pet/{petId}"; + private final Logger coverageLogger = LoggerFactory.getLogger(UpdatePetWithFormRequest.class); + + private String petId; + + + public UpdatePetWithFormRequest() { + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("PetStore".toLowerCase() + ":updatePetWithFormRequestType"); + } + + public String getOperationName() { + return "updatePetWithForm"; + } + + public String getMethod() { + return "POST"; + } + + public String getPath() { + return "/pet/{petId}"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send() + .post(replacePathParams(ENDPOINT)); + + HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport(); + messageBuilderSupport.accept(responseAcceptType); + + if (cookies != null) { + cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + } + + if (headers != null) { + headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + headers.forEach(messageBuilderSupport::header); + } + + String bodyLog = ""; + String payload = null; + String payloadType = null; + if (StringUtils.isNotBlank(this.bodyFile)) { + try { + payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset()); + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read payload resource", e); + } + payloadType = this.bodyContentType; + } else if (StringUtils.isNotBlank(this.bodyLiteral)) { + payload = this.bodyLiteral; + payloadType = this.bodyLiteralContentType; + } + String body = ""; + String bodyType = ""; + if(payload != null && payloadType != null) { + messageBuilderSupport.body(payload).contentType(payloadType); + body = context.replaceDynamicContentInString(payload); + bodyType = context.replaceDynamicContentInString(payloadType); + } + + bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\""; + + Map queryParams = new HashMap<>(); + + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver()); + httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder); + + httpClientRequestActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "updatePetWithForm;POST;\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + bodyLog); + } + + public void setPetId(String petId) { + this.petId = petId; + } + + private String replacePathParams(String endpoint) { + endpoint = endpoint.replace("{" + "petId" + "}", petId); + return endpoint; + } + } + /** uploadFile (POST /pet/{petId}/uploadImage) + uploads an image + + **/ + public static class UploadFileRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest { + + private static final String ENDPOINT = "/pet/{petId}/uploadImage"; + private final Logger coverageLogger = LoggerFactory.getLogger(UploadFileRequest.class); + + private String petId; + + private String additionalMetadata; + + private String _file; + + + public UploadFileRequest() { + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("PetStore".toLowerCase() + ":uploadFileRequestType"); + } + + public String getOperationName() { + return "uploadFile"; + } + + public String getMethod() { + return "POST"; + } + + public String getPath() { + return "/pet/{petId}/uploadImage"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send() + .post(replacePathParams(ENDPOINT)); + + HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport(); + messageBuilderSupport.accept(responseAcceptType); + + if (cookies != null) { + cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + } + + if (headers != null) { + headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + headers.forEach(messageBuilderSupport::header); + } + + String bodyLog = ""; + MultiValueMap multiValues = new LinkedMultiValueMap<>(); + if (StringUtils.isNotBlank(additionalMetadata)) { + // first try to load from resource + ClassPathResource resource = null; + try { + resource = new ClassPathResource(additionalMetadata); + } + catch(Exception ignore) { + // Use plain text instead of resource + } + + if(resource != null && resource.exists()){ + multiValues.add("additionalMetadata", resource); + } else { + multiValues.add("additionalMetadata", additionalMetadata); + } + bodyLog += additionalMetadata.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") +","; + } + if (StringUtils.isNotBlank(_file)) { + multiValues.add("_file", new ClassPathResource(_file)); + bodyLog += _file.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") +","; + } + + bodyLog += "\";\"" + MediaType.MULTIPART_FORM_DATA_VALUE + "\""; + messageBuilderSupport.contentType(MediaType.MULTIPART_FORM_DATA_VALUE) + .body(multiValues); + + + Map queryParams = new HashMap<>(); + + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver()); + httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder); + + httpClientRequestActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "uploadFile;POST;\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + bodyLog); + } + + public void setPetId(String petId) { + this.petId = petId; + } + + public void setAdditionalMetadata(String additionalMetadata) { + this.additionalMetadata = additionalMetadata; + } + + public void set_file(String _file) { + this._file = _file; + } + + private String replacePathParams(String endpoint) { + endpoint = endpoint.replace("{" + "petId" + "}", petId); + return endpoint; + } + } +} 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 new file mode 100644 index 0000000000..406f97f2f9 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/request/StoreApi.java @@ -0,0 +1,433 @@ +/** + * ================================================== + * GENERATED CLASS, ALL CHANGES WILL BE LOST + * ================================================== + */ + +package org.citrusframework.openapi.generator.rest.petstore.request; + +import jakarta.annotation.Generated; +import org.citrusframework.testapi.GeneratedApi; +import org.citrusframework.testapi.GeneratedApiRequest; +import jakarta.servlet.http.Cookie; +import org.apache.commons.lang3.StringUtils; +import org.citrusframework.context.TestContext; +import org.citrusframework.exceptions.CitrusRuntimeException; +import org.citrusframework.spi.Resources; +import org.citrusframework.http.actions.HttpActionBuilder; +import org.citrusframework.http.actions.HttpClientRequestActionBuilder; +import org.citrusframework.http.actions.HttpClientRequestActionBuilder.HttpMessageBuilderSupport; +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; + + +@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen") +public class StoreApi implements GeneratedApi +{ + + public static final StoreApi INSTANCE = new StoreApi(); + + public String getApiTitle() { + return "OpenAPI Petstore"; + } + + public String getApiVersion() { + return "1.0.0"; + } + + public String getApiPrefix() { + return "PetStore"; + } + + public Map getApiInfoExtensions() { + Map infoExtensionMap = new HashMap<>(); + infoExtensionMap.put("x-citrus-api-name", "petstore"); + infoExtensionMap.put("x-citrus-app", "PETS"); + return infoExtensionMap; + } + + /** deleteOrder (DELETE /store/order/{order_id}) + Delete purchase order by ID + + **/ + public static class DeleteOrderRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest { + + private static final String ENDPOINT = "/store/order/{order_id}"; + private final Logger coverageLogger = LoggerFactory.getLogger(DeleteOrderRequest.class); + + private String orderId; + + + public DeleteOrderRequest() { + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("PetStore".toLowerCase() + ":deleteOrderRequestType"); + } + + public String getOperationName() { + return "deleteOrder"; + } + + public String getMethod() { + return "DELETE"; + } + + public String getPath() { + return "/store/order/{order_id}"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send() + .delete(replacePathParams(ENDPOINT)); + + HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport(); + messageBuilderSupport.accept(responseAcceptType); + + if (cookies != null) { + cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + } + + if (headers != null) { + headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + headers.forEach(messageBuilderSupport::header); + } + + String bodyLog = ""; + String payload = null; + String payloadType = null; + if (StringUtils.isNotBlank(this.bodyFile)) { + try { + payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset()); + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read payload resource", e); + } + payloadType = this.bodyContentType; + } else if (StringUtils.isNotBlank(this.bodyLiteral)) { + payload = this.bodyLiteral; + payloadType = this.bodyLiteralContentType; + } + String body = ""; + String bodyType = ""; + if(payload != null && payloadType != null) { + messageBuilderSupport.body(payload).contentType(payloadType); + body = context.replaceDynamicContentInString(payload); + bodyType = context.replaceDynamicContentInString(payloadType); + } + + bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\""; + + Map queryParams = new HashMap<>(); + + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver()); + httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder); + + httpClientRequestActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "deleteOrder;DELETE;\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + bodyLog); + } + + public void setOrderId(String orderId) { + this.orderId = orderId; + } + + private String replacePathParams(String endpoint) { + endpoint = endpoint.replace("{" + "order_id" + "}", orderId); + return endpoint; + } + } + /** getInventory (GET /store/inventory) + Returns pet inventories by status + + **/ + public static class GetInventoryRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest { + + private static final String ENDPOINT = "/store/inventory"; + private final Logger coverageLogger = LoggerFactory.getLogger(GetInventoryRequest.class); + + + public GetInventoryRequest() { + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("PetStore".toLowerCase() + ":getInventoryRequestType"); + } + + public String getOperationName() { + return "getInventory"; + } + + public String getMethod() { + return "GET"; + } + + public String getPath() { + return "/store/inventory"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send() + .get(replacePathParams(ENDPOINT)); + + HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport(); + messageBuilderSupport.accept(responseAcceptType); + + if (cookies != null) { + cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + } + + if (headers != null) { + headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + headers.forEach(messageBuilderSupport::header); + } + + String bodyLog = ""; + String payload = null; + String payloadType = null; + if (StringUtils.isNotBlank(this.bodyFile)) { + try { + payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset()); + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read payload resource", e); + } + payloadType = this.bodyContentType; + } else if (StringUtils.isNotBlank(this.bodyLiteral)) { + payload = this.bodyLiteral; + payloadType = this.bodyLiteralContentType; + } + String body = ""; + String bodyType = ""; + if(payload != null && payloadType != null) { + messageBuilderSupport.body(payload).contentType(payloadType); + body = context.replaceDynamicContentInString(payload); + bodyType = context.replaceDynamicContentInString(payloadType); + } + + bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\""; + + Map queryParams = new HashMap<>(); + + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver()); + httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder); + + httpClientRequestActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "getInventory;GET;\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + bodyLog); + } + + private String replacePathParams(String endpoint) { + + return endpoint; + } + } + /** getOrderById (GET /store/order/{order_id}) + Find purchase order by ID + + **/ + public static class GetOrderByIdRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest { + + private static final String ENDPOINT = "/store/order/{order_id}"; + private final Logger coverageLogger = LoggerFactory.getLogger(GetOrderByIdRequest.class); + + private String orderId; + + + public GetOrderByIdRequest() { + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("PetStore".toLowerCase() + ":getOrderByIdRequestType"); + } + + public String getOperationName() { + return "getOrderById"; + } + + public String getMethod() { + return "GET"; + } + + public String getPath() { + return "/store/order/{order_id}"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send() + .get(replacePathParams(ENDPOINT)); + + HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport(); + messageBuilderSupport.accept(responseAcceptType); + + if (cookies != null) { + cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + } + + if (headers != null) { + headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + headers.forEach(messageBuilderSupport::header); + } + + String bodyLog = ""; + String payload = null; + String payloadType = null; + if (StringUtils.isNotBlank(this.bodyFile)) { + try { + payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset()); + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read payload resource", e); + } + payloadType = this.bodyContentType; + } else if (StringUtils.isNotBlank(this.bodyLiteral)) { + payload = this.bodyLiteral; + payloadType = this.bodyLiteralContentType; + } + String body = ""; + String bodyType = ""; + if(payload != null && payloadType != null) { + messageBuilderSupport.body(payload).contentType(payloadType); + body = context.replaceDynamicContentInString(payload); + bodyType = context.replaceDynamicContentInString(payloadType); + } + + bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\""; + + Map queryParams = new HashMap<>(); + + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver()); + httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder); + + httpClientRequestActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "getOrderById;GET;\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + bodyLog); + } + + public void setOrderId(String orderId) { + this.orderId = orderId; + } + + private String replacePathParams(String endpoint) { + endpoint = endpoint.replace("{" + "order_id" + "}", orderId); + return endpoint; + } + } + /** placeOrder (POST /store/order) + Place an order for a pet + + **/ + public static class PlaceOrderRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest { + + private static final String ENDPOINT = "/store/order"; + private final Logger coverageLogger = LoggerFactory.getLogger(PlaceOrderRequest.class); + + + public PlaceOrderRequest() { + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("PetStore".toLowerCase() + ":placeOrderRequestType"); + } + + public String getOperationName() { + return "placeOrder"; + } + + public String getMethod() { + return "POST"; + } + + public String getPath() { + return "/store/order"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send() + .post(replacePathParams(ENDPOINT)); + + HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport(); + messageBuilderSupport.accept(responseAcceptType); + + if (cookies != null) { + cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + } + + if (headers != null) { + headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + headers.forEach(messageBuilderSupport::header); + } + + String bodyLog = ""; + String payload = null; + String payloadType = null; + if (StringUtils.isNotBlank(this.bodyFile)) { + try { + payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset()); + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read payload resource", e); + } + payloadType = this.bodyContentType; + } else if (StringUtils.isNotBlank(this.bodyLiteral)) { + payload = this.bodyLiteral; + payloadType = this.bodyLiteralContentType; + } + String body = ""; + String bodyType = ""; + if(payload != null && payloadType != null) { + messageBuilderSupport.body(payload).contentType(payloadType); + body = context.replaceDynamicContentInString(payload); + bodyType = context.replaceDynamicContentInString(payloadType); + } + + bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\""; + + Map queryParams = new HashMap<>(); + + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver()); + httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder); + + httpClientRequestActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "placeOrder;POST;\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + bodyLog); + } + + private String replacePathParams(String endpoint) { + + return endpoint; + } + } +} 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 new file mode 100644 index 0000000000..01f4558954 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/request/UserApi.java @@ -0,0 +1,819 @@ +/** + * ================================================== + * GENERATED CLASS, ALL CHANGES WILL BE LOST + * ================================================== + */ + +package org.citrusframework.openapi.generator.rest.petstore.request; + +import jakarta.annotation.Generated; +import org.citrusframework.testapi.GeneratedApi; +import org.citrusframework.testapi.GeneratedApiRequest; +import jakarta.servlet.http.Cookie; +import org.apache.commons.lang3.StringUtils; +import org.citrusframework.context.TestContext; +import org.citrusframework.exceptions.CitrusRuntimeException; +import org.citrusframework.spi.Resources; +import org.citrusframework.http.actions.HttpActionBuilder; +import org.citrusframework.http.actions.HttpClientRequestActionBuilder; +import org.citrusframework.http.actions.HttpClientRequestActionBuilder.HttpMessageBuilderSupport; +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; + + +@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen") +public class UserApi implements GeneratedApi +{ + + public static final UserApi INSTANCE = new UserApi(); + + public String getApiTitle() { + return "OpenAPI Petstore"; + } + + public String getApiVersion() { + return "1.0.0"; + } + + public String getApiPrefix() { + return "PetStore"; + } + + public Map getApiInfoExtensions() { + Map infoExtensionMap = new HashMap<>(); + infoExtensionMap.put("x-citrus-api-name", "petstore"); + infoExtensionMap.put("x-citrus-app", "PETS"); + return infoExtensionMap; + } + + /** createUser (POST /user) + Create user + + **/ + public static class CreateUserRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest { + + private static final String ENDPOINT = "/user"; + private final Logger coverageLogger = LoggerFactory.getLogger(CreateUserRequest.class); + + + public CreateUserRequest() { + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("PetStore".toLowerCase() + ":createUserRequestType"); + } + + public String getOperationName() { + return "createUser"; + } + + public String getMethod() { + return "POST"; + } + + public String getPath() { + return "/user"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send() + .post(replacePathParams(ENDPOINT)); + + HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport(); + messageBuilderSupport.accept(responseAcceptType); + + if (cookies != null) { + cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + } + + if (headers != null) { + headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + headers.forEach(messageBuilderSupport::header); + } + + String bodyLog = ""; + String payload = null; + String payloadType = null; + if (StringUtils.isNotBlank(this.bodyFile)) { + try { + payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset()); + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read payload resource", e); + } + payloadType = this.bodyContentType; + } else if (StringUtils.isNotBlank(this.bodyLiteral)) { + payload = this.bodyLiteral; + payloadType = this.bodyLiteralContentType; + } + String body = ""; + String bodyType = ""; + if(payload != null && payloadType != null) { + messageBuilderSupport.body(payload).contentType(payloadType); + body = context.replaceDynamicContentInString(payload); + bodyType = context.replaceDynamicContentInString(payloadType); + } + + bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\""; + + Map queryParams = new HashMap<>(); + + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver()); + httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder); + + httpClientRequestActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "createUser;POST;\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + bodyLog); + } + + private String replacePathParams(String endpoint) { + + return endpoint; + } + } + /** createUsersWithArrayInput (POST /user/createWithArray) + Creates list of users with given input array + + **/ + public static class CreateUsersWithArrayInputRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest { + + private static final String ENDPOINT = "/user/createWithArray"; + private final Logger coverageLogger = LoggerFactory.getLogger(CreateUsersWithArrayInputRequest.class); + + + public CreateUsersWithArrayInputRequest() { + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("PetStore".toLowerCase() + ":createUsersWithArrayInputRequestType"); + } + + public String getOperationName() { + return "createUsersWithArrayInput"; + } + + public String getMethod() { + return "POST"; + } + + public String getPath() { + return "/user/createWithArray"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send() + .post(replacePathParams(ENDPOINT)); + + HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport(); + messageBuilderSupport.accept(responseAcceptType); + + if (cookies != null) { + cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + } + + if (headers != null) { + headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + headers.forEach(messageBuilderSupport::header); + } + + String bodyLog = ""; + String payload = null; + String payloadType = null; + if (StringUtils.isNotBlank(this.bodyFile)) { + try { + payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset()); + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read payload resource", e); + } + payloadType = this.bodyContentType; + } else if (StringUtils.isNotBlank(this.bodyLiteral)) { + payload = this.bodyLiteral; + payloadType = this.bodyLiteralContentType; + } + String body = ""; + String bodyType = ""; + if(payload != null && payloadType != null) { + messageBuilderSupport.body(payload).contentType(payloadType); + body = context.replaceDynamicContentInString(payload); + bodyType = context.replaceDynamicContentInString(payloadType); + } + + bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\""; + + Map queryParams = new HashMap<>(); + + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver()); + httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder); + + httpClientRequestActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "createUsersWithArrayInput;POST;\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + bodyLog); + } + + private String replacePathParams(String endpoint) { + + return endpoint; + } + } + /** createUsersWithListInput (POST /user/createWithList) + Creates list of users with given input array + + **/ + public static class CreateUsersWithListInputRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest { + + private static final String ENDPOINT = "/user/createWithList"; + private final Logger coverageLogger = LoggerFactory.getLogger(CreateUsersWithListInputRequest.class); + + + public CreateUsersWithListInputRequest() { + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("PetStore".toLowerCase() + ":createUsersWithListInputRequestType"); + } + + public String getOperationName() { + return "createUsersWithListInput"; + } + + public String getMethod() { + return "POST"; + } + + public String getPath() { + return "/user/createWithList"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send() + .post(replacePathParams(ENDPOINT)); + + HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport(); + messageBuilderSupport.accept(responseAcceptType); + + if (cookies != null) { + cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + } + + if (headers != null) { + headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + headers.forEach(messageBuilderSupport::header); + } + + String bodyLog = ""; + String payload = null; + String payloadType = null; + if (StringUtils.isNotBlank(this.bodyFile)) { + try { + payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset()); + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read payload resource", e); + } + payloadType = this.bodyContentType; + } else if (StringUtils.isNotBlank(this.bodyLiteral)) { + payload = this.bodyLiteral; + payloadType = this.bodyLiteralContentType; + } + String body = ""; + String bodyType = ""; + if(payload != null && payloadType != null) { + messageBuilderSupport.body(payload).contentType(payloadType); + body = context.replaceDynamicContentInString(payload); + bodyType = context.replaceDynamicContentInString(payloadType); + } + + bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\""; + + Map queryParams = new HashMap<>(); + + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver()); + httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder); + + httpClientRequestActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "createUsersWithListInput;POST;\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + bodyLog); + } + + private String replacePathParams(String endpoint) { + + return endpoint; + } + } + /** deleteUser (DELETE /user/{username}) + Delete user + + **/ + public static class DeleteUserRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest { + + private static final String ENDPOINT = "/user/{username}"; + private final Logger coverageLogger = LoggerFactory.getLogger(DeleteUserRequest.class); + + private String username; + + + public DeleteUserRequest() { + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("PetStore".toLowerCase() + ":deleteUserRequestType"); + } + + public String getOperationName() { + return "deleteUser"; + } + + public String getMethod() { + return "DELETE"; + } + + public String getPath() { + return "/user/{username}"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send() + .delete(replacePathParams(ENDPOINT)); + + HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport(); + messageBuilderSupport.accept(responseAcceptType); + + if (cookies != null) { + cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + } + + if (headers != null) { + headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + headers.forEach(messageBuilderSupport::header); + } + + String bodyLog = ""; + String payload = null; + String payloadType = null; + if (StringUtils.isNotBlank(this.bodyFile)) { + try { + payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset()); + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read payload resource", e); + } + payloadType = this.bodyContentType; + } else if (StringUtils.isNotBlank(this.bodyLiteral)) { + payload = this.bodyLiteral; + payloadType = this.bodyLiteralContentType; + } + String body = ""; + String bodyType = ""; + if(payload != null && payloadType != null) { + messageBuilderSupport.body(payload).contentType(payloadType); + body = context.replaceDynamicContentInString(payload); + bodyType = context.replaceDynamicContentInString(payloadType); + } + + bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\""; + + Map queryParams = new HashMap<>(); + + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver()); + httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder); + + httpClientRequestActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "deleteUser;DELETE;\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + bodyLog); + } + + public void setUsername(String username) { + this.username = username; + } + + private String replacePathParams(String endpoint) { + endpoint = endpoint.replace("{" + "username" + "}", username); + return endpoint; + } + } + /** getUserByName (GET /user/{username}) + Get user by user name + + **/ + public static class GetUserByNameRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest { + + private static final String ENDPOINT = "/user/{username}"; + private final Logger coverageLogger = LoggerFactory.getLogger(GetUserByNameRequest.class); + + private String username; + + + public GetUserByNameRequest() { + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("PetStore".toLowerCase() + ":getUserByNameRequestType"); + } + + public String getOperationName() { + return "getUserByName"; + } + + public String getMethod() { + return "GET"; + } + + public String getPath() { + return "/user/{username}"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send() + .get(replacePathParams(ENDPOINT)); + + HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport(); + messageBuilderSupport.accept(responseAcceptType); + + if (cookies != null) { + cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + } + + if (headers != null) { + headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + headers.forEach(messageBuilderSupport::header); + } + + String bodyLog = ""; + String payload = null; + String payloadType = null; + if (StringUtils.isNotBlank(this.bodyFile)) { + try { + payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset()); + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read payload resource", e); + } + payloadType = this.bodyContentType; + } else if (StringUtils.isNotBlank(this.bodyLiteral)) { + payload = this.bodyLiteral; + payloadType = this.bodyLiteralContentType; + } + String body = ""; + String bodyType = ""; + if(payload != null && payloadType != null) { + messageBuilderSupport.body(payload).contentType(payloadType); + body = context.replaceDynamicContentInString(payload); + bodyType = context.replaceDynamicContentInString(payloadType); + } + + bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\""; + + Map queryParams = new HashMap<>(); + + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver()); + httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder); + + httpClientRequestActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "getUserByName;GET;\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + bodyLog); + } + + public void setUsername(String username) { + this.username = username; + } + + private String replacePathParams(String endpoint) { + endpoint = endpoint.replace("{" + "username" + "}", username); + return endpoint; + } + } + /** loginUser (GET /user/login) + Logs user into the system + + **/ + public static class LoginUserRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest { + + private static final String ENDPOINT = "/user/login"; + private final Logger coverageLogger = LoggerFactory.getLogger(LoginUserRequest.class); + + private String username; + + private String password; + + + public LoginUserRequest() { + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("PetStore".toLowerCase() + ":loginUserRequestType"); + } + + public String getOperationName() { + return "loginUser"; + } + + public String getMethod() { + return "GET"; + } + + public String getPath() { + return "/user/login"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send() + .get(replacePathParams(ENDPOINT)); + + HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport(); + messageBuilderSupport.accept(responseAcceptType); + + if (cookies != null) { + cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + } + + if (headers != null) { + headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + headers.forEach(messageBuilderSupport::header); + } + + String bodyLog = ""; + String payload = null; + String payloadType = null; + if (StringUtils.isNotBlank(this.bodyFile)) { + try { + payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset()); + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read payload resource", e); + } + payloadType = this.bodyContentType; + } else if (StringUtils.isNotBlank(this.bodyLiteral)) { + payload = this.bodyLiteral; + payloadType = this.bodyLiteralContentType; + } + String body = ""; + String bodyType = ""; + if(payload != null && payloadType != null) { + messageBuilderSupport.body(payload).contentType(payloadType); + body = context.replaceDynamicContentInString(payload); + bodyType = context.replaceDynamicContentInString(payloadType); + } + + bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\""; + + Map queryParams = new HashMap<>(); + + + if (StringUtils.isNotBlank(this.username)) { + queryParams.put("username", context.replaceDynamicContentInString(this.username)); + httpClientRequestActionBuilder.queryParam("username", this.username); + } + + + if (StringUtils.isNotBlank(this.password)) { + queryParams.put("password", context.replaceDynamicContentInString(this.password)); + httpClientRequestActionBuilder.queryParam("password", this.password); + } + + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver()); + httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder); + + httpClientRequestActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "loginUser;GET;\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + bodyLog); + } + + public void setUsername(String username) { + this.username = username; + } + + public void setPassword(String password) { + this.password = password; + } + + private String replacePathParams(String endpoint) { + + return endpoint; + } + } + /** logoutUser (GET /user/logout) + Logs out current logged in user session + + **/ + public static class LogoutUserRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest { + + private static final String ENDPOINT = "/user/logout"; + private final Logger coverageLogger = LoggerFactory.getLogger(LogoutUserRequest.class); + + + public LogoutUserRequest() { + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("PetStore".toLowerCase() + ":logoutUserRequestType"); + } + + public String getOperationName() { + return "logoutUser"; + } + + public String getMethod() { + return "GET"; + } + + public String getPath() { + return "/user/logout"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send() + .get(replacePathParams(ENDPOINT)); + + HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport(); + messageBuilderSupport.accept(responseAcceptType); + + if (cookies != null) { + cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + } + + if (headers != null) { + headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + headers.forEach(messageBuilderSupport::header); + } + + String bodyLog = ""; + String payload = null; + String payloadType = null; + if (StringUtils.isNotBlank(this.bodyFile)) { + try { + payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset()); + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read payload resource", e); + } + payloadType = this.bodyContentType; + } else if (StringUtils.isNotBlank(this.bodyLiteral)) { + payload = this.bodyLiteral; + payloadType = this.bodyLiteralContentType; + } + String body = ""; + String bodyType = ""; + if(payload != null && payloadType != null) { + messageBuilderSupport.body(payload).contentType(payloadType); + body = context.replaceDynamicContentInString(payload); + bodyType = context.replaceDynamicContentInString(payloadType); + } + + bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\""; + + Map queryParams = new HashMap<>(); + + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver()); + httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder); + + httpClientRequestActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "logoutUser;GET;\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + bodyLog); + } + + private String replacePathParams(String endpoint) { + + return endpoint; + } + } + /** updateUser (PUT /user/{username}) + Updated user + + **/ + public static class UpdateUserRequest extends PetStoreAbstractTestRequest implements GeneratedApiRequest { + + private static final String ENDPOINT = "/user/{username}"; + private final Logger coverageLogger = LoggerFactory.getLogger(UpdateUserRequest.class); + + private String username; + + + public UpdateUserRequest() { + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("PetStore".toLowerCase() + ":updateUserRequestType"); + } + + public String getOperationName() { + return "updateUser"; + } + + public String getMethod() { + return "PUT"; + } + + public String getPath() { + return "/user/{username}"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send() + .put(replacePathParams(ENDPOINT)); + + HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport(); + messageBuilderSupport.accept(responseAcceptType); + + if (cookies != null) { + cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + } + + if (headers != null) { + headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v))); + headers.forEach(messageBuilderSupport::header); + } + + String bodyLog = ""; + String payload = null; + String payloadType = null; + if (StringUtils.isNotBlank(this.bodyFile)) { + try { + payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset()); + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read payload resource", e); + } + payloadType = this.bodyContentType; + } else if (StringUtils.isNotBlank(this.bodyLiteral)) { + payload = this.bodyLiteral; + payloadType = this.bodyLiteralContentType; + } + String body = ""; + String bodyType = ""; + if(payload != null && payloadType != null) { + messageBuilderSupport.body(payload).contentType(payloadType); + body = context.replaceDynamicContentInString(payload); + bodyType = context.replaceDynamicContentInString(payloadType); + } + + bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\""; + + Map queryParams = new HashMap<>(); + + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver()); + httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder); + + httpClientRequestActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "updateUser;PUT;\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + bodyLog); + } + + public void setUsername(String username) { + this.username = username; + } + + private String replacePathParams(String endpoint) { + endpoint = endpoint.replace("{" + "username" + "}", username); + return endpoint; + } + } +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/spring/PetStoreBeanConfiguration.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/spring/PetStoreBeanConfiguration.java new file mode 100644 index 0000000000..cb89458c0b --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/rest/petstore/spring/PetStoreBeanConfiguration.java @@ -0,0 +1,142 @@ +/** + * ================================================== + * GENERATED CLASS, ALL CHANGES WILL BE LOST + * ================================================== + */ + +package org.citrusframework.openapi.generator.rest.petstore.spring; + +import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE; + +import org.citrusframework.openapi.generator.rest.petstore.request.PetApi; +import org.citrusframework.openapi.generator.rest.petstore.request.StoreApi; +import org.citrusframework.openapi.generator.rest.petstore.request.UserApi; +import javax.annotation.processing.Generated; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; + +@Configuration +@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen") +public class PetStoreBeanConfiguration { + + @Bean + @Scope(SCOPE_PROTOTYPE) + public PetApi.AddPetRequest addPetRequest() { + return new PetApi.AddPetRequest(); + } + + @Bean + @Scope(SCOPE_PROTOTYPE) + public PetApi.DeletePetRequest deletePetRequest() { + return new PetApi.DeletePetRequest(); + } + + @Bean + @Scope(SCOPE_PROTOTYPE) + public PetApi.FindPetsByStatusRequest findPetsByStatusRequest() { + return new PetApi.FindPetsByStatusRequest(); + } + + @Bean + @Scope(SCOPE_PROTOTYPE) + public PetApi.FindPetsByTagsRequest findPetsByTagsRequest() { + return new PetApi.FindPetsByTagsRequest(); + } + + @Bean + @Scope(SCOPE_PROTOTYPE) + public PetApi.GetPetByIdRequest getPetByIdRequest() { + return new PetApi.GetPetByIdRequest(); + } + + @Bean + @Scope(SCOPE_PROTOTYPE) + public PetApi.UpdatePetRequest updatePetRequest() { + return new PetApi.UpdatePetRequest(); + } + + @Bean + @Scope(SCOPE_PROTOTYPE) + public PetApi.UpdatePetWithFormRequest updatePetWithFormRequest() { + return new PetApi.UpdatePetWithFormRequest(); + } + + @Bean + @Scope(SCOPE_PROTOTYPE) + public PetApi.UploadFileRequest uploadFileRequest() { + return new PetApi.UploadFileRequest(); + } + + @Bean + @Scope(SCOPE_PROTOTYPE) + public StoreApi.DeleteOrderRequest deleteOrderRequest() { + return new StoreApi.DeleteOrderRequest(); + } + + @Bean + @Scope(SCOPE_PROTOTYPE) + public StoreApi.GetInventoryRequest getInventoryRequest() { + return new StoreApi.GetInventoryRequest(); + } + + @Bean + @Scope(SCOPE_PROTOTYPE) + public StoreApi.GetOrderByIdRequest getOrderByIdRequest() { + return new StoreApi.GetOrderByIdRequest(); + } + + @Bean + @Scope(SCOPE_PROTOTYPE) + public StoreApi.PlaceOrderRequest placeOrderRequest() { + return new StoreApi.PlaceOrderRequest(); + } + + @Bean + @Scope(SCOPE_PROTOTYPE) + public UserApi.CreateUserRequest createUserRequest() { + return new UserApi.CreateUserRequest(); + } + + @Bean + @Scope(SCOPE_PROTOTYPE) + public UserApi.CreateUsersWithArrayInputRequest createUsersWithArrayInputRequest() { + return new UserApi.CreateUsersWithArrayInputRequest(); + } + + @Bean + @Scope(SCOPE_PROTOTYPE) + public UserApi.CreateUsersWithListInputRequest createUsersWithListInputRequest() { + return new UserApi.CreateUsersWithListInputRequest(); + } + + @Bean + @Scope(SCOPE_PROTOTYPE) + public UserApi.DeleteUserRequest deleteUserRequest() { + return new UserApi.DeleteUserRequest(); + } + + @Bean + @Scope(SCOPE_PROTOTYPE) + public UserApi.GetUserByNameRequest getUserByNameRequest() { + return new UserApi.GetUserByNameRequest(); + } + + @Bean + @Scope(SCOPE_PROTOTYPE) + public UserApi.LoginUserRequest loginUserRequest() { + return new UserApi.LoginUserRequest(); + } + + @Bean + @Scope(SCOPE_PROTOTYPE) + public UserApi.LogoutUserRequest logoutUserRequest() { + return new UserApi.LogoutUserRequest(); + } + + @Bean + @Scope(SCOPE_PROTOTYPE) + public UserApi.UpdateUserRequest updateUserRequest() { + return new UserApi.UpdateUserRequest(); + } +} 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 new file mode 100644 index 0000000000..8acda64ca3 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/citrus/OpenApiFromWsdlAbstractTestRequest.java @@ -0,0 +1,187 @@ +/** + * ================================================== + * GENERATED CLASS, ALL CHANGES WILL BE LOST + * ================================================== + */ + +package org.citrusframework.openapi.generator.soap.bookservice.citrus; + +import jakarta.annotation.Generated; +import java.util.List; +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; +import org.citrusframework.validation.DelegatingPayloadVariableExtractor; +import org.citrusframework.validation.PathExpressionValidationContext; +import org.citrusframework.validation.script.ScriptValidationContext; +import org.citrusframework.ws.actions.ReceiveSoapMessageAction; +import org.citrusframework.ws.actions.ReceiveSoapMessageAction.SoapMessageBuilderSupport; +import org.citrusframework.ws.actions.SendSoapMessageAction; +import org.citrusframework.ws.actions.SoapActionBuilder; +import org.citrusframework.ws.client.WebServiceClient; +import org.slf4j.Marker; +import org.slf4j.MarkerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.util.CollectionUtils; + +import javax.sql.DataSource; +import java.util.Map; + +@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen") +public abstract class OpenApiFromWsdlAbstractTestRequest extends AbstractTestAction { + + protected final Marker coverageMarker = MarkerFactory.getMarker("OPENAPIFROMWSDL-API-COVERAGE"); + + @Autowired + @Qualifier("soapSampleEndpoint") + protected WebServiceClient wsClient; + + @Autowired(required = false) + protected DataSource dataSource; + + @Autowired(required = false) + private List actionBuilderCustomizerServices; + + // attributes of differentNodes + protected String bodyContentType; + protected String bodyLiteralContentType; + protected String bodyFile; + protected String bodyLiteral; + + // children of response element + protected String resource; + protected Map responseVariable; // Contains the 'XPATH' as key and the 'VARIABLE NAME' as value + protected Map responseValue; // Contains the 'XPATH' as key and the 'VALUE TO BE VALIDATED' as value + protected String script; + protected String type; // default script type is groovy - supported types see com.consol.citrus.script.ScriptTypes + protected Map soapHeaders; + protected Map mimeHeaders; + + @Override + public void doExecute(TestContext context) { + sendRequest(context); + receiveResponse(context); + } + + /** + * This method receives the HTTP-Response + */ + public void receiveResponse(TestContext context) { + + ReceiveSoapMessageAction.Builder soapReceiveMessageActionBuilder = new SoapActionBuilder().client(wsClient).receive(); + SoapMessageBuilderSupport messageBuilderSupport = soapReceiveMessageActionBuilder.getMessageBuilderSupport(); + + if (resource != null) { + messageBuilderSupport.body(Resources.create(resource)); + } + + if (!CollectionUtils.isEmpty(responseVariable)) { + DelegatingPayloadVariableExtractor.Builder extractorBuilder = new DelegatingPayloadVariableExtractor.Builder(); + responseVariable.forEach(extractorBuilder::expression); + messageBuilderSupport.extract(extractorBuilder); + } + + if (!CollectionUtils.isEmpty(responseValue)) { + PathExpressionValidationContext.Builder validationContextBuilder = new PathExpressionValidationContext.Builder(); + responseValue.forEach(validationContextBuilder::expression); + messageBuilderSupport.validate(validationContextBuilder); + } + + if (script != null) { + ScriptValidationContext.Builder scriptValidationContextBuilder = new ScriptValidationContext.Builder(); + if (type != null) { + scriptValidationContextBuilder.scriptType(type); + } + scriptValidationContextBuilder.script(script); + messageBuilderSupport.validate(scriptValidationContextBuilder); + } + + soapReceiveMessageActionBuilder.withReferenceResolver(context.getReferenceResolver()); + soapReceiveMessageActionBuilder.build().execute(context); + } + + public abstract void sendRequest(TestContext context); + + public void setBodyLiteral(String bodyLiteral) { + this.bodyLiteral = bodyLiteral; + } + public void setBodyContentType(String bodyContentType) { + this.bodyContentType = bodyContentType; + } + + public void setBodyLiteralContentType(String bodyLiteralContentType) { + this.bodyLiteralContentType = bodyLiteralContentType; + } + + public void setBodyFile(String bodyFile) { + this.bodyFile = bodyFile; + } + + public void setResource(String resource) { + this.resource = resource; + } + + public void setResponseVariable(Map responseVariable) { + this.responseVariable = responseVariable; + } + + public void setResponseValue(Map responseValue) { + this.responseValue = responseValue; + } + + public void setScript(String script) { + this.script = script; + } + + public void setType(String type) { + this.type = type; + } + + public void setSoapHeader(Map soapHeaders) { + this.soapHeaders = soapHeaders; + } + + public void setMimeHeader(Map mimeHeaders) { + this.mimeHeaders = mimeHeaders; + } + + protected SendSoapMessageAction.Builder customizeBuilder(GeneratedApi generatedApi, + TestContext context, SendSoapMessageAction.Builder sendSoapMessageActionBuilder) { + + sendSoapMessageActionBuilder = customizeByBeans(generatedApi, context, sendSoapMessageActionBuilder); + + sendSoapMessageActionBuilder = customizeBySpi(generatedApi, context, sendSoapMessageActionBuilder); + + return sendSoapMessageActionBuilder; + } + + private SendSoapMessageAction.Builder customizeBySpi(GeneratedApi generatedApi, TestContext context, + SendSoapMessageAction.Builder sendSoapMessageActionBuilder) { + + ServiceLoader serviceLoader = ServiceLoader.load( + ApiActionBuilderCustomizerService.class, ApiActionBuilderCustomizerService.class.getClassLoader()); + for (ApiActionBuilderCustomizerService service :serviceLoader) { + sendSoapMessageActionBuilder = service.build(generatedApi, this, context, sendSoapMessageActionBuilder); + } + + return sendSoapMessageActionBuilder; + } + + private SendSoapMessageAction.Builder customizeByBeans( + GeneratedApi generatedApi, TestContext context, SendSoapMessageAction.Builder sendSoapMessageActionBuilder) { + + if (actionBuilderCustomizerServices != null) { + for (ApiActionBuilderCustomizerService apiActionBuilderCustomizer : actionBuilderCustomizerServices) { + sendSoapMessageActionBuilder = apiActionBuilderCustomizer.build(generatedApi, this, + context, sendSoapMessageActionBuilder); + } + } + + return sendSoapMessageActionBuilder; + } +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/citrus/OpenApiFromWsdlBeanDefinitionParser.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/citrus/OpenApiFromWsdlBeanDefinitionParser.java new file mode 100644 index 0000000000..1c72d58016 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/citrus/OpenApiFromWsdlBeanDefinitionParser.java @@ -0,0 +1,215 @@ +/** + * ================================================== + * GENERATED CLASS, ALL CHANGES WILL BE LOST + * ================================================== + */ + +package org.citrusframework.openapi.generator.soap.bookservice.citrus; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.annotation.processing.Generated; + +import org.citrusframework.exceptions.CitrusRuntimeException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.core.Conventions; +import org.springframework.util.Assert; +import org.apache.commons.lang3.StringUtils; +import org.springframework.util.xml.DomUtils; +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + + +@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen") +public class OpenApiFromWsdlBeanDefinitionParser implements BeanDefinitionParser { + + private static final String COOKIE = "cookie"; + private static final String HEADER = "header"; + private static final String SOAP_HEADER = "soapHeader"; + private static final String MIME_HEADER = "mimeHeader"; + private static final String NAME = "name"; + private static final String REQUEST_BODY = "body"; + private static final String REQUEST_BODY_LITERAL = "bodyLiteral"; + private static final String MULTIPART_BODY = "multipartBody"; + private static final String RESPONSE = "response"; + private static final String RESPONSE_JSONPATH = "json-path"; + private static final String RESPONSE_XPATH = "xpath"; + private static final String EXPRESSION = "expression"; + private static final String VALUE = "value"; + private static final String RESPONSE_RESOURCE = "resource"; + private static final String FILE = "file"; + private static final String RESPONSE_VARIABLE = "responseVariable"; + private static final String RESPONSE_VALUE = "responseValue"; + private static final String SCRIPT = "script"; + private static final String TYPE = "type"; + private static final String SQL = "sql"; + private static final String COLUMN = "column"; + private static final String VARIABLE = "variable"; + // new + private static final String SCHEMA = "schema"; + // new + private static final String SCHEMA_VALIDATION = "schemaValidation"; + + private final Class beanClass; + + public OpenApiFromWsdlBeanDefinitionParser(Class beanClass) { + this.beanClass = beanClass; + } + + public BeanDefinition parse(Element element) { + return parse(element, null); + } + + /** + * Note: The {@link OpenApiFromWsdlBeanDefinitionParser#parse(Element element)} allows access direct + * access without the {@link org.springframework.beans.factory.xml.ParserContext} for convenience. + */ + @Override + public BeanDefinition parse(Element element, ParserContext parserContext) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(beanClass); + retrieveRootNodeAttributes(element, builder); + retrieveOptionalNodeAttributes(element, REQUEST_BODY, builder); + retrieveTextContentAndNodeAttributes(element, REQUEST_BODY_LITERAL, builder); + retrieveOptionalNodeAttributes(element, RESPONSE, builder); + retrieveParamNodeData(element, builder, COOKIE); + retrieveParamNodeData(element, builder, HEADER); + retrieveParamNodeData(element, builder, SOAP_HEADER); + retrieveParamNodeData(element, builder, MIME_HEADER); + retrieveOptionalNodeAttributes(element, SCHEMA, builder); + retrieveOptionalNodeAttributes(element, SCHEMA_VALIDATION, builder); + retrieveOptionalMultipartElements(element, builder); + retrieveResponseNodeData(element, builder); + builder.addPropertyValue("name", element.getTagName()); + return builder.getBeanDefinition(); + } + + private void retrieveOptionalMultipartElements(Element element, BeanDefinitionBuilder builder) { + var multipartBodyElement = DomUtils.getChildElementByTagName(element, MULTIPART_BODY); + if (multipartBodyElement != null) { + var multipartBodyChildElements = DomUtils.getChildElements(multipartBodyElement); + for(int i = 0; i < multipartBodyChildElements.size(); i++){ + var multipartBodyChildElement = multipartBodyChildElements.get(i); + String propertyName = Conventions.attributeNameToPropertyName(multipartBodyChildElement.getLocalName()); + builder.addPropertyValue(propertyName, multipartBodyChildElement.getTextContent()); + } + } + } + + private void retrieveRootNodeAttributes(Element element, BeanDefinitionBuilder builder) { + NamedNodeMap attributes = element.getAttributes(); + for (int x = 0; x < attributes.getLength(); x++) { + Attr attribute = (Attr) attributes.item(x); + String propertyName = Conventions.attributeNameToPropertyName(attribute.getLocalName()); + Assert.state(StringUtils.isNotBlank(propertyName), "Illegal property name returned, it must not be null or empty."); + builder.addPropertyValue(propertyName, attribute.getValue()); + } + } + + private void retrieveOptionalNodeAttributes(Element element, String elementName, BeanDefinitionBuilder builder) { + if (!DomUtils.getChildElementsByTagName(element, elementName).isEmpty()) { + Element el = DomUtils.getChildElementsByTagName(element, elementName).get(0); + NamedNodeMap attributes = el.getAttributes(); + for (int x = 0; x < attributes.getLength(); x++) { + Attr attribute = (Attr) attributes.item(x); + String propertyName = Conventions.attributeNameToPropertyName(attribute.getLocalName()); + Assert.state(StringUtils.isNotBlank(propertyName), "Illegal property name returned, it must not be null or empty."); + String variableName = el.getLocalName() + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1); + builder.addPropertyValue(variableName, attribute.getValue()); + } + } + } + + private void retrieveTextContentAndNodeAttributes(Element element, String elementName, BeanDefinitionBuilder builder) { + if (!DomUtils.getChildElementsByTagName(element, elementName).isEmpty()) { + Element el1 = DomUtils.getChildElementsByTagName(element, elementName).get(0); + NamedNodeMap attributes = el1.getAttributes(); + for (int x = 0; x < attributes.getLength(); x++) { + Attr attribute = (Attr) attributes.item(x); + String propertyName1 = Conventions.attributeNameToPropertyName(attribute.getLocalName()); + Assert.state(StringUtils.isNotBlank(propertyName1), "Illegal property name returned, it must not be null or empty."); + String variableName = el1.getLocalName() + propertyName1.substring(0, 1).toUpperCase() + propertyName1.substring(1); + builder.addPropertyValue(variableName, attribute.getValue()); + } + Element el = DomUtils.getChildElementsByTagName(element, elementName).get(0); + builder.addPropertyValue(elementName, el.getTextContent()); + } + } + + private void retrieveParamNodeData(Element element, BeanDefinitionBuilder builder, String paramType) { + if (!DomUtils.getChildElementsByTagName(element, paramType).isEmpty()) { + Map params = new HashMap<>(); + List elements = DomUtils.getChildElementsByTagName(element, paramType); + elements.forEach(e -> { + String name = e.getAttribute(NAME); + String value = e.getAttribute(VALUE); + + Assert.state(StringUtils.isNotBlank(name), "Illegal attribute value returned. The 'name' attribute must not be null or empty."); + Assert.state(StringUtils.isNotBlank(value), "Illegal attribute value returned. The 'value' attribute must not be null or empty."); + + params.put(name, value); + }); + builder.addPropertyValue(paramType, params); + } + } + + private void retrieveResponseNodeData(Element element, BeanDefinitionBuilder builder) { + + if (!DomUtils.getChildElementsByTagName(element, RESPONSE).isEmpty()) { + Element response = DomUtils.getChildElementsByTagName(element, RESPONSE).get(0); + List elements = DomUtils.getChildElements(response); + + Map responseVariable = new HashMap<>(); + Map responseValue = new HashMap<>(); + + for (int i = 0; i < elements.size(); i++) { + Element e = elements.get(i); + + if (e.getTagName().contains(RESPONSE_JSONPATH) || e.getTagName().contains(RESPONSE_XPATH)) { + String expression = e.getAttribute(EXPRESSION); + String value = e.getAttribute(VALUE); + + Assert.state(StringUtils.isNotBlank(expression), "Illegal attribute value returned. The 'expression' attribute must not be null or empty."); + Assert.state(StringUtils.isNotBlank(value), "Illegal attribute value returned. The 'value' attribute must not be null or empty."); + + // variable to save @variable('ebid')@ else value to validate + if (value.matches("\\@variable\\('.*'\\)\\@")) { + Matcher match = Pattern.compile("\\'(.*?)\\'").matcher(value); + if (match.find()) { + responseVariable.put(expression, value.substring(match.start() + 1, match.end() - 1)); + } + } else { + responseValue.put(expression, value); + } + } else if (e.getTagName().contains(SCRIPT)) { + String script = e.getTextContent(); + Assert.state(StringUtils.isNotBlank(script), "Illegal attribute value returned. The 'script' attribute must not be null or empty."); + builder.addPropertyValue(SCRIPT, script); + + if (!e.getAttribute(TYPE).isEmpty()) { + String type = e.getAttribute(TYPE); + Assert.state(StringUtils.isNotBlank(type), "Illegal attribute value returned. The 'type' attribute must not be null or empty."); + builder.addPropertyValue(TYPE, type); + } + } else if (e.getTagName().contains(RESPONSE_RESOURCE)) { + String filePath = e.getAttribute(FILE); + Assert.state(StringUtils.isNotBlank(filePath), "Illegal attribute value returned. The 'file' attribute must not be null or empty."); + builder.addPropertyValue(RESPONSE_RESOURCE, filePath); + } + + } + + builder.addPropertyValue(RESPONSE_VARIABLE, responseVariable); + builder.addPropertyValue(RESPONSE_VALUE, responseValue); + } + } +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/citrus/extension/OpenApiFromWsdlNamespaceHandler.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/citrus/extension/OpenApiFromWsdlNamespaceHandler.java new file mode 100644 index 0000000000..04773ddb99 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/citrus/extension/OpenApiFromWsdlNamespaceHandler.java @@ -0,0 +1,26 @@ +/** + * ================================================== + * GENERATED CLASS, ALL CHANGES WILL BE LOST + * ================================================== + */ + +package org.citrusframework.openapi.generator.soap.bookservice.citrus.extension; + +import org.citrusframework.openapi.generator.soap.bookservice.request.BookServiceSoapApi; +import org.citrusframework.openapi.generator.soap.bookservice.citrus.OpenApiFromWsdlBeanDefinitionParser; + +import javax.annotation.processing.Generated; + +import org.springframework.beans.factory.xml.NamespaceHandlerSupport; + + +@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen") +public class OpenApiFromWsdlNamespaceHandler extends NamespaceHandlerSupport { + + @Override + public void init() { + registerBeanDefinitionParser("addBookRequest", new OpenApiFromWsdlBeanDefinitionParser(BookServiceSoapApi.AddBookRequest.class)); + registerBeanDefinitionParser("getAllBooksRequest", new OpenApiFromWsdlBeanDefinitionParser(BookServiceSoapApi.GetAllBooksRequest.class)); + registerBeanDefinitionParser("getBookRequest", new OpenApiFromWsdlBeanDefinitionParser(BookServiceSoapApi.GetBookRequest.class)); + } +} 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 new file mode 100644 index 0000000000..dd84878034 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/request/BookServiceSoapApi.java @@ -0,0 +1,330 @@ +/** + * ================================================== + * GENERATED CLASS, ALL CHANGES WILL BE LOST + * ================================================== + */ + +package org.citrusframework.openapi.generator.soap.bookservice.request; + +import jakarta.annotation.Generated; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; +import org.citrusframework.context.TestContext; +import org.citrusframework.exceptions.CitrusRuntimeException; +import org.citrusframework.testapi.GeneratedApi; +import org.citrusframework.testapi.GeneratedApiRequest; +import org.citrusframework.openapi.generator.soap.bookservice.citrus.OpenApiFromWsdlAbstractTestRequest; +import org.citrusframework.spi.Resources; +import org.citrusframework.util.FileUtils; +import org.citrusframework.ws.actions.SendSoapMessageAction; +import org.citrusframework.ws.actions.SendSoapMessageAction.Builder.SendSoapMessageBuilderSupport; +import org.citrusframework.ws.actions.SoapActionBuilder; +import org.slf4j.Logger; +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 +{ + public static final BookServiceSoapApi INSTANCE = new BookServiceSoapApi(); + + public String getApiTitle() { + return "Generated api from wsdl"; + } + + public String getApiVersion() { + return "1.0.0"; + } + + public String getApiPrefix() { + return "OpenApiFromWsdl"; + } + + public Map getApiInfoExtensions() { + Map infoExtensionMap = new HashMap<>(); + return infoExtensionMap; + } + + /** + addBook (POST /AddBook) + + + **/ + public static class AddBookRequest extends OpenApiFromWsdlAbstractTestRequest implements GeneratedApiRequest { + + private final Logger coverageLogger = LoggerFactory.getLogger(AddBookRequest.class); + + // Query params + + + public AddBookRequest(){ + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("OpenApiFromWsdl".toLowerCase() + ":addBookRequestType"); + } + + public String getOperationName() { + return "addBook"; + } + + public String getMethod() { + return "POST"; + } + + public String getPath() { + return "/AddBook"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + + SendSoapMessageAction.Builder soapSendMessageActionBuilder = new SoapActionBuilder().client(wsClient).send(); + SendSoapMessageBuilderSupport messageBuilderSupport = soapSendMessageActionBuilder.getMessageBuilderSupport(); + + messageBuilderSupport.soapAction("addBook"); + + String payload = null; + String payloadType = null; + + if (StringUtils.isNotBlank(this.bodyFile)) { + try { + payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset()); + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read payload resource", e); + } + payloadType = this.bodyContentType; + } else if (StringUtils.isNotBlank(this.bodyLiteral)) { + payload = this.bodyLiteral; + payloadType = this.bodyLiteralContentType; + } + + String body = ""; + String bodyType = ""; + if(payload != null && payloadType != null) { + messageBuilderSupport.body(payload).contentType(payloadType); + body = context.replaceDynamicContentInString(payload); + bodyType = context.replaceDynamicContentInString(payloadType); + } + + if (!CollectionUtils.isEmpty(soapHeaders)) { + for (Entry entry : soapHeaders.entrySet()) { + messageBuilderSupport = messageBuilderSupport.header(entry.getKey(), + entry.getValue()); + } + } + + if (!CollectionUtils.isEmpty(mimeHeaders)) { + for (Entry entry : mimeHeaders.entrySet()) { + messageBuilderSupport = messageBuilderSupport.header("citrus_http_" + entry.getKey(), + entry.getValue()); + } + } + + Map queryParams = new HashMap<>(); + + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + soapSendMessageActionBuilder.withReferenceResolver(context.getReferenceResolver()); + soapSendMessageActionBuilder = customizeBuilder(INSTANCE, context, soapSendMessageActionBuilder); + + soapSendMessageActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "addBook;POST;\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\""); + } + + + } + /** + getAllBooks (POST /GetAllBooks) + + + **/ + public static class GetAllBooksRequest extends OpenApiFromWsdlAbstractTestRequest implements GeneratedApiRequest { + + private final Logger coverageLogger = LoggerFactory.getLogger(GetAllBooksRequest.class); + + // Query params + + + public GetAllBooksRequest(){ + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("OpenApiFromWsdl".toLowerCase() + ":getAllBooksRequestType"); + } + + public String getOperationName() { + return "getAllBooks"; + } + + public String getMethod() { + return "POST"; + } + + public String getPath() { + return "/GetAllBooks"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + + SendSoapMessageAction.Builder soapSendMessageActionBuilder = new SoapActionBuilder().client(wsClient).send(); + SendSoapMessageBuilderSupport messageBuilderSupport = soapSendMessageActionBuilder.getMessageBuilderSupport(); + + messageBuilderSupport.soapAction("getAllBooks"); + + String payload = null; + String payloadType = null; + + if (StringUtils.isNotBlank(this.bodyFile)) { + try { + payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset()); + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read payload resource", e); + } + payloadType = this.bodyContentType; + } else if (StringUtils.isNotBlank(this.bodyLiteral)) { + payload = this.bodyLiteral; + payloadType = this.bodyLiteralContentType; + } + + String body = ""; + String bodyType = ""; + if(payload != null && payloadType != null) { + messageBuilderSupport.body(payload).contentType(payloadType); + body = context.replaceDynamicContentInString(payload); + bodyType = context.replaceDynamicContentInString(payloadType); + } + + if (!CollectionUtils.isEmpty(soapHeaders)) { + for (Entry entry : soapHeaders.entrySet()) { + messageBuilderSupport = messageBuilderSupport.header(entry.getKey(), + entry.getValue()); + } + } + + if (!CollectionUtils.isEmpty(mimeHeaders)) { + for (Entry entry : mimeHeaders.entrySet()) { + messageBuilderSupport = messageBuilderSupport.header("citrus_http_" + entry.getKey(), + entry.getValue()); + } + } + + Map queryParams = new HashMap<>(); + + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + soapSendMessageActionBuilder.withReferenceResolver(context.getReferenceResolver()); + soapSendMessageActionBuilder = customizeBuilder(INSTANCE, context, soapSendMessageActionBuilder); + + soapSendMessageActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "getAllBooks;POST;\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\""); + } + + + } + /** + getBook (POST /GetBook) + + + **/ + public static class GetBookRequest extends OpenApiFromWsdlAbstractTestRequest implements GeneratedApiRequest { + + private final Logger coverageLogger = LoggerFactory.getLogger(GetBookRequest.class); + + // Query params + + + public GetBookRequest(){ + // The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml + setName("OpenApiFromWsdl".toLowerCase() + ":getBookRequestType"); + } + + public String getOperationName() { + return "getBook"; + } + + public String getMethod() { + return "POST"; + } + + public String getPath() { + return "/GetBook"; + } + + /** + * This method sends the HTTP-Request + */ + public void sendRequest(TestContext context) { + + SendSoapMessageAction.Builder soapSendMessageActionBuilder = new SoapActionBuilder().client(wsClient).send(); + SendSoapMessageBuilderSupport messageBuilderSupport = soapSendMessageActionBuilder.getMessageBuilderSupport(); + + messageBuilderSupport.soapAction("getBook"); + + String payload = null; + String payloadType = null; + + if (StringUtils.isNotBlank(this.bodyFile)) { + try { + payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset()); + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read payload resource", e); + } + payloadType = this.bodyContentType; + } else if (StringUtils.isNotBlank(this.bodyLiteral)) { + payload = this.bodyLiteral; + payloadType = this.bodyLiteralContentType; + } + + String body = ""; + String bodyType = ""; + if(payload != null && payloadType != null) { + messageBuilderSupport.body(payload).contentType(payloadType); + body = context.replaceDynamicContentInString(payload); + bodyType = context.replaceDynamicContentInString(payloadType); + } + + if (!CollectionUtils.isEmpty(soapHeaders)) { + for (Entry entry : soapHeaders.entrySet()) { + messageBuilderSupport = messageBuilderSupport.header(entry.getKey(), + entry.getValue()); + } + } + + if (!CollectionUtils.isEmpty(mimeHeaders)) { + for (Entry entry : mimeHeaders.entrySet()) { + messageBuilderSupport = messageBuilderSupport.header("citrus_http_" + entry.getKey(), + entry.getValue()); + } + } + + Map queryParams = new HashMap<>(); + + String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + soapSendMessageActionBuilder.withReferenceResolver(context.getReferenceResolver()); + soapSendMessageActionBuilder = customizeBuilder(INSTANCE, context, soapSendMessageActionBuilder); + + soapSendMessageActionBuilder.build().execute(context); + + coverageLogger.trace(coverageMarker, "getBook;POST;\"" + + query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + + body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\""); + } + + + } +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/spring/OpenApiFromWsdlBeanConfiguration.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/spring/OpenApiFromWsdlBeanConfiguration.java new file mode 100644 index 0000000000..e462cae9f8 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/JavaCitrusCodegenIntegrationTest/expectedgen/soap/bookservice/spring/OpenApiFromWsdlBeanConfiguration.java @@ -0,0 +1,38 @@ +/** + * ================================================== + * GENERATED CLASS, ALL CHANGES WILL BE LOST + * ================================================== + */ + +package org.citrusframework.openapi.generator.soap.bookservice.spring; + +import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE; + +import org.citrusframework.openapi.generator.soap.bookservice.request.BookServiceSoapApi; +import javax.annotation.processing.Generated; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; + +@Configuration +@Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen") +public class OpenApiFromWsdlBeanConfiguration { + + @Bean + @Scope(SCOPE_PROTOTYPE) + public BookServiceSoapApi.AddBookRequest addBookRequest() { + return new BookServiceSoapApi.AddBookRequest(); + } + + @Bean + @Scope(SCOPE_PROTOTYPE) + public BookServiceSoapApi.GetAllBooksRequest getAllBooksRequest() { + return new BookServiceSoapApi.GetAllBooksRequest(); + } + + @Bean + @Scope(SCOPE_PROTOTYPE) + public BookServiceSoapApi.GetBookRequest getBookRequest() { + return new BookServiceSoapApi.GetBookRequest(); + } +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest/BookDatatypes.xsd b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest/BookDatatypes.xsd new file mode 100644 index 0000000000..c093acef11 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest/BookDatatypes.xsd @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest/BookService-generated.yaml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest/BookService-generated.yaml new file mode 100644 index 0000000000..480e462dfe --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest/BookService-generated.yaml @@ -0,0 +1,33 @@ +--- +info: + contact: + name: "org.citrusframework.openapi.generator.SimpleWsdlToOpenApiTransformer" + description: "This api has been generated from the following wsdl 'BookService.wsdl'.\ + \ It's purpose is solely to serve as input for SOAP API generation. Note that\ + \ only operations are extracted from the WSDL. No schema information whatsoever\ + \ is generated!" + title: "Generated api from wsdl" + version: "1.0.0" +openapi: "3.0.1" +paths: + /GetBook: + post: + description: "" + operationId: "GetBook" + responses: {} + tags: + - "BookServiceSOAP" + /AddBook: + post: + description: "" + operationId: "AddBook" + responses: {} + tags: + - "BookServiceSOAP" + /GetAllBooks: + post: + description: "" + operationId: "GetAllBooks" + responses: {} + tags: + - "BookServiceSOAP" \ No newline at end of file diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest/BookService.wsdl b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest/BookService.wsdl new file mode 100644 index 0000000000..5243c102d5 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/SimpleWsdlToOpenApiTransformerTest/BookService.wsdl @@ -0,0 +1,110 @@ + + + Definition for a web service called BookService, + which can be used to add or retrieve books from a collection. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test-api-generator/citrus-test-api-generator-maven-plugin/pom.xml b/test-api-generator/citrus-test-api-generator-maven-plugin/pom.xml new file mode 100644 index 0000000000..e92fd84d94 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-maven-plugin/pom.xml @@ -0,0 +1,111 @@ + + 4.0.0 + + + citrus-test-api-generator + org.citrusframework + 4.3.0-SNAPSHOT + ../pom.xml + + + citrus-test-api-generator-maven-plugin + maven-plugin + + Citrus :: Test API Generator :: Maven Plugin + Maven Plugin for generation of Citrus Test API + + + 2.2.21 + + + + + + org.apache.maven.plugins + maven-plugin-plugin + ${maven.plugin.plugin.version} + + true + + + + + + + + + org.citrusframework + citrus-test-api-generator-core + ${project.version} + + + + commons-io + commons-io + ${commons.io.version} + + + io.swagger.core.v3 + swagger-core + ${swagger.version} + + + io.swagger.core.v3 + swagger-models-jakarta + ${swagger.version} + + + org.apache.maven + maven-artifact + ${maven.version} + provided + + + org.apache.maven + maven-core + ${maven.version} + provided + + + org.apache.maven + maven-plugin-api + ${maven.version} + provided + + + org.apache.maven.plugin-tools + maven-plugin-annotations + ${maven.plugin.annotations.version} + provided + + + + + org.apache.maven + maven-compat + ${maven.version} + test + + + org.apache.maven.plugin-testing + maven-plugin-testing-harness + ${maven.plugin.testing.harness.version} + test + + + org.assertj + assertj-core + ${assertj.version} + test + + + org.junit.jupiter + junit-jupiter-params + test + + + diff --git a/test-api-generator/citrus-test-api-generator-maven-plugin/src/main/java/org/citrusframework/maven/plugin/CodeGenMojoWrapper.java b/test-api-generator/citrus-test-api-generator-maven-plugin/src/main/java/org/citrusframework/maven/plugin/CodeGenMojoWrapper.java new file mode 100644 index 0000000000..ea92db1efb --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-maven-plugin/src/main/java/org/citrusframework/maven/plugin/CodeGenMojoWrapper.java @@ -0,0 +1,86 @@ +package org.citrusframework.maven.plugin; + +import static org.citrusframework.openapi.generator.JavaCitrusCodegen.CODEGEN_NAME; +import static java.lang.String.format; + +import org.citrusframework.openapi.generator.JavaCitrusCodegen; +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import org.apache.maven.plugin.MojoExecution; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.project.MavenProject; +import org.openapitools.codegen.plugin.CodeGenMojo; + +/** + * Wrapper class that uses reflection to expose several properties of the {@link CodeGenMojo} for explicit assignment. + * + * @author Thorsten Schlathoelter + */ +public class CodeGenMojoWrapper extends CodeGenMojo { + + private final Map configOptionsProperties = new HashMap<>(); + + public CodeGenMojoWrapper() throws MojoExecutionException { + setFixedConfigOptions(); + setPrivateField("configOptions", configOptionsProperties); + } + + private void setFixedConfigOptions() throws MojoExecutionException { + setPrivateField("generateSupportingFiles", true); + setPrivateField( "generatorName", CODEGEN_NAME); + } + + public CodeGenMojoWrapper project(MavenProject mavenProject) throws MojoExecutionException { + setPrivateField("project", mavenProject); + return this; + } + + public CodeGenMojoWrapper output(File output) throws MojoExecutionException { + setPrivateField("output", output); + return this; + } + + public CodeGenMojoWrapper inputSpec(String inputSpec) throws MojoExecutionException { + setPrivateField("inputSpec", inputSpec); + return this; + } + + public CodeGenMojoWrapper mojoExecution(MojoExecution mojoExecution) throws MojoExecutionException { + setPrivateField("mojo", mojoExecution); + return this; + } + + public CodeGenMojoWrapper configOptions(Map configOptionsProperties) { + this.configOptionsProperties.putAll(configOptionsProperties); + return this; + } + + public CodeGenMojoWrapper schemaFolder(String schemaFolder) { + configOptionsProperties.put(JavaCitrusCodegen.GENERATED_SCHEMA_FOLDER, schemaFolder); + return this; + } + + public CodeGenMojoWrapper resourceFolder(String resourceFolder) { + configOptionsProperties.put(JavaCitrusCodegen.RESOURCE_FOLDER, resourceFolder); + return this; + } + + public CodeGenMojoWrapper sourceFolder(String sourceFolder) { + configOptionsProperties.put(JavaCitrusCodegen.SOURCE_FOLDER, sourceFolder); + return this; + } + + @SuppressWarnings("java:S3011") // Accessibility bypass + private void setPrivateField(String fieldName, Object fieldValue) throws MojoExecutionException { + try { + var field = CodeGenMojo.class.getDeclaredField(fieldName); + field.setAccessible(true); + field.set(this, fieldValue); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new MojoExecutionException( + format("Could not reflectively set field value '%s' for field '%s'", fieldValue, fieldName)); + } + } + +} diff --git a/test-api-generator/citrus-test-api-generator-maven-plugin/src/main/java/org/citrusframework/maven/plugin/SpringMetaFileGenerator.java b/test-api-generator/citrus-test-api-generator-maven-plugin/src/main/java/org/citrusframework/maven/plugin/SpringMetaFileGenerator.java new file mode 100644 index 0000000000..23ae7b161f --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-maven-plugin/src/main/java/org/citrusframework/maven/plugin/SpringMetaFileGenerator.java @@ -0,0 +1,156 @@ +package org.citrusframework.maven.plugin; + +import static java.lang.String.format; +import static java.util.Collections.emptyList; + +import org.citrusframework.maven.plugin.TestApiGeneratorMojo.ApiConfig; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; +import org.apache.maven.plugin.MojoExecutionException; +import org.citrusframework.exceptions.CitrusRuntimeException; + +/** + * Utility class responsible for generating the Spring meta files 'spring.handlers' and 'spring.schemas', used + * in Spring integration testing. These meta files define mappings between XML namespace URIs and corresponding + * handler classes. The class provides methods to generate these meta files based on the configuration provided. + *

+ * The generated meta files can be created either in the generated folder or in the main resources folder. See + * {@link TestApiGeneratorMojo#RESOURCE_FOLDER_PROPERTY} for details. The implemented algorithm carefully updates these + * files and tries to keep non generated information unchanged. Therefore, a special segment in the namespace uri is used, namely + * {@link TestApiGeneratorMojo#CITRUS_TEST_SCHEMA}. + *

+ * + * @author Thorsten Schlathoelter + * + */ +public class SpringMetaFileGenerator { + + private final TestApiGeneratorMojo testApiGeneratorMojo; + + public SpringMetaFileGenerator(TestApiGeneratorMojo testApiGeneratorMojo) { + this.testApiGeneratorMojo = testApiGeneratorMojo; + } + + public void generateSpringIntegrationMetaFiles() throws MojoExecutionException { + + String springMetafileDirectory = format("%s/%s", testApiGeneratorMojo.getMavenProject().getBasedir(), + testApiGeneratorMojo.metaInfFolder()); + File metaFolder = new File(springMetafileDirectory); + if (!metaFolder.exists() && !metaFolder.mkdirs()) { + throw new CitrusRuntimeException( + format("Unable to create spring meta file directory: '%s'", springMetafileDirectory)); + } + + try { + writeSpringSchemaMetaFile(metaFolder); + writeSpringHandlerMetaFile(metaFolder); + } catch (MetaFileWriteException e) { + throw new MojoExecutionException(e); + } + } + + private void writeSpringSchemaMetaFile(File springMetafileDirectory) throws MojoExecutionException { + + String filename = "spring.schemas"; + writeSpringMetaFile(springMetafileDirectory, filename, (fileWriter, apiConfig) -> { + String targetXmlnsNamespace = TestApiGeneratorMojo.replaceDynamicVarsToLowerCase(apiConfig.getTargetXmlnsNamespace(), apiConfig.getPrefix(), + apiConfig.getVersion()); + String schemaFolderPath = TestApiGeneratorMojo.replaceDynamicVars(testApiGeneratorMojo.schemaFolder(apiConfig), apiConfig.getPrefix(), + apiConfig.getVersion()); + String schemaPath = String.format("%s/%s-api.xsd", schemaFolderPath, apiConfig.getPrefix().toLowerCase()); + appendLine(fileWriter, format("%s.xsd=%s%n", targetXmlnsNamespace.replace("http://", "http\\://"), schemaPath), filename); + }); + } + + private void writeSpringHandlerMetaFile(File springMetafileDirectory) throws MojoExecutionException { + String filename = "spring.handlers"; + writeSpringMetaFile(springMetafileDirectory, filename, (fileWriter, apiConfig) -> { + String targetXmlnsNamespace = TestApiGeneratorMojo.replaceDynamicVarsToLowerCase(apiConfig.getTargetXmlnsNamespace(), apiConfig.getPrefix(), + apiConfig.getVersion()); + String invokerPackage = TestApiGeneratorMojo.replaceDynamicVarsToLowerCase(apiConfig.getInvokerPackage(), apiConfig.getPrefix(), apiConfig.getVersion()); + String namespaceHandlerClass = invokerPackage + ".citrus.extension." + apiConfig.getPrefix() + "NamespaceHandler"; + appendLine(fileWriter, format("%s=%s%n", targetXmlnsNamespace.replace("http://", "http\\://"), namespaceHandlerClass), + filename); + }); + } + + private void writeSpringMetaFile(File springMetafileDirectory, String filename, BiConsumer contentFormatter) + throws MojoExecutionException { + + File handlerFile = new File(format("%s/%s", springMetafileDirectory.getPath(), filename)); + List filteredLines = readAndFilterLines(handlerFile); + + try (FileWriter fileWriter = new FileWriter(handlerFile)) { + + for (String line : filteredLines) { + fileWriter.write(format("%s%n", line)); + } + + for (ApiConfig apiConfig : testApiGeneratorMojo.getApiConfigs()) { + contentFormatter.accept(fileWriter, apiConfig); + } + + } catch (IOException e) { + throw new MojoExecutionException("Unable to write spring meta file!", e); + } + } + + /** + * Reads the lines from the specified file and filters out lines indicating a generated test API, + * while maintaining all non-generated test API lines. This method is used to process files + * containing both generated and non-generated test APIs, allowing seamless integration and + * modification of both types of APIs in the same source files. + * + *

+ * Generated test API lines are identified by the presence of the {@code CITRUS_TEST_SCHEMA} + * string and excluded from the output of this method, while all other lines are preserved. + * This enables the algorithm to operate on files that are not purely generated, for example, + * when mixing generated with non-generated APIs in 'src/main/META-INF'. + *

+ * + * @param file the file to read and filter + * @return a list of filtered lines, excluding lines indicating a generated test API + * @throws CitrusRuntimeException if an error occurs while reading the file + */ + private static List readAndFilterLines(File file) { + + if (!file.exists()) { + return emptyList(); + } + + List filteredLines = new ArrayList<>(); + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + String line; + while ((line = reader.readLine()) != null) { + if (!line.contains(TestApiGeneratorMojo.CITRUS_TEST_SCHEMA)) { + filteredLines.add(line); + } + } + } catch (IOException e) { + throw new CitrusRuntimeException(format("Unable to read file file: '%s'", file.getPath()), e); + } + + return filteredLines; + } + + private void appendLine(FileWriter fileWriter, String format, String filename) { + try { + fileWriter.append(format); + } catch (IOException e) { + throw new MetaFileWriteException(format("Unable to write spring meta file '%s'!", filename), e); + } + } + + private static final class MetaFileWriteException extends RuntimeException { + + public MetaFileWriteException(String message, Throwable cause) { + super(message, cause); + } + } +} diff --git a/test-api-generator/citrus-test-api-generator-maven-plugin/src/main/java/org/citrusframework/maven/plugin/TestApiGeneratorMojo.java b/test-api-generator/citrus-test-api-generator-maven-plugin/src/main/java/org/citrusframework/maven/plugin/TestApiGeneratorMojo.java new file mode 100644 index 0000000000..d5b2b1cd53 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-maven-plugin/src/main/java/org/citrusframework/maven/plugin/TestApiGeneratorMojo.java @@ -0,0 +1,412 @@ +package org.citrusframework.maven.plugin; + +import static org.citrusframework.openapi.generator.JavaCitrusCodegen.API_ENDPOINT; +import static org.citrusframework.openapi.generator.JavaCitrusCodegen.API_TYPE; +import static org.citrusframework.openapi.generator.JavaCitrusCodegen.PREFIX; +import static org.citrusframework.openapi.generator.JavaCitrusCodegen.TARGET_XMLNS_NAMESPACE; +import static java.lang.String.format; +import static org.apache.commons.lang3.StringUtils.isBlank; + +import com.google.common.annotations.VisibleForTesting; +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecution; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; +import org.apache.maven.project.MavenProject; +import org.openapitools.codegen.plugin.CodeGenMojo; +import org.sonatype.plexus.build.incremental.BuildContext; +import org.sonatype.plexus.build.incremental.DefaultBuildContext; + +/** + * 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 + * {@code 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. + *

+ * + * @author Thorsten Schlathoelter + * + */ +@Mojo( + name = "create-test-api", + defaultPhase = LifecyclePhase.GENERATE_TEST_SOURCES, + requiresDependencyCollection = ResolutionScope.TEST, + requiresDependencyResolution = ResolutionScope.TEST, + threadSafe = true +) +public class TestApiGeneratorMojo extends AbstractMojo { + + public static final String DEFAULT_SOURCE_FOLDER = "generated-test-sources"; + public static final String DEFAULT_RESOURCE_FOLDER = "generated-test-resources"; + public static final String DEFAULT_BASE_PACKAGE = "org.citrusframework.automation.%PREFIX%.%VERSION%"; + public static final String DEFAULT_INVOKER_PACKAGE = DEFAULT_BASE_PACKAGE; + public static final String DEFAULT_API_PACKAGE = DEFAULT_BASE_PACKAGE+".api"; + public static final String DEFAULT_MODEL_PACKAGE = DEFAULT_BASE_PACKAGE+".model"; + public static final String DEFAULT_SCHEMA_FOLDER_TEMPLATE = "schema/xsd/%VERSION%"; + public static final ApiType DEFAULT_API_TYPE = ApiType.REST; + + /** + * Marker fragment in the schema name of a generated schema. Used to distinguish generated from non generated values, when manipulating + * spring meta-data files. + */ + public static final String CITRUS_TEST_SCHEMA = "citrus-test-schema"; + + /** + * Specifies the default target namespace template. When changing the default value, it's important to maintain the 'citrus-test-schema' + * part, as this name serves to differentiate between generated and non-generated schemas. This differentiation aids in the creation of + * supporting Spring files such as 'spring.handlers' and 'spring.schemas'. + */ + public static final String DEFAULT_TARGET_NAMESPACE_TEMPLATE = + "http://www.citrusframework.org/" + CITRUS_TEST_SCHEMA + "/%VERSION%/%PREFIX%-api"; + + /** + * The default META-INF folder. Note that it points into the main resources, not generated resources, to allow for non generated + * schemas/handlers. See also {@link TestApiGeneratorMojo}#DEFAULT_TARGET_NAMESPACE_TEMPLATE. + */ + public static final String DEFAULT_META_INF_FOLDER = "target/generated-test-resources/META-INF"; + + @Component + private final BuildContext buildContext = new DefaultBuildContext(); + + @Parameter(defaultValue = "${project}", readonly = true) + private MavenProject mavenProject; + + @Parameter(defaultValue = "${mojoExecution}", readonly = true) + private MojoExecution mojoExecution; + + /** + * sourceFolder: specifies the location to which the sources are generated. Defaults to 'generated-test-sources'. + */ + public static final String SOURCE_FOLDER_PROPERTY = "citrus.test.api.generator.source.folder"; + @Parameter(property = SOURCE_FOLDER_PROPERTY, defaultValue = DEFAULT_SOURCE_FOLDER) + @SuppressWarnings({"FieldCanBeLocal", "FieldMayBeFinal"}) // Maven injected + private String sourceFolder = DEFAULT_SOURCE_FOLDER; + + /** + * resourceFolder: specifies the location to which the resources are generated. Defaults to 'generated-test-resources'. + */ + public static final String RESOURCE_FOLDER_PROPERTY = "citrus.test.api.generator.resource.folder"; + @Parameter(property = RESOURCE_FOLDER_PROPERTY, defaultValue = DEFAULT_RESOURCE_FOLDER) + @SuppressWarnings({"FieldCanBeLocal", "FieldMayBeFinal"}) // Maven injected + private String resourceFolder = DEFAULT_RESOURCE_FOLDER; + + /** + * schemaFolder: specifies the location for the generated xsd schemas. Defaults to 'schema/xsd/%VERSION%' + */ + public static final String API_SCHEMA_FOLDER = "citrus.test.api.generator.schema.folder"; + @Parameter(property = API_SCHEMA_FOLDER, defaultValue = DEFAULT_SCHEMA_FOLDER_TEMPLATE) + @SuppressWarnings({"FieldCanBeLocal", "FieldMayBeFinal"}) // Maven injected + private String schemaFolder = DEFAULT_SCHEMA_FOLDER_TEMPLATE; + + /** + * metaInfFolder: specifies the location to which the resources are generated. Defaults to 'generated-resources'. + */ + public static final String META_INF_FOLDER = "citrus.test.api.generator.meta.inf.folder"; + @Parameter(property = META_INF_FOLDER, defaultValue = DEFAULT_META_INF_FOLDER) + @SuppressWarnings({"FieldCanBeLocal", "FieldMayBeFinal"}) // Maven injected + private String metaInfFolder = DEFAULT_META_INF_FOLDER; + + /** + * resourceFolder: specifies the location to which the resources are generated. Defaults to 'generated-resources'. + */ + public static final String GENERATE_SPRING_INTEGRATION_FILES = "citrus.test.api.generator.generate.spring.integration.files"; + @Parameter(property = GENERATE_SPRING_INTEGRATION_FILES, defaultValue = "true") + @SuppressWarnings({"FieldCanBeLocal", "FieldMayBeFinal"}) // Maven injected + private boolean generateSpringIntegrationFiles = true; + + @Parameter + private List apis; + + protected MavenProject getMavenProject() { + return mavenProject; + } + + protected void setMavenProject(MavenProject mavenProject) { + this.mavenProject = mavenProject; + } + + public List getApiConfigs() { + return apis; + } + + public String metaInfFolder() { + return metaInfFolder; + } + + @VisibleForTesting + void setMojoExecution(MojoExecution mojoExecution) { + this.mojoExecution = mojoExecution; + } + + /** + * Returns the fully qualified schema folder + */ + public String schemaFolder(ApiConfig apiConfig) { + return replaceDynamicVars(schemaFolder, apiConfig.getPrefix(), apiConfig.getVersion()); + } + + @Override + public void execute() throws MojoExecutionException { + + for (int index = 0; index < apis.size(); index++) { + ApiConfig apiConfig = apis.get(index); + validateApiConfig(index, apiConfig); + CodeGenMojo codeGenMojo = configureCodeGenMojo(apiConfig); + codeGenMojo.execute(); + } + + if (generateSpringIntegrationFiles) { + new SpringMetaFileGenerator(this).generateSpringIntegrationMetaFiles(); + } + } + + CodeGenMojo configureCodeGenMojo(ApiConfig apiConfig) throws MojoExecutionException { + CodeGenMojo codeGenMojo = new CodeGenMojoWrapper() + .resourceFolder(resourceFolder) + .sourceFolder(sourceFolder) + .schemaFolder(schemaFolder(apiConfig)) + .output(new File(mavenProject.getBuild().getDirectory())) + .mojoExecution(mojoExecution) + .project(mavenProject) + .inputSpec(apiConfig.getSource()) + .configOptions(apiConfig.toConfigOptionsProperties()); + + codeGenMojo.setPluginContext(getPluginContext()); + codeGenMojo.setBuildContext(buildContext); + return codeGenMojo; + } + + private void validateApiConfig(int apiIndex, ApiConfig apiConfig) throws MojoExecutionException { + requireNonBlankParameter("prefix", apiIndex, apiConfig.getPrefix()); + requireNonBlankParameter("source", apiIndex, apiConfig.getSource()); + } + + private void requireNonBlankParameter(String name, int index, String parameterValue) throws MojoExecutionException { + if (isBlank(parameterValue)) { + throw new MojoExecutionException(format("Required parameter '%s' not set for api at index '%d'!", name, index)); + } + } + + /** + * Replace the placeholders '%PREFIX%' and '%VERSION%' in the given text. + */ + static String replaceDynamicVars(String text, String prefix, String version) { + + if (text == null) { + return null; + } + + return text.replace("%PREFIX%", prefix) + .replace(".%VERSION%", version != null ? "." + version : "") + .replace("/%VERSION%", version != null ? "/" + version : "") + .replace("-%VERSION%", version != null ? "-" + version : "") + .replace("%VERSION%", version != null ? version : ""); + } + + /** + * Replace the placeholders '%PREFIX%' and '%VERSION%' in the given text, performing a toLowerCase on the prefix. + */ + static String replaceDynamicVarsToLowerCase(String text, String prefix, String version) { + return replaceDynamicVars(text, prefix.toLowerCase(), version); + } + + public enum ApiType { + REST, SOAP + } + + /** + * Note that the default values are not properly set by maven processor. Therefore, the default values have been assigned additionally + * on field level. + */ + public static class ApiConfig { + + public static final String DEFAULT_ENDPOINT = "PREFIX_ENDPOINT"; + + /** + * prefix: specifies the prefixed used for the test api. Typically, an acronym for the application which is being tested. + */ + public static final String API_PREFIX_PROPERTY = "citrus.test.api.generator.prefix"; + @Parameter(required = true, property = API_PREFIX_PROPERTY) + private String prefix; + + /** + * source: specifies the source of the test api. + */ + public static final String API_SOURCE_PROPERTY = "citrus.test.api.generator.source"; + @Parameter(required = true, property = API_SOURCE_PROPERTY) + private String source; + + /** + * version: specifies the version of the api. May be null. + */ + public static final String API_VERSION_PROPERTY = "citrus.test.api.generator.version"; + @Parameter(property = API_VERSION_PROPERTY) + private String version; + + /** + * endpoint: specifies the endpoint of the test api. Defaults to 'prefixEndpoint'. + */ + public static final String API_ENDPOINT_PROPERTY = "citrus.test.api.generator.endpoint"; + @Parameter(property = API_ENDPOINT_PROPERTY, defaultValue = DEFAULT_ENDPOINT) + private String endpoint = DEFAULT_ENDPOINT; + + /** + * type: specifies the type of the test api. Defaults to 'REST' + */ + public static final String API_TYPE_PROPERTY = "citrus.test.api.generator.type"; + @Parameter(property = API_TYPE_PROPERTY, defaultValue = "REST") + private ApiType type = DEFAULT_API_TYPE; + + /** + * useTags: specifies whether tags should be used by the generator. Defaults to 'true'. If useTags is set to true, the generator + * will organize the generated code based on the tags defined in your API specification. + */ + public static final String API_USE_TAGS_PROPERTY = "citrus.test.api.generator.use.tags"; + @Parameter(property = API_USE_TAGS_PROPERTY, defaultValue = "true") + private boolean useTags = true; + + /** + * invokerPackage: specifies the package for the test api classes. Defaults to + * 'org.citrusframework.automation.%PREFIX%.%VERSION%'. + */ + public static final String API_INVOKER_PACKAGE_PROPERTY = "citrus.test.api.generator.invoker.package"; + @Parameter(property = API_INVOKER_PACKAGE_PROPERTY, defaultValue = DEFAULT_INVOKER_PACKAGE) + private String invokerPackage = DEFAULT_INVOKER_PACKAGE; + + /** + * apiPackage: specifies the package for the test api classes. Defaults to + * 'org.citrusframework.automation.%PREFIX%.%VERSION%.api'. + */ + public static final String API_API_PACKAGE_PROPERTY = "citrus.test.api.generator.api.package"; + @Parameter(property = API_API_PACKAGE_PROPERTY, defaultValue = DEFAULT_API_PACKAGE) + private String apiPackage = DEFAULT_API_PACKAGE; + + /** + * modelPackage: specifies the package for the test api classes. Defaults to + * 'org.citrusframework.automation.%PREFIX%.%VERSION%.model'. + */ + public static final String API_MODEL_PACKAGE_PROPERTY = "citrus.test.api.generator.model.package"; + @Parameter(property = API_MODEL_PACKAGE_PROPERTY, defaultValue = DEFAULT_MODEL_PACKAGE) + private String modelPackage = DEFAULT_MODEL_PACKAGE; + + /** + * targetXmlNamespace: specifies the xml namespace to be used by the api. Defaults to + * 'http://www.citrusframework.org/schema/%VERSION%/%PREFIX%-api' + */ + @SuppressWarnings("JavadocLinkAsPlainText") + public static final String API_NAMESPACE_PROPERTY = "citrus.test.api.generator.namespace"; + @Parameter(property = API_NAMESPACE_PROPERTY, defaultValue = DEFAULT_TARGET_NAMESPACE_TEMPLATE) + private String targetXmlnsNamespace = DEFAULT_TARGET_NAMESPACE_TEMPLATE; + + + public String getPrefix() { + return prefix; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String qualifiedEndpoint() { + return DEFAULT_ENDPOINT.equals(endpoint) ? getPrefix().toLowerCase() + "Endpoint" : endpoint; + } + + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + + public ApiType getType() { + return type; + } + + public void setType(ApiType type) { + this.type = type; + } + + public void setUseTags(boolean useTags) { + this.useTags = useTags; + } + + public String getInvokerPackage() { + return invokerPackage; + } + + public void setInvokerPackage(String invokerPackage) { + this.invokerPackage = invokerPackage; + } + + public String getApiPackage() { + return apiPackage; + } + + public void setApiPackage(String apiPackage) { + this.apiPackage = apiPackage; + } + + public String getModelPackage() { + return modelPackage; + } + + public void setModelPackage(String modelPackage) { + this.modelPackage = modelPackage; + } + + public String getTargetXmlnsNamespace() { + return targetXmlnsNamespace; + } + + public void setTargetXmlnsNamespace(String targetXmlnsNamespace) { + this.targetXmlnsNamespace = targetXmlnsNamespace; + } + + Map toConfigOptionsProperties() { + + Map configOptionsProperties = new HashMap<>(); + configOptionsProperties.put(PREFIX, prefix); + configOptionsProperties.put(API_ENDPOINT, qualifiedEndpoint()); + configOptionsProperties.put(API_TYPE, type.toString()); + configOptionsProperties.put(TARGET_XMLNS_NAMESPACE, + replaceDynamicVarsToLowerCase(targetXmlnsNamespace, prefix, version)); + configOptionsProperties.put("invokerPackage", + replaceDynamicVarsToLowerCase(invokerPackage, prefix, version)); + configOptionsProperties.put("apiPackage", + replaceDynamicVarsToLowerCase(apiPackage, prefix, version)); + configOptionsProperties.put("modelPackage", + replaceDynamicVarsToLowerCase(modelPackage, prefix, version)); + configOptionsProperties.put("useTags", useTags); + + return configOptionsProperties; + } + + } + + +} diff --git a/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/java/org/citrusframework/maven/plugin/TestApiGeneratorMojoIntegrationTest.java b/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/java/org/citrusframework/maven/plugin/TestApiGeneratorMojoIntegrationTest.java new file mode 100644 index 0000000000..d4bcc740b9 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/java/org/citrusframework/maven/plugin/TestApiGeneratorMojoIntegrationTest.java @@ -0,0 +1,314 @@ +package org.citrusframework.maven.plugin; + +import static com.google.common.collect.Streams.concat; +import static java.lang.Boolean.TRUE; +import static java.util.Arrays.stream; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.citrusframework.maven.plugin.TestApiGeneratorMojo.CITRUS_TEST_SCHEMA; +import static org.citrusframework.maven.plugin.TestApiGeneratorMojo.replaceDynamicVars; +import static org.citrusframework.maven.plugin.TestApiGeneratorMojo.replaceDynamicVarsToLowerCase; +import static org.junit.jupiter.params.provider.Arguments.arguments; +import static org.springframework.test.util.ReflectionTestUtils.getField; + +import jakarta.validation.constraints.NotNull; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugin.testing.AbstractMojoTestCase; +import org.apache.maven.project.MavenProject; +import org.assertj.core.api.Assertions; +import org.citrusframework.exceptions.CitrusRuntimeException; +import org.citrusframework.exceptions.TestCaseFailedException; +import org.citrusframework.maven.plugin.TestApiGeneratorMojo.ApiConfig; +import org.citrusframework.maven.plugin.TestApiGeneratorMojo.ApiType; +import org.citrusframework.maven.plugin.stubs.CitrusOpenApiGeneratorMavenProjectStub; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * @author Thorsten Schlathoelter + */ +class TestApiGeneratorMojoIntegrationTest extends AbstractMojoTestCase { + + public static final String OTHER_META_FILE_CONTENT = "somenamespace=somevalue"; + + public static final String OTHER_CITRUS_META_FILE_CONTENT = String.format("somenamespace/%s/aa=somevalue", CITRUS_TEST_SCHEMA); + + /** + * Array containing path templates for each generated file, specified with tokens. Tokens can be replaced with values of the respective + * testing scenario. + */ + private static final String[] STANDARD_FILE_PATH_TEMPLATES = new String[]{ + "%TARGET_FOLDER%/%GENERATED_SOURCES_FOLDER%/%INVOKER_FOLDER%/citrus/extension/%CAMEL_PREFIX%NamespaceHandler.java", + "%TARGET_FOLDER%/%GENERATED_SOURCES_FOLDER%/%INVOKER_FOLDER%/citrus/%CAMEL_PREFIX%AbstractTestRequest.java", + "%TARGET_FOLDER%/%GENERATED_SOURCES_FOLDER%/%INVOKER_FOLDER%/citrus/%CAMEL_PREFIX%BeanDefinitionParser.java", + "%TARGET_FOLDER%/%GENERATED_SOURCES_FOLDER%/%INVOKER_FOLDER%/spring/%CAMEL_PREFIX%BeanConfiguration.java", + "%TARGET_FOLDER%/%GENERATED_SOURCES_FOLDER%/%MODEL_FOLDER%/PingReqType.java", + "%TARGET_FOLDER%/%GENERATED_SOURCES_FOLDER%/%MODEL_FOLDER%/PingRespType.java", + "%TARGET_FOLDER%/%GENERATED_SOURCES_FOLDER%/%REQUEST_FOLDER%/PingApi.java", + "%TARGET_FOLDER%/%GENERATED_SOURCES_FOLDER%/%REQUEST_FOLDER%/PungApi.java", + "%TARGET_FOLDER%/%GENERATED_RESOURCES_FOLDER%/%SCHEMA_FOLDER%/%LOWER_PREFIX%-api.xsd", + "%TARGET_FOLDER%/%GENERATED_RESOURCES_FOLDER%/%LOWER_PREFIX%-api-model.csv" + }; + + /** + * Array containing path templates for each generated spring meta file, specified with tokens. Tokens can be replaced with values of the respective + * testing scenario. + */ + private static final String[] SPRING_META_FILE_TEMPLATES = new String[]{ + "%BASE_FOLDER%/%META_INF_FOLDER%/spring.handlers", + "%BASE_FOLDER%/%META_INF_FOLDER%/spring.schemas" + }; + + private TestApiGeneratorMojo fixture; + + @BeforeEach + @SuppressWarnings("JUnitMixedFramework") + void beforeEachSetup() throws Exception { + setUp(); + } + + static Stream executeMojoWithConfigurations() { + return Stream.of( + arguments("pom-missing-prefix", + new MojoExecutionException("Required parameter 'prefix' not set for api at index '0'!")), + arguments("pom-missing-source", + new MojoExecutionException("Required parameter 'source' not set for api at index '0'!")), + arguments("pom-minimal-config", null), + arguments("pom-minimal-with-version-config", null), + arguments("pom-multi-config", null), + arguments("pom-full-config", null), + arguments("pom-full-with-version-config", null), + arguments("pom-soap-config", null) + ); + } + + @ParameterizedTest + @MethodSource + void executeMojoWithConfigurations(String configName, Exception expectedException) + throws Exception { + + try { + fixture = fixtureFromPom(configName); + } catch (MojoExecutionException | MojoFailureException e) { + Assertions.fail("Test setup failed!", e); + } + + @SuppressWarnings("unchecked") + List apiConfigs = (List) getField(fixture, "apis"); + + assertThat(apiConfigs).isNotNull(); + + if (expectedException == null) { + // Given + writeSomeValuesToSpringMetaFiles(apiConfigs); + + // When + assertThatCode(() -> fixture.execute()).doesNotThrowAnyException(); + + // Then + for (ApiConfig apiConfig : apiConfigs) { + assertFilesGenerated(apiConfig); + assertSpecificFileContent(apiConfig); + } + } else { + // When/Then + assertThatThrownBy(() -> fixture.execute()).isInstanceOf(expectedException.getClass()) + .hasMessage(expectedException.getMessage()); + } + } + + /** + * Writes values to spring meta files, to make sure existing non generated and existing generated values are treated properly. + */ + private void writeSomeValuesToSpringMetaFiles(List apiConfigs) { + for (ApiConfig apiConfig : apiConfigs) { + for (String filePathTemplate : SPRING_META_FILE_TEMPLATES) { + + String filePath = resolveFilePath(apiConfig, filePathTemplate); + File file = new File(filePath); + if (!file.getParentFile().exists() && !new File(filePath).getParentFile().mkdirs()) { + Assertions.fail("Unable to prepare test data."); + } + + try (FileWriter fileWriter = new FileWriter(filePath)) { + fileWriter.append(String.format("%s%n", OTHER_META_FILE_CONTENT)); + fileWriter.append(String.format("%s%n", OTHER_CITRUS_META_FILE_CONTENT)); + } catch (IOException e) { + throw new CitrusRuntimeException("Unable to write spring meta files", e); + } + } + } + } + + private void assertFilesGenerated(ApiConfig apiConfig) { + + for (String filePathTemplate : STANDARD_FILE_PATH_TEMPLATES) { + String filePath = resolveFilePath(apiConfig, filePathTemplate); + assertThat(new File(filePath)).isFile().exists(); + } + + if (TRUE.equals(getField(fixture, "generateSpringIntegrationFiles"))) { + for (String filePathTemplate : SPRING_META_FILE_TEMPLATES) { + String filePath = resolveFilePath(apiConfig, filePathTemplate); + assertThat(new File(filePath)).isFile().exists(); + } + } + } + + private void assertSpecificFileContent(ApiConfig apiConfig) { + try { + assertEndpointName(apiConfig); + assertTargetNamespace(apiConfig); + assertApiType(apiConfig); + assertSchemasInSpringSchemas(apiConfig); + assertHandlersInSpringHandlers(apiConfig); + } catch (IOException e) { + throw new TestCaseFailedException(e); + } + } + + private void assertHandlersInSpringHandlers(ApiConfig apiConfig) throws IOException { + String targetNamespace = replaceDynamicVarsToLowerCase(apiConfig.getTargetXmlnsNamespace(), apiConfig.getPrefix(), apiConfig.getVersion()); + targetNamespace = targetNamespace.replace(":", "\\:"); + String invokerPackage = replaceDynamicVarsToLowerCase(apiConfig.getInvokerPackage(), apiConfig.getPrefix(), apiConfig.getVersion()); + + String text = String.format("%s=%s.citrus.extension.%sNamespaceHandler", targetNamespace, invokerPackage, apiConfig.getPrefix()); + + assertThat(getContentOfFile(apiConfig, "spring.handlers")).contains(text); + + // Other specific meta info should be retained + assertThat(getContentOfFile(apiConfig, "spring.handlers")).contains(OTHER_META_FILE_CONTENT); + // Other citrus generated meta info should be deleted + assertThat(getContentOfFile(apiConfig, "spring.handlers")).doesNotContain(OTHER_CITRUS_META_FILE_CONTENT); + } + + private void assertSchemasInSpringSchemas(ApiConfig apiConfig) throws IOException { + + String targetNamespace = replaceDynamicVarsToLowerCase(apiConfig.getTargetXmlnsNamespace(), apiConfig.getPrefix(), apiConfig.getVersion()); + targetNamespace = targetNamespace.replace(":", "\\:"); + String schemaPath = replaceDynamicVarsToLowerCase((String)getField(fixture, "schemaFolder"), apiConfig.getPrefix(), apiConfig.getVersion()); + + String text = String.format("%s.xsd=%s/%s-api.xsd", targetNamespace, schemaPath, apiConfig.getPrefix().toLowerCase()); + + // Other specific meta info should be retained assertThat(getContentOfFile(apiConfig, "spring.schemas")).contains(OTHER_META_FILE_CONTENT); + assertThat(getContentOfFile(apiConfig, "spring.schemas")).contains(String.format("%s", text)); + // Other citrus generated meta info should be deleted + assertThat(getContentOfFile(apiConfig, "spring.schemas")).doesNotContain(OTHER_CITRUS_META_FILE_CONTENT); + } + + private void assertApiType(ApiConfig apiConfig) throws IOException { + String text; + switch (apiConfig.getType()) { + case REST -> text = "HttpClient httpClient"; + case SOAP -> text = "WebServiceClient wsClient"; + default -> throw new IllegalArgumentException(String.format("No apiTye set in ApiConfig. Expected one of %s", + stream(ApiType.values()).map(ApiType::toString).collect( + Collectors.joining()))); + } + assertThat(getContentOfFile(apiConfig, "AbstractTestRequest.java")).contains(text); + } + + private void assertTargetNamespace(ApiConfig apiConfig) throws IOException { + assertThat(getContentOfFile(apiConfig, "-api.xsd")).contains( + String.format("targetNamespace=\"%s\"", + replaceDynamicVarsToLowerCase(apiConfig.getTargetXmlnsNamespace(), apiConfig.getPrefix(), apiConfig.getVersion()))); + } + + private void assertEndpointName(ApiConfig apiConfig) throws IOException { + assertThat(getContentOfFile(apiConfig, "AbstractTestRequest")).contains( + String.format("@Qualifier(\"%s\")", apiConfig.qualifiedEndpoint())); + } + + private String getContentOfFile(ApiConfig apiConfig, String fileIdentifier) throws IOException { + String filePathTemplate = getTemplateContaining(fileIdentifier); + String filePath = resolveFilePath(apiConfig, filePathTemplate); + + File file = new File(filePath); + + assertThat(file).exists(); + Path path = Paths.get(filePath); + byte[] bytes = Files.readAllBytes(path); + return new String(bytes, StandardCharsets.UTF_8); + } + + private String getTemplateContaining(String text) { + return concat(stream(STANDARD_FILE_PATH_TEMPLATES), stream(SPRING_META_FILE_TEMPLATES)) + .filter(path -> path.contains(text)).findFirst() + .orElseThrow(() -> new AssertionError(String.format("Can't find file template with content: '%s'", text))); + } + + @NotNull + private String resolveFilePath(ApiConfig apiConfig, String filePathTemplate) { + + String lowerCasePrefix = apiConfig.getPrefix().toLowerCase(); + char[] prefixCharArray = apiConfig.getPrefix().toCharArray(); + prefixCharArray[0] = Character.toUpperCase(prefixCharArray[0]); + String camelCasePrefix = new String(prefixCharArray); + + String invokerFolder = toFolder( + replaceDynamicVarsToLowerCase(apiConfig.getInvokerPackage(), apiConfig.getPrefix(), apiConfig.getVersion())); + String modelFolder = toFolder( + replaceDynamicVarsToLowerCase(apiConfig.getModelPackage(), apiConfig.getPrefix(), apiConfig.getVersion())); + String requestFolder = toFolder( + replaceDynamicVarsToLowerCase(apiConfig.getApiPackage(), apiConfig.getPrefix(), apiConfig.getVersion())); + String schemaFolder = toFolder( + replaceDynamicVars((String)getField(fixture, "schemaFolder"), apiConfig.getPrefix(), apiConfig.getVersion())); + String generatedSourcesFolder = toFolder( + replaceDynamicVars((String)getField(fixture, "sourceFolder"), apiConfig.getPrefix(), apiConfig.getVersion())); + String generatedResourcesFolder = toFolder( + replaceDynamicVars((String)getField(fixture, "resourceFolder"), apiConfig.getPrefix(), apiConfig.getVersion())); + + return filePathTemplate + .replace("%BASE_FOLDER%", fixture.getMavenProject().getBasedir().getPath()) + .replace("%TARGET_FOLDER%", fixture.getMavenProject().getBuild().getDirectory()) + .replace("%SOURCE_FOLDER%", fixture.getMavenProject().getBuild().getSourceDirectory()) + .replace("%GENERATED_SOURCES_FOLDER%", generatedSourcesFolder) + .replace("%GENERATED_RESOURCES_FOLDER%", generatedResourcesFolder) + .replace("%INVOKER_FOLDER%", invokerFolder) + .replace("%MODEL_FOLDER%", modelFolder) + .replace("%REQUEST_FOLDER%", requestFolder) + .replace("%SCHEMA_FOLDER%", schemaFolder) + .replace("%LOWER_PREFIX%", lowerCasePrefix) + .replace("%CAMEL_PREFIX%", camelCasePrefix) + .replace("%META_INF_FOLDER%", toFolder((String) getField(fixture, "metaInfFolder"))); + } + + private String toFolder(String text) { + + if (text == null) { + return ""; + } + + return text.replace(".", "/"); + } + + private TestApiGeneratorMojo fixtureFromPom(String configName) throws Exception { + String goal = "create-test-api"; + + File pomFile = new File(getBasedir(), String.format("src/test/resources/%s/%s", getClass().getSimpleName(), configName + ".xml")); + assertThat(pomFile).exists(); + + MavenProject mavenProject = new CitrusOpenApiGeneratorMavenProjectStub(configName); + + TestApiGeneratorMojo testApiGeneratorMojo = (TestApiGeneratorMojo) lookupMojo(goal, pomFile); + testApiGeneratorMojo.setMavenProject(mavenProject); + testApiGeneratorMojo.setMojoExecution(newMojoExecution(goal)); + + return testApiGeneratorMojo; + } + +} diff --git a/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/java/org/citrusframework/maven/plugin/TestApiGeneratorMojoUnitTest.java b/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/java/org/citrusframework/maven/plugin/TestApiGeneratorMojoUnitTest.java new file mode 100644 index 0000000000..c63753525f --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/java/org/citrusframework/maven/plugin/TestApiGeneratorMojoUnitTest.java @@ -0,0 +1,286 @@ +package org.citrusframework.maven.plugin; + +import static org.citrusframework.maven.plugin.TestApiGeneratorMojo.DEFAULT_API_PACKAGE; +import static org.citrusframework.maven.plugin.TestApiGeneratorMojo.DEFAULT_API_TYPE; +import static org.citrusframework.maven.plugin.TestApiGeneratorMojo.DEFAULT_INVOKER_PACKAGE; +import static org.citrusframework.maven.plugin.TestApiGeneratorMojo.DEFAULT_META_INF_FOLDER; +import static org.citrusframework.maven.plugin.TestApiGeneratorMojo.DEFAULT_MODEL_PACKAGE; +import static org.citrusframework.maven.plugin.TestApiGeneratorMojo.DEFAULT_RESOURCE_FOLDER; +import static org.citrusframework.maven.plugin.TestApiGeneratorMojo.DEFAULT_SCHEMA_FOLDER_TEMPLATE; +import static org.citrusframework.maven.plugin.TestApiGeneratorMojo.DEFAULT_SOURCE_FOLDER; +import static org.citrusframework.maven.plugin.TestApiGeneratorMojo.DEFAULT_TARGET_NAMESPACE_TEMPLATE; +import static org.citrusframework.maven.plugin.TestApiGeneratorMojo.replaceDynamicVars; +import static org.citrusframework.maven.plugin.TestApiGeneratorMojo.replaceDynamicVarsToLowerCase; +import static java.lang.Boolean.TRUE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; +import static org.mockito.Mockito.doReturn; +import static org.springframework.test.util.ReflectionTestUtils.getField; + +import jakarta.validation.constraints.NotNull; +import org.citrusframework.maven.plugin.TestApiGeneratorMojo; +import org.citrusframework.maven.plugin.TestApiGeneratorMojo.ApiConfig; +import org.citrusframework.maven.plugin.TestApiGeneratorMojo.ApiType; +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; +import org.apache.maven.model.Build; +import org.apache.maven.plugin.MojoExecution; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.testing.AbstractMojoTestCase; +import org.apache.maven.project.MavenProject; +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.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.openapitools.codegen.plugin.CodeGenMojo; + + +/** + * @author Thorsten Schlathoelter + */ +@ExtendWith(MockitoExtension.class) +@SuppressWarnings({"JUnitMalformedDeclaration", "JUnitMixedFramework"}) +class TestApiGeneratorMojoUnitTest extends AbstractMojoTestCase { + + private TestApiGeneratorMojo fixture; + + @Mock + private Build buildMock; + + @Mock + private MavenProject mavenProjectMock; + + @Mock + private MojoExecution mojoExecutionMock; + + @BeforeEach + void beforeEach() { + fixture = new TestApiGeneratorMojo(); + } + + static Stream replaceDynamicVarsInPattern() { + return Stream.of( + arguments("%PREFIX%-aa-%VERSION%", "MyPrefix", "1", false, "MyPrefix-aa-1"), + arguments("%PREFIX%-aa-%VERSION%", "MyPrefix", null, false, "MyPrefix-aa"), + arguments("%PREFIX%/aa/%VERSION%", "MyPrefix", "1", false, "MyPrefix/aa/1"), + arguments("%PREFIX%/aa/%VERSION%", "MyPrefix", null, false, "MyPrefix/aa"), + arguments("%PREFIX%.aa.%VERSION%", "MyPrefix", "1", true, "myprefix.aa.1"), + arguments("%PREFIX%.aa.%VERSION%", "MyPrefix", null, true, "myprefix.aa") + ); + } + + @ParameterizedTest + @MethodSource + void replaceDynamicVarsInPattern(String pattern, String prefix, String version, boolean toLowerCasePrefix, String expectedResult) { + + if (toLowerCasePrefix) { + assertThat( + replaceDynamicVarsToLowerCase(pattern, prefix, version)).isEqualTo(expectedResult); + } else { + assertThat( + replaceDynamicVars(pattern, prefix, version)).isEqualTo(expectedResult); + } + } + + static Stream configureMojo() { + return Stream.of( + arguments("DefaultConfigWithoutVersion", createMinimalApiConfig(null), + createMinimalCodeGenMojoParams( + "schema/xsd", + "org.citrusframework.automation.mydefaultprefix", + "org.citrusframework.automation.mydefaultprefix.model", + "org.citrusframework.automation.mydefaultprefix.api", + "http://www.citrusframework.org/citrus-test-schema/mydefaultprefix-api" + )), + arguments("DefaultConfigWithVersion", createMinimalApiConfig("v1"), + createMinimalCodeGenMojoParams( + "schema/xsd/v1", + "org.citrusframework.automation.mydefaultprefix.v1", + "org.citrusframework.automation.mydefaultprefix.v1.model", + "org.citrusframework.automation.mydefaultprefix.v1.api", + "http://www.citrusframework.org/citrus-test-schema/v1/mydefaultprefix-api" + )), + arguments("CustomConfigWithoutVersion", createFullApiConfig(null), + createCustomCodeGenMojoParams( + "schema/xsd", + "my.mycustomprefix.invoker.package", + "my.mycustomprefix.model.package", + "my.mycustomprefix.api.package", + "myNamespace/citrus-test-schema/mycustomprefix" + )), + arguments("CustomConfigWithVersion", createFullApiConfig("v1"), + createCustomCodeGenMojoParams( + "schema/xsd/v1", + "my.mycustomprefix.v1.invoker.package", + "my.mycustomprefix.v1.model.package", + "my.mycustomprefix.v1.api.package", + "myNamespace/citrus-test-schema/mycustomprefix/v1" + )) + ); + } + + @ParameterizedTest(name = "{0}") + @MethodSource + void configureMojo(String name, ApiConfig apiConfig, CodeGenMojoParams controlParams) throws MojoExecutionException { + doReturn("target").when(buildMock).getDirectory(); + doReturn(buildMock).when(mavenProjectMock).getBuild(); + fixture.setMavenProject(mavenProjectMock); + fixture.setMojoExecution(mojoExecutionMock); + + CodeGenMojo codeGenMojo = fixture.configureCodeGenMojo(apiConfig); + assertThat(getField(codeGenMojo, "project")).isEqualTo(mavenProjectMock); + assertThat(getField(codeGenMojo, "mojo")).isEqualTo(mojoExecutionMock); + assertThat(((File) getField(codeGenMojo, "output"))).hasName(controlParams.output); + assertThat(getField(codeGenMojo, "inputSpec")).isEqualTo(controlParams.source); + assertThat(getField(codeGenMojo, "generateSupportingFiles")).isEqualTo(TRUE); + assertThat(getField(codeGenMojo, "generatorName")).isEqualTo("java-citrus"); + + //noinspection unchecked + assertThat((Map)getField(codeGenMojo, "configOptions")) + .containsExactlyInAnyOrderEntriesOf(controlParams.configOptions); + } + + /** + * Create an {@link ApiConfig} with the minimal configuration, that is required. All other values will be chosen as defaults. + */ + @NotNull + private static ApiConfig createMinimalApiConfig(String version) { + ApiConfig apiConfig = new ApiConfig(); + apiConfig.setPrefix("MyDefaultPrefix"); + apiConfig.setSource("myDefaultSource"); + apiConfig.setVersion(version); + + return apiConfig; + } + + /** + * Create an {@link ApiConfig} with all possible configurations, no defaults. + */ + @NotNull + private static ApiConfig createFullApiConfig(String version) { + ApiConfig apiConfig = new ApiConfig(); + apiConfig.setPrefix("MyCustomPrefix"); + apiConfig.setSource("myCustomSource"); + apiConfig.setApiPackage("my.%PREFIX%.%VERSION%.api.package"); + apiConfig.setInvokerPackage("my.%PREFIX%.%VERSION%.invoker.package"); + apiConfig.setModelPackage("my.%PREFIX%.%VERSION%.model.package"); + apiConfig.setEndpoint("myEndpoint"); + apiConfig.setTargetXmlnsNamespace("myNamespace/citrus-test-schema/%PREFIX%/%VERSION%"); + apiConfig.setUseTags(false); + apiConfig.setType(ApiType.SOAP); + apiConfig.setVersion(version); + + return apiConfig; + } + + @NotNull + private static CodeGenMojoParams createMinimalCodeGenMojoParams(String schemaFolder, String invokerPackage, String modelPackage, String apiPackage, String targetXmlnsNamespace) { + + Map configOptionsControlMap = new HashMap<>(); + configOptionsControlMap.put("prefix", "MyDefaultPrefix"); + configOptionsControlMap.put("generatedSchemaFolder", schemaFolder); + configOptionsControlMap.put("invokerPackage", invokerPackage); + configOptionsControlMap.put("apiPackage", apiPackage); + configOptionsControlMap.put("modelPackage", modelPackage); + configOptionsControlMap.put("resourceFolder", "generated-test-resources"); + configOptionsControlMap.put("sourceFolder", "generated-test-sources"); + configOptionsControlMap.put("apiEndpoint", "mydefaultprefixEndpoint"); + configOptionsControlMap.put("targetXmlnsNamespace", targetXmlnsNamespace); + configOptionsControlMap.put("apiType", "REST"); + configOptionsControlMap.put("useTags", true); + + return new CodeGenMojoParams("target", "myDefaultSource", configOptionsControlMap); + } + + @NotNull + private static CodeGenMojoParams createCustomCodeGenMojoParams(String schemaFolder, String invokerPackage, String modelPackage, String apiPackage, String targetXmlnsNamespace) { + + Map configOptionsControlMap = new HashMap<>(); + configOptionsControlMap.put("prefix", "MyCustomPrefix"); + configOptionsControlMap.put("generatedSchemaFolder", schemaFolder); + configOptionsControlMap.put("invokerPackage", invokerPackage); + configOptionsControlMap.put("modelPackage", modelPackage); + configOptionsControlMap.put("apiPackage", apiPackage); + configOptionsControlMap.put("resourceFolder", "generated-test-resources"); + configOptionsControlMap.put("sourceFolder", "generated-test-sources"); + configOptionsControlMap.put("apiEndpoint", "myEndpoint"); + configOptionsControlMap.put("targetXmlnsNamespace", targetXmlnsNamespace); + configOptionsControlMap.put("apiType", "SOAP"); + configOptionsControlMap.put("useTags", false); + + return new CodeGenMojoParams("target", "myCustomSource", configOptionsControlMap); + } + + @Nested + class ApiConfigTest { + + private ApiConfig configFixture; + + @BeforeEach + void beforeEach() { + configFixture = new ApiConfig(); + } + + @Test + void apiPackagePathDefault() { + assertThat(configFixture.getApiPackage()).isEqualTo(DEFAULT_API_PACKAGE); + } + + @Test + void invokerPackagePathDefault() { + assertThat(configFixture.getInvokerPackage()).isEqualTo(DEFAULT_INVOKER_PACKAGE); + } + + @Test + void modelPackagePathDefault() { + assertThat(configFixture.getModelPackage()).isEqualTo(DEFAULT_MODEL_PACKAGE); + } + + @Test + void targetXmlnsNamespaceDefault() { + assertThat(configFixture.getTargetXmlnsNamespace()).isEqualTo(DEFAULT_TARGET_NAMESPACE_TEMPLATE); + } + + @Test + void endpointDefault() { + configFixture.setPrefix("MyPrefix"); + assertThat(configFixture.qualifiedEndpoint()).isEqualTo("myprefixEndpoint"); + } + + @Test + void apiTypeDefault() { + assertThat(configFixture.getType()).isEqualTo(DEFAULT_API_TYPE); + } + + @Test + void schemaFolderDefault() { + assertThat((String)getField(fixture, "schemaFolder")).isEqualTo(DEFAULT_SCHEMA_FOLDER_TEMPLATE); + } + + @Test + void sourceFolderDefault() { + assertThat((String) getField(fixture, "sourceFolder")).isEqualTo(DEFAULT_SOURCE_FOLDER); + } + + @Test + void resourceFolderDefault() { + assertThat((String) getField(fixture, "resourceFolder")).isEqualTo(DEFAULT_RESOURCE_FOLDER); + } + + @Test + void metaInfFolderDefault() { + assertThat((String) getField(fixture, "metaInfFolder")).isEqualTo(DEFAULT_META_INF_FOLDER); + } + } + + private record CodeGenMojoParams(String output, String source, Map configOptions) { + + } +} diff --git a/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/java/org/citrusframework/maven/plugin/stubs/CitrusOpenApiGeneratorMavenProjectStub.java b/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/java/org/citrusframework/maven/plugin/stubs/CitrusOpenApiGeneratorMavenProjectStub.java new file mode 100644 index 0000000000..94345c88ad --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/java/org/citrusframework/maven/plugin/stubs/CitrusOpenApiGeneratorMavenProjectStub.java @@ -0,0 +1,42 @@ +package org.citrusframework.maven.plugin.stubs; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import org.apache.maven.model.Build; +import org.apache.maven.model.io.xpp3.MavenXpp3Reader; +import org.apache.maven.plugin.testing.stubs.MavenProjectStub; +import org.codehaus.plexus.PlexusTestCase; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +public class CitrusOpenApiGeneratorMavenProjectStub extends MavenProjectStub { + + private final String config; + + public CitrusOpenApiGeneratorMavenProjectStub(String config) { + this.config = config; + initModel(); + initBuild(); + } + + private void initBuild() { + Build build = new Build(); + build.setDirectory(getBasedir() + "/target"); + build.setSourceDirectory(getBasedir() + "/src"); + setBuild(build); + } + + private void initModel() { + MavenXpp3Reader reader = new MavenXpp3Reader(); + try { + setModel(reader.read(new FileReader("pom.xml"))); + } catch (IOException | XmlPullParserException e) { + throw new IllegalArgumentException(e); + } + } + + @Override + public File getBasedir() { + return new File(PlexusTestCase.getBasedir() + "/target/" + config); + } +} diff --git a/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/resources/TestApiGeneratorMojoIntegrationTest/pom-full-config.xml b/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/resources/TestApiGeneratorMojoIntegrationTest/pom-full-config.xml new file mode 100644 index 0000000000..97fcd85dd4 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/resources/TestApiGeneratorMojoIntegrationTest/pom-full-config.xml @@ -0,0 +1,42 @@ + + 4.0.0 + + full-config + + + + + citrus-test-api-generator-maven-plugin + + + + org.mypackage.%PREFIX%.api + myEndpoint + org.mypackage.%PREFIX%.invoker + org.mypackage.%PREFIX%.model + Full + api/test-api.yml + "http://myNamespace" + + + generated-sources-mod + generated-resources-mod + myschema/xsd + main-mod/resources-mod/META-INF-MOD + + + + + create-test-api + + + + + + + + diff --git a/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/resources/TestApiGeneratorMojoIntegrationTest/pom-full-with-version-config.xml b/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/resources/TestApiGeneratorMojoIntegrationTest/pom-full-with-version-config.xml new file mode 100644 index 0000000000..401ea5fa50 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/resources/TestApiGeneratorMojoIntegrationTest/pom-full-with-version-config.xml @@ -0,0 +1,42 @@ + + 4.0.0 + + full-config + + + + + citrus-test-api-generator-maven-plugin + + + + org.mypackage.%PREFIX%.api + myEndpoint + org.mypackage.%PREFIX%.invoker + org.mypackage.%PREFIX%.model + FullWithVersion + api/test-api.yml + "http://myNamespace" + v1 + + + generated-sources-mod + generated-resources-mod + myschema/xsd + main-mod/resources-mod/META-INF-MOD + + + + + create-test-api + + + + + + + diff --git a/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/resources/TestApiGeneratorMojoIntegrationTest/pom-minimal-config.xml b/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/resources/TestApiGeneratorMojoIntegrationTest/pom-minimal-config.xml new file mode 100644 index 0000000000..ffb294db1d --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/resources/TestApiGeneratorMojoIntegrationTest/pom-minimal-config.xml @@ -0,0 +1,32 @@ + + 4.0.0 + + minimal-config + + + + + citrus-test-api-generator-maven-plugin + + + + Minimal + api/test-api.yml + + + + + + + create-test-api + + + + + + + diff --git a/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/resources/TestApiGeneratorMojoIntegrationTest/pom-minimal-with-version-config.xml b/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/resources/TestApiGeneratorMojoIntegrationTest/pom-minimal-with-version-config.xml new file mode 100644 index 0000000000..90ee2d6e4d --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/resources/TestApiGeneratorMojoIntegrationTest/pom-minimal-with-version-config.xml @@ -0,0 +1,33 @@ + + 4.0.0 + + minimal-config + + + + + citrus-test-api-generator-maven-plugin + + + + MinimalWithVersion + api/test-api.yml + v2 + + + + + + + create-test-api + + + + + + + diff --git a/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/resources/TestApiGeneratorMojoIntegrationTest/pom-missing-prefix.xml b/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/resources/TestApiGeneratorMojoIntegrationTest/pom-missing-prefix.xml new file mode 100644 index 0000000000..fbdd07bd0d --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/resources/TestApiGeneratorMojoIntegrationTest/pom-missing-prefix.xml @@ -0,0 +1,32 @@ + + 4.0.0 + + missing-prefix + + + + + citrus-test-api-generator-maven-plugin + + + + mySource + + + + + + + create-test-api + + + + + + + + diff --git a/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/resources/TestApiGeneratorMojoIntegrationTest/pom-missing-source.xml b/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/resources/TestApiGeneratorMojoIntegrationTest/pom-missing-source.xml new file mode 100644 index 0000000000..03f9f49074 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/resources/TestApiGeneratorMojoIntegrationTest/pom-missing-source.xml @@ -0,0 +1,32 @@ + + 4.0.0 + + missing-source + + + + + citrus-test-api-generator-maven-plugin + + + + MissingSource + + + + + + + create-test-api + + + + + + + + diff --git a/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/resources/TestApiGeneratorMojoIntegrationTest/pom-multi-config.xml b/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/resources/TestApiGeneratorMojoIntegrationTest/pom-multi-config.xml new file mode 100644 index 0000000000..f71d499ba4 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/resources/TestApiGeneratorMojoIntegrationTest/pom-multi-config.xml @@ -0,0 +1,40 @@ + + 4.0.0 + + multi-config + + + + + citrus-test-api-generator-maven-plugin + + + + Multi1 + api/test-api.yml + + + Multi2 + api/test-api.yml + + + Multi3 + api/test-api.yml + + + + + + + create-test-api + + + + + + + diff --git a/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/resources/TestApiGeneratorMojoIntegrationTest/pom-soap-config.xml b/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/resources/TestApiGeneratorMojoIntegrationTest/pom-soap-config.xml new file mode 100644 index 0000000000..434de65370 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/resources/TestApiGeneratorMojoIntegrationTest/pom-soap-config.xml @@ -0,0 +1,33 @@ + + 4.0.0 + + soap-config + + + + + citrus-test-api-generator-maven-plugin + + + + Minimal + api/test-api.yml + SOAP + + + + + + + create-test-api + + + + + + + diff --git a/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/resources/api/test-api.yml b/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/resources/api/test-api.yml new file mode 100644 index 0000000000..eea3f01335 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-maven-plugin/src/test/resources/api/test-api.yml @@ -0,0 +1,125 @@ +openapi: 3.0.3 +info: + title: Schema Test API + version: 1.0.0 + description: | + A very simple test OpenAPI specification that is compliant with the random response generator (e.g. only contains responses of + media-type `application/json`). + +servers: + - url: http://localhost:9000/services/rest/ping/v1 + - url: http://localhost:9000/ping/v1 + +paths: + /ping/{id}: + put: + tags: + - ping + summary: Do the ping + operationId: doPing + parameters: + - name: id + in: path + description: Id to ping + required: true + explode: true + schema: + type: integer + format: int64 + - name: q1 + in: query + description: Some queryParameter + required: true + explode: true + schema: + type: integer + format: int64 + - name: api_key + in: header + required: true + schema: + type: string + requestBody: + description: ping data + content: + application/json: + schema: + $ref: '#/components/schemas/PingReqType' + required: true + responses: + 200: + description: successful operation + headers: + Ping-Time: + required: false + description: response time + schema: + type: integer + format: int64 + content: + application/json: + schema: + $ref: '#/components/schemas/PingRespType' + 201: + description: successful operation + headers: + Ping-Time: + required: false + description: response time + schema: + type: integer + format: int64 + content: + application/xml: + schema: + $ref: '#/components/schemas/PingRespType' + application/json: + schema: + $ref: '#/components/schemas/PingRespType' + 400: + description: Some error + content: + text/plain: + schema: + type: string + /pung/{id}: + get: + tags: + - pung + summary: Do the pung + operationId: doPung + parameters: + - name: id + in: path + description: Id to pung + required: true + explode: true + schema: + type: integer + format: int64 + responses: + 200: + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/PingRespType' + 400: + description: Invalid status value + content: {} +components: + schemas: + PingReqType: + type: object + properties: + id: + type: integer + format: int64 + PingRespType: + type: object + properties: + id: + type: integer + format: int64 + value: + type: string diff --git a/test-api-generator/pom.xml b/test-api-generator/pom.xml new file mode 100644 index 0000000000..f6298e3e11 --- /dev/null +++ b/test-api-generator/pom.xml @@ -0,0 +1,37 @@ + + 4.0.0 + + + org.citrusframework + citrus + 4.3.0-SNAPSHOT + ../pom.xml + + + citrus-test-api-generator + Citrus :: Test API Generator + Citrus Test API Generator + pom + + + true + + + + + org.junit.jupiter + junit-jupiter-engine + ${junit.jupiter.version} + test + + + + + citrus-test-api-generator-core + citrus-test-api-generator-maven-plugin + + + + diff --git a/validation/citrus-validation-text/src/main/java/org/citrusframework/validation/text/PlainTextMessageValidator.java b/validation/citrus-validation-text/src/main/java/org/citrusframework/validation/text/PlainTextMessageValidator.java index d7152db132..8fa005c685 100644 --- a/validation/citrus-validation-text/src/main/java/org/citrusframework/validation/text/PlainTextMessageValidator.java +++ b/validation/citrus-validation-text/src/main/java/org/citrusframework/validation/text/PlainTextMessageValidator.java @@ -16,9 +16,12 @@ package org.citrusframework.validation.text; +import static java.lang.Boolean.parseBoolean; +import static java.lang.Integer.parseInt; +import static org.citrusframework.message.MessagePayloadUtils.normalizeWhitespace; + import java.util.regex.Matcher; import java.util.regex.Pattern; - import org.citrusframework.CitrusSettings; import org.citrusframework.context.TestContext; import org.citrusframework.exceptions.ValidationException; @@ -29,9 +32,6 @@ import org.citrusframework.validation.context.ValidationContext; import org.citrusframework.validation.matcher.ValidationMatcherUtils; -import static java.lang.Boolean.parseBoolean; -import static java.lang.Integer.parseInt; - /** * Plain text validator using simple String comparison. * @@ -59,8 +59,8 @@ public void validateMessage(Message receivedMessage, Message controlMessage, Tes logger.debug("Start text message validation"); try { - String resultValue = normalizeWhitespace(receivedMessage.getPayload(String.class).trim()); - String controlValue = normalizeWhitespace(context.replaceDynamicContentInString(controlMessage.getPayload(String.class).trim())); + String resultValue = normalizeWhitespace(receivedMessage.getPayload(String.class).trim(), ignoreWhitespace, ignoreNewLineType); + String controlValue = normalizeWhitespace(context.replaceDynamicContentInString(controlMessage.getPayload(String.class).trim()), ignoreWhitespace, ignoreNewLineType); controlValue = processIgnoreStatements(controlValue, resultValue); controlValue = processVariableStatements(controlValue, resultValue, context); @@ -181,38 +181,6 @@ private void validateText(String receivedMessagePayload, String controlMessagePa } } - /** - * Normalize whitespace characters if appropriate. Based on system property settings this method normalizes - * new line characters exclusively or filters all whitespaces such as double whitespaces and new lines. - * - * @param payload - * @return - */ - private String normalizeWhitespace(String payload) { - if (ignoreWhitespace) { - StringBuilder result = new StringBuilder(); - boolean lastWasSpace = true; - for (int i = 0; i < payload.length(); i++) { - char c = payload.charAt(i); - if (Character.isWhitespace(c)) { - if (!lastWasSpace) { - result.append(' '); - } - lastWasSpace = true; - } else { - result.append(c); - lastWasSpace = false; - } - } - return result.toString().trim(); - } - - if (ignoreNewLineType) { - return payload.replaceAll("\\r(\\n)?", "\n"); - } - - return payload; - } @Override public boolean supportsMessageType(String messageType, Message message) {