rule = new Ulid(); }); test('validates empty values as true (handled by Required rule)', function () { expect($this->rule->validate(null))->toBeTrue() ->and($this->rule->validate(''))->toBeTrue(); }); test('validates non-string values as false', function () { expect($this->rule->validate(123))->toBeFalse() ->and($this->rule->validate(['not', 'a', 'ulid']))->toBeFalse() ->and($this->rule->validate(new stdClass()))->toBeFalse() ->and($this->rule->validate(true))->toBeFalse() ->and($this->rule->validate(12.34))->toBeFalse(); }); test('validates valid ULIDs', function () { $validUlids = [ '01ARZ3NDEKTSV4RRFFQ69G5FAV', // Standard ULID format '01BX5ZZKBKACTAV9WEVGEMMVRY', // Another valid ULID '01CA2ZZKBKACTAV9WEVGEMMVRY', // Year 2022 timestamp '7ZZZZZZZZZZZZZZZZZZZZZZZZZ', // Max timestamp, max random '00000000000000000000000000', // Min ULID (all zeros) '0123456789ABCDEFGHJKMNPQRS', // Using all valid Crockford Base32 chars ]; foreach ($validUlids as $ulid) { expect($this->rule->validate($ulid))->toBeTrue("ULID should be valid: $ulid"); } }); test('validates invalid ULIDs as false', function () { $invalidUlids = [ // Wrong length '01ARZ3NDEK', // Too short '01ARZ3NDEKTSV4RRFFQ69G5FAVX', // Too long (27 chars) '01ARZ3NDEKTSV4RRFFQ69G5FA', // Too short (25 chars) // Invalid characters (not in Crockford Base32) '01ARZ3NDEKTSV4RRFFQ69G5FI0', // Contains 'I' (invalid) '01ARZ3NDEKTSV4RRFFQ69G5FL0', // Contains 'L' (invalid) '01ARZ3NDEKTSV4RRFFQ69G5FO0', // Contains 'O' (invalid) '01ARZ3NDEKTSV4RRFFQ69G5FU0', // Contains 'U' (invalid) '01ARZ3NDEKTSV4RRFFQ69G5f@V', // Contains '@' (invalid) '01ARZ3NDEKTSV4RRFFQ69G5f_V', // Contains '_' (invalid) '01ARZ3NDEKTSV4RRFFQ69G5f-V', // Contains '-' (invalid) // Lowercase (ULIDs should be uppercase) '01arz3ndektsv4rrffq69g5fav', // All lowercase '01ARZ3ndektsv4rrffq69g5fav', // Mixed case // Completely invalid formats 'not-a-ulid-at-all', '01ARZ3NDEKTSV4RRFFQ69G5F V', // Contains space '01ARZ3NDEKTSV4RRFFQ69G5F\tV', // Contains tab '01ARZ3NDEKTSV4RRFFQ69G5F\nV', // Contains newline // Empty variations ' ', // Space only '\t', // Tab only '\n', // Newline only // Common mistakes '01ARZ3NDEKTSV4RRFFQ69G5FAV ', // Trailing space ' 01ARZ3NDEKTSV4RRFFQ69G5FAV', // Leading space '01ARZ3NDEKTSV4RRFFQ69G5FAV\n', // Trailing newline ]; foreach ($invalidUlids as $ulid) { expect($this->rule->validate($ulid))->toBeFalse("ULID should be invalid: '$ulid'"); } }); test('validates ULIDs with edge case timestamps', function () { // ULIDs with specific timestamp patterns that should be valid (all 26 chars) $edgeCaseUlids = [ '00000000000000000000000000', // Min ULID (all zeros) '7ZZZZZZZZZZZZZZZZZZZZZZZZZ', // Max timestamp (~10889 AD) '01FXYZ0000000000000000000V', // Year 2021 with min random '01FXYZ999999999999999999ZZ', // Year 2021 with high random 'GGGGGGGGGGGGGGGGGGGGGGGGGG', // All G's (valid in Crockford Base32) ]; foreach ($edgeCaseUlids as $ulid) { expect($this->rule->validate($ulid))->toBeTrue("Edge case ULID should be valid: $ulid"); } // Test Y case separately (26 characters) $yUlid = 'YYYYYYYYYYYYYYYYYYYYYYYYYY'; expect(strlen($yUlid))->toBe(26, "Y ULID should be 26 chars"); expect($this->rule->validate($yUlid))->toBeTrue("Y ULID should be valid: $yUlid"); }); test('validates strict Crockford Base32 alphabet', function () { // Test each valid character $validChars = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; $baseUlid = '01ARZ3NDEKTSV4RRFFQ69G5FA'; for ($i = 0; $i < strlen($validChars); $i++) { $char = $validChars[$i]; $testUlid = $baseUlid . $char; expect($this->rule->validate($testUlid))->toBeTrue("ULID with char '$char' should be valid"); } // Test invalid characters from standard Base32 that are excluded in Crockford $invalidChars = 'ILOU'; // These are excluded in Crockford Base32 foreach (str_split($invalidChars) as $char) { $testUlid = $baseUlid . $char; expect($this->rule->validate($testUlid))->toBeFalse("ULID with invalid char '$char' should be invalid"); } }); test('default error message', function () { $messages = $this->rule->getErrorMessages(); expect($messages)->toHaveCount(1) ->and($messages[0])->toBe('Bitte geben Sie eine gültige ULID ein (26 Zeichen, Crockford Base32).'); }); test('custom error message', function () { $customRule = new Ulid(message: 'Diese ULID ist nicht korrekt!'); $messages = $customRule->getErrorMessages(); expect($messages)->toHaveCount(1) ->and($messages[0])->toBe('Diese ULID ist nicht korrekt!'); }); test('validates performance with many ULIDs', function () { // Performance test - should validate many ULIDs quickly $validUlid = '01ARZ3NDEKTSV4RRFFQ69G5FAV'; $startTime = microtime(true); for ($i = 0; $i < 1000; $i++) { $this->rule->validate($validUlid); } $endTime = microtime(true); $duration = $endTime - $startTime; // Should complete 1000 validations in less than 100ms expect($duration)->toBeLessThan(0.1); });