ca.gc.aafc.dina.testsupport.specs.OpenAPI3Assertions Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dina-test-support Show documentation
Show all versions of dina-test-support Show documentation
Base DINA API test support package
package ca.gc.aafc.dina.testsupport.specs;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.openapi4j.core.exception.EncodeException;
import org.openapi4j.core.exception.ResolutionException;
import org.openapi4j.core.model.reference.Reference;
import org.openapi4j.core.model.v3.OAI3;
import org.openapi4j.core.model.v3.OAI3SchemaKeywords;
import org.openapi4j.core.validation.ValidationException;
import org.openapi4j.parser.OpenApi3Parser;
import org.openapi4j.parser.model.v3.OpenApi3;
import org.openapi4j.parser.model.v3.Schema;
import org.openapi4j.parser.validation.v3.OpenApi3Validator;
import org.openapi4j.schema.validator.ValidationContext;
import org.openapi4j.schema.validator.ValidationData;
import org.openapi4j.schema.validator.v3.SchemaValidator;
import org.springframework.http.HttpMethod;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.fail;
/**
* Collections of utility test methods related to OpenAPI 3 specifications and schemas.
*/
@Log4j2
public final class OpenAPI3Assertions {
public static final String SKIP_REMOTE_SCHEMA_VALIDATION_PROPERTY = "testing.skip-remote-schema-validation";
private static final ObjectMapper MAPPER = new ObjectMapper();
private OpenAPI3Assertions() {
}
/**
* Same as {@link #assertSchema(URL, String, String, ValidationRestrictionOptions)} but the assertion can be skipped by setting
* the System property {@link #SKIP_REMOTE_SCHEMA_VALIDATION_PROPERTY} to true. Strict mode will be enabled,
* see {@link #assertRemoteSchema(URL, String, String, ValidationRestrictionOptions)}
*
* @param specsUrl location of the spec
* @param schemaName schema name
* @param apiResponse the api response to assert
*/
public static void assertRemoteSchema(URL specsUrl, String schemaName, String apiResponse) {
assertRemoteSchema(specsUrl, schemaName, apiResponse, ValidationRestrictionOptions.FULL_RESTRICTIONS);
}
/**
* Same as {@link #assertSchema(OpenApi3, String, String, ValidationRestrictionOptions)} but the assertion can be skipped by setting
* the System property {@link #SKIP_REMOTE_SCHEMA_VALIDATION_PROPERTY} to true.
*
* @param specsUrl location of the spec
* @param schemaName schema name
* @param apiResponse the api response to assert
* @param options validation restriction options to specify allowable missing fields and if additional
* fields are allowed.
*/
public static void assertRemoteSchema(URL specsUrl, String schemaName, String apiResponse, ValidationRestrictionOptions options) {
if (!Boolean.valueOf(System.getProperty(SKIP_REMOTE_SCHEMA_VALIDATION_PROPERTY))) {
assertSchema(specsUrl, schemaName, apiResponse, options);
} else {
log.warn("Skipping schema validation." + "System property testing.skip-remote-schema-validation set to true.");
}
}
/**
* Assert an API response against an OpenAPI 3 Specification located at specsUrl with strict mode on. See
* {@link #assertSchema(OpenApi3, String, String, ValidationRestrictionOptions)}
*
* @param specsUrl location of the spec
* @param schemaName schema name
* @param apiResponse the api response to assert
*/
public static void assertSchema(URL specsUrl, String schemaName, String apiResponse) {
assertSchema(specsUrl, schemaName, apiResponse, ValidationRestrictionOptions.FULL_RESTRICTIONS);
}
/**
* Assert an API response against an OpenAPI 3 Specification located at specsUrl.
*
* @param specsUrl location of the spec
* @param schemaName schema name
* @param apiResponse the api response to assert
* @param options validation restriction options to specify allowable missing fields and if additional
* fields are allowed.
*/
public static void assertSchema(URL specsUrl, String schemaName, String apiResponse, ValidationRestrictionOptions options) {
Objects.requireNonNull(specsUrl, "specsUrl shall be provided");
Objects.requireNonNull(schemaName, "schemaName shall be provided");
Objects.requireNonNull(apiResponse, "apiResponse shall be provided");
OpenApi3 openApi3 = innerParseAndValidateOpenAPI3Specs(specsUrl) ;
assertSchema(openApi3, schemaName, apiResponse, options);
}
/**
* Assert an API response against the provided OpenAPI 3 Specification with strict mode on. See {@link
* #assertSchema(URL, String, String, ValidationRestrictionOptions)}
*
* @param openApi provided specification
* @param schemaName schema name
* @param apiResponse the api response to assert
*/
public static void assertSchema(OpenApi3 openApi, String schemaName, String apiResponse) {
assertSchema(openApi, schemaName, apiResponse, ValidationRestrictionOptions.FULL_RESTRICTIONS);
}
/**
* Assert an API response against the provided OpenAPI 3 Specification.
*
* @param openApi provided specification
* @param schemaName schema name
* @param apiResponse the api response to assert
* @param options strict mode will make all the fields from the schema required to make sure a complete
* api response is valid.
*/
public static void assertSchema(OpenApi3 openApi, String schemaName, String apiResponse, ValidationRestrictionOptions options) {
SchemaValidator schemaValidator;
try {
ValidationContext context = new ValidationContext<>(openApi.getContext());
context.addValidator(OAI3SchemaKeywords.PROPERTIES,
(context1, schemaNode, schemaParentNode, parentSchema) ->
new RestrictiveFieldValidator(context1, schemaNode, schemaParentNode, parentSchema, options));
JsonNode schemaNode = loadSchemaAsJsonNode(openApi, schemaName);
if (schemaNode == null ) {
fail("can't find schema " + schemaName);
}
schemaValidator = new SchemaValidator(context, null, schemaNode);
} catch (EncodeException rEx) {
fail(rEx);
return;
}
JsonNode apiResponseNode;
try {
apiResponseNode = MAPPER.readTree(apiResponse);
} catch (IOException ioEx) {
fail(ioEx);
return;
}
ValidationData> validationData = new ValidationData<>();
schemaValidator.validate(apiResponseNode, validationData);
if (!validationData.isValid()) {
fail(validationData.results().toString());
}
}
/**
* Load a schema inside an OpenApi3 object.
*
* @param openApi
* @param schemaName
* @return the schema as {@link JsonNode}
* @throws EncodeException
*/
private static JsonNode loadSchemaAsJsonNode(OpenApi3 openApi, String schemaName)
throws EncodeException {
// try to locate the schema in the main OpenAPI3 file
if (openApi.getComponents() != null) {
Schema schema = openApi.getComponents().getSchema(schemaName);
if (schema != null) {
return schema.toNode();
}
}
// then, try to reach it be refs from the paths
Set filenamesFromPathRefs = getFilenamesFromPathRefs(openApi);
return loadFirstFoundSchema(openApi, filenamesFromPathRefs, schemaName);
}
/**
* Extract a set of filenames from the list of Paths declared in the OpenApi3 file
* @param openApi
* @return set of paths or empty set if no paths
*/
private static Set getFilenamesFromPathRefs(OpenApi3 openApi) {
if (openApi.getPaths() == null) {
return Collections.emptySet();
}
return openApi.getPaths().values().stream()
.map(path -> StringUtils.substringBefore(path.getRef(), "#"))
.collect(Collectors.toSet());
}
/**
* From a set of filenames, return the first content that can be located.
* @param openApi
* @param filenames
* @param schemaName
* @return content as JsonNode or null if not found.
*/
private static JsonNode loadFirstFoundSchema(OpenApi3 openApi, Set filenames,
String schemaName) {
Reference ref;
for (String filename : filenames) {
ref = openApi.getContext().getReferenceRegistry()
.getRef(filename + "#/components/schemas/" + schemaName);
if (ref != null) {
return ref.getContent();
}
}
return null;
}
/**
* Parse and validate the OpenAPI 3 specifications at the provided URL.
*
* @param specsURL
* @return the OpenApi3 as {@link OpenApi3}
* @throws ValidationException
* @throws ResolutionException
*/
public static OpenApi3 parseAndValidateOpenAPI3Specs(URL specsURL) throws ResolutionException, ValidationException {
OpenApi3 api = new OpenApi3Parser().parse(specsURL, new ArrayList<>(), false);
OpenApi3Validator.instance().validate(api);
return api;
}
/**
* Checking the given path is existing in Open API 3 spec
* and the path contains the provided http method
*
* @param specsUrl Specs URL to check endpoints against
* @param path path of endpoint
* @param method method at the endpoint path
*/
public static void assertEndpoint(URL specsUrl, String path, HttpMethod method) {
OpenApi3 openApi3 = innerParseAndValidateOpenAPI3Specs(specsUrl);
for (String key : openApi3.getPaths().keySet()) {
if (key.equals(path)
&& openApi3.getPaths().get(key).getOperation(method.name().toLowerCase()) != null) {
return;
}
}
fail("Failed find " + method.name() + " " + path + " in OpenAPI 3 specs");
}
private static OpenApi3 innerParseAndValidateOpenAPI3Specs(URL specsUrl) {
try {
return parseAndValidateOpenAPI3Specs(specsUrl);
} catch (ResolutionException | ValidationException ex) {
fail("Failed to parse and validate the provided schema", ex);
}
return null;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy