Skip to content

Commit dfeca32

Browse files
Adding more tests
1 parent aa044cd commit dfeca32

7 files changed

+177
-7
lines changed

.idea/problem-details-symfony-bundle.iml

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

phpstan.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ parameters:
44
- src
55
parallel:
66
maximumNumberOfProcesses: 4
7+
treatPhpDocTypesAsCertain: false

src/ProblemDetailsResponse.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public static function create(
5353
];
5454

5555
if (!empty($detail)) {
56-
$data['detail'] = $instance;
56+
$data['detail'] = $detail;
5757
}
5858

5959
if (!empty($instance)) {
@@ -87,12 +87,8 @@ public static function assertReservedResponseFields(array $extensions): void
8787
/**
8888
* Validates if the given status code is a valid client-side (4xx) or server-side (5xx) error.
8989
*/
90-
protected static function assertValidStatusCode(?int $statusCode): void
90+
protected static function assertValidStatusCode(int $statusCode): void
9191
{
92-
if (!$statusCode) {
93-
return;
94-
}
95-
9692
if (!($statusCode >= 400 && $statusCode < 500) && !($statusCode >= 500 && $statusCode < 600)) {
9793
throw new LogicException(sprintf(
9894
'Invalid status code %s provided for a Problem Details response. '

src/ThrowableToProblemDetailsKernelListener.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public function __construct(
3232
protected array $exceptionConverters = []
3333
) {
3434
if (empty($this->exceptionConverters)) {
35-
throw new InvalidArgumentException('No exception converter passed!');
35+
throw new InvalidArgumentException('At least one converter must be provided');
3636
}
3737
}
3838

@@ -49,6 +49,10 @@ private function processConverters(ExceptionEvent $event): void
4949
{
5050
$throwable = $event->getThrowable();
5151
foreach ($this->exceptionConverters as $exceptionConverter) {
52+
if (!$exceptionConverter instanceof ExceptionConverterInterface) {
53+
throw new InvalidArgumentException('All converters must implement ' . ExceptionConverterInterface::class);
54+
}
55+
5256
if (!$exceptionConverter->canHandle($throwable)) {
5357
continue;
5458
}

tests/Unit/ExceptionConversion/ValidationFailedExceptionConverterTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@
1111
use Phauthentic\Symfony\ProblemDetails\Validation\ValidationErrorsBuilder;
1212
use PHPUnit\Framework\Attributes\Test;
1313
use PHPUnit\Framework\TestCase;
14+
use ReflectionMethod;
15+
use RuntimeException;
1416
use Symfony\Component\HttpFoundation\Request;
1517
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
18+
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
1619
use Symfony\Component\HttpKernel\HttpKernelInterface;
1720
use Symfony\Component\Validator\ConstraintViolation;
1821
use Symfony\Component\Validator\ConstraintViolationList;
@@ -102,4 +105,22 @@ private function getConstraintViolationList(): ConstraintViolationList
102105

103106
return new ConstraintViolationList([$violation1, $violation2]);
104107
}
108+
109+
#[Test]
110+
public function testExtractValidationFailedExceptionThrowsRuntimeException(): void
111+
{
112+
// Arrange
113+
$exception = new UnprocessableEntityHttpException('Validation failed', new Exception(), 0, []);
114+
$kernel = $this->createMock(HttpKernelInterface::class);
115+
$request = new Request([], [], [], [], [], ['REQUEST_URI' => '/profile/1']);
116+
$request->headers->add(['Accept' => 'application/json']);
117+
$event = new ExceptionEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, $exception);
118+
119+
// Assert
120+
$this->expectException(RuntimeException::class);
121+
$this->expectExceptionMessage('ValidationFailedException not found');
122+
123+
// Act
124+
$this->converter->convertExceptionToErrorDetails($exception, $event);
125+
}
105126
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Phauthentic\Symfony\ProblemDetails\Tests\Unit;
6+
7+
use InvalidArgumentException;
8+
use LogicException;
9+
use PHPUnit\Framework\TestCase;
10+
use Phauthentic\Symfony\ProblemDetails\ProblemDetailsResponse;
11+
12+
class ProblemDetailsResponseTest extends TestCase
13+
{
14+
public function testCreateValidResponse(): void
15+
{
16+
// Arrange
17+
$status = 422;
18+
$type = 'https://example.com/probs/out-of-credit';
19+
$title = 'You do not have enough credit.';
20+
$detail = 'Your current balance is 30, but that costs 50.';
21+
$instance = '/account/12345/msgs/abc';
22+
$extensions = ['balance' => 30, 'accounts' => ['/account/12345', '/account/67890']];
23+
24+
// Act
25+
$response = ProblemDetailsResponse::create(
26+
status: $status,
27+
type: $type,
28+
title: $title,
29+
detail: $detail,
30+
instance: $instance,
31+
extensions: $extensions
32+
);
33+
34+
// Assert
35+
$this->assertEquals($status, $response->getStatusCode());
36+
$this->assertEquals('application/problem+json', $response->headers->get('Content-Type'));
37+
$this->assertJsonStringEqualsJsonString(
38+
json_encode([
39+
'status' => $status,
40+
'type' => $type,
41+
'title' => $title,
42+
'detail' => $detail,
43+
'instance' => $instance,
44+
'balance' => 30,
45+
'accounts' => ['/account/12345', '/account/67890']
46+
], JSON_THROW_ON_ERROR),
47+
$response->getContent()
48+
);
49+
}
50+
51+
public function testCreateResponseWithReservedFieldInExtensions(): void
52+
{
53+
$this->expectException(InvalidArgumentException::class);
54+
$this->expectExceptionMessage('The key "status" is a reserved key and cannot be used as an extension.');
55+
56+
// Arrange
57+
$status = 422;
58+
$type = 'https://example.com/probs/out-of-credit';
59+
$title = 'You do not have enough credit.';
60+
$detail = 'Your current balance is 30, but that costs 50.';
61+
$instance = '/account/12345/msgs/abc';
62+
$extensions = ['status' => 'reserved'];
63+
64+
// Act
65+
ProblemDetailsResponse::create(
66+
status: $status,
67+
type: $type,
68+
title: $title,
69+
detail: $detail,
70+
instance: $instance,
71+
extensions: $extensions
72+
);
73+
}
74+
75+
public function testCreateResponseWithInvalidStatusCode(): void
76+
{
77+
$this->expectException(LogicException::class);
78+
$this->expectExceptionMessage('Invalid status code 200 provided for a Problem Details response.');
79+
80+
// Arrange
81+
$status = 200;
82+
$type = 'https://example.com/probs/out-of-credit';
83+
$title = 'You do not have enough credit.';
84+
$detail = 'Your current balance is 30, but that costs 50.';
85+
$instance = '/account/12345/msgs/abc';
86+
87+
// Act
88+
ProblemDetailsResponse::create(
89+
status: $status,
90+
type: $type,
91+
title: $title,
92+
detail: $detail,
93+
instance: $instance
94+
);
95+
}
96+
97+
public function testCreateResponseWithMinimalParameters(): void
98+
{
99+
// Arrange
100+
$status = 500;
101+
102+
// Act
103+
$response = ProblemDetailsResponse::create($status);
104+
105+
// Assert
106+
$this->assertEquals($status, $response->getStatusCode());
107+
$this->assertEquals('application/problem+json', $response->headers->get('Content-Type'));
108+
$this->assertJsonStringEqualsJsonString(
109+
json_encode([
110+
'status' => $status,
111+
'type' => 'about:blank',
112+
'title' => null,
113+
], JSON_THROW_ON_ERROR),
114+
$response->getContent()
115+
);
116+
}
117+
}

tests/Unit/ThrowableToProblemDetailsKernelListenerTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Phauthentic\Symfony\ProblemDetails\Tests\Unit;
66

77
use Exception;
8+
use InvalidArgumentException;
89
use Phauthentic\Symfony\ProblemDetails\ExceptionConversion\GenericThrowableConverter;
910
use Phauthentic\Symfony\ProblemDetails\ProblemDetailsFactory;
1011
use PHPUnit\Framework\Attributes\DataProvider;
@@ -65,4 +66,33 @@ public function testOnKernelException(string $environment, bool $shouldHaveTrace
6566
$this->assertArrayNotHasKey('trace', $data);
6667
}
6768
}
69+
70+
#[Test]
71+
public function testInstantiationWithoutConverters(): void
72+
{
73+
$this->expectException(InvalidArgumentException::class);
74+
$this->expectExceptionMessage('At least one converter must be provided');
75+
76+
new ThrowableToProblemDetailsKernelListener([]);
77+
}
78+
79+
#[Test]
80+
public function testInstantiationWithoutValidConverter(): void
81+
{
82+
// Arrange
83+
$throwable = new Exception('Unmapped exception');
84+
$kernel = $this->createMock(HttpKernelInterface::class);
85+
$request = new Request(
86+
server: ['HTTP_ACCEPT' => 'application/json']
87+
);
88+
$event = new ExceptionEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, $throwable);
89+
90+
// Expect
91+
$this->expectException(InvalidArgumentException::class);
92+
$this->expectExceptionMessage('All converters must implement Phauthentic\Symfony\ProblemDetails\ExceptionConversion\ExceptionConverterInterface');
93+
94+
// Act
95+
$listener = new ThrowableToProblemDetailsKernelListener([new \stdClass()]);
96+
$listener->onKernelException($event);
97+
}
6898
}

0 commit comments

Comments
 (0)