From 955c5fc6881273567fdc62e2833a28de1977ef8e Mon Sep 17 00:00:00 2001 From: Thorsten Schlathoelter Date: Fri, 24 Jan 2025 15:54:59 +0100 Subject: [PATCH] fix: add draft open api documentation --- .../openapi/OpenApiRepository.java | 87 +++++++-- .../openapi/OpenApiSpecification.java | 112 +++++++---- .../OpenApiClientRequestActionBuilder.java | 3 +- .../validation/OpenApiSchemaValidation.java | 23 ++- .../OpenApiValidationContextLoader.java | 71 +------ .../openapi/OpenApiRepositoryTest.java | 2 +- .../openapi/groovy/OpenApiClientTest.java | 8 +- .../OpenApiClientApiContextPathIT.java | 155 +++++++++++++++ .../openapi/integration/OpenApiClientIT.java | 83 ++++++--- .../openapi/integration/OpenApiServerIT.java | 174 +++++++++++------ .../openapi/xml/OpenApiClientTest.java | 18 +- .../openapi/yaml/OpenApiClientTest.java | 8 +- .../org/citrusframework/util/StringUtils.java | 6 +- .../citrusframework/util/StringUtilsTest.java | 2 + src/manual/connector-openapi.adoc | 95 +++++++++- .../RestApiSendMessageActionBuilder.java | 11 +- .../main/resources/java-citrus/api.mustache | 21 ++- .../resources/java-citrus/api_soap.mustache | 21 ++- .../java-citrus/bean_configuration.mustache | 4 +- .../openapi/generator/GeneratedRestApiIT.java | 26 +++ .../resources/apis/petstore-extended-v3.yaml | 32 ++++ .../rest/extpetstore/model/Category.java | 2 +- .../extpetstore/model/HistoricalData.java | 2 +- .../rest/extpetstore/model/Pet.java | 2 +- .../rest/extpetstore/model/PetIdentifier.java | 2 +- .../rest/extpetstore/model/Tag.java | 2 +- .../model/VaccinationDocumentResult.java | 2 +- .../rest/extpetstore/request/ExtPetApi.java | 176 +++++++++++++++++- .../spring/ExtPetStoreBeanConfiguration.java | 6 +- .../spring/ExtPetStoreNamespaceHandler.java | 8 +- .../rest/petstore/model/Address.java | 2 +- .../rest/petstore/model/Category.java | 2 +- .../rest/petstore/model/Customer.java | 2 +- .../rest/petstore/model/ModelApiResponse.java | 2 +- .../rest/petstore/model/Order.java | 2 +- .../expectedgen/rest/petstore/model/Pet.java | 2 +- .../expectedgen/rest/petstore/model/Tag.java | 2 +- .../expectedgen/rest/petstore/model/User.java | 2 +- .../rest/petstore/request/PetApi.java | 23 ++- .../rest/petstore/request/StoreApi.java | 23 ++- .../rest/petstore/request/UserApi.java | 23 ++- .../spring/PetStoreBeanConfiguration.java | 14 +- .../spring/PetStoreNamespaceHandler.java | 2 +- .../request/BookServiceSoapApi.java | 23 ++- .../spring/BookServiceBeanConfiguration.java | 6 +- .../spring/BookServiceNamespaceHandler.java | 2 +- .../plugin/TestApiGeneratorMojoUnitTest.java | 3 +- .../org/citrusframework/util/XMLUtils.java | 52 +++--- .../xml/DomXmlMessageValidator.java | 52 +----- .../xml/schema/XmlSchemaValidation.java | 22 +-- 50 files changed, 1013 insertions(+), 412 deletions(-) create mode 100644 connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/integration/OpenApiClientApiContextPathIT.java diff --git a/connectors/citrus-openapi/src/main/java/org/citrusframework/openapi/OpenApiRepository.java b/connectors/citrus-openapi/src/main/java/org/citrusframework/openapi/OpenApiRepository.java index 65607479e4..788635ebeb 100644 --- a/connectors/citrus-openapi/src/main/java/org/citrusframework/openapi/OpenApiRepository.java +++ b/connectors/citrus-openapi/src/main/java/org/citrusframework/openapi/OpenApiRepository.java @@ -16,8 +16,12 @@ package org.citrusframework.openapi; +import static java.lang.String.format; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Collections.synchronizedList; +import static org.citrusframework.openapi.OpenApiSettings.isNeglectBasePathGlobally; +import static org.citrusframework.openapi.OpenApiSettings.isRequestValidationEnabledGlobally; +import static org.citrusframework.openapi.OpenApiSettings.isResponseValidationEnabledGlobally; import jakarta.annotation.Nullable; import jakarta.validation.constraints.NotNull; @@ -28,6 +32,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import org.citrusframework.exceptions.CitrusRuntimeException; import org.citrusframework.openapi.validation.OpenApiValidationPolicy; import org.citrusframework.repository.BaseRepository; import org.citrusframework.spi.Resource; @@ -57,11 +62,22 @@ public class OpenApiRepository extends BaseRepository { */ private String rootContextPath; - private boolean requestValidationEnabled = true; + /** + * Flag to indicate whether the base path of the OpenAPI should be part of the path or not. + */ + private boolean neglectBasePath = isNeglectBasePathGlobally(); + + /** + * Flag to indicate whether OpenAPIs managed by this repository should perform request validation. + */ + private boolean requestValidationEnabled = isRequestValidationEnabledGlobally(); - private boolean responseValidationEnabled = true; + /** + * Flag to indicate whether OpenAPIs managed by this repository should perform response validation. + */ + private boolean responseValidationEnabled = isResponseValidationEnabledGlobally(); - private OpenApiValidationPolicy openApiValidationPolicy = OpenApiSettings.getOpenApiValidationPolicy(); + private OpenApiValidationPolicy validationPolicy = OpenApiSettings.getOpenApiValidationPolicy(); public OpenApiRepository() { super(DEFAULT_NAME); @@ -124,6 +140,24 @@ public void setRootContextPath(String rootContextPath) { this.rootContextPath = rootContextPath; } + public OpenApiRepository rootContextPath(String rootContextPath) { + setRootContextPath(rootContextPath); + return this; + } + + public boolean isNeglectBasePath() { + return neglectBasePath; + } + + public void setNeglectBasePath(boolean neglectBasePath) { + this.neglectBasePath = neglectBasePath; + } + + public OpenApiRepository neglectBasePath(boolean neglectBasePath) { + setNeglectBasePath(neglectBasePath); + return this; + } + public boolean isRequestValidationEnabled() { return requestValidationEnabled; } @@ -132,6 +166,11 @@ public void setRequestValidationEnabled(boolean requestValidationEnabled) { this.requestValidationEnabled = requestValidationEnabled; } + public OpenApiRepository requestValidationEnabled(boolean requestValidationEnabled) { + setRequestValidationEnabled(requestValidationEnabled); + return this; + } + public boolean isResponseValidationEnabled() { return responseValidationEnabled; } @@ -140,15 +179,24 @@ public void setResponseValidationEnabled(boolean responseValidationEnabled) { this.responseValidationEnabled = responseValidationEnabled; } - public OpenApiValidationPolicy getOpenApiValidationPolicy() { - return openApiValidationPolicy; + public OpenApiRepository responseValidationEnabled(boolean responseValidationEnabled) { + setResponseValidationEnabled(responseValidationEnabled); + return this; } - public void setOpenApiValidationPolicy( - OpenApiValidationPolicy openApiValidationPolicy) { - this.openApiValidationPolicy = openApiValidationPolicy; + public OpenApiValidationPolicy getValidationPolicy() { + return validationPolicy; } + public void setValidationPolicy( + OpenApiValidationPolicy validationPolicy) { + this.validationPolicy = validationPolicy; + } + + public OpenApiRepository validationPolicy(OpenApiValidationPolicy openApiValidationPolicy) { + setValidationPolicy(openApiValidationPolicy); + return this; + } /** * Adds an OpenAPI Specification specified by the given resource to the repository. If an alias @@ -158,14 +206,21 @@ public void setOpenApiValidationPolicy( */ @Override public void addRepository(Resource openApiResource) { - OpenApiSpecification openApiSpecification = OpenApiSpecification.from(openApiResource, - openApiValidationPolicy); - determineResourceAlias(openApiResource).ifPresent(openApiSpecification::addAlias); - openApiSpecification.setApiRequestValidationEnabled(requestValidationEnabled); - openApiSpecification.setApiResponseValidationEnabled(responseValidationEnabled); - openApiSpecification.setRootContextPath(rootContextPath); - - addRepository(openApiSpecification); + + try { + OpenApiSpecification openApiSpecification = OpenApiSpecification.from(openApiResource, + validationPolicy); + determineResourceAlias(openApiResource).ifPresent(openApiSpecification::addAlias); + openApiSpecification.setApiRequestValidationEnabled(requestValidationEnabled); + openApiSpecification.setApiResponseValidationEnabled(responseValidationEnabled); + openApiSpecification.setRootContextPath(rootContextPath); + openApiSpecification.neglectBasePath(neglectBasePath); + addRepository(openApiSpecification); + } catch (Exception e) { + logger.error(format("Unable to read OpenApiSpecification from location: %s", openApiResource.getURI())); + throw new CitrusRuntimeException(e); + } + } /** diff --git a/connectors/citrus-openapi/src/main/java/org/citrusframework/openapi/OpenApiSpecification.java b/connectors/citrus-openapi/src/main/java/org/citrusframework/openapi/OpenApiSpecification.java index 9b8f8143a8..b3e4585fe4 100644 --- a/connectors/citrus-openapi/src/main/java/org/citrusframework/openapi/OpenApiSpecification.java +++ b/connectors/citrus-openapi/src/main/java/org/citrusframework/openapi/OpenApiSpecification.java @@ -86,8 +86,10 @@ public class OpenApiSpecification { private static final String HTTP = "http"; /** - * An uid to uniquely identify this specification at runtime. The uid is based on the SHA of the - * OpenAPI document and the root context, to which it is attached. + * A unique identifier (UID) for this specification at runtime. The UID is generated based on the SHA + * of the OpenAPI document combined with the full context path to which the API is attached. + * + * @see OpenApiSpecification#determineUid for detailed information on how the UID is generated. */ private String uid; @@ -221,18 +223,16 @@ public static OpenApiSpecification from(URL specUrl, OpenApiValidationContext openApiValidationContext; if (specUrl.getProtocol().startsWith(HTTPS)) { openApiDoc = OpenApiResourceLoader.fromSecuredWebResource(specUrl); - openApiValidationContext = OpenApiValidationContextLoader.fromSecuredWebResource( - specUrl); } else { openApiDoc = OpenApiResourceLoader.fromWebResource(specUrl); - openApiValidationContext = OpenApiValidationContextLoader.fromWebResource(specUrl, - openApiValidationPolicy); } + openApiValidationContext = OpenApiValidationContextLoader.fromSpec(OasModelHelper.toJson(openApiDoc), openApiValidationPolicy); + specification.setOpenApiValidationContext(openApiValidationContext); + specification.setSpecUrl(specUrl.toString()); specification.initPathLookups(); specification.setOpenApiDoc(openApiDoc); - specification.setOpenApiValidationContext(openApiValidationContext); specification.setRequestUrl( format("%s://%s%s%s", specUrl.getProtocol(), specUrl.getHost(), specUrl.getPort() > 0 ? ":" + specUrl.getPort() : "", @@ -267,7 +267,7 @@ public static OpenApiSpecification from(Resource resource, OasDocument openApiDoc = OpenApiResourceLoader.fromFile(resource); specification.setOpenApiValidationContext( - OpenApiValidationContextLoader.fromFile(resource, openApiValidationPolicy)); + OpenApiValidationContextLoader.fromSpec(OasModelHelper.toJson(openApiDoc), openApiValidationPolicy)); specification.setOpenApiDoc(openApiDoc); String schemeToUse = Optional.ofNullable(OasModelHelper.getSchemes(openApiDoc)) @@ -280,7 +280,7 @@ public static OpenApiSpecification from(Resource resource, specification.setSpecUrl(resource.getLocation()); specification.setRequestUrl( format("%s://%s%s", schemeToUse, OasModelHelper.getHost(openApiDoc), - specification.rootContextPath)); + getBasePath(openApiDoc))); return specification; } @@ -314,10 +314,21 @@ public static OpenApiSpecification fromString(String openApi) { return specification; } + /** + * Get the UID of this specification. + */ public String getUid() { return uid; } + /** + * Get the unique id of the given operation. + */ + @SuppressWarnings("unused") + public String getUniqueId(OasOperation oasOperation) { + return operationToUniqueId.get(oasOperation); + } + public synchronized OasDocument getOpenApiDoc(TestContext context) { if (openApiDoc != null) { return openApiDoc; @@ -351,13 +362,8 @@ public synchronized OasDocument getOpenApiDoc(TestContext context) { URL specWebResource = toSpecUrl(resolvedSpecUrl); if (resolvedSpecUrl.startsWith(HTTPS)) { initApiDoc(() -> OpenApiResourceLoader.fromSecuredWebResource(specWebResource)); - setOpenApiValidationContext( - OpenApiValidationContextLoader.fromSecuredWebResource(specWebResource)); } else { initApiDoc(() -> OpenApiResourceLoader.fromWebResource(specWebResource)); - setOpenApiValidationContext( - OpenApiValidationContextLoader.fromWebResource(specWebResource, - openApiValidationPolicy)); } if (requestUrl == null) { @@ -370,8 +376,7 @@ public synchronized OasDocument getOpenApiDoc(TestContext context) { } else { Resource resource = Resources.create(resolvedSpecUrl); initApiDoc(() -> OpenApiResourceLoader.fromFile(resource)); - setOpenApiValidationContext(OpenApiValidationContextLoader.fromFile(resource, - openApiValidationPolicy)); + if (requestUrl == null) { String schemeToUse = Optional.ofNullable(OasModelHelper.getSchemes(openApiDoc)) @@ -388,6 +393,10 @@ public synchronized OasDocument getOpenApiDoc(TestContext context) { } } + setOpenApiValidationContext( + OpenApiValidationContextLoader.fromSpec(OasModelHelper.toJson(openApiDoc), + openApiValidationPolicy)); + return openApiDoc; } @@ -427,8 +436,7 @@ private void initPathLookups() { return; } - uid = DigestUtils.sha256Hex(OasModelHelper.toJson(openApiDoc) + rootContextPath); - aliases.add(uid); + determineUid(); operationIdToOperationPathAdapter.clear(); OasModelHelper.visitOasOperations(this.openApiDoc, (oasPathItem, oasOperation) -> { @@ -446,6 +454,14 @@ private void initPathLookups() { }); } + private void determineUid() { + if (uid != null) { + aliases.remove(uid); + } + uid = DigestUtils.sha256Hex(OasModelHelper.toJson(openApiDoc) + getFullContextPath()); + aliases.add(uid); + } + /** * Stores an {@link OperationPathAdapter} in * {@link org.citrusframework.openapi.OpenApiSpecification#operationIdToOperationPathAdapter}. @@ -470,6 +486,7 @@ private void storeOperationPathAdapter(OasOperation operation, OasPathItem pathI appendSegmentToUrlPath(fullContextPath, path), operation, uniqueOperationId); operationIdToOperationPathAdapter.put(uniqueOperationId, operationPathAdapter); + if (hasText(operation.operationId)) { operationIdToOperationPathAdapter.put(operation.operationId, operationPathAdapter); } @@ -556,8 +573,9 @@ public void setRootContextPath(String rootContextPath) { initPathLookups(); } - public OpenApiValidationPolicy getOpenApiValidationPolicy() { - return openApiValidationPolicy; + public OpenApiSpecification rootContextPath(String rootContextPath) { + setRootContextPath(rootContextPath); + return this; } public void addAlias(String alias) { @@ -608,15 +626,6 @@ public void initOpenApiDoc(TestContext context) { } } - public OpenApiSpecification withRootContext(String rootContextPath) { - setRootContextPath(rootContextPath); - return this; - } - - public String getUniqueId(OasOperation oasOperation) { - return operationToUniqueId.get(oasOperation); - } - /** * Get the full path for the given {@link OasPathItem}. *

@@ -633,12 +642,31 @@ public String getUniqueId(OasOperation oasOperation) { * item */ public String getFullPath(OasPathItem oasPathItem) { + return appendSegmentToUrlPath(rootContextPath, + getFullBasePath(oasPathItem)); + } + + /** + * Get the full base-path for the given {@link OasPathItem}. + *

+ * The full base-path is constructed by concatenating the base path (if + * applicable), and the path of the given {@code oasPathItem}. The resulting format is: + *

+ *
+     * /basePath/pathItemPath
+     * 
+ * If the base path is to be neglected, it is excluded from the final constructed path. + * + * @param oasPathItem the OpenAPI path item whose full base-path is to be constructed + * @return the full base URL path, consisting of the base path, and the given path + * item + */ + public String getFullBasePath(OasPathItem oasPathItem) { return appendSegmentToUrlPath( - appendSegmentToUrlPath(rootContextPath, - neglectBasePath ? null : getBasePath(openApiDoc)), - oasPathItem.getPath()); + getApplicableBasePath(), oasPathItem.getPath()); } + /** * Constructs the full context path for the given {@link OasPathItem}. *

@@ -654,7 +682,7 @@ public String getFullPath(OasPathItem oasPathItem) { */ public String getFullContextPath() { return appendSegmentToUrlPath(rootContextPath, - neglectBasePath ? null : getBasePath(openApiDoc)); + getApplicableBasePath()); } /** @@ -674,9 +702,23 @@ public void setNeglectBasePath(boolean neglectBasePath) { } public OpenApiSpecification neglectBasePath(boolean neglectBasePath) { - this.neglectBasePath = neglectBasePath; - initPathLookups(); + setNeglectBasePath(neglectBasePath); return this; } + /** + * Gets the base path if basePath should be applied. + */ + private String getApplicableBasePath() { + return neglectBasePath ? "" : getBasePath(openApiDoc); + } + + + /** + * Add another alias for this specification. + */ + public OpenApiSpecification alias(String alias) { + addAlias(alias); + return this; + } } diff --git a/connectors/citrus-openapi/src/main/java/org/citrusframework/openapi/actions/OpenApiClientRequestActionBuilder.java b/connectors/citrus-openapi/src/main/java/org/citrusframework/openapi/actions/OpenApiClientRequestActionBuilder.java index a14a48e931..84f5305214 100644 --- a/connectors/citrus-openapi/src/main/java/org/citrusframework/openapi/actions/OpenApiClientRequestActionBuilder.java +++ b/connectors/citrus-openapi/src/main/java/org/citrusframework/openapi/actions/OpenApiClientRequestActionBuilder.java @@ -129,6 +129,7 @@ public static class OpenApiClientRequestMessageBuilder extends HttpMessageBuilde private final String operationId; + // TODO: document me private AutoFillType autoFill = OpenApiSettings.getRequestAutoFillRandomValues(); public OpenApiClientRequestMessageBuilder(HttpMessage httpMessage, @@ -175,7 +176,7 @@ private void buildMessageFromOperation(OpenApiSpecification openApiSpecification OperationPathAdapter operationPathAdapter, TestContext context) { OasOperation operation = operationPathAdapter.operation(); - String path = operationPathAdapter.apiPath(); + String path = operationPathAdapter.fullPath(); HttpMethod method = HttpMethod.valueOf( operationPathAdapter.operation().getMethod().toUpperCase(Locale.US)); diff --git a/connectors/citrus-openapi/src/main/java/org/citrusframework/openapi/validation/OpenApiSchemaValidation.java b/connectors/citrus-openapi/src/main/java/org/citrusframework/openapi/validation/OpenApiSchemaValidation.java index 05f80224ec..1118117faa 100644 --- a/connectors/citrus-openapi/src/main/java/org/citrusframework/openapi/validation/OpenApiSchemaValidation.java +++ b/connectors/citrus-openapi/src/main/java/org/citrusframework/openapi/validation/OpenApiSchemaValidation.java @@ -1,6 +1,8 @@ package org.citrusframework.openapi.validation; import static java.util.Collections.emptyList; +import static org.citrusframework.openapi.OpenApiMessageHeaders.OAS_SPECIFICATION_ID; +import static org.citrusframework.openapi.OpenApiMessageHeaders.OAS_UNIQUE_OPERATION_ID; import static org.citrusframework.util.StringUtils.isNotEmpty; import com.atlassian.oai.validator.report.ValidationReport; @@ -61,12 +63,15 @@ public void validate(Message message, TestContext context, findSchemaRepositories(context), validationContext); - if (validationReportData != null && validationReportData.report != null) { - if (validationReportData.report.hasErrors()) { - logger.error("Failed to validate Json schema for message:\n{}\nand origin path:\n{}", - httpMessage.getPayload(String.class), httpMessage.getPath()); - throw new ValidationException(constructErrorMessage(validationReportData)); + if (validationReportData != null + && validationReportData.report != null + && validationReportData.report.hasErrors()) { + if (logger.isErrorEnabled()) { + logger.error( + "Failed to validate Json schema for message:\n{}\nand origin path:\n{}", + httpMessage.getPayload(String.class), httpMessage.getPath()); } + throw new ValidationException(constructErrorMessage(validationReportData)); } logger.debug("Json schema validation successful: All values OK"); @@ -86,8 +91,8 @@ public void validate(Message message, TestContext context, public boolean supportsMessageType(String messageType, Message message) { return "JSON".equals(messageType) || ( - message != null && IsJsonPredicate.getInstance().test(message.getPayload(String.class)) - || message.getHeader(OpenApiMessageHeaders.OAS_UNIQUE_OPERATION_ID) != null); + message != null && (IsJsonPredicate.getInstance().test(message.getPayload(String.class)) + || message.getHeader(OAS_UNIQUE_OPERATION_ID) != null)); } private String constructErrorMessage(ValidationReportData validationReportData) { @@ -121,10 +126,10 @@ private ValidationReportData validate(TestContext context, return null; } else { String operationId = validationContext.getSchema() != null ? validationContext.getSchema() - : (String) message.getHeader(OpenApiMessageHeaders.OAS_UNIQUE_OPERATION_ID); + : (String) message.getHeader(OAS_UNIQUE_OPERATION_ID); String specificationId = validationContext.getSchemaRepository() != null ? validationContext.getSchemaRepository() - : (String) message.getHeader(OpenApiMessageHeaders.OAS_SPECIFICATION_ID); + : (String) message.getHeader(OAS_SPECIFICATION_ID); if (isNotEmpty(specificationId) && isNotEmpty(operationId)) { return validateOpenApiOperation(context, message, schemaRepositories, diff --git a/connectors/citrus-openapi/src/main/java/org/citrusframework/openapi/validation/OpenApiValidationContextLoader.java b/connectors/citrus-openapi/src/main/java/org/citrusframework/openapi/validation/OpenApiValidationContextLoader.java index ceb44ae74e..a2a14fe775 100644 --- a/connectors/citrus-openapi/src/main/java/org/citrusframework/openapi/validation/OpenApiValidationContextLoader.java +++ b/connectors/citrus-openapi/src/main/java/org/citrusframework/openapi/validation/OpenApiValidationContextLoader.java @@ -29,7 +29,6 @@ import io.swagger.v3.parser.core.models.ParseOptions; import io.swagger.v3.parser.core.models.SwaggerParseResult; import jakarta.annotation.Nonnull; -import java.net.URL; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -37,7 +36,6 @@ import org.citrusframework.exceptions.CitrusRuntimeException; import org.citrusframework.exceptions.ValidationException; import org.citrusframework.openapi.OpenApiResourceLoader; -import org.citrusframework.spi.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,73 +57,12 @@ private OpenApiValidationContextLoader() { // Static access only } - /** - * Creates an OpenApiValidationContext from a secured OpenAPI web resource. - * - * @param url the URL of the secured OpenAPI web resource - * @return the OpenApiValidationContext - */ - public static OpenApiValidationContext fromSecuredWebResource(@Nonnull URL url) { - return createValidationContext(new OpenApiLoader().loadApi( - SpecSource.inline(OpenApiResourceLoader.rawFromSecuredWebResource(url)), emptyList(), - defaultParseOptions())); - } - - /** - * Creates an OpenApiValidationContext from an OpenAPI web resource. - * - * @param url the URL of the OpenAPI web resource - * @return the OpenApiValidationContext - */ - public static OpenApiValidationContext fromWebResource(@Nonnull URL url, - OpenApiValidationPolicy openApiValidationPolicy) { - return createValidationContext( - loadOpenApi(url.toString(), - SpecSource.inline(OpenApiResourceLoader.rawFromWebResource(url)), - openApiValidationPolicy)); - } - - /** - * Creates an OpenApiValidationContext from an OpenAPI file. - * - * @param resource the resource containing the OpenAPI specification - * @param openApiValidationPolicy the policy used for validation of the OpenApi - * @return the OpenApiValidationContext - */ - public static OpenApiValidationContext fromFile(@Nonnull Resource resource, + public static OpenApiValidationContext fromSpec(String openApiSpecAsString, OpenApiValidationPolicy openApiValidationPolicy) { - return createValidationContext( - loadOpenApi(resource.getLocation(), - SpecSource.inline(OpenApiResourceLoader.rawFromFile(resource)), - openApiValidationPolicy)); - } - - private static OpenAPI loadOpenApi(String identifier, SpecSource specSource, - OpenApiValidationPolicy openApiValidationPolicy) { - - logger.debug("Loading OpenApi: {}", identifier); - OpenAPIParser openAPIParser = new OpenAPIParser(); - - SwaggerParseResult swaggerParseResult; - if (specSource.isInlineSpecification()) { - swaggerParseResult = openAPIParser.readContents(specSource.getValue(), emptyList(), - defaultParseOptions()); - } else if (specSource.isSpecUrl()) { - swaggerParseResult = openAPIParser.readLocation(specSource.getValue(), emptyList(), - defaultParseOptions()); - } else { - // Try to load as a URL first... - swaggerParseResult = openAPIParser.readLocation(specSource.getValue(), emptyList(), - defaultParseOptions()); - if (swaggerParseResult == null) { - // ...then try to load as a content string - swaggerParseResult = openAPIParser.readContents(specSource.getValue(), emptyList(), - defaultParseOptions()); - } - } - - return handleSwaggerParserResult(identifier, swaggerParseResult, openApiValidationPolicy); + return createValidationContext( + handleSwaggerParserResult(openApiSpecAsString, openAPIParser.readContents(openApiSpecAsString, emptyList(), + defaultParseOptions()), openApiValidationPolicy)); } private static OpenAPI handleSwaggerParserResult(String identifier, diff --git a/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/OpenApiRepositoryTest.java b/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/OpenApiRepositoryTest.java index 5940039ec4..817b06825d 100644 --- a/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/OpenApiRepositoryTest.java +++ b/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/OpenApiRepositoryTest.java @@ -82,7 +82,7 @@ public void shouldInitializeFaultyOpenApiRepositoryByDefault() { @Test public void shouldFailOnFaultyOpenApiRepositoryByStrictValidation() { OpenApiRepository openApiRepository = new OpenApiRepository(); - openApiRepository.setOpenApiValidationPolicy(OpenApiValidationPolicy.STRICT); + openApiRepository.setValidationPolicy(OpenApiValidationPolicy.STRICT); openApiRepository.setLocations( singletonList("org/citrusframework/openapi/faulty/faulty-ping-api.yaml")); diff --git a/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/groovy/OpenApiClientTest.java b/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/groovy/OpenApiClientTest.java index 7ce3343f5b..bc9a4d14b9 100644 --- a/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/groovy/OpenApiClientTest.java +++ b/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/groovy/OpenApiClientTest.java @@ -167,8 +167,8 @@ public void shouldLoadOpenApiClientActions() { assertNotNull(httpMessageBuilder.getMessage().getHeaders().get(MessageHeaders.ID)); assertNotNull(httpMessageBuilder.getMessage().getHeaders().get(MessageHeaders.TIMESTAMP)); assertEquals(httpMessageBuilder.getMessage().getHeaders().get(HttpMessageHeaders.HTTP_REQUEST_METHOD), HttpMethod.GET.name()); - assertEquals(httpMessageBuilder.getMessage().getHeaders().get(EndpointUriResolver.REQUEST_PATH_HEADER_NAME), "/pet/${petId}"); - assertEquals(httpMessageBuilder.getMessage().getHeaders().get(HttpMessageHeaders.HTTP_REQUEST_URI), "/pet/${petId}"); + assertEquals(httpMessageBuilder.getMessage().getHeaders().get(EndpointUriResolver.REQUEST_PATH_HEADER_NAME), "/petstore/v3/pet/${petId}"); + assertEquals(httpMessageBuilder.getMessage().getHeaders().get(HttpMessageHeaders.HTTP_REQUEST_URI), "/petstore/v3/pet/${petId}"); assertNull(httpMessageBuilder.getMessage().getHeaders().get(HttpMessageHeaders.HTTP_QUERY_PARAMS)); assertNull(httpMessageBuilder.getMessage().getHeaders().get(EndpointUriResolver.ENDPOINT_URI_HEADER_NAME)); assertEquals(sendMessageAction.getEndpoint(), httpClient); @@ -209,8 +209,8 @@ public void shouldLoadOpenApiClientActions() { Map requestHeaders = httpMessageBuilder.buildMessageHeaders(context); assertEquals(requestHeaders.size(), 4L); assertEquals(requestHeaders.get(HttpMessageHeaders.HTTP_REQUEST_METHOD), HttpMethod.POST.name()); - assertEquals(requestHeaders.get(EndpointUriResolver.REQUEST_PATH_HEADER_NAME), "/pet"); - assertEquals(requestHeaders.get(HttpMessageHeaders.HTTP_REQUEST_URI), "/pet"); + assertEquals(requestHeaders.get(EndpointUriResolver.REQUEST_PATH_HEADER_NAME), "/petstore/v3/pet"); + assertEquals(requestHeaders.get(HttpMessageHeaders.HTTP_REQUEST_URI), "/petstore/v3/pet"); assertEquals(requestHeaders.get(HttpMessageHeaders.HTTP_CONTENT_TYPE), APPLICATION_JSON_VALUE); assertNull(sendMessageAction.getEndpointUri()); assertEquals(sendMessageAction.getEndpoint(), httpClient); diff --git a/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/integration/OpenApiClientApiContextPathIT.java b/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/integration/OpenApiClientApiContextPathIT.java new file mode 100644 index 0000000000..f3386fcc41 --- /dev/null +++ b/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/integration/OpenApiClientApiContextPathIT.java @@ -0,0 +1,155 @@ +/* + * 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.integration; + +import static org.citrusframework.http.actions.HttpActionBuilder.http; +import static org.citrusframework.openapi.actions.OpenApiActionBuilder.openapi; +import static org.springframework.http.HttpStatus.OK; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +import org.citrusframework.annotations.CitrusTest; +import org.citrusframework.http.client.HttpClient; +import org.citrusframework.http.client.HttpClientBuilder; +import org.citrusframework.http.server.HttpServer; +import org.citrusframework.http.server.HttpServerBuilder; +import org.citrusframework.openapi.AutoFillType; +import org.citrusframework.openapi.OpenApiSpecification; +import org.citrusframework.openapi.integration.OpenApiClientIT.Config; +import org.citrusframework.spi.Resources; +import org.citrusframework.testng.spring.TestNGCitrusSpringSupport; +import org.citrusframework.util.SocketUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.ContextConfiguration; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * This integration test demonstrates how to configure and test OpenAPI specifications with different context paths + * using the Citrus framework. {@link OpenApiSpecification}s can be hooked to different context paths, with detailed control + * over how the context path is constructed. + *

+ * By default, the basePath from the OpenAPI specification is used. If the basePath is not present, the plain operation + * path is used. However, you can configure detailed control over the context path using the `contextPath` and `neglectBasePath` properties. + *

+ * The test uses these specifications with different context paths to verify the correct API paths are used in the HTTP requests and responses. + * + * @see OpenApiSpecification + * @see CitrusTest + */ +@Test +@ContextConfiguration(classes = {Config.class}) +public class OpenApiClientApiContextPathIT extends TestNGCitrusSpringSupport { + + public static final String VALID_PET_PATH = "classpath:org/citrusframework/openapi/petstore/pet.json"; + + @Autowired + private HttpServer httpServer; + + @Autowired + private HttpClient httpClient; + + private static final OpenApiSpecification petstoreSpec = OpenApiSpecification.from( + Resources.create("classpath:org/citrusframework/openapi/petstore/petstore-v3.json")) + .alias("spec1"); + + private static final OpenApiSpecification petstoreSpecNeglectingBasePath = OpenApiSpecification.from( + Resources.create("classpath:org/citrusframework/openapi/petstore/petstore-v3.json")) + .alias("spec2") + .neglectBasePath(true); + + private static final OpenApiSpecification petstoreSpecWithContextPath = OpenApiSpecification.from( + Resources.create("classpath:org/citrusframework/openapi/petstore/petstore-v3.json")) + .alias("spec3") + .rootContextPath("/api"); + + private static final OpenApiSpecification petstoreSpecWithContextPathNeglectingBasePath = OpenApiSpecification.from( + Resources.create("classpath:org/citrusframework/openapi/petstore/petstore-v3.json")) + .alias("spec4") + .neglectBasePath(true) + .rootContextPath("/api"); + + @DataProvider + public static Object[][] openApiSpecifications() { + return new Object[][]{ + {petstoreSpec, "/petstore/v3/pet"}, + {petstoreSpecNeglectingBasePath, "/pet"}, + {petstoreSpecWithContextPath, "/api/petstore/v3/pet"}, + {petstoreSpecWithContextPathNeglectingBasePath, "/api/pet"}, + }; + } + + @CitrusTest + @Test(dataProvider = "openApiSpecifications") + public void shouldExecuteGetPetById(OpenApiSpecification spec, String contextPath) { + + variable("petId", "1001"); + + when(openapi(spec) + .client(httpClient) + .send("getPetById") + .autoFill(AutoFillType.ALL) + .message() + .fork(true)); + + then(http().server(httpServer) + .receive() + .get(contextPath+"/1001") + .message() + .accept("@contains('application/json')@")); + + then(http().server(httpServer) + .send() + .response(OK) + .message() + .body(Resources.create(VALID_PET_PATH)) + .contentType(APPLICATION_JSON_VALUE)); + + then(openapi(spec) + .client(httpClient).receive("getPetById", OK) + .schemaValidation(true)); + + } + + + @Configuration + public static class Config { + + private final int port = SocketUtils.findAvailableTcpPort(8080); + + @Bean + public HttpServer httpServer() { + + return new HttpServerBuilder() + .port(port) + .timeout(5000L) + .autoStart(true) + .defaultStatus(HttpStatus.NO_CONTENT) + .build(); + } + + @Bean + public HttpClient httpClient() { + return new HttpClientBuilder() + .requestUrl("http://localhost:%d".formatted(port)) + .build(); + } + + } +} diff --git a/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/integration/OpenApiClientIT.java b/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/integration/OpenApiClientIT.java index c11171e9c8..6b563cf507 100644 --- a/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/integration/OpenApiClientIT.java +++ b/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/integration/OpenApiClientIT.java @@ -36,9 +36,9 @@ import org.citrusframework.message.MessageType; import org.citrusframework.openapi.AutoFillType; import org.citrusframework.openapi.OpenApiRepository; -import org.citrusframework.openapi.OpenApiSpecification; import org.citrusframework.openapi.actions.OpenApiClientResponseActionBuilder; import org.citrusframework.openapi.integration.OpenApiClientIT.Config; +import org.citrusframework.openapi.validation.OpenApiValidationPolicy; import org.citrusframework.spi.Resources; import org.citrusframework.testng.spring.TestNGCitrusSpringSupport; import org.citrusframework.util.SocketUtils; @@ -57,9 +57,6 @@ public class OpenApiClientIT extends TestNGCitrusSpringSupport { public static final String VALID_PET_PATH = "classpath:org/citrusframework/openapi/petstore/pet.json"; public static final String INVALID_PET_PATH = "classpath:org/citrusframework/openapi/petstore/pet_invalid.json"; - private final OpenApiSpecification petstoreSpec = OpenApiSpecification.from( - Resources.create("classpath:org/citrusframework/openapi/petstore/petstore-v3.json")); - @Autowired private HttpServer httpServer; @@ -72,7 +69,7 @@ public void shouldExecuteGetPetById() { variable("petId", "1001"); - when(openapi(petstoreSpec) + when(openapi("petstore-v3") .client(httpClient) .send("getPetById") .autoFill(AutoFillType.ALL) @@ -81,7 +78,7 @@ public void shouldExecuteGetPetById() { then(http().server(httpServer) .receive() - .get("/pet/${petId}") + .get("/pet/1001") .message() .accept("@contains('application/json')@")); @@ -92,7 +89,7 @@ public void shouldExecuteGetPetById() { .body(Resources.create(VALID_PET_PATH)) .contentType(APPLICATION_JSON_VALUE)); - then(openapi(petstoreSpec) + then(openapi("petstore-v3") .client(httpClient).receive("getPetById", OK) .schemaValidation(true)); @@ -104,7 +101,7 @@ public void shouldFailOnMissingNameInResponse() { variable("petId", "1001"); - when(openapi(petstoreSpec) + when(openapi("petstore-v3") .client(httpClient) .send("getPetById") .autoFill(AutoFillType.ALL) @@ -124,7 +121,7 @@ public void shouldFailOnMissingNameInResponse() { .body(Resources.create(INVALID_PET_PATH)) .contentType(APPLICATION_JSON_VALUE)); - OpenApiClientResponseActionBuilder clientResponseActionBuilder = openapi(petstoreSpec) + OpenApiClientResponseActionBuilder clientResponseActionBuilder = openapi("petstore-v3") .client(httpClient).receive("getPetById", OK) .schemaValidation(true); assertThrows(() -> then(clientResponseActionBuilder)); @@ -159,6 +156,8 @@ public void shouldFailOnMissingNameInResponseWhenUsingExplicitSchemaValidation() .body(Resources.create(INVALID_PET_PATH)) .contentType(APPLICATION_JSON_VALUE)); + // We can validate an OpenAPI response against a specific schema, + // even without using open api client. HttpClientResponseActionBuilder.HttpMessageBuilderSupport receiveGetPetById = http().client( httpClient) .receive() @@ -167,7 +166,7 @@ public void shouldFailOnMissingNameInResponseWhenUsingExplicitSchemaValidation() .type(MessageType.JSON) .validate(openApi(null) .schemaValidation(true) - .schemaRepository(petstoreSpec.getUid()) + .schemaRepository("petstore-v3.json") .schema("getPetById")); assertThrows(TestCaseFailedException.class, () -> then(receiveGetPetById)); @@ -180,7 +179,7 @@ public void shouldSucceedOnMissingNameInResponseWithValidationDisabled() { variable("petId", "1001"); - when(openapi(petstoreSpec) + when(openapi("petstore-v3") .client(httpClient) .send("getPetById") .autoFill(AutoFillType.ALL) @@ -200,7 +199,7 @@ public void shouldSucceedOnMissingNameInResponseWithValidationDisabled() { .body(Resources.create(INVALID_PET_PATH)) .contentType(APPLICATION_JSON_VALUE)); - then(openapi(petstoreSpec) + then(openapi("petstore-v3") .client(httpClient).receive("getPetById", OK) .schemaValidation(false)); @@ -208,10 +207,10 @@ public void shouldSucceedOnMissingNameInResponseWithValidationDisabled() { @CitrusTest @Test - public void shouldProperlyExecuteGetAndAddPetFromRepository() { + public void shouldProperlyExecuteAddPetFromRepository() { variable("petId", "1001"); - when(openapi(petstoreSpec) + when(openapi("petstore-v3") .client(httpClient) .send("addPet") .autoFill(AutoFillType.ALL) @@ -241,7 +240,7 @@ public void shouldProperlyExecuteGetAndAddPetFromRepository() { .response(HttpStatus.CREATED) .message()); - then(openapi(petstoreSpec) + then(openapi("petstore-v3") .client(httpClient) .receive("addPet", HttpStatus.CREATED)); @@ -252,7 +251,7 @@ public void shouldProperlyExecuteGetAndAddPetFromRepository() { public void shouldFailOnMissingNameInRequest() { variable("petId", "1001"); - HttpMessageBuilderSupport addPetBuilder = openapi(petstoreSpec) + HttpMessageBuilderSupport addPetBuilder = openapi("petstore-v3") .client(httpClient) .send("addPet") .message().body(Resources.create(INVALID_PET_PATH)); @@ -264,7 +263,7 @@ public void shouldFailOnMissingNameInRequest() { @Test public void shouldFailOnWrongQueryIdType() { variable("petId", "xxxx"); - HttpMessageBuilderSupport addPetBuilder = openapi(petstoreSpec) + HttpMessageBuilderSupport addPetBuilder = openapi("petstore-v3") .client(httpClient) .send("addPet") .message().body(Resources.create(VALID_PET_PATH)); @@ -275,7 +274,7 @@ public void shouldFailOnWrongQueryIdType() { @Test public void shouldSucceedOnWrongQueryIdTypeWithOasDisabled() { variable("petId", "xxxx"); - HttpMessageBuilderSupport addPetBuilder = openapi(petstoreSpec) + HttpMessageBuilderSupport addPetBuilder = openapi("petstore-v3") .client(httpClient) .send("addPet") .schemaValidation(false) @@ -289,9 +288,47 @@ public void shouldSucceedOnWrongQueryIdTypeWithOasDisabled() { } - @DataProvider(name = "pingApiOperationDataprovider") - public static Object[][] pingApiOperationDataprovider() { - return new Object[][]{{"doPing"}, {"doPong"}, {"doPung"}}; + @DataProvider + public Object[][] aliases() { + return new Object[][] { + {"petstore-v3"}, + {"Swagger Petstore"}, + {"Swagger Petstore/1.0.1"}, + }; + } + + /** + * Using OpenApiRepository, we should be able to resolve the OpenAPI spec from the name of the + * spec file or one of its aliases. + */ + @CitrusTest + @Test(dataProvider = "aliases") + public void getPetByIdUsingOpenApiRepositoryAndAlias(String alias) { + variable("petId", "1001"); + + when(openapi(alias) + .client(httpClient) + .send("getPetById") + .autoFill(AutoFillType.ALL) + .message() + .fork(true)); + + then(http().server(httpServer) + .receive() + .get("/pet/${petId}") + .message() + .accept("@contains('application/json')@")); + + then(http().server(httpServer) + .send() + .response(OK) + .message() + .body(Resources.create(VALID_PET_PATH)) + .contentType(APPLICATION_JSON_VALUE)); + + then(openapi(alias) + .client(httpClient).receive("getPetById", OK) + .schemaValidation(true)); } @Configuration @@ -321,7 +358,9 @@ public HttpClient httpClient() { public OpenApiRepository petstoreOpenApiRepository() { return new OpenApiRepository() .locations(singletonList( - "classpath:org/citrusframework/openapi/petstore/petstore-v3.json")); + "classpath:org/citrusframework/openapi/petstore/petstore-v3.json")) + .neglectBasePath(true) + .validationPolicy(OpenApiValidationPolicy.REPORT); } } } diff --git a/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/integration/OpenApiServerIT.java b/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/integration/OpenApiServerIT.java index 89831f15a2..1a1a19e820 100644 --- a/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/integration/OpenApiServerIT.java +++ b/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/integration/OpenApiServerIT.java @@ -16,6 +16,13 @@ package org.citrusframework.openapi.integration; +import static java.util.Collections.singletonList; +import static org.citrusframework.http.actions.HttpActionBuilder.http; +import static org.citrusframework.openapi.actions.OpenApiActionBuilder.openapi; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.testng.Assert.assertThrows; +import static org.testng.Assert.fail; + import org.citrusframework.annotations.CitrusTest; import org.citrusframework.exceptions.TestCaseFailedException; import org.citrusframework.http.actions.HttpServerResponseActionBuilder.HttpMessageBuilderSupport; @@ -25,6 +32,7 @@ import org.citrusframework.http.server.HttpServerBuilder; import org.citrusframework.openapi.AutoFillType; import org.citrusframework.openapi.OpenApiRepository; +import org.citrusframework.openapi.OpenApiSpecification; import org.citrusframework.openapi.actions.OpenApiActionBuilder; import org.citrusframework.openapi.actions.OpenApiServerRequestActionBuilder; import org.citrusframework.openapi.actions.OpenApiServerResponseActionBuilder; @@ -37,15 +45,9 @@ import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; import org.springframework.test.context.ContextConfiguration; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import static java.util.Collections.singletonList; -import static org.citrusframework.http.actions.HttpActionBuilder.http; -import static org.citrusframework.openapi.actions.OpenApiActionBuilder.openapi; -import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; -import static org.testng.Assert.assertThrows; -import static org.testng.Assert.fail; - @Test @ContextConfiguration(classes = {Config.class}) public class OpenApiServerIT extends TestNGCitrusSpringSupport { @@ -59,22 +61,11 @@ public class OpenApiServerIT extends TestNGCitrusSpringSupport { @Autowired private HttpClient httpClient; - @CitrusTest - public void shouldExecuteGetPetById() { - executeGetPetById(HttpStatus.OK); - } + private final OpenApiSpecification petstoreSpec = OpenApiSpecification.from( + Resources.create("classpath:org/citrusframework/openapi/petstore/petstore-v3.json")); - /** - * Sending an open api response with a status not specified in the spec should not fail. - * This is because the OpenAPI spec does not strictly require modelling of all possible - * responses. - */ @CitrusTest - public void shouldExecuteGetPetByIdWithUnknownResponse() { - executeGetPetById(HttpStatus.CREATED); - } - - private void executeGetPetById(HttpStatus httpStatus) { + public void getPetById() { variable("petId", "1001"); when(http() @@ -85,13 +76,14 @@ private void executeGetPetById(HttpStatus httpStatus) { .accept(APPLICATION_JSON_VALUE) .fork(true)); - then(openapi("petstore-v3") + then(openapi(petstoreSpec) .server(httpServer) .receive("getPetById") .message() ); - then(openapi("petstore-v3") + HttpStatus httpStatus = HttpStatus.OK; + then(openapi(petstoreSpec) .server(httpServer) .send("getPetById", httpStatus)); @@ -115,32 +107,42 @@ private void executeGetPetById(HttpStatus httpStatus) { """)); } + /** + * Sending an open api response with a status not specified in the spec should not fail. + * This is because the OpenAPI spec does not strictly require modelling of all possible + * responses. + */ @CitrusTest - public void shouldExecuteGetPetByIdWithRandomizedId() { + public void getPetByIdWithUnknownResponse() { + variable("petId", "1001"); + when(http() - .client(httpClient) - .send() - .get("/pet/726354") - .message() - .accept(APPLICATION_JSON_VALUE) - .fork(true)); + .client(httpClient) + .send() + .get("/pet/${petId}") + .message() + .accept(APPLICATION_JSON_VALUE) + .fork(true)); - then(openapi("petstore-v3") - .server(httpServer) - .receive("getPetById") - .message() + then(openapi(petstoreSpec) + .server(httpServer) + .receive("getPetById") + .message() ); - then(openapi("petstore-v3") - .server(httpServer) - .send("getPetById", HttpStatus.OK)); + // Fake any unexpected status code, to test whether we do not run into + // validation issues. + HttpStatus httpStatus = HttpStatus.CREATED; + then(openapi(petstoreSpec) + .server(httpServer) + .send("getPetById", httpStatus)); then(http() - .client(httpClient) - .receive() - .response(HttpStatus.OK) - .message() - .body(""" + .client(httpClient) + .receive() + .response(httpStatus) + .message() + .body(""" { "id": "@isNumber()@", "name": "@notEmpty()@", @@ -155,8 +157,9 @@ public void shouldExecuteGetPetByIdWithRandomizedId() { """)); } + @CitrusTest - public void executeGetPetByIdShouldFailOnInvalidResponse() { + public void getPetByIdShouldFailOnInvalidResponse() { variable("petId", "1001"); when(http() @@ -167,11 +170,11 @@ public void executeGetPetByIdShouldFailOnInvalidResponse() { .accept(APPLICATION_JSON_VALUE) .fork(true)); - then(openapi("petstore-v3") + then(openapi(petstoreSpec) .server(httpServer) .receive("getPetById")); - HttpMessageBuilderSupport getPetByIdResponseBuilder = openapi("petstore-v3") + HttpMessageBuilderSupport getPetByIdResponseBuilder = openapi(petstoreSpec) .server(httpServer) .send("getPetById", HttpStatus.OK) .message().body(""" @@ -191,7 +194,7 @@ public void executeGetPetByIdShouldFailOnInvalidResponse() { } @CitrusTest - public void executeGetPetByIdShouldSucceedOnInvalidResponseWithValidationDisabled() { + public void getPetByIdShouldSucceedOnInvalidResponseWithValidationDisabled() { variable("petId", "1001"); when(http() @@ -202,11 +205,11 @@ public void executeGetPetByIdShouldSucceedOnInvalidResponseWithValidationDisable .accept(APPLICATION_JSON_VALUE) .fork(true)); - then(openapi("petstore-v3") + then(openapi(petstoreSpec) .server(httpServer) .receive("getPetById")); - HttpMessageBuilderSupport getPetByIdResponseBuilder = openapi("petstore-v3") + HttpMessageBuilderSupport getPetByIdResponseBuilder = openapi(petstoreSpec) .server(httpServer) .send("getPetById", HttpStatus.OK) .schemaValidation(false) @@ -247,17 +250,17 @@ public void executeGetPetByIdShouldSucceedOnInvalidResponseWithValidationDisable @CitrusTest public void shouldExecuteAddPet() { - shouldExecuteAddPet(openapi("petstore-v3"), VALID_PET_PATH, true, true); + shouldExecuteAddPet(openapi(petstoreSpec), VALID_PET_PATH, true, true); } @CitrusTest public void shouldFailOnMissingNameInRequest() { - shouldExecuteAddPet(openapi("petstore-v3"), INVALID_PET_PATH, false, true); + shouldExecuteAddPet(openapi(petstoreSpec), INVALID_PET_PATH, false, true); } @CitrusTest public void shouldPassOnMissingNameInRequestIfValidationIsDisabled() { - shouldExecuteAddPet(openapi("petstore-v3"), INVALID_PET_PATH, false, false); + shouldExecuteAddPet(openapi(petstoreSpec), INVALID_PET_PATH, false, false); } @CitrusTest @@ -272,11 +275,11 @@ public void shouldFailOnMissingNameInResponse() { .accept(APPLICATION_JSON_VALUE) .fork(true)); - then(openapi("petstore-v3") + then(openapi(petstoreSpec) .server(httpServer) .receive("getPetById")); - OpenApiServerResponseActionBuilder sendMessageActionBuilder = openapi("petstore-v3") + OpenApiServerResponseActionBuilder sendMessageActionBuilder = openapi(petstoreSpec) .server(httpServer) .send("getPetById", HttpStatus.OK); sendMessageActionBuilder.message().body(Resources.create(INVALID_PET_PATH)); @@ -296,11 +299,11 @@ public void shouldFailOnMissingPayload() { .accept(APPLICATION_JSON_VALUE) .fork(true)); - then(openapi("petstore-v3") + then(openapi(petstoreSpec) .server(httpServer) .receive("getPetById")); - OpenApiServerResponseActionBuilder sendMessageActionBuilder = openapi("petstore-v3") + OpenApiServerResponseActionBuilder sendMessageActionBuilder = openapi(petstoreSpec) .server(httpServer) .send("getPetById", HttpStatus.OK) .autoFill(AutoFillType.NONE); @@ -321,7 +324,7 @@ public void shouldFailOnWrongQueryIdTypeWithOasDisabled() { .contentType(APPLICATION_JSON_VALUE) .fork(true)); - OpenApiServerRequestActionBuilder addPetBuilder = openapi("petstore-v3") + OpenApiServerRequestActionBuilder addPetBuilder = openapi(petstoreSpec) .server(httpServer) .receive("addPet"); @@ -341,7 +344,7 @@ public void shouldSucceedOnWrongQueryIdTypeWithOasDisabled() { .contentType(APPLICATION_JSON_VALUE) .fork(true)); - OpenApiServerRequestActionBuilder addPetBuilder = openapi("petstore-v3") + OpenApiServerRequestActionBuilder addPetBuilder = openapi(petstoreSpec) .server(httpServer) .receive("addPet") .schemaValidation(false); @@ -386,6 +389,63 @@ private void shouldExecuteAddPet(OpenApiActionBuilder openapi, String requestFil } } + @DataProvider + public Object[][] aliases() { + return new Object[][] { + {"petstore-v3"}, + {"Swagger Petstore"}, + {"Swagger Petstore/1.0.1"}, + }; + } + + /** + * Using OpenApiRepository, we should be able to resolve the OpenAPI spec from the name of the + * spec file or one of its aliases. + */ + @CitrusTest + @Test(dataProvider = "aliases") + public void getPetByIdUsingOpenApiRepositoryAndAlias(String alias) { + variable("petId", "1001"); + + when(http() + .client(httpClient) + .send() + .get("/pet/${petId}") + .message() + .accept(APPLICATION_JSON_VALUE) + .fork(true)); + + then(openapi(alias) + .server(httpServer) + .receive("getPetById") + .message() + ); + + HttpStatus httpStatus = HttpStatus.OK; + then(openapi(alias) + .server(httpServer) + .send("getPetById", httpStatus)); + + then(http() + .client(httpClient) + .receive() + .response(httpStatus) + .message() + .body(""" + { + "id": "@isNumber()@", + "name": "@notEmpty()@", + "category": { + "id": "@isNumber()@", + "name": "@notEmpty()@" + }, + "photoUrls": "@notEmpty()@", + "tags": "@ignore@", + "status": "@matches(sold|pending|available)@" + } + """)); + } + @Configuration public static class Config { diff --git a/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/xml/OpenApiClientTest.java b/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/xml/OpenApiClientTest.java index 06478846a5..598590e947 100644 --- a/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/xml/OpenApiClientTest.java +++ b/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/xml/OpenApiClientTest.java @@ -76,11 +76,13 @@ public class OpenApiClientTest extends AbstractXmlActionTest { private final int port = SocketUtils.findAvailableTcpPort(8080); private final String uri = "http://localhost:" + port + "/test"; - private final MessageQueue inboundQueue = new DefaultMessageQueue("inboundQueue"); - private final Queue responses = new ArrayBlockingQueue<>(6); + private HttpServer httpServer; private HttpClient httpClient; + private final MessageQueue inboundQueue = new DefaultMessageQueue("inboundQueue"); + private final Queue responses = new ArrayBlockingQueue<>(6); + @BeforeClass public void setupEndpoints() { EndpointAdapter endpointAdapter = new DirectEndpointAdapter(new DirectSyncEndpointConfiguration()) { @@ -122,9 +124,7 @@ public void shouldLoadOpenApiClientActions() throws IOException { context.getReferenceResolver().bind("httpClient", httpClient); context.getReferenceResolver().bind("httpServer", httpServer); - String apiAsString = FileUtils.readToString(Resources.create("classpath:org/citrusframework/openapi/petstore/petstore-v3.yaml")); - responses.add(new HttpMessage(apiAsString)); - responses.add(new HttpMessage(apiAsString)); + responses.add(new HttpMessage(FileUtils.readToString(Resources.create("classpath:org/citrusframework/openapi/petstore/petstore-v3.yaml")))); responses.add(new HttpMessage(""" { "id": 1000, @@ -170,8 +170,8 @@ public void shouldLoadOpenApiClientActions() throws IOException { Assert.assertNotNull(httpMessageBuilder.getMessage().getHeaders().get(MessageHeaders.ID)); Assert.assertNotNull(httpMessageBuilder.getMessage().getHeaders().get(MessageHeaders.TIMESTAMP)); Assert.assertEquals(httpMessageBuilder.getMessage().getHeaders().get(HttpMessageHeaders.HTTP_REQUEST_METHOD), HttpMethod.GET.name()); - Assert.assertEquals(httpMessageBuilder.getMessage().getHeaders().get(EndpointUriResolver.REQUEST_PATH_HEADER_NAME), "/pet/${petId}"); - Assert.assertEquals(httpMessageBuilder.getMessage().getHeaders().get(HttpMessageHeaders.HTTP_REQUEST_URI), "/pet/${petId}"); + Assert.assertEquals(httpMessageBuilder.getMessage().getHeaders().get(EndpointUriResolver.REQUEST_PATH_HEADER_NAME), "/petstore/v3/pet/${petId}"); + Assert.assertEquals(httpMessageBuilder.getMessage().getHeaders().get(HttpMessageHeaders.HTTP_REQUEST_URI), "/petstore/v3/pet/${petId}"); Assert.assertNull(httpMessageBuilder.getMessage().getHeaders().get(HttpMessageHeaders.HTTP_QUERY_PARAMS)); Assert.assertNull(httpMessageBuilder.getMessage().getHeaders().get(EndpointUriResolver.ENDPOINT_URI_HEADER_NAME)); Assert.assertEquals(sendMessageAction.getEndpointUri(), "httpClient"); @@ -212,8 +212,8 @@ public void shouldLoadOpenApiClientActions() throws IOException { Map requestHeaders = httpMessageBuilder.buildMessageHeaders(context); Assert.assertEquals(requestHeaders.size(), 4L); Assert.assertEquals(requestHeaders.get(HttpMessageHeaders.HTTP_REQUEST_METHOD), HttpMethod.POST.name()); - Assert.assertEquals(requestHeaders.get(EndpointUriResolver.REQUEST_PATH_HEADER_NAME), "/pet"); - Assert.assertEquals(requestHeaders.get(HttpMessageHeaders.HTTP_REQUEST_URI), "/pet"); + Assert.assertEquals(requestHeaders.get(EndpointUriResolver.REQUEST_PATH_HEADER_NAME), "/petstore/v3/pet"); + Assert.assertEquals(requestHeaders.get(HttpMessageHeaders.HTTP_REQUEST_URI), "/petstore/v3/pet"); Assert.assertEquals(requestHeaders.get(HttpMessageHeaders.HTTP_CONTENT_TYPE), APPLICATION_JSON_VALUE); Assert.assertNull(sendMessageAction.getEndpoint()); Assert.assertEquals(sendMessageAction.getEndpointUri(), "httpClient"); diff --git a/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/yaml/OpenApiClientTest.java b/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/yaml/OpenApiClientTest.java index 00961c3202..8685381ef7 100644 --- a/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/yaml/OpenApiClientTest.java +++ b/connectors/citrus-openapi/src/test/java/org/citrusframework/openapi/yaml/OpenApiClientTest.java @@ -164,8 +164,8 @@ public void shouldLoadOpenApiClientActions() { Assert.assertNotNull(httpMessageBuilder.getMessage().getHeaders().get(MessageHeaders.ID)); Assert.assertNotNull(httpMessageBuilder.getMessage().getHeaders().get(MessageHeaders.TIMESTAMP)); Assert.assertEquals(httpMessageBuilder.getMessage().getHeaders().get(HttpMessageHeaders.HTTP_REQUEST_METHOD), HttpMethod.GET.name()); - Assert.assertEquals(httpMessageBuilder.getMessage().getHeaders().get(EndpointUriResolver.REQUEST_PATH_HEADER_NAME), "/pet/${petId}"); - Assert.assertEquals(httpMessageBuilder.getMessage().getHeaders().get(HttpMessageHeaders.HTTP_REQUEST_URI), "/pet/${petId}"); + Assert.assertEquals(httpMessageBuilder.getMessage().getHeaders().get(EndpointUriResolver.REQUEST_PATH_HEADER_NAME), "/petstore/v3/pet/${petId}"); + Assert.assertEquals(httpMessageBuilder.getMessage().getHeaders().get(HttpMessageHeaders.HTTP_REQUEST_URI), "/petstore/v3/pet/${petId}"); Assert.assertNull(httpMessageBuilder.getMessage().getHeaders().get(HttpMessageHeaders.HTTP_QUERY_PARAMS)); Assert.assertNull(httpMessageBuilder.getMessage().getHeaders().get(EndpointUriResolver.ENDPOINT_URI_HEADER_NAME)); Assert.assertEquals(sendMessageAction.getEndpointUri(), "httpClient"); @@ -206,8 +206,8 @@ public void shouldLoadOpenApiClientActions() { Map requestHeaders = httpMessageBuilder.buildMessageHeaders(context); Assert.assertEquals(requestHeaders.size(), 4L); Assert.assertEquals(requestHeaders.get(HttpMessageHeaders.HTTP_REQUEST_METHOD), HttpMethod.POST.name()); - Assert.assertEquals(requestHeaders.get(EndpointUriResolver.REQUEST_PATH_HEADER_NAME), "/pet"); - Assert.assertEquals(requestHeaders.get(HttpMessageHeaders.HTTP_REQUEST_URI), "/pet"); + Assert.assertEquals(requestHeaders.get(EndpointUriResolver.REQUEST_PATH_HEADER_NAME), "/petstore/v3/pet"); + Assert.assertEquals(requestHeaders.get(HttpMessageHeaders.HTTP_REQUEST_URI), "/petstore/v3/pet"); Assert.assertEquals(requestHeaders.get(HttpMessageHeaders.HTTP_CONTENT_TYPE), APPLICATION_JSON_VALUE); Assert.assertNull(sendMessageAction.getEndpoint()); Assert.assertEquals(sendMessageAction.getEndpointUri(), "httpClient"); diff --git a/core/citrus-base/src/main/java/org/citrusframework/util/StringUtils.java b/core/citrus-base/src/main/java/org/citrusframework/util/StringUtils.java index 604d67aac0..c3f0d1f383 100644 --- a/core/citrus-base/src/main/java/org/citrusframework/util/StringUtils.java +++ b/core/citrus-base/src/main/java/org/citrusframework/util/StringUtils.java @@ -38,8 +38,6 @@ public static boolean hasText(String str) { /** * Helper method checks for null or blank String. - * @param str - * @return */ public static boolean hasNoText(String str) { return !hasText(str); @@ -54,8 +52,6 @@ public static boolean isEmpty(String str) { /** * String helper checking for isEmpty String and adds null check on given parameter. - * @param str - * @return */ public static boolean isNotEmpty(String str) { return !isEmpty(str); @@ -78,7 +74,7 @@ public static String appendSegmentToUrlPath(String path, String segment) { return segment; } - if (segment == null) { + if (isEmpty(segment)) { return path; } diff --git a/core/citrus-base/src/test/java/org/citrusframework/util/StringUtilsTest.java b/core/citrus-base/src/test/java/org/citrusframework/util/StringUtilsTest.java index a1cf7a9dda..8f067a0bc3 100644 --- a/core/citrus-base/src/test/java/org/citrusframework/util/StringUtilsTest.java +++ b/core/citrus-base/src/test/java/org/citrusframework/util/StringUtilsTest.java @@ -62,7 +62,9 @@ public void isEmpty_returnsFalse_forBlankText(String str) { @Test public void appendSegmentToPath() { assertEquals(StringUtils.appendSegmentToUrlPath("s1", "s2"), "s1/s2"); + assertEquals(StringUtils.appendSegmentToUrlPath("s1", ""), "s1"); assertEquals(StringUtils.appendSegmentToUrlPath("s1/", "s2"), "s1/s2"); + assertEquals(StringUtils.appendSegmentToUrlPath("s1/", ""), "s1/"); assertEquals(StringUtils.appendSegmentToUrlPath("s1/", "/s2"), "s1/s2"); assertEquals(StringUtils.appendSegmentToUrlPath("/s1", "/s2"), "/s1/s2"); assertEquals(StringUtils.appendSegmentToUrlPath("/s1/", "/s2"), "/s1/s2"); diff --git a/src/manual/connector-openapi.adoc b/src/manual/connector-openapi.adoc index b2b1dded6a..a70bdba30e 100644 --- a/src/manual/connector-openapi.adoc +++ b/src/manual/connector-openapi.adoc @@ -4,10 +4,17 @@ https://www.openapis.org/[OpenAPI] is a popular specification language to describe HTTP APIs and its exposure to clients. Citrus is able to leverage the specification to auto generate client and server request/response message data. The generated message data follows the rules of a given operation in the specification. -In particular, the message body is generated from the given Json schema rules in that specification. +In particular, the message body is generated according to the given Json schema rules in that specification. This way users may do contract-driven testing where the client and the server ensure the conformity with the contract to obey to the same specification rules. -NOTE: The OpenAPI support in Citrus get enabled by adding a separate Maven module as dependency to your project +Taking it a step further, Citrus OpenAPI offers a TestAPI generator powered by OpenApiGenerator. This +generator produces the necessary Builders, which define explicit actions for each operation in a given +OpenAPI. These actions enable access to any operation within the OpenAPI, making it ideal for testing +the API itself or interacting with other supporting services. The current implementation of Citrus +TestAPI uses Maven for generation and Spring for integration. For more details, please refer to +<> . + +NOTE: The OpenAPI support in Citrus gets enabled by adding a separate Maven module as dependency to your project [source,xml] ---- @@ -29,6 +36,90 @@ Or you may just point the OpenAPI components to a local specification file. Citrus supports OpenAPI on both client and server components so the next sections will describe the usage for each of those. +[[openapi-repository]] +== OpenAPI specification repositories + +An OpenApiRepository can be used to manage OpenAPI specifications. + +.Java +[source,java,indent=0,role="primary"] +---- +@Bean +public OpenApiRepository petstoreOpenApiRepository() { + return new OpenApiRepository() + .locations(singletonList( + "classpath:org/citrusframework/openapi/petstore/petstore-v3.json")) + .neglectBasePath(true) + .validationPolicy(OpenApiValidationPolicy.REPORT) + .rootContextPath("/petstore/v3/api") + .requestValidationEnabled(false) + .responseValidationEnabled(false) + ; +} +---- + +.XML +[source,xml,indent=0,role="secondary"] +---- + + + + + + + + +---- + +|=== +|Property | Description | Default + +| locations | Defines the location(s) of the OpenAPI specification files. May be one file or a directory. E.g. `classpath:org/citrusframework/openapi/petstore/*.json` | +| rootContextPath | Sets the root context path for the API, which overrides any context path defined within the specification itself. | Use the context path specified in the first server element of the OpenApi. +| validationPolicy | Controls the handling of validation errors. The available options are: + +- `FAIL`: Causes the test to fail when validation errors occur. + +- `REPORT`: Reports validation errors without failing the test. + +- `IGNORE`: Ignores any validation errors during test execution. | REPORT (from OpenApiSettings) +| requestValidationEnabled | Enables or disables validation of requests. | true (from OpenApiSettings) +| responseValidationEnabled | Enables or disables validation of responses. | true (from OpenApiSettings) + +|=== + +=== Hooking the Operation Path + +The final path used for an operation during testing is built based on the OpenAPI specification's `basePath`, +the `rootContextPath` configuration, and the `neglectBasePath` setting. The path construction follows these rules: + +1. **Default Path Construction**: +- By default, the path is constructed using the `basePath` from the OpenAPI specification (if defined) combined with the `operationPath`. +- For example, if the `basePath` is `/v1/petstore` and the operation path is `/pet`, the final path will be: +``` +/v1/petstore/pet +``` + +2. **When `rootContextPath` is Set**: +- If a `rootContextPath` is provided, it will be prepended to the `basePath` (if present) and the `operationPath`. +- If the `basePath` is `/v1/petstore`, the `operationPath` is `/pet`, and the `rootContextPath` is `/api`, the resulting path will be: +``` +/api/v1/petstore/pet +``` + +3. **When `neglectBasePath` is Set**: +- If `neglectBasePath` is set to `true`, the `basePath` is ignored, and only the `operationPath` and `rootContextPath` is used. +- For example, if the `basePath` is `/v1/petstore` and the `operationPath` is `/pet`, and the `rootContextPath` is `/api`, setting `neglectBasePath=true` will result in the path: +``` +/api/pet +``` + +Likewise, if the `rootContextPath` is not set, the resulting path will be the `operationPath` only: +``` +/pet +``` + + +These properties allow for flexible configuration of the OpenAPI repository, enabling you to customize how API validation is handled and how the OpenAPI specifications are loaded into the system. + + [[openapi-client]] == OpenAPI client diff --git a/test-api-generator/citrus-test-api-core/src/main/java/org/citrusframework/openapi/testapi/RestApiSendMessageActionBuilder.java b/test-api-generator/citrus-test-api-core/src/main/java/org/citrusframework/openapi/testapi/RestApiSendMessageActionBuilder.java index f8d2d86963..6aa56a26c4 100644 --- a/test-api-generator/citrus-test-api-core/src/main/java/org/citrusframework/openapi/testapi/RestApiSendMessageActionBuilder.java +++ b/test-api-generator/citrus-test-api-core/src/main/java/org/citrusframework/openapi/testapi/RestApiSendMessageActionBuilder.java @@ -17,6 +17,7 @@ package org.citrusframework.openapi.testapi; import static java.lang.String.format; +import static java.net.URLEncoder.encode; import static org.citrusframework.openapi.testapi.OpenApiParameterFormatter.formatArray; import static org.citrusframework.openapi.util.OpenApiUtils.createFullPathOperationIdentifier; import static org.citrusframework.util.FileUtils.getDefaultCharset; @@ -24,7 +25,6 @@ import jakarta.servlet.http.Cookie; import java.lang.reflect.Array; -import java.net.URLEncoder; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -131,7 +131,9 @@ protected void queryParameter(final String name, Object value) { return; } - setParameter((paramName, paramValue) -> super.queryParam(paramName, paramValue != null ? paramValue.toString() : null), name, value); + setParameter((paramName, paramValue) -> super.queryParam( + paramName.replace(" ", "%20"), + paramValue != null ? paramValue.toString() : null), name, value); } protected void queryParameter(final String name, Object value, ParameterStyle parameterStyle, boolean explode, boolean isObject) { @@ -209,6 +211,9 @@ public SendMessageAction doBuild() { // If no endpoint was set explicitly, use the default endpoint given by api if (getEndpoint() == null && getEndpointUri() == null) { + if (generatedApi.getEndpoint() == null) { + throw new CitrusRuntimeException("No endpoint specified for action!"); + } endpoint(generatedApi.getEndpoint()); } @@ -269,7 +274,7 @@ private static void encodeArrayStyleCookies(HttpMessage message) { if (message.getCookies() != null && !message.getCookies().isEmpty()) { for (Cookie cookie : message.getCookies()) { if (cookie.getValue().contains(",")) { - cookie.setValue(URLEncoder.encode(cookie.getValue(), getDefaultCharset())); + cookie.setValue(encode(cookie.getValue(), getDefaultCharset())); } } } 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 34b5319aec..9500cacff1 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 @@ -25,6 +25,7 @@ import static org.citrusframework.util.StringUtils.isNotEmpty; import static {{invokerPackage}}.{{#lambda.titlecase}}{{prefix}}{{/lambda.titlecase}}OpenApi.{{#lambda.camelcase}}{{prefix}}{{/lambda.camelcase}}Specification; +import jakarta.annotation.Nullable; import jakarta.validation.constraints.NotNull; import java.math.BigDecimal; import java.net.URL; @@ -84,19 +85,22 @@ public class {{classname}} implements GeneratedApi private final List customizers; - private final Endpoint endpoint; + /** + * An optional default endpoint which will be passed into the requests. + */ + private final Endpoint defaultEndpoint; - public {{classname}}(Endpoint endpoint) { - this(endpoint, emptyList()); + public {{classname}}(@Nullable Endpoint defaultEndpoint) { + this(defaultEndpoint, emptyList()); } - public {{classname}}(Endpoint endpoint, List customizers) { - this.endpoint = endpoint; + public {{classname}}(@Nullable Endpoint defaultEndpoint, @Nullable List customizers) { + this.defaultEndpoint = defaultEndpoint; this.customizers = customizers; } - public static {{classname}} {{#lambda.camelcase}}{{classname}}{{/lambda.camelcase}}(Endpoint endpoint) { - return new {{classname}}(endpoint); + public static {{classname}} {{#lambda.camelcase}}{{classname}}{{/lambda.camelcase}}(Endpoint defaultEndpoint) { + return new {{classname}}(defaultEndpoint); } @Override @@ -129,8 +133,9 @@ public class {{classname}} implements GeneratedApi } @Override + @Nullable public Endpoint getEndpoint() { - return endpoint; + return defaultEndpoint; } @Override 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 index c0b7fb0ca7..d0a65618da 100644 --- 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 @@ -20,6 +20,7 @@ package {{package}}; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; +import jakarta.annotation.Nullable; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -37,21 +38,24 @@ import org.citrusframework.openapi.testapi.SoapApiSendMessageActionBuilder; public class {{classname}} implements GeneratedApi { - private final Endpoint endpoint; + /** + * An optional default endpoint which will be passed into the requests. + */ + private final Endpoint defaultEndpoint; private final List customizers; - public {{classname}}(Endpoint endpoint) { - this(endpoint, emptyList()); + public {{classname}}(@Nullable Endpoint defaultEndpoint) { + this(defaultEndpoint, emptyList()); } - public {{classname}}(Endpoint endpoint, List customizers) { - this.endpoint = endpoint; + public {{classname}}(@Nullable Endpoint defaultEndpoint, @Nullable List customizers) { + this.defaultEndpoint = defaultEndpoint; this.customizers = customizers; } - public static {{classname}} {{#lambda.camelcase}}{{classname}}{{/lambda.camelcase}}(Endpoint endpoint) { - return new {{classname}}(endpoint); + public static {{classname}} {{#lambda.camelcase}}{{classname}}{{/lambda.camelcase}}(Endpoint defaultEndpoint) { + return new {{classname}}(defaultEndpoint); } @Override @@ -84,8 +88,9 @@ public class {{classname}} implements GeneratedApi } @Override + @Nullable public Endpoint getEndpoint() { - return endpoint; + return defaultEndpoint; } @Override 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 index 7f247f107b..47798c712d 100644 --- 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 @@ -49,8 +49,8 @@ public class {{#lambda.titlecase}}{{prefix}}{{/lambda.titlecase}}BeanConfigurati {{#apiInfo}} {{#apis}} @Bean(name="{{classname}}") - public {{classname}} {{#lambda.camelcase}}{{classname}}{{/lambda.camelcase}}(@Qualifier("{{apiEndpoint}}") Endpoint endpoint, @Autowired(required = false) List customizers) { - return new {{classname}}(endpoint, customizers); + public {{classname}} {{#lambda.camelcase}}{{classname}}{{/lambda.camelcase}}(@Autowired(required = false) @Qualifier("{{apiEndpoint}}") Endpoint defaultEndpoint, @Autowired(required = false) List customizers) { + return new {{classname}}(defaultEndpoint, customizers); } {{/apis}} diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/GeneratedRestApiIT.java b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/GeneratedRestApiIT.java index 7dc844405e..d51316443a 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/GeneratedRestApiIT.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/java/org/citrusframework/openapi/generator/GeneratedRestApiIT.java @@ -1954,6 +1954,32 @@ void java(@CitrusResource TestCaseRunner runner) { } } + @Nested + class ParameterThatNeedUrlEncoding { + + @Test + void java_parameter_with_url_encoding(@CitrusResource TestCaseRunner runner) { + + runner.when(extPetApi.sendGetPetWithParametersRequiringEncoding(1) + .queryID(4) + .fork(true)); + + runner.then(http().server(httpServer) + .receive() + .get("/api/v3/ext/pet/parameter-with-url-encoding-required/1") + .message()); + + runner.then(http().server(httpServer) + .send() + .response(OK) + .message() + .contentType(APPLICATION_JSON_VALUE) + .body("[]")); + + runner.when(extPetApi.receiveGetPetWithSimpleStyleArray("200")); + } + } + /** * Demonstrates the usage of form data. */ diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/apis/petstore-extended-v3.yaml b/test-api-generator/citrus-test-api-generator-core/src/test/resources/apis/petstore-extended-v3.yaml index 4aa50c03f2..da8ceca2fe 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/apis/petstore-extended-v3.yaml +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/apis/petstore-extended-v3.yaml @@ -399,6 +399,38 @@ paths: description: Invalid ID supplied '404': description: Pet not found + /pet/parameter-with-url-encoding-required/{Pet ID}: + get: + tags: + - extPet + summary: Get a pet by id with parameters that potentially require encoding + operationId: getPetWithParametersRequiringEncoding + description: "Returns multiple pets by ID using multiple ids in path parameter." + parameters: + - name: Pet ID + in: path + description: IDs of pet to return + required: true + schema: + type: integer + - name: Query ID + in: query + description: A query parameter containing a space + schema: + type: integer + responses: + '200': + description: Successful operation + content: + application/json: + schema: + type: array + items: + $ref: './petstore-v3.yaml#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found /pet/header/simple: get: tags: diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/model/Category.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/model/Category.java index 11a1afb9f8..f87ae2279b 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/model/Category.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/model/Category.java @@ -24,7 +24,7 @@ /** * Category */ -@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-09T10:28:07.306037900+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") +@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-23T18:00:07.512935700+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") public class Category { private Long id; diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/model/HistoricalData.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/model/HistoricalData.java index fde96ad0ae..e4d8b586d7 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/model/HistoricalData.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/model/HistoricalData.java @@ -25,7 +25,7 @@ /** * Additional historical data for a vaccination report, not contained in internal storage. */ -@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-09T10:28:07.306037900+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") +@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-23T18:00:07.512935700+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") public class HistoricalData { private LocalDate lastVaccinationDate; diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/model/Pet.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/model/Pet.java index c81d0e3837..c124787b5a 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/model/Pet.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/model/Pet.java @@ -29,7 +29,7 @@ /** * Pet */ -@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-09T10:28:07.306037900+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") +@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-23T18:00:07.512935700+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") public class Pet { private Long id; diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/model/PetIdentifier.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/model/PetIdentifier.java index 79db62bb29..e214406d81 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/model/PetIdentifier.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/model/PetIdentifier.java @@ -24,7 +24,7 @@ /** * PetIdentifier */ -@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-09T10:28:07.306037900+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") +@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-23T18:00:07.512935700+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") public class PetIdentifier { private String _name; diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/model/Tag.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/model/Tag.java index c10c692c20..f133cd3e31 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/model/Tag.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/model/Tag.java @@ -24,7 +24,7 @@ /** * Tag */ -@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-09T10:28:07.306037900+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") +@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-23T18:00:07.512935700+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") public class Tag { private Long id; diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/model/VaccinationDocumentResult.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/model/VaccinationDocumentResult.java index 2ee2b3ca1f..4955814194 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/model/VaccinationDocumentResult.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/model/VaccinationDocumentResult.java @@ -24,7 +24,7 @@ /** * VaccinationDocumentResult */ -@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-09T10:28:07.306037900+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") +@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-23T18:00:07.512935700+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") public class VaccinationDocumentResult { private String documentId; diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/request/ExtPetApi.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/request/ExtPetApi.java index 969d47fd03..b85817a1ff 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/request/ExtPetApi.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/request/ExtPetApi.java @@ -8,6 +8,7 @@ import static org.citrusframework.openapi.generator.rest.extpetstore.ExtPetStoreOpenApi.extPetStoreSpecification; +import jakarta.annotation.Nullable; import jakarta.validation.constraints.NotNull; import java.math.BigDecimal; import java.net.URL; @@ -42,7 +43,7 @@ import org.citrusframework.openapi.generator.rest.extpetstore.model.VaccinationDocumentResult; @SuppressWarnings("unused") -@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-09T10:28:07.306037900+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") +@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-23T18:00:07.512935700+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") public class ExtPetApi implements GeneratedApi { @@ -69,19 +70,22 @@ public class ExtPetApi implements GeneratedApi private final List customizers; - private final Endpoint endpoint; + /** + * An optional default endpoint which will be passed into the requests. + */ + private final Endpoint defaultEndpoint; - public ExtPetApi(Endpoint endpoint) { - this(endpoint, emptyList()); + public ExtPetApi(@Nullable Endpoint defaultEndpoint) { + this(defaultEndpoint, emptyList()); } - public ExtPetApi(Endpoint endpoint, List customizers) { - this.endpoint = endpoint; + public ExtPetApi(@Nullable Endpoint defaultEndpoint, @Nullable List customizers) { + this.defaultEndpoint = defaultEndpoint; this.customizers = customizers; } - public static ExtPetApi extPetApi(Endpoint endpoint) { - return new ExtPetApi(endpoint); + public static ExtPetApi extPetApi(Endpoint defaultEndpoint) { + return new ExtPetApi(defaultEndpoint); } @Override @@ -105,8 +109,9 @@ public Map getApiInfoExtensions() { } @Override + @Nullable public Endpoint getEndpoint() { - return endpoint; + return defaultEndpoint; } @Override @@ -612,6 +617,28 @@ public GetPetWithMatrixStyleObjectExplodedReceiveActionBuilder receiveGetPetWith return new GetPetWithMatrixStyleObjectExplodedReceiveActionBuilder(this, statusCode); } + /** + * Builder with type safe required parameters. + */ + public GetPetWithParametersRequiringEncodingSendActionBuilder sendGetPetWithParametersRequiringEncoding(Integer petID) { + return new GetPetWithParametersRequiringEncodingSendActionBuilder(this, petID); + } + + /** + * Builder with required parameters as string, allowing dynamic content using citrus expressions. + */ + public GetPetWithParametersRequiringEncodingSendActionBuilder sendGetPetWithParametersRequiringEncoding$(String petIDExpression ) { + return new GetPetWithParametersRequiringEncodingSendActionBuilder(petIDExpression, this); + } + + public GetPetWithParametersRequiringEncodingReceiveActionBuilder receiveGetPetWithParametersRequiringEncoding(@NotNull HttpStatus statusCode) { + return new GetPetWithParametersRequiringEncodingReceiveActionBuilder(this, Integer.toString(statusCode.value())); + } + + public GetPetWithParametersRequiringEncodingReceiveActionBuilder receiveGetPetWithParametersRequiringEncoding(@NotNull String statusCode) { + return new GetPetWithParametersRequiringEncodingReceiveActionBuilder(this, statusCode); + } + /** * Builder with type safe required parameters. */ @@ -3689,6 +3716,137 @@ public ReceiveMessageAction doBuild() { } + public static class GetPetWithParametersRequiringEncodingSendActionBuilder extends + RestApiSendMessageActionBuilder implements GeneratedApiOperationInfo { + + private static final String METHOD = "GET"; + + private static final String ENDPOINT = "/api/v3/ext/pet/parameter-with-url-encoding-required/{Pet ID}"; + + private static final String OPERATION_NAME = "getPetWithParametersRequiringEncoding"; + + /** + * Constructor with type safe required parameters. + */ + public GetPetWithParametersRequiringEncodingSendActionBuilder(ExtPetApi extPetApi, Integer petID) { + super(extPetApi, extPetStoreSpecification, METHOD, ENDPOINT, OPERATION_NAME); + pathParameter("Pet ID", petID, ParameterStyle.SIMPLE, false, false); + } + + /** + * Constructor with required parameters as string to allow for dynamic content. + */ + public GetPetWithParametersRequiringEncodingSendActionBuilder(String petIDExpression, ExtPetApi extPetApi) { + super(extPetApi, extPetStoreSpecification, METHOD, ENDPOINT, OPERATION_NAME); + pathParameter("Pet ID", petIDExpression, ParameterStyle.SIMPLE, false, false); + } + + @Override + public String getOperationName() { + return OPERATION_NAME; + } + + @Override + public String getMethod() { + return METHOD; + } + + @Override + public String getPath() { + return ENDPOINT; + } + + /** + * Constructor with required parameters as string to allow for dynamic content. + */ + public GetPetWithParametersRequiringEncodingSendActionBuilder(ExtPetApi extPetApi, TestApiClientRequestMessageBuilder messageBuilder, String petIDExpression) { + super(extPetApi, extPetStoreSpecification, messageBuilder, messageBuilder.getMessage(), METHOD, ENDPOINT, OPERATION_NAME); + pathParameter("Pet ID", petIDExpression, ParameterStyle.SIMPLE, false, false); + } + + public GetPetWithParametersRequiringEncodingSendActionBuilder petID(Integer petID) { + pathParameter("Pet ID", petID, ParameterStyle.SIMPLE, false, false); + return this; + } + + public GetPetWithParametersRequiringEncodingSendActionBuilder petID(String petIDExpression) { + pathParameter("Pet ID", petIDExpression, ParameterStyle.SIMPLE, false, false); + return this; + } + + public GetPetWithParametersRequiringEncodingSendActionBuilder queryID(Integer queryID) { + queryParameter("Query ID", queryID, ParameterStyle.FORM, true, false); + return this; + } + + public void setQueryID(Integer queryID) { + queryParameter("Query ID", queryID, ParameterStyle.FORM, true, false); + } + + public GetPetWithParametersRequiringEncodingSendActionBuilder queryID(String queryIDExpression) { + queryParameter("Query ID", queryIDExpression, ParameterStyle.FORM, true, false); + return this; + } + + public void setQueryID(String queryIDExpression) { + queryParameter("Query ID", queryIDExpression, ParameterStyle.FORM, true, false); + } + + @Override + public SendMessageAction doBuild() { + + if (getCustomizers() != null) { + getCustomizers().forEach(customizer -> customizer.customizeRequestBuilder(this, this)); + } + + return super.doBuild(); + } + } + + public static class GetPetWithParametersRequiringEncodingReceiveActionBuilder extends + RestApiReceiveMessageActionBuilder implements GeneratedApiOperationInfo { + + private static final String METHOD = "GET"; + + private static final String ENDPOINT = "/api/v3/ext/pet/parameter-with-url-encoding-required/{Pet ID}"; + + private static final String OPERATION_NAME = "getPetWithParametersRequiringEncoding"; + + public GetPetWithParametersRequiringEncodingReceiveActionBuilder(ExtPetApi extPetApi, String statusCode) { + super(extPetApi, extPetStoreSpecification, METHOD, ENDPOINT, OPERATION_NAME, statusCode); + } + + public GetPetWithParametersRequiringEncodingReceiveActionBuilder(ExtPetApi extPetApi, OpenApiClientResponseMessageBuilder messageBuilder) { + super(extPetApi, extPetStoreSpecification, messageBuilder, messageBuilder.getMessage(), METHOD, ENDPOINT, OPERATION_NAME); + } + + @Override + public String getOperationName() { + return OPERATION_NAME; + } + + @Override + public String getMethod() { + return METHOD; + } + + @Override + public String getPath() { + return ENDPOINT; + } + + @Override + public ReceiveMessageAction doBuild() { + + if (getCustomizers() != null) { + getCustomizers().forEach(customizer -> customizer.customizeResponseBuilder(this, this)); + } + + return super.doBuild(); + } + + } + public static class GetPetWithSimpleStyleArraySendActionBuilder extends RestApiSendMessageActionBuilder implements GeneratedApiOperationInfo { diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/spring/ExtPetStoreBeanConfiguration.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/spring/ExtPetStoreBeanConfiguration.java index fcd1063f29..793bb8fc8e 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/spring/ExtPetStoreBeanConfiguration.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/spring/ExtPetStoreBeanConfiguration.java @@ -15,7 +15,7 @@ import org.citrusframework.openapi.generator.rest.extpetstore.ExtPetStoreOpenApi; @Configuration -@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-09T10:28:07.306037900+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") +@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-23T18:00:07.512935700+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") public class ExtPetStoreBeanConfiguration { @Bean @@ -26,8 +26,8 @@ public OpenApiRepository extPetStoreOpenApiRepository() { } @Bean(name="ExtPetApi") - public ExtPetApi extPetApi(@Qualifier("extpetstore.endpoint") Endpoint endpoint, @Autowired(required = false) List customizers) { - return new ExtPetApi(endpoint, customizers); + public ExtPetApi extPetApi(@Autowired(required = false) @Qualifier("extpetstore.endpoint") Endpoint defaultEndpoint, @Autowired(required = false) List customizers) { + return new ExtPetApi(defaultEndpoint, customizers); } } diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/spring/ExtPetStoreNamespaceHandler.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/spring/ExtPetStoreNamespaceHandler.java index d90e9f9897..6c56c475d2 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/spring/ExtPetStoreNamespaceHandler.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/extpetstore/spring/ExtPetStoreNamespaceHandler.java @@ -12,7 +12,7 @@ import org.citrusframework.openapi.testapi.GeneratedApi; import org.springframework.beans.factory.xml.NamespaceHandlerSupport; -@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-09T10:28:07.306037900+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") +@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-23T18:00:07.512935700+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") public class ExtPetStoreNamespaceHandler extends NamespaceHandlerSupport { @Override @@ -150,6 +150,12 @@ public void init() { new String[]{ "petId" }, new String[]{ }); + registerOperationParsers(ExtPetApi.class,"get-pet-with-parameters-requiring-encoding", "getPetWithParametersRequiringEncoding", "/pet/parameter-with-url-encoding-required/{Pet ID}", + ExtPetApi.GetPetWithParametersRequiringEncodingSendActionBuilder.class, + ExtPetApi.GetPetWithParametersRequiringEncodingReceiveActionBuilder.class, + new String[]{ "petID" }, + new String[]{ "queryID" }); + registerOperationParsers(ExtPetApi.class,"get-pet-with-simple-style-array", "getPetWithSimpleStyleArray", "/pet/simple/{petId}", ExtPetApi.GetPetWithSimpleStyleArraySendActionBuilder.class, ExtPetApi.GetPetWithSimpleStyleArrayReceiveActionBuilder.class, diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/Address.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/Address.java index 1e0b69afc3..79cbc4a5b9 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/Address.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/Address.java @@ -24,7 +24,7 @@ /** * Address */ -@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-09T10:28:06.562367500+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") +@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-23T18:00:06.769523100+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") public class Address { private String street; diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/Category.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/Category.java index b65788bb74..adb7ab5b4a 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/Category.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/Category.java @@ -24,7 +24,7 @@ /** * Category */ -@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-09T10:28:06.562367500+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") +@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-23T18:00:06.769523100+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") public class Category { private Long id; diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/Customer.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/Customer.java index 93d3776f57..304150abb7 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/Customer.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/Customer.java @@ -28,7 +28,7 @@ /** * Customer */ -@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-09T10:28:06.562367500+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") +@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-23T18:00:06.769523100+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") public class Customer { private Long id; diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/ModelApiResponse.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/ModelApiResponse.java index 27a740b992..178c3a56fa 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/ModelApiResponse.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/ModelApiResponse.java @@ -24,7 +24,7 @@ /** * ModelApiResponse */ -@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-09T10:28:06.562367500+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") +@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-23T18:00:06.769523100+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") public class ModelApiResponse { private Integer code; diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/Order.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/Order.java index 9dbd21669d..4d2916f2ff 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/Order.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/Order.java @@ -25,7 +25,7 @@ /** * Order */ -@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-09T10:28:06.562367500+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") +@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-23T18:00:06.769523100+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") public class Order { private Long id; diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/Pet.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/Pet.java index 96cc08f4f1..3863a75cb4 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/Pet.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/Pet.java @@ -29,7 +29,7 @@ /** * Pet */ -@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-09T10:28:06.562367500+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") +@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-23T18:00:06.769523100+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") public class Pet { private Long id; diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/Tag.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/Tag.java index 319de5c72f..d0779114f5 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/Tag.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/Tag.java @@ -24,7 +24,7 @@ /** * Tag */ -@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-09T10:28:06.562367500+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") +@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-23T18:00:06.769523100+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") public class Tag { private Long id; diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/User.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/User.java index 63c474fdc8..fb5338f6b8 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/User.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/model/User.java @@ -24,7 +24,7 @@ /** * User */ -@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-09T10:28:06.562367500+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") +@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-23T18:00:06.769523100+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") public class User { private Long id; diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/request/PetApi.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/request/PetApi.java index d26ecd2a76..dd944f0e25 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/request/PetApi.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/request/PetApi.java @@ -8,6 +8,7 @@ import static org.citrusframework.openapi.generator.rest.petstore.PetStoreOpenApi.petStoreSpecification; +import jakarta.annotation.Nullable; import jakarta.validation.constraints.NotNull; import java.math.BigDecimal; import java.net.URL; @@ -37,7 +38,7 @@ import org.citrusframework.openapi.generator.rest.petstore.model.Pet; @SuppressWarnings("unused") -@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-09T10:28:06.562367500+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") +@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-23T18:00:06.769523100+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") public class PetApi implements GeneratedApi { @@ -49,19 +50,22 @@ public class PetApi implements GeneratedApi private final List customizers; - private final Endpoint endpoint; + /** + * An optional default endpoint which will be passed into the requests. + */ + private final Endpoint defaultEndpoint; - public PetApi(Endpoint endpoint) { - this(endpoint, emptyList()); + public PetApi(@Nullable Endpoint defaultEndpoint) { + this(defaultEndpoint, emptyList()); } - public PetApi(Endpoint endpoint, List customizers) { - this.endpoint = endpoint; + public PetApi(@Nullable Endpoint defaultEndpoint, @Nullable List customizers) { + this.defaultEndpoint = defaultEndpoint; this.customizers = customizers; } - public static PetApi petApi(Endpoint endpoint) { - return new PetApi(endpoint); + public static PetApi petApi(Endpoint defaultEndpoint) { + return new PetApi(defaultEndpoint); } @Override @@ -85,8 +89,9 @@ public Map getApiInfoExtensions() { } @Override + @Nullable public Endpoint getEndpoint() { - return endpoint; + return defaultEndpoint; } @Override diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/request/StoreApi.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/request/StoreApi.java index 346e4740d7..e3997dbece 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/request/StoreApi.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/request/StoreApi.java @@ -8,6 +8,7 @@ import static org.citrusframework.openapi.generator.rest.petstore.PetStoreOpenApi.petStoreSpecification; +import jakarta.annotation.Nullable; import jakarta.validation.constraints.NotNull; import java.math.BigDecimal; import java.net.URL; @@ -36,7 +37,7 @@ import org.citrusframework.openapi.generator.rest.petstore.model.Order; @SuppressWarnings("unused") -@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-09T10:28:06.562367500+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") +@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-23T18:00:06.769523100+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") public class StoreApi implements GeneratedApi { @@ -48,19 +49,22 @@ public class StoreApi implements GeneratedApi private final List customizers; - private final Endpoint endpoint; + /** + * An optional default endpoint which will be passed into the requests. + */ + private final Endpoint defaultEndpoint; - public StoreApi(Endpoint endpoint) { - this(endpoint, emptyList()); + public StoreApi(@Nullable Endpoint defaultEndpoint) { + this(defaultEndpoint, emptyList()); } - public StoreApi(Endpoint endpoint, List customizers) { - this.endpoint = endpoint; + public StoreApi(@Nullable Endpoint defaultEndpoint, @Nullable List customizers) { + this.defaultEndpoint = defaultEndpoint; this.customizers = customizers; } - public static StoreApi storeApi(Endpoint endpoint) { - return new StoreApi(endpoint); + public static StoreApi storeApi(Endpoint defaultEndpoint) { + return new StoreApi(defaultEndpoint); } @Override @@ -84,8 +88,9 @@ public Map getApiInfoExtensions() { } @Override + @Nullable public Endpoint getEndpoint() { - return endpoint; + return defaultEndpoint; } @Override diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/request/UserApi.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/request/UserApi.java index 22c95378ef..0a36c15896 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/request/UserApi.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/request/UserApi.java @@ -8,6 +8,7 @@ import static org.citrusframework.openapi.generator.rest.petstore.PetStoreOpenApi.petStoreSpecification; +import jakarta.annotation.Nullable; import jakarta.validation.constraints.NotNull; import java.math.BigDecimal; import java.net.URL; @@ -37,7 +38,7 @@ import org.citrusframework.openapi.generator.rest.petstore.model.User; @SuppressWarnings("unused") -@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-09T10:28:06.562367500+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") +@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-23T18:00:06.769523100+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") public class UserApi implements GeneratedApi { @@ -49,19 +50,22 @@ public class UserApi implements GeneratedApi private final List customizers; - private final Endpoint endpoint; + /** + * An optional default endpoint which will be passed into the requests. + */ + private final Endpoint defaultEndpoint; - public UserApi(Endpoint endpoint) { - this(endpoint, emptyList()); + public UserApi(@Nullable Endpoint defaultEndpoint) { + this(defaultEndpoint, emptyList()); } - public UserApi(Endpoint endpoint, List customizers) { - this.endpoint = endpoint; + public UserApi(@Nullable Endpoint defaultEndpoint, @Nullable List customizers) { + this.defaultEndpoint = defaultEndpoint; this.customizers = customizers; } - public static UserApi userApi(Endpoint endpoint) { - return new UserApi(endpoint); + public static UserApi userApi(Endpoint defaultEndpoint) { + return new UserApi(defaultEndpoint); } @Override @@ -85,8 +89,9 @@ public Map getApiInfoExtensions() { } @Override + @Nullable public Endpoint getEndpoint() { - return endpoint; + return defaultEndpoint; } @Override diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/spring/PetStoreBeanConfiguration.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/spring/PetStoreBeanConfiguration.java index 7a2334129c..96e9fae6cf 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/spring/PetStoreBeanConfiguration.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/spring/PetStoreBeanConfiguration.java @@ -17,7 +17,7 @@ import org.citrusframework.openapi.generator.rest.petstore.PetStoreOpenApi; @Configuration -@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-09T10:28:06.562367500+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") +@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-23T18:00:06.769523100+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") public class PetStoreBeanConfiguration { @Bean @@ -28,18 +28,18 @@ public OpenApiRepository petStoreOpenApiRepository() { } @Bean(name="PetApi") - public PetApi petApi(@Qualifier("petstore.endpoint") Endpoint endpoint, @Autowired(required = false) List customizers) { - return new PetApi(endpoint, customizers); + public PetApi petApi(@Autowired(required = false) @Qualifier("petstore.endpoint") Endpoint defaultEndpoint, @Autowired(required = false) List customizers) { + return new PetApi(defaultEndpoint, customizers); } @Bean(name="StoreApi") - public StoreApi storeApi(@Qualifier("petstore.endpoint") Endpoint endpoint, @Autowired(required = false) List customizers) { - return new StoreApi(endpoint, customizers); + public StoreApi storeApi(@Autowired(required = false) @Qualifier("petstore.endpoint") Endpoint defaultEndpoint, @Autowired(required = false) List customizers) { + return new StoreApi(defaultEndpoint, customizers); } @Bean(name="UserApi") - public UserApi userApi(@Qualifier("petstore.endpoint") Endpoint endpoint, @Autowired(required = false) List customizers) { - return new UserApi(endpoint, customizers); + public UserApi userApi(@Autowired(required = false) @Qualifier("petstore.endpoint") Endpoint defaultEndpoint, @Autowired(required = false) List customizers) { + return new UserApi(defaultEndpoint, customizers); } } diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/spring/PetStoreNamespaceHandler.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/spring/PetStoreNamespaceHandler.java index 222daac623..4155d1ad5d 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/spring/PetStoreNamespaceHandler.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/rest/petstore/spring/PetStoreNamespaceHandler.java @@ -14,7 +14,7 @@ import org.citrusframework.openapi.testapi.GeneratedApi; import org.springframework.beans.factory.xml.NamespaceHandlerSupport; -@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-09T10:28:06.562367500+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") +@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-23T18:00:06.769523100+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") public class PetStoreNamespaceHandler extends NamespaceHandlerSupport { @Override diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/soap/bookservice/request/BookServiceSoapApi.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/soap/bookservice/request/BookServiceSoapApi.java index ccdf22a2ea..4e76813642 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/soap/bookservice/request/BookServiceSoapApi.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/soap/bookservice/request/BookServiceSoapApi.java @@ -3,6 +3,7 @@ import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; +import jakarta.annotation.Nullable; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -16,25 +17,28 @@ import org.citrusframework.openapi.testapi.SoapApiSendMessageActionBuilder; @SuppressWarnings("unused") -@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-09T10:28:07.680135700+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") +@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-23T18:00:07.870607200+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") public class BookServiceSoapApi implements GeneratedApi { - private final Endpoint endpoint; + /** + * An optional default endpoint which will be passed into the requests. + */ + private final Endpoint defaultEndpoint; private final List customizers; - public BookServiceSoapApi(Endpoint endpoint) { - this(endpoint, emptyList()); + public BookServiceSoapApi(@Nullable Endpoint defaultEndpoint) { + this(defaultEndpoint, emptyList()); } - public BookServiceSoapApi(Endpoint endpoint, List customizers) { - this.endpoint = endpoint; + public BookServiceSoapApi(@Nullable Endpoint defaultEndpoint, @Nullable List customizers) { + this.defaultEndpoint = defaultEndpoint; this.customizers = customizers; } - public static BookServiceSoapApi bookServiceSoapApi(Endpoint endpoint) { - return new BookServiceSoapApi(endpoint); + public static BookServiceSoapApi bookServiceSoapApi(Endpoint defaultEndpoint) { + return new BookServiceSoapApi(defaultEndpoint); } @Override @@ -58,8 +62,9 @@ public Map getApiInfoExtensions() { } @Override + @Nullable public Endpoint getEndpoint() { - return endpoint; + return defaultEndpoint; } @Override diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/soap/bookservice/spring/BookServiceBeanConfiguration.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/soap/bookservice/spring/BookServiceBeanConfiguration.java index 643177200f..96e2616ee2 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/soap/bookservice/spring/BookServiceBeanConfiguration.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/soap/bookservice/spring/BookServiceBeanConfiguration.java @@ -15,7 +15,7 @@ import org.citrusframework.openapi.generator.soap.bookservice.BookServiceOpenApi; @Configuration -@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-09T10:28:07.680135700+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") +@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-23T18:00:07.870607200+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") public class BookServiceBeanConfiguration { @Bean @@ -26,8 +26,8 @@ public OpenApiRepository bookServiceOpenApiRepository() { } @Bean(name="BookServiceSoapApi") - public BookServiceSoapApi bookServiceSoapApi(@Qualifier("bookstore.endpoint") Endpoint endpoint, @Autowired(required = false) List customizers) { - return new BookServiceSoapApi(endpoint, customizers); + public BookServiceSoapApi bookServiceSoapApi(@Autowired(required = false) @Qualifier("bookstore.endpoint") Endpoint defaultEndpoint, @Autowired(required = false) List customizers) { + return new BookServiceSoapApi(defaultEndpoint, customizers); } } diff --git a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/soap/bookservice/spring/BookServiceNamespaceHandler.java b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/soap/bookservice/spring/BookServiceNamespaceHandler.java index e561cfd699..610ef9a276 100644 --- a/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/soap/bookservice/spring/BookServiceNamespaceHandler.java +++ b/test-api-generator/citrus-test-api-generator-core/src/test/resources/org/citrusframework/openapi/generator/ExpectedCodeGenIT/expectedgen/soap/bookservice/spring/BookServiceNamespaceHandler.java @@ -10,7 +10,7 @@ import org.citrusframework.openapi.testapi.GeneratedApi; import org.springframework.beans.factory.xml.NamespaceHandlerSupport; -@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-09T10:28:07.680135700+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") +@jakarta.annotation.Generated(value = "org.citrusframework.openapi.generator.CitrusJavaCodegen", date = "2025-01-23T18:00:07.870607200+01:00[Europe/Zurich]", comments = "Generator version: 7.9.0") public class BookServiceNamespaceHandler extends NamespaceHandlerSupport { @Override 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 index 9d313cc84d..e864da5b46 100644 --- 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 @@ -55,7 +55,8 @@ class TestApiGeneratorMojoUnitTest extends AbstractMojoTestCase { @Mock private MojoExecution mojoExecutionMock; - +// TODO: test real world scenario with prefix=manageBooking and prefix=ManageBooking (should result in ManageBookingNamespaceHandler instead of manageBookingNamespaceHandler. Also check namespace name it contains managebooking - is that reasonable, also the api yaml cannot be loaded because of capital letters )? + // TODO: Account Number as OpenAPI Parameter Name is allowed but leads to error as the space needs to be url encoded. static Stream replaceDynamicVarsInPattern() { return Stream.of( arguments("%PREFIX%-aa-%VERSION%", "MyPrefix", "1", false, "MyPrefix-aa-1"), diff --git a/validation/citrus-validation-xml/src/main/java/org/citrusframework/util/XMLUtils.java b/validation/citrus-validation-xml/src/main/java/org/citrusframework/util/XMLUtils.java index ee01180dbd..0c2d9857ea 100644 --- a/validation/citrus-validation-xml/src/main/java/org/citrusframework/util/XMLUtils.java +++ b/validation/citrus-validation-xml/src/main/java/org/citrusframework/util/XMLUtils.java @@ -16,6 +16,8 @@ package org.citrusframework.util; +import static org.citrusframework.util.StringUtils.isEmpty; + import java.io.ByteArrayInputStream; import java.io.StringWriter; import java.io.UnsupportedEncodingException; @@ -63,7 +65,6 @@ private XMLUtils() { /** * Initializes XML utilities with custom configurer. - * @param xmlConfigurer */ public static void initialize(XmlConfigurer xmlConfigurer) { configurer = xmlConfigurer; @@ -71,7 +72,6 @@ public static void initialize(XmlConfigurer xmlConfigurer) { /** * Creates basic parser instance. - * @return */ public static LSParser createLSParser() { return configurer.createLSParser(); @@ -79,7 +79,6 @@ public static LSParser createLSParser() { /** * Creates basic serializer instance. - * @return */ public static LSSerializer createLSSerializer() { return configurer.createLSSerializer(); @@ -87,7 +86,6 @@ public static LSSerializer createLSSerializer() { /** * Creates LSInput from dom implementation. - * @return */ public static LSInput createLSInput() { return configurer.createLSInput(); @@ -95,7 +93,6 @@ public static LSInput createLSInput() { /** * Creates LSOutput from dom implementation. - * @return */ public static LSOutput createLSOutput() { return configurer.createLSOutput(); @@ -165,13 +162,14 @@ public static Node findNodeByName(Document doc, String pathExpression) { * @param element the root node to normalize. */ public static void stripWhitespaceNodes(Node element) { - Node node, child; + Node node; + Node child; for (child = element.getFirstChild(); child != null; child = node) { node = child.getNextSibling(); stripWhitespaceNodes(child); } - if (element.getNodeType() == Node.TEXT_NODE && element.getNodeValue().trim().length()==0) { + if (element.getNodeType() == Node.TEXT_NODE && element.getNodeValue().trim().isEmpty()) { element.getParentNode().removeChild(element); } } @@ -185,43 +183,42 @@ public static void stripWhitespaceNodes(Node element) { * @return the path expression representing the node in DOM tree. */ public static String getNodesPathName(Node node) { - final StringBuffer buffer = new StringBuffer(); + final StringBuilder builder = new StringBuilder(); if (node.getNodeType() == Node.ATTRIBUTE_NODE) { - buildNodeName(((Attr) node).getOwnerElement(), buffer); - buffer.append("."); - buffer.append(node.getLocalName()); + buildNodeName(((Attr) node).getOwnerElement(), builder); + builder.append("."); + builder.append(node.getLocalName()); } else { - buildNodeName(node, buffer); + buildNodeName(node, builder); } - return buffer.toString(); + return builder.toString(); } /** * Builds the node path expression for a node in the DOM tree. * @param node in a DOM tree. - * @param buffer string buffer. + * @param builder string builder. */ - private static void buildNodeName(Node node, StringBuffer buffer) { + private static void buildNodeName(Node node, StringBuilder builder) { if (node.getParentNode() == null) { return; } - buildNodeName(node.getParentNode(), buffer); + buildNodeName(node.getParentNode(), builder); if (node.getParentNode() != null && node.getParentNode().getParentNode() != null) { - buffer.append("."); + builder.append("."); } - buffer.append(node.getLocalName()); + builder.append(node.getLocalName()); } /** * Serializes a DOM document - * @param doc - * @throws CitrusRuntimeException + * * @return serialized XML string */ public static String serialize(Document doc) { @@ -240,12 +237,15 @@ public static String serialize(Document doc) { } /** - * Pretty prints a XML string. - * @param xml - * @throws CitrusRuntimeException + * Pretty prints an XML string. * @return pretty printed XML string */ public static String prettyPrint(String xml) { + + if (isEmpty(xml)) { + return xml; + } + LSParser parser = configurer.createLSParser(); configurer.setParserConfigParameter(parser, XmlConfigurer.VALIDATE_IF_SCHEMA, false); @@ -306,8 +306,6 @@ public static Map lookupNamespaces(Node referenceNode) { /** * Parse message payload with DOM implementation. - * @param messagePayload - * @throws CitrusRuntimeException * @return DOM document. */ public static Document parseMessagePayload(String messagePayload) { @@ -327,8 +325,6 @@ public static Document parseMessagePayload(String messagePayload) { /** * Try to find encoding for document node. Also supports Citrus default encoding set * as System property. - * @param doc - * @return */ public static Charset getTargetCharset(Document doc) { String defaultEncoding = System.getProperty(CitrusSettings.CITRUS_FILE_ENCODING_PROPERTY, @@ -397,8 +393,6 @@ private static Charset getTargetCharset(String messagePayload) throws Unsupporte /** * Removes leading XML declaration from xml if present. - * @param xml - * @return */ public static String omitXmlDeclaration(String xml) { if (xml.startsWith("")) { diff --git a/validation/citrus-validation-xml/src/main/java/org/citrusframework/validation/xml/DomXmlMessageValidator.java b/validation/citrus-validation-xml/src/main/java/org/citrusframework/validation/xml/DomXmlMessageValidator.java index 8609a53463..2ff3c013eb 100644 --- a/validation/citrus-validation-xml/src/main/java/org/citrusframework/validation/xml/DomXmlMessageValidator.java +++ b/validation/citrus-validation-xml/src/main/java/org/citrusframework/validation/xml/DomXmlMessageValidator.java @@ -78,7 +78,12 @@ public DomXmlMessageValidator(XmlSchemaValidation schemaValidator) { @Override public void validateMessage(Message receivedMessage, Message controlMessage, TestContext context, XmlMessageValidationContext validationContext) throws ValidationException { - logger.debug("Start XML message validation: {}", prettyPrint(receivedMessage.getPayload(String.class))); + + if (logger.isDebugEnabled()) { + logger.debug("Start XML message validation: {}", + prettyPrint(receivedMessage.getPayload(String.class))); + } + try { if (validationContext.isSchemaValidationEnabled()) { schemaValidator.validate(receivedMessage, context, validationContext); @@ -110,9 +115,6 @@ public void validateMessage(Message receivedMessage, Message controlMessage, * Validate namespaces in message. The method compares namespace declarations in the root * element of the received message to expected namespaces. Prefixes are important too, so * differing namespace prefixes will fail the validation. - * - * @param expectedNamespaces - * @param receivedMessage */ protected void validateNamespaces(Map expectedNamespaces, Message receivedMessage) { if (expectedNamespaces == null || expectedNamespaces.isEmpty()) { @@ -188,10 +190,6 @@ private void doElementNamespaceValidation(Node received, Node source) { /** * Validate message payloads by comparing to a control message. - * - * @param receivedMessage - * @param validationContext - * @param context */ protected void validateMessageContent(Message receivedMessage, Message controlMessage, XmlMessageValidationContext validationContext, TestContext context) { @@ -232,10 +230,6 @@ protected void validateMessageContent(Message receivedMessage, Message controlMe /** * Validates XML header fragment data. - * @param receivedHeaderData - * @param controlHeaderData - * @param validationContext - * @param context */ private void validateXmlHeaderFragment(String receivedHeaderData, String controlHeaderData, XmlMessageValidationContext validationContext, TestContext context) { @@ -257,10 +251,6 @@ private void validateXmlHeaderFragment(String receivedHeaderData, String control /** * Walk the XML tree and validate all nodes. - * - * @param received - * @param source - * @param validationContext */ private void validateXmlTree(Node received, Node source, XmlMessageValidationContext validationContext, NamespaceContext namespaceContext, TestContext context) { @@ -289,10 +279,6 @@ private void validateXmlTree(Node received, Node source, /** * Handle document type definition with validation of publicId and systemId. - * @param received - * @param source - * @param validationContext - * @param namespaceContext */ private void doDocumentTypeDefinition(Node received, Node source, XmlMessageValidationContext validationContext, @@ -336,10 +322,6 @@ private void doDocumentTypeDefinition(Node received, Node source, /** * Handle element node. - * - * @param received - * @param source - * @param validationContext */ private void doElement(Node received, Node source, XmlMessageValidationContext validationContext, NamespaceContext namespaceContext, TestContext context) { @@ -397,9 +379,6 @@ private void doElement(Node received, Node source, /** * Handle text node during validation. - * - * @param received - * @param source */ private void doText(Element received, Element source) { logger.debug("Validating node value for element: {}", received.getLocalName()); @@ -412,16 +391,13 @@ private void doText(Element received, Element source) { + received.getLocalName() + "'", sourceText.trim(), receivedText.trim())); } - logger.debug("Node value '{}': OK", receivedText.trim()); + if (logger.isDebugEnabled()) { + logger.debug("Node value '{}': OK", receivedText.trim()); + } } /** * Handle attribute node during validation. - * - * @param receivedElement - * @param receivedAttribute - * @param sourceElement - * @param validationContext */ private void doAttribute(Node receivedElement, Node receivedAttribute, Node sourceElement, XmlMessageValidationContext validationContext, NamespaceContext namespaceContext, TestContext context) { @@ -466,10 +442,6 @@ private void doAttribute(Node receivedElement, Node receivedAttribute, Node sour /** * Perform validation on namespace qualified attribute values if present. This includes the validation of namespace presence * and equality. - * @param receivedElement - * @param receivedAttribute - * @param sourceElement - * @param sourceAttribute */ private void doNamespaceQualifiedAttributeValidation(Node receivedElement, Node receivedAttribute, Node sourceElement, Node sourceAttribute) { String receivedValue = receivedAttribute.getNodeValue(); @@ -511,8 +483,6 @@ private void doNamespaceQualifiedAttributeValidation(Node receivedElement, Node /** * Handle processing instruction during validation. - * - * @param received */ private void doPI(Node received) { logger.debug("Ignored processing instruction ({}={})", received.getLocalName(), received.getNodeValue()); @@ -537,7 +507,6 @@ private int countAttributes(NamedNodeMap attributesR) { /** * Checks whether the given node contains a validation matcher - * @param node * @return true if node value contains validation matcher, false if not */ private boolean isValidationMatcherExpression(Node node) { @@ -565,8 +534,6 @@ public boolean supportsMessageType(String messageType, Message message) { /** * Get explicit namespace context builder set on this class or obtain instance from reference resolver. - * @param context - * @return */ private NamespaceContextBuilder getNamespaceContextBuilder(TestContext context) { if (namespaceContextBuilder != null) { @@ -578,7 +545,6 @@ private NamespaceContextBuilder getNamespaceContextBuilder(TestContext context) /** * Sets the namespace context builder. - * @param namespaceContextBuilder */ public void setNamespaceContextBuilder(NamespaceContextBuilder namespaceContextBuilder) { this.namespaceContextBuilder = namespaceContextBuilder; diff --git a/validation/citrus-validation-xml/src/main/java/org/citrusframework/validation/xml/schema/XmlSchemaValidation.java b/validation/citrus-validation-xml/src/main/java/org/citrusframework/validation/xml/schema/XmlSchemaValidation.java index 26074b3308..d0182d4de2 100644 --- a/validation/citrus-validation-xml/src/main/java/org/citrusframework/validation/xml/schema/XmlSchemaValidation.java +++ b/validation/citrus-validation-xml/src/main/java/org/citrusframework/validation/xml/schema/XmlSchemaValidation.java @@ -55,6 +55,7 @@ import static java.lang.String.format; import static org.citrusframework.validation.xml.schema.ValidationStrategy.FAIL; +import static org.citrusframework.xml.schema.AbstractSchemaCollection.W3C_XML_SCHEMA_NS_URI; public class XmlSchemaValidation implements SchemaValidator { @@ -87,10 +88,6 @@ public XmlSchemaValidation(ValidationStrategy noSchemaFoundStrategy) { /** * Validate message with an XML schema. - * - * @param message - * @param context - * @param validationContext */ @Override public void validate(Message message, TestContext context, XmlMessageValidationContext validationContext) { @@ -162,14 +159,18 @@ private void validateSchema(Message message, TestContext context, XmlMessageVali .stream() .map(AbstractSchemaCollection::toSpringResource) .toList() - .toArray(new org.springframework.core.io.Resource[]{}), WsdlXsdSchema.W3C_XML_SCHEMA_NS_URI); + .toArray(new org.springframework.core.io.Resource[]{}), W3C_XML_SCHEMA_NS_URI); } SAXParseException[] results = validator.validate(new DOMSource(doc)); if (results.length == 0) { logger.debug("XML schema validation successful: All values OK"); } else { - logger.error("XML schema validation failed for message:\n{}", XMLUtils.prettyPrint(message.getPayload(String.class))); + + if (logger.isErrorEnabled()) { + logger.error("XML schema validation failed for message:\n{}", + XMLUtils.prettyPrint(message.getPayload(String.class))); + } // Report all parsing errors logger.debug("Found {} schema validation errors", results.length); @@ -178,7 +179,10 @@ private void validateSchema(Message message, TestContext context, XmlMessageVali errors.append(e.toString()); errors.append("\n"); } - logger.debug(errors.toString()); + + if (logger.isDebugEnabled()) { + logger.debug(errors.toString()); + } throw new ValidationException("XML schema validation failed:", results[0]); } @@ -188,9 +192,6 @@ private void validateSchema(Message message, TestContext context, XmlMessageVali } /** - * - * @param messageType - * @param message * @return true if the message or message type is supported by this validator */ @Override @@ -233,7 +234,6 @@ public boolean canValidate(Message message, boolean schemaValidationEnabled) { /** * Get setting to determine if xml schema validation is enabled by default. - * @return */ private static boolean isXmlSchemaValidationEnabled() { return Boolean.getBoolean(CitrusSettings.OUTBOUND_SCHEMA_VALIDATION_ENABLED_PROPERTY)