mod evaluate;
mod numeric;
mod pointer;
mod ratchet;
mod types;
pub use evaluate::evaluate_policy;
pub use pointer::resolve_pointer;
pub use ratchet::evaluate_ratchet_policy;
pub use types::{
GateError, GateResult, PolicyConfig, PolicyRule, RatchetConfig, RatchetGateResult,
RatchetResult, RatchetRule, RuleLevel, RuleOperator, RuleResult,
};
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn resolve_pointer_simple_path() {
let doc = json!({"a": {"b": 42}});
assert_eq!(resolve_pointer(&doc, "/a/b"), Some(&json!(42)));
}
#[test]
fn resolve_pointer_missing_path() {
let doc = json!({"a": 1});
assert_eq!(resolve_pointer(&doc, "/b"), None);
}
#[test]
fn resolve_pointer_empty_is_whole_doc() {
let doc = json!({"x": 1});
assert_eq!(resolve_pointer(&doc, ""), Some(&doc));
}
#[test]
fn evaluate_policy_all_pass() {
let receipt = json!({"tokens": 100, "files": 5});
let policy = PolicyConfig {
rules: vec![PolicyRule {
name: "max_tokens".into(),
pointer: "/tokens".into(),
op: RuleOperator::Lte,
value: Some(json!(1000)),
values: None,
negate: false,
level: RuleLevel::Error,
message: None,
}],
fail_fast: false,
allow_missing: false,
};
let result = evaluate_policy(&receipt, &policy);
assert!(result.passed);
assert_eq!(result.errors, 0);
assert_eq!(result.warnings, 0);
}
#[test]
fn evaluate_policy_with_failure() {
let receipt = json!({"tokens": 2000});
let policy = PolicyConfig {
rules: vec![PolicyRule {
name: "max_tokens".into(),
pointer: "/tokens".into(),
op: RuleOperator::Lte,
value: Some(json!(1000)),
values: None,
negate: false,
level: RuleLevel::Error,
message: Some("Too many tokens".into()),
}],
fail_fast: false,
allow_missing: false,
};
let result = evaluate_policy(&receipt, &policy);
assert!(!result.passed);
assert_eq!(result.errors, 1);
}
#[test]
fn evaluate_policy_warn_does_not_fail() {
let receipt = json!({"tokens": 2000});
let policy = PolicyConfig {
rules: vec![PolicyRule {
name: "token_warning".into(),
pointer: "/tokens".into(),
op: RuleOperator::Lte,
value: Some(json!(1000)),
values: None,
negate: false,
level: RuleLevel::Warn,
message: None,
}],
fail_fast: false,
allow_missing: false,
};
let result = evaluate_policy(&receipt, &policy);
assert!(result.passed); assert_eq!(result.warnings, 1);
}
#[test]
fn ratchet_policy_pass() {
let baseline = json!({"complexity": 10.0});
let current = json!({"complexity": 10.5}); let config = RatchetConfig {
rules: vec![RatchetRule {
pointer: "/complexity".into(),
max_increase_pct: Some(10.0),
max_value: None,
level: RuleLevel::Error,
description: None,
}],
fail_fast: false,
allow_missing_baseline: false,
allow_missing_current: false,
};
let result = evaluate_ratchet_policy(&config, &baseline, ¤t);
assert!(result.passed);
assert_eq!(result.errors, 0);
}
#[test]
fn ratchet_policy_fail_regression() {
let baseline = json!({"complexity": 10.0});
let current = json!({"complexity": 15.0}); let config = RatchetConfig {
rules: vec![RatchetRule {
pointer: "/complexity".into(),
max_increase_pct: Some(10.0),
max_value: None,
level: RuleLevel::Error,
description: None,
}],
fail_fast: false,
allow_missing_baseline: false,
allow_missing_current: false,
};
let result = evaluate_ratchet_policy(&config, &baseline, ¤t);
assert!(!result.passed);
assert_eq!(result.errors, 1);
}
#[test]
fn policy_config_from_toml() {
let toml = r#"
fail_fast = false
allow_missing = true
[[rules]]
name = "check_tokens"
pointer = "/tokens"
op = "lte"
value = 500000
"#;
let policy = PolicyConfig::from_toml(toml).unwrap();
assert!(!policy.fail_fast);
assert!(policy.allow_missing);
assert_eq!(policy.rules.len(), 1);
assert_eq!(policy.rules[0].name, "check_tokens");
}
#[test]
fn policy_config_default_is_empty() {
let policy = PolicyConfig::default();
assert!(policy.rules.is_empty());
assert!(!policy.fail_fast);
assert!(!policy.allow_missing);
}
#[test]
fn ratchet_config_from_toml() {
let toml = r#"
fail_fast = true
allow_missing_baseline = true
[[rules]]
pointer = "/complexity/avg"
max_increase_pct = 5.0
level = "error"
"#;
let config = RatchetConfig::from_toml(toml).unwrap();
assert!(config.fail_fast);
assert!(config.allow_missing_baseline);
assert_eq!(config.rules.len(), 1);
}
#[test]
fn gate_result_from_empty_results() {
let result = GateResult::from_results(vec![]);
assert!(result.passed);
assert_eq!(result.errors, 0);
assert_eq!(result.warnings, 0);
}
#[test]
fn ratchet_gate_result_from_empty_results() {
let result = RatchetGateResult::from_results(vec![]);
assert!(result.passed);
assert_eq!(result.errors, 0);
assert_eq!(result.warnings, 0);
}
#[test]
fn rule_operator_display() {
assert_eq!(RuleOperator::Gt.to_string(), ">");
assert_eq!(RuleOperator::Lte.to_string(), "<=");
assert_eq!(RuleOperator::Eq.to_string(), "==");
assert_eq!(RuleOperator::In.to_string(), "in");
assert_eq!(RuleOperator::Exists.to_string(), "exists");
}
#[test]
fn rule_operator_default_is_eq() {
assert_eq!(RuleOperator::default(), RuleOperator::Eq);
}
#[test]
fn rule_level_default_is_error() {
assert_eq!(RuleLevel::default(), RuleLevel::Error);
}
}