Files
RustProxy/tests/policies.rs
T
2026-10-03 00:27:09 +01:00

109 lines
3.6 KiB
Rust

use http::HeaderMap;
use rust_proxy::{
acl::{routing_path, Acl, Action, Rule as AclRule},
config::{compile, demo_config, ValidationPolicy},
headers::{apply as apply_headers, HeaderRules, Operation, Rule as HeaderRule},
vhost::{LocationResponse, PathRoute},
};
fn policy() -> ValidationPolicy {
ValidationPolicy {
tls_addr: None,
cert_dir: None,
data_dir: None,
proxy_addr: "127.0.0.1:8080".parse().unwrap(),
admin_addr: "127.0.0.1:9090".parse().unwrap(),
}
}
fn local_route() -> PathRoute {
PathRoute {
id: "local-status".into(),
path_prefix: "/status".into(),
response: Some(LocationResponse {
status: 200,
content_type: "text/plain; charset=utf-8".into(),
body: "ok".into(),
}),
..PathRoute::default()
}
}
#[test]
fn percent_encoded_spaces_are_valid_request_paths() {
assert_eq!(
routing_path("/Ubuntu%2014.04%20LTS/").unwrap(),
"/Ubuntu 14.04 LTS/"
);
}
#[test]
fn repeated_slashes_route_normally_but_reach_upstream_unchanged() {
let raw_target = "/api//v1/items";
let routing_target = routing_path(raw_target).unwrap();
assert_eq!(routing_target, "/api/v1/items");
let mut config = demo_config("127.0.0.1:9000".parse().unwrap());
config.vhosts[0].routes = vec![PathRoute {
id: "api".into(),
path_prefix: "/api".into(),
..PathRoute::default()
}];
let snapshot = compile(config, 1, &policy()).unwrap();
let selected = snapshot.select("localhost", &routing_target).unwrap();
assert_eq!(selected.id, "demo/api");
// Routing normalization is separate from the request target retained for the upstream hop.
assert_eq!(raw_target, "/api//v1/items");
}
#[test]
fn local_location_response_uses_response_rules_without_opening_backend() {
let mut config = demo_config("127.0.0.1:9000".parse().unwrap());
let mut route = local_route();
route.headers = HeaderRules {
request: vec![],
response: vec![HeaderRule {
op: Operation::Set,
name: "x-local-response".into(),
value: Some("yes".into()),
}],
};
config.vhosts[0].routes = vec![route];
let snapshot = compile(config, 1, &policy()).unwrap();
let selected = snapshot.select("localhost", "/status").unwrap();
let response = selected.response.as_ref().expect("route must remain local");
assert_eq!(response.status, 200);
assert_eq!(response.body, "ok");
let mut headers = HeaderMap::new();
apply_headers(&mut headers, &selected.headers.response);
assert_eq!(headers.get("x-local-response").unwrap(), "yes");
// The proxy checks route.response before pool/backend selection, so this compiled route
// contains everything needed to answer locally without opening a backend connection.
assert!(selected.response.is_some());
}
#[test]
fn local_location_response_still_respects_route_acl() {
let mut config = demo_config("127.0.0.1:9000".parse().unwrap());
let mut route = local_route();
route.acl = Some(Acl {
default: Action::Deny,
rules: vec![AclRule {
network: "10.0.0.0/8".into(),
action: Action::Allow,
}],
});
config.vhosts[0].routes = vec![route];
let snapshot = compile(config, 1, &policy()).unwrap();
let selected = snapshot.select("localhost", "/status").unwrap();
let acl = selected.acl.as_ref().expect("route ACL must be compiled");
assert!(acl.allows("10.1.2.3".parse().unwrap()));
assert!(!acl.allows("192.0.2.1".parse().unwrap()));
assert!(selected.response.is_some());
}