Skip to content

Commit ce7a905

Browse files
committed
refactor(validator): address review feedback for non-JSON content type skip logic
Add explanatory comments to clarify skip/mismatch behavior in the validator and Laravel trait. Add tests for mixed JSON and non-JSON content types (valid and invalid bodies) and an integration test confirming non-JSON endpoints are skipped but still recorded for coverage.
1 parent 9a25bbd commit ce7a905

5 files changed

Lines changed: 82 additions & 0 deletions

File tree

src/Laravel/ValidatesOpenApiSchema.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ protected function assertResponseMatchesOpenApiSchema(
6161
$this->extractJsonBody($response, $content, $contentType),
6262
);
6363

64+
// Record coverage for any matched endpoint, including those where body
65+
// validation was skipped (e.g. non-JSON content types). "Covered" means
66+
// the endpoint was exercised in a test, not that its body was validated.
6467
if ($result->matchedPath() !== null) {
6568
OpenApiCoverageTracker::record(
6669
$specName,
@@ -69,6 +72,10 @@ protected function assertResponseMatchesOpenApiSchema(
6972
);
7073
}
7174

75+
// This guard catches the case where the spec defines a JSON content type
76+
// but the actual response has a non-JSON Content-Type header. The validator
77+
// itself skips validation for specs that define *only* non-JSON content
78+
// types (returning success), so this guard only fires for the mismatch case.
7279
if (!$result->isValid() && $hasNonJsonContentType) {
7380
$this->fail(
7481
"OpenAPI schema validation failed for {$resolvedMethod} {$resolvedPath} (spec: {$specName}):\n"

src/OpenApiResponseValidator.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ public function validate(
6868
$content = $responseSpec['content'];
6969
$jsonContentType = $this->findJsonContentType($content);
7070

71+
// If no JSON-compatible content type is defined, skip body validation.
72+
// This validator only handles JSON schemas; non-JSON types (e.g. text/html,
73+
// application/xml) are outside its scope.
7174
if ($jsonContentType === null) {
7275
return OpenApiValidationResult::success($matchedPath);
7376
}

tests/Integration/ResponseValidationTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,27 @@ public function full_pipeline_v31_validate_and_track_coverage(): void
9595
$this->assertSame(1, $coverage['coveredCount']);
9696
}
9797

98+
#[Test]
99+
public function non_json_endpoint_skips_validation_and_records_coverage(): void
100+
{
101+
$result = $this->validator->validate(
102+
'petstore-3.0',
103+
'GET',
104+
'/v1/logout',
105+
200,
106+
'<html><body>Logged out</body></html>',
107+
);
108+
$this->assertTrue($result->isValid());
109+
110+
if ($result->matchedPath() !== null) {
111+
OpenApiCoverageTracker::record('petstore-3.0', 'GET', $result->matchedPath());
112+
}
113+
114+
$coverage = OpenApiCoverageTracker::computeCoverage('petstore-3.0');
115+
$this->assertSame(1, $coverage['coveredCount']);
116+
$this->assertContains('GET /v1/logout', $coverage['covered']);
117+
}
118+
98119
#[Test]
99120
public function invalid_response_produces_descriptive_errors(): void
100121
{

tests/Unit/OpenApiResponseValidatorTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,36 @@ public function v30_text_html_only_content_type_skips_validation(): void
274274
$this->assertSame('/v1/logout', $result->matchedPath());
275275
}
276276

277+
#[Test]
278+
public function v30_mixed_json_and_non_json_content_types_validates_json_schema(): void
279+
{
280+
$result = $this->validator->validate(
281+
'petstore-3.0',
282+
'POST',
283+
'/v1/pets',
284+
409,
285+
['error' => 'Pet already exists'],
286+
);
287+
288+
$this->assertTrue($result->isValid());
289+
$this->assertSame('/v1/pets', $result->matchedPath());
290+
}
291+
292+
#[Test]
293+
public function v30_mixed_content_types_with_invalid_json_body_fails(): void
294+
{
295+
$result = $this->validator->validate(
296+
'petstore-3.0',
297+
'POST',
298+
'/v1/pets',
299+
409,
300+
['wrong_key' => 'value'],
301+
);
302+
303+
$this->assertFalse($result->isValid());
304+
$this->assertNotEmpty($result->errors());
305+
}
306+
277307
// ========================================
278308
// OAS 3.1 tests
279309
// ========================================

tests/fixtures/specs/petstore-3.0.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,27 @@
130130
}
131131
}
132132
},
133+
"409": {
134+
"description": "Conflict",
135+
"content": {
136+
"text/html": {
137+
"schema": {
138+
"type": "string"
139+
}
140+
},
141+
"application/json": {
142+
"schema": {
143+
"type": "object",
144+
"required": ["error"],
145+
"properties": {
146+
"error": {
147+
"type": "string"
148+
}
149+
}
150+
}
151+
}
152+
}
153+
},
133154
"415": {
134155
"description": "Unsupported media type",
135156
"content": {

0 commit comments

Comments
 (0)