diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/ValueObject/Point.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/ValueObject/Point.php index 05ab8a17..91739bfa 100644 --- a/src/MartinGeorgiev/Doctrine/DBAL/Types/ValueObject/Point.php +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/ValueObject/Point.php @@ -11,7 +11,9 @@ */ final class Point implements \Stringable { - private const POINT_REGEX = '/\((-?\d+(?:\.\d{1,6})?),\s*(-?\d+(?:\.\d{1,6})?)\)/'; + private const COORDINATE_PATTERN = '-?\d+(?:\.\d{1,6})?'; + + private const POINT_REGEX = '/\(('.self::COORDINATE_PATTERN.'),\s*('.self::COORDINATE_PATTERN.')\)/'; public function __construct( private readonly float $x, @@ -51,7 +53,8 @@ private function validateCoordinate(float $value, string $name): void { $stringValue = (string) $value; - if (!\preg_match('/^-?\d+(\.\d{1,6})?$/', $stringValue)) { + $floatRegex = '/^'.self::COORDINATE_PATTERN.'$/'; + if (!\preg_match($floatRegex, $stringValue)) { throw new \InvalidArgumentException( \sprintf('Invalid %s coordinate format: %s', $name, $stringValue) ); diff --git a/tests/Unit/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php b/tests/Unit/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php index d318a199..a87e2de2 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php @@ -4,8 +4,8 @@ namespace Tests\Unit\MartinGeorgiev\Doctrine\DBAL\Types; -use Doctrine\DBAL\Types\ConversionException; use MartinGeorgiev\Doctrine\DBAL\Types\BaseFloatArray; +use MartinGeorgiev\Doctrine\DBAL\Types\Exceptions\InvalidFloatArrayItemForPHPException; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; @@ -64,7 +64,7 @@ abstract public static function provideValidTransformations(): array; #[Test] public function throws_domain_exception_when_invalid_array_item_value(): void { - $this->expectException(ConversionException::class); + $this->expectException(InvalidFloatArrayItemForPHPException::class); $this->expectExceptionMessage('cannot be transformed to valid PHP float'); $this->fixture->transformArrayItemForPHP('1.e234'); diff --git a/tests/Unit/MartinGeorgiev/Doctrine/DBAL/Types/BaseNetworkTypeArrayTest.php b/tests/Unit/MartinGeorgiev/Doctrine/DBAL/Types/BaseNetworkTypeArrayTest.php index 04baf046..5ee2b46c 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/DBAL/Types/BaseNetworkTypeArrayTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/DBAL/Types/BaseNetworkTypeArrayTest.php @@ -6,6 +6,7 @@ use Doctrine\DBAL\Platforms\AbstractPlatform; use MartinGeorgiev\Doctrine\DBAL\Types\BaseNetworkTypeArray; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -55,24 +56,28 @@ public function has_name(): void } #[Test] - public function can_convert_null_to_database_value(): void + #[DataProvider('provideValidTransformations')] + public function can_convert_to_php_value(?array $phpValue, ?string $postgresValue): void { - self::assertNull($this->fixture->convertToDatabaseValue(null, $this->platform)); + self::assertEquals($phpValue, $this->fixture->convertToPHPValue($postgresValue, $this->platform)); } - #[Test] - public function can_convert_empty_array_to_database_value(): void + public static function provideValidTransformations(): array { - self::assertEquals('{}', $this->fixture->convertToDatabaseValue([], $this->platform)); - } - - #[Test] - public function can_convert_valid_array_to_database_value(): void - { - self::assertEquals( - '{"valid_address","valid_address"}', - $this->fixture->convertToDatabaseValue(['valid_address', 'valid_address'], $this->platform) - ); + return [ + 'null' => [ + 'phpValue' => null, + 'postgresValue' => null, + ], + 'empty array' => [ + 'phpValue' => [], + 'postgresValue' => '{}', + ], + 'valid array' => [ + 'phpValue' => ['valid_address', 'valid_address'], + 'postgresValue' => '{"valid_address","valid_address"}', + ], + ]; } #[Test] @@ -84,23 +89,31 @@ public function throws_exception_for_invalid_type(): void } #[Test] - public function can_convert_null_to_php_value(): void + #[DataProvider('provideInvalidValues')] + public function throws_exception_for_invalid_values(mixed $arrayItem, string $exceptionMessage): void { - self::assertNull($this->fixture->convertToPHPValue(null, $this->platform)); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage($exceptionMessage); + $this->fixture->transformArrayItemForPHP($arrayItem); } - #[Test] - public function can_convert_empty_array_to_php_value(): void + public static function provideInvalidValues(): array { - self::assertEquals([], $this->fixture->convertToPHPValue('{}', $this->platform)); + return [ + 'invalid type' => [ + 'arrayItem' => [], + 'exceptionMessage' => 'Invalid type', + ], + 'invalid format' => [ + 'arrayItem' => '"invalid_address"', + 'exceptionMessage' => 'Invalid format', + ], + ]; } #[Test] - public function can_convert_valid_string_to_php_value(): void + public function transform_array_item_for_php_handles_valid_string(): void { - self::assertEquals( - ['valid_address', 'valid_address'], - $this->fixture->convertToPHPValue('{"valid_address","valid_address"}', $this->platform) - ); + $this->assertSame('valid_address', $this->fixture->transformArrayItemForPHP('"valid_address"')); } } diff --git a/tests/Unit/MartinGeorgiev/Doctrine/DBAL/Types/PointArrayTest.php b/tests/Unit/MartinGeorgiev/Doctrine/DBAL/Types/PointArrayTest.php index 57604e3f..9fa4a8ae 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/DBAL/Types/PointArrayTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/DBAL/Types/PointArrayTest.php @@ -191,4 +191,37 @@ public function returns_empty_array_for_non_standard_postgres_array_format(): vo self::assertEquals([], $result1); self::assertEquals([], $result2); } + + #[Test] + public function transform_array_item_for_php_returns_null_for_null(): void + { + $this->assertNull($this->fixture->transformArrayItemForPHP(null)); + } + + #[Test] + #[DataProvider('provideInvalidTypes')] + public function transform_array_item_for_php_throws_for_invalid_type(mixed $value): void + { + $this->expectException(InvalidPointArrayItemForPHPException::class); + $this->fixture->transformArrayItemForPHP($value); + } + + #[Test] + #[DataProvider('provideInvalidTypes')] + public function transform_postgres_array_to_php_array_returns_empty_for_invalid_format(mixed $value): void + { + $reflectionObject = new \ReflectionObject($this->fixture); + $reflectionMethod = $reflectionObject->getMethod('transformPostgresArrayToPHPArray'); + $reflectionMethod->setAccessible(true); + $this->assertSame([], $reflectionMethod->invoke($this->fixture, $value)); + } + + public static function provideInvalidTypes(): array + { + return [ + [123], + ['not-a-point-instance'], + ['{invalid}'], + ]; + } } diff --git a/tests/Unit/MartinGeorgiev/Doctrine/DBAL/Types/PointTest.php b/tests/Unit/MartinGeorgiev/Doctrine/DBAL/Types/PointTest.php index a855450e..82f2486a 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/DBAL/Types/PointTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/DBAL/Types/PointTest.php @@ -122,4 +122,19 @@ public static function provideInvalidDatabaseValues(): array 'float precision is too granular' => ['(1.23456789,7.89)'], ]; } + + #[Test] + public function allows_coordinate_with_exactly_6_decimal_places(): void + { + $point = new PointValueObject(1.123456, 2.654321); + $this->assertSame(1.123456, $point->getX()); + $this->assertSame(2.654321, $point->getY()); + } + + #[Test] + public function throws_for_coordinate_with_more_than_6_decimal_places(): void + { + $this->expectException(\InvalidArgumentException::class); + new PointValueObject(1.1234567, 2.0); + } } diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseVariadicFunctionTestCase.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseVariadicFunctionTestCase.php index 3c73c64b..5fa87b36 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseVariadicFunctionTestCase.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseVariadicFunctionTestCase.php @@ -7,8 +7,10 @@ use Doctrine\ORM\Configuration; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Query; +use Doctrine\ORM\Query\AST\Node; use Doctrine\ORM\Query\Parser; use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\BaseVariadicFunction; +use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Exception\InvalidArgumentForVariadicFunctionException; use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Exception\ParserException; use PHPUnit\Framework\Attributes\Test; @@ -38,4 +40,70 @@ public function throws_an_exception_when_lexer_is_not_populated_with_a_lookahead $reflectionMethod->setAccessible(true); $reflectionMethod->invoke($baseVariadicFunction, $parser, 'ArithmeticPrimary'); } + + #[Test] + public function throws_exception_when_argument_count_is_too_low(): void + { + $function = new class('TEST') extends BaseVariadicFunction { + protected function getFunctionName(): string + { + return 'TEST'; + } + + protected function getNodeMappingPattern(): array + { + return ['StringPrimary']; + } + + protected function getMinArgumentCount(): int + { + return 2; + } + + protected function getMaxArgumentCount(): int + { + return 3; + } + }; + $this->expectException(InvalidArgumentForVariadicFunctionException::class); + $reflectionClass = new \ReflectionClass($function); + $reflectionMethod = $reflectionClass->getMethod('validateArguments'); + $reflectionMethod->setAccessible(true); + + $node = $this->createMock(Node::class); + $reflectionMethod->invoke($function, $node); // 1 argument when min 2 are required + } + + #[Test] + public function throws_exception_when_argument_count_is_too_high(): void + { + $function = new class('TEST') extends BaseVariadicFunction { + protected function getFunctionName(): string + { + return 'TEST'; + } + + protected function getNodeMappingPattern(): array + { + return ['StringPrimary']; + } + + protected function getMinArgumentCount(): int + { + return 1; + } + + protected function getMaxArgumentCount(): int + { + return 2; + } + }; + $this->expectException(InvalidArgumentForVariadicFunctionException::class); + $reflectionClass = new \ReflectionClass($function); + $reflectionMethod = $reflectionClass->getMethod('validateArguments'); + $reflectionMethod->setAccessible(true); + + $node = $this->createMock(Node::class); + $reflectionMethod->invoke($function, $node, $node, $node); // 3 arguments when max 2 are required + } } diff --git a/tests/Unit/MartinGeorgiev/Utils/PHPArrayToPostgresValueTransformerTest.php b/tests/Unit/MartinGeorgiev/Utils/PHPArrayToPostgresValueTransformerTest.php index 8ce9bebf..7988b6ab 100644 --- a/tests/Unit/MartinGeorgiev/Utils/PHPArrayToPostgresValueTransformerTest.php +++ b/tests/Unit/MartinGeorgiev/Utils/PHPArrayToPostgresValueTransformerTest.php @@ -104,6 +104,18 @@ public static function provideValidTransformations(): array 'phpValue' => ['Hello δΈ–η•Œ', '🌍 Earth'], 'postgresValue' => '{"Hello δΈ–η•Œ","🌍 Earth"}', ], + 'with only nulls' => [ + 'phpValue' => [null, null], + 'postgresValue' => '{NULL,NULL}', + ], + 'with only booleans' => [ + 'phpValue' => [true, false, true], + 'postgresValue' => '{true,false,true}', + ], + 'with empty empty strings' => [ + 'phpValue' => ['', ''], + 'postgresValue' => '{"",""}', + ], ]; } @@ -237,4 +249,17 @@ public static function provideMultiDimensionalArrays(): array ], ]; } + + #[Test] + public function can_transform_array_with_gd_resource(): void + { + if (!\function_exists('imagecreatetruecolor')) { + $this->markTestSkipped('GD extension not available'); + } + + $resource = \imagecreatetruecolor(1, 1); + $result = PHPArrayToPostgresValueTransformer::transformToPostgresTextArray([$resource]); + $this->assertStringContainsString('GdImage', $result); + \imagedestroy($resource); + } } diff --git a/tests/Unit/MartinGeorgiev/Utils/PostgresArrayToPHPArrayTransformerTest.php b/tests/Unit/MartinGeorgiev/Utils/PostgresArrayToPHPArrayTransformerTest.php index 38d27736..d24607ca 100644 --- a/tests/Unit/MartinGeorgiev/Utils/PostgresArrayToPHPArrayTransformerTest.php +++ b/tests/Unit/MartinGeorgiev/Utils/PostgresArrayToPHPArrayTransformerTest.php @@ -122,6 +122,26 @@ public static function provideValidTransformations(): array 'phpValue' => ['quoted', 'unquoted'], 'postgresValue' => '{"quoted",unquoted}', ], + 'only whitespace' => [ + 'phpValue' => [], + 'postgresValue' => ' ', + ], + 'with trailing comma' => [ + 'phpValue' => ['a'], + 'postgresValue' => '{a,}}', + ], + 'with only backslashes' => [ + 'phpValue' => ['\\'], + 'postgresValue' => '{\}', + ], + 'with only double quotes' => [ + 'phpValue' => ['"'], + 'postgresValue' => '{"\""}', + ], + 'with empty quoted strings' => [ + 'phpValue' => ['', ''], + 'postgresValue' => '{"",""}', + ], ]; }