use std::path::PathBuf;
use serde_json::json;
use vane_core::compile::{RawRuleFile, compile};
use vane_core::error::Error;
use vane_core::fetch::{FetchKind, FetchOutputModes, FetchPhase};
use vane_core::metadata::{
FetchMetadata, FetchMetadataProvider, MiddlewareMetadata, MiddlewareMetadataProvider,
};
use vane_core::middleware::MiddlewareKind;
use vane_core::preset::RuleEntry;
struct Providers;
fn validate_ok(_: &serde_json::Value) -> Result<(), Error> {
Ok(())
}
impl MiddlewareMetadataProvider for Providers {
fn get(&self, _name: &str) -> Option<MiddlewareMetadata> {
Some(MiddlewareMetadata {
kind: MiddlewareKind::L7Request,
stateless: true,
needs_body: false,
validate_args: validate_ok,
})
}
}
impl FetchMetadataProvider for Providers {
fn get(&self, kind: FetchKind) -> Option<FetchMetadata> {
Some(FetchMetadata {
kind,
phase: match kind {
FetchKind::L4Forward => FetchPhase::L4,
_ => FetchPhase::L7,
},
output_modes: match kind {
FetchKind::L4Forward => FetchOutputModes { response: false, tunnel: true },
FetchKind::WebSocketUpgrade => FetchOutputModes { response: true, tunnel: true },
_ => FetchOutputModes { response: true, tunnel: false },
},
validate_args: validate_ok,
})
}
}
fn rule_file(entries: Vec<RuleEntry>) -> RawRuleFile {
RawRuleFile { path: PathBuf::from("rules/zero_rtt.json"), order: 0, rules: entries }
}
fn parse_entry(raw: serde_json::Value) -> RuleEntry {
serde_json::from_value(raw).expect("parse rule entry")
}
fn compile_one(raw: serde_json::Value) -> Result<(), Error> {
let entry = parse_entry(raw);
compile(vec![rule_file(vec![entry])], &Providers, &Providers).map(|_| ())
}
#[test]
fn missing_allow_zero_rtt_on_tls_l7_rule_errors() {
let err = compile_one(json!({
"name": "api",
"listen": [":443"],
"terminate": { "type": "http_proxy", "upstream": "127.0.0.1:8080" },
"tls": {
"cert_file": "/tmp/cert.pem",
"key_file": "/tmp/key.pem",
"enable_zero_rtt": false,
},
}))
.expect_err("missing allow_zero_rtt on TLS-L7 rule must error");
let msg = err.to_string();
assert!(msg.contains("allow_zero_rtt"), "error names the field: {msg}");
assert!(msg.contains("api"), "error names the rule: {msg}");
}
#[test]
fn allow_zero_rtt_on_plaintext_rule_errors() {
let err = compile_one(json!({
"name": "api",
"listen": [":80"],
"terminate": { "type": "http_proxy", "upstream": "127.0.0.1:8080" },
"allow_zero_rtt": false,
}))
.expect_err("allow_zero_rtt on plaintext rule must error");
let msg = err.to_string();
assert!(msg.contains("allow_zero_rtt"), "error names the field: {msg}");
assert!(msg.contains("L7 rules"), "error explains the constraint: {msg}");
}
#[test]
fn allow_zero_rtt_on_l4_rule_errors() {
let err = compile_one(json!({
"name": "ssh",
"listen": ["tcp:2222"],
"terminate": { "type": "tcp_forward", "upstream": "10.0.0.5:22" },
"allow_zero_rtt": false,
}))
.expect_err("allow_zero_rtt on L4 rule must error");
let msg = err.to_string();
assert!(msg.contains("allow_zero_rtt"), "error names the field: {msg}");
}
#[test]
fn allow_zero_rtt_true_on_listener_with_enable_false_errors() {
let err = compile_one(json!({
"name": "api",
"listen": [":443"],
"match": { "http.method": { "equals": "GET" } },
"terminate": { "type": "http_proxy", "upstream": "127.0.0.1:8080" },
"allow_zero_rtt": true,
"tls": {
"cert_file": "/tmp/cert.pem",
"key_file": "/tmp/key.pem",
"enable_zero_rtt": false,
},
}))
.expect_err("allow_zero_rtt true with enable_zero_rtt false must error");
let msg = err.to_string();
assert!(msg.contains("allow_zero_rtt: true"), "error names the rule field: {msg}");
assert!(msg.contains("enable_zero_rtt: false"), "error names the listener field: {msg}");
assert!(msg.contains("api"), "error names the rule: {msg}");
}
#[test]
fn allow_zero_rtt_true_without_method_constraint_errors() {
let err = compile_one(json!({
"name": "api",
"listen": [":443"],
"terminate": { "type": "http_proxy", "upstream": "127.0.0.1:8080" },
"allow_zero_rtt": true,
"tls": {
"cert_file": "/tmp/cert.pem",
"key_file": "/tmp/key.pem",
"enable_zero_rtt": true,
},
}))
.expect_err("allow_zero_rtt true without method constraint must error");
let msg = err.to_string();
assert!(msg.contains("method constraint"), "error explains the constraint: {msg}");
assert!(msg.contains("GET / HEAD / OPTIONS"), "error names the idempotent set: {msg}");
}
#[test]
fn allow_zero_rtt_true_with_post_in_any_of_errors() {
let err = compile_one(json!({
"name": "api",
"listen": [":443"],
"match": {
"any_of": [
{ "http.method": { "equals": "GET" } },
{ "http.method": { "equals": "POST" } },
],
},
"terminate": { "type": "http_proxy", "upstream": "127.0.0.1:8080" },
"allow_zero_rtt": true,
"tls": {
"cert_file": "/tmp/cert.pem",
"key_file": "/tmp/key.pem",
"enable_zero_rtt": true,
},
}))
.expect_err("allow_zero_rtt true with POST in any_of must error");
let msg = err.to_string();
assert!(msg.contains("method constraint"), "error explains the constraint: {msg}");
}
#[test]
fn allow_zero_rtt_true_with_get_only_predicate_compiles() {
compile_one(json!({
"name": "api",
"listen": [":443"],
"match": { "http.method": { "equals": "GET" } },
"terminate": { "type": "http_proxy", "upstream": "127.0.0.1:8080" },
"allow_zero_rtt": true,
"tls": {
"cert_file": "/tmp/cert.pem",
"key_file": "/tmp/key.pem",
"enable_zero_rtt": true,
},
}))
.expect("GET-only predicate satisfies the idempotent gate");
}
#[test]
fn allow_zero_rtt_true_with_any_of_idempotent_only_compiles() {
compile_one(json!({
"name": "api",
"listen": [":443"],
"match": {
"any_of": [
{ "http.method": { "equals": "GET" } },
{ "http.method": { "equals": "HEAD" } },
{ "http.method": { "equals": "OPTIONS" } },
],
},
"terminate": { "type": "http_proxy", "upstream": "127.0.0.1:8080" },
"allow_zero_rtt": true,
"tls": {
"cert_file": "/tmp/cert.pem",
"key_file": "/tmp/key.pem",
"enable_zero_rtt": true,
},
}))
.expect("any_of of idempotent methods satisfies the gate");
}
#[test]
fn allow_zero_rtt_true_with_all_of_containing_get_method_compiles() {
compile_one(json!({
"name": "api",
"listen": [":443"],
"match": {
"all_of": [
{ "http.method": { "equals": "GET" } },
{ "http.uri.path": { "prefix": "/api" } },
],
},
"terminate": { "type": "http_proxy", "upstream": "127.0.0.1:8080" },
"allow_zero_rtt": true,
"tls": {
"cert_file": "/tmp/cert.pem",
"key_file": "/tmp/key.pem",
"enable_zero_rtt": true,
},
}))
.expect("all_of with GET conjunct satisfies the gate");
}
#[test]
fn rules_disagree_on_enable_zero_rtt_listener_aggregation_errors() {
let a = parse_entry(json!({
"name": "api-a",
"listen": [":443"],
"match": { "tls.sni": { "equals": "a.example.com" } },
"terminate": { "type": "http_proxy", "upstream": "127.0.0.1:8080" },
"allow_zero_rtt": false,
"tls": {
"sni": "a.example.com",
"cert_file": "/tmp/a.pem",
"key_file": "/tmp/a.key",
"enable_zero_rtt": true,
},
}));
let b = parse_entry(json!({
"name": "api-b",
"listen": [":443"],
"match": { "tls.sni": { "equals": "b.example.com" } },
"terminate": { "type": "http_proxy", "upstream": "127.0.0.1:8081" },
"allow_zero_rtt": false,
"tls": {
"sni": "b.example.com",
"cert_file": "/tmp/b.pem",
"key_file": "/tmp/b.key",
"enable_zero_rtt": false,
},
}));
let err = compile(vec![rule_file(vec![a, b])], &Providers, &Providers)
.expect_err("conflicting enable_zero_rtt must error");
let msg = err.to_string();
assert!(msg.contains("enable_zero_rtt"), "error names the field: {msg}");
assert!(
msg.contains("agree") || msg.contains("disagree"),
"error explains the aggregation requirement: {msg}",
);
}
#[test]
fn allow_zero_rtt_true_with_method_in_idempotent_list_compiles() {
compile_one(json!({
"name": "api",
"listen": [":443"],
"match": { "http.method": { "in": ["GET", "HEAD"] } },
"terminate": { "type": "http_proxy", "upstream": "127.0.0.1:8080" },
"allow_zero_rtt": true,
"tls": {
"cert_file": "/tmp/cert.pem",
"key_file": "/tmp/key.pem",
"enable_zero_rtt": true,
},
}))
.expect("method `in [GET, HEAD]` satisfies the gate");
}
#[test]
fn allow_zero_rtt_true_with_method_in_list_containing_post_errors() {
let err = compile_one(json!({
"name": "api",
"listen": [":443"],
"match": { "http.method": { "in": ["GET", "POST"] } },
"terminate": { "type": "http_proxy", "upstream": "127.0.0.1:8080" },
"allow_zero_rtt": true,
"tls": {
"cert_file": "/tmp/cert.pem",
"key_file": "/tmp/key.pem",
"enable_zero_rtt": true,
},
}))
.expect_err("method `in [GET, POST]` admits POST and must error");
let msg = err.to_string();
assert!(msg.contains("method constraint"), "error explains the constraint: {msg}");
}
#[test]
fn enable_zero_rtt_aggregates_into_listener_spec() {
let entry = parse_entry(json!({
"name": "api",
"listen": [":443"],
"match": { "http.method": { "equals": "GET" } },
"terminate": { "type": "http_proxy", "upstream": "127.0.0.1:8080" },
"allow_zero_rtt": true,
"tls": {
"cert_file": "/tmp/cert.pem",
"key_file": "/tmp/key.pem",
"enable_zero_rtt": true,
},
}));
let graph = compile(vec![rule_file(vec![entry])], &Providers, &Providers).expect("compile");
for spec in graph.meta.listener_tls.values() {
assert!(spec.enable_zero_rtt, "listener-level enable_zero_rtt must reflect the rule's setting");
}
}