#![cfg(feature = "test-utils")]
use xchecker::redaction::{
SecretRedactor, redact_user_optional, redact_user_string, redact_user_strings,
};
use xchecker::test_support;
#[test]
fn test_redaction_in_error_messages() {
let token = test_support::github_pat();
let error_msg = format!("Failed to authenticate with token {}", token);
let redacted = redact_user_string(&error_msg);
assert!(redacted.contains("Failed to authenticate"));
assert!(redacted.contains("***"));
assert!(!redacted.contains("ghp_"));
assert!(!redacted.contains(&token));
}
#[test]
fn test_redaction_in_context_strings() {
let token = test_support::bearer_token();
let context = format!("Request to API failed with {}", token);
let redacted = redact_user_string(&context);
assert!(redacted.contains("Request to API failed"));
assert!(redacted.contains("***"));
assert!(!redacted.contains(&token));
}
#[test]
fn test_redaction_in_warnings() {
let github_token = test_support::github_pat();
let aws_key = test_support::aws_access_key_id();
let warnings = vec![
format!("Warning: deprecated token {}", github_token),
"Warning: rate limit exceeded".to_string(),
format!("Warning: AWS key {} is invalid", aws_key),
];
let redacted = redact_user_strings(&warnings);
assert_eq!(redacted.len(), 3);
assert!(redacted[0].contains("***"));
assert!(!redacted[0].contains("ghp_"));
assert_eq!(redacted[1], "Warning: rate limit exceeded");
assert!(redacted[2].contains("***"));
assert!(!redacted[2].contains("AKIA"));
}
#[test]
fn test_redaction_in_stderr() {
let token = test_support::github_pat();
let stderr = format!(
"Error: Authentication failed\nToken: {}\nPlease check your credentials",
token
);
let redacted = redact_user_string(&stderr);
assert!(redacted.contains("Error: Authentication failed"));
assert!(redacted.contains("***"));
assert!(!redacted.contains("ghp_"));
assert!(redacted.contains("Please check your credentials"));
}
#[test]
fn test_redaction_preserves_structure() {
let token = test_support::github_pat();
let text = format!("Line 1: safe\nLine 2: {}\nLine 3: safe", token);
let redacted = redact_user_string(&text);
assert!(redacted.contains("Line 1: safe"));
assert!(redacted.contains("Line 2:"));
assert!(redacted.contains("***"));
assert!(redacted.contains("Line 3: safe"));
assert!(!redacted.contains("ghp_"));
}
#[test]
fn test_redaction_with_optional_none() {
let none_value: Option<String> = None;
let redacted = redact_user_optional(&none_value);
assert!(redacted.is_none());
}
#[test]
fn test_redaction_with_optional_some() {
let token = test_support::github_pat();
let some_value = Some(format!("Error with token {}", token));
let redacted = redact_user_optional(&some_value);
assert!(redacted.is_some());
let redacted_str = redacted.unwrap();
assert!(redacted_str.contains("***"));
assert!(!redacted_str.contains("ghp_"));
}
#[test]
fn test_all_default_patterns_redacted() {
let redactor = SecretRedactor::new().unwrap();
let github_token = test_support::github_pat();
let aws_key = test_support::aws_access_key_id();
let aws_secret = test_support::aws_secret_access_key();
let slack_token = test_support::slack_bot_token();
let bearer_token = test_support::bearer_token();
let test_cases = vec![
("GitHub PAT", format!("token: {}", github_token), "ghp_"),
("AWS Access Key", format!("key: {}", aws_key), "AKIA"),
("AWS Secret Key", aws_secret, "AWS_SECRET_ACCESS_KEY"),
("Slack Token", format!("slack: {}", slack_token), "xoxb-"),
(
"Bearer Token",
format!("Authorization: {}", bearer_token),
"Bearer eyJ",
),
];
for (name, input, secret_part) in test_cases {
let redacted = redactor.redact_string(&input);
assert!(redacted.contains("***"), "Failed to redact {name}");
assert!(
!redacted.contains(secret_part),
"Secret {name} still present in: {redacted}"
);
}
}
#[test]
fn test_redaction_in_file_paths() {
let token = test_support::github_pat();
let path = format!("/home/user/.config/{}/config.yaml", token);
let redacted = redact_user_string(&path);
assert!(redacted.contains("/home/user/.config/"));
assert!(redacted.contains("***"));
assert!(!redacted.contains("ghp_"));
assert!(redacted.contains("/config.yaml"));
}
#[test]
fn test_redaction_in_json_like_strings() {
let token = test_support::github_pat();
let json_str = format!(r#"{{"token": "{}", "user": "test"}}"#, token);
let redacted = redact_user_string(&json_str);
assert!(redacted.contains(r#"{"token": "***"#));
assert!(!redacted.contains("ghp_"));
assert!(redacted.contains(r#""user": "test""#));
}
#[test]
fn test_redaction_empty_string() {
let empty = "";
let redacted = redact_user_string(empty);
assert_eq!(redacted, "");
}
#[test]
fn test_redaction_whitespace_only() {
let whitespace = " \n\t ";
let redacted = redact_user_string(whitespace);
assert_eq!(redacted, whitespace);
}
#[test]
fn test_redaction_multiple_occurrences() {
let token = test_support::github_pat();
let text = format!("First: {token}, Second: {token}");
let redacted = redact_user_string(&text);
assert!(!redacted.contains("ghp_"));
let count = redacted.matches("***").count();
assert_eq!(count, 2, "Expected 2 redactions, got {count}");
}
#[test]
fn test_redaction_mixed_secrets() {
let github_token = test_support::github_pat();
let aws_key = test_support::aws_access_key_id();
let slack_token = test_support::slack_bot_token();
let text = format!(
"GitHub: {}, AWS: {}, Slack: {}",
github_token, aws_key, slack_token
);
let redacted = redact_user_string(&text);
assert!(!redacted.contains("ghp_"));
assert!(!redacted.contains("AKIA"));
assert!(!redacted.contains("xoxb-"));
assert!(redacted.contains("***"));
}
#[test]
fn test_redaction_case_sensitivity() {
let redactor = SecretRedactor::new().unwrap();
let aws_upper = test_support::aws_access_key_id();
let redacted_upper = redactor.redact_string(&aws_upper);
assert!(redacted_upper.contains("***"));
let aws_lower = aws_upper.to_lowercase();
let redacted_lower = redactor.redact_string(&aws_lower);
assert_eq!(redacted_lower, aws_lower); }
#[test]
fn test_redaction_partial_matches() {
let text = "This is not a secret: ghp_short or AKIA_short";
let redacted = redact_user_string(text);
assert_eq!(redacted, text);
}
#[test]
fn test_redaction_in_urls() {
let token = test_support::github_pat();
let url = format!("https://api.github.com/repos/owner/repo?token={}", token);
let redacted = redact_user_string(&url);
assert!(redacted.contains("https://api.github.com/repos/owner/repo?token="));
assert!(redacted.contains("***"));
assert!(!redacted.contains("ghp_"));
}
#[test]
fn test_redaction_in_command_output() {
let token = test_support::bearer_token();
let output = format!(
"$ curl -H 'Authorization: {}' https://api.example.com\nHTTP/1.1 200 OK",
token
);
let redacted = redact_user_string(&output);
assert!(redacted.contains("$ curl -H 'Authorization:"));
assert!(redacted.contains("***"));
assert!(!redacted.contains("Bearer eyJ"));
assert!(redacted.contains("HTTP/1.1 200 OK"));
}
#[test]
fn test_redaction_performance() {
let token = test_support::github_pat();
let large_text = "safe text ".repeat(1000) + &token;
let start = std::time::Instant::now();
let redacted = redact_user_string(&large_text);
let duration = start.elapsed();
assert!(redacted.contains("***"));
assert!(!redacted.contains("ghp_"));
assert!(
duration.as_millis() < 100,
"Redaction took too long: {duration:?}"
);
}