use super::core::{scrub, scrub_compat};
use super::types::{MAX_BODY_BYTES, ScrubChange};
fn scrub_pair(text: &str) -> (String, Vec<ScrubChange>) {
scrub_compat(text)
}
#[test]
fn paths_redacted() {
let (out, changes) = scrub_pair("failed to open /Users/alice/projects/foo.db");
assert!(!out.contains("/Users/alice"), "path not scrubbed: {out}");
assert!(out.contains('~'), "expected ~ replacement: {out}");
assert!(changes.iter().any(|c| c.pattern == "AbsolutePath"));
}
#[test]
fn bearer_redacted() {
let (out, changes) = scrub_pair("Authorization: Bearer sk-proj-abcXYZ123");
assert!(
!out.contains("sk-proj-abcXYZ123"),
"bearer token not scrubbed: {out}"
);
assert!(
out.contains("[REDACTED_TOKEN]") || out.contains("[REDACTED_API_KEY]"),
"{out}"
);
assert!(
changes
.iter()
.any(|c| c.pattern == "BearerToken" || c.pattern == "SkApiKey"),
"{changes:?}"
);
}
#[test]
fn jwt_redacted() {
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.payload.sig";
let (out, changes) = scrub_pair(&format!("authorization_header={token}"));
assert!(!out.contains("eyJhbG"), "jwt not scrubbed: {out}");
let redacted = out.contains("[REDACTED_JWT]") || out.contains("[REDACTED_VALUE]");
assert!(redacted, "expected JWT redaction: {out}");
let caught = changes
.iter()
.any(|c| c.pattern == "JwtToken" || c.pattern == "EnvSecret");
assert!(caught, "expected jwt or env scrub rule: {changes:?}");
}
#[test]
fn env_kv_redacted() {
let (out, changes) = scrub_pair("OPENAI_API_KEY=sk-proj-abc123\nNAME=alice");
assert!(!out.contains("sk-proj-abc123"), "key not scrubbed: {out}");
assert!(
out.contains("NAME=alice"),
"unrelated var should be kept: {out}"
);
let scrubbed = changes
.iter()
.any(|c| c.pattern == "EnvSecret" || c.pattern == "SkApiKey");
assert!(scrubbed, "expected env or sk scrub: {changes:?}");
}
#[test]
fn truncation_applies() {
let long = "x".repeat(MAX_BODY_BYTES + 1000);
let (out, changes) = scrub_pair(&long);
assert!(out.len() <= MAX_BODY_BYTES + 100, "should be truncated");
assert!(
out.contains("truncated"),
"should mention truncation: {out}"
);
assert!(changes.iter().any(|c| c.pattern == "Truncation"));
}
#[test]
fn clean_text_unchanged() {
let text = "connection refused: dial tcp 127.0.0.1:5432";
let (out, changes) = scrub_pair(text);
assert_eq!(out, text);
assert!(changes.is_empty(), "no changes expected: {changes:?}");
}
#[test]
fn windows_path_redacted() {
let (out, changes) = scrub_pair(r"failed to open C:\Users\alice\projects\foo.db");
assert!(!out.contains("Users"), "windows path not scrubbed: {out}");
assert!(changes.iter().any(|c| c.pattern == "WindowsPath"));
}
#[test]
fn aws_key_redacted() {
let (out, changes) = scrub_pair("aws_key=AKIAIOSFODNN7EXAMPLE_EXTRA_PAD");
assert!(
!out.contains("AKIAIOSFODNN7"),
"AWS key not scrubbed: {out}"
);
assert!(
changes
.iter()
.any(|c| c.pattern == "AwsKey" || c.pattern == "EnvSecret"),
"expected AwsKey or EnvSecret: {changes:?}"
);
}
#[test]
fn aws_key_standalone_redacted() {
let (out, changes) = scrub_pair("access key is AKIAIOSFODNN7EXAMPLEQ");
assert!(
!out.contains("AKIAIOSFODNN7"),
"AWS key not scrubbed: {out}"
);
assert!(
changes.iter().any(|c| c.pattern == "AwsKey"),
"expected AwsKey: {changes:?}"
);
}
#[test]
fn google_key_redacted() {
let key = "AIzaSyDdI0hCZtE6vySjMm-WEfRq3CPzqKqqsHI"; let (out, changes) = scrub_pair(&format!("google api key: {key}"));
assert!(!out.contains("AIzaSy"), "Google key not scrubbed: {out}");
assert!(
changes.iter().any(|c| c.pattern == "GoogleKey"),
"expected GoogleKey: {changes:?}"
);
}
#[test]
fn slack_token_redacted() {
let (out, changes) = scrub_pair("token=xoxb-1234567890-ABCDEFGHIJ");
assert!(!out.contains("xoxb-"), "Slack token not scrubbed: {out}");
assert!(
changes
.iter()
.any(|c| c.pattern == "SlackToken" || c.pattern == "EnvSecret"),
"expected SlackToken or EnvSecret: {changes:?}"
);
}
#[test]
fn slack_token_standalone_redacted() {
let (out, changes) = scrub_pair("using slack token xoxp-987654321-xyz");
assert!(!out.contains("xoxp-"), "Slack token not scrubbed: {out}");
assert!(
changes.iter().any(|c| c.pattern == "SlackToken"),
"expected SlackToken: {changes:?}"
);
}
#[test]
fn pem_block_redacted() {
let pem = "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA...\n-----END RSA PRIVATE KEY-----"; let (out, changes) = scrub_pair(pem);
assert!(
!out.contains("BEGIN RSA PRIVATE KEY"),
"PEM block not scrubbed: {out}"
);
assert!(
changes.iter().any(|c| c.pattern == "PemPrivateKey"),
"expected PemPrivateKey: {changes:?}"
);
}
#[test]
fn connection_string_redacted() {
let (out, changes) = scrub_pair("db_url=postgres://admin:s3cr3tp4ss@db.example.com:5432/mydb"); assert!(!out.contains("s3cr3tp4ss"), "password not scrubbed: {out}");
assert!(
changes
.iter()
.any(|c| c.pattern == "ConnString" || c.pattern == "EnvSecret"),
"expected ConnString or EnvSecret: {changes:?}"
);
}
#[test]
fn connection_string_standalone_redacted() {
let (out, changes) = scrub_pair("connecting to postgres://user:hunter2@localhost/prod"); assert!(!out.contains("hunter2"), "password not scrubbed: {out}");
assert!(
changes.iter().any(|c| c.pattern == "ConnString"),
"expected ConnString: {changes:?}"
);
}
#[test]
fn sk_prefix_redacted() {
let (out, changes) = scrub_pair("OPENAI_KEY=sk-abcdef1234567890abcdef1234");
assert!(!out.contains("sk-abcdef"), "sk- key not scrubbed: {out}");
assert!(
changes
.iter()
.any(|c| c.pattern == "SkApiKey" || c.pattern == "EnvSecret"),
"expected SkApiKey or EnvSecret: {changes:?}"
);
}
#[test]
fn sk_ant_prefix_redacted() {
let (out, changes) = scrub_pair("ANTHROPIC_KEY=sk-ant-api03-abcdefghijklmnopqrstuvwxyz");
assert!(
!out.contains("sk-ant-api03"),
"sk-ant- key not scrubbed: {out}"
);
assert!(
changes
.iter()
.any(|c| c.pattern == "SkApiKey" || c.pattern == "EnvSecret"),
"expected SkApiKey or EnvSecret: {changes:?}"
);
}
#[test]
fn github_token_prefixes_redacted() {
let tokens = [
"ghp_aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890", "gho_aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890", "ghu_aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890", "ghs_aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890", ];
for token in &tokens {
let (out, changes) = scrub_pair(&format!("using token {token}"));
assert!(
!out.contains(&token[..8]),
"GitHub token not scrubbed: {out}"
);
assert!(
changes.iter().any(|c| c.pattern == "GithubToken"),
"expected GithubToken for {token}: {changes:?}"
);
}
}
#[test]
fn scrub_result_summary() {
let text = "token ghp_aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890 at /Users/alice/db"; let result = scrub(text);
assert!(!result.text.contains("ghp_aB"), "{}", result.text);
assert!(!result.text.contains("/Users/alice"), "{}", result.text);
assert!(
result.redaction_summary.contains("secret") || result.redaction_summary.contains("path"),
"summary: {}",
result.redaction_summary
);
}
#[test]
fn clean_text_summary_is_nothing_redacted() {
let result = scrub("connection refused: 127.0.0.1:5432");
assert_eq!(result.redaction_summary, "nothing redacted");
}
#[test]
fn home_dir_path_scrubbed() {
let (out, changes) = scrub_pair("reading /home/bob/.config/trusty-mpm/config.toml");
assert!(!out.contains("/home/bob"), "home path not scrubbed: {out}");
assert!(
changes.iter().any(|c| c.pattern == "AbsolutePath"),
"{changes:?}"
);
}