Initial commit

This commit is contained in:
2025-12-29 16:27:01 +00:00
committed by Siteworx Pro Gitea
commit 23f2b6432b
147 changed files with 14731 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Siteworxpro\Tests\Http;
use PHPUnit\Framework\TestCase;
use Siteworxpro\App\Http\JsonResponseFactory;
use Siteworxpro\HttpStatus\CodesEnum;
class JsonResponseFactoryTest extends TestCase
{
/**
* @throws \JsonException
*/
public function testCreateJsonResponseReturnsValidResponse(): void
{
$data = ['key' => 'value'];
$statusCode = CodesEnum::OK;
$response = JsonResponseFactory::createJsonResponse($data, $statusCode);
$this->assertSame($statusCode->value, $response->getStatusCode());
$this->assertSame('application/json', $response->getHeaderLine('Content-Type'));
$this->assertSame(json_encode($data), (string) $response->getBody());
}
/**
* @throws \JsonException
*/
public function testCreateJsonResponseHandlesEmptyData(): void
{
$data = [];
$statusCode = CodesEnum::NO_CONTENT;
$response = JsonResponseFactory::createJsonResponse($data, $statusCode);
$this->assertSame($statusCode->value, $response->getStatusCode());
$this->assertSame('application/json', $response->getHeaderLine('Content-Type'));
$this->assertSame(json_encode($data), (string) $response->getBody());
}
public function testCreateJsonResponseThrowsExceptionOnInvalidData(): void
{
$this->expectException(\JsonException::class);
$data = ["invalid" => "\xB1\x31"];
JsonResponseFactory::createJsonResponse($data);
}
}

View File

@@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
namespace Siteworxpro\Tests\Http\Middleware;
use Nyholm\Psr7\Response;
use Nyholm\Psr7\ServerRequest;
use Psr\Http\Server\RequestHandlerInterface;
use Siteworxpro\App\Http\Middleware\CorsMiddleware;
use Siteworxpro\App\Services\Facades\Config;
use Siteworxpro\Tests\Unit;
class CorsMiddlewareTest extends Middleware
{
public function testAllowsConfiguredOrigin(): void
{
Config::shouldReceive('get')
->with('cors.allowed_origins')
->andReturn('https://example.com,https://another.com');
Config::shouldReceive('get')->with('cors.allow_credentials')->andReturn(false);
Config::shouldReceive('get')->with('cors.max_age')->andReturn('');
$middleware = new CorsMiddleware();
$request = new ServerRequest('GET', '/')->withHeader('Origin', 'https://example.com');
$handler = $this->mockHandler(new Response(200));
$response = $middleware->process($request, $handler);
$this->assertEquals('https://example.com', $response->getHeaderLine('Access-Control-Allow-Origin'));
}
public function testBlocksUnconfiguredOrigin(): void
{
Config::shouldReceive('get')
->with('cors.allowed_origins')
->andReturn('https://example.com,https://another.com');
$middleware = new CorsMiddleware();
$request = new ServerRequest('GET', '/')->withHeader('Origin', 'https://unauthorized.com');
$handler = $this->mockHandler(new Response(200));
$response = $middleware->process($request, $handler);
$this->assertEmpty($response->getHeaderLine('Access-Control-Allow-Origin'));
}
public function testHandlesOptionsRequest(): void
{
Config::shouldReceive('get')->with('cors.allowed_origins')->andReturn('https://example.com');
Config::shouldReceive('get')->with('cors.allow_credentials')->andReturn(false);
Config::shouldReceive('get')->with('cors.max_age')->andReturn('86400');
$middleware = new CorsMiddleware();
$request = new ServerRequest('OPTIONS', '/')->withHeader('Origin', 'https://example.com');
$handler = $this->mockHandler(new Response(200));
$response = $middleware->process($request, $handler);
$this->assertEquals(204, $response->getStatusCode());
$this->assertEquals('86400', $response->getHeaderLine('Access-Control-Max-Age'));
}
public function testAddsAllowCredentialsHeader(): void
{
Config::shouldReceive('get')
->with('cors.allowed_origins')
->andReturn('https://example.com');
Config::shouldReceive('get')->with('cors.allowed_origins')->andReturn('https://example.com');
Config::shouldReceive('get')->with('cors.allow_credentials')->andReturn(true);
Config::shouldReceive('get')->with('cors.max_age')->andReturn('86400');
$middleware = new CorsMiddleware();
$request = new ServerRequest('GET', '/')->withHeader('Origin', 'https://example.com');
$handler = $this->mockHandler(new Response(200));
$response = $middleware->process($request, $handler);
$this->assertEquals('true', $response->getHeaderLine('Access-Control-Allow-Credentials'));
}
}

View File

@@ -0,0 +1,366 @@
<?php
declare(strict_types=1);
namespace Siteworxpro\Tests\Http\Middleware;
use DateTimeImmutable;
use Lcobucci\JWT\JwtFacade;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Token\Builder;
use League\Route\Dispatcher;
use Nyholm\Psr7\Response;
use Nyholm\Psr7\ServerRequest;
use Siteworxpro\App\Attributes\Guards\Jwt;
use Siteworxpro\App\Http\Middleware\JwtMiddleware;
use Siteworxpro\App\Services\Facades\Config;
use Siteworxpro\App\Services\Facades\Guzzle;
use Siteworxpro\App\Services\Facades\Redis;
use Siteworxpro\HttpStatus\CodesEnum;
class JwtMiddlewareTest extends Middleware
{
private const string TEST_SIGNING_KEY = 'test_signing_key_123456444478901234';
private const string TEST_RSA_PRIVATE_KEY = <<<EOD
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAqTheAdlelxJL0K15BqUEo0lBzY06P7J0PhMfPlg2fgIJH+ng
ZmrpYFhBkj2L5Fnvxz0y58eu9WhhokwpS0GzgFIw+KfLV/WLX4PgionsQshrt0Pi
XvthaSH1xuYtg2N13dVVTv3Au0BBFLUHMrQ+bO5hgvowHBNfFf0GaHLW2m0eZ2Um
hWbtdv4HxrXBO5gI2N4UevyQ+inczN7RBZR6ZzyNoDO6Up6kS23/58zOruO+PGi7
q9eb7hU+getpVgA29wEWMgT+N6c5n5AcENgM1sHxZK43GR5vhMGbVJqnrUsMGof7
rT9Lxey3gjPS2r5nz2PNFcQ1i07QKDzvQHp2wwIDAQABAoIBAFMAC9QaWzP8TGWJ
gNBKhnDU0MrSl5yAmlWMKYn52JiLxQ/7Ng7mJ5wTDe5986zIlDyEfwCCyAUk8qaZ
drOsATBSoCSGoM1+6aKq26r4JYNILNVSHal64XegqZ2qbu6ADWMGbXZ2Ll9qD8Hp
XSN4lxn0/q0wrAJJWh094zO+CDZP+zBbX9oHxb5JAVxjCaNW84sI6/6agXM5zzgK
wcBt5Y0i8V8f7n9kg+CPNqY6BKg7o2ONFYTEVKuuEnVS/eupHQwBWExPCdxc85Tb
YqFL0dmgehE0OTQ6FrEN7Xh6jE4GMJtWmTvBNpqhsMZ0i08tAZSPs+Us9rnppKkK
T1SC2xECgYEA7yOv4C7dtHmFbn0YfnbBEfgvGAubv5jPDtZ5u6tUEhhU3rOcWexM
Xhj7OFV4I8lbu2t7GY+2BR7Y2ikOLW9MrOGo6qWhsjTQuZs6QaRKObcPvl2s0LYY
GxD1u84VjHPzID2pKVPqxaQ7KdcIaujAedWwAf4PV/uK2prKdGvzIksCgYEAtSau
4Ml1UpXvKxiBcVKsHIoEO0g3NL1+wAbdStg8TFi+leCMJoPwZ01t64BTtHF+pgDP
vn6VEgDSP3J4+W3dVhoajQeKBioT3MpDRP/qKDsImi2zJrg+hh9DMTlZd0Ab3EXv
ycjw3FWRcpcU/1l261fA/m3QPwZikF2VlO/0cmkCgYEAvtefCuy718RHHObOPlZt
O/bxNmJFOEEttOyql39iB1LNoDB8bTLruwh6q/lheEXAZDChO8P5gdqdOnUbMF0r
Nqib0i6+fOYzUHw1oJ8I8UhLUyOUv7ciQ69kPC15+u2psCglMKscp/+pi3lk6VS4
DkLfRKfI/PDsXgq72O8xSEMCgYEApukSnvngyQxvR1UYB7N19AHTLlA21bh4LjTk
905QGMR4Lp6sY9yTyIsWabRe69bbK9d5kvsNHX52OpGeF6z8EJaSujklGtLwZDJV
UyE9vn3OSkkrVdTTfz8U6Sj/XxpJ0Wb7LwCftVR+ZIgCh9kF8ohzwbqq8zdN39jq
t0V1BWkCgYEA2Mk2gOdYAN8aZgydFYKhogY5UNK/CFpq7hhekEyt73uxzxguVpZn
AJ9mq2L1CVJ5WqAUk2IzioeR7XAndntesbOafDuR4mhCUJhX+m/YQlKbTrs2dScR
S88z05AnmQmr5eCbQmVULZGo9xeLDB+GDWvvjpQ+NWcha2uO0O0RTQY=
-----END RSA PRIVATE KEY-----
EOD;
private const string TEST_JWKS_JSON = <<<EOD
{
"keys": [
{
"alg": "RS256",
"e": "AQAB",
"ext": true,
"key_ops": [
"verify"
],
"kty": "RSA",
"n": "qTheAdlelxJL0K15BqUEo0lBzY06P7J0PhMfPlg2fgIJH-ngZmrpYFhBkj2L5Fnvxz0y58eu9WhhokwpS0GzgFIw-KfLV_WLX4PgionsQshrt0PiXvthaSH1xuYtg2N13dVVTv3Au0BBFLUHMrQ-bO5hgvowHBNfFf0GaHLW2m0eZ2UmhWbtdv4HxrXBO5gI2N4UevyQ-inczN7RBZR6ZzyNoDO6Up6kS23_58zOruO-PGi7q9eb7hU-getpVgA29wEWMgT-N6c5n5AcENgM1sHxZK43GR5vhMGbVJqnrUsMGof7rT9Lxey3gjPS2r5nz2PNFcQ1i07QKDzvQHp2ww",
"kid": "2o5IaHnjxYtkpNWEcdPlwnaRJnaCJ2k2LY2nR4z6cN4=",
"use": "sig"
}
]
}
EOD;
public function getClass(): object
{
return new class {
public function getCallable(): array
{
return [$this, 'index'];
}
#[Jwt]
public function index()
{
// Dummy method for testing
}
};
}
protected function setUp(): void
{
parent::setUp();
Config::set('jwt.signing_key', self::TEST_SIGNING_KEY);
}
/**
* @throws \JsonException
*/
public function testIgnoresNoJwtAttribute()
{
$class = new class {
public function getCallable(): array
{
return [$this, 'index'];
}
public function index()
{
// Dummy method for testing
}
};
$handler = \Mockery::mock(Dispatcher::class);
$handler->shouldReceive('getMiddlewareStack')
->andReturn([$class]);
$handler
->shouldReceive('handle')
->once()
->andReturn(new Response(200));
$request = new ServerRequest('GET', '/');
$middleware = new JwtMiddleware();
$response = $middleware->process($request, $handler);
$this->assertEquals(CodesEnum::OK->value, $response->getStatusCode());
}
/**
* @throws \JsonException
*/
public function testIgnoresJwtAttributeButNoToken()
{
$class = $this->getClass();
$handler = \Mockery::mock(Dispatcher::class);
$handler->shouldReceive('getMiddlewareStack')
->andReturn([$class]);
$request = new ServerRequest('GET', '/');
$middleware = new JwtMiddleware();
$response = $middleware->process($request, $handler);
$this->assertEquals(CodesEnum::UNAUTHORIZED->value, $response->getStatusCode());
}
/**
* @throws \JsonException
*/
public function testInvalidToken()
{
$class = $this->getClass();
$handler = \Mockery::mock(Dispatcher::class);
$handler->shouldReceive('getMiddlewareStack')
->andReturn([$class]);
$request = new ServerRequest('GET', '/');
$request = $request->withHeader('Authorization', 'Bearer ' . 'invalid_token_string');
$middleware = new JwtMiddleware();
$response = $middleware->process($request, $handler);
$this->assertEquals(CodesEnum::UNAUTHORIZED->value, $response->getStatusCode());
$this->assertStringContainsString(
'Unauthorized: Invalid token',
$response->getBody()->getContents()
);
}
/**
* @throws \JsonException
*/
public function testJwtAttributeWithTokenButWrongAud()
{
$class = $this->getClass();
$handler = \Mockery::mock(Dispatcher::class);
$handler->shouldReceive('getMiddlewareStack')
->andReturn([$class]);
$request = new ServerRequest('GET', '/');
$request = $request->withHeader('Authorization', 'Bearer ' . $this->getJwt());
$middleware = new JwtMiddleware();
$response = $middleware->process($request, $handler);
$this->assertEquals(CodesEnum::UNAUTHORIZED->value, $response->getStatusCode());
$this->assertStringContainsString(
'The token is not allowed to be used by this audience',
$response->getBody()->getContents()
);
}
/**
* @throws \JsonException
*/
public function testJwtAttributeWithTokenButWrongIss()
{
Config::set('jwt.audience', 'https://client-app.io');
$class = $this->getClass();
$handler = \Mockery::mock(Dispatcher::class);
$handler->shouldReceive('getMiddlewareStack')
->andReturn([$class]);
$request = new ServerRequest('GET', '/');
$request = $request->withHeader('Authorization', 'Bearer ' . $this->getJwt());
$middleware = new JwtMiddleware();
$response = $middleware->process($request, $handler);
$this->assertEquals(CodesEnum::UNAUTHORIZED->value, $response->getStatusCode());
$this->assertStringContainsString(
'The token was not issued by the given issuers',
$response->getBody()->getContents()
);
}
/**
* @throws \JsonException
*/
public function testJwtAttributeWithTokenWithDiffIssuer()
{
Config::set('jwt.audience', 'https://client-app.io');
Config::set('jwt.issuer', 'https://different-issuer.io');
$class = $this->getClass();
$handler = \Mockery::mock(Dispatcher::class);
$handler->shouldReceive('getMiddlewareStack')
->andReturn([$class]);
$request = new ServerRequest('GET', '/');
$request = $request->withHeader('Authorization', 'Bearer ' . $this->getJwt());
$middleware = new JwtMiddleware();
$response = $middleware->process($request, $handler);
$this->assertEquals(CodesEnum::UNAUTHORIZED->value, $response->getStatusCode());
$this->assertStringContainsString(
'The token was not issued by the given issuers',
$response->getBody()->getContents()
);
}
public function testJwtAttributeWithToken()
{
Config::set('jwt.audience', 'https://client-app.io');
Config::set('jwt.issuer', 'https://api.my-awesome-app.io');
$class = $this->getClass();
$handler = \Mockery::mock(Dispatcher::class);
$handler->shouldReceive('getMiddlewareStack')
->andReturn([$class]);
$handler
->shouldReceive('handle')
->once()
->andReturn(new Response(200));
$request = new ServerRequest('GET', '/');
$request = $request->withHeader('Authorization', 'Bearer ' . $this->getJwt());
$middleware = new JwtMiddleware();
$response = $middleware->process($request, $handler);
$this->assertEquals(CodesEnum::OK->value, $response->getStatusCode());
}
/**
* @throws \JsonException
*/
public function testJwtFromJwkEndpoint()
{
Config::set('jwt.audience', 'https://client-app.io');
Config::set('jwt.issuer', 'https://api.my-awesome-app.io');
Redis::partialMock()->shouldReceive('get')->andReturn(null);
Redis::shouldReceive('set')->andReturn('OK');
Guzzle::partialMock()->shouldReceive('get')
->with('https://test.com/.well-known/openid-configuration')
->andReturn(new Response(200, [], json_encode([
'jwks_uri' => 'https://test.com/keys'
], JSON_THROW_ON_ERROR)));
Guzzle::shouldReceive('get')
->with('https://test.com/keys')
->andReturn(new Response(200, [], self::TEST_JWKS_JSON));
Config::set('jwt.signing_key', 'https://test.com/.well-known/openid-configuration');
$class = $this->getClass();
$handler = \Mockery::mock(Dispatcher::class);
$handler->shouldReceive('getMiddlewareStack')
->andReturn([$class]);
$handler
->shouldReceive('handle')
->once()
->andReturn(new Response(200));
$request = new ServerRequest('GET', '/');
$request = $request->withHeader('Authorization', 'Bearer ' . $this->getJwtRsa());
$middleware = new JwtMiddleware();
$response = $middleware->process($request, $handler);
$this->assertEquals(CodesEnum::OK->value, $response->getStatusCode());
}
/**
* @throws \JsonException
*/
public function testCatchesInvalidJwksUrl()
{
Config::set('jwt.signing_key', 'https://test.com/.well-known/openid-configuration');
Redis::partialMock()->shouldReceive('get')->andReturn(null);
Redis::shouldReceive('set')->andReturn('OK');
Guzzle::partialMock()->shouldReceive('get')
->with('https://test.com/.well-known/openid-configuration')
->andReturn(new Response(200, [], json_encode([], JSON_THROW_ON_ERROR)));
$class = $this->getClass();
$handler = \Mockery::mock(Dispatcher::class);
$handler->shouldReceive('getMiddlewareStack')
->andReturn([$class]);
$request = new ServerRequest('GET', '/');
$request = $request->withHeader('Authorization', 'Bearer ' . $this->getJwtRsa());
$middleware = new JwtMiddleware();
$response = $middleware->process($request, $handler);
$this->assertEquals(CodesEnum::INTERNAL_SERVER_ERROR->value, $response->getStatusCode());
}
private function getJwtRsa(): string
{
$key = InMemory::plainText(self::TEST_RSA_PRIVATE_KEY);
$signer = new \Lcobucci\JWT\Signer\Rsa\Sha256();
$token = new JwtFacade()->issue(
$signer,
$key,
static fn(
Builder $builder,
DateTimeImmutable $issuedAt
): Builder => $builder
->issuedBy('https://api.my-awesome-app.io')
->permittedFor('https://client-app.io')
->expiresAt($issuedAt->modify('+10 minutes'))
);
return $token->toString();
}
private function getJwt(): string
{
$key = InMemory::plainText(self::TEST_SIGNING_KEY);
$signer = new Sha256();
$token = new JwtFacade()->issue(
$signer,
$key,
static fn(
Builder $builder,
DateTimeImmutable $issuedAt
): Builder => $builder
->issuedBy('https://api.my-awesome-app.io')
->permittedFor('https://client-app.io')
->expiresAt($issuedAt->modify('+10 minutes'))
);
return $token->toString();
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Siteworxpro\Tests\Http\Middleware;
use Nyholm\Psr7\Response;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Siteworxpro\Tests\Unit;
abstract class Middleware extends Unit
{
protected function mockHandler(Response $response): RequestHandlerInterface
{
return new class ($response) implements RequestHandlerInterface {
private Response $response;
public function __construct(Response $response)
{
$this->response = $response;
}
public function handle(
ServerRequestInterface $request
): ResponseInterface {
return $this->response;
}
};
}
}

View File

@@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
namespace Siteworxpro\Tests\Http\Middleware;
use League\Route\Dispatcher;
use Nyholm\Psr7\Response;
use Nyholm\Psr7\ServerRequest;
use Siteworxpro\App\Attributes\Guards\Scope;
use Siteworxpro\App\Http\Middleware\ScopeMiddleware;
use Siteworxpro\HttpStatus\CodesEnum;
class ScopeMiddlewareTest extends Middleware
{
/**
* @throws \ReflectionException
* @throws \JsonException
*/
public function testHandlesNoScopes()
{
$class = new class {
public function getCallable(): array
{
return [ $this, 'index' ];
}
public function index()
{
// Dummy method for testing
}
};
$handler = \Mockery::mock(Dispatcher::class);
$handler->shouldReceive('getMiddlewareStack')
->andReturn([$class]);
$handler
->shouldReceive('handle')
->once()
->andReturn(new Response(200));
$request = new ServerRequest('GET', '/');
$middleware = new ScopeMiddleware();
$response = $middleware->process($request, $handler);
$this->assertEquals(200, $response->getStatusCode());
}
/**
* @throws \ReflectionException
* @throws \JsonException
*/
public function testAllowsWithScope()
{
$class = new class {
public function getCallable(): array
{
return [ $this, 'index' ];
}
#[Scope(['admin'])]
public function index()
{
// Dummy method for testing
}
};
$handler = \Mockery::mock(Dispatcher::class);
$handler->shouldReceive('getMiddlewareStack')
->andReturn([$class]);
$handler
->shouldReceive('handle')
->once()
->andReturn(new Response(200));
$request = new ServerRequest('GET', '/')->withAttribute('scope', ['admin', 'user']);
$middleware = new ScopeMiddleware();
$response = $middleware->process($request, $handler);
$this->assertEquals(CodesEnum::OK->value, $response->getStatusCode());
}
/**
* @throws \ReflectionException
* @throws \JsonException
*/
public function testDisallowsWithScope()
{
$class = new class {
public function getCallable(): array
{
return [ $this, 'index' ];
}
#[Scope(['admin'])]
public function index()
{
// Dummy method for testing
}
};
$handler = \Mockery::mock(Dispatcher::class);
$handler->shouldReceive('getMiddlewareStack')
->andReturn([$class]);
$request = new ServerRequest('GET', '/');
$middleware = new ScopeMiddleware();
$response = $middleware->process($request, $handler);
$this->assertEquals(CodesEnum::FORBIDDEN->value, $response->getStatusCode());
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Siteworxpro\Tests\Http\Responses;
use Siteworxpro\App\Http\Responses\NotFoundResponse;
use Siteworxpro\Tests\Unit;
class NotFoundResponseTest extends Unit
{
public function testToArray(): void
{
$response = new NotFoundResponse('/api/resource', ['key' => 'value']);
$expected = [
'message' => 'The requested resource /api/resource was not found.',
'context' => ['key' => 'value'],
];
$this->assertEquals($expected, $response->toArray());
}
}

View File

@@ -0,0 +1,93 @@
<?php
namespace Siteworxpro\Tests\Http\Responses;
use Siteworxpro\App\Http\Responses\ServerErrorResponse;
use Siteworxpro\App\Services\Facades\Config;
use Siteworxpro\Tests\Unit;
class ServerErrorResponseTest extends Unit
{
public function testToArrayInDevMode(): void
{
Config::set('app.dev_mode', true);
try {
// Simulate an exception to generate a server error response
throw new \Exception('A Test Error occurred.');
} catch (\Exception $e) {
$response = new ServerErrorResponse($e, ['operation' => 'data_processing']);
$expected = [
'code' => 500,
'message' => 'A Test Error occurred.',
'context' => [
'operation' => 'data_processing'
],
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTrace(),
];
$this->assertEquals($expected, $response->toArray());
}
}
public function testToArrayNotInDevMode(): void
{
Config::set('app.dev_mode', false);
try {
throw new \Exception('A Test Error occurred.');
} catch (\Exception $exception) {
$response = new ServerErrorResponse($exception);
$expected = [
'code' => 500,
'message' => 'An internal server error occurred.',
];
$this->assertEquals($expected, $response->toArray());
}
}
public function testToArrayIfCodeIsSet(): void
{
Config::set('app.dev_mode', false);
try {
throw new \Exception('A Test Error occurred.', 1234);
} catch (\Exception $exception) {
$response = new ServerErrorResponse($exception);
$expected = [
'code' => 1234,
'message' => 'An internal server error occurred.',
];
$this->assertEquals($expected, $response->toArray());
}
}
public function testToArrayIfCodeIsSetDevMode(): void
{
Config::set('app.dev_mode', true);
try {
throw new \Exception('A Test Error occurred.', 1234);
} catch (\Exception $exception) {
$response = new ServerErrorResponse($exception);
$expected = [
'code' => 1234,
'message' => 'A Test Error occurred.',
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'trace' => $exception->getTrace(),
'context' => [],
];
$this->assertEquals($expected, $response->toArray());
}
}
}