use super::*;
#[test]
fn identity_passthrough() {
assert_eq!(
apply_encoding("test<>&\"'", "identity").unwrap(),
"test<>&\"'"
);
assert_eq!(apply_encoding("test<>&\"'", "raw").unwrap(), "test<>&\"'");
}
#[test]
fn url_encoding() {
assert_eq!(apply_encoding("a b", "url_encode").unwrap(), "a%20b");
assert_eq!(apply_encoding("a b", "url").unwrap(), "a%20b");
}
#[test]
fn double_url() {
assert_eq!(apply_encoding("a b", "double_url").unwrap(), "a%2520b");
}
#[test]
fn hex_encoding() {
assert_eq!(apply_encoding("AB", "hex").unwrap(), "%41%42");
}
#[test]
fn unicode_encoding() {
assert_eq!(apply_encoding("AB", "unicode").unwrap(), "\\u0041\\u0042");
}
#[test]
fn octal_encoding_exact_output() {
assert_eq!(apply_encoding("AB", "octal").unwrap(), "\\101\\102");
assert_eq!(apply_encoding("\u{0}\u{7}", "octal").unwrap(), "\\000\\007");
}
#[test]
fn css_escape_encoding_exact_output() {
assert_eq!(apply_encoding("AB", "css_escape").unwrap(), "\\41\\42");
assert_eq!(apply_encoding("é", "css_escape").unwrap(), "\\e9");
}
#[test]
fn html_entities() {
assert_eq!(
apply_encoding("<script>alert('xss')</script>", "html_entities").unwrap(),
"<script>alert('xss')</script>"
);
}
#[test]
fn null_byte() {
assert_eq!(apply_encoding("test", "null_byte").unwrap(), "test%00");
}
#[test]
fn base64() {
assert_eq!(apply_encoding("hello", "base64").unwrap(), "aGVsbG8=");
assert_eq!(apply_encoding("AB", "base64").unwrap(), "QUI=");
assert_eq!(apply_encoding("ABC", "base64").unwrap(), "QUJD");
}
#[test]
fn js_concat_escapes_structural_chars() {
assert_eq!(apply_encoding("ab", "js_concat").unwrap(), "'a'+'b'");
assert_eq!(apply_encoding("a'b", "js_concat").unwrap(), "'a'+'\\''+'b'");
assert_eq!(
apply_encoding("a\\b", "js_concat").unwrap(),
"'a'+'\\\\'+'b'"
);
assert_eq!(apply_encoding("\n", "js_concat").unwrap(), "'\\n'");
}
#[test]
fn charcode() {
assert_eq!(
apply_encoding("AB", "charcode").unwrap(),
"String.fromCodePoint(65,66)"
);
assert_eq!(
apply_encoding("\u{1F600}", "charcode").unwrap(),
"String.fromCodePoint(128512)"
);
}
#[test]
fn concat_split() {
assert_eq!(apply_encoding("AB", "concat_split").unwrap(), "'A'+'B'");
}
#[test]
fn case_alternate() {
assert_eq!(
apply_encoding("script", "case_alternate").unwrap(),
"sCrIpT"
);
}
#[test]
fn php_chr() {
assert_eq!(apply_encoding("AB", "php_chr").unwrap(), "chr(65).chr(66)");
}
#[test]
fn python_chr() {
assert_eq!(
apply_encoding("AB", "python_chr").unwrap(),
"\"\".join([chr(65),chr(66)])"
);
}
#[test]
fn sql_char() {
assert_eq!(
apply_encoding("AB", "sql_char").unwrap(),
"CONCAT(CHAR(65),CHAR(66))"
);
}
#[test]
fn unknown_encoding_returns_error() {
let err = apply_encoding("test", "unknown_enc").unwrap_err();
assert!(matches!(
err,
EncodingError::UnknownTransform { transform } if transform == "unknown_enc"
));
}
#[test]
fn all_builtins_listed() {
for name in BuiltinEncoding::ALL {
assert!(!name.is_empty());
}
assert!(BuiltinEncoding::ALL.len() >= 18);
}
#[cfg(test)]
mod roundtrip_tests {
use super::*;
#[test]
fn url_encode_preserves_alphanumeric() {
let input = "abcdefghijklmnopqrstuvwxyz0123456789";
let encoded = apply_encoding(input, "url_encode").unwrap();
assert_eq!(
encoded, input,
"alphanumeric should pass through URL encoding"
);
}
#[test]
fn url_encode_encodes_special_chars() {
assert!(apply_encoding("<script>", "url_encode")
.unwrap()
.contains("%3C"));
assert!(apply_encoding(" ", "url_encode").unwrap().contains("%20"));
assert!(apply_encoding("'", "url_encode").unwrap().contains("%27"));
}
#[test]
fn double_url_differs_from_single() {
let input = "hello world";
let single = apply_encoding(input, "url_encode").unwrap();
let double = apply_encoding(input, "double_url").unwrap();
assert_ne!(single, double);
assert!(
double.contains("%25"),
"double URL should encode the % itself"
);
let adversarial = "<script>alert(1)</script>";
let single_adv = apply_encoding(adversarial, "url_encode").unwrap();
let double_adv = apply_encoding(adversarial, "double_url").unwrap();
assert_eq!(single_adv, "%3Cscript%3Ealert%281%29%3C%2Fscript%3E");
assert_eq!(
double_adv,
"%253Cscript%253Ealert%25281%2529%253C%252Fscript%253E"
);
}
#[test]
fn double_url_massive_input() {
let input = " ".repeat(10_000);
let single = apply_encoding(&input, "url_encode").unwrap();
let double = apply_encoding(&input, "double_url").unwrap();
assert_eq!(single.len(), 30_000);
assert_eq!(double.len(), 50_000); }
#[test]
fn unicode_escape_adversarial() {
let encoded = apply_encoding("é \u{0000} 🤡 <script>", "unicode").unwrap();
assert_eq!(encoded, "\\u00e9\\u0020\\u0000\\u0020\\ud83e\\udd21\\u0020\\u003c\\u0073\\u0063\\u0072\\u0069\\u0070\\u0074\\u003e");
}
#[test]
fn hex_produces_percent_encoded_bytes() {
let encoded = apply_encoding("A", "hex").unwrap();
assert_eq!(encoded, "%41");
}
#[test]
fn unicode_produces_backslash_u() {
let encoded = apply_encoding("A", "unicode").unwrap();
assert_eq!(encoded, "\\u0041");
}
#[test]
fn base64_known_vectors() {
assert_eq!(apply_encoding("", "base64").unwrap(), "");
assert_eq!(apply_encoding("f", "base64").unwrap(), "Zg==");
assert_eq!(apply_encoding("fo", "base64").unwrap(), "Zm8=");
assert_eq!(apply_encoding("foo", "base64").unwrap(), "Zm9v");
assert_eq!(apply_encoding("foob", "base64").unwrap(), "Zm9vYg==");
assert_eq!(apply_encoding("fooba", "base64").unwrap(), "Zm9vYmE=");
assert_eq!(apply_encoding("foobar", "base64").unwrap(), "Zm9vYmFy");
}
#[test]
fn html_entities_escapes_all_dangerous() {
let encoded = apply_encoding("<>&\"'", "html_entities").unwrap();
assert!(!encoded.contains('<'));
assert!(!encoded.contains('>'));
assert_eq!(encoded, "<>&"'");
assert!(!encoded.contains('"') || encoded.contains("""));
}
#[test]
fn html_entities_adversarial_unicode() {
let encoded = apply_encoding("é \u{0000} 🤡 <script>", "html_entities").unwrap();
assert_eq!(encoded, "é \u{0000} 🤡 <script>");
}
#[test]
fn base64_adversarial_inputs() {
let malformed = "\0\n\r\t";
let encoded = apply_encoding(malformed, "base64").unwrap();
assert_eq!(encoded, "AAoNCQ=="); }
#[test]
fn null_byte_appends() {
assert!(apply_encoding("test", "null_byte")
.unwrap()
.ends_with("%00"));
}
#[test]
fn charcode_produces_fromcodepoint() {
let encoded = apply_encoding("a", "charcode").unwrap();
assert_eq!(encoded, "String.fromCodePoint(97)");
}
#[test]
fn sql_char_produces_concat() {
let encoded = apply_encoding("A", "sql_char").unwrap();
assert_eq!(encoded, "CONCAT(CHAR(65))");
}
#[test]
fn empty_input_all_encodings() {
for name in BuiltinEncoding::ALL {
let result = apply_encoding("", name).unwrap();
assert!(
result.is_empty() || !result.is_empty(),
"encoding {name} should return a concrete string for empty input"
);
}
}
#[test]
fn unicode_input_all_encodings() {
for name in BuiltinEncoding::ALL {
let result = apply_encoding("日本語テスト", name).unwrap();
assert!(
!result.is_empty(),
"encoding {name} should preserve a concrete unicode output"
);
}
}
#[test]
fn very_long_input() {
let long = "A".repeat(10_000);
for name in &["identity", "url_encode", "hex", "base64"] {
let result = apply_encoding(&long, name).unwrap();
assert!(!result.is_empty());
}
}
}
#[test]
fn all_and_dispatch_are_bidirectionally_complete() {
use std::str::FromStr;
for name in BuiltinEncoding::ALL {
let variant = BuiltinEncoding::from_str(name)
.unwrap_or_else(|_| panic!("ALL entry {name} does not parse"));
apply_encoding("probe", name)
.unwrap_or_else(|_| panic!("ALL entry {name} does not apply"));
assert_eq!(
BuiltinEncoding::from_str(&variant.to_string()).as_ref(),
Ok(&variant),
"canonical name of {variant} does not round-trip"
);
assert!(
BuiltinEncoding::ALL.contains(&variant.to_string().as_str()),
"canonical name of {variant} missing from ALL"
);
}
assert!(BuiltinEncoding::from_str("not_an_encoding").is_err());
assert!(apply_encoding("probe", "not_an_encoding").is_err());
}