use super::evaluate::evaluate_ratchet_with_options;
use crate::types::{RatchetConfig, RatchetGateResult, RuleLevel};
use serde_json::Value;
pub fn evaluate_ratchet_policy(
config: &RatchetConfig,
baseline: &Value,
current: &Value,
) -> RatchetGateResult {
let mut ratchet_results = Vec::with_capacity(config.rules.len());
for rule in &config.rules {
let result = evaluate_ratchet_with_options(
rule,
baseline,
current,
config.allow_missing_baseline,
config.allow_missing_current,
);
let failed_error = !result.passed && result.rule.level == RuleLevel::Error;
ratchet_results.push(result);
if config.fail_fast && failed_error {
break;
}
}
RatchetGateResult::from_results(ratchet_results)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{RatchetRule, RuleLevel};
use serde_json::json;
fn make_rule(
pointer: &str,
max_increase_pct: Option<f64>,
max_value: Option<f64>,
level: RuleLevel,
) -> RatchetRule {
RatchetRule {
pointer: pointer.to_string(),
max_increase_pct,
max_value,
level,
description: None,
}
}
fn make_config(rules: Vec<RatchetRule>, fail_fast: bool) -> RatchetConfig {
RatchetConfig {
rules,
fail_fast,
allow_missing_baseline: false,
allow_missing_current: false,
}
}
#[test]
fn empty_rules_passes_with_no_results() {
let config = make_config(vec![], false);
let result = evaluate_ratchet_policy(&config, &json!({}), &json!({}));
assert!(result.passed);
assert!(result.ratchet_results.is_empty());
assert_eq!(result.errors, 0);
assert_eq!(result.warnings, 0);
}
#[test]
fn all_rules_pass_when_within_thresholds() {
let baseline = json!({"a": 10.0, "b": 100.0});
let current = json!({"a": 10.0, "b": 105.0});
let config = make_config(
vec![
make_rule("/a", Some(10.0), None, RuleLevel::Error),
make_rule("/b", Some(10.0), None, RuleLevel::Error),
],
false,
);
let result = evaluate_ratchet_policy(&config, &baseline, ¤t);
assert!(result.passed);
assert_eq!(result.ratchet_results.len(), 2);
assert_eq!(result.errors, 0);
assert_eq!(result.warnings, 0);
}
#[test]
fn any_error_fails_overall_when_mixed_with_passes() {
let baseline = json!({"a": 10.0, "b": 100.0});
let current = json!({"a": 10.0, "b": 200.0}); let config = make_config(
vec![
make_rule("/a", Some(10.0), None, RuleLevel::Error),
make_rule("/b", Some(10.0), None, RuleLevel::Error),
],
false,
);
let result = evaluate_ratchet_policy(&config, &baseline, ¤t);
assert!(!result.passed);
assert_eq!(result.errors, 1);
assert_eq!(result.ratchet_results.len(), 2);
}
#[test]
fn warn_level_failures_do_not_fail_overall() {
let baseline = json!({"a": 10.0});
let current = json!({"a": 20.0});
let config = make_config(
vec![make_rule("/a", Some(10.0), None, RuleLevel::Warn)],
false,
);
let result = evaluate_ratchet_policy(&config, &baseline, ¤t);
assert!(result.passed); assert_eq!(result.warnings, 1);
assert_eq!(result.errors, 0);
}
#[test]
fn fail_fast_stops_after_first_error_level_failure() {
let baseline = json!({"a": 10.0, "b": 10.0, "c": 10.0});
let current = json!({"a": 20.0, "b": 20.0, "c": 20.0});
let config = make_config(
vec![
make_rule("/a", Some(10.0), None, RuleLevel::Error),
make_rule("/b", Some(10.0), None, RuleLevel::Error),
make_rule("/c", Some(10.0), None, RuleLevel::Error),
],
true,
);
let result = evaluate_ratchet_policy(&config, &baseline, ¤t);
assert!(!result.passed);
assert_eq!(
result.ratchet_results.len(),
1,
"fail_fast should stop after first error"
);
assert_eq!(result.errors, 1);
}
#[test]
fn fail_fast_does_not_stop_on_warn_failure() {
let baseline = json!({"a": 10.0, "b": 10.0});
let current = json!({"a": 20.0, "b": 20.0});
let config = make_config(
vec![
make_rule("/a", Some(10.0), None, RuleLevel::Warn), make_rule("/b", Some(10.0), None, RuleLevel::Error), ],
true,
);
let result = evaluate_ratchet_policy(&config, &baseline, ¤t);
assert!(!result.passed);
assert_eq!(
result.ratchet_results.len(),
2,
"fail_fast must continue past warn-level failures"
);
assert_eq!(result.warnings, 1);
assert_eq!(result.errors, 1);
}
#[test]
fn fail_fast_does_not_stop_on_pass() {
let baseline = json!({"a": 10.0, "b": 10.0});
let current = json!({"a": 10.0, "b": 20.0}); let config = make_config(
vec![
make_rule("/a", Some(10.0), None, RuleLevel::Error),
make_rule("/b", Some(10.0), None, RuleLevel::Error),
],
true,
);
let result = evaluate_ratchet_policy(&config, &baseline, ¤t);
assert!(!result.passed);
assert_eq!(
result.ratchet_results.len(),
2,
"fail_fast must not stop after a passing rule"
);
}
#[test]
fn allow_missing_baseline_is_forwarded_to_evaluation() {
let baseline = json!({}); let current = json!({"value": 5.0});
let config = RatchetConfig {
rules: vec![make_rule("/value", Some(10.0), None, RuleLevel::Error)],
fail_fast: false,
allow_missing_baseline: true,
allow_missing_current: false,
};
let result = evaluate_ratchet_policy(&config, &baseline, ¤t);
assert!(result.passed);
assert_eq!(result.errors, 0);
}
#[test]
fn allow_missing_current_is_forwarded_to_evaluation() {
let baseline = json!({"value": 5.0});
let current = json!({}); let config = RatchetConfig {
rules: vec![make_rule("/value", Some(10.0), None, RuleLevel::Error)],
fail_fast: false,
allow_missing_baseline: false,
allow_missing_current: true,
};
let result = evaluate_ratchet_policy(&config, &baseline, ¤t);
assert!(result.passed);
assert_eq!(result.errors, 0);
}
#[test]
fn results_preserve_rule_order() {
let baseline = json!({"a": 10.0, "b": 10.0, "c": 10.0});
let current = json!({"a": 10.0, "b": 10.0, "c": 10.0});
let config = make_config(
vec![
make_rule("/a", Some(10.0), None, RuleLevel::Error),
make_rule("/b", Some(10.0), None, RuleLevel::Error),
make_rule("/c", Some(10.0), None, RuleLevel::Error),
],
false,
);
let result = evaluate_ratchet_policy(&config, &baseline, ¤t);
assert_eq!(result.ratchet_results.len(), 3);
assert_eq!(result.ratchet_results[0].rule.pointer, "/a");
assert_eq!(result.ratchet_results[1].rule.pointer, "/b");
assert_eq!(result.ratchet_results[2].rule.pointer, "/c");
}
}