@@ -43,6 +43,46 @@ public static function validLengthProvider(): array
4343 ];
4444 }
4545
46+ /**
47+ * Provides valid CUID2 strings for testing isValid method.
48+ *
49+ * @return array<string, array<mixed>>
50+ */
51+ public static function validCuidProvider (): array
52+ {
53+ return [
54+ 'minimum length ' => ['a1b2 ' , 4 ],
55+ 'short cuid ' => ['x7z9k2m1 ' , 8 ],
56+ 'medium cuid ' => ['q5w8e3r6t9y2u1i4 ' , 16 ],
57+ 'default length ' => ['p0o9i8u7y6t5r4e3w2q1a2s3 ' , 24 ],
58+ 'maximum length ' => ['z1x2c3v4b5n6m7q8w9e0r1t2y3u4i5o6 ' , 32 ],
59+ 'mixed characters ' => ['a1b2c3d4e5f6g7h8i9j0k1l2 ' , 24 ],
60+ 'all letters ' => ['abcd ' , 4 ],
61+ 'starts with letter and numbers ' => ['a123 ' , 4 ],
62+ ];
63+ }
64+
65+ /**
66+ * Provides invalid CUID2 strings for testing isValid method.
67+ *
68+ * @return array<string, array<mixed>>
69+ */
70+ public static function invalidCuidProvider (): array
71+ {
72+ return [
73+ 'too short ' => ['abc ' , null ],
74+ 'too long ' => ['a ' . str_repeat ('1 ' , 32 ), null ],
75+ 'empty string ' => ['' , null ],
76+ 'contains uppercase ' => ['A1b2c3d4 ' , null ],
77+ 'contains special chars ' => ['a1b2-c3d4 ' , null ],
78+ 'contains spaces ' => ['a1b2 c3d4 ' , null ],
79+ 'contains underscore ' => ['a1b2_c3d4 ' , null ],
80+ 'starts with number ' => ['1abc ' , null ],
81+ 'unicode characters ' => ['a1b2ñ3d4 ' , null ],
82+ 'contains symbols ' => ['a1b2@3d4 ' , null ],
83+ ];
84+ }
85+
4686 /**
4787 * Tests that the generated CUID2 contains only valid base36 characters.
4888 *
@@ -468,9 +508,192 @@ public function testConstructorAndGenerateMethodEquivalence(): void
468508 $ generateResult = (string )$ generateCuid ;
469509
470510 // Should have same length and format, but different values
471- $ this ->assertEquals (strlen ($ constructorResult ), strlen ($ generateResult ), 'Both methods should produce same length ' );
472- $ this ->assertMatchesRegularExpression ('/^[a-z][0-9a-z]*$/ ' , $ constructorResult , 'Constructor result should have correct format ' );
473- $ this ->assertMatchesRegularExpression ('/^[a-z][0-9a-z]*$/ ' , $ generateResult , 'Generate result should have correct format ' );
511+ $ this ->assertEquals (strlen ($ constructorResult ),
512+ strlen ($ generateResult ),
513+ 'Both methods should produce same length '
514+ );
515+
516+ $ this ->assertMatchesRegularExpression ('/^[a-z][0-9a-z]*$/ ' ,
517+ $ constructorResult ,
518+ 'Constructor result should have correct format '
519+ );
520+
521+ $ this ->assertMatchesRegularExpression ('/^[a-z][0-9a-z]*$/ ' ,
522+ $ generateResult ,
523+ 'Generate result should have correct format '
524+ );
525+
474526 $ this ->assertNotEquals ($ constructorResult , $ generateResult , 'Results should be unique ' );
475527 }
528+
529+ /**
530+ * Tests isValid method with valid CUID2 strings.
531+ *
532+ * @dataProvider validCuidProvider
533+ */
534+ public function testIsValidWithValidCuids (string $ cuid , ?int $ expectedLength = null ): void
535+ {
536+ $ this ->assertTrue (
537+ Cuid2::isValid ($ cuid , $ expectedLength ),
538+ "CUID ' $ cuid' should be considered valid "
539+ );
540+ }
541+
542+ /**
543+ * Tests isValid method with invalid CUID2 strings.
544+ *
545+ * @dataProvider invalidCuidProvider
546+ */
547+ public function testIsValidWithInvalidCuids (string $ cuid , ?int $ expectedLength = null ): void
548+ {
549+ $ this ->assertFalse (
550+ Cuid2::isValid ($ cuid , $ expectedLength ),
551+ "CUID ' $ cuid' should be considered invalid "
552+ );
553+ }
554+
555+ /**
556+ * Tests isValid method with length validation.
557+ */
558+ public function testIsValidWithExpectedLength (): void
559+ {
560+ $ validCuid = 'a1b2c3d4e5f6g7h8 ' ;
561+
562+ // Should be valid when length matches
563+ $ this ->assertTrue (
564+ Cuid2::isValid ($ validCuid , 16 ),
565+ 'Valid CUID should pass when expected length matches actual length '
566+ );
567+
568+ // Should be invalid when length doesn't match
569+ $ this ->assertFalse (
570+ Cuid2::isValid ($ validCuid , 24 ),
571+ 'Valid CUID should fail when expected length differs from actual length '
572+ );
573+
574+ $ this ->assertFalse (
575+ Cuid2::isValid ($ validCuid , 8 ),
576+ 'Valid CUID should fail when expected length is shorter than actual length '
577+ );
578+ }
579+
580+ /**
581+ * Tests isValid method with edge cases for length validation.
582+ */
583+ public function testIsValidLengthEdgeCases (): void
584+ {
585+ // Test minimum valid length
586+ $ this ->assertTrue (Cuid2::isValid ('a1b2 ' , 4 ), 'Minimum length CUID should be valid ' );
587+ $ this ->assertFalse (Cuid2::isValid ('a1b ' , 3 ), 'Below minimum length should be invalid even with matching expected length ' );
588+
589+ // Test maximum valid length
590+ $ maxLengthCuid = 'a ' . str_repeat ('1 ' , 31 );
591+ $ this ->assertTrue (Cuid2::isValid ($ maxLengthCuid , 32 ), 'Maximum length CUID should be valid ' );
592+
593+ // Test above maximum length
594+ $ tooLongCuid = 'a ' . str_repeat ('1 ' , 32 );
595+ $ this ->assertFalse (Cuid2::isValid ($ tooLongCuid , 33 ), 'Above maximum length should be invalid even with matching expected length ' );
596+ }
597+
598+ /**
599+ * Tests isValid method without expected length parameter.
600+ */
601+ public function testIsValidWithoutExpectedLength (): void
602+ {
603+ $ this ->assertTrue (Cuid2::isValid ('a1b2 ' ), 'Valid minimum length CUID should pass without expected length ' );
604+ $ this ->assertTrue (Cuid2::isValid ('a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6 ' ), 'Valid maximum length CUID should pass without expected length ' );
605+ $ this ->assertFalse (Cuid2::isValid ('abc ' ), 'Too short CUID should fail without expected length ' );
606+ $ this ->assertFalse (Cuid2::isValid ('A1b2 ' ), 'CUID with uppercase should fail without expected length ' );
607+ }
608+
609+ /**
610+ * Tests isValid method with generated CUIDs.
611+ *
612+ * @throws Exception
613+ */
614+ public function testIsValidWithGeneratedCuids (): void
615+ {
616+ $ lengths = [4 , 8 , 16 , 24 , 32 ];
617+
618+ foreach ($ lengths as $ length ) {
619+ $ cuid = Cuid2::generate ($ length );
620+ $ cuidString = (string )$ cuid ;
621+
622+ // Generated CUID should always be valid
623+ $ this ->assertTrue (
624+ Cuid2::isValid ($ cuidString ),
625+ "Generated CUID of length $ length should be valid "
626+ );
627+
628+ // Generated CUID should be valid with correct expected length
629+ $ this ->assertTrue (
630+ Cuid2::isValid ($ cuidString , $ length ),
631+ "Generated CUID should be valid with matching expected length $ length "
632+ );
633+
634+ // Generated CUID should be invalid with wrong expected length
635+ $ wrongLength = $ length === 4 ? 8 : 4 ;
636+ $ this ->assertFalse (
637+ Cuid2::isValid ($ cuidString , $ wrongLength ),
638+ "Generated CUID should be invalid with wrong expected length $ wrongLength "
639+ );
640+ }
641+ }
642+
643+ /**
644+ * Tests isValid method with character set validation.
645+ */
646+ public function testIsValidCharacterSetValidation (): void
647+ {
648+ // Test all valid base36 characters
649+ $ validChars = '0123456789abcdefghijklmnopqrstuvwxyz ' ;
650+ $ testCuid = 'a ' . substr (str_shuffle ($ validChars ), 0 , 7 );
651+ $ this ->assertTrue (Cuid2::isValid ($ testCuid ), 'CUID with all valid base36 characters should be valid ' );
652+
653+ // Test invalid characters
654+ $ invalidChars = ['A ' , 'Z ' , '- ' , '_ ' , '@ ' , '# ' , '$ ' , '% ' , '^ ' , '& ' , '* ' ];
655+ foreach ($ invalidChars as $ invalidChar ) {
656+ $ invalidCuid = 'a1b2 ' . $ invalidChar . '3d4 ' ;
657+ $ this ->assertFalse (
658+ Cuid2::isValid ($ invalidCuid ),
659+ "CUID containing invalid character ' $ invalidChar' should be invalid "
660+ );
661+ }
662+ }
663+
664+ /**
665+ * Tests isValid method regex pattern matching.
666+ */
667+ public function testIsValidRegexPattern (): void
668+ {
669+ // Test the regex pattern directly through various scenarios
670+ $ validPatterns = [
671+ 'a123 ' ,
672+ 'z999 ' ,
673+ 'x1y2z3a4b5c6 ' ,
674+ 'q0w1e2r3t4y5u6i7o8p9 ' ,
675+ ];
676+
677+ foreach ($ validPatterns as $ pattern ) {
678+ $ this ->assertTrue (
679+ Cuid2::isValid ($ pattern ),
680+ "Pattern ' $ pattern' should match valid CUID regex "
681+ );
682+ }
683+
684+ $ invalidPatterns = [
685+ 'A123 ' , // uppercase
686+ 'a-23 ' , // contains hyphen
687+ 'a_23 ' , // contains underscore
688+ 'a 23 ' , // contains space
689+ 'a.23 ' , // contains dot
690+ ];
691+
692+ foreach ($ invalidPatterns as $ pattern ) {
693+ $ this ->assertFalse (
694+ Cuid2::isValid ($ pattern ),
695+ "Pattern ' $ pattern' should not match valid CUID regex "
696+ );
697+ }
698+ }
476699}
0 commit comments