Skip to content

Commit

Permalink
Add new argument to explicetly set if response generation should acco…
Browse files Browse the repository at this point in the history
…unt for anyOf/oneOf
  • Loading branch information
en-milie committed Apr 3, 2024
1 parent 98d9bd8 commit ac1ee06
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 21 deletions.
4 changes: 4 additions & 0 deletions src/main/java/com/endava/cats/args/ProcessingArguments.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ public class ProcessingArguments {
description = "Max number of anyOf/oneOf combinations. By default considers all possible anyOf/oneOf combinations. Default: @|bold,underline ${DEFAULT-VALUE}|@")
private int limitXxxOfCombinations = 10;

@CommandLine.Option(names = {"--generateAllXxxCombinationsForResponses"},
description = "Max number of anyOf/oneOf combinations. By default considers all possible anyOf/oneOf combinations. Default: @|bold,underline ${DEFAULT-VALUE}|@")
private boolean generateAllXxxCombinationsForResponses = false;

/**
* Represents a wildcard pattern for JSON content type with optional parameters.
*/
Expand Down
48 changes: 29 additions & 19 deletions src/main/java/com/endava/cats/factory/FuzzingDataFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;

import java.util.*;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -410,7 +411,7 @@ private MediaType getMediaType(Operation operation, OpenAPI openAPI) {

private List<String> getRequestPayloadsSamples(MediaType mediaType, String reqSchemaName) {
OpenAPIModelGenerator generator = new OpenAPIModelGenerator(globalContext, validDataFormat, processingArguments.isUseExamples(), processingArguments.getSelfReferenceDepth());
List<String> result = this.generateSample(reqSchemaName, generator);
List<String> result = this.generateSample(reqSchemaName, generator, true);

if (mediaType != null && CatsModelUtils.isArraySchema(mediaType.getSchema())) {
/*when dealing with ArraySchemas we make sure we have 2 elements in the array*/
Expand All @@ -419,7 +420,7 @@ private List<String> getRequestPayloadsSamples(MediaType mediaType, String reqSc
return result;
}

private List<String> generateSample(String reqSchemaName, OpenAPIModelGenerator generator) {
private List<String> generateSample(String reqSchemaName, OpenAPIModelGenerator generator, boolean createXxxOfCombinations) {
long t0 = System.currentTimeMillis();
logger.debug("Starting to generate example for schema name {}", reqSchemaName);
Map<String, String> examples = generator.generate(reqSchemaName);
Expand All @@ -431,7 +432,11 @@ private List<String> generateSample(String reqSchemaName, OpenAPIModelGenerator
String payloadSample = examples.get("example");

payloadSample = this.squashAllOfElements(payloadSample);
List<String> payloadCombinationsBasedOnOneOfAndAnyOf = this.getPayloadCombinationsBasedOnOneOfAndAnyOf(payloadSample);
List<String> payloadCombinationsBasedOnOneOfAndAnyOf = List.of(payloadSample);

if (createXxxOfCombinations) {
payloadCombinationsBasedOnOneOfAndAnyOf = this.getPayloadCombinationsBasedOnOneOfAndAnyOf(payloadSample);
}

if (processingArguments.getLimitXxxOfCombinations() > 0) {
int maxCombinations = Math.min(processingArguments.getLimitXxxOfCombinations(), payloadCombinationsBasedOnOneOfAndAnyOf.size());
Expand Down Expand Up @@ -633,21 +638,7 @@ private String squashAllOfElements(String payloadSample) {
private JsonElement squashAllOf(JsonElement element) {
if (element.isJsonObject()) {
JsonObject originalObject = element.getAsJsonObject();
JsonObject newObject = new JsonObject();
for (String key : originalObject.keySet()) {
if (key.equalsIgnoreCase("ALL_OF") || key.endsWith("ALL_OF#null")) {
JsonElement jsonElement = originalObject.get(key);
if (jsonElement.isJsonObject()) {
JsonObject allOfObject = originalObject.getAsJsonObject(key);
mergeJsonObject(newObject, squashAllOf(allOfObject));
} else {
newObject.add(key.substring(0, key.indexOf("ALL_OF")), squashAllOf(jsonElement));
}
} else if (!key.contains("ALL_OF")) {
newObject.add(key, squashAllOf(originalObject.get(key)));
}
}
return newObject;
return buildNewObject(originalObject);
} else if (element.isJsonArray()) {
JsonArray originalArray = element.getAsJsonArray();
JsonArray newArray = new JsonArray();
Expand All @@ -660,6 +651,25 @@ private JsonElement squashAllOf(JsonElement element) {
}
}

@NotNull
private JsonObject buildNewObject(JsonObject originalObject) {
JsonObject newObject = new JsonObject();
for (String key : originalObject.keySet()) {
if (key.equalsIgnoreCase("ALL_OF") || key.endsWith("ALL_OF#null")) {
JsonElement jsonElement = originalObject.get(key);
if (jsonElement.isJsonObject()) {
JsonObject allOfObject = originalObject.getAsJsonObject(key);
mergeJsonObject(newObject, squashAllOf(allOfObject));
} else {
newObject.add(key.substring(0, key.indexOf("ALL_OF")), squashAllOf(jsonElement));
}
} else if (!key.contains("ALL_OF")) {
newObject.add(key, squashAllOf(originalObject.get(key)));
}
}
return newObject;
}

private void mergeJsonObject(JsonObject original, JsonElement toMerge) {
for (Map.Entry<String, JsonElement> entry : toMerge.getAsJsonObject().entrySet()) {
original.add(entry.getKey(), entry.getValue());
Expand Down Expand Up @@ -728,7 +738,7 @@ private Map<String, List<String>> getResponsePayloads(Operation operation) {
String responseSchemaRef = this.extractResponseSchemaRef(operation, responseCode);
if (responseSchemaRef != null) {
String respSchemaName = this.getSchemaName(responseSchemaRef);
List<String> samples = this.generateSample(respSchemaName, generator);
List<String> samples = this.generateSample(respSchemaName, generator, processingArguments.isGenerateAllXxxCombinationsForResponses());

responses.put(responseCode, samples);
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/endava/cats/json/JsonUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ public static boolean isCyclicReference(String currentProperty, int depth) {
for (int i = 0; i < properties.length - 1; i++) {
for (int j = i + 1; j <= properties.length - 1; j++) {
if (properties[i].equalsIgnoreCase(properties[j])) {
LOGGER.trace("Found cyclic dependencies for {}", currentProperty);
LOGGER.trace("Found cyclic reference for {}", currentProperty);
return true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ public Map<String, String> generate(String modelName) {
}

private <T> Object resolvePropertyToExample(String propertyName, Schema<T> propertySchema) {
if (JsonUtils.isCyclicSchemaReference(currentProperty, schemaRefMap, selfReferenceDepth)) {
return null;
}

Object enumOrDefault = this.getEnumOrDefault(propertySchema);

if (enumOrDefault != null) {
Expand Down Expand Up @@ -457,7 +461,7 @@ private void populateWithComposedSchema(Map<String, Object> values, String prope

boolean innerAllOff = values.values()
.stream()
.filter(key -> key instanceof Map)
.filter(Map.class::isInstance)
.count() == values.size();

for (Map.Entry<String, Object> entry : values.entrySet()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ void setup() {
filterArguments = Mockito.mock(FilterArguments.class);
Mockito.when(processingArguments.isUseExamples()).thenReturn(true);
Mockito.when(processingArguments.getLimitXxxOfCombinations()).thenReturn(10);
Mockito.when(processingArguments.isGenerateAllXxxCombinationsForResponses()).thenReturn(true);
Mockito.when(processingArguments.getContentType()).thenReturn(List.of(ProcessingArguments.JSON_WILDCARD, "application/x-www-form-urlencoded"));
fuzzingDataFactory = new FuzzingDataFactory(filesArguments, processingArguments, catsGlobalContext, validDataFormat, filterArguments);
}
Expand Down

0 comments on commit ac1ee06

Please sign in to comment.