diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/ConstantTest.php | 70 | ||||
| -rw-r--r-- | tests/ManagerTest.php | 70 | ||||
| -rw-r--r-- | tests/SequenceTest.php | 53 | ||||
| -rw-r--r-- | tests/StructureTest.php | 70 | ||||
| -rw-r--r-- | tests/TimestampTest.php | 67 |
5 files changed, 330 insertions, 0 deletions
diff --git a/tests/ConstantTest.php b/tests/ConstantTest.php new file mode 100644 index 0000000..78d1be3 --- /dev/null +++ b/tests/ConstantTest.php @@ -0,0 +1,70 @@ +<?php + +declare(strict_types=1); + +namespace Zhineng\Snowflake\Tests; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; +use Zhineng\Snowflake\Constant; + +#[CoversClass(Constant::class)] +final class ConstantTest extends TestCase +{ + public function testConstantCanBeInitialized(): void + { + $field = new Constant('machine_id', 10); + $this->assertSame('machine_id', $field->name); + $this->assertSame(10, $field->bits); + $this->assertSame(0, $field->value); + } + + public function testMaxValueShouldBeCalculatedCorrectly(): void + { + $field = new Constant('machine_id', 10); + $maxValue = (1 << 10) - 1; + $this->assertSame($maxValue, $field->maxValue()); + } + + public function testInitialValueCanBeSet(): void + { + $field = new Constant('machine_id', 10, 2); + $this->assertSame(2, $field->value); + } + + public function testInitialValueMustBePositive(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Field value must be non-negative.'); + new Constant('machine_id', 10, -1); + } + + public function testInitialValueMustNotExceedMaxValue(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Field value 1024 exceeds maximum 1023 for 10 bits.'); + new Constant('machine_id', 10, 1024); + } + + public function testConstantHasMakeFactoryMethod(): void + { + $field = Constant::make('machine_id', 10, 5); + $this->assertSame('machine_id', $field->name); + $this->assertSame(10, $field->bits); + $this->assertSame(5, $field->value); + } + + public function testBitsMustBeAtLeastOne(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Bits must be between 1 and 63.'); + new Constant('machine_id', 0); + } + + public function testBitsMustNotExceed63(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Bits must be between 1 and 63.'); + new Constant('machine_id', 64); + } +} diff --git a/tests/ManagerTest.php b/tests/ManagerTest.php new file mode 100644 index 0000000..dc81a06 --- /dev/null +++ b/tests/ManagerTest.php @@ -0,0 +1,70 @@ +<?php + +declare(strict_types=1); + +namespace Zhineng\Snowflake\Tests; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; +use Zhineng\Snowflake\Constant; +use Zhineng\Snowflake\Field; +use Zhineng\Snowflake\Manager; +use Zhineng\Snowflake\Sequence; +use Zhineng\Snowflake\Structure; +use Zhineng\Snowflake\Timestamp; + +#[CoversClass(Manager::class)] +final class ManagerTest extends TestCase +{ + public function testNextIdResolution(): void + { + $struct = new Structure; + $struct->add(Sequence::make('sequence', 12)); + $struct->add(Constant::make('instance_id', 10)); + $struct->add(Timestamp::make()); + + $manager = new Manager; + $manager->structureUsing($struct); + $this->assertNotSame($manager->nextId(), $manager->nextId()); + } + + public function testSequenceShouldBeResetWhenAnyOtherFieldChanges(): void + { + $struct = new Structure; + $struct->add(Sequence::make('sequence', 12)); + $struct->add($field = new class ('test', 10) extends Field { + public int $value = 0; + + public function value(): int + { + return $this->value; + } + + public function setValue(int $value): void + { + $this->value = $value; + } + }); + + $manager = new Manager; + $manager->structureUsing($struct); + + $id1 = $manager->nextId(); + $id2 = $manager->nextId(); + $field->setValue(1); + $id3 = $manager->nextId(); + + $sequenceMask = (1 << 12) - 1; + $this->assertSame(0, $id1 & $sequenceMask); + $this->assertSame(1, $id2 & $sequenceMask); + $this->assertSame(0, $id3 & $sequenceMask); + } + + public function testExceptionShouldBeThrownWhenMissingStructure(): void + { + $manager = new Manager; + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('ID structure is not defined.'); + $manager->nextId(); + } +} diff --git a/tests/SequenceTest.php b/tests/SequenceTest.php new file mode 100644 index 0000000..fc02d87 --- /dev/null +++ b/tests/SequenceTest.php @@ -0,0 +1,53 @@ +<?php + +declare(strict_types=1); + +namespace Zhineng\Snowflake\Tests; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; +use Zhineng\Snowflake\Sequence; + +#[CoversClass(Sequence::class)] +final class SequenceTest extends TestCase +{ + public function testSequenceCanBeInitialized(): void + { + $seq = new Sequence('sequence', 12); + $this->assertSame('sequence', $seq->name); + $this->assertSame(12, $seq->bits); + } + + public function testSequenceHasMakeFactoryMethod(): void + { + $seq = Sequence::make('sequence', 12); + $this->assertSame('sequence', $seq->name); + $this->assertSame(12, $seq->bits); + } + + public function testNextValueResolution(): void + { + $seq = new Sequence('sequence', 12); + $this->assertSame(0, $seq->next()); + $this->assertSame(1, $seq->next()); + $this->assertSame(2, $seq->next()); + } + + public function testExceptionShouldBeThrownWhenMaxValueExceeded(): void + { + $seq = new Sequence('sequence', 1); // Max value is 1 + $this->assertSame(0, $seq->next()); + $this->assertSame(1, $seq->next()); + $this->expectException(\OverflowException::class); + $this->expectExceptionMessage('Sequence "sequence" exceeded its maximum value of 1.'); + $seq->next(); + } + + public function testSequenceCanBeReset(): void + { + $seq = new Sequence('sequence', 12); + $this->assertSame(0, $seq->next()); + $this->assertSame(1, $seq->next()); + $this->assertSame(0, $seq->reset()->next()); + } +} diff --git a/tests/StructureTest.php b/tests/StructureTest.php new file mode 100644 index 0000000..80d94fe --- /dev/null +++ b/tests/StructureTest.php @@ -0,0 +1,70 @@ +<?php + +declare(strict_types=1); + +namespace Zhineng\Snowflake\Tests; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; +use Zhineng\Snowflake\Constant; +use Zhineng\Snowflake\Sequence; +use Zhineng\Snowflake\Structure; + +#[CoversClass(Structure::class)] +final class StructureTest extends TestCase +{ + public function testAddFieldAndRetrieveComponents(): void + { + $struct = new Structure; + $struct->add(Constant::make('machine_id', 10)); + $this->assertCount(1, $struct->components()); + } + + public function testAddMultipleFields(): void + { + $struct = new Structure; + $struct->add(Constant::make('machine_id', 5)); + $struct->add(Constant::make('datacenter_id', 5)); + $this->assertCount(2, $struct->components()); + } + + public function testOffsetsAreCalculatedCorrectly(): void + { + $struct = new Structure; + $struct->add($machineId = Constant::make('machine_id', 5)); + $struct->add($dataCenterId = Constant::make('datacenter_id', 5)); + $this->assertSame(0, $machineId->offset()); + $this->assertSame(5, $dataCenterId->offset()); + } + + public function testSizeResolution(): void + { + $struct = new Structure; + $struct->add(Constant::make('machine_id', 5)); + $struct->add(Constant::make('datacenter_id', 5)); + $this->assertSame(10, $struct->size()); + } + + public function testAtMostOneSequenceIsAllowed(): void + { + $struct = new Structure; + $struct->add(Sequence::make('seq1', 12)); + + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('Only one sequence field is allowed in a structure.'); + + $struct->add(Sequence::make('seq2', 12)); + } + + public function testTotalSizeCannotExceed63Bits(): void + { + $struct = new Structure; + $struct->add(Constant::make('field1', 32)); + $struct->add(Constant::make('field2', 31)); + + $this->expectException(\OverflowException::class); + $this->expectExceptionMessage('Total structure size cannot exceed 63 bits.'); + + $struct->add(Constant::make('field3', 1)); + } +} diff --git a/tests/TimestampTest.php b/tests/TimestampTest.php new file mode 100644 index 0000000..d157adf --- /dev/null +++ b/tests/TimestampTest.php @@ -0,0 +1,67 @@ +<?php + +declare(strict_types=1); + +namespace Zhineng\Snowflake\Tests; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; +use Zhineng\Snowflake\Timestamp; + +#[CoversClass(Timestamp::class)] +final class TimestampTest extends TestCase +{ + public function testTimestampCanBeInitialized(): void + { + $field = new Timestamp('timestamp', 41); + $this->assertSame('timestamp', $field->name); + $this->assertSame(41, $field->bits); + } + + public function testTimestampHasDefaultParameterValues(): void + { + $field = new Timestamp; + $this->assertSame('timestamp', $field->name); + $this->assertSame(41, $field->bits); + } + + public function testTimestampHasMakeFactoryMethod(): void + { + $field = Timestamp::make('timestamp', 41); + $this->assertSame('timestamp', $field->name); + $this->assertSame(41, $field->bits); + } + + public function testTimestampHasDynamicValue(): void + { + $field = new Timestamp; + $value1 = $field->value(); + usleep(1000); // Sleep for 1 millisecond + $value2 = $field->value(); + $this->assertGreaterThan($value1, $value2); + } + + public function testEpochCanBeCustomized(): void + { + $epoch = new \DateTime('2026-01-01 00:00:00'); + $field = new Timestamp('timestamp', 41, $epoch); + $now = (int) floor(microtime(as_float: true) * 1000); + $this->assertSame($now - $epoch->getTimestamp() * 1000, $field->value()); + } + + public function testEpochCanBeSetAsInteger(): void + { + $epoch = new \DateTime('2026-01-01 00:00:00'); + $timestampInMillis = $epoch->getTimestamp() * 1000; + $field = new Timestamp('timestamp', 41, $timestampInMillis); + $now = (int) floor(microtime(as_float: true) * 1000); + $this->assertSame($now - $timestampInMillis, $field->value()); + } + + public function testEpochMustBeNonNegative(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Epoch must be non-negative.'); + new Timestamp('timestamp', 41, -1); + } +} |
