@@ -594,3 +594,139 @@ async def test_header_exceeding_limit_rejected(
594594 mock_warning .assert_called_once ()
595595 assert mock_warning .call_args .args [1 ] == header_size
596596 assert mock_warning .call_args .args [2 ] == max_size
597+
598+
599+ class TestRHIdentityFieldValidation :
600+ """Test suite for RHIdentityData string field validation."""
601+
602+ @pytest .mark .parametrize (
603+ "field_path,bad_value" ,
604+ [
605+ (("user" , "user_id" ), None ),
606+ (("user" , "user_id" ), 12345 ),
607+ (("user" , "user_id" ), True ),
608+ (("user" , "user_id" ), []),
609+ (("user" , "user_id" ), {}),
610+ (("user" , "user_id" ), 3.14 ),
611+ (("user" , "username" ), None ),
612+ (("user" , "username" ), 12345 ),
613+ ],
614+ )
615+ def test_user_non_string_types_rejected (
616+ self , user_identity_data : dict , field_path : tuple [str , str ], bad_value : object
617+ ) -> None :
618+ """Reject non-string values in User identity string fields."""
619+ user_identity_data ["identity" ][field_path [0 ]][field_path [1 ]] = bad_value
620+ with pytest .raises (HTTPException ) as exc_info :
621+ RHIdentityData (user_identity_data )
622+ assert exc_info .value .status_code == 400
623+
624+ @pytest .mark .parametrize (
625+ "field_path,bad_value" ,
626+ [
627+ (("system" , "cn" ), None ),
628+ (("system" , "cn" ), 12345 ),
629+ (("system" , "cn" ), True ),
630+ (("system" , "cn" ), []),
631+ (("system" , "cn" ), {}),
632+ (("account_number" ,), None ),
633+ (("account_number" ,), 12345 ),
634+ (("account_number" ,), False ),
635+ (("account_number" ,), []),
636+ (("account_number" ,), {}),
637+ ],
638+ )
639+ def test_system_non_string_types_rejected (
640+ self ,
641+ system_identity_data : dict ,
642+ field_path : tuple [str , ...],
643+ bad_value : object ,
644+ ) -> None :
645+ """Reject non-string values in System identity string fields."""
646+ identity = system_identity_data ["identity" ]
647+ if len (field_path ) == 1 :
648+ identity [field_path [0 ]] = bad_value
649+ else :
650+ identity [field_path [0 ]][field_path [1 ]] = bad_value
651+ with pytest .raises (HTTPException ) as exc_info :
652+ RHIdentityData (system_identity_data )
653+ assert exc_info .value .status_code == 400
654+
655+ @pytest .mark .parametrize ("bad_value" , ["" , " " , "\t " , "\n " ])
656+ def test_empty_whitespace_rejected (
657+ self , user_identity_data : dict , bad_value : str
658+ ) -> None :
659+ """Reject empty and whitespace-only strings."""
660+ user_identity_data ["identity" ]["user" ]["user_id" ] = bad_value
661+ with pytest .raises (HTTPException ) as exc_info :
662+ RHIdentityData (user_identity_data )
663+ assert exc_info .value .status_code == 400
664+
665+ @pytest .mark .parametrize (
666+ "bad_value" ,
667+ ["user\x00 id" , "user\n id" , "user\r id" , "a\x1f b" , "a\x7f b" ],
668+ )
669+ def test_control_characters_rejected (
670+ self , user_identity_data : dict , bad_value : str
671+ ) -> None :
672+ """Reject strings containing control characters."""
673+ user_identity_data ["identity" ]["user" ]["user_id" ] = bad_value
674+ with pytest .raises (HTTPException ) as exc_info :
675+ RHIdentityData (user_identity_data )
676+ assert exc_info .value .status_code == 400
677+
678+ def test_oversized_value_rejected (self , user_identity_data : dict ) -> None :
679+ """Reject values longer than 256 characters."""
680+ user_identity_data ["identity" ]["user" ]["user_id" ] = "a" * 257
681+ with pytest .raises (HTTPException ) as exc_info :
682+ RHIdentityData (user_identity_data )
683+ assert exc_info .value .status_code == 400
684+
685+ def test_max_length_boundary_accepted (self , user_identity_data : dict ) -> None :
686+ """Accept values exactly 256 characters long."""
687+ user_identity_data ["identity" ]["user" ]["user_id" ] = "a" * 256
688+ RHIdentityData (user_identity_data )
689+
690+ def test_org_id_missing_accepted (self , user_identity_data : dict ) -> None :
691+ """Allow missing org_id."""
692+ user_identity_data ["identity" ].pop ("org_id" , None )
693+ RHIdentityData (user_identity_data )
694+
695+ def test_org_id_empty_accepted (self , user_identity_data : dict ) -> None :
696+ """Allow empty org_id."""
697+ user_identity_data ["identity" ]["org_id" ] = ""
698+ RHIdentityData (user_identity_data )
699+
700+ def test_org_id_non_string_rejected (self , user_identity_data : dict ) -> None :
701+ """Reject non-string org_id when provided and non-empty."""
702+ user_identity_data ["identity" ]["org_id" ] = 12345
703+ with pytest .raises (HTTPException ) as exc_info :
704+ RHIdentityData (user_identity_data )
705+ assert exc_info .value .status_code == 400
706+
707+ def test_org_id_valid_accepted (self , user_identity_data : dict ) -> None :
708+ """Accept valid string org_id."""
709+ user_identity_data ["identity" ]["org_id" ] = "valid-org-id"
710+ RHIdentityData (user_identity_data )
711+
712+ def test_org_id_oversized_rejected (self , user_identity_data : dict ) -> None :
713+ """Reject oversized org_id."""
714+ user_identity_data ["identity" ]["org_id" ] = "a" * 257
715+ with pytest .raises (HTTPException ) as exc_info :
716+ RHIdentityData (user_identity_data )
717+ assert exc_info .value .status_code == 400
718+
719+ def test_org_id_control_chars_rejected (self , user_identity_data : dict ) -> None :
720+ """Reject org_id containing control characters."""
721+ user_identity_data ["identity" ]["org_id" ] = "org\x00 id"
722+ with pytest .raises (HTTPException ) as exc_info :
723+ RHIdentityData (user_identity_data )
724+ assert exc_info .value .status_code == 400
725+
726+ def test_valid_user_data_still_passes (self , user_identity_data : dict ) -> None :
727+ """Regression: valid User identity data passes validation."""
728+ RHIdentityData (user_identity_data )
729+
730+ def test_valid_system_data_still_passes (self , system_identity_data : dict ) -> None :
731+ """Regression: valid System identity data passes validation."""
732+ RHIdentityData (system_identity_data )
0 commit comments