Skip to content

Commit 016d309

Browse files
committed
test: add content negotiation and responseContentType parameter tests
Add 7 unit tests for OpenApiResponseValidator covering mixed content types, charset handling, undefined types, and backward compatibility. Update ValidatesOpenApiSchemaTest for new error message format. Add integration test for content negotiation with coverage tracking.
1 parent bafcd4f commit 016d309

3 files changed

Lines changed: 156 additions & 1 deletion

File tree

tests/Integration/ResponseValidationTest.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,28 @@ public function non_json_endpoint_skips_validation_and_records_coverage(): void
116116
$this->assertContains('GET /v1/logout', $coverage['covered']);
117117
}
118118

119+
#[Test]
120+
public function content_negotiation_non_json_response_succeeds_and_records_coverage(): void
121+
{
122+
$result = $this->validator->validate(
123+
'petstore-3.0',
124+
'POST',
125+
'/v1/pets',
126+
409,
127+
null,
128+
'text/html',
129+
);
130+
$this->assertTrue($result->isValid());
131+
132+
if ($result->matchedPath() !== null) {
133+
OpenApiCoverageTracker::record('petstore-3.0', 'POST', $result->matchedPath());
134+
}
135+
136+
$coverage = OpenApiCoverageTracker::computeCoverage('petstore-3.0');
137+
$this->assertSame(1, $coverage['coveredCount']);
138+
$this->assertContains('POST /v1/pets', $coverage['covered']);
139+
}
140+
119141
#[Test]
120142
public function invalid_response_produces_descriptive_errors(): void
121143
{

tests/Unit/OpenApiResponseValidatorTest.php

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,123 @@ public function v30_mixed_content_types_with_invalid_json_body_fails(): void
304304
$this->assertNotEmpty($result->errors());
305305
}
306306

307+
// ========================================
308+
// OAS 3.0 content negotiation tests (responseContentType parameter)
309+
// ========================================
310+
311+
#[Test]
312+
public function v30_mixed_content_type_with_non_json_response_content_type_succeeds(): void
313+
{
314+
$result = $this->validator->validate(
315+
'petstore-3.0',
316+
'POST',
317+
'/v1/pets',
318+
409,
319+
null,
320+
'text/html',
321+
);
322+
323+
$this->assertTrue($result->isValid());
324+
$this->assertSame('/v1/pets', $result->matchedPath());
325+
}
326+
327+
#[Test]
328+
public function v30_mixed_content_type_with_json_response_content_type_validates_schema(): void
329+
{
330+
$result = $this->validator->validate(
331+
'petstore-3.0',
332+
'POST',
333+
'/v1/pets',
334+
409,
335+
['error' => 'Pet already exists'],
336+
'application/json',
337+
);
338+
339+
$this->assertTrue($result->isValid());
340+
$this->assertSame('/v1/pets', $result->matchedPath());
341+
}
342+
343+
#[Test]
344+
public function v30_mixed_content_type_with_json_response_content_type_and_invalid_body_fails(): void
345+
{
346+
$result = $this->validator->validate(
347+
'petstore-3.0',
348+
'POST',
349+
'/v1/pets',
350+
409,
351+
['wrong_key' => 'value'],
352+
'application/json',
353+
);
354+
355+
$this->assertFalse($result->isValid());
356+
$this->assertNotEmpty($result->errors());
357+
}
358+
359+
#[Test]
360+
public function v30_response_content_type_not_in_spec_fails(): void
361+
{
362+
$result = $this->validator->validate(
363+
'petstore-3.0',
364+
'POST',
365+
'/v1/pets',
366+
409,
367+
null,
368+
'text/plain',
369+
);
370+
371+
$this->assertFalse($result->isValid());
372+
$this->assertStringContainsString('not defined', $result->errors()[0]);
373+
$this->assertStringContainsString('text/plain', $result->errors()[0]);
374+
}
375+
376+
#[Test]
377+
public function v30_response_content_type_with_charset_matches_spec(): void
378+
{
379+
$result = $this->validator->validate(
380+
'petstore-3.0',
381+
'POST',
382+
'/v1/pets',
383+
409,
384+
null,
385+
'text/html; charset=utf-8',
386+
);
387+
388+
$this->assertTrue($result->isValid());
389+
$this->assertSame('/v1/pets', $result->matchedPath());
390+
}
391+
392+
#[Test]
393+
public function v30_null_response_content_type_preserves_existing_behavior(): void
394+
{
395+
$result = $this->validator->validate(
396+
'petstore-3.0',
397+
'POST',
398+
'/v1/pets',
399+
409,
400+
null,
401+
null,
402+
);
403+
404+
$this->assertFalse($result->isValid());
405+
$this->assertStringContainsString('Response body is empty', $result->errors()[0]);
406+
}
407+
408+
#[Test]
409+
public function v30_non_json_only_spec_with_matching_response_content_type_succeeds(): void
410+
{
411+
$result = $this->validator->validate(
412+
'petstore-3.0',
413+
'GET',
414+
'/v1/logout',
415+
200,
416+
null,
417+
'text/html',
418+
);
419+
420+
$this->assertTrue($result->isValid());
421+
$this->assertSame('/v1/logout', $result->matchedPath());
422+
}
423+
307424
// ========================================
308425
// OAS 3.1 tests
309426
// ========================================

tests/Unit/ValidatesOpenApiSchemaTest.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ public function non_json_body_fails_with_content_type_mismatch(): void
169169
);
170170

171171
$this->expectException(AssertionFailedError::class);
172-
$this->expectExceptionMessage("Response has Content-Type 'text/html' but the spec expects a JSON response");
172+
$this->expectExceptionMessage('not defined');
173173

174174
$this->assertResponseMatchesOpenApiSchema(
175175
$response,
@@ -242,6 +242,22 @@ public function missing_content_type_header_still_parses_json(): void
242242
);
243243
}
244244

245+
#[Test]
246+
public function non_json_content_type_in_spec_with_mixed_content_types_passes(): void
247+
{
248+
$response = $this->makeTestResponse(
249+
'<html><body>Conflict</body></html>',
250+
409,
251+
['Content-Type' => 'text/html'],
252+
);
253+
254+
$this->assertResponseMatchesOpenApiSchema(
255+
$response,
256+
HttpMethod::POST,
257+
'/v1/pets',
258+
);
259+
}
260+
245261
protected function openApiSpec(): string
246262
{
247263
return 'petstore-3.0';

0 commit comments

Comments
 (0)