Skip to content

Commit

Permalink
Proper fix for #85 - arrays were not always correctly replaced
Browse files Browse the repository at this point in the history
  • Loading branch information
en-milie committed Nov 17, 2023
1 parent 56626e6 commit c4d4f2b
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 11 deletions.
17 changes: 10 additions & 7 deletions src/main/java/com/endava/cats/fuzzer/special/CustomFuzzerUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import io.github.ludovicianul.prettylogger.PrettyLoggerFactory;
import jakarta.enterprise.context.ApplicationScoped;
import lombok.Getter;
import net.minidev.json.JSONArray;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
Expand Down Expand Up @@ -268,7 +269,7 @@ public void executeTestCases(FuzzingData data, String key, Object value, CustomF

if (this.entryIsValid((Map<String, Object>) value) && isValidOneOf) {
this.pathsWithInputVariables.put(data.getPath(), (Map<String, Object>) value);
List<Map<String, Object>> individualTestCases = this.createIndividualRequest((Map<String, Object>) value);
List<Map<String, Object>> individualTestCases = this.createIndividualRequest((Map<String, Object>) value, data.getPayload());
for (Map<String, Object> testCase : individualTestCases) {
testCaseListener.createAndExecuteTest(log, fuzzer, () -> this.process(data, key, testCase));
}
Expand Down Expand Up @@ -311,18 +312,20 @@ private boolean entryIsValid(Map<String, Object> currentPathValues) {
* @param testCase object from the custom fuzzer file
* @return individual requests
*/
public List<Map<String, Object>> createIndividualRequest(Map<String, Object> testCase) {
public List<Map<String, Object>> createIndividualRequest(Map<String, Object> testCase, String payload) {
Optional<Map.Entry<String, Object>> listOfValuesOptional = testCase.entrySet().stream().filter(entry -> entry.getValue() instanceof List).findFirst();
List<Map<String, Object>> allValues = new ArrayList<>();

if (listOfValuesOptional.isPresent()) {
Map.Entry<String, Object> listOfValues = listOfValuesOptional.get();
for (Object value : (List<?>) listOfValues.getValue()) {
testCase.put(listOfValues.getKey(), value);
allValues.add(testCase.entrySet()
.stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
if (!(JsonUtils.getVariableFromJson(payload, listOfValues.getKey()) instanceof JSONArray)) {
for (Object value : (List<?>) listOfValues.getValue()) {
testCase.put(listOfValues.getKey(), value);
allValues.add(testCase.entrySet()
.stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
}
return List.copyOf(allValues);
}
return List.copyOf(allValues);
}

return Collections.singletonList(testCase.entrySet()
Expand Down
18 changes: 17 additions & 1 deletion src/main/java/com/endava/cats/util/CatsUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public FuzzingResult replaceField(String payload, String jsonPropertyForReplacem
}

private static void replaceOldValueWithNewOne(String jsonPropertyForReplacement, DocumentContext jsonDocument, Object valueToSet) {
if (JsonUtils.isValidJson(String.valueOf(valueToSet))) {
if (JsonUtils.isValidJson(elementToString(valueToSet))) {
if (areBothPropertyToReplaceAndValueToReplaceArrays(jsonPropertyForReplacement, valueToSet)) {
jsonPropertyForReplacement = removeArrayTermination(jsonPropertyForReplacement);
}
Expand Down Expand Up @@ -168,4 +168,20 @@ private void setMapValues(DocumentContext jsonDoc, String additionalProperties,
jsonDoc.put(JsonPath.compile(prefix), entry[0].trim(), CatsDSLParser.parseAndGetResult(entry[1].trim(), Map.of(Parser.REQUEST, jsonDoc.jsonString())));
}
}

private static String elementToString(Object obj) {
if (obj instanceof List<?> stringList) {
return "[" + stringList.stream()
.map(element -> {
if (element instanceof Number) {
return String.valueOf(element);
} else {
return "\"" + element + "\"";
}
})
.collect(Collectors.joining(", ")) + "]";
}

return String.valueOf(obj);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ void setup() {
serviceCaller = Mockito.mock(ServiceCaller.class);
filesArguments = new FilesArguments();
customFuzzerUtil = new CustomFuzzerUtil(serviceCaller, catsUtil, testCaseListener);
functionalFuzzer = new FunctionalFuzzer(filesArguments, customFuzzerUtil);
functionalFuzzer = new FunctionalFuzzer(filesArguments, customFuzzerUtil, Mockito.mock(TestCaseListener.class));
ReflectionTestUtils.setField(testCaseListener, "testCaseExporter", Mockito.mock(TestCaseExporter.class));
}

Expand Down Expand Up @@ -140,7 +140,7 @@ private FuzzingData setupFuzzingData(CatsResponse catsResponse, String... custom
ReflectionTestUtils.setField(filesArguments, "customFuzzerDetails", createCustomFuzzerFile(customFieldValues));
Mockito.when(serviceCaller.call(Mockito.any())).thenReturn(catsResponse);
customFuzzerUtil = new CustomFuzzerUtil(serviceCaller, mockCatsUtil, testCaseListener);
functionalFuzzer = new FunctionalFuzzer(filesArguments, customFuzzerUtil);
functionalFuzzer = new FunctionalFuzzer(filesArguments, customFuzzerUtil, Mockito.mock(TestCaseListener.class));
ReflectionTestUtils.setField(filesArguments, "customFuzzerFile", new File("custom"));

return data;
Expand Down Expand Up @@ -231,6 +231,17 @@ void givenACustomFuzzerFileWithVerifyParameters_whenTheFuzzerRuns_thenVerifyPara
Mockito.verify(testCaseListener, Mockito.times(3)).reportResultInfo(Mockito.any(), Mockito.any(), Mockito.eq("Response matches all 'verify' parameters"), Mockito.any());
}

@ParameterizedTest
@CsvSource({"src/test/resources/functionalFuzzer-array-replace.yml,1","src/test/resources/functionalFuzzer-array-iterate.yml,2"})
void shouldReplaceArraysProperly(String file, int times) throws Exception {
FuzzingData data = setContext(file, "[]");
FunctionalFuzzer spyFunctionalFuzzer = Mockito.spy(functionalFuzzer);
filesArguments.loadCustomFuzzerFile();
spyFunctionalFuzzer.fuzz(data);
spyFunctionalFuzzer.executeCustomFuzzerTests();
Mockito.verify(testCaseListener, Mockito.times(times)).reportResultInfo(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
}

@Test
void shouldVerifyWithRequestVariables() throws Exception {
FuzzingData data = setContext("src/test/resources/functionalFuzzer-req-verify.yml", "{\"name\": {\"first\": \"Cats\"}, \"id\": \"25\", \"code\":\"150\",\"petName\":\"myPet\"}");
Expand Down Expand Up @@ -289,7 +300,7 @@ private FuzzingData setContext(String fuzzerFile, String responsePayload) throws
responses.put("200", Collections.singletonList("response"));
CatsResponse catsResponse = CatsResponse.from(200, responsePayload, "POST", 2);

FuzzingData data = FuzzingData.builder().path("/pets/{id}/move").payload("{\"pet\":\"oldValue\", \"name\":\"dodo\"}").
FuzzingData data = FuzzingData.builder().path("/pets/{id}/move").payload("{\"pet\":\"oldValue\", \"name\":\"dodo\",\"key_ops\":[\"1\",\"2\"]}").
responses(responses).responseCodes(Collections.singleton("200")).reqSchema(new StringSchema()).method(HttpMethod.POST)
.headers(new HashSet<>())
.requestContentTypes(List.of("application/json")).build();
Expand Down
8 changes: 8 additions & 0 deletions src/test/resources/functionalFuzzer-array-iterate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/pets/{id}/move:
test_1:
description: Create a new Key
pet: [ "dog","cat" ]
httpMethod: POST
expectedResponseCode: 2XX
verify:
$.size(): 0
8 changes: 8 additions & 0 deletions src/test/resources/functionalFuzzer-array-replace.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/pets/{id}/move:
test_1:
description: Create a new Key
key_ops: [ "sign","verify" ]
httpMethod: POST
expectedResponseCode: 2XX
verify:
$.size(): 0

0 comments on commit c4d4f2b

Please sign in to comment.