Skip to content

Commit

Permalink
test: add mapping errors check method for consistency between tests
Browse files Browse the repository at this point in the history
  • Loading branch information
romm committed Dec 27, 2024
1 parent 5646918 commit 9e04361
Show file tree
Hide file tree
Showing 24 changed files with 264 additions and 272 deletions.
33 changes: 33 additions & 0 deletions tests/Integration/IntegrationTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@

use CuyZ\Valinor\Cache\FileSystemCache;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\Mapper\Tree\Message\Messages;
use CuyZ\Valinor\Mapper\Tree\Node;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\Mapping\Namespace\NamespacedInterfaceInferringTest;
use PHPUnit\Framework\TestCase;
use Psr\SimpleCache\CacheInterface;

use function array_keys;
use function bin2hex;
use function implode;
use function iterator_to_array;
Expand Down Expand Up @@ -65,6 +67,37 @@ protected function mapperBuilder(): MapperBuilder
return $builder;
}

/**
* @param non-empty-array<non-empty-string> $expected
*/
protected function assertMappingErrors(MappingError $error, array $expected): void
{
$errors = [];

foreach (Messages::flattenFromNode($error->node()) as $message) {
$errors[$message->node()->path()] = $message;
}

$remainingErrors = $errors;

foreach ($expected as $path => $message) {
self::assertArrayHasKey($path, $remainingErrors, "Error path `$path` not found in error messages, the following path(s) were found: `" . implode('`, `', array_keys($errors)) . '`.');

if (! preg_match('/^\[([^]]+)] (.*)/', $message, $matches)) {
self::fail('Incorrect error message format. Expected format: `[code] message`.');
}

self::assertSame($matches[1], $remainingErrors[$path]->code());
self::assertSame($matches[2], $remainingErrors[$path]->toString());

unset($remainingErrors[$path]);
}

if ($remainingErrors !== []) {
self::fail('Untested error messages at path(s): `' . implode('`, `', array_keys($remainingErrors)) . '`');
}
}

protected function mappingFail(MappingError $error): never
{
$errorFinder = static function (Node $node, callable $errorFinder) {
Expand Down
10 changes: 7 additions & 3 deletions tests/Integration/Mapping/Closure/ArgumentsMappingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,10 @@ public function test_invalid_source_with_one_error_throws_mapping_error(): void
} catch (MappingError $exception) {
self::assertMatchesRegularExpression('/Could not map arguments of `[^`]+` with value array{foo: false, bar: false}. A total of 2 errors were encountered./', $exception->getMessage());

self::assertSame('Value false is not a valid string.', (string)$exception->node()->children()['foo']->messages()[0]);
self::assertSame('Value false is not a valid integer.', (string)$exception->node()->children()['bar']->messages()[0]);
self::assertMappingErrors($exception, [
'foo' => '[unknown] Value false is not a valid string.',
'bar' => '[unknown] Value false is not a valid integer.',
]);
}
}

Expand All @@ -97,7 +99,9 @@ public function test_invalid_source_with_two_errors_throws_mapping_error(): void
} catch (MappingError $exception) {
self::assertMatchesRegularExpression('/Could not map arguments of `[^`]+`. An error occurred at path foo: Value false is not a valid string./', $exception->getMessage());

self::assertSame('Value false is not a valid string.', (string)$exception->node()->children()['foo']->messages()[0]);
self::assertMappingErrors($exception, [
'foo' => '[unknown] Value false is not a valid string.',
]);
}
}
}
Expand Down
14 changes: 6 additions & 8 deletions tests/Integration/Mapping/ConstructorRegistrationMappingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -673,10 +673,9 @@ public function test_source_not_matching_registered_constructors_throws_exceptio
->mapper()
->map(stdClass::class, []);
} catch (MappingError $exception) {
$error = $exception->node()->messages()[0];

self::assertSame('1642183169', $error->code());
self::assertSame('Value array (empty) does not match any of `string`, `array{bar: int, baz?: float}`.', (string)$error);
self::assertMappingErrors($exception, [
'*root*' => "[1642183169] Value array (empty) does not match any of `string`, `array{bar: int, baz?: float}`.",
]);
}
}

Expand Down Expand Up @@ -813,10 +812,9 @@ public function test_registered_constructor_throwing_exception_fails_mapping_wit

self::fail('No mapping error when one was expected');
} catch (MappingError $exception) {
$error = $exception->node()->messages()[0];

self::assertSame('1656076090', $error->code());
self::assertSame('some error message', (string)$error);
self::assertMappingErrors($exception, [
'*root*' => "[1656076090] some error message",
]);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,9 @@ public function test_register_internal_from_constructor_is_overridden_by_library
->mapper()
->map(BackedStringEnum::class, 'fiz');
} catch (MappingError $exception) {
$error = $exception->node()->messages()[0];

self::assertSame('1607027306', $error->code());
self::assertSame("Value 'fiz' does not match any of 'foo', 'bar', 'baz'.", (string)$error);
self::assertMappingErrors($exception, [
'*root*' => "[1607027306] Value 'fiz' does not match any of 'foo', 'bar', 'baz'.",
]);
}
}

Expand All @@ -137,10 +136,9 @@ public function test_register_internal_try_from_constructor_is_overridden_by_lib
->mapper()
->map(BackedStringEnum::class, 'fiz');
} catch (MappingError $exception) {
$error = $exception->node()->messages()[0];

self::assertSame('1607027306', $error->code());
self::assertSame("Value 'fiz' does not match any of 'foo', 'bar', 'baz'.", (string)$error);
self::assertMappingErrors($exception, [
'*root*' => "[1607027306] Value 'fiz' does not match any of 'foo', 'bar', 'baz'.",
]);
}
}
}
5 changes: 3 additions & 2 deletions tests/Integration/Mapping/EnumValueOfMappingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,9 @@ public function test_array_keys_using_value_of_error(): void
->mapper()
->map('array<value-of<' . SomeStringEnumForValueOf::class . '>, string>', ['oof' => 'foo']);
} catch (MappingError $exception) {
$error = $exception->node()->children()['oof']->messages()[0];
self::assertSame("Key 'oof' does not match type `'FOO'|'FOZ'|'BAZ'`.", (string)$error);
self::assertMappingErrors($exception, [
'oof' => "[1630946163] Key 'oof' does not match type `'FOO'|'FOZ'|'BAZ'`.",
]);
}
}
}
Expand Down
7 changes: 3 additions & 4 deletions tests/Integration/Mapping/ExceptionFilteringTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,9 @@ public function test_userland_exception_filtered_is_caught_and_added_to_mapping_
->mapper()
->map(ClassThatThrowsExceptionIfInvalidValue::class, 'bar');
} catch (MappingError $exception) {
$error = $exception->node()->messages()[0];

self::assertSame('1657197780', $error->code());
self::assertSame('some error message', (string)$error);
self::assertMappingErrors($exception, [
'*root*' => "[1657197780] some error message",
]);
}
}
}
Expand Down
20 changes: 9 additions & 11 deletions tests/Integration/Mapping/InterfaceInferringMappingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -351,10 +351,9 @@ public function test_invalid_source_throws_exception(): void
->mapper()
->map(SomeInterface::class, 42);
} catch (MappingError $exception) {
$error = $exception->node()->messages()[0];

self::assertSame('1632903281', $error->code());
self::assertSame('Value 42 does not match type `array{type: string, key: int}`.', (string)$error);
self::assertMappingErrors($exception, [
'*root*' => "[1632903281] Value 42 does not match type `array{type: string, key: int}`.",
]);
}
}

Expand All @@ -370,9 +369,9 @@ public function test_invalid_source_value_throws_exception(): void
->mapper()
->map(SomeInterface::class, 'foo');
} catch (MappingError $exception) {
$error = $exception->node()->children()['key']->messages()[0];

self::assertSame("Value 'foo' is not a valid integer.", (string)$error);
self::assertMappingErrors($exception, [
'key' => "[unknown] Value 'foo' is not a valid integer.",
]);
}
}

Expand All @@ -391,10 +390,9 @@ public function test_superfluous_values_throws_exception(): void
'superfluousValue' => 'bar',
]);
} catch (MappingError $exception) {
$error = $exception->node()->messages()[0];

self::assertSame('1655117782', $error->code());
self::assertSame('Unexpected key(s) `superfluousValue`, expected `valueA`.', (string)$error);
self::assertMappingErrors($exception, [
'*root*' => "[1655117782] Unexpected key(s) `superfluousValue`, expected `valueA`.",
]);
}
}
}
Expand Down
21 changes: 8 additions & 13 deletions tests/Integration/Mapping/Object/ArrayValuesMappingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,27 +69,22 @@ public function test_values_are_mapped_properly(): void
public function test_empty_array_in_non_empty_array_throws_exception(): void
{
try {
$this->mapperBuilder()->mapper()->map(ArrayValues::class, [
'nonEmptyArraysOfStrings' => [],
]);
$this->mapperBuilder()->mapper()->map('non-empty-array<string>', []);
} catch (MappingError $exception) {
$error = $exception->node()->children()['nonEmptyArraysOfStrings']->messages()[0];

self::assertSame('1630678334', $error->code());
self::assertSame('Value array (empty) does not match type `non-empty-array<string>`.', (string)$error);
self::assertMappingErrors($exception, [
'*root*' => '[1630678334] Value array (empty) does not match type `non-empty-array<string>`.',
]);
}
}

public function test_value_with_invalid_type_throws_exception(): void
{
try {
$this->mapperBuilder()->mapper()->map(ArrayValues::class, [
'integers' => ['foo'],
]);
$this->mapperBuilder()->mapper()->map('array<int>', ['foo']);
} catch (MappingError $exception) {
$error = $exception->node()->children()['integers']->children()[0]->messages()[0];

self::assertSame("Value 'foo' is not a valid integer.", (string)$error);
self::assertMappingErrors($exception, [
'0' => "[unknown] Value 'foo' is not a valid integer.",
]);
}
}
}
Expand Down
14 changes: 6 additions & 8 deletions tests/Integration/Mapping/Object/ConstantValuesMappingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,9 @@ public function test_private_constant_cannot_be_mapped(): void
->mapper()
->map(ObjectWithConstants::class . '::CONST_WITH_STRING_*', 'some private string value');
} catch (MappingError $exception) {
$error = $exception->node()->messages()[0];

self::assertSame('1607027306', $error->code());
self::assertSame("Value 'some private string value' does not match any of 'some string value', 'another string value'.", (string)$error);
self::assertMappingErrors($exception, [
'*root*' => "[1607027306] Value 'some private string value' does not match any of 'some string value', 'another string value'.",
]);
}
}

Expand All @@ -84,10 +83,9 @@ public function test_constant_not_matching_pattern_cannot_be_mapped(): void
->mapper()
->map(ObjectWithConstants::class . '::CONST_WITH_STRING_*', 'some prefixed string value');
} catch (MappingError $exception) {
$error = $exception->node()->messages()[0];

self::assertSame('1607027306', $error->code());
self::assertSame("Value 'some prefixed string value' does not match any of 'some string value', 'another string value'.", (string)$error);
self::assertMappingErrors($exception, [
'*root*' => "[1607027306] Value 'some prefixed string value' does not match any of 'some string value', 'another string value'.",
]);
}
}
}
Expand Down
27 changes: 12 additions & 15 deletions tests/Integration/Mapping/Object/DateTimeMappingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ public function test_default_datetime_constructor_cannot_be_used(): void
->mapper()
->map(DateTimeInterface::class, ['datetime' => '2022/08/05', 'timezone' => 'Europe/Paris']);
} catch (MappingError $exception) {
$error = $exception->node()->messages()[0];

self::assertSame('1607027306', $error->code());
self::assertMappingErrors($exception, [
'*root*' => "[1607027306] Value array{datetime: '2022/08/05', timezone: 'Europe/Paris'} does not match any of `non-empty-string`, `int`, `float`.",
]);
}
}

Expand Down Expand Up @@ -139,10 +139,9 @@ public function test_default_date_constructor_with_invalid_source_throws_excepti
->mapper()
->map(DateTimeInterface::class, 'invalid datetime');
} catch (MappingError $exception) {
$error = $exception->node()->messages()[0];

self::assertSame('1630686564', $error->code());
self::assertSame("Value 'invalid datetime' does not match any of the following formats: `Y-m-d\TH:i:sP`, `Y-m-d\TH:i:s.uP`, `U`, `U.u`.", (string)$error);
self::assertMappingErrors($exception, [
'*root*' => "[1630686564] Value 'invalid datetime' does not match any of the following formats: `Y-m-d\TH:i:sP`, `Y-m-d\TH:i:s.uP`, `U`, `U.u`.",
]);
}
}

Expand All @@ -154,10 +153,9 @@ public function test_registered_date_constructor_with_invalid_source_throws_exce
->mapper()
->map(DateTimeInterface::class, 'invalid datetime');
} catch (MappingError $exception) {
$error = $exception->node()->messages()[0];

self::assertSame('1630686564', $error->code());
self::assertSame("Value 'invalid datetime' does not match any of the following formats: `Y/m/d`.", (string)$error);
self::assertMappingErrors($exception, [
'*root*' => "[1630686564] Value 'invalid datetime' does not match any of the following formats: `Y/m/d`.",
]);
}
}

Expand All @@ -170,10 +168,9 @@ public function test_date_constructor_with_overridden_format_source_throws_excep
->mapper()
->map(DateTimeInterface::class, '1971-11-08');
} catch (MappingError $exception) {
$error = $exception->node()->messages()[0];

self::assertSame('1630686564', $error->code());
self::assertSame("Value '1971-11-08' does not match any of the following formats: `d/m/Y`.", (string)$error);
self::assertMappingErrors($exception, [
'*root*' => "[1630686564] Value '1971-11-08' does not match any of the following formats: `d/m/Y`.",
]);
}
}
}
6 changes: 3 additions & 3 deletions tests/Integration/Mapping/Object/DateTimeZoneMappingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ public function test_invalid_timezone_throws_exception(): void
try {
$this->mapperBuilder()->mapper()->map(DateTimeZone::class, 'Jupiter/Europa');
} catch (MappingError $exception) {
$error = $exception->node()->messages()[0];

self::assertSame("Value 'Jupiter/Europa' is not a valid timezone.", $error->toString());
self::assertMappingErrors($exception, [
'*root*' => "[unknown] Value 'Jupiter/Europa' is not a valid timezone.",
]);
}
}
}
Loading

0 comments on commit 9e04361

Please sign in to comment.