perimeterx-fastly-enforcer 2.2.2

PerimeterX Fastly Compute@Edge Rust Enforcer
Documentation
use base64::{engine::general_purpose, Engine as _};
use std::collections::HashMap;

use crate::modules::pxutils::*;

#[test]
fn parse_cookie_header_splits_pairs() {
    let cookies = parse_cookie_header("cookie1=value1; cookie3=value3");
    assert_eq!(cookies.get("cookie1").map(String::as_str), Some("value1"));
    assert_eq!(cookies.get("cookie3").map(String::as_str), Some("value3"));
}

#[test]
fn merge_cookie_headers_custom_overrides_cookie() {
    let cookie = parse_cookie_header("cookie1=value1; cookie3=value3");
    let custom = parse_cookie_header("cookie2=value2; cookie3=value4");
    let merged = merge_cookie_headers(cookie, custom);

    assert_eq!(merged.get("cookie1").map(String::as_str), Some("value1"));
    assert_eq!(merged.get("cookie2").map(String::as_str), Some("value2"));
    assert_eq!(merged.get("cookie3").map(String::as_str), Some("value4"));
}

#[test]
fn extract_cookie_value_reads_from_map() {
    let mut cookies = HashMap::new();
    cookies.insert("_pxvid".to_string(), "vid-123".to_string());
    assert_eq!(extract_cookie_value(&cookies, "_pxvid"), "vid-123");
    assert!(extract_cookie_value(&cookies, "missing").is_empty());
}

#[test]
fn build_merged_request_cookies_without_custom_header() {
    let merged = build_merged_request_cookies("cookie1=value1; _pxvid=vid1", "");
    assert_eq!(merged.get("cookie1").map(String::as_str), Some("value1"));
    assert_eq!(merged.get("_pxvid").map(String::as_str), Some("vid1"));
}

#[test]
fn decode_jwt_payload_reads_url_safe_segment() {
    let payload = serde_json::json!({"user": {"id": "abc"}});
    let encoded = general_purpose::URL_SAFE_NO_PAD.encode(payload.to_string().as_bytes());
    let token = format!("hdr.{encoded}.sig");
    let decoded = decode_jwt_payload(&token);
    assert_eq!(
        decoded
            .as_ref()
            .and_then(|v| v.get("user"))
            .and_then(|v| v.get("id"))
            .and_then(serde_json::Value::as_str),
        Some("abc")
    );
}

#[test]
fn get_value_at_path_supports_dot_notation() {
    let value = serde_json::json!({"profile": {"id": "value_1"}});
    assert_eq!(
        get_value_at_path(&value, "profile.id").and_then(serde_json::Value::as_str),
        Some("value_1")
    );
}

#[test]
fn is_valid_uuid_accepts_lowercase_v4() {
    assert!(is_valid_uuid("550e8400-e29b-41d4-a716-446655440000"));
}

#[test]
fn is_valid_uuid_rejects_empty_and_missing() {
    assert!(!is_valid_uuid(""));
}

#[test]
fn is_valid_uuid_rejects_uppercase() {
    assert!(!is_valid_uuid("550E8400-E29B-41D4-A716-446655440000"));
}

#[test]
fn is_valid_uuid_rejects_malformed_values() {
    assert!(!is_valid_uuid("vid-123"));
    assert!(!is_valid_uuid("not-a-uuid"));
}

#[test]
fn is_valid_uuid_rejects_invalid_version_and_variant() {
    assert!(!is_valid_uuid("550e8400-e29b-61d4-a716-446655440000"));
    assert!(!is_valid_uuid("550e8400-e29b-41d4-c716-446655440000"));
}

#[test]
fn build_merged_request_cookies_derives_px_fields() {
    let merged = build_merged_request_cookies(
        "cookie1=value1; _pxvid=vid-cookie; _pxhd=hd-cookie; pxcts=cts-cookie; _pxac=ac-cookie",
        "_pxvid=vid-custom; cookie3=value4",
    );

    assert_eq!(extract_cookie_value(&merged, "_pxvid"), "vid-custom");
    assert_eq!(extract_cookie_value(&merged, "_pxhd"), "hd-cookie");
    assert_eq!(extract_cookie_value(&merged, "pxcts"), "cts-cookie");
    assert_eq!(extract_cookie_value(&merged, "_pxac"), "ac-cookie");
    assert_eq!(merged.get("cookie3").map(String::as_str), Some("value4"));
    assert_eq!(merged.len(), 6);
}

#[test]
fn first_ip_from_comma_separated_header_uses_first_nonempty() {
    assert_eq!(
        first_ip_from_header_value(" 203.0.113.1 , 198.51.100.2 "),
        Some("203.0.113.1".to_owned())
    );
}

#[test]
fn first_ip_from_empty_value_returns_none() {
    assert_eq!(first_ip_from_header_value(""), None);
    assert_eq!(first_ip_from_header_value("  ,  "), None);
}

#[test]
fn extract_ip_prefers_first_configured_header() {
    let headers_map = HashMap::from([
        ("True-Client-IP", "1.1.1.1"),
        ("X-Forwarded-For", "2.2.2.2"),
    ]);
    let ip_headers = vec!["True-Client-IP".to_owned(), "X-Forwarded-For".to_owned()];
    assert_eq!(
        extract_ip_from_header_values(&ip_headers, |name| {
            headers_map.get(name).map(|value| (*value).to_owned())
        }),
        Some("1.1.1.1".to_owned())
    );
}

#[test]
fn extract_ip_skips_missing_and_empty_headers() {
    let headers_map = HashMap::from([("X-Forwarded-For", "  , 203.0.113.9 ")]);
    let ip_headers = vec!["True-Client-IP".to_owned(), "X-Forwarded-For".to_owned()];
    assert_eq!(
        extract_ip_from_header_values(&ip_headers, |name| {
            headers_map.get(name).map(|value| (*value).to_owned())
        }),
        Some("203.0.113.9".to_owned())
    );
}

#[test]
fn extract_ip_returns_none_when_no_configured_header_has_ip() {
    let headers_map: HashMap<&str, &str> = HashMap::new();
    let ip_headers = vec!["X-Forwarded-For".to_owned()];
    assert_eq!(
        extract_ip_from_header_values(&ip_headers, |name| {
            headers_map.get(name).map(|value| (*value).to_owned())
        }),
        None
    );
}

#[test]
fn ip_matches_filter_exact_ipv4() {
    assert!(ip_matches_filter("10.0.0.1", "10.0.0.1"));
    assert!(!ip_matches_filter("10.0.0.2", "10.0.0.1"));
}

#[test]
fn ip_matches_filter_cidr_8() {
    assert!(ip_matches_filter("10.1.2.3", "10.0.0.0/8"));
    assert!(!ip_matches_filter("11.1.2.3", "10.0.0.0/8"));
}

#[test]
fn ip_matches_filter_cidr_16() {
    assert!(ip_matches_filter("10.1.2.3", "10.1.0.0/16"));
    assert!(!ip_matches_filter("10.2.2.3", "10.1.0.0/16"));
}

#[test]
fn ip_matches_filter_cidr_24() {
    assert!(ip_matches_filter("10.1.2.3", "10.1.2.0/24"));
    assert!(!ip_matches_filter("10.1.3.3", "10.1.2.0/24"));
}

#[test]
fn ip_matches_filter_cidr_32() {
    assert!(ip_matches_filter("10.1.2.3", "10.1.2.3/32"));
    assert!(!ip_matches_filter("10.1.2.4", "10.1.2.3/32"));
}

#[test]
fn ip_matches_filter_cidr_0() {
    assert!(ip_matches_filter("10.1.2.3", "0.0.0.0/0"));
    assert!(ip_matches_filter("203.0.113.9", "0.0.0.0/0"));
}

#[test]
fn ip_matches_filter_rejects_unsupported_cidr_prefix() {
    assert!(!ip_matches_filter("10.1.2.3", "10.1.0.0/12"));
}

#[test]
fn ip_matches_filter_rejects_invalid_values() {
    assert!(!ip_matches_filter("not-an-ip", "10.0.0.1"));
    assert!(!ip_matches_filter("10.0.0.1", "not-an-ip"));
    assert!(!ip_matches_filter("10.0.0.1", "10.0.0.0/99"));
}