generate(); expect($nanoId)->toBeInstanceOf(NanoId::class); expect($nanoId->getLength())->toBe(NanoId::DEFAULT_SIZE); }); it('creates generator with custom settings', function () { $generator = new NanoIdGenerator(new SecureRandomGenerator(), 10, 'ABC123'); $nanoId = $generator->generate(); expect($nanoId->getLength())->toBe(10); expect($nanoId->matchesAlphabet('ABC123'))->toBeTrue(); }); it('generates NanoId with custom size', function () { $generator = new NanoIdGenerator(new SecureRandomGenerator()); $nanoId = $generator->generateWithSize(15); expect($nanoId->getLength())->toBe(15); }); it('generates NanoId with custom alphabet', function () { $generator = new NanoIdGenerator(new SecureRandomGenerator()); $nanoId = $generator->generateWithAlphabet('XYZ789'); expect($nanoId->matchesAlphabet('XYZ789'))->toBeTrue(); }); it('generates custom NanoId with size and alphabet', function () { $generator = new NanoIdGenerator(new SecureRandomGenerator()); $nanoId = $generator->generateCustom(8, 'ABCD'); expect($nanoId->getLength())->toBe(8); expect($nanoId->matchesAlphabet('ABCD'))->toBeTrue(); }); it('generates safe NanoId', function () { $generator = new NanoIdGenerator(new SecureRandomGenerator()); $nanoId = $generator->generateSafe(); expect($nanoId->isSafe())->toBeTrue(); }); it('generates numeric NanoId', function () { $generator = new NanoIdGenerator(new SecureRandomGenerator()); $nanoId = $generator->generateNumeric(); expect($nanoId->isNumeric())->toBeTrue(); }); it('generates lowercase alphanumeric NanoId', function () { $generator = new NanoIdGenerator(new SecureRandomGenerator()); $nanoId = $generator->generateLowercase(); expect($nanoId->toString())->toMatch('/^[0-9a-z]+$/'); }); it('generates uppercase alphanumeric NanoId', function () { $generator = new NanoIdGenerator(new SecureRandomGenerator()); $nanoId = $generator->generateUppercase(); expect($nanoId->toString())->toMatch('/^[0-9A-Z]+$/'); }); it('generates NanoId for entity types', function () { $generator = new NanoIdGenerator(new SecureRandomGenerator()); $userNanoId = $generator->generateForEntity('user'); expect($userNanoId->toString())->toStartWith('usr_'); $orderNanoId = $generator->generateForEntity('order'); expect($orderNanoId->toString())->toStartWith('ord_'); $customNanoId = $generator->generateForEntity('widget'); expect($customNanoId->toString())->toStartWith('wid_'); }); it('generates time-prefixed NanoId', function () { $generator = new NanoIdGenerator(new SecureRandomGenerator()); $nanoId = $generator->generateTimePrefixed(); expect($nanoId->toString())->toContain('_'); // Check that prefix is a valid base36 timestamp $parts = explode('_', $nanoId->toString()); expect($parts)->toHaveCount(2); $timestamp = base_convert($parts[0], 36, 10); expect($timestamp)->toBeNumeric(); expect((int)$timestamp)->toBeLessThanOrEqual(time()); expect((int)$timestamp)->toBeGreaterThan(time() - 10); // Within last 10 seconds }); it('generates batch of unique NanoIds', function () { $generator = new NanoIdGenerator(new SecureRandomGenerator()); $batch = $generator->generateBatch(100); expect($batch)->toHaveCount(100); expect($batch[0])->toBeInstanceOf(NanoId::class); // Check uniqueness $values = array_map(fn ($id) => $id->toString(), $batch); $uniqueValues = array_unique($values); expect($uniqueValues)->toHaveCount(100); }); it('validates NanoIds correctly', function () { $generator = new NanoIdGenerator(new SecureRandomGenerator()); $validId = $generator->generate()->toString(); expect($generator->isValid($validId))->toBeTrue(); expect($generator->isValid(''))->toBeFalse(); expect($generator->isValid(str_repeat('a', 256)))->toBeFalse(); // Test with custom alphabet generator $customGenerator = new NanoIdGenerator(new SecureRandomGenerator(), 10, 'ABC'); expect($customGenerator->isValid('ABCABC'))->toBeTrue(); expect($customGenerator->isValid('XYZ123'))->toBeFalse(); }); it('creates generator using factory methods', function () { $randomGen = new SecureRandomGenerator(); $defaultGenerator = NanoIdGenerator::create($randomGen); expect($defaultGenerator->generate())->toBeInstanceOf(NanoId::class); $safeGenerator = NanoIdGenerator::createSafe($randomGen); expect($safeGenerator->generate()->isSafe())->toBeTrue(); $numericGenerator = NanoIdGenerator::createNumeric($randomGen); expect($numericGenerator->generate()->isNumeric())->toBeTrue(); }); it('throws exception for invalid default size', function () { $randomGen = new SecureRandomGenerator(); expect(fn () => new NanoIdGenerator($randomGen, 0))->toThrow(InvalidArgumentException::class, 'Default size must be between 1 and 255'); expect(fn () => new NanoIdGenerator($randomGen, 256))->toThrow(InvalidArgumentException::class, 'Default size must be between 1 and 255'); }); it('throws exception for empty default alphabet', function () { $randomGen = new SecureRandomGenerator(); expect(fn () => new NanoIdGenerator($randomGen, 21, ''))->toThrow(InvalidArgumentException::class, 'Default alphabet cannot be empty'); }); it('throws exception for invalid batch count', function () { $generator = new NanoIdGenerator(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 generation parameters', function () { $generator = new NanoIdGenerator(new SecureRandomGenerator()); // Test invalid size expect(fn () => $generator->generateWithSize(0))->toThrow(InvalidArgumentException::class, 'Size must be between 1 and 255'); expect(fn () => $generator->generateWithSize(256))->toThrow(InvalidArgumentException::class, 'Size must be between 1 and 255'); // Test empty alphabet expect(fn () => $generator->generateWithAlphabet(''))->toThrow(InvalidArgumentException::class, 'Alphabet cannot be empty'); // Test alphabet too long $longAlphabet = str_repeat('a', 256); expect(fn () => $generator->generateWithAlphabet($longAlphabet))->toThrow(InvalidArgumentException::class, 'Alphabet cannot exceed 255 characters'); }); it('generates different entity type prefixes correctly', function () { $generator = new NanoIdGenerator(new SecureRandomGenerator()); $prefixes = [ 'user' => 'usr_', 'order' => 'ord_', 'product' => 'prd_', 'session' => 'ses_', 'token' => 'tok_', 'transaction' => 'txn_', 'invoice' => 'inv_', 'customer' => 'cus_', 'payment' => 'pay_', 'subscription' => 'sub_', ]; foreach ($prefixes as $entity => $expectedPrefix) { $nanoId = $generator->generateForEntity($entity); expect($nanoId->toString())->toStartWith($expectedPrefix); } }); it('generates unique IDs across multiple calls', function () { $generator = new NanoIdGenerator(new SecureRandomGenerator()); $ids = []; $count = 1000; for ($i = 0; $i < $count; $i++) { $ids[] = $generator->generate()->toString(); } $uniqueIds = array_unique($ids); expect(count($uniqueIds))->toBe($count); });