use super::*;
const TOKEN: &str = "ghp_9124SyntheticNotARealToken0000";
#[test]
fn redact_url_strips_user_and_token() {
let url = format!("https://octo:{TOKEN}@github.com/acme/widget.git");
let out = redact_url(&url);
assert!(!out.contains(TOKEN), "the token survived");
assert!(!out.contains("octo"), "the user survived");
assert_eq!(out, "https://***@github.com/acme/widget.git");
}
#[test]
fn redact_url_strips_an_x_access_token() {
let url = format!("https://x-access-token:{TOKEN}@github.com/acme/widget");
assert_eq!(redact_url(&url), "https://***@github.com/acme/widget");
let bare = format!("https://{TOKEN}@github.com/acme/widget");
assert_eq!(redact_url(&bare), "https://***@github.com/acme/widget");
}
#[test]
fn redact_url_leaves_a_clean_url_alone() {
for clean in [
"https://github.com/acme/widget.git",
"git@github.com:acme/widget.git",
"https://github.com/acme/widget/blob/main/a@b.md",
"/Users/someone/projects/widget",
] {
assert!(
matches!(redact_url(clean), Cow::Borrowed(s) if s == clean),
"{clean} was rewritten"
);
}
}
#[test]
fn redact_url_redacts_every_url_in_free_text() {
let text = format!(
"fatal: unable to access 'https://octo:{TOKEN}@github.com/a/b.git/': refused; \
also ssh://deploy:{TOKEN}@host:22/c.git"
);
let out = redact_url(&text);
assert!(!out.contains(TOKEN), "a token survived in free text");
assert!(out.contains("'https://***@github.com/a/b.git/'"));
assert!(out.contains("ssh://***@host:22/c.git"));
}
#[test]
fn redact_url_masks_a_password_holding_a_raw_slash() {
let url = "https://octo:ghp_9124Head/Tail9124Secret@github.com/acme/widget.git";
let out = redact_url(url);
assert!(!out.contains("Tail9124Secret"), "the tail survived");
assert!(!out.contains("ghp_9124Head"), "the head survived");
assert_eq!(out, "https://***@github.com/acme/widget.git");
}
#[test]
fn redact_url_masks_a_password_holding_a_raw_query_or_fragment_char() {
for url in [
"https://octo:ab#cd9124Secret@github.com/x",
"https://octo:ab?cd9124Secret@github.com/x",
"https://octo:a?b#c@d9124Secret@github.com/x?page=2#top",
] {
let out = redact_url(url);
assert!(!out.contains("9124Secret"), "a password survived");
assert!(!out.contains("octo"), "the user survived");
assert!(out.starts_with("https://***@github.com/x"), "{out}");
}
let query = "https://host:8443/p?who=a@b";
assert_eq!(redact_url(query), "https://***@b");
}
#[test]
fn redact_url_masks_query_string_tokens() {
for key in [
"access_token",
"private_token",
"token",
"oauth_token",
"Access_Token",
] {
let url =
format!("https://gitlab.example/api/v4/projects?per_page=5&{key}={TOKEN}&x=1#top");
let out = redact_url(&url);
assert!(!out.contains(TOKEN), "{key}: the token survived");
assert_eq!(
out,
format!("https://gitlab.example/api/v4/projects?per_page=5&{key}=***&x=1#top")
);
}
let first = format!("fetch 'https://h/o/r.git?token={TOKEN}' failed");
assert_eq!(
redact_url(&first),
"fetch 'https://h/o/r.git?token=***' failed"
);
let other = "https://h/o?mytoken=abc&page=2";
assert!(matches!(redact_url(other), Cow::Borrowed(_)));
}
#[test]
fn redact_stored_url_masks_a_quoted_or_spaced_password() {
for (i, (url, want)) in [
("https://u:pa'ss9227@host/o/r", "https://***@host/o/r"),
("https://u:pa ss9227@host/o/r", "https://***@host/o/r"),
("https://u:pa\"ss9227@host/o/r", "https://***@host/o/r"),
("https://u:pa`ss9227@host/o/r", "https://***@host/o/r"),
("https://u:pa\tss9227@host/o/r", "https://***@host/o/r"),
("ssh://u:pa'ss9227@host:22/o/r", "ssh://***@host:22/o/r"),
("https://u:pa/ss9227@host/o/r", "https://***@host/o/r"),
("https://u:p'a/ss9227@host/o/r", "https://***@host/o/r"),
(
"https://u:pa'ss9227@host/o/r?token=ss9227",
"https://***@host/o/r?token=***",
),
("u:pa'ss9227@host:o/r", "***@host:o/r"),
("u:pa ss9227@h:o/r://x", "***@h:o/r://x"),
]
.into_iter()
.enumerate()
{
let out = redact_stored_url(url);
assert!(
!out.contains("ss9227"),
"case {i}: a password fragment survived"
);
assert!(
!out.contains("u:pa"),
"case {i}: the user:password pair survived"
);
assert!(out == want, "case {i}: unexpected masked form");
}
}
#[test]
fn redact_stored_url_keeps_a_clean_url_as_redact_url_shows_it() {
for (url, want) in [
("https://github.com/o/r.git", "https://github.com/o/r.git"),
("git@github.com:o/r.git", "git@github.com:o/r.git"),
("ssh://git@host:22/o/r.git", "ssh://***@host:22/o/r.git"),
(
"https://github.com/o/r/blob/main/a@b.md",
"https://github.com/o/r/blob/main/a@b.md",
),
("/srv/dir@x/repo", "/srv/dir@x/repo"),
] {
assert_eq!(redact_stored_url(url), want);
assert_eq!(redact_stored_url(url), redact_url(url), "{url}");
}
assert!(matches!(
redact_stored_url("https://github.com/o/r.git"),
Cow::Borrowed(_)
));
}
#[test]
fn every_single_url_type_masks_a_quoted_or_spaced_password() {
use crate::core::local_repo_url::NonLocalRepoUrl;
use crate::core::standalone::registry::RegistryError;
let rows = [
("https://u:pa'ss9259@host/o/r", "u:p"),
("https://u:p\"a\"ss9259@host/o/r", "u:p"),
("https://u:pa ss9259@host/o/r", "u:p"),
("https://user:\"p@ss9259\"@host/repo", "user:"),
];
for (i, (url, prefix)) in rows.into_iter().enumerate() {
let non_local = NonLocalRepoUrl {
repo_url: url.to_owned(),
};
let duplicate = RegistryError::DuplicateAlias {
alias: "a".to_owned(),
existing_url: url.into(),
};
#[allow(unused_mut)]
let mut shown = vec![
("RedactedUrl Debug", format!("{:?}", RedactedUrl::from(url))),
("NonLocalRepoUrl Display", non_local.to_string()),
("NonLocalRepoUrl Debug", format!("{non_local:?}")),
("DuplicateAlias Display", duplicate.to_string()),
("DuplicateAlias Debug", format!("{duplicate:?}")),
];
#[cfg(feature = "daemon")]
{
let e = crate::daemon::error::DaemonError::ProjectNotFoundForRepoUrl {
repo_url: url.into(),
};
shown.push(("ProjectNotFoundForRepoUrl Display", e.to_string()));
shown.push(("ProjectNotFoundForRepoUrl Debug", format!("{e:?}")));
}
for (what, text) in shown {
assert!(
!text.contains("ss9259"),
"row {i}: {what} kept a password fragment"
);
assert!(
!text.contains(prefix),
"row {i}: {what} kept the {prefix:?} prefix"
);
assert!(
text.contains("***@host/"),
"row {i}: {what} lost the masked host"
);
}
}
}