Skip to content

Commit

Permalink
feat: Add new linter to check to check relevant response codes have r…
Browse files Browse the repository at this point in the history
…esponse bodies
  • Loading branch information
en-milie committed May 30, 2024
1 parent 1f936a9 commit daeed6b
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public class ProcessingArguments {
description = "Generate anyOf/oneOf combinations also for response schemas. By default it creates one response payload with all possibilities. Default: @|bold,underline ${DEFAULT-VALUE}|@")
private boolean generateAllXxxCombinationsForResponses;

@Setter
@CommandLine.Option(names = {"--filterXxxFromRequestPayloads"}, negatable = true, defaultValue = "true", fallbackValue = "true",
description = "In extremely rare cases when CATS fails to generate anyOf/oneOf combinations some requests may still contain ONE_OF/ANY_OF markers. They are filtered out by default. " +
"Setting this to false will send them as requests which will probably fail. It's mostly for debug purposes. Default: @|bold,underline ${DEFAULT-VALUE}|@")
Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/endava/cats/command/LintCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public void run() {
catsCommand.filterArguments.customFilter("Linter");
catsCommand.filterArguments.getSkipFuzzers().addAll(Optional.ofNullable(skipFuzzers).orElse(Collections.emptyList()));
catsCommand.filterArguments.getCheckArguments().setIncludeContract(true);
catsCommand.processingArguments.setFilterXxxFromRequestPayloads(false);
catsCommand.run();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,4 @@ protected String runKey(FuzzingData data) {
public String description() {
return "verifies that the current path contains all recommended HTTP response codes for all operations";
}

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.endava.cats.fuzzer.contract;

import com.endava.cats.annotations.LinterFuzzer;
import com.endava.cats.http.HttpMethod;
import com.endava.cats.model.FuzzingData;
import com.endava.cats.report.TestCaseListener;
import io.github.ludovicianul.prettylogger.PrettyLogger;
import io.github.ludovicianul.prettylogger.PrettyLoggerFactory;
import jakarta.inject.Singleton;

import java.util.List;
import java.util.Map;

/**
* Checks if the http response codes have a response body.
*/
@Singleton
@LinterFuzzer
public class ResponsesWithBodiesLinterFuzzer extends BaseLinterFuzzer {
private final PrettyLogger log = PrettyLoggerFactory.getLogger(this.getClass());

/**
* Creates a new ResponsesWithBodiesLinterFuzzer instance.
*
* @param tcl the test case listener
*/
public ResponsesWithBodiesLinterFuzzer(TestCaseListener tcl) {
super(tcl);
}

@Override
public void process(FuzzingData data) {
testCaseListener.addScenario(log, "Check if all http response codes for HTTP method {} have a response body", data.getMethod());
testCaseListener.addExpectedResult(log, "All HTTP response codes (except for 204 and 304) have a response body");

List<String> httpResponseCodesMissingBody = data.getResponses()
.entrySet()
.stream()
.filter(entry -> entry.getValue().isEmpty())
.map(Map.Entry::getKey)
.toList();

if (httpResponseCodesMissingBody.isEmpty()) {
testCaseListener.reportResultInfo(log, data, "All HTTP response codes have a response body");
} else {
testCaseListener.reportResultError(log, data, "Missing response body for some HTTP response codes", "The following HTTP response codes are missing a response body: {}", httpResponseCodesMissingBody);
}
}

@Override
public List<HttpMethod> skipForHttpMethods() {
return List.of(HttpMethod.HEAD);
}

@Override
protected String runKey(FuzzingData data) {
return data.getPath() + data.getMethod();
}

@Override
public String description() {
return "verifies that HTTP response codes (except for 204 and 304) have a response body";
}
}
4 changes: 2 additions & 2 deletions src/test/java/com/endava/cats/args/FilterArgumentsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ void shouldIncludeAllFuzzers() {
List<String> fuzzers = filterArguments.getFirstPhaseFuzzersForPath();

Assertions.assertThat(fuzzers).contains("LeadingControlCharsInHeadersFuzzer", "LeadingWhitespacesInHeadersFuzzer", "LeadingMultiCodePointEmojisInFieldsTrimValidateFuzzer"
, "RemoveFieldsFuzzer", "CheckSecurityHeadersFuzzer").hasSize(138);
, "RemoveFieldsFuzzer", "CheckSecurityHeadersFuzzer").hasSize(139);
}

@Test
Expand Down Expand Up @@ -144,7 +144,7 @@ void shouldReturnGetAndDeleteWhenNotHttpMethodSupplied() {

@Test
void shouldReturnAllRegisteredFuzzers() {
Assertions.assertThat(filterArguments.getAllRegisteredFuzzers()).hasSize(143);
Assertions.assertThat(filterArguments.getAllRegisteredFuzzers()).hasSize(144);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.endava.cats.fuzzer.contract;

import com.endava.cats.args.IgnoreArguments;
import com.endava.cats.args.ReportingArguments;
import com.endava.cats.context.CatsGlobalContext;
import com.endava.cats.model.FuzzingData;
import com.endava.cats.report.ExecutionStatisticsListener;
import com.endava.cats.report.TestCaseExporter;
import com.endava.cats.report.TestCaseExporterHtmlJs;
import com.endava.cats.report.TestCaseListener;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.enterprise.inject.Instance;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

@QuarkusTest
class ResponsesWithBodiesLinterFuzzerTest {

private TestCaseListener testCaseListener;

private ResponsesWithBodiesLinterFuzzer responsesWithBodiesLinterFuzzer;

@BeforeEach
void setup() {
Instance<TestCaseExporter> exporters = Mockito.mock(Instance.class);
TestCaseExporter exporter = Mockito.mock(TestCaseExporterHtmlJs.class);
Mockito.when(exporters.stream()).thenReturn(Stream.of(exporter));
testCaseListener = Mockito.spy(new TestCaseListener(Mockito.mock(CatsGlobalContext.class), Mockito.mock(ExecutionStatisticsListener.class), exporters,
Mockito.mock(IgnoreArguments.class), Mockito.mock(ReportingArguments.class)));
responsesWithBodiesLinterFuzzer = new ResponsesWithBodiesLinterFuzzer(testCaseListener);
}

@Test
void shouldReturnInfo() {
FuzzingData data = FuzzingData.builder().responses(Map.of("200", List.of("1", "2"))).build();
responsesWithBodiesLinterFuzzer.fuzz(data);

Mockito.verify(testCaseListener, Mockito.times(1)).reportResultInfo(Mockito.any(), Mockito.any(), Mockito.eq("All HTTP response codes have a response body"));
}

@Test
void shouldReturnError() {
FuzzingData data = FuzzingData.builder().responses(Map.of("200", List.of())).build();
responsesWithBodiesLinterFuzzer.fuzz(data);

Mockito.verify(testCaseListener, Mockito.times(1)).reportResultError(Mockito.any(), Mockito.any(),
Mockito.eq("Missing response body for some HTTP response codes"), Mockito.eq("The following HTTP response codes are missing a response body: {}"), Mockito.eq(List.of("200")));
}

@Test
void shouldReturnSimpleClassNameForToString() {
Assertions.assertThat(responsesWithBodiesLinterFuzzer).hasToString(responsesWithBodiesLinterFuzzer.getClass().getSimpleName());
}

@Test
void shouldReturnMeaningfulDescription() {
Assertions.assertThat(responsesWithBodiesLinterFuzzer.description()).isEqualTo("verifies that HTTP response codes (except for 204 and 304) have a response body");
}
}

0 comments on commit daeed6b

Please sign in to comment.