toString())->toBe($value); expect($cuid->getValue())->toBe($value); }); it('creates Cuid from components', function () { $timestamp = 1609459200000; // 2021-01-01 00:00:00.000 UTC $counter = 1234; $fingerprint = 'abcd'; $random = '12345678'; $cuid = Cuid::fromComponents($timestamp, $counter, $fingerprint, $random); expect($cuid->getTimestamp())->toBe($timestamp); expect($cuid->getCounter())->toBe($counter); expect($cuid->getFingerprint())->toBe($fingerprint); expect($cuid->getRandom())->toBe($random); expect($cuid->toString())->toStartWith('c'); }); it('validates Cuid length', function () { expect(fn () => Cuid::fromString('c' . str_repeat('a', 30))) ->toThrow(InvalidArgumentException::class, 'Cuid must be exactly 25 characters long'); expect(fn () => Cuid::fromString('ctooshort')) ->toThrow(InvalidArgumentException::class, 'Cuid must be exactly 25 characters long'); }); it('validates Cuid prefix', function () { expect(fn () => Cuid::fromString('x' . str_repeat('a', 24))) ->toThrow(InvalidArgumentException::class, 'Cuid must start with "c"'); }); it('validates Cuid characters', function () { expect(fn () => Cuid::fromString('c' . str_repeat('!', 24))) ->toThrow(InvalidArgumentException::class, 'Cuid contains invalid characters'); }); it('validates component lengths', function () { $timestamp = 1609459200000; $counter = 1234; expect(fn () => Cuid::fromComponents($timestamp, $counter, 'abc', '12345678')) ->toThrow(InvalidArgumentException::class, 'Fingerprint must be exactly 4 characters'); expect(fn () => Cuid::fromComponents($timestamp, $counter, 'abcd', '1234567')) ->toThrow(InvalidArgumentException::class, 'Random part must be exactly 8 characters'); }); it('parses timestamp correctly', function () { $expectedTimestamp = 1609459200000; // 2021-01-01 00:00:00.000 UTC $cuid = Cuid::fromComponents($expectedTimestamp, 0, 'abcd', '12345678'); expect($cuid->getTimestamp())->toBe($expectedTimestamp); }); it('gets DateTime from timestamp', function () { $timestamp = 1609459200500; // 2021-01-01 00:00:00.500 UTC $cuid = Cuid::fromComponents($timestamp, 0, 'abcd', '12345678'); $dateTime = $cuid->getDateTime(); expect($dateTime->getTimestamp())->toBe(1609459200); // Seconds part expect($dateTime->format('u'))->toBe('500000'); // Microseconds part }); it('checks equality between Cuids', function () { $value = 'cjld2cjxh0000qzrmn831i7rn'; $cuid1 = Cuid::fromString($value); $cuid2 = Cuid::fromString($value); $cuid3 = Cuid::fromString('cjld2cjxh0000qzrmn831i7ro'); expect($cuid1->equals($cuid2))->toBeTrue(); expect($cuid1->equals($cuid3))->toBeFalse(); }); it('compares Cuids for sorting', function () { $value1 = 'cjld2cjxh0000qzrmn831i7rn'; $value2 = 'cjld2cjxh0000qzrmn831i7ro'; // Lexicographically greater $cuid1 = Cuid::fromString($value1); $cuid2 = Cuid::fromString($value2); expect($cuid1->compare($cuid2))->toBeLessThan(0); expect($cuid2->compare($cuid1))->toBeGreaterThan(0); expect($cuid1->compare($cuid1))->toBe(0); }); it('checks age comparisons', function () { $olderTimestamp = 1609459200000; // 2021-01-01 00:00:00.000 UTC $newerTimestamp = 1609459201000; // 1 second later $olderCuid = Cuid::fromComponents($olderTimestamp, 0, 'abcd', '12345678'); $newerCuid = Cuid::fromComponents($newerTimestamp, 0, 'abcd', '87654321'); expect($olderCuid->isOlderThan($newerCuid))->toBeTrue(); expect($newerCuid->isNewerThan($olderCuid))->toBeTrue(); expect($olderCuid->isNewerThan($newerCuid))->toBeFalse(); expect($newerCuid->isOlderThan($olderCuid))->toBeFalse(); }); it('calculates age in milliseconds', function () { $timestamp = intval(microtime(true) * 1000) - 5000; // 5 seconds ago $cuid = Cuid::fromComponents($timestamp, 0, 'abcd', '12345678'); $ageMs = $cuid->getAgeInMilliseconds(); expect($ageMs)->toBeGreaterThanOrEqual(4900); // Allow some tolerance expect($ageMs)->toBeLessThanOrEqual(5100); }); it('calculates age in seconds', function () { $timestamp = intval(microtime(true) * 1000) - 3000; // 3 seconds ago $cuid = Cuid::fromComponents($timestamp, 0, 'abcd', '12345678'); $ageSeconds = $cuid->getAgeInSeconds(); expect($ageSeconds)->toBeGreaterThanOrEqual(2.9); expect($ageSeconds)->toBeLessThanOrEqual(3.1); }); it('checks same process', function () { $cuid1 = Cuid::fromComponents(1609459200000, 0, 'abcd', '12345678'); $cuid2 = Cuid::fromComponents(1609459201000, 1, 'abcd', '87654321'); // Same fingerprint $cuid3 = Cuid::fromComponents(1609459202000, 2, 'efgh', '11223344'); // Different fingerprint expect($cuid1->isSameProcess($cuid2))->toBeTrue(); expect($cuid1->isSameProcess($cuid3))->toBeFalse(); }); it('converts to string using magic method', function () { $value = 'cjld2cjxh0000qzrmn831i7rn'; $cuid = Cuid::fromString($value); expect((string)$cuid)->toBe($value); }); it('parses all components correctly', function () { // Create a known Cuid and verify all components are parsed correctly $timestamp = 1609459200000; $counter = 1234; $fingerprint = 'test'; $random = 'abcd1234'; $cuid = Cuid::fromComponents($timestamp, $counter, $fingerprint, $random); // Parse it back $parsed = Cuid::fromString($cuid->toString()); expect($parsed->getTimestamp())->toBe($timestamp); expect($parsed->getCounter())->toBe($counter); expect($parsed->getFingerprint())->toBe($fingerprint); expect($parsed->getRandom())->toBe($random); }); it('handles Base36 conversion correctly', function () { // Test with various numbers to ensure Base36 conversion works $testCases = [ ['timestamp' => 1609459200000, 'counter' => 0], ['timestamp' => 1609459200000, 'counter' => 35], // Max single digit in Base36 ['timestamp' => 1609459200000, 'counter' => 36], // Two digits in Base36 ['timestamp' => 1609459200000, 'counter' => 1295], // Multiple digits ]; foreach ($testCases as $case) { $cuid = Cuid::fromComponents( $case['timestamp'], $case['counter'], 'test', 'abcd1234' ); $parsed = Cuid::fromString($cuid->toString()); expect($parsed->getTimestamp())->toBe($case['timestamp']); expect($parsed->getCounter())->toBe($case['counter']); } }); it('throws exception for empty Cuid', function () { expect(fn () => Cuid::fromString('')) ->toThrow(InvalidArgumentException::class, 'Cuid cannot be empty'); }); it('maintains lexicographic ordering by timestamp', function () { $timestamp1 = 1609459200000; $timestamp2 = 1609459201000; // 1 second later $cuid1 = Cuid::fromComponents($timestamp1, 0, 'abcd', '12345678'); $cuid2 = Cuid::fromComponents($timestamp2, 0, 'abcd', '12345678'); // String comparison should match timestamp order expect($cuid1->toString() < $cuid2->toString())->toBeTrue(); }); it('handles maximum timestamp values', function () { $maxTimestamp = (36 ** 8) - 1; // Max value for 8 Base36 digits $cuid = Cuid::fromComponents($maxTimestamp, 0, 'abcd', '12345678'); expect($cuid->getTimestamp())->toBe($maxTimestamp); }); it('handles maximum counter values', function () { $maxCounter = (36 ** 4) - 1; // Max value for 4 Base36 digits $cuid = Cuid::fromComponents(1609459200000, $maxCounter, 'abcd', '12345678'); expect($cuid->getCounter())->toBe($maxCounter); });