use santh_error::{redact_secrets, SanthError};
#[test]
fn redact_embedded_jwt() {
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
let input = format!("token={}", jwt);
let out = redact_secrets(&input);
assert!(!out.contains("eyJhbGci"));
assert!(out.contains("[REDACTED]"));
}
#[test]
fn redact_github_token() {
let input = "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
let out = redact_secrets(input);
assert!(!out.contains("ghp_"));
assert!(out.contains("[REDACTED]"));
}
#[test]
fn redact_github_fine_grained_pat() {
let input = "github_pat_11ABCDEIJ0lmNoPQRstu_vwxyz12345678901234567890ABCDEFGHIJK";
let out = redact_secrets(input);
assert!(!out.contains("github_pat_"));
assert!(out.contains("[REDACTED]"));
}
#[test]
fn redact_password_field() {
let cases = [
("password=hunter2", "hunter2"),
("passwd:hunter2", "hunter2"),
("Password = hunter2", "hunter2"),
("PASSWORD:hunter2", "hunter2"),
("api_key=secret123", "secret123"),
("api-key:secret123", "secret123"),
("token=abcd", "abcd"),
("secret=shh", "shh"),
];
for (input, secret) in &cases {
let out = redact_secrets(input);
assert!(!out.contains(secret), "failed to redact secret in: {input}");
assert!(out.contains("[REDACTED]"), "missing [REDACTED] in: {input}");
}
}
#[test]
fn redact_bearer_token() {
let input = "Authorization: Bearer abc123def456";
let out = redact_secrets(input);
assert!(!out.contains("abc123def456"));
assert!(out.contains("[REDACTED]"));
}
#[test]
fn redact_openai_key() {
let input = "sk-abcdefghijklmnopqrstuvwxyz123456";
let out = redact_secrets(input);
assert!(!out.contains("sk-abcdefghijklmnopqrstuvwxyz123456"));
assert!(out.contains("[REDACTED]"));
}
#[test]
fn redact_private_key() {
let key = "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC...\n-----END PRIVATE KEY-----";
let out = redact_secrets(key);
assert!(!out.contains("MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC"));
assert!(out.contains("[REDACTED]"));
}
#[test]
fn redact_rsa_private_key() {
let key = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA...\n-----END RSA PRIVATE KEY-----";
let out = redact_secrets(key);
assert!(!out.contains("MIIEpAIBAAKCAQEA"));
assert!(out.contains("[REDACTED]"));
}
#[test]
fn unicode_in_context_values() {
let err = SanthError::new("TEST-U001", "Unicode test")
.with_context("emoji", "🚀🔥💀")
.with_context("chinese", "这是一个测试")
.with_context("arabic", "هذا اختبار")
.with_context("zalgo", "T̷͓̖͈̲̩̗h̴͍͙͚͕͓i̶͈s̷̡̛̞")
.fix("Fix: No action needed, this is a test.")
.build();
let msg = err.actionable_message();
assert!(msg.contains("🚀🔥💀"));
assert!(msg.contains("这是一个测试"));
assert!(msg.contains("هذا اختبار"));
assert!(msg.contains("T̷͓̖͈̲̩̗h̴͍͙͚͕͓i̶͈s̷̡̛̞"));
assert!(msg.contains("Fix: "));
}
#[test]
fn very_long_message_redaction() {
let secret = "sk-".to_string() + &"a".repeat(100_000);
let input = format!("prefix {} suffix", secret);
let out = redact_secrets(&input);
assert!(!out.contains(&secret));
assert!(out.contains("[REDACTED]"));
assert!(out.starts_with("prefix "));
assert!(out.ends_with(" suffix"));
}
#[test]
fn multiple_secrets_in_one_string() {
let input = "password=foo api_key=bar token=baz secret=qux";
let out = redact_secrets(input);
assert!(!out.contains("foo"));
assert!(!out.contains("bar"));
assert!(!out.contains("baz"));
assert!(!out.contains("qux"));
}
#[test]
fn secret_in_context_gets_redacted_in_actionable_message() {
let err = SanthError::new("TEST-SEC-01", "Request failed")
.with_context("auth", "Bearer supersecrettoken12345")
.fix("Fix: Retry the request with a valid token.")
.build();
let msg = err.actionable_message();
assert!(!msg.contains("supersecrettoken12345"));
assert!(msg.contains("[REDACTED]"));
}
#[test]
fn aws_key_in_title_gets_redacted() {
let err = SanthError::new("TEST-SEC-02", "Key AKIAIOSFODNN7EXAMPLE is invalid")
.fix("Fix: Rotate the AWS access key.")
.build();
let msg = err.actionable_message();
assert!(!msg.contains("AKIAIOSFODNN7EXAMPLE"));
assert!(msg.contains("[REDACTED]"));
}