summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/ConstantTest.php70
-rw-r--r--tests/ManagerTest.php70
-rw-r--r--tests/SequenceTest.php53
-rw-r--r--tests/StructureTest.php70
-rw-r--r--tests/TimestampTest.php67
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);
+ }
+}