diff --git a/core/citrus-base/src/main/java/org/citrusframework/actions/SendMessageAction.java b/core/citrus-base/src/main/java/org/citrusframework/actions/SendMessageAction.java index 53f6fcabd6..20fa11ccff 100644 --- a/core/citrus-base/src/main/java/org/citrusframework/actions/SendMessageAction.java +++ b/core/citrus-base/src/main/java/org/citrusframework/actions/SendMessageAction.java @@ -381,7 +381,7 @@ public String getEndpointUri() { /** * Action builder. */ - public static final class Builder extends SendMessageActionBuilder { + public static class Builder extends SendMessageActionBuilder { /** * Fluent API action building entry method used in Java DSL. diff --git a/test-api-generator/citrus-test-api-generator-core/pom.xml b/test-api-generator/citrus-test-api-generator-core/pom.xml index 5e1af11aa2..3af2a0689e 100644 --- a/test-api-generator/citrus-test-api-generator-core/pom.xml +++ b/test-api-generator/citrus-test-api-generator-core/pom.xml @@ -32,6 +32,11 @@ citrus-http ${project.version} + + org.citrusframework + citrus-openapi + ${project.version} + org.citrusframework citrus-spring diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/java/org/citrusframework/openapi/generator/TestApiClientRequestActionBuilder.java b/test-api-generator/citrus-test-api-generator-core/src/main/java/org/citrusframework/openapi/generator/TestApiClientRequestActionBuilder.java new file mode 100644 index 0000000000..7945002312 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/main/java/org/citrusframework/openapi/generator/TestApiClientRequestActionBuilder.java @@ -0,0 +1,69 @@ +package org.citrusframework.openapi.generator; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import org.citrusframework.actions.SendMessageAction; +import org.citrusframework.openapi.OpenApiSpecification; +import org.citrusframework.openapi.actions.OpenApiClientRequestActionBuilder; +import org.springframework.http.MediaType; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +public class TestApiClientRequestActionBuilder extends OpenApiClientRequestActionBuilder { + + // TODO: do we really need this? + protected OpenApiSpecification openApiSpec; + + private final String path; + + private final Map pathParameters = new HashMap<>(); + + private final MultiValueMap formData = new LinkedMultiValueMap<>(); + + // TODO: can we just pass in the operation? + public TestApiClientRequestActionBuilder(OpenApiSpecification openApiSpec, String method, String path, String operationName) { + super(openApiSpec, "%s_%s".formatted(method, path)); + name(String.format("%s:%s", "PetStore".toLowerCase(), operationName)); + getMessageBuilderSupport().header("citrus_open_api_operation_name", operationName); + getMessageBuilderSupport().header("citrus_open_api_method", method); + getMessageBuilderSupport().header("citrus_open_api_path", path); + + this.openApiSpec = openApiSpec; + this.path = path; + } + + protected void pathParameter(String name, String value) { + pathParameters.put(name, value); + } + + protected void formData(String name, String value) { + formData.add(name, value); + } + + protected String qualifiedPath(String path) { + + String qualifiedPath = path; + for (Entry entry : pathParameters.entrySet()) { + qualifiedPath = qualifiedPath.replace("{%s}".formatted(entry.getKey()), entry.getValue()); + } + return qualifiedPath; + } + + protected String toQueryParam(String...arrayElements) { + return String.join(",", arrayElements); + } + + @Override + public SendMessageAction doBuild() { + // TODO: register callback to modify builder + path(qualifiedPath(path)); + if (!formData.isEmpty()) { + // TODO: do we have to explicitly set the content type or is this done by citrus + messageBuilderSupport.contentType(MediaType.MULTIPART_FORM_DATA_VALUE); + getMessageBuilderSupport().body(formData); + } + return super.doBuild(); + } + + } \ No newline at end of file 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 index a023345fb7..cb2473c14b 100644 --- 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 @@ -2,34 +2,15 @@ package {{package}}; -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; +import org.citrusframework.http.client.HttpClient; +import org.citrusframework.openapi.OpenApiSpecification; +import org.citrusframework.openapi.generator.TestApiClientRequestActionBuilder; +import org.citrusframework.openapi.generator.rest.petstore.model.Pet; +import org.citrusframework.testapi.GeneratedApi; + +import {{invokerPackage}}.citrus.{{prefix}}AbstractSendAction; {{>additionalModelTypeAnnotations}}{{>generatedAnnotation}}{{#discriminator}}{{>typeInfoAnnotation}}{{/discriminator}}{{>xmlAnnotation}} public class {{classname}} implements GeneratedApi @@ -37,223 +18,67 @@ public class {{classname}} implements GeneratedApi public static final {{classname}} INSTANCE = new {{classname}}(); + private OpenApiSpecification openApiSpecification = null; + public String getApiTitle() { - return "{{appName}}"; + return "{{appName}}"; } public String getApiVersion() { - return "{{appVersion}}"; + return "{{appVersion}}"; } public String getApiPrefix() { - return "{{prefix}}"; + return "{{prefix}}"; } public Map getApiInfoExtensions() { Map infoExtensionMap = new HashMap<>(); {{#infoExtensions}} {{#entrySet}} - infoExtensionMap.put("{{key}}", "{{value}}"); + infoExtensionMap.put("{{key}}", "{{value}}"); {{/entrySet}} {{/infoExtensions}} return infoExtensionMap; } - {{#operations}} +{{#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 {{operationIdCamelCase}}ActionBuilder {{operationId}}() { + return new {{operationIdCamelCase}}ActionBuilder(); } - 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}} + {{/operation}} +{{/operations}} - 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); +{{#operations}} + {{#operation}} + public class {{operationIdCamelCase}}ActionBuilder extends TestApiClientRequestActionBuilder { - httpClientRequestActionBuilder.build().execute(context); + private static final String METHOD = "{{httpMethod}}"; - coverageLogger.trace(coverageMarker, "{{operationId}};{{#lambda.uppercase}}{{httpMethod}}{{/lambda.uppercase}};\"" + - query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + - bodyLog); - } - {{#queryParams}} + private static final String ENDPOINT = "{{path}}"; - public void set{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}}(String {{paramName}}) { - this.{{paramName}} = {{paramName}}; - } - {{/queryParams}} - {{#pathParams}} + private static final String OPERATION_NAME = "{{operationId}}"; - public void set{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}}(String {{paramName}}) { - this.{{paramName}} = {{paramName}}; - } - {{/pathParams}} - {{#isMultipart}} - {{#formParams}} + public AddPetActionBuilder() { + super(openApiSpecification, METHOD, ENDPOINT, OPERATION_NAME); + } - public void set{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}}(String {{paramName}}) { - this.{{paramName}} = {{paramName}}; - } - {{/formParams}} - {{/isMultipart}} - {{#authMethods}}{{#isBasic}} + {{#queryParams}} + public {{operationIdCamelCase}}ActionBuilder with{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}}(String {{paramName}}) { + queryParam("{{paramName}}", {{paramName}}); + return this; + } + {{/queryParams}} - public void setBasicUsername(String basicUsername) { - this.basicUsername = basicUsername; - } + // public AddPetActionBuilder withPet(Pet pet) { + // // TODO: fix this + // getMessageBuilderSupport().body(pet.toString()); + // return this; + // } - 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}} -} +{{/operations}} +} \ No newline at end of file diff --git a/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api_old.mustache b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api_old.mustache new file mode 100644 index 0000000000..a023345fb7 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/api_old.mustache @@ -0,0 +1,259 @@ +{{>licenseInfo}} + +package {{package}}; + +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; + +{{>additionalModelTypeAnnotations}}{{>generatedAnnotation}}{{#discriminator}}{{>typeInfoAnnotation}}{{/discriminator}}{{>xmlAnnotation}} +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/openApi.mustache b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/openApi.mustache new file mode 100644 index 0000000000..b59349ae63 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/main/resources/java-citrus/openApi.mustache @@ -0,0 +1,83 @@ +{{>licenseInfo}} + +package {{package}}; + +import java.util.HashMap; +import java.util.Map; +import org.citrusframework.http.client.HttpClient; +import org.citrusframework.openapi.OpenApiSpecification; +import org.citrusframework.openapi.generator.rest.petstore.model.Pet; +import org.citrusframework.testapi.GeneratedApi; + +import {{invokerPackage}}.citrus.{{prefix}}AbstractSendAction; + +{{>additionalModelTypeAnnotations}}{{>generatedAnnotation}}{{#discriminator}}{{>typeInfoAnnotation}}{{/discriminator}}{{>xmlAnnotation}} +public class {{classname}} implements GeneratedApi +{ + + public static final {{classname}} INSTANCE = new {{classname}}(); + + private OpenApiSpecification openApiSpecification = null; + + 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}} + public {{operationIdCamelCase}}ActionBuilder {{operationId}}() { + return new {{operationIdCamelCase}}ActionBuilder(); + } + {{/operation}} + {{/operations}} + + {{#operations}} + {{#operation}} + public class {{operationIdCamelCase}}ActionBuilder extends {{prefix}}AbstractSendAction.Builder { + + private static final String METHOD = ""{{httpMethod}}"; + + private static final String ENDPOINT = "{{path}}"; + + private static final String OPERATION_NAME = "{{operationId}}"; + + public AddPetActionBuilder() { + super(openApiSpecification, METHOD, ENDPOINT, OPERATION_NAME); + } + + {{#queryParams}} + public {{operationIdCamelCase}}ActionBuilder with{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}}(String {{paramName}}) { + queryParam("{{paramName}}", {{paramName}}); + return this; + } + } + {{/queryParams}} + +// public AddPetActionBuilder withPet(Pet pet) { +// // TODO: fix this +// getMessageBuilderSupport().body(pet.toString()); +// return this; +// } + + } + {{/operation}} + {{/operations}} +} \ No newline at end of file diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/GetPetByIdIT.java b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/GetPetByIdIT.java index 72b9369ca3..77961399e3 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/GetPetByIdIT.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/GetPetByIdIT.java @@ -2,7 +2,15 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.citrusframework.container.Assert.Builder.assertException; +import static org.citrusframework.http.actions.HttpActionBuilder.http; +import static org.citrusframework.openapi.generator.sample.OpenApiPetStore.AddressEntityValidationContext.Builder.address; +import static org.citrusframework.openapi.generator.sample.OpenApiPetStore.AggregateEntityValidationContext.Builder.allOf; +import static org.citrusframework.openapi.generator.sample.OpenApiPetStore.AggregateEntityValidationContext.Builder.anyOf; +import static org.citrusframework.openapi.generator.sample.OpenApiPetStore.AggregateEntityValidationContext.Builder.oneOf; +import static org.citrusframework.openapi.generator.sample.OpenApiPetStore.PetEntityValidationContext.Builder.pet; +import static org.citrusframework.openapi.generator.sample.OpenApiPetStore.openApiPetStore; import static org.citrusframework.util.FileUtils.readToString; +import static org.citrusframework.validation.json.JsonPathMessageValidationContext.Builder.jsonPath; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -10,6 +18,7 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.List; import java.util.Map; import org.citrusframework.TestCaseRunner; import org.citrusframework.annotations.CitrusResource; @@ -19,6 +28,8 @@ import org.citrusframework.endpoint.EndpointConfiguration; import org.citrusframework.http.client.HttpClient; import org.citrusframework.http.client.HttpEndpointConfiguration; +import org.citrusframework.http.server.HttpServer; +import org.citrusframework.http.server.HttpServerBuilder; import org.citrusframework.junit.jupiter.spring.CitrusSpringExtension; import org.citrusframework.message.DefaultMessage; import org.citrusframework.message.Message; @@ -27,7 +38,13 @@ import org.citrusframework.openapi.generator.GetPetByIdIT.Config; import org.citrusframework.openapi.generator.rest.petstore.request.PetApi.GetPetByIdRequest; import org.citrusframework.openapi.generator.rest.petstore.spring.PetStoreBeanConfiguration; +import org.citrusframework.openapi.generator.sample.OpenApiPetStore.AddressEntityValidationContext; +import org.citrusframework.openapi.generator.sample.OpenApiPetStore.AggregateEntityValidationContext; +import org.citrusframework.openapi.generator.sample.OpenApiPetStore.PetEntityValidationContext.Builder; +import org.citrusframework.openapi.generator.sample.PetApi; +import org.citrusframework.spi.BindToRegistry; import org.citrusframework.spi.Resources; +import org.citrusframework.util.SocketUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -42,6 +59,16 @@ @SpringBootTest(classes = {PetStoreBeanConfiguration.class, CitrusSpringConfig.class, Config.class}) class GetPetByIdIT { + private final int port = SocketUtils.findAvailableTcpPort(8080); + + @BindToRegistry + private final HttpServer httpServer = new HttpServerBuilder() + .port(port) + .timeout(5000L) + .autoStart(true) + .defaultStatus(HttpStatus.NO_CONTENT) + .build(); + @Autowired private GetPetByIdRequest getPetByIdRequest; @@ -66,22 +93,145 @@ public void beforeTest() throws IOException { */ @Test @CitrusTest - void testByJsonPath(@CitrusResource TestCaseRunner runner) { - - // Given - getPetByIdRequest.setPetId("1234"); + void testByEntityMatcher(@CitrusResource TestCaseRunner runner) { + + when(PetApi.openApiPetStore(httpClient) + .getPetById() + .withId("1234") + .fork(true)); + + runner.then(http().server(httpServer) + .receive() + .get("/pet/${petId}") + .message() + .accept("@contains('application/json')@")); + + runner.then(http().server(httpServer) + .send() + .response(HttpStatus.OK) + .message() + .body(Resources.create("classpath:org/citrusframework/openapi/petstore/pet.json")) + .contentType("application/json")); + + runner.then(openApiPetStore(httpClient) + .receivePetById(HttpStatus.OK) + .message() + .validate(pet().id("1234") + .name("Garfield") + .category("Cat") + .address(address -> address + .street("Nina Hagen Hang") + .zip("12345") + .city("Hagen ATW")) + .owners(anyOf(List.of( + owner -> owner.name("Peter Lustig"), + owner -> owner.name("Hans Meier") + ))) + .owners(oneOf(List.of( + owner -> owner.name("Seppel Hinterhuber") + ))) + .urls(0, "url1") + .urls(1, "url2") + .urls("@contains('url1', 'url2')")). + validate(jsonPath().expression("$.name", "Garfield"))); + + runner.then(openApiPetStore(httpClient) + .receivePetById200() + .withPet(validator -> validator.id("1234") + .name("Garfield") + .category("Cat") + .urls(0,"url1") + .urls(1,"url2") + .urls("@contains('url1', 'url2')")). + validate(jsonPath().expression("$.name", "Garfield")) + ); + } - // Then - getPetByIdRequest.setResponseStatus(HttpStatus.OK.value()); - getPetByIdRequest.setResponseReasonPhrase(HttpStatus.OK.getReasonPhrase()); + @Test + @CitrusTest + void testFindByStatus(@CitrusResource TestCaseRunner runner) { + + when(openApiPetStore(httpClient) + .findByStatus() + .withStatus("SOLD") + .fork(true)); + + runner.then(http().server(httpServer) + .receive() + .get("/pet/${petId}") + .message() + .accept("@contains('application/json')@")); + + runner.then(http().server(httpServer) + .send() + .response(HttpStatus.OK) + .message() + .body(Resources.create("classpath:org/citrusframework/openapi/petstore/pet.json")) + .contentType("application/json")); + + runner.then(openApiPetStore(httpClient) + .receivePetById(HttpStatus.OK) + .message() + .validate(pet().id("1234") + .name("Garfield") + .category("Cat") + .address(address -> address + .street("Nina Hagen Hang") + .zip("12345") + .city("Hagen ATW")) + .owners(anyOf(List.of( + owner -> owner.name("Peter Lustig"), + owner -> owner.name("Hans Meier") + ))) + .owners(oneOf(List.of( + owner -> owner.name("Seppel Hinterhuber") + ))) + .urls(0, "url1") + .urls(1, "url2") + .urls("@contains('url1', 'url2')")). + validate(jsonPath().expression("$.name", "Garfield"))); + + runner.then(openApiPetStore(httpClient) + .receivePetById200() + .withPet(validator -> validator.id("1234") + .name("Garfield") + .category("Cat") + .urls(0,"url1") + .urls(1,"url2") + .urls("@contains('url1', 'url2')")). + validate(jsonPath().expression("$.name", "Garfield")) + ); + } - // Assert body by json path - getPetByIdRequest.setResponseValue(Map.of("$.name", "Snoopy")); + @Test + @CitrusTest + void testByJsonPath(@CitrusResource TestCaseRunner runner) { - // When - runner.$(getPetByIdRequest); + when(openApiPetStore(httpClient) + .getPetById() + .withPetId("1234") + .fork(true)); + + runner.then(http().server(httpServer) + .receive() + .get("/pet/${petId}") + .message() + .accept("@contains('application/json')@")); + + runner.then(http().server(httpServer) + .send() + .response(HttpStatus.OK) + .message() + .body(Resources.create("classpath:org/citrusframework/openapi/petstore/pet.json")) + .contentType("application/json")); + + runner.then(openApiPetStore(httpClient) + .receivePetById(HttpStatus.OK) + .message().validate(jsonPath().expression("$.name", "Garfield")) + ); } + /** * TODO #1161 - Improve with builder pattern */ diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/OpenApiPetStoreTest.java b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/OpenApiPetStoreTest.java new file mode 100644 index 0000000000..372219d262 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/OpenApiPetStoreTest.java @@ -0,0 +1,181 @@ +package org.citrusframework.openapi.generator; + +import static org.citrusframework.openapi.generator.sample.OpenApiPetStore.PetEntityValidationContext.Builder.pet; +import static org.mockito.Mockito.mock; + +import com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.ReadContext; +import java.util.Map; +import net.minidev.json.parser.JSONParser; +import net.minidev.json.parser.ParseException; +import org.citrusframework.context.TestContext; +import org.citrusframework.exceptions.CitrusRuntimeException; +import org.citrusframework.functions.DefaultFunctionRegistry; +import org.citrusframework.json.JsonPathUtils; +import org.citrusframework.message.DefaultMessage; +import org.citrusframework.message.Message; +import org.citrusframework.openapi.generator.sample.OpenApiPetStore.AggregateEntityValidationContext; +import org.citrusframework.openapi.generator.sample.OpenApiPetStore.EntityValidationContext; +import org.citrusframework.openapi.generator.sample.OpenApiPetStore.PetEntityValidationContext; +import org.citrusframework.openapi.generator.sample.OpenApiPetStore.PetEntityValidationContext.Builder; +import org.citrusframework.validation.AbstractMessageValidator; +import org.citrusframework.validation.DefaultMessageValidatorRegistry; +import org.citrusframework.validation.ValidationUtils; +import org.testng.annotations.Test; + +public class OpenApiPetStoreTest { + + @Test + public void test() { + Builder petValidationContextBuilder = pet().id("1234") + .name("Garfield") + .category("Cat") + .address(address -> address + .street("Nina Hagen Hang") + .zip("12345") + .city("Hagen ATW")) +// .owners(anyOf(List.of( +// owner -> owner.name("Peter Lustig"), +// owner -> owner.name("Hans Meier") +// ))) +// .owners(oneOf(List.of( +// owner -> owner.name("Seppel Hinterhuber") +// ))) +// .urls(0, "url1") +// .urls(1, "url2") +// .urls("@contains('url1', 'url2')") + ; + + PetEntityValidationContext petValidationContext = petValidationContextBuilder.build(); + OpenApiEntityValidator validator = new OpenApiEntityValidator(); + + Message receivedMessage = new DefaultMessage(); + receivedMessage.setPayload(""" + { + "id": 1234, + "name": "Garfield", + "category": "Cat", + "address": { + "street": "Nina Hagen Hang", + "zip": "12345", + "city": "Hagen ATW" + }, + "owners": [ + { + "name": "Peter Lustig" + }, + { + "name": "Hans Meier" + } + ] + } + """); + TestContext testContext = new TestContext(); + testContext.setReferenceResolver(mock()); + testContext.setMessageValidatorRegistry(new DefaultMessageValidatorRegistry()); + testContext.setFunctionRegistry(new DefaultFunctionRegistry()); + + validator.validateMessage(receivedMessage, null, testContext, petValidationContext); + + + } + + public class OpenApiEntityValidator extends + AbstractMessageValidator { + + public void validateMessage(Message receivedMessage, Message controlMessage, + TestContext context, EntityValidationContext validationContext) { + System.out.println("asSD"); + + validateJson(receivedMessage.getPayload(String.class), context, validationContext); + + + } + + private void validateJson(String jsonString, TestContext context, + EntityValidationContext validationContext) { + validateJsonPathExpressions(jsonString, context, validationContext); + validateNestedJsonPathExpressions(jsonString, context, validationContext); + } + + @Override + protected Class getRequiredValidationContextType() { + return EntityValidationContext.class; + } + + @Override + public boolean supportsMessageType(String messageType, Message message) { + return true; + } + + + private void validateJsonPathExpressions(String jsonString, TestContext context, + EntityValidationContext validationContext) { + String jsonPathExpression; + try { + JSONParser parser = new JSONParser(JSONParser.MODE_JSON_SIMPLE); + Object receivedJson = parser.parse(jsonString); + ReadContext readerContext = JsonPath.parse(receivedJson); + + for (Map.Entry entry : validationContext.getJsonPathExpressions() + .entrySet()) { + Object expectedValue = entry.getValue(); + + jsonPathExpression = context.replaceDynamicContentInString(entry.getKey()); + Object jsonPathResult = JsonPathUtils.evaluate(readerContext, + jsonPathExpression); + + if (expectedValue instanceof EntityValidationContext entityValidationContext) { + validateJson((String) jsonPathResult, context, entityValidationContext); + } else if (expectedValue instanceof AggregateEntityValidationContext) { + + } else { + + if (expectedValue instanceof String) { + //check if expected value is variable or function (and resolve it, if yes) + expectedValue = context.replaceDynamicContentInString( + String.valueOf(expectedValue)); + } + + //do the validation of actual and expected value for element + ValidationUtils.validateValues(jsonPathResult, expectedValue, + jsonPathExpression, context); + + logger.debug("Validating element: {}='{}': OK", jsonPathExpression, + expectedValue); + } + + } + + logger.debug("JSONPath element validation successful: All values OK"); + } catch (ParseException e) { + throw new CitrusRuntimeException("Failed to parse JSON text", e); + } + } + + private void validateNestedJsonPathExpressions(String jsonString, TestContext context, + EntityValidationContext validationContext) { + String jsonPathExpression; + try { + JSONParser parser = new JSONParser(JSONParser.MODE_JSON_SIMPLE); + Object receivedJson = parser.parse(jsonString); + ReadContext readerContext = JsonPath.parse(receivedJson); + + for (Map.Entry entry : validationContext.getNestedValidationContextsBuilders() + .entrySet()) { + + jsonPathExpression = context.replaceDynamicContentInString(entry.getKey()); + Object jsonPathResult = JsonPathUtils.evaluate(readerContext, + jsonPathExpression); + + validateJson(jsonPathResult.toString(), context, entry.getValue()); + + } + + logger.debug("JSONPath element validation successful: All values OK"); + } catch (ParseException e) { + throw new CitrusRuntimeException("Failed to parse JSON text", e); + } + } + } +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/sample/OpenApiPetStore.java b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/sample/OpenApiPetStore.java new file mode 100644 index 0000000000..5ed27a3765 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/sample/OpenApiPetStore.java @@ -0,0 +1,306 @@ +package org.citrusframework.openapi.generator.sample; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import org.citrusframework.builder.WithExpressions; +import org.citrusframework.http.actions.HttpClientRequestActionBuilder; +import org.citrusframework.http.actions.HttpClientResponseActionBuilder; +import org.citrusframework.http.client.HttpClient; +import org.citrusframework.message.DelegatingPathExpressionProcessor; +import org.citrusframework.message.MessageProcessor; +import org.citrusframework.message.MessageProcessorAdapter; +import org.citrusframework.openapi.generator.sample.OpenApiPetStore.EntityValidationContext; +import org.citrusframework.openapi.generator.sample.OpenApiPetStore.PetEntityValidationContext.Builder; +import org.citrusframework.openapi.generator.sample.PetApi.FindPetByStatusActionBuilder; +import org.citrusframework.validation.DelegatingPayloadVariableExtractor; +import org.citrusframework.validation.context.DefaultValidationContext; +import org.citrusframework.validation.context.ValidationContext; +import org.citrusframework.variable.VariableExtractor; +import org.citrusframework.variable.VariableExtractorAdapter; +import org.springframework.http.HttpStatus; + +public class OpenApiPetStore { + + public static OpenApiPetStore openApiPetStore(HttpClient httpClient) { + return new OpenApiPetStore(); + } + + public GetPetIdRequestActionBuilder getPetById() { + return new GetPetIdRequestActionBuilder(); + } + + public FindPetByStatusActionBuilder findByStatus() { + return new PetApi.FindPetByStatusActionBuilder(); + } + + public static class GetPetIdRequestActionBuilder extends HttpClientRequestActionBuilder { + + public GetPetIdRequestActionBuilder withPetId(String petId) { + return this; + } + + } + + public GetPetIdResponseActionBuilder receivePetById(HttpStatus status) { + return new GetPetIdResponseActionBuilder(); + } + + public GetPetIdResponseActionBuilder200 receivePetById200() { + return new GetPetIdResponseActionBuilder200(); + } + + public static class GetPetIdResponseActionBuilder extends HttpClientResponseActionBuilder { + + } + + // Per configured response + public static class GetPetIdResponseActionBuilder200 extends HttpClientResponseActionBuilder { + + public HttpMessageBuilderSupport withPet( + Consumer validator) { + PetEntityValidationContext.Builder builder = new PetEntityValidationContext.Builder(); + validator.accept(builder); + return message().validate(builder); + } + } + + public static class EntityValidationContext extends DefaultValidationContext { + + private Map expressions; + + + private Map nestedValidationContextsBuilders = new HashMap<>(); + + public EntityValidationContext(Builder builder) { + super(); + this.expressions = builder.expressions; + builder.nestedValidationContextBuilders.forEach((key, value) -> + nestedValidationContextsBuilders.put(key, value.build())); + + } + + public Map getJsonPathExpressions() { + return expressions; + } + + public Map getNestedValidationContextsBuilders() { + return nestedValidationContextsBuilders; + } + + public static class Builder> implements + ValidationContext.Builder, + WithExpressions, VariableExtractorAdapter, + MessageProcessorAdapter { + + private final Map expressions = new HashMap<>(); + + protected final Map> nestedValidationContextBuilders= new HashMap<>(); + + @Override + public B expressions(Map expressions) { + this.expressions.putAll(expressions); + return (B) this; + } + + @Override + public B expression(final String expression, + final Object value) { + this.expressions.put(expression, value); + return (B) this; + } + + @Override + public EntityValidationContext build() { + return new EntityValidationContext(this); + } + + @Override + public MessageProcessor asProcessor() { + return new DelegatingPathExpressionProcessor.Builder() + .expressions(expressions) + .build(); + } + + @Override + public VariableExtractor asExtractor() { + return new DelegatingPayloadVariableExtractor.Builder() + .expressions(expressions) + .build(); + } + + } + } + + public static class PetEntityValidationContext extends EntityValidationContext { + + public PetEntityValidationContext(Builder builder) { + super(builder); + } + + public static class Builder extends + EntityValidationContext.Builder { + + public Builder id(String expression) { + return expression("$.id", expression); + } + + public Builder name(String expression) { + return expression("$.name", expression); + } + + public Builder category(String expression) { + return expression("$.category", expression); + } + + public Builder urls(String expression) { + return expression("$.urls", expression); + } + + + public Builder urls(int index, String expression) { + return expression("$.urls[%d]".formatted(index), expression); + } + + public Builder address(Consumer validator) { + AddressEntityValidationContext.Builder addressEntityValidationContextBuilder = new AddressEntityValidationContext.Builder(); + validator.accept(addressEntityValidationContextBuilder); + nestedValidationContextBuilders.put("$.address", addressEntityValidationContextBuilder); + + return this; + } + + + public Builder owners(AggregateEntityValidationContext.Builder aggregateContext) { + nestedValidationContextBuilders.put("$.owners", aggregateContext); + return this; + } + + public static Builder pet() { + return new Builder(); + } + + @Override + public PetEntityValidationContext build() { + return new PetEntityValidationContext(this); + } + + } + } + + public static class AddressEntityValidationContext extends EntityValidationContext { + + public AddressEntityValidationContext(Builder builder) { + super(builder); + } + + public static class Builder extends + EntityValidationContext.Builder { + + + public Builder street(String expression) { + return expression("$.street", expression); + } + + public Builder city(String expression) { + return expression("$.city", expression); + } + + public Builder zip(String expression) { + return expression("$.zip", expression); + } + + public static Builder address() { + return new Builder(); + } + + @Override + public AddressEntityValidationContext build() { + return new AddressEntityValidationContext(this); + } + + } + } + + public static class OwnerEntityValidationContext extends EntityValidationContext { + + public OwnerEntityValidationContext(Builder builder) { + super(builder); + } + + public static class Builder extends + EntityValidationContext.Builder { + + public OwnerEntityValidationContext.Builder address(Consumer validator) { + AddressEntityValidationContext.Builder addressEntityValidationContextBuilder = new AddressEntityValidationContext.Builder(); + validator.accept(addressEntityValidationContextBuilder); + nestedValidationContextBuilders.put("address", addressEntityValidationContextBuilder); + return this; + } + + public OwnerEntityValidationContext.Builder name(String expression) { + expression("$.name", expression); + return this; + } + + @Override + public OwnerEntityValidationContext build() { + return new OwnerEntityValidationContext(this); + } + } + } + + public static class AggregateEntityValidationContext> extends EntityValidationContext { + + private final Type type; + + private List> validator; + + public AggregateEntityValidationContext(Builder builder) { + super(builder); + + this.type = builder.type; + this.validator = builder.validator; + } + + public enum Type { + ONE_OF, ANY_OF, ALL_OF, NONE_OF + } + + public static class Builder> extends EntityValidationContext.Builder, Builder> { + + private final Type type; + + private final List> validator; + + public Builder(Type type, List> validator) { + this.type = type; + this.validator = validator; + } + + public static > AggregateEntityValidationContext.Builder anyOf(List> validator) { + return new Builder<>(Type.ANY_OF, validator); + } + + public static > AggregateEntityValidationContext.Builder allOf(List> validator) { + return new Builder<>(Type.ALL_OF, validator); + } + + public static > AggregateEntityValidationContext.Builder noneOf(List> validator) { + + return new Builder<>(Type.NONE_OF, validator); + } + + public static > AggregateEntityValidationContext.Builder oneOf(List> validator) { + return new Builder<>(Type.ONE_OF, validator); + } + + @Override + public AggregateEntityValidationContext build() { + return null; + } + } + + } +} \ No newline at end of file diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/sample/OpenApiPetStore_.java b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/sample/OpenApiPetStore_.java new file mode 100644 index 0000000000..b810d81281 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/sample/OpenApiPetStore_.java @@ -0,0 +1,304 @@ +package org.citrusframework.openapi.generator.sample; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import org.citrusframework.builder.WithExpressions; +import org.citrusframework.http.actions.HttpClientRequestActionBuilder; +import org.citrusframework.http.actions.HttpClientResponseActionBuilder; +import org.citrusframework.http.client.HttpClient; +import org.citrusframework.message.DelegatingPathExpressionProcessor; +import org.citrusframework.message.MessageProcessor; +import org.citrusframework.message.MessageProcessorAdapter; +import org.citrusframework.openapi.generator.sample.PetApi.FindPetByStatusActionBuilder; +import org.citrusframework.validation.DelegatingPayloadVariableExtractor; +import org.citrusframework.validation.context.DefaultValidationContext; +import org.citrusframework.validation.context.ValidationContext; +import org.citrusframework.variable.VariableExtractor; +import org.citrusframework.variable.VariableExtractorAdapter; +import org.springframework.http.HttpStatus; + +public class OpenApiPetStore_ { + + public static OpenApiPetStore_ openApiPetStore(HttpClient httpClient) { + return new OpenApiPetStore_(); + } + + public GetPetIdRequestActionBuilder getPetById() { + return new GetPetIdRequestActionBuilder(); + } + + public FindPetByStatusActionBuilder findByStatus() { + return new FindPetByStatusActionBuilder(); + } + + public static class GetPetIdRequestActionBuilder extends HttpClientRequestActionBuilder { + + public GetPetIdRequestActionBuilder withPetId(String petId) { + return this; + } + + } + + public GetPetIdResponseActionBuilder receivePetById(HttpStatus status) { + return new GetPetIdResponseActionBuilder(); + } + + public GetPetIdResponseActionBuilder200 receivePetById200() { + return new GetPetIdResponseActionBuilder200(); + } + + public static class GetPetIdResponseActionBuilder extends HttpClientResponseActionBuilder { + + } + + // Per configured response + public static class GetPetIdResponseActionBuilder200 extends HttpClientResponseActionBuilder { + + public HttpMessageBuilderSupport withPet( + Consumer validator) { + PetEntityValidationContext.Builder builder = new PetEntityValidationContext.Builder(); + validator.accept(builder); + return message().validate(builder); + } + } + + public static class EntityValidationContext extends DefaultValidationContext { + + private Map expressions; + + + private Map nestedValidationContextsBuilders = new HashMap<>(); + + public EntityValidationContext(Builder builder) { + super(); + this.expressions = builder.expressions; + builder.nestedValidationContextBuilders.forEach((key, value) -> + nestedValidationContextsBuilders.put(key, value.build())); + + } + + public Map getJsonPathExpressions() { + return expressions; + } + + public Map getNestedValidationContextsBuilders() { + return nestedValidationContextsBuilders; + } + + public static class Builder> implements + ValidationContext.Builder, + WithExpressions, VariableExtractorAdapter, + MessageProcessorAdapter { + + private final Map expressions = new HashMap<>(); + + protected final Map> nestedValidationContextBuilders= new HashMap<>(); + + @Override + public B expressions(Map expressions) { + this.expressions.putAll(expressions); + return (B) this; + } + + @Override + public B expression(final String expression, + final Object value) { + this.expressions.put(expression, value); + return (B) this; + } + + @Override + public EntityValidationContext build() { + return new EntityValidationContext(this); + } + + @Override + public MessageProcessor asProcessor() { + return new DelegatingPathExpressionProcessor.Builder() + .expressions(expressions) + .build(); + } + + @Override + public VariableExtractor asExtractor() { + return new DelegatingPayloadVariableExtractor.Builder() + .expressions(expressions) + .build(); + } + + } + } + + public static class PetEntityValidationContext extends EntityValidationContext { + + public PetEntityValidationContext(Builder builder) { + super(builder); + } + + public static class Builder extends + EntityValidationContext.Builder { + + public Builder id(String expression) { + return expression("$.id", expression); + } + + public Builder name(String expression) { + return expression("$.name", expression); + } + + public Builder category(String expression) { + return expression("$.category", expression); + } + + public Builder urls(String expression) { + return expression("$.urls", expression); + } + + + public Builder urls(int index, String expression) { + return expression("$.urls[%d]".formatted(index), expression); + } + + public Builder address(Consumer validator) { + AddressEntityValidationContext.Builder addressEntityValidationContextBuilder = new AddressEntityValidationContext.Builder(); + validator.accept(addressEntityValidationContextBuilder); + nestedValidationContextBuilders.put("$.address", addressEntityValidationContextBuilder); + + return this; + } + + + public Builder owners(AggregateEntityValidationContext.Builder aggregateContext) { + nestedValidationContextBuilders.put("$.owners", aggregateContext); + return this; + } + + public static Builder pet() { + return new Builder(); + } + + @Override + public PetEntityValidationContext build() { + return new PetEntityValidationContext(this); + } + + } + } + + public static class AddressEntityValidationContext extends EntityValidationContext { + + public AddressEntityValidationContext(Builder builder) { + super(builder); + } + + public static class Builder extends + EntityValidationContext.Builder { + + + public Builder street(String expression) { + return expression("$.street", expression); + } + + public Builder city(String expression) { + return expression("$.city", expression); + } + + public Builder zip(String expression) { + return expression("$.zip", expression); + } + + public static Builder address() { + return new Builder(); + } + + @Override + public AddressEntityValidationContext build() { + return new AddressEntityValidationContext(this); + } + + } + } + + public static class OwnerEntityValidationContext extends EntityValidationContext { + + public OwnerEntityValidationContext(Builder builder) { + super(builder); + } + + public static class Builder extends + EntityValidationContext.Builder { + + public Builder address(Consumer validator) { + AddressEntityValidationContext.Builder addressEntityValidationContextBuilder = new AddressEntityValidationContext.Builder(); + validator.accept(addressEntityValidationContextBuilder); + nestedValidationContextBuilders.put("address", addressEntityValidationContextBuilder); + return this; + } + + public Builder name(String expression) { + expression("$.name", expression); + return this; + } + + @Override + public OwnerEntityValidationContext build() { + return new OwnerEntityValidationContext(this); + } + } + } + + public static class AggregateEntityValidationContext> extends EntityValidationContext { + + private final Type type; + + private List> validator; + + public AggregateEntityValidationContext(Builder builder) { + super(builder); + + this.type = builder.type; + this.validator = builder.validator; + } + + public enum Type { + ONE_OF, ANY_OF, ALL_OF, NONE_OF + } + + public static class Builder> extends EntityValidationContext.Builder, Builder> { + + private final Type type; + + private final List> validator; + + public Builder(Type type, List> validator) { + this.type = type; + this.validator = validator; + } + + public static > Builder anyOf(List> validator) { + return new Builder<>(Type.ANY_OF, validator); + } + + public static > Builder allOf(List> validator) { + return new Builder<>(Type.ALL_OF, validator); + } + + public static > Builder noneOf(List> validator) { + + return new Builder<>(Type.NONE_OF, validator); + } + + public static > Builder oneOf(List> validator) { + return new Builder<>(Type.ONE_OF, validator); + } + + @Override + public AggregateEntityValidationContext build() { + return null; + } + } + + } +} \ No newline at end of file diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/sample/PetApi.java b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/sample/PetApi.java new file mode 100644 index 0000000000..325be20a88 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/sample/PetApi.java @@ -0,0 +1,273 @@ +/* + * Copyright the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.citrusframework.openapi.generator.sample; + +import java.util.HashMap; +import java.util.Map; +import org.citrusframework.http.client.HttpClient; +import org.citrusframework.openapi.OpenApiSpecification; +import org.citrusframework.openapi.generator.TestApiClientRequestActionBuilder; +import org.citrusframework.openapi.generator.rest.petstore.model.Pet; +import org.citrusframework.testapi.GeneratedApi; + +@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen", date = "2024-07-20T08:47:39.378047600+02:00[Europe/Zurich]", comments = "Generator version: 7.5.0") +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"; + } + + private OpenApiSpecification openApiSpecification = null; + + public Map getApiInfoExtensions() { + Map infoExtensionMap = new HashMap<>(); + infoExtensionMap.put("x-citrus-api-name", "petstore"); + infoExtensionMap.put("x-citrus-app", "PETS"); + return infoExtensionMap; + } + + public static PetApi openApiPetStore(HttpClient httpClient) { + + return new PetApi(); + } + + private static OpenApiSpecification petApi() { + // TODO implement me + return null; + } + + public AddPetActionBuilder addPet() { + return new AddPetActionBuilder(); + } + + public DeletePetActionBuilder deletePet() { + return new DeletePetActionBuilder(); + } + + public FindPetByStatusActionBuilder findPetsByStatus() { + return new FindPetByStatusActionBuilder(); + } + + public FindPetsByTagsActionBuilder findPetsByTags() { + return new FindPetsByTagsActionBuilder(); + } + + public GetPetByIdActionBuilder getPetById() { + return new GetPetByIdActionBuilder(); + } + + public class AddPetActionBuilder extends PetStoreAbstractSendAction.Builder { + + private static final String METHOD = "POST"; + + private static final String ENDPOINT = "/pet"; + + private static final String OPERATION_NAME = "addPet"; + + public AddPetActionBuilder() { + super(openApiSpecification, METHOD, ENDPOINT, OPERATION_NAME); + } + + public AddPetActionBuilder withStatus(String status) { + queryParam("status", status); + return this; + } + + public AddPetActionBuilder withPet(Pet pet) { + // TODO: fix this + getMessageBuilderSupport().body(pet.toString()); + return this; + } + + } + + public class DeletePetActionBuilder extends TestApiClientRequestActionBuilder { + + private static final String METHOD = "DELETE"; + + private static final String ENDPOINT = "/pet/{petId}"; + + private static final String OPERATION_NAME = "deletePet"; + + public DeletePetActionBuilder() { + super(openApiSpecification, METHOD, ENDPOINT, OPERATION_NAME); + } + + public DeletePetActionBuilder withId(String id) { + pathParameter("id", id); + return this; + } + + public DeletePetActionBuilder withPet(Pet pet) { + // TODO: fix this pet.toString will not properly work + getMessageBuilderSupport().body(pet.toString()); + return this; + } + + } + + public static class FindPetByStatusActionBuilder extends PetStoreAbstractSendAction.Builder { + + private static final String METHOD = "GET"; + + private static final String ENDPOINT = "/pet/findByStatus"; + + private static final String OPERATION_NAME = "findPetsByStatus"; + + public FindPetByStatusActionBuilder() { + super(PetApi.petApi(), METHOD, ENDPOINT, OPERATION_NAME); + } + + public FindPetByStatusActionBuilder withStatus(String status) { + queryParam("status", status); + return this; + } + + } + + public static class FindPetsByTagsActionBuilder extends PetStoreAbstractSendAction.Builder { + + private static final String METHOD = "GET"; + + private static final String ENDPOINT = "/pet/findByTags"; + + private static final String OPERATION_NAME = "findPetsByTags"; + + public FindPetsByTagsActionBuilder() { + super(PetApi.petApi(), METHOD, ENDPOINT, OPERATION_NAME); + } + + public FindPetsByTagsActionBuilder withTags(String... tags) { + queryParam("tags", toQueryParam(tags)); + return this; + } + } + + public static class GetPetByIdActionBuilder extends PetStoreAbstractSendAction.Builder { + + private static final String METHOD = "GET"; + + private static final String ENDPOINT = "/pet/{petId}"; + + private static final String OPERATION_NAME = "getPetById"; + + public GetPetByIdActionBuilder() { + super(PetApi.petApi(), METHOD, ENDPOINT, OPERATION_NAME); + } + + public GetPetByIdActionBuilder withId(String id) { + pathParameter("id", id); + return this; + } + + + + // TODO: find solution for authentication +// public GetPetByIdActionBuilder withBasicUsername(String basicUsername) { +// this.basicUsername = basicUsername; +// return this; +// } +// +// public GetPetByIdActionBuilder withBasicPassword(String basicPassword) { +// this.basicPassword = basicPassword; +// return this; +// } + } + + public static class UpdatePetActionBuilder extends PetStoreAbstractSendAction.Builder { + + private static final String METHOD = "PUT"; + + private static final String ENDPOINT = "/pet"; + + private static final String OPERATION_NAME = "updatePet"; + + public UpdatePetActionBuilder() { + super(PetApi.petApi(), METHOD, ENDPOINT, OPERATION_NAME); + } + + public UpdatePetActionBuilder withId(String id) { + pathParameter("id", id); + return this; + } + + public UpdatePetActionBuilder withPet(Pet pet) { + // TODO: fix this pet.toString + getMessageBuilderSupport().body(pet.toString()); + return this; + } + } + + public static class UpdatePetWithFormDataActionBuilder extends + PetStoreAbstractSendAction.Builder { + + private static final String METHOD = "POST"; + + private static final String ENDPOINT = "/pet/{petId}"; + + private static final String OPERATION_NAME = "updatePetWithForm"; + + public UpdatePetWithFormDataActionBuilder() { + super(PetApi.petApi(), METHOD, ENDPOINT, OPERATION_NAME); + } + + public UpdatePetWithFormDataActionBuilder withId(String id) { + pathParameter("id", id); + return this; + } + + // TODO: what is the magic about form data request? + } + + public static class UploadFileActionBuilder extends PetStoreAbstractSendAction.Builder { + + private static final String METHOD = "POST"; + + private static final String ENDPOINT = "/pet/{petId}/uploadImage"; + + private static final String OPERATION_NAME = "uploadImage"; + + public UploadFileActionBuilder() { + super(PetApi.petApi(), METHOD, ENDPOINT, OPERATION_NAME); + } + + public UploadFileActionBuilder withId(String id) { + pathParameter("id", id); + return this; + } + + public UploadFileActionBuilder withAdditionalMetadata(String additionalMetadata) { + + // TODO: what is the magic about form data request? + formData("additionalMetadata", additionalMetadata); + return this; + } + } + + + +} diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/sample/PetStoreAbstractReceiveActionBuilder.java b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/sample/PetStoreAbstractReceiveActionBuilder.java new file mode 100644 index 0000000000..e8f1e5c473 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/sample/PetStoreAbstractReceiveActionBuilder.java @@ -0,0 +1,250 @@ +/* +* Copyright the original author or authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package org.citrusframework.openapi.generator.sample; + +import static org.springframework.util.CollectionUtils.isEmpty; + +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.spi.Resources; +import org.citrusframework.testapi.ApiActionBuilderCustomizerService; +import org.citrusframework.testapi.GeneratedApi; +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; + +@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen", date = "2024-07-20T08:47:39.378047600+02:00[Europe/Zurich]", comments = "Generator version: 7.5.0") +public abstract class PetStoreAbstractReceiveActionBuilder 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) { + recieveResponse(context); + } + + /** + * This method receives the HTTP-Response. + * + * @deprecated use {@link PetStoreAbstractReceiveActionBuilder#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 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/java/org/citrusframework/openapi/generator/sample/PetStoreAbstractSendActionBuilder.java b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/sample/PetStoreAbstractSendActionBuilder.java new file mode 100644 index 0000000000..a02a8d4639 --- /dev/null +++ b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/sample/PetStoreAbstractSendActionBuilder.java @@ -0,0 +1,234 @@ +/* +* Copyright the original author or authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package org.citrusframework.openapi.generator.sample; + +import static org.springframework.util.CollectionUtils.isEmpty; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.ServiceLoader; +import javax.sql.DataSource; +import org.citrusframework.actions.SendMessageAction; +import org.citrusframework.context.TestContext; +import org.citrusframework.http.actions.HttpClientRequestActionBuilder; +import org.citrusframework.http.client.HttpClient; +import org.citrusframework.openapi.OpenApiSpecification; +import org.citrusframework.openapi.actions.OpenApiClientRequestActionBuilder; +import org.citrusframework.testapi.ApiActionBuilderCustomizerService; +import org.citrusframework.testapi.GeneratedApi; +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.http.MediaType; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.JavaCitrusCodegen", date = "2024-07-20T08:47:39.378047600+02:00[Europe/Zurich]", comments = "Generator version: 7.5.0") +public abstract class PetStoreAbstractSendAction extends SendMessageAction { + + 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); + } + + + 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 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; + } + + public static class Builder extends OpenApiClientRequestActionBuilder { + + // TODO: do we really need this? + protected OpenApiSpecification openApiSpec; + + private final String path; + + private final Map pathParameters = new HashMap<>(); + + private final MultiValueMap formData = new LinkedMultiValueMap<>(); + + // TODO: can we just pass in the operation? + public Builder(OpenApiSpecification openApiSpec, String method, String path, String operationName) { + super(openApiSpec, "%s_%s".formatted(method, path)); + name(String.format("%s:%s", "PetStore".toLowerCase(), operationName)); + getMessageBuilderSupport().header("citrus_open_api_operation_name", operationName); + getMessageBuilderSupport().header("citrus_open_api_method", method); + getMessageBuilderSupport().header("citrus_open_api_path", path); + + this.openApiSpec = openApiSpec; + this.path = path; + } + + protected void pathParameter(String name, String value) { + pathParameters.put(name, value); + } + + protected void formData(String name, String value) { + formData.add(name, value); + } + + protected String qualifiedPath(String path) { + + String qualifiedPath = path; + for (Entry entry : pathParameters.entrySet()) { + qualifiedPath = qualifiedPath.replace("{%s}".formatted(entry.getKey()), entry.getValue()); + } + return qualifiedPath; + } + + protected String toQueryParam(String...arrayElements) { + return String.join(",", arrayElements); + } + + @Override + public SendMessageAction doBuild() { + // TODO: register callback to modify builder + path(qualifiedPath(path)); + if (!formData.isEmpty()) { + // TODO: do we have to explicitly set the content type or is this done by citrus + messageBuilderSupport.contentType(MediaType.MULTIPART_FORM_DATA_VALUE); + getMessageBuilderSupport().body(formData); + } + return super.doBuild(); + } + + } +}