Skip to content

Commit 4a21a78

Browse files
thcedclaude
andcommitted
test: Add anyOf and not integration coverage
Add /filters (anyOf) and /blocked (not) fixture endpoints in both the YAML and JSON test-spec twins. Register five new IT tests across two new nested classes (Filters, Blocked) in OpenApiServerIT: valid string, valid integer, and short-string-rejected for anyOf; accepted-token and forbidden-token for not. Also clarify the empty-schema comment in SchemaParserTest to be more precise about what permissive ObjectSchema means. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 15ab4b7 commit 4a21a78

4 files changed

Lines changed: 198 additions & 2 deletions

File tree

src/test/java/com/retailsvc/http/OpenApiServerIT.java

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,4 +489,113 @@ void postShape_missingDiscriminatorReturns400() {
489489
}
490490
}
491491
}
492+
493+
@Nested
494+
class Filters {
495+
496+
String path = "/filters";
497+
498+
@Test
499+
void postFilter_validStringValueReturns200() {
500+
try (var server = newServer(Map.of("post-filter", new EchoHandler()));
501+
var client = httpClient()) {
502+
var body = "{\"value\":\"abcd\"}";
503+
var request = newRequest(server, path, "POST", ofString(body));
504+
505+
var response = client.send(request, BodyHandlers.ofString());
506+
507+
assertThat(response.statusCode()).isEqualTo(200);
508+
} catch (IOException e) {
509+
fail(e);
510+
} catch (InterruptedException e) {
511+
Thread.currentThread().interrupt();
512+
fail(e);
513+
}
514+
}
515+
516+
@Test
517+
void postFilter_validIntegerValueReturns200() {
518+
try (var server = newServer(Map.of("post-filter", new EchoHandler()));
519+
var client = httpClient()) {
520+
var body = "{\"value\":42}";
521+
var request = newRequest(server, path, "POST", ofString(body));
522+
523+
var response = client.send(request, BodyHandlers.ofString());
524+
525+
assertThat(response.statusCode()).isEqualTo(200);
526+
} catch (IOException e) {
527+
fail(e);
528+
} catch (InterruptedException e) {
529+
Thread.currentThread().interrupt();
530+
fail(e);
531+
}
532+
}
533+
534+
@Test
535+
void postFilter_shortStringMatchesNoBranchReturns400() {
536+
// "ab" has length < 3 (string branch fails) and is not an integer (integer branch fails).
537+
try (var server = newServer(Map.of("post-filter", new EchoHandler()));
538+
var client = httpClient()) {
539+
var body = "{\"value\":\"ab\"}";
540+
var request = newRequest(server, path, "POST", ofString(body));
541+
542+
var response = client.send(request, BodyHandlers.ofString());
543+
544+
assertThat(response.statusCode()).isEqualTo(400);
545+
assertThat(response.headers().firstValue("Content-Type").orElse(""))
546+
.contains("application/problem+json");
547+
assertThat(response.body()).contains("anyOf");
548+
} catch (IOException e) {
549+
fail(e);
550+
} catch (InterruptedException e) {
551+
Thread.currentThread().interrupt();
552+
fail(e);
553+
}
554+
}
555+
}
556+
557+
@Nested
558+
class Blocked {
559+
560+
String path = "/blocked";
561+
562+
@Test
563+
void postBlocked_acceptedTokenReturns200() {
564+
try (var server = newServer(Map.of("post-blocked", new EchoHandler()));
565+
var client = httpClient()) {
566+
var body = "{\"token\":\"allowed\"}";
567+
var request = newRequest(server, path, "POST", ofString(body));
568+
569+
var response = client.send(request, BodyHandlers.ofString());
570+
571+
assertThat(response.statusCode()).isEqualTo(200);
572+
} catch (IOException e) {
573+
fail(e);
574+
} catch (InterruptedException e) {
575+
Thread.currentThread().interrupt();
576+
fail(e);
577+
}
578+
}
579+
580+
@Test
581+
void postBlocked_forbiddenTokenReturns400() {
582+
try (var server = newServer(Map.of("post-blocked", new EchoHandler()));
583+
var client = httpClient()) {
584+
var body = "{\"token\":\"forbidden\"}";
585+
var request = newRequest(server, path, "POST", ofString(body));
586+
587+
var response = client.send(request, BodyHandlers.ofString());
588+
589+
assertThat(response.statusCode()).isEqualTo(400);
590+
assertThat(response.headers().firstValue("Content-Type").orElse(""))
591+
.contains("application/problem+json");
592+
assertThat(response.body()).contains("not");
593+
} catch (IOException e) {
594+
fail(e);
595+
} catch (InterruptedException e) {
596+
Thread.currentThread().interrupt();
597+
fail(e);
598+
}
599+
}
600+
}
492601
}

src/test/java/com/retailsvc/http/spec/schema/SchemaParserTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,8 @@ void multipleCombinatorsInOneSchemaWrapInAllOf() {
237237

238238
@Test
239239
void parsesEmptySchemaAsPermissiveObject() {
240-
// {} accepts anything that's an object; this is the JSON Schema 3.1 default
241-
// and a behaviour change from the previous parser, which returned NullSchema.
240+
// {} emits no type assertion (permissive ObjectSchema); JSON Schema 3.1 default.
241+
// Behaviour change from the previous parser, which returned NullSchema.
242242
Schema s = SchemaParser.parse(Map.of());
243243
assertThat(s).isInstanceOf(ObjectSchema.class);
244244
ObjectSchema obj = (ObjectSchema) s;

src/test/resources/openapi.json

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,54 @@
202202
},
203203
"responses": { "200": { "description": "OK" } }
204204
}
205+
},
206+
"/filters": {
207+
"post": {
208+
"operationId": "post-filter",
209+
"requestBody": {
210+
"required": true,
211+
"content": {
212+
"application/json": {
213+
"schema": {
214+
"type": "object",
215+
"required": ["value"],
216+
"properties": {
217+
"value": {
218+
"anyOf": [
219+
{ "type": "string", "minLength": 3 },
220+
{ "type": "integer" }
221+
]
222+
}
223+
}
224+
}
225+
}
226+
}
227+
},
228+
"responses": { "200": { "description": "OK" } }
229+
}
230+
},
231+
"/blocked": {
232+
"post": {
233+
"operationId": "post-blocked",
234+
"requestBody": {
235+
"required": true,
236+
"content": {
237+
"application/json": {
238+
"schema": {
239+
"type": "object",
240+
"required": ["token"],
241+
"properties": {
242+
"token": {
243+
"type": "string",
244+
"not": { "enum": ["forbidden"] }
245+
}
246+
}
247+
}
248+
}
249+
}
250+
},
251+
"responses": { "200": { "description": "OK" } }
252+
}
205253
}
206254
},
207255
"components": {

src/test/resources/openapi.yaml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,45 @@ paths:
138138
"200":
139139
description: OK
140140

141+
/filters:
142+
post:
143+
operationId: post-filter
144+
requestBody:
145+
required: true
146+
content:
147+
application/json:
148+
schema:
149+
type: object
150+
required: [value]
151+
properties:
152+
value:
153+
anyOf:
154+
- type: string
155+
minLength: 3
156+
- type: integer
157+
responses:
158+
"200":
159+
description: OK
160+
161+
/blocked:
162+
post:
163+
operationId: post-blocked
164+
requestBody:
165+
required: true
166+
content:
167+
application/json:
168+
schema:
169+
type: object
170+
required: [token]
171+
properties:
172+
token:
173+
type: string
174+
not:
175+
enum: [forbidden]
176+
responses:
177+
"200":
178+
description: OK
179+
141180
components:
142181
parameters:
143182
Name-Header:

0 commit comments

Comments
 (0)