Skip to content

Commit

Permalink
Test everything
Browse files Browse the repository at this point in the history
  • Loading branch information
dakujem committed Oct 16, 2023
1 parent 512e8ed commit 2360ab2
Show file tree
Hide file tree
Showing 7 changed files with 676 additions and 0 deletions.
16 changes: 16 additions & 0 deletions tests/bootstrap.php
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Dakujem\Test;

define('ROOT', __DIR__);

require_once __DIR__ . '/../vendor/autoload.php';

use Tester\Environment;

// tester
Environment::setup();


133 changes: 133 additions & 0 deletions tests/custom.phpt
@@ -0,0 +1,133 @@
<?php

declare(strict_types=1);

namespace Dakujem\Test;

require_once __DIR__ . '/bootstrap.php';
require_once __DIR__ . '/tests.php';

use Dakujem\Strata\Contracts\IndicatesClientFault;
use Dakujem\Strata\Contracts\IndicatesServerFault;
use Dakujem\Strata\Contracts\IndicatesThirdPartyFault;
use Dakujem\Strata\Http\HttpClientContextStrata;
use Dakujem\Strata\Http\HttpServerContextStrata;
use Dakujem\Strata\Support\SuggestsErrorMessage;
use Dakujem\Strata\Support\SuggestsHttpStatus;
use Dakujem\Strata\Support\SupportsContextStrata;
use Throwable;


function testCustomException(
Throwable $instance,
array $interfaces,
int $httpStatusCode,
string $httpStatusMessage,
): void {
testCompatibility($instance);
testInterfaces(
$instance,
SupportsContextStrata::class,
SuggestsHttpStatus::class,
SuggestsErrorMessage::class,
...$interfaces,
);

// internals
testExplanation($instance);
testInternalContext($instance);
testTagging($instance);

// conveying
testPublicContext($instance);

// http status/message
testHttpStatusCodeSuggestion($instance, $httpStatusCode);
testErrorMessageSuggestion($instance, $httpStatusMessage);
}

class ServerFault extends \Exception implements
SuggestsErrorMessage,
SuggestsHttpStatus,
SupportsContextStrata,
IndicatesServerFault
{
use HttpServerContextStrata;
}

class ThirdPartyFault extends \Exception implements
SuggestsErrorMessage,
SuggestsHttpStatus,
SupportsContextStrata,
IndicatesThirdPartyFault
{
use HttpServerContextStrata;
}

class DefaultClientFault extends \Exception implements
SuggestsErrorMessage,
SuggestsHttpStatus,
SupportsContextStrata,
IndicatesClientFault
{
use HttpClientContextStrata;
}

class GenericClientFault extends \Exception implements
SuggestsErrorMessage,
SuggestsHttpStatus,
SupportsContextStrata,
IndicatesClientFault
{
use HttpClientContextStrata;

public function getDefaultMessageToConvey(): string
{
// When no explicit message is passed to the convey method,
// the internal exception message is conveyed (potentially EXPOSED to the client).
return $this->getMessage();
}
}

testCustomException(
instance: new ServerFault(),
interfaces: [IndicatesServerFault::class],
httpStatusCode: 500,
httpStatusMessage: 'Internal server error',
);

testCustomException(
instance: new DefaultClientFault(),
interfaces: [IndicatesClientFault::class],
httpStatusCode: 400,
httpStatusMessage: 'Bad request',
);

testCustomException(
instance: new ThirdPartyFault(),
interfaces: [IndicatesThirdPartyFault::class],
httpStatusCode: 500,
httpStatusMessage: 'Internal server error',
);


testPublicConveying(
new ServerFault('Whatever happens, we do not want the clients see this message.'),
fn(): string => 'A system error has occurred. We are sorry for the inconvenience.',
);
testPublicConveying(
new ThirdPartyFault('Whatever happens, we do not want the clients see this message.'),
fn(): string => 'A system error has occurred. We are sorry for the inconvenience.',
);

// Note: The only way to make `convey` method expose the internal exception message is via overriding the `getDefaultMessageToConvey` method by the exception.
testPublicConveying(
new DefaultClientFault('Whatever happens, we do not want the clients see this message.'),
fn(): string => 'A system error has occurred. We are sorry for the inconvenience.',
);

// Note: Conveying the internal message by overriding the `getDefaultMessageToConvey` method.
testPublicConveying(
new GenericClientFault('Yeah, this should be the message for the client.'),
fn(): string => 'Yeah, this should be the message for the client.',
);
53 changes: 53 additions & 0 deletions tests/errorContainer.phpt
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

namespace Dakujem\Test;

require_once __DIR__ . '/bootstrap.php';
require_once __DIR__ . '/tests.php';

use Dakujem\Strata\Support\ErrorContainer;
use ReflectionClass;
use Tester\Assert;


// not much to test here, but we'll do that anyway
$ec = new ErrorContainer(
message: 'An important message.',
source: 'input.email',
detail: 'A description for humans.',
meta: ['any' => 'metadata'],
status: 400,
code: 'crashed...somehow',
);

Assert::same('An important message.', $ec->message);
Assert::same('input.email', $ec->source);
Assert::same('A description for humans.', $ec->detail);
Assert::same(['any' => 'metadata'], $ec->meta);
Assert::same(400, $ec->status);
Assert::same('crashed...somehow', $ec->code);

Assert::same([
'message' => 'An important message.',
'detail' => 'A description for humans.',
'source' => 'input.email',
'code' => 'crashed...somehow',
'status' => 400,
'meta' => ['any' => 'metadata'],
], $ec->jsonSerialize());

// test that all public props are present in json
$rf = new ReflectionClass(ErrorContainer::class);
Assert::same(count($rf->getProperties()), count($ec->jsonSerialize()));

// test that null values are omitted when serializing
$ec2 = new ErrorContainer('only the message is set');
Assert::same(1, count($ec2->jsonSerialize()));
$ec3 = new ErrorContainer(meta: null);
Assert::same([], $ec3->jsonSerialize());

// but string (even empty) are not
$ec4 = new ErrorContainer(message: '', detail: '', code: null);
Assert::same(2, count($ec4->jsonSerialize()));
137 changes: 137 additions & 0 deletions tests/http.phpt
@@ -0,0 +1,137 @@
<?php

declare(strict_types=1);

namespace Dakujem\Test;

require_once __DIR__ . '/bootstrap.php';
require_once __DIR__ . '/tests.php';

use Dakujem\Strata\Contracts\IndicatesAuthenticationFault;
use Dakujem\Strata\Contracts\IndicatesAuthorizationFault;
use Dakujem\Strata\Contracts\IndicatesClientFault;
use Dakujem\Strata\Contracts\IndicatesConflict;
use Dakujem\Strata\Contracts\IndicatesGoodMood;
use Dakujem\Strata\Contracts\IndicatesInvalidInput;
use Dakujem\Strata\Contracts\IndicatesMissingResource;
use Dakujem\Strata\Http\BadRequest;
use Dakujem\Strata\Http\Conflict;
use Dakujem\Strata\Http\Forbidden;
use Dakujem\Strata\Http\GenericClientHttpException;
use Dakujem\Strata\Http\ImATeapot;
use Dakujem\Strata\Http\NotFound;
use Dakujem\Strata\Http\Unauthorized;
use Dakujem\Strata\Http\UnprocessableContent;
use Dakujem\Strata\Support\SuggestsErrorMessage;
use Dakujem\Strata\Support\SuggestsHttpStatus;
use Dakujem\Strata\Support\SupportsContextStrata;
use Throwable;


function testHttpException(
Throwable $instance,
array $interfaces,
int $httpStatusCode,
string $httpStatusMessage,
?callable $defaultConveyMessageGetter = null,
): void {
testCompatibility($instance);

testInterfaces(
$instance,
SupportsContextStrata::class,
SuggestsHttpStatus::class,
SuggestsErrorMessage::class,
IndicatesClientFault::class,
...$interfaces,
);

// internals
testExplanation($instance);
testInternalContext($instance);
testTagging($instance);

// conveying
testPublicContext($instance);
testPublicConveying(
$instance,
$defaultConveyMessageGetter ?? fn(Throwable $e): string => $e->getMessage(),
);

// http status/message
testHttpStatusCodeSuggestion($instance, $httpStatusCode);
testErrorMessageSuggestion($instance, $httpStatusMessage);
}

// Here we test the HTTP exceptions:
testHttpException(
instance: new BadRequest(),
interfaces: [],
httpStatusCode: 400,
httpStatusMessage: 'Bad request',
);

testHttpException(
instance: new Unauthorized(),
interfaces: [IndicatesAuthenticationFault::class],
httpStatusCode: 401,
httpStatusMessage: 'Unauthorized',
);

testHttpException(
instance: new Forbidden(),
interfaces: [IndicatesAuthorizationFault::class],
httpStatusCode: 403,
httpStatusMessage: 'Forbidden',
);

testHttpException(
instance: new NotFound(),
interfaces: [IndicatesMissingResource::class],
httpStatusCode: 404,
httpStatusMessage: 'Not found',
);

testHttpException(
instance: new Conflict(),
interfaces: [IndicatesConflict::class],
httpStatusCode: 409,
httpStatusMessage: 'Conflict',
);

testHttpException(
instance: new UnprocessableContent(),
interfaces: [IndicatesInvalidInput::class],
httpStatusCode: 422,
httpStatusMessage: 'Unprocessable content',
);

testHttpException(
instance: new ImATeapot(),
interfaces: [IndicatesGoodMood::class],
httpStatusCode: 418,
httpStatusMessage: 'I\'m a teapot',
);


testHttpException(
instance: new GenericClientHttpException(),
interfaces: [],
httpStatusCode: 400,
httpStatusMessage: 'Bad request',
);
testHttpException(
instance: new GenericClientHttpException('Oops!', 499),
interfaces: [],
httpStatusCode: 400,
httpStatusMessage: 'Bad request',
defaultConveyMessageGetter: fn() => 'Oops!',
);
testHttpException(
instance: (new GenericClientHttpException('Oops!'))->setHttpStatus(499),
interfaces: [],
httpStatusCode: 499,
httpStatusMessage: 'Bad request',
defaultConveyMessageGetter: fn() => 'Oops!',
);

0 comments on commit 2360ab2

Please sign in to comment.