Files
michaelschiemer/tests/Unit/Framework/Cuid/CuidGeneratorTest.php
Michael Schiemer 55a330b223 Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
2025-08-11 20:13:26 +02:00

277 lines
9.9 KiB
PHP

<?php
declare(strict_types=1);
use App\Framework\Cuid\Cuid;
use App\Framework\Cuid\CuidGenerator;
use App\Framework\Random\SecureRandomGenerator;
it('generates Cuid with current timestamp', function () {
$generator = new CuidGenerator(new SecureRandomGenerator());
$cuid = $generator->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
});