generate('api_key'); expect($token)->toBeInstanceOf(SecureToken::class); expect($token->getType())->toBe('api_key'); expect($token->getFormat())->toBe(SecureTokenGenerator::FORMAT_BASE64_URL); expect($token->getLength())->toBe(32); }); it('generates API key with prefix', function () { $generator = new SecureTokenGenerator(new SecureRandomGenerator()); $token = $generator->generateApiKey('myapp'); expect($token->getType())->toBe(SecureTokenGenerator::TYPE_API_KEY); expect($token->getPrefix())->toBe('myapp'); expect($token->getValue())->toStartWith('myapp_'); }); it('generates session token', function () { $generator = new SecureTokenGenerator(new SecureRandomGenerator()); $token = $generator->generateSessionToken(); expect($token->getType())->toBe(SecureTokenGenerator::TYPE_SESSION); expect($token->getMetadataValue('purpose'))->toBe('session_management'); expect($token->getMetadataValue('secure'))->toBeTrue(); }); it('generates CSRF token', function () { $generator = new SecureTokenGenerator(new SecureRandomGenerator()); $token = $generator->generateCsrfToken(); expect($token->getType())->toBe(SecureTokenGenerator::TYPE_CSRF); expect($token->getMetadataValue('purpose'))->toBe('csrf_protection'); }); it('generates verification token', function () { $generator = new SecureTokenGenerator(new SecureRandomGenerator()); $token = $generator->generateVerificationToken('email_verification'); expect($token->getType())->toBe(SecureTokenGenerator::TYPE_VERIFICATION); expect($token->getMetadataValue('purpose'))->toBe('email_verification'); expect($token->getMetadataValue('single_use'))->toBeTrue(); }); it('generates OTP token with correct digits', function () { $generator = new SecureTokenGenerator(new SecureRandomGenerator()); $token = $generator->generateOtpToken(6); expect($token->getType())->toBe(SecureTokenGenerator::TYPE_OTP); expect($token->getValue())->toMatch('/^\d{6}$/'); expect($token->getLength())->toBe(6); expect($token->getMetadataValue('digits'))->toBe(6); }); it('generates webhook token', function () { $generator = new SecureTokenGenerator(new SecureRandomGenerator()); $token = $generator->generateWebhookToken(); expect($token->getType())->toBe(SecureTokenGenerator::TYPE_WEBHOOK); expect($token->getPrefix())->toBe('whsec'); expect($token->getValue())->toStartWith('whsec_'); }); it('generates bearer token', function () { $generator = new SecureTokenGenerator(new SecureRandomGenerator()); $token = $generator->generateBearerToken(); expect($token->getType())->toBe(SecureTokenGenerator::TYPE_BEARER); expect($token->getMetadataValue('purpose'))->toBe('api_authorization'); }); it('generates refresh token with longer length', function () { $generator = new SecureTokenGenerator(new SecureRandomGenerator()); $token = $generator->generateRefreshToken(); expect($token->getType())->toBe(SecureTokenGenerator::TYPE_REFRESH); expect($token->getLength())->toBe(64); expect($token->getMetadataValue('long_lived'))->toBeTrue(); }); it('generates tokens in different formats', function () { $generator = new SecureTokenGenerator(new SecureRandomGenerator()); $base64Token = $generator->generate(SecureTokenGenerator::TYPE_API_KEY, 32, SecureTokenGenerator::FORMAT_BASE64); $hexToken = $generator->generate(SecureTokenGenerator::TYPE_API_KEY, 32, SecureTokenGenerator::FORMAT_HEX); $base32Token = $generator->generate(SecureTokenGenerator::TYPE_API_KEY, 32, SecureTokenGenerator::FORMAT_BASE32); expect($base64Token->getFormat())->toBe(SecureTokenGenerator::FORMAT_BASE64); expect($hexToken->getFormat())->toBe(SecureTokenGenerator::FORMAT_HEX); expect($base32Token->getFormat())->toBe(SecureTokenGenerator::FORMAT_BASE32); // Check format patterns expect($hexToken->getValue())->toMatch('/^[0-9a-f]+$/'); expect($base32Token->getValue())->toMatch('/^[A-Z2-7]+$/'); }); it('generates custom tokens with alphabet', function () { $generator = new SecureTokenGenerator(new SecureRandomGenerator()); $alphabet = '0123456789ABCDEF'; $token = $generator->generateCustom($alphabet, 16); expect($token->getValue())->toMatch('/^[0-9A-F]{16}$/'); expect($token->getMetadataValue('alphabet'))->toBe($alphabet); expect($token->getMetadataValue('alphabet_size'))->toBe(16); }); it('generates batch of tokens', function () { $generator = new SecureTokenGenerator(new SecureRandomGenerator()); $tokens = $generator->generateBatch('session', 5, 32); expect($tokens)->toHaveCount(5); expect($tokens[0])->toBeInstanceOf(SecureToken::class); // All tokens should be unique $values = array_map(fn ($token) => $token->getValue(), $tokens); $uniqueValues = array_unique($values); expect($uniqueValues)->toHaveCount(5); }); it('validates token formats correctly', function () { $generator = new SecureTokenGenerator(new SecureRandomGenerator()); expect($generator->isValidFormat('dGVzdA', SecureTokenGenerator::FORMAT_BASE64))->toBeTrue(); expect($generator->isValidFormat('dGVzdA-_', SecureTokenGenerator::FORMAT_BASE64_URL))->toBeTrue(); expect($generator->isValidFormat('deadbeef', SecureTokenGenerator::FORMAT_HEX))->toBeTrue(); expect($generator->isValidFormat('MFRGG', SecureTokenGenerator::FORMAT_BASE32))->toBeTrue(); expect($generator->isValidFormat('Test123', SecureTokenGenerator::FORMAT_ALPHANUMERIC))->toBeTrue(); // Invalid formats expect($generator->isValidFormat('invalid!@#', SecureTokenGenerator::FORMAT_BASE64_URL))->toBeFalse(); expect($generator->isValidFormat('invalidhex', SecureTokenGenerator::FORMAT_HEX))->toBeFalse(); }); it('calculates token entropy correctly', function () { $generator = new SecureTokenGenerator(new SecureRandomGenerator()); $base64Token = $generator->generate(SecureTokenGenerator::TYPE_API_KEY, 32, SecureTokenGenerator::FORMAT_BASE64_URL); $hexToken = $generator->generate(SecureTokenGenerator::TYPE_API_KEY, 32, SecureTokenGenerator::FORMAT_HEX); $base64Entropy = $generator->getEntropy($base64Token); $hexEntropy = $generator->getEntropy($hexToken); expect($base64Entropy)->toBeGreaterThan($hexEntropy); // Base64 has higher entropy per character expect($base64Entropy)->toBeGreaterThan(100); // Should have significant entropy }); it('throws exception for too short token length', function () { $generator = new SecureTokenGenerator(new SecureRandomGenerator()); expect(fn () => $generator->generate('test', 8)) ->toThrow(InvalidArgumentException::class, 'Token length must be at least 16 bytes'); }); it('throws exception for too long token length', function () { $generator = new SecureTokenGenerator(new SecureRandomGenerator()); expect(fn () => $generator->generate('test', 512)) ->toThrow(InvalidArgumentException::class, 'Token length cannot exceed 256 bytes'); }); it('throws exception for unsupported token type', function () { $generator = new SecureTokenGenerator(new SecureRandomGenerator()); expect(fn () => $generator->generate('unsupported_type')) ->toThrow(InvalidArgumentException::class, 'Unsupported token type'); }); it('throws exception for unsupported format', function () { $generator = new SecureTokenGenerator(new SecureRandomGenerator()); expect(fn () => $generator->generate('api_key', 32, 'unsupported_format')) ->toThrow(InvalidArgumentException::class, 'Unsupported format'); }); it('throws exception for invalid OTP digits', function () { $generator = new SecureTokenGenerator(new SecureRandomGenerator()); expect(fn () => $generator->generateOtpToken(2)) ->toThrow(InvalidArgumentException::class, 'OTP digits must be between 4 and 12'); expect(fn () => $generator->generateOtpToken(15)) ->toThrow(InvalidArgumentException::class, 'OTP digits must be between 4 and 12'); }); it('throws exception for too small custom alphabet', function () { $generator = new SecureTokenGenerator(new SecureRandomGenerator()); expect(fn () => $generator->generateCustom('ABC', 16)) ->toThrow(InvalidArgumentException::class, 'Alphabet must contain at least 16 characters'); }); it('throws exception for too short custom token', function () { $generator = new SecureTokenGenerator(new SecureRandomGenerator()); expect(fn () => $generator->generateCustom('0123456789ABCDEF', 4)) ->toThrow(InvalidArgumentException::class, 'Token length must be at least 8 characters'); }); it('throws exception for invalid batch count', function () { $generator = new SecureTokenGenerator(new SecureRandomGenerator()); expect(fn () => $generator->generateBatch('session', 0)) ->toThrow(InvalidArgumentException::class, 'Count must be positive'); expect(fn () => $generator->generateBatch('session', 2000)) ->toThrow(InvalidArgumentException::class, 'Batch size cannot exceed 1000'); }); it('creates instance using factory method', function () { $randomGen = new SecureRandomGenerator(); $generator = SecureTokenGenerator::create($randomGen); expect($generator)->toBeInstanceOf(SecureTokenGenerator::class); }); it('removes duplicate characters from custom alphabet', function () { $generator = new SecureTokenGenerator(new SecureRandomGenerator()); $alphabetWithDuplicates = '0123456789ABCDEFABCDEF'; // Has duplicate ABCDEF $token = $generator->generateCustom($alphabetWithDuplicates, 8); $metadata = $token->getMetadata(); $actualAlphabet = $metadata['alphabet']; expect($actualAlphabet)->toBe('0123456789ABCDEF'); // Duplicates removed expect($metadata['alphabet_size'])->toBe(16); }); it('generates unique tokens in batch', function () { $generator = new SecureTokenGenerator(new SecureRandomGenerator()); $tokens = $generator->generateBatch('api_key', 10, 32); $values = array_map(fn ($token) => $token->getValue(), $tokens); $uniqueValues = array_unique($values); expect($uniqueValues)->toHaveCount(10); // All should be unique });