@@ -828,6 +828,34 @@ def test_remove_password_from_command_line(self):
828828 assert authorization not in " " .join (redacted_cmd_6 )
829829 assert "http.extraHeader=Authorization: *****" in redacted_cmd_6
830830
831+ def test_remove_password_keeps_host_intact (self ):
832+ """Redaction must not touch the host, even when it contains the username."""
833+ redacted = remove_password_if_present (["git" , "clone" , "https://git@github.com/user/repo.git" ])
834+ assert redacted == ["git" , "clone" , "https://*****@github.com/user/repo.git" ]
835+
836+ redacted = remove_password_if_present (["git" , "clone" , "ssh://git@github.com/u/r.git" ])
837+ assert redacted == ["git" , "clone" , "ssh://*****@github.com/u/r.git" ]
838+
839+ def test_remove_empty_password_keeps_host_intact (self ):
840+ """An empty password must not expand into every position of the netloc."""
841+ redacted = remove_password_if_present (["git" , "clone" , "https://:@fakerepo.example.com/testrepo" ])
842+ assert redacted == ["git" , "clone" , "https://*****:*****@fakerepo.example.com/testrepo" ]
843+
844+ @ddt .data (
845+ (
846+ "https://user%40example.com:p%40ss@GitHub.COM:00443/repo@name?q=a@b#c@d" ,
847+ "https://*****:*****@GitHub.COM:00443/repo@name?q=a@b#c@d" ,
848+ ),
849+ ("//user:pass@[2001:db8::1]:0080/repo" , "//*****:*****@[2001:db8::1]:0080/repo" ),
850+ ("https://user:p@ss@example.com/repo" , "https://*****:*****@example.com/repo" ),
851+ ("https://user:@example.com/repo" , "https://*****:*****@example.com/repo" ),
852+ ("https://@example.com/repo" , "https://*****@example.com/repo" ),
853+ ("https://example.com/repo@name?q=a@b#c@d" , "https://example.com/repo@name?q=a@b#c@d" ),
854+ )
855+ @ddt .unpack
856+ def test_remove_password_preserves_url_components (self , url , expected ):
857+ assert remove_password_if_present ([url ]) == [expected ]
858+
831859
832860def test_mode_str_to_int_accepts_bytes ():
833861 assert mode_str_to_int ("100644" ) == 0o100644
0 commit comments