Skip to content

Commit

Permalink
Improve coverage and unit tests for fields, GUIDs, and nonstandard UUIDs
Browse files Browse the repository at this point in the history
  • Loading branch information
ramsey committed Jan 3, 2020
1 parent c5cd0f6 commit ad0544c
Show file tree
Hide file tree
Showing 13 changed files with 476 additions and 3 deletions.
3 changes: 2 additions & 1 deletion src/Builder/FallbackBuilder.php
Expand Up @@ -41,7 +41,8 @@ public function __construct(array $builders)
}

/**
* Builds and returns a UuidInterface instance
* Builds and returns a UuidInterface instance using the first builder that
* succeeds
*
* @param CodecInterface $codec The codec to use for building this instance
* @param string[] $fields An array of fields from which to construct an instance;
Expand Down
4 changes: 4 additions & 0 deletions src/Guid/GuidFields.php
Expand Up @@ -136,6 +136,10 @@ public function getNode(): string

public function getVersion(): ?int
{
if ($this->isNil()) {
return null;
}

$parts = unpack('n*', $this->bytes);

return ((int) $parts[4] >> 4) & 0x00f;
Expand Down
52 changes: 52 additions & 0 deletions tests/Builder/FallbackBuilderTest.php
@@ -0,0 +1,52 @@
<?php

declare(strict_types=1);

namespace Ramsey\Uuid\Test\Builder;

use Mockery;
use Ramsey\Uuid\Builder\FallbackBuilder;
use Ramsey\Uuid\Builder\UuidBuilderInterface;
use Ramsey\Uuid\Codec\CodecInterface;
use Ramsey\Uuid\Exception\BuilderNotFoundException;
use Ramsey\Uuid\Exception\InvalidArgumentException;
use Ramsey\Uuid\Test\TestCase;

class FallbackBuilderTest extends TestCase
{
public function testBuildThrowsExceptionAfterAllConfiguredBuildersHaveErrored(): void
{
$codec = Mockery::mock(CodecInterface::class);
$fields = ['someFields'];

$builder1 = Mockery::mock(UuidBuilderInterface::class);
$builder1
->shouldReceive('build')
->once()
->with($codec, $fields)
->andThrow(InvalidArgumentException::class);

$builder2 = Mockery::mock(UuidBuilderInterface::class);
$builder2
->shouldReceive('build')
->once()
->with($codec, $fields)
->andThrow(InvalidArgumentException::class);

$builder3 = Mockery::mock(UuidBuilderInterface::class);
$builder3
->shouldReceive('build')
->once()
->with($codec, $fields)
->andThrow(InvalidArgumentException::class);

$fallbackBuilder = new FallbackBuilder([$builder1, $builder2, $builder3]);

$this->expectException(BuilderNotFoundException::class);
$this->expectExceptionMessage(
'Could not find a suitable builder for the provided codec and fields'
);

$fallbackBuilder->build($codec, $fields);
}
}
95 changes: 95 additions & 0 deletions tests/DegradedUuidTest.php
Expand Up @@ -11,6 +11,8 @@
use Ramsey\Uuid\Converter\TimeConverterInterface;
use Ramsey\Uuid\DegradedUuid;
use Ramsey\Uuid\Exception\DateTimeException;
use Ramsey\Uuid\Exception\UnsatisfiedDependencyException;
use Ramsey\Uuid\Exception\UnsupportedOperationException;
use Ramsey\Uuid\UuidFactory;

class DegradedUuidTest extends TestCase
Expand Down Expand Up @@ -48,4 +50,97 @@ public function testGetDateTime(): void

$uuid->getDateTime();
}

public function testGetDateTimeThrowsExceptionIfUuidIsNotVersion1(): void
{
$degradedUuid = Mockery::mock(DegradedUuid::class, [
'getVersion' => 4,
]);

$degradedUuid->shouldReceive('getDateTime')->passthru();

$this->expectException(UnsupportedOperationException::class);
$this->expectExceptionMessage('Not a time-based UUID');

$degradedUuid->getDateTime();
}

public function testGetTimestampThrowsExceptionIfUuidIsNotVersion1(): void
{
$degradedUuid = Mockery::mock(DegradedUuid::class, [
'getVersion' => 4,
]);

$degradedUuid->shouldReceive('getTimestamp')->passthru();

$this->expectException(UnsupportedOperationException::class);
$this->expectExceptionMessage('Not a time-based UUID');

$degradedUuid->getTimestamp();
}

public function testGetFieldsThrowsException(): void
{
$degradedUuid = Mockery::mock(DegradedUuid::class);
$degradedUuid->shouldReceive('getFields')->passthru();

$this->expectException(UnsatisfiedDependencyException::class);
$this->expectExceptionMessage(
'Cannot call Ramsey\\Uuid\\DegradedUuid::getFields on a 32-bit '
. 'system, since some values overflow the system max integer value; '
. 'consider calling getFieldsHex instead'
);

$degradedUuid->getFields();
}

public function testGetNodeThrowsException(): void
{
$degradedUuid = Mockery::mock(DegradedUuid::class);
$degradedUuid->shouldReceive('getNode')->passthru();

$this->expectException(UnsatisfiedDependencyException::class);
$this->expectExceptionMessage(
'Cannot call Ramsey\\Uuid\\DegradedUuid::getNode on a 32-bit '
. 'system, since node is an unsigned 48-bit integer and can '
. 'overflow the system max integer value; consider calling '
. 'getNodeHex instead'
);

$degradedUuid->getNode();
}

public function testGetTimeLowThrowsException(): void
{
$degradedUuid = Mockery::mock(DegradedUuid::class);
$degradedUuid->shouldReceive('getTimeLow')->passthru();

$this->expectException(UnsatisfiedDependencyException::class);
$this->expectExceptionMessage(
'Cannot call Ramsey\\Uuid\\DegradedUuid::getTimeLow on a 32-bit '
. 'system, since time_low is an unsigned 32-bit integer and can '
. 'overflow the system max integer value; consider calling '
. 'getTimeLowHex instead'
);

$degradedUuid->getTimeLow();
}

public function testGetTimestampThrowsExceptionFor32BitSystem(): void
{
$degradedUuid = Mockery::mock(DegradedUuid::class, [
'getVersion' => 1,
]);
$degradedUuid->shouldReceive('getTimestamp')->passthru();

$this->expectException(UnsatisfiedDependencyException::class);
$this->expectExceptionMessage(
'Cannot call Ramsey\\Uuid\\DegradedUuid::getTimestamp on a 32-bit '
. 'system, since timestamp is an unsigned 60-bit integer and can '
. 'overflow the system max integer value; consider calling '
. 'getTimestampHex instead'
);

$degradedUuid->getTimestamp();
}
}
38 changes: 38 additions & 0 deletions tests/FeatureSetTest.php
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace Ramsey\Uuid\Test;

use Mockery;
use Ramsey\Uuid\Builder\FallbackBuilder;
use Ramsey\Uuid\FeatureSet;
use Ramsey\Uuid\Guid\DegradedGuidBuilder;
use Ramsey\Uuid\Validator\ValidatorInterface;

class FeatureSetTest extends TestCase
{
public function testDegradedGuidBuilderIsSelectedOn32BitSystem(): void
{
$featureSet = new FeatureSet(true, true);

$this->assertInstanceOf(DegradedGuidBuilder::class, $featureSet->getBuilder());
}

public function testFallbackBuilderIsSelectedOn32BitSystem(): void
{
$featureSet = new FeatureSet(false, true);

$this->assertInstanceOf(FallbackBuilder::class, $featureSet->getBuilder());
}

public function testSetValidatorSetsTheProvidedValidator(): void
{
$validator = Mockery::mock(ValidatorInterface::class);

$featureSet = new FeatureSet();
$featureSet->setValidator($validator);

$this->assertSame($validator, $featureSet->getValidator());
}
}
52 changes: 52 additions & 0 deletions tests/Guid/DegradedGuidBuilderTest.php
@@ -0,0 +1,52 @@
<?php

declare(strict_types=1);

namespace Ramsey\Uuid\Test\Guid;

use Mockery;
use Ramsey\Uuid\Codec\CodecInterface;
use Ramsey\Uuid\Converter\NumberConverterInterface;
use Ramsey\Uuid\Converter\TimeConverterInterface;
use Ramsey\Uuid\Guid\DegradedGuid;
use Ramsey\Uuid\Guid\DegradedGuidBuilder;
use Ramsey\Uuid\Test\TestCase;

class DegradedGuidBuilderTest extends TestCase
{
public function testBuilderBuildsDegradedGuid(): void
{
$numberConverter = Mockery::mock(NumberConverterInterface::class);
$codec = Mockery::mock(CodecInterface::class);
$timeConverter = Mockery::mock(TimeConverterInterface::class);

$fields = [
'b1484596',
'25dc',
'11ea',
'978f',
'2e728ce88125',
];

$expectedFields = $fields;

// Bytes are expected to be swapped before being passed to DegradedGuidBuilder.
$bytes = (string) hex2bin(implode('', $fields));
$fields[0] = bin2hex($bytes[3] . $bytes[2] . $bytes[1] . $bytes[0]);
$fields[1] = bin2hex($bytes[5] . $bytes[4]);
$fields[2] = bin2hex($bytes[7] . $bytes[6]);

$builder = new DegradedGuidBuilder($numberConverter, $timeConverter);
$degradedGuid = $builder->build($codec, $fields);

$this->assertInstanceOf(DegradedGuid::class, $degradedGuid);
$this->assertSame($expectedFields[0], $degradedGuid->getTimeLowHex());
$this->assertSame($expectedFields[1], $degradedGuid->getTimeMidHex());
$this->assertSame($expectedFields[2], $degradedGuid->getTimeHiAndVersionHex());
$this->assertSame(
$expectedFields[3],
$degradedGuid->getClockSeqHiAndReservedHex() . $degradedGuid->getClockSeqLowHex()
);
$this->assertSame($expectedFields[4], $degradedGuid->getNodeHex());
}
}
50 changes: 50 additions & 0 deletions tests/Guid/DegradedGuidTest.php
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

namespace Ramsey\Uuid\Test\Guid;

use Mockery;
use Ramsey\Uuid\Codec\CodecInterface;
use Ramsey\Uuid\Converter\NumberConverterInterface;
use Ramsey\Uuid\Converter\TimeConverterInterface;
use Ramsey\Uuid\Guid\DegradedGuid;
use Ramsey\Uuid\Test\TestCase;

class DegradedGuidTest extends TestCase
{
public function testConstructorConstructsDegradedGuid(): void
{
$numberConverter = Mockery::mock(NumberConverterInterface::class);
$codec = Mockery::mock(CodecInterface::class);
$timeConverter = Mockery::mock(TimeConverterInterface::class);

$fields = [
'b1484596',
'25dc',
'11ea',
'978f',
'2e728ce88125',
];

$expectedFields = $fields;

// Bytes are expected to be swapped before being passed to DegradedGuid.
$bytes = (string) hex2bin(implode('', $fields));
$fields[0] = bin2hex($bytes[3] . $bytes[2] . $bytes[1] . $bytes[0]);
$fields[1] = bin2hex($bytes[5] . $bytes[4]);
$fields[2] = bin2hex($bytes[7] . $bytes[6]);

$degradedGuid = new DegradedGuid($fields, $numberConverter, $codec, $timeConverter);

$this->assertInstanceOf(DegradedGuid::class, $degradedGuid);
$this->assertSame($expectedFields[0], $degradedGuid->getTimeLowHex());
$this->assertSame($expectedFields[1], $degradedGuid->getTimeMidHex());
$this->assertSame($expectedFields[2], $degradedGuid->getTimeHiAndVersionHex());
$this->assertSame(
$expectedFields[3],
$degradedGuid->getClockSeqHiAndReservedHex() . $degradedGuid->getClockSeqLowHex()
);
$this->assertSame($expectedFields[4], $degradedGuid->getNodeHex());
}
}
15 changes: 15 additions & 0 deletions tests/Guid/GuidFieldsTest.php
Expand Up @@ -123,6 +123,7 @@ public function fieldGetterMethodProvider(): array
['b08c6fff7dc5e111cb210800200c9a66', 'getTimeMid', 'c57d'],
['b08c6fff7dc5e111cb210800200c9a66', 'getVariant', 6],
['b08c6fff7dc5e111cb210800200c9a66', 'getVersion', 1],
['b08c6fff7dc5e111cb210800200c9a66', 'isNil', false],

// For ff6f8cb0-c57d-41e1-db21-0800200c9a66
['b08c6fff7dc5e141db210800200c9a66', 'getClockSeqHiAndReserved', 'db'],
Expand All @@ -133,6 +134,7 @@ public function fieldGetterMethodProvider(): array
['b08c6fff7dc5e141db210800200c9a66', 'getTimeMid', 'c57d'],
['b08c6fff7dc5e141db210800200c9a66', 'getVariant', 6],
['b08c6fff7dc5e141db210800200c9a66', 'getVersion', 4],
['b08c6fff7dc5e141db210800200c9a66', 'isNil', false],

// For ff6f8cb0-c57d-31e1-8b21-0800200c9a66
['b08c6fff7dc5e1318b210800200c9a66', 'getClockSeqHiAndReserved', '8b'],
Expand All @@ -143,6 +145,7 @@ public function fieldGetterMethodProvider(): array
['b08c6fff7dc5e1318b210800200c9a66', 'getTimeMid', 'c57d'],
['b08c6fff7dc5e1318b210800200c9a66', 'getVariant', 2],
['b08c6fff7dc5e1318b210800200c9a66', 'getVersion', 3],
['b08c6fff7dc5e1318b210800200c9a66', 'isNil', false],

// For ff6f8cb0-c57d-51e1-9b21-0800200c9a66
['b08c6fff7dc5e1519b210800200c9a66', 'getClockSeqHiAndReserved', '9b'],
Expand All @@ -153,6 +156,18 @@ public function fieldGetterMethodProvider(): array
['b08c6fff7dc5e1519b210800200c9a66', 'getTimeMid', 'c57d'],
['b08c6fff7dc5e1519b210800200c9a66', 'getVariant', 2],
['b08c6fff7dc5e1519b210800200c9a66', 'getVersion', 5],
['b08c6fff7dc5e1519b210800200c9a66', 'isNil', false],

// For 00000000-0000-0000-0000-000000000000
['00000000000000000000000000000000', 'getClockSeqHiAndReserved', '00'],
['00000000000000000000000000000000', 'getClockSeqLow', '00'],
['00000000000000000000000000000000', 'getNode', '000000000000'],
['00000000000000000000000000000000', 'getTimeHiAndVersion', '0000'],
['00000000000000000000000000000000', 'getTimeLow', '00000000'],
['00000000000000000000000000000000', 'getTimeMid', '0000'],
['00000000000000000000000000000000', 'getVariant', 0],
['00000000000000000000000000000000', 'getVersion', null],
['00000000000000000000000000000000', 'isNil', true],
];
}

Expand Down

0 comments on commit ad0544c

Please sign in to comment.