#![allow(missing_docs)]
use rtb_redact::{
is_sensitive_header, redact_header_value, string, string_into, SENSITIVE_HEADERS,
};
#[test]
fn t1_empty_string_roundtrips() {
assert_eq!(string(""), "");
}
#[test]
fn t2_clean_string_roundtrips() {
let input = "hello world — nothing sensitive here";
assert_eq!(string(input), input);
}
#[test]
fn t3_url_userinfo_redacted() {
let out = string("connect to https://alice:hunter2@host/path");
assert!(out.contains("https://[redacted]@host/path"), "got: {out}");
assert!(!out.contains("alice"), "userinfo leaked: {out}");
assert!(!out.contains("hunter2"), "password leaked: {out}");
}
#[test]
fn t4_bearer_token_redacted() {
let out = string("Authorization: Bearer ghp_abcdefghijklmnopqrstuvwxyz123456");
assert!(out.contains("Bearer [redacted]"), "got: {out}");
assert!(!out.contains("ghp_"), "token leaked: {out}");
}
#[test]
fn t5_basic_token_redacted() {
let out = string("Authorization: Basic ZGF2ZTpodW50ZXIy");
assert!(out.contains("Basic [redacted]"), "got: {out}");
assert!(!out.contains("ZGF2ZTpodW50ZXIy"), "token leaked: {out}");
}
#[test]
fn t6_query_param_redacted_key_preserved() {
let out = string("GET /foo?api_key=sk-abcdef1234567890abcdef&tag=prod");
assert!(out.contains("api_key=[redacted]"), "got: {out}");
assert!(out.contains("tag=prod"), "non-sensitive param lost: {out}");
assert!(!out.contains("sk-abcdef"), "secret leaked: {out}");
}
#[test]
fn t7_case_insensitive_query_key() {
let cases = [
"?API_KEY=verylongtokenxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"?apikey=verylongtokenxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"?X-API-Key=verylongtokenxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
];
for input in cases {
let out = string(input);
assert!(out.contains("=[redacted]"), "case {input} → {out}");
assert!(!out.contains("verylongtoken"), "secret leaked in {out}");
}
}
#[test]
fn t8_provider_prefixes_redacted() {
let samples = [
concat!("sk-", "abc123456789abcdef"), concat!("sk-ant-", "api03-abc123456789abcdefghij"), concat!("ghp_", "abc1234567890abcdef"), concat!("glpat-", "abc1234567890abcd"), concat!("AIza", "SyABCDEFGHIJKLMNOPQR"), concat!("AKIA", "ABCDEFGHIJKLMNOP"), concat!("xoxb-", "1234567890-abcdefghijkl"), ];
for secret in samples {
let input = format!("token is {secret} done");
let out = string(&input);
assert!(out.contains("[redacted]"), "sample {secret} → {out}");
assert!(!out.contains(secret), "sample {secret} leaked: {out}");
}
}
#[test]
fn t9_short_prefix_passthrough() {
let out = string("short sk-abc tail");
assert_eq!(out, "short sk-abc tail");
}
#[test]
fn t10_long_opaque_token_redacted() {
let token = concat!("abcdefghijklmnop", "qrstuvwxyz0123456789ABCD"); let input = format!("opaque {token} done");
let out = string(&input);
assert!(out.contains("[redacted]"), "got: {out}");
assert!(!out.contains(token), "token leaked: {out}");
assert!(out.contains("opaque "), "leading space lost: {out}");
assert!(out.contains(" done"), "trailing space lost: {out}");
}
#[test]
fn t11_jwt_redacted() {
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.\
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.\
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
let input = format!("auth: {jwt}");
let out = string(&input);
assert!(out.contains("[redacted]"), "got: {out}");
assert!(!out.contains("eyJhbGciOiJIUzI1NiI"), "JWT header leaked: {out}");
}
#[test]
fn t12_pem_private_key_redacted() {
let pem = "prefix text\n\
-----BEGIN RSA PRIVATE KEY-----\n\
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7VJTUt9Us8cKj\n\
MZngKj9Y4oEZ9Yyo8D0lfMfPcE0yXBX3vHvwvqjjHGmTIabsoklBhuBXUoMAwawI\n\
-----END RSA PRIVATE KEY-----\n\
suffix text";
let out = string(pem);
assert!(out.contains("-----BEGIN PRIVATE KEY-----"), "header lost: {out}");
assert!(out.contains("[redacted]"), "body not redacted: {out}");
assert!(out.contains("-----END PRIVATE KEY-----"), "footer lost: {out}");
assert!(!out.contains("MIIEvQIBADAN"), "key material leaked: {out}");
assert!(out.contains("prefix text"), "surrounding text lost: {out}");
assert!(out.contains("suffix text"), "surrounding text lost: {out}");
}
#[test]
fn t13_sensitive_headers_coverage() {
for required in [
"authorization",
"x-api-key",
"cookie",
"x-anthropic-api-key",
"x-openai-api-key",
"x-goog-api-key",
"x-amz-security-token",
] {
assert!(SENSITIVE_HEADERS.contains(required), "missing required header: {required}");
}
}
#[test]
fn t14_is_sensitive_header_case_insensitive() {
assert!(is_sensitive_header("AUTHORIZATION"));
assert!(is_sensitive_header("Authorization"));
assert!(is_sensitive_header("authorization"));
assert!(is_sensitive_header("X-API-Key"));
assert!(!is_sensitive_header("content-type"));
}
#[test]
fn t15_redact_header_value() {
assert_eq!(redact_header_value(""), "");
assert_eq!(redact_header_value("Bearer abc"), "[redacted]");
assert_eq!(redact_header_value("anything"), "[redacted]");
}
#[test]
fn t16_string_into_reuses_buffer() {
let mut buf = String::with_capacity(256);
string_into("hello world", &mut buf);
assert_eq!(buf, "hello world");
let cap_before = buf.capacity();
string_into("different", &mut buf);
assert_eq!(buf, "different");
assert!(
buf.capacity() >= cap_before,
"capacity shrank unexpectedly: {} → {}",
cap_before,
buf.capacity(),
);
buf.push_str(" leftover");
string_into("fresh", &mut buf);
assert_eq!(buf, "fresh");
}
#[test]
fn t17_no_unsafe_code_is_forbid() {
}
#[test]
fn cow_borrowed_on_clean_input() {
let input = "hello world";
let out = string(input);
match out {
std::borrow::Cow::Borrowed(s) => assert_eq!(s, input),
std::borrow::Cow::Owned(_) => {
panic!("expected Borrowed for clean input")
}
}
}
#[test]
fn postgres_url_only_userinfo_redacted() {
let out = string("postgres://app:hunter2@db.internal/mydb");
assert_eq!(out, "postgres://[redacted]@db.internal/mydb");
}