diff --git a/src/Guid/GuidBuilder.php b/src/Guid/GuidBuilder.php index b518e4c..b38eb82 100644 --- a/src/Guid/GuidBuilder.php +++ b/src/Guid/GuidBuilder.php @@ -67,7 +67,7 @@ class GuidBuilder implements UuidBuilderInterface { try { return new Guid( - new Fields($bytes), + $this->buildFields($bytes), $this->numberConverter, $codec, $this->timeConverter @@ -76,4 +76,12 @@ class GuidBuilder implements UuidBuilderInterface throw new UnableToBuildUuidException($e->getMessage(), (int) $e->getCode(), $e); } } + + /** + * Proxy method to allow injecting a mock, for testing + */ + protected function buildFields(string $bytes): Fields + { + return new Fields($bytes); + } } diff --git a/src/Math/BrickMathCalculator.php b/src/Math/BrickMathCalculator.php index 4e9228b..3e0e90b 100644 --- a/src/Math/BrickMathCalculator.php +++ b/src/Math/BrickMathCalculator.php @@ -102,7 +102,7 @@ final class BrickMathCalculator implements CalculatorInterface { try { return new IntegerObject((string) BigInteger::fromBase($value, $base)); - } catch (MathException $exception) { + } catch (MathException | \InvalidArgumentException $exception) { throw new InvalidArgumentException( $exception->getMessage(), (int) $exception->getCode(), @@ -115,7 +115,7 @@ final class BrickMathCalculator implements CalculatorInterface { try { return BigInteger::of($value->toString())->toBase($base); - } catch (MathException $exception) { + } catch (MathException | \InvalidArgumentException $exception) { throw new InvalidArgumentException( $exception->getMessage(), (int) $exception->getCode(), diff --git a/src/Nonstandard/UuidBuilder.php b/src/Nonstandard/UuidBuilder.php index ea8dd88..e6750f6 100644 --- a/src/Nonstandard/UuidBuilder.php +++ b/src/Nonstandard/UuidBuilder.php @@ -66,7 +66,7 @@ class UuidBuilder implements UuidBuilderInterface { try { return new Uuid( - new Fields($bytes), + $this->buildFields($bytes), $this->numberConverter, $codec, $this->timeConverter @@ -75,4 +75,12 @@ class UuidBuilder implements UuidBuilderInterface throw new UnableToBuildUuidException($e->getMessage(), (int) $e->getCode(), $e); } } + + /** + * Proxy method to allow injecting a mock, for testing + */ + protected function buildFields(string $bytes): Fields + { + return new Fields($bytes); + } } diff --git a/src/Rfc4122/UuidBuilder.php b/src/Rfc4122/UuidBuilder.php index b705ac6..e3de0f0 100644 --- a/src/Rfc4122/UuidBuilder.php +++ b/src/Rfc4122/UuidBuilder.php @@ -69,7 +69,7 @@ class UuidBuilder implements UuidBuilderInterface public function build(CodecInterface $codec, string $bytes): UuidInterface { try { - $fields = new Fields($bytes); + $fields = $this->buildFields($bytes); if ($fields->isNil()) { return new NilUuid($fields, $this->numberConverter, $codec, $this->timeConverter); @@ -98,4 +98,12 @@ class UuidBuilder implements UuidBuilderInterface throw new UnableToBuildUuidException($e->getMessage(), (int) $e->getCode(), $e); } } + + /** + * Proxy method to allow injecting a mock, for testing + */ + protected function buildFields(string $bytes): FieldsInterface + { + return new Fields($bytes); + } } diff --git a/src/Type/Decimal.php b/src/Type/Decimal.php index a64eabd..a561a45 100644 --- a/src/Type/Decimal.php +++ b/src/Type/Decimal.php @@ -51,6 +51,16 @@ final class Decimal implements NumberInterface ); } + // Remove the leading +-symbol. + if (strpos($value, '+') === 0) { + $value = substr($value, 1); + } + + // For cases like `-0` or `-0.0000`, convert the value to `0`. + if (abs((float) $value) === 0.0) { + $value = '0'; + } + $this->value = $value; } diff --git a/tests/DeprecatedUuidMethodsTraitTest.php b/tests/DeprecatedUuidMethodsTraitTest.php new file mode 100644 index 0000000..aefb46d --- /dev/null +++ b/tests/DeprecatedUuidMethodsTraitTest.php @@ -0,0 +1,59 @@ +assertInstanceOf(DateTimeInterface::class, $uuid->getDateTime()); + $this->assertSame('2012-07-04T02:14:34+00:00', $uuid->getDateTime()->format('c')); + $this->assertSame('1341368074.491000', $uuid->getDateTime()->format('U.u')); + } + + public function testGetDateTimeThrowsException(): void + { + $fields = Mockery::mock(FieldsInterface::class, [ + 'getVersion' => 1, + 'getTimestamp' => new Hexadecimal('0'), + ]); + + $numberConverter = Mockery::mock(NumberConverterInterface::class); + $codec = Mockery::mock(CodecInterface::class); + + $timeConverter = Mockery::mock(TimeConverterInterface::class, [ + 'convertTime' => new Time('0', '1234567'), + ]); + + $uuid = new Uuid($fields, $numberConverter, $codec, $timeConverter); + + $this->expectException(DateTimeException::class); + + $uuid->getDateTime(); + } +} diff --git a/tests/FeatureSetTest.php b/tests/FeatureSetTest.php index 4948b27..551338d 100644 --- a/tests/FeatureSetTest.php +++ b/tests/FeatureSetTest.php @@ -11,6 +11,7 @@ use Ramsey\Uuid\FeatureSet; use Ramsey\Uuid\Generator\DefaultNameGenerator; use Ramsey\Uuid\Generator\PeclUuidTimeGenerator; use Ramsey\Uuid\Guid\GuidBuilder; +use Ramsey\Uuid\Math\BrickMathCalculator; use Ramsey\Uuid\Validator\ValidatorInterface; class FeatureSetTest extends TestCase @@ -59,4 +60,11 @@ class FeatureSetTest extends TestCase $this->assertInstanceOf(PeclUuidTimeGenerator::class, $featureSet->getTimeGenerator()); } + + public function testGetCalculator(): void + { + $featureSet = new FeatureSet(); + + $this->assertInstanceOf(BrickMathCalculator::class, $featureSet->getCalculator()); + } } diff --git a/tests/Guid/GuidBuilderTest.php b/tests/Guid/GuidBuilderTest.php new file mode 100644 index 0000000..7938ade --- /dev/null +++ b/tests/Guid/GuidBuilderTest.php @@ -0,0 +1,32 @@ +shouldAllowMockingProtectedMethods(); + $builder->shouldReceive('buildFields')->andThrow( + \RuntimeException::class, + 'exception thrown' + ); + $builder->shouldReceive('build')->passthru(); + + $this->expectException(UnableToBuildUuidException::class); + $this->expectExceptionMessage('exception thrown'); + + $builder->build($codec, 'foobar'); + } +} diff --git a/tests/Math/BrickMathCalculatorTest.php b/tests/Math/BrickMathCalculatorTest.php index 73ff73a..57989cd 100644 --- a/tests/Math/BrickMathCalculatorTest.php +++ b/tests/Math/BrickMathCalculatorTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Ramsey\Uuid\Test\Math; +use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Math\BrickMathCalculator; use Ramsey\Uuid\Math\RoundingMode; use Ramsey\Uuid\Test\TestCase; @@ -92,4 +93,24 @@ class BrickMathCalculatorTest extends TestCase $this->assertInstanceOf(Hexadecimal::class, $result); $this->assertSame('ffffffffffffffffffff', $result->toString()); } + + public function testFromBaseThrowsException(): void + { + $calculator = new BrickMathCalculator(); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('"o" is not a valid character in base 16'); + + $calculator->fromBase('foobar', 16); + } + + public function testToBaseThrowsException(): void + { + $calculator = new BrickMathCalculator(); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Base 1024 is out of range [2, 36]'); + + $calculator->toBase(new IntegerObject(42), 1024); + } } diff --git a/tests/Nonstandard/UuidBuilderTest.php b/tests/Nonstandard/UuidBuilderTest.php new file mode 100644 index 0000000..fb80d55 --- /dev/null +++ b/tests/Nonstandard/UuidBuilderTest.php @@ -0,0 +1,32 @@ +shouldAllowMockingProtectedMethods(); + $builder->shouldReceive('buildFields')->andThrow( + \RuntimeException::class, + 'exception thrown' + ); + $builder->shouldReceive('build')->passthru(); + + $this->expectException(UnableToBuildUuidException::class); + $this->expectExceptionMessage('exception thrown'); + + $builder->build($codec, 'foobar'); + } +} diff --git a/tests/Nonstandard/UuidV6Test.php b/tests/Nonstandard/UuidV6Test.php index 558b092..3c0300e 100644 --- a/tests/Nonstandard/UuidV6Test.php +++ b/tests/Nonstandard/UuidV6Test.php @@ -9,11 +9,14 @@ use Mockery; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; +use Ramsey\Uuid\Exception\DateTimeException; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Nonstandard\UuidV6; use Ramsey\Uuid\Rfc4122\FieldsInterface; use Ramsey\Uuid\Rfc4122\UuidV1; use Ramsey\Uuid\Test\TestCase; +use Ramsey\Uuid\Type\Hexadecimal; +use Ramsey\Uuid\Type\Time; use Ramsey\Uuid\Uuid; class UuidV6Test extends TestCase @@ -157,4 +160,25 @@ class UuidV6Test extends TestCase ], ]; } + + public function testGetDateTimeThrowsException(): void + { + $fields = Mockery::mock(FieldsInterface::class, [ + 'getVersion' => 6, + 'getTimestamp' => new Hexadecimal('0'), + ]); + + $numberConverter = Mockery::mock(NumberConverterInterface::class); + $codec = Mockery::mock(CodecInterface::class); + + $timeConverter = Mockery::mock(TimeConverterInterface::class, [ + 'convertTime' => new Time('0', '1234567'), + ]); + + $uuid = new UuidV6($fields, $numberConverter, $codec, $timeConverter); + + $this->expectException(DateTimeException::class); + + $uuid->getDateTime(); + } } diff --git a/tests/Provider/Node/SystemNodeProviderTest.php b/tests/Provider/Node/SystemNodeProviderTest.php index ad5485b..fd02bb1 100644 --- a/tests/Provider/Node/SystemNodeProviderTest.php +++ b/tests/Provider/Node/SystemNodeProviderTest.php @@ -6,6 +6,7 @@ namespace Ramsey\Uuid\Test\Provider\Node; use AspectMock\Proxy\FuncProxy; use AspectMock\Test as AspectMock; +use Mockery; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Provider\Node\SystemNodeProvider; use Ramsey\Uuid\Test\TestCase; @@ -877,4 +878,32 @@ TXT 'Too long -- extra tuple' => ["\n01-AA-BB-CC-DD-EE-FF\n", '01AABBCCDDEE'], ]; } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function testInternalNodeValueIsArray(): void + { + $provider = Mockery::mock(SystemNodeProvider::class); + $provider->shouldAllowMockingProtectedMethods(); + $provider->shouldReceive('getSysfs')->andReturn(['foo:bar']); + $provider->shouldReceive('getNode')->passthru(); + + $this->assertSame('foobar', $provider->getNode()); + } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function testInternalNodeValueIsArrayWithNoElements(): void + { + $provider = Mockery::mock(SystemNodeProvider::class); + $provider->shouldAllowMockingProtectedMethods(); + $provider->shouldReceive('getSysfs')->andReturn([]); + $provider->shouldReceive('getNode')->passthru(); + + $this->assertFalse($provider->getNode()); + } } diff --git a/tests/Rfc4122/UuidBuilderTest.php b/tests/Rfc4122/UuidBuilderTest.php index 089cd05..9e39067 100644 --- a/tests/Rfc4122/UuidBuilderTest.php +++ b/tests/Rfc4122/UuidBuilderTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Ramsey\Uuid\Test\Rfc4122; +use Mockery; use Ramsey\Uuid\Codec\StringCodec; use Ramsey\Uuid\Converter\Number\GenericNumberConverter; use Ramsey\Uuid\Converter\Time\GenericTimeConverter; @@ -11,6 +12,7 @@ use Ramsey\Uuid\Exception\UnableToBuildUuidException; use Ramsey\Uuid\Math\BrickMathCalculator; use Ramsey\Uuid\Nonstandard\UuidV6; use Ramsey\Uuid\Rfc4122\Fields; +use Ramsey\Uuid\Rfc4122\FieldsInterface; use Ramsey\Uuid\Rfc4122\UuidBuilder; use Ramsey\Uuid\Rfc4122\UuidV1; use Ramsey\Uuid\Rfc4122\UuidV2; @@ -104,4 +106,26 @@ class UuidBuilderTest extends TestCase $builder->build($codec, $bytes); } + + public function testBuildThrowsUnableToBuildExceptionForIncorrectVersionFields(): void + { + $fields = Mockery::mock(FieldsInterface::class, [ + 'isNil' => false, + 'getVersion' => 255, + ]); + + $builder = Mockery::mock(UuidBuilder::class); + $builder->shouldAllowMockingProtectedMethods(); + $builder->shouldReceive('buildFields')->andReturn($fields); + $builder->shouldReceive('build')->passthru(); + + $codec = Mockery::mock(StringCodec::class); + + $this->expectException(UnableToBuildUuidException::class); + $this->expectExceptionMessage( + 'The UUID version in the given fields is not supported by this UUID builder' + ); + + $builder->build($codec, 'foobar'); + } } diff --git a/tests/Rfc4122/UuidV1Test.php b/tests/Rfc4122/UuidV1Test.php index 74d6703..6889067 100644 --- a/tests/Rfc4122/UuidV1Test.php +++ b/tests/Rfc4122/UuidV1Test.php @@ -9,10 +9,13 @@ use Mockery; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; +use Ramsey\Uuid\Exception\DateTimeException; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Rfc4122\FieldsInterface; use Ramsey\Uuid\Rfc4122\UuidV1; use Ramsey\Uuid\Test\TestCase; +use Ramsey\Uuid\Type\Hexadecimal; +use Ramsey\Uuid\Type\Time; use Ramsey\Uuid\Uuid; class UuidV1Test extends TestCase @@ -95,4 +98,25 @@ class UuidV1Test extends TestCase ], ]; } + + public function testGetDateTimeThrowsException(): void + { + $fields = Mockery::mock(FieldsInterface::class, [ + 'getVersion' => 1, + 'getTimestamp' => new Hexadecimal('0'), + ]); + + $numberConverter = Mockery::mock(NumberConverterInterface::class); + $codec = Mockery::mock(CodecInterface::class); + + $timeConverter = Mockery::mock(TimeConverterInterface::class, [ + 'convertTime' => new Time('0', '1234567'), + ]); + + $uuid = new UuidV1($fields, $numberConverter, $codec, $timeConverter); + + $this->expectException(DateTimeException::class); + + $uuid->getDateTime(); + } } diff --git a/tests/Rfc4122/ValidatorTest.php b/tests/Rfc4122/ValidatorTest.php index 2203ba4..415e20f 100644 --- a/tests/Rfc4122/ValidatorTest.php +++ b/tests/Rfc4122/ValidatorTest.php @@ -77,4 +77,14 @@ class ValidatorTest extends TestCase ], ]); } + + public function testGetPattern(): void + { + $expectedPattern = '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-' + . '[1-5]{1}[0-9A-Fa-f]{3}-[ABab89]{1}[0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$'; + + $validator = new Validator(); + + $this->assertSame($expectedPattern, $validator->getPattern()); + } } diff --git a/tests/Type/DecimalTest.php b/tests/Type/DecimalTest.php new file mode 100644 index 0000000..491d5c2 --- /dev/null +++ b/tests/Type/DecimalTest.php @@ -0,0 +1,229 @@ +assertSame($expected, $decimal->toString()); + $this->assertSame($expected, (string) $decimal); + } + + /** + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification + */ + public function provideDecimal(): array + { + return [ + [ + 'value' => '-11386878954224802805705605120', + 'expected' => '-11386878954224802805705605120', + ], + [ + 'value' => '-9223372036854775808', + 'expected' => '-9223372036854775808', + ], + [ + 'value' => -99986838650880, + 'expected' => '-99986838650880', + ], + [ + 'value' => -4294967296, + 'expected' => '-4294967296', + ], + [ + 'value' => -2147483649, + 'expected' => '-2147483649', + ], + [ + 'value' => -123456.0, + 'expected' => '-123456', + ], + [ + 'value' => -1.00000000000001, + 'expected' => '-1', + ], + [ + 'value' => -1, + 'expected' => '-1', + ], + [ + 'value' => '-1', + 'expected' => '-1', + ], + [ + 'value' => 0, + 'expected' => '0', + ], + [ + 'value' => '0', + 'expected' => '0', + ], + [ + 'value' => -0, + 'expected' => '0', + ], + [ + 'value' => '-0', + 'expected' => '0', + ], + [ + 'value' => '+0', + 'expected' => '0', + ], + [ + 'value' => 1, + 'expected' => '1', + ], + [ + 'value' => '1', + 'expected' => '1', + ], + [ + 'value' => '+1', + 'expected' => '1', + ], + [ + 'value' => 1.00000000000001, + 'expected' => '1', + ], + [ + 'value' => 123456.0, + 'expected' => '123456', + ], + [ + 'value' => 2147483648, + 'expected' => '2147483648', + ], + [ + 'value' => 4294967294, + 'expected' => '4294967294', + ], + [ + 'value' => 99965363767850, + 'expected' => '99965363767850', + ], + [ + 'value' => '9223372036854775808', + 'expected' => '9223372036854775808', + ], + [ + 'value' => '11386878954224802805705605120', + 'expected' => '11386878954224802805705605120', + ], + [ + 'value' => -9223372036854775809, + 'expected' => '-9.2233720368548E+18', + ], + [ + 'value' => 9223372036854775808, + 'expected' => '9.2233720368548E+18', + ], + [ + 'value' => -123456.789, + 'expected' => '-123456.789', + ], + [ + 'value' => -1.0000000000001, + 'expected' => '-1.0000000000001', + ], + [ + 'value' => -0.5, + 'expected' => '-0.5', + ], + [ + 'value' => 0.5, + 'expected' => '0.5', + ], + [ + 'value' => 1.0000000000001, + 'expected' => '1.0000000000001', + ], + [ + 'value' => 123456.789, + 'expected' => '123456.789', + ], + [ + 'value' => '-0', + 'expected' => '0', + ], + [ + 'value' => '+0', + 'expected' => '0', + ], + [ + 'value' => -0.00000000, + 'expected' => '0', + ], + [ + 'value' => 0.00000000, + 'expected' => '0', + ], + [ + 'value' => -0.0001, + 'expected' => '-0.0001', + ], + [ + 'value' => 0.0001, + 'expected' => '0.0001', + ], + [ + 'value' => -0.00001, + 'expected' => '-1.0E-5', + ], + [ + 'value' => 0.00001, + 'expected' => '1.0E-5', + ], + [ + 'value' => '+1234.56', + 'expected' => '1234.56', + ], + ]; + } + + /** + * @param int|float|string $value + * + * @dataProvider provideDecimalBadValues + */ + public function testDecimalTypeThrowsExceptionForBadValues($value): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage( + 'Value must be a signed decimal or a string containing only ' + . 'digits 0-9 and, optionally, a decimal point or sign (+ or -)' + ); + + new Decimal($value); + } + + /** + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification + */ + public function provideDecimalBadValues(): array + { + return [ + ['123abc'], + ['abc123'], + ['foobar'], + ['123.456a'], + ['+abcd'], + ['-0012.a'], + ]; + } +} diff --git a/tests/Validator/GenericValidatorTest.php b/tests/Validator/GenericValidatorTest.php index 59ba46c..5a8699b 100644 --- a/tests/Validator/GenericValidatorTest.php +++ b/tests/Validator/GenericValidatorTest.php @@ -74,4 +74,13 @@ class GenericValidatorTest extends TestCase ], ]); } + + public function testGetPattern(): void + { + $expectedPattern = '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$'; + + $validator = new GenericValidator(); + + $this->assertSame($expectedPattern, $validator->getPattern()); + } }