use std::collections::HashMap;
use hyper::HeaderMap;
use hyper::header::{HeaderName, HeaderValue};
use super::Headers;
fn parse_headers(toml_text: &str) -> Headers {
let wrapped = format!("[headers]\n{}", toml_text);
#[derive(serde::Deserialize)]
struct Wrapper {
headers: Headers,
}
let w: Wrapper = toml::from_str(&wrapped).expect("parse headers TOML");
w.headers
}
fn make_request_headers<I: IntoIterator<Item = (&'static str, &'static str)>>(
pairs: I,
) -> HeaderMap<HeaderValue> {
let mut map = HeaderMap::new();
for (k, v) in pairs {
map.insert(
HeaderName::from_static(k),
HeaderValue::from_str(v).unwrap(),
);
}
map
}
#[test]
fn is_match_op_default_equal_when_op_omitted() {
let headers = parse_headers(r#"x-api-key = { value = "secret" }"#);
let req = make_request_headers([("x-api-key", "secret")]);
assert!(headers.is_match(&req, 0, 0));
}
#[test]
fn is_match_op_default_equal_no_match() {
let headers = parse_headers(r#"x-api-key = { value = "secret" }"#);
let req = make_request_headers([("x-api-key", "wrong")]);
assert!(!headers.is_match(&req, 0, 0));
}
#[test]
fn is_match_op_equal_match() {
let headers = parse_headers(r#"x-api-key = { op = "equal", value = "secret" }"#);
let req = make_request_headers([("x-api-key", "secret")]);
assert!(headers.is_match(&req, 0, 0));
}
#[test]
fn is_match_op_not_equal_match() {
let headers = parse_headers(r#"x-api-key = { op = "not_equal", value = "secret" }"#);
let req = make_request_headers([("x-api-key", "different")]);
assert!(headers.is_match(&req, 0, 0));
}
#[test]
fn is_match_op_not_equal_when_equal_returns_false() {
let headers = parse_headers(r#"x-api-key = { op = "not_equal", value = "secret" }"#);
let req = make_request_headers([("x-api-key", "secret")]);
assert!(!headers.is_match(&req, 0, 0));
}
#[test]
fn is_match_op_starts_with_match() {
let headers = parse_headers(r#"user-agent = { op = "starts_with", value = "Mozilla" }"#);
let req = make_request_headers([("user-agent", "Mozilla/5.0 (X11)")]);
assert!(headers.is_match(&req, 0, 0));
}
#[test]
fn is_match_op_starts_with_no_match() {
let headers = parse_headers(r#"user-agent = { op = "starts_with", value = "Mozilla" }"#);
let req = make_request_headers([("user-agent", "curl/8.4")]);
assert!(!headers.is_match(&req, 0, 0));
}
#[test]
fn is_match_op_contains_match() {
let headers = parse_headers(r#"user-agent = { op = "contains", value = "Firefox" }"#);
let req = make_request_headers([("user-agent", "Mozilla/5.0 Firefox/120")]);
assert!(headers.is_match(&req, 0, 0));
}
#[test]
fn is_match_op_contains_no_match() {
let headers = parse_headers(r#"user-agent = { op = "contains", value = "Firefox" }"#);
let req = make_request_headers([("user-agent", "Mozilla/5.0 Chrome/120")]);
assert!(!headers.is_match(&req, 0, 0));
}
#[test]
fn is_match_op_wild_card_match() {
let headers = parse_headers(r#"user-agent = { op = "wild_card", value = "Mozilla*" }"#);
let req = make_request_headers([("user-agent", "Mozilla/5.0 anything-here")]);
assert!(headers.is_match(&req, 0, 0));
}
#[test]
fn is_match_key_missing_returns_false() {
let headers = parse_headers(r#"x-api-key = { value = "secret" }"#);
let req = make_request_headers([("user-agent", "x")]);
assert!(!headers.is_match(&req, 0, 0));
}
#[test]
fn is_match_multiple_conditions_all_match() {
let headers = parse_headers(
r#"x-api-key = { value = "secret" }
x-tenant = { op = "equal", value = "acme" }"#,
);
let req = make_request_headers([("x-api-key", "secret"), ("x-tenant", "acme")]);
assert!(headers.is_match(&req, 0, 0));
}
#[test]
fn is_match_multiple_conditions_one_fails() {
let headers = parse_headers(
r#"x-api-key = { value = "secret" }
x-tenant = { op = "equal", value = "acme" }"#,
);
let req = make_request_headers([("x-api-key", "secret"), ("x-tenant", "wrong")]);
assert!(!headers.is_match(&req, 0, 0));
}
#[test]
fn is_match_utf8_decode_failure_returns_true() {
let headers = parse_headers(r#"x-thing = { value = "anything" }"#);
let mut req = HeaderMap::new();
req.insert(
HeaderName::from_static("x-thing"),
HeaderValue::from_bytes(&[0xff, 0xfe, 0x80]).unwrap(),
);
assert!(headers.is_match(&req, 0, 0));
}
#[test]
fn validate_empty_returns_false() {
let h = Headers(indexmap::IndexMap::new());
assert!(!h.validate());
}
#[test]
fn validate_non_empty_returns_true() {
let h = parse_headers(r#"x-api-key = { value = "v" }"#);
assert!(h.validate());
}
#[test]
fn deserialize_value_only() {
let h = parse_headers(r#"x-api-key = { value = "secret" }"#);
assert!(h.0.contains_key("x-api-key"));
assert!(h.0["x-api-key"].op.is_none());
assert_eq!(h.0["x-api-key"].value, "secret");
}
#[test]
fn deserialize_with_each_op_variant() {
let h = parse_headers(
r#"a = { op = "equal", value = "x" }
b = { op = "not_equal", value = "x" }
c = { op = "starts_with", value = "x" }
d = { op = "contains", value = "x" }
e = { op = "wild_card", value = "x" }"#,
);
assert_eq!(h.0.len(), 5);
for key in ["a", "b", "c", "d", "e"] {
assert!(h.0[key].op.is_some(), "op missing for `{}`", key);
}
}
#[test]
fn deserialize_multiple_keys_preserve_each_value() {
let h = parse_headers(
r#"x-one = { value = "alpha" }
x-two = { value = "beta" }
x-three = { value = "gamma" }"#,
);
assert_eq!(h.0.len(), 3);
assert_eq!(h.0["x-one"].value, "alpha");
assert_eq!(h.0["x-two"].value, "beta");
assert_eq!(h.0["x-three"].value, "gamma");
}
#[test]
fn headers_programmatic_insertion_preserves_order() {
use indexmap::IndexMap;
use crate::rule_set::rule::when::condition_statement::ConditionStatement;
let mut map: IndexMap<String, ConditionStatement> = IndexMap::new();
map.insert("z-header".to_owned(), ConditionStatement { op: None, value: "z".to_owned() });
map.insert("a-header".to_owned(), ConditionStatement { op: None, value: "a".to_owned() });
map.insert("m-header".to_owned(), ConditionStatement { op: None, value: "m".to_owned() });
let h = Headers(map);
let keys: Vec<&str> = h.0.keys().map(String::as_str).collect();
assert_eq!(
keys,
vec!["z-header", "a-header", "m-header"],
"IndexMap must iterate in insertion order, not alphabetical"
);
}
#[test]
fn when_view_headers_programmatic_insertion_order() {
use indexmap::IndexMap;
use crate::rule_set::rule::when::condition_statement::ConditionStatement;
use crate::view::build::build_when_view;
use crate::rule_set::rule::when::{When, request::Request};
let mut map: IndexMap<String, ConditionStatement> = IndexMap::new();
map.insert("authorization".to_owned(),
ConditionStatement { op: None, value: "Bearer x".to_owned() });
map.insert("x-tenant-id".to_owned(),
ConditionStatement { op: None, value: "acme".to_owned() });
map.insert("content-type".to_owned(),
ConditionStatement { op: None, value: "json".to_owned() });
let when = When {
request: Request {
url_path_config: None,
url_path: None,
http_method: None,
headers: Some(Headers(map)),
body: None,
},
};
let view = build_when_view(&when);
let names: Vec<&str> = view.headers.iter().map(|c| c.name.as_str()).collect();
assert_eq!(
names,
vec!["authorization", "x-tenant-id", "content-type"],
"WhenView.headers must reflect programmatic insertion order (not alphabetical)"
);
}