Skip to content

Commit 9a25bbd

Browse files
committed
fix(validator): skip validation for non-JSON content types instead of failing
Fixes a regression introduced in v0.6.0 (PR #19) where endpoints with non-JSON content types (e.g. text/html) incorrectly returned a failure result instead of skipping validation. Now returns success when no JSON-compatible content type is found in the spec. Fixes #20
1 parent f1d0d29 commit 9a25bbd

5 files changed

Lines changed: 46 additions & 19 deletions

File tree

src/OpenApiResponseValidator.php

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
use Opis\JsonSchema\Validator;
1111

1212
use function array_keys;
13-
use function implode;
1413
use function json_decode;
1514
use function json_encode;
1615
use function str_ends_with;
@@ -70,11 +69,7 @@ public function validate(
7069
$jsonContentType = $this->findJsonContentType($content);
7170

7271
if ($jsonContentType === null) {
73-
$definedTypes = array_keys($content);
74-
75-
return OpenApiValidationResult::failure([
76-
"No JSON-compatible content type found for {$method} {$matchedPath} (status {$statusCode}) in '{$specName}' spec. Defined content types: " . implode(', ', $definedTypes),
77-
]);
72+
return OpenApiValidationResult::success($matchedPath);
7873
}
7974

8075
if (!isset($content[$jsonContentType]['schema'])) {

tests/Integration/ResponseValidationTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,12 @@ public function full_pipeline_v30_validate_and_track_coverage(): void
6464

6565
// Check coverage
6666
$coverage = OpenApiCoverageTracker::computeCoverage('petstore-3.0');
67-
$this->assertSame(5, $coverage['total']);
67+
$this->assertSame(6, $coverage['total']);
6868
$this->assertSame(2, $coverage['coveredCount']);
6969
$this->assertContains('GET /v1/pets', $coverage['covered']);
7070
$this->assertContains('POST /v1/pets', $coverage['covered']);
7171
$this->assertContains('GET /v1/health', $coverage['uncovered']);
72+
$this->assertContains('GET /v1/logout', $coverage['uncovered']);
7273
$this->assertContains('DELETE /v1/pets/{petId}', $coverage['uncovered']);
7374
$this->assertContains('GET /v1/pets/{petId}', $coverage['uncovered']);
7475
}

tests/Unit/OpenApiCoverageTrackerTest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,21 +67,21 @@ public function compute_coverage_returns_correct_stats(): void
6767
$result = OpenApiCoverageTracker::computeCoverage('petstore-3.0');
6868

6969
// See tests/fixtures/specs/petstore-3.0.json for the full endpoint list
70-
$this->assertSame(5, $result['total']);
70+
$this->assertSame(6, $result['total']);
7171
$this->assertSame(2, $result['coveredCount']);
7272
$this->assertCount(2, $result['covered']);
73-
$this->assertCount(3, $result['uncovered']);
73+
$this->assertCount(4, $result['uncovered']);
7474
}
7575

7676
#[Test]
7777
public function compute_coverage_with_no_coverage(): void
7878
{
7979
$result = OpenApiCoverageTracker::computeCoverage('petstore-3.0');
8080

81-
$this->assertSame(5, $result['total']);
81+
$this->assertSame(6, $result['total']);
8282
$this->assertSame(0, $result['coveredCount']);
8383
$this->assertCount(0, $result['covered']);
84-
$this->assertCount(5, $result['uncovered']);
84+
$this->assertCount(6, $result['uncovered']);
8585
}
8686

8787
#[Test]

tests/Unit/OpenApiResponseValidatorTest.php

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ public function v30_problem_json_empty_body_fails(): void
211211
}
212212

213213
#[Test]
214-
public function v30_content_without_json_compatible_type_fails(): void
214+
public function v30_non_json_content_type_skips_validation(): void
215215
{
216216
$result = $this->validator->validate(
217217
'petstore-3.0',
@@ -221,9 +221,8 @@ public function v30_content_without_json_compatible_type_fails(): void
221221
'<error>Unsupported</error>',
222222
);
223223

224-
$this->assertFalse($result->isValid());
225-
$this->assertStringContainsString('No JSON-compatible content type found', $result->errors()[0]);
226-
$this->assertStringContainsString('application/xml', $result->errors()[0]);
224+
$this->assertTrue($result->isValid());
225+
$this->assertSame('/v1/pets', $result->matchedPath());
227226
}
228227

229228
#[Test]
@@ -260,6 +259,21 @@ public function v30_json_content_type_without_schema_skips_validation(): void
260259
$this->assertSame('/v1/pets', $result->matchedPath());
261260
}
262261

262+
#[Test]
263+
public function v30_text_html_only_content_type_skips_validation(): void
264+
{
265+
$result = $this->validator->validate(
266+
'petstore-3.0',
267+
'GET',
268+
'/v1/logout',
269+
200,
270+
'<html><body>Logged out</body></html>',
271+
);
272+
273+
$this->assertTrue($result->isValid());
274+
$this->assertSame('/v1/logout', $result->matchedPath());
275+
}
276+
263277
// ========================================
264278
// OAS 3.1 tests
265279
// ========================================
@@ -324,7 +338,7 @@ public function v31_problem_json_valid_response_passes(): void
324338
}
325339

326340
#[Test]
327-
public function v31_content_without_json_compatible_type_fails(): void
341+
public function v31_non_json_content_type_skips_validation(): void
328342
{
329343
$result = $this->validator->validate(
330344
'petstore-3.1',
@@ -334,9 +348,8 @@ public function v31_content_without_json_compatible_type_fails(): void
334348
'<error>Unsupported</error>',
335349
);
336350

337-
$this->assertFalse($result->isValid());
338-
$this->assertStringContainsString('No JSON-compatible content type found', $result->errors()[0]);
339-
$this->assertStringContainsString('application/xml', $result->errors()[0]);
351+
$this->assertTrue($result->isValid());
352+
$this->assertSame('/v1/pets', $result->matchedPath());
340353
}
341354

342355
#[Test]

tests/fixtures/specs/petstore-3.0.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,24 @@
143143
}
144144
}
145145
},
146+
"/v1/logout": {
147+
"get": {
148+
"summary": "Logout page",
149+
"operationId": "logout",
150+
"responses": {
151+
"200": {
152+
"description": "Logout confirmation page",
153+
"content": {
154+
"text/html": {
155+
"schema": {
156+
"type": "string"
157+
}
158+
}
159+
}
160+
}
161+
}
162+
}
163+
},
146164
"/v1/health": {
147165
"get": {
148166
"summary": "Health check",

0 commit comments

Comments
 (0)