Skip to content

Commit c567d61

Browse files
committed
feat: Check Allow header for http methods that are not documented
1 parent 605cd08 commit c567d61

2 files changed

Lines changed: 79 additions & 6 deletions

File tree

src/main/java/dev/dochia/cli/core/playbook/body/HttpMethodPlaybookUtil.java

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import dev.dochia.cli.core.playbook.executor.SimpleExecutor;
99
import dev.dochia.cli.core.playbook.executor.SimpleExecutorContext;
1010
import dev.dochia.cli.core.report.TestCaseListener;
11+
import dev.dochia.cli.core.util.KeyValuePair;
1112
import io.github.ludovicianul.prettylogger.PrettyLogger;
1213
import io.github.ludovicianul.prettylogger.PrettyLoggerFactory;
1314
import jakarta.inject.Inject;
@@ -42,9 +43,9 @@ public HttpMethodPlaybookUtil(TestCaseListener tcl, SimpleExecutor se) {
4243
* and HTTP method. It configures the execution context with the necessary parameters, including the logger,
4344
* expected response code, payload, scenario description, response processor, and additional fuzzing-related details.</p>
4445
*
45-
* @param testCasePlaybook The Playbook instance responsible for generating test cases and payloads during fuzzing.
46-
* @param data The FuzzingData containing information about the path, method, payload, and headers.
47-
* @param httpMethod The HTTP method for which fuzzing is being performed.
46+
* @param testCasePlaybook The Playbook instance responsible for generating test cases and payloads during fuzzing.
47+
* @param data The FuzzingData containing information about the path, method, payload, and headers.
48+
* @param httpMethod The HTTP method for which fuzzing is being performed.
4849
*/
4950
public void process(TestCasePlaybook testCasePlaybook, PlaybookData data, HttpMethod httpMethod) {
5051
simpleExecutor.execute(
@@ -63,8 +64,7 @@ public void process(TestCasePlaybook testCasePlaybook, PlaybookData data, HttpMe
6364

6465
private void checkResponse(HttpResponse response, PlaybookData data) {
6566
if (response.getResponseCode() == 405) {
66-
testCaseListener.reportResultInfo(logger, data, "Request failed as expected for http method [{}] with response code [{}]",
67-
response.getHttpMethod(), response.getResponseCode());
67+
this.handle405(response, data);
6868
} else if (ResponseCodeFamily.is2xxCode(response.getResponseCode())) {
6969
testCaseListener.reportResultError(logger, data, "Unexpected response code: %s".formatted(response.getResponseCode()), "Request succeeded unexpectedly for http method [{}]: expected [{}], actual [{}]",
7070
response.getHttpMethod(), 405, response.getResponseCode());
@@ -73,4 +73,16 @@ private void checkResponse(HttpResponse response, PlaybookData data) {
7373
response.getHttpMethod(), 405, response.getResponseCode());
7474
}
7575
}
76+
77+
private void handle405(HttpResponse response, PlaybookData data) {
78+
KeyValuePair<String, String> allowHeader = response.getHeader("Allow");
79+
if (allowHeader == null) {
80+
testCaseListener.reportResultWarn(logger, data, "Request failed as expected for http method [{}] with response code [{}], but missing Allow header", response.getHttpMethod(), response.getResponseCode());
81+
} else if (allowHeader.getValue().contains(response.getHttpMethod())) {
82+
testCaseListener.reportResultWarn(logger, data, "Request failed as expected for http method [{}] with response code [{}], but Allow header contains [{}]", response.getHttpMethod(), response.getResponseCode(), response.getHttpMethod());
83+
} else {
84+
testCaseListener.reportResultInfo(logger, data, "Request failed as expected for http method [{}] with response code [{}]",
85+
response.getHttpMethod(), response.getResponseCode());
86+
}
87+
}
7688
}

src/test/java/dev/dochia/cli/core/playbook/body/HttpMethodsPlaybookTest.java

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import dev.dochia.cli.core.playbook.executor.SimpleExecutor;
77
import dev.dochia.cli.core.report.TestCaseListener;
88
import dev.dochia.cli.core.report.TestReportsGenerator;
9+
import dev.dochia.cli.core.util.KeyValuePair;
910
import io.quarkus.test.junit.QuarkusTest;
1011
import io.quarkus.test.junit.mockito.InjectSpy;
1112
import io.swagger.v3.oas.models.Operation;
@@ -57,7 +58,12 @@ void givenAGetOperationImplemented_whenCallingTheHttpMethodsPlaybook_thenResults
5758
@Test
5859
void givenAnOperation_whenCallingTheHttpMethodsPlaybook_thenResultsAreCorrectlyReported() {
5960
PlaybookData data = PlaybookData.builder().pathItem(new PathItem()).reqSchema(new StringSchema()).requestContentTypes(List.of("application/json")).build();
60-
HttpResponse httpResponse = HttpResponse.builder().body("{}").responseCode(405).httpMethod("POST").build();
61+
HttpResponse httpResponse = HttpResponse.builder()
62+
.body("{}")
63+
.responseCode(405)
64+
.httpMethod("POST")
65+
.headers(List.of(new KeyValuePair<>("Allow", "GET, PUT, DELETE")))
66+
.build();
6167
Mockito.when(serviceCaller.call(Mockito.any())).thenReturn(httpResponse);
6268

6369
httpMethodsPlaybook.run(data);
@@ -100,4 +106,59 @@ void shouldNotRunSamePathTwice() {
100106
httpMethodsPlaybook.run(data);
101107
Mockito.verify(testCaseListener, Mockito.times(7)).reportResultError(Mockito.any(), Mockito.any(), Mockito.anyString(), Mockito.anyString(), AdditionalMatchers.aryEq(new Object[]{"POST", 405, 200}));
102108
}
109+
110+
@Test
111+
void givenAnOperation_whenCallingTheHttpMethodsPlaybookAndTheServiceResponsesWithNon2xxNon405_thenWarningIsReported() {
112+
PlaybookData data = PlaybookData.builder().pathItem(new PathItem()).reqSchema(new StringSchema()).requestContentTypes(List.of("application/json")).build();
113+
HttpResponse httpResponse = HttpResponse.builder().body("{}").responseCode(500).httpMethod("POST").build();
114+
Mockito.when(serviceCaller.call(Mockito.any())).thenReturn(httpResponse);
115+
116+
httpMethodsPlaybook.run(data);
117+
Mockito.verify(testCaseListener, Mockito.times(7)).reportResultWarn(Mockito.any(), Mockito.any(), Mockito.anyString(), Mockito.anyString(), AdditionalMatchers.aryEq(new Object[]{"POST", 405, 500}));
118+
}
119+
120+
@Test
121+
void givenAnOperation_whenCallingTheHttpMethodsPlaybookAndTheServiceResponsesWith405ButMissingAllowHeader_thenWarningIsReported() {
122+
PlaybookData data = PlaybookData.builder().pathItem(new PathItem()).reqSchema(new StringSchema()).requestContentTypes(List.of("application/json")).build();
123+
HttpResponse httpResponse = HttpResponse.builder()
124+
.body("{}")
125+
.responseCode(405)
126+
.httpMethod("POST")
127+
.headers(List.of())
128+
.build();
129+
Mockito.when(serviceCaller.call(Mockito.any())).thenReturn(httpResponse);
130+
131+
httpMethodsPlaybook.run(data);
132+
Mockito.verify(testCaseListener, Mockito.times(7)).reportResultWarn(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.eq("POST"), AdditionalMatchers.aryEq(new Object[]{405}));
133+
}
134+
135+
@Test
136+
void givenAnOperation_whenCallingTheHttpMethodsPlaybookAndTheServiceResponsesWith405AndAllowHeaderContainsMethod_thenWarningIsReported() {
137+
PlaybookData data = PlaybookData.builder().pathItem(new PathItem()).reqSchema(new StringSchema()).requestContentTypes(List.of("application/json")).build();
138+
HttpResponse httpResponse = HttpResponse.builder()
139+
.body("{}")
140+
.responseCode(405)
141+
.httpMethod("POST")
142+
.headers(List.of(new KeyValuePair<>("Allow", "GET, POST, PUT")))
143+
.build();
144+
Mockito.when(serviceCaller.call(Mockito.any())).thenReturn(httpResponse);
145+
146+
httpMethodsPlaybook.run(data);
147+
Mockito.verify(testCaseListener, Mockito.times(7)).reportResultWarn(Mockito.any(), Mockito.any(), Mockito.anyString(), Mockito.eq("POST"), AdditionalMatchers.aryEq(new Object[]{405, "POST"}));
148+
}
149+
150+
@Test
151+
void givenAnOperation_whenCallingTheHttpMethodsPlaybookAndTheServiceResponsesWith405AndValidAllowHeader_thenInfoIsReported() {
152+
PlaybookData data = PlaybookData.builder().pathItem(new PathItem()).reqSchema(new StringSchema()).requestContentTypes(List.of("application/json")).build();
153+
HttpResponse httpResponse = HttpResponse.builder()
154+
.body("{}")
155+
.responseCode(405)
156+
.httpMethod("POST")
157+
.headers(List.of(new KeyValuePair<>("Allow", "GET, PUT, DELETE")))
158+
.build();
159+
Mockito.when(serviceCaller.call(Mockito.any())).thenReturn(httpResponse);
160+
161+
httpMethodsPlaybook.run(data);
162+
Mockito.verify(testCaseListener, Mockito.times(7)).reportResultInfo(Mockito.any(), Mockito.any(), Mockito.anyString(), AdditionalMatchers.aryEq(new Object[]{"POST", 405}));
163+
}
103164
}

0 commit comments

Comments
 (0)