Skip to content

Commit

Permalink
Adding more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
floriankraemer committed Jan 6, 2025
1 parent aa044cd commit dfeca32
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 7 deletions.
1 change: 1 addition & 0 deletions .idea/problem-details-symfony-bundle.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ parameters:
- src
parallel:
maximumNumberOfProcesses: 4
treatPhpDocTypesAsCertain: false
8 changes: 2 additions & 6 deletions src/ProblemDetailsResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public static function create(
];

if (!empty($detail)) {
$data['detail'] = $instance;
$data['detail'] = $detail;
}

if (!empty($instance)) {
Expand Down Expand Up @@ -87,12 +87,8 @@ public static function assertReservedResponseFields(array $extensions): void
/**
* Validates if the given status code is a valid client-side (4xx) or server-side (5xx) error.
*/
protected static function assertValidStatusCode(?int $statusCode): void
protected static function assertValidStatusCode(int $statusCode): void
{
if (!$statusCode) {
return;
}

if (!($statusCode >= 400 && $statusCode < 500) && !($statusCode >= 500 && $statusCode < 600)) {
throw new LogicException(sprintf(
'Invalid status code %s provided for a Problem Details response. '
Expand Down
6 changes: 5 additions & 1 deletion src/ThrowableToProblemDetailsKernelListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public function __construct(
protected array $exceptionConverters = []
) {
if (empty($this->exceptionConverters)) {
throw new InvalidArgumentException('No exception converter passed!');
throw new InvalidArgumentException('At least one converter must be provided');
}
}

Expand All @@ -49,6 +49,10 @@ private function processConverters(ExceptionEvent $event): void
{
$throwable = $event->getThrowable();
foreach ($this->exceptionConverters as $exceptionConverter) {
if (!$exceptionConverter instanceof ExceptionConverterInterface) {
throw new InvalidArgumentException('All converters must implement ' . ExceptionConverterInterface::class);
}

if (!$exceptionConverter->canHandle($throwable)) {
continue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@
use Phauthentic\Symfony\ProblemDetails\Validation\ValidationErrorsBuilder;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use ReflectionMethod;
use RuntimeException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
Expand Down Expand Up @@ -102,4 +105,22 @@ private function getConstraintViolationList(): ConstraintViolationList

return new ConstraintViolationList([$violation1, $violation2]);
}

#[Test]
public function testExtractValidationFailedExceptionThrowsRuntimeException(): void
{
// Arrange
$exception = new UnprocessableEntityHttpException('Validation failed', new Exception(), 0, []);
$kernel = $this->createMock(HttpKernelInterface::class);
$request = new Request([], [], [], [], [], ['REQUEST_URI' => '/profile/1']);
$request->headers->add(['Accept' => 'application/json']);
$event = new ExceptionEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, $exception);

// Assert
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('ValidationFailedException not found');

// Act
$this->converter->convertExceptionToErrorDetails($exception, $event);
}
}
117 changes: 117 additions & 0 deletions tests/Unit/ProblemDetailsResponseTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php

declare(strict_types=1);

namespace Phauthentic\Symfony\ProblemDetails\Tests\Unit;

use InvalidArgumentException;
use LogicException;
use PHPUnit\Framework\TestCase;
use Phauthentic\Symfony\ProblemDetails\ProblemDetailsResponse;

class ProblemDetailsResponseTest extends TestCase
{
public function testCreateValidResponse(): void
{
// Arrange
$status = 422;
$type = 'https://example.com/probs/out-of-credit';
$title = 'You do not have enough credit.';
$detail = 'Your current balance is 30, but that costs 50.';
$instance = '/account/12345/msgs/abc';
$extensions = ['balance' => 30, 'accounts' => ['/account/12345', '/account/67890']];

// Act
$response = ProblemDetailsResponse::create(
status: $status,
type: $type,
title: $title,
detail: $detail,
instance: $instance,
extensions: $extensions
);

// Assert
$this->assertEquals($status, $response->getStatusCode());
$this->assertEquals('application/problem+json', $response->headers->get('Content-Type'));
$this->assertJsonStringEqualsJsonString(
json_encode([
'status' => $status,
'type' => $type,
'title' => $title,
'detail' => $detail,
'instance' => $instance,
'balance' => 30,
'accounts' => ['/account/12345', '/account/67890']
], JSON_THROW_ON_ERROR),
$response->getContent()
);
}

public function testCreateResponseWithReservedFieldInExtensions(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('The key "status" is a reserved key and cannot be used as an extension.');

// Arrange
$status = 422;
$type = 'https://example.com/probs/out-of-credit';
$title = 'You do not have enough credit.';
$detail = 'Your current balance is 30, but that costs 50.';
$instance = '/account/12345/msgs/abc';
$extensions = ['status' => 'reserved'];

// Act
ProblemDetailsResponse::create(
status: $status,
type: $type,
title: $title,
detail: $detail,
instance: $instance,
extensions: $extensions
);
}

public function testCreateResponseWithInvalidStatusCode(): void
{
$this->expectException(LogicException::class);
$this->expectExceptionMessage('Invalid status code 200 provided for a Problem Details response.');

// Arrange
$status = 200;
$type = 'https://example.com/probs/out-of-credit';
$title = 'You do not have enough credit.';
$detail = 'Your current balance is 30, but that costs 50.';
$instance = '/account/12345/msgs/abc';

// Act
ProblemDetailsResponse::create(
status: $status,
type: $type,
title: $title,
detail: $detail,
instance: $instance
);
}

public function testCreateResponseWithMinimalParameters(): void
{
// Arrange
$status = 500;

// Act
$response = ProblemDetailsResponse::create($status);

// Assert
$this->assertEquals($status, $response->getStatusCode());
$this->assertEquals('application/problem+json', $response->headers->get('Content-Type'));
$this->assertJsonStringEqualsJsonString(
json_encode([
'status' => $status,
'type' => 'about:blank',
'title' => null,
], JSON_THROW_ON_ERROR),
$response->getContent()
);
}
}
30 changes: 30 additions & 0 deletions tests/Unit/ThrowableToProblemDetailsKernelListenerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Phauthentic\Symfony\ProblemDetails\Tests\Unit;

use Exception;
use InvalidArgumentException;
use Phauthentic\Symfony\ProblemDetails\ExceptionConversion\GenericThrowableConverter;
use Phauthentic\Symfony\ProblemDetails\ProblemDetailsFactory;
use PHPUnit\Framework\Attributes\DataProvider;
Expand Down Expand Up @@ -65,4 +66,33 @@ public function testOnKernelException(string $environment, bool $shouldHaveTrace
$this->assertArrayNotHasKey('trace', $data);
}
}

#[Test]
public function testInstantiationWithoutConverters(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('At least one converter must be provided');

new ThrowableToProblemDetailsKernelListener([]);
}

#[Test]
public function testInstantiationWithoutValidConverter(): void
{
// Arrange
$throwable = new Exception('Unmapped exception');
$kernel = $this->createMock(HttpKernelInterface::class);
$request = new Request(
server: ['HTTP_ACCEPT' => 'application/json']
);
$event = new ExceptionEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, $throwable);

// Expect
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('All converters must implement Phauthentic\Symfony\ProblemDetails\ExceptionConversion\ExceptionConverterInterface');

// Act
$listener = new ThrowableToProblemDetailsKernelListener([new \stdClass()]);
$listener->onKernelException($event);
}
}

0 comments on commit dfeca32

Please sign in to comment.