generate(); expect($cuid)->toBeInstanceOf(Cuid::class); expect($cuid->toString())->toStartWith('c'); expect($cuid->toString())->toHaveLength(Cuid::LENGTH); $currentTimeMs = intval(microtime(true) * 1000); expect($cuid->getTimestamp())->toBeGreaterThanOrEqual($currentTimeMs - 100); expect($cuid->getTimestamp())->toBeLessThanOrEqual($currentTimeMs + 100); }); it('generates Cuid at specific timestamp', function () { $generator = new CuidGenerator(new SecureRandomGenerator()); $timestamp = 1609459200000; // 2021-01-01 00:00:00.000 UTC $cuid = $generator->generateAt($timestamp); expect($cuid->getTimestamp())->toBe($timestamp); }); it('generates Cuid in the past', function () { $generator = new CuidGenerator(new SecureRandomGenerator()); $millisecondsAgo = 5000; // 5 seconds ago $cuid = $generator->generateInPast($millisecondsAgo); $expectedTimestamp = intval(microtime(true) * 1000) - $millisecondsAgo; expect($cuid->getTimestamp())->toBeGreaterThanOrEqual($expectedTimestamp - 100); expect($cuid->getTimestamp())->toBeLessThanOrEqual($expectedTimestamp + 100); }); it('generates batch of Cuids with incrementing counters', function () { $generator = CuidGenerator::createWithFingerprint(new SecureRandomGenerator(), 'test'); $generator->resetCounter(); // Start from 0 $cuids = $generator->generateBatch(5); expect($cuids)->toHaveCount(5); expect($cuids[0])->toBeInstanceOf(Cuid::class); // All should have the same timestamp and fingerprint $firstTimestamp = $cuids[0]->getTimestamp(); foreach ($cuids as $i => $cuid) { expect($cuid->getTimestamp())->toBe($firstTimestamp); expect($cuid->getFingerprint())->toBe('test'); expect($cuid->getCounter())->toBe($i); // Incrementing counter } // All should be unique $values = array_map(fn ($cuid) => $cuid->toString(), $cuids); $uniqueValues = array_unique($values); expect($uniqueValues)->toHaveCount(5); }); it('generates sequence of Cuids with incrementing timestamps', function () { $generator = new CuidGenerator(new SecureRandomGenerator()); $count = 3; $interval = 1000; // 1 second intervals $cuids = $generator->generateSequence($count, $interval); expect($cuids)->toHaveCount($count); // Check timestamps are incrementing for ($i = 1; $i < $count; $i++) { $timeDiff = $cuids[$i]->getTimestamp() - $cuids[$i - 1]->getTimestamp(); expect($timeDiff)->toBe($interval); } }); it('generates Cuid with specific counter', function () { $generator = new CuidGenerator(new SecureRandomGenerator()); $counter = 1234; $cuid = $generator->generateWithCounter($counter); expect($cuid->getCounter())->toBe($counter); }); it('increments counter correctly', function () { $generator = CuidGenerator::createWithFingerprint(new SecureRandomGenerator(), 'test'); $generator->resetCounter(); $cuid1 = $generator->generate(); $cuid2 = $generator->generate(); $cuid3 = $generator->generate(); expect($cuid1->getCounter())->toBe(0); expect($cuid2->getCounter())->toBe(1); expect($cuid3->getCounter())->toBe(2); }); it('counter increments and resets correctly', function () { $generator = CuidGenerator::createWithFingerprint(new SecureRandomGenerator(), 'test'); $generator->resetCounter(); // Generate a few Cuids to test counter increment $cuid1 = $generator->generate(); $cuid2 = $generator->generate(); $cuid3 = $generator->generate(); expect($cuid1->getCounter())->toBe(0); expect($cuid2->getCounter())->toBe(1); expect($cuid3->getCounter())->toBe(2); expect($generator->getCurrentCounter())->toBe(3); // Test reset $generator->resetCounter(); expect($generator->getCurrentCounter())->toBe(0); }); it('validates Cuid strings', function () { $generator = new CuidGenerator(new SecureRandomGenerator()); $validCuid = $generator->generate(); expect($generator->isValid($validCuid->toString()))->toBeTrue(); expect($generator->isValid('invalid'))->toBeFalse(); expect($generator->isValid(''))->toBeFalse(); expect($generator->isValid('x' . str_repeat('a', 24)))->toBeFalse(); // Wrong prefix expect($generator->isValid('c' . str_repeat('!', 24)))->toBeFalse(); // Invalid characters }); it('parses Cuid strings', function () { $generator = new CuidGenerator(new SecureRandomGenerator()); $original = $generator->generate(); $parsed = $generator->parse($original->toString()); expect($parsed->equals($original))->toBeTrue(); }); it('generates consistent fingerprints', function () { $generator = new CuidGenerator(new SecureRandomGenerator()); $cuid1 = $generator->generate(); $cuid2 = $generator->generate(); // Same generator should produce same fingerprint expect($cuid1->getFingerprint())->toBe($cuid2->getFingerprint()); expect($generator->isSameGenerator($cuid1))->toBeTrue(); expect($generator->isSameGenerator($cuid2))->toBeTrue(); }); it('uses custom fingerprint', function () { $customFingerprint = 'abcd'; $generator = CuidGenerator::createWithFingerprint(new SecureRandomGenerator(), $customFingerprint); expect($generator->getFingerprint())->toBe($customFingerprint); $cuid = $generator->generate(); expect($cuid->getFingerprint())->toBe($customFingerprint); }); it('generates different fingerprints for different generators', function () { $generator1 = CuidGenerator::createWithFingerprint(new SecureRandomGenerator(), 'aaaa'); $generator2 = CuidGenerator::createWithFingerprint(new SecureRandomGenerator(), 'bbbb'); expect($generator1->getFingerprint())->not->toBe($generator2->getFingerprint()); $cuid1 = $generator1->generate(); $cuid2 = $generator2->generate(); expect($generator1->isSameGenerator($cuid2))->toBeFalse(); expect($generator2->isSameGenerator($cuid1))->toBeFalse(); }); it('resets counter correctly', function () { $generator = new CuidGenerator(new SecureRandomGenerator()); // Generate a few to increment counter $generator->generate(); $generator->generate(); expect($generator->getCurrentCounter())->toBeGreaterThan(0); // Reset and check $generator->resetCounter(); expect($generator->getCurrentCounter())->toBe(0); }); it('throws exception for invalid batch count', function () { $generator = new CuidGenerator(new SecureRandomGenerator()); expect(fn () => $generator->generateBatch(0)) ->toThrow(InvalidArgumentException::class, 'Count must be positive'); expect(fn () => $generator->generateBatch(10001)) ->toThrow(InvalidArgumentException::class, 'Batch size cannot exceed 10000'); }); it('throws exception for invalid sequence count', function () { $generator = new CuidGenerator(new SecureRandomGenerator()); expect(fn () => $generator->generateSequence(0)) ->toThrow(InvalidArgumentException::class, 'Count must be positive'); expect(fn () => $generator->generateSequence(1001)) ->toThrow(InvalidArgumentException::class, 'Sequence size cannot exceed 1000'); }); it('throws exception for negative interval', function () { $generator = new CuidGenerator(new SecureRandomGenerator()); expect(fn () => $generator->generateSequence(3, -1)) ->toThrow(InvalidArgumentException::class, 'Interval must be non-negative'); }); it('throws exception for invalid counter value', function () { $generator = new CuidGenerator(new SecureRandomGenerator()); $maxCounter = 36 ** Cuid::COUNTER_LENGTH; expect(fn () => $generator->generateWithCounter(-1)) ->toThrow(InvalidArgumentException::class, 'Counter must be between 0 and'); expect(fn () => $generator->generateWithCounter($maxCounter)) ->toThrow(InvalidArgumentException::class, 'Counter must be between 0 and'); }); it('throws exception for invalid fingerprint length', function () { expect(fn () => CuidGenerator::createWithFingerprint(new SecureRandomGenerator(), 'abc')) ->toThrow(InvalidArgumentException::class, 'Fingerprint must be exactly 4 characters'); expect(fn () => CuidGenerator::createWithFingerprint(new SecureRandomGenerator(), 'abcde')) ->toThrow(InvalidArgumentException::class, 'Fingerprint must be exactly 4 characters'); }); it('creates generator using factory method', function () { $randomGen = new SecureRandomGenerator(); $generator = CuidGenerator::create($randomGen); expect($generator)->toBeInstanceOf(CuidGenerator::class); expect($generator->generate())->toBeInstanceOf(Cuid::class); }); it('generates sortable Cuids by timestamp', function () { $generator = new CuidGenerator(new SecureRandomGenerator()); $older = $generator->generateInPast(5000); $newer = $generator->generate(); // Lexicographic comparison should match timestamp order expect($older->compare($newer))->toBeLessThan(0); expect($older->toString() < $newer->toString())->toBeTrue(); }); it('generates unique Cuids across multiple calls', function () { $generator = new CuidGenerator(new SecureRandomGenerator()); $cuids = []; $count = 1000; for ($i = 0; $i < $count; $i++) { $cuids[] = $generator->generate()->toString(); } $uniqueCuids = array_unique($cuids); expect($uniqueCuids)->toHaveCount($count); }); it('generates only lowercase characters', function () { $generator = new CuidGenerator(new SecureRandomGenerator()); $cuid = $generator->generate(); $cuidString = $cuid->toString(); expect($cuidString)->toBe(strtolower($cuidString)); // Should already be lowercase expect($cuidString)->toMatch('/^[c0-9a-z]+$/'); // Only valid Base36 chars });