1mod evaluate;
31mod pointer;
32mod ratchet;
33mod types;
34
35pub use evaluate::evaluate_policy;
36pub use pointer::resolve_pointer;
37pub use ratchet::evaluate_ratchet_policy;
38pub use types::{
39 GateError, GateResult, PolicyConfig, PolicyRule, RatchetConfig, RatchetGateResult,
40 RatchetResult, RatchetRule, RuleLevel, RuleOperator, RuleResult,
41};
42
43#[cfg(test)]
44mod tests {
45 use super::*;
46 use serde_json::json;
47
48 #[test]
50 fn resolve_pointer_simple_path() {
51 let doc = json!({"a": {"b": 42}});
52 assert_eq!(resolve_pointer(&doc, "/a/b"), Some(&json!(42)));
53 }
54
55 #[test]
56 fn resolve_pointer_missing_path() {
57 let doc = json!({"a": 1});
58 assert_eq!(resolve_pointer(&doc, "/b"), None);
59 }
60
61 #[test]
62 fn resolve_pointer_empty_is_whole_doc() {
63 let doc = json!({"x": 1});
64 assert_eq!(resolve_pointer(&doc, ""), Some(&doc));
65 }
66
67 #[test]
69 fn evaluate_policy_all_pass() {
70 let receipt = json!({"tokens": 100, "files": 5});
71 let policy = PolicyConfig {
72 rules: vec![PolicyRule {
73 name: "max_tokens".into(),
74 pointer: "/tokens".into(),
75 op: RuleOperator::Lte,
76 value: Some(json!(1000)),
77 values: None,
78 negate: false,
79 level: RuleLevel::Error,
80 message: None,
81 }],
82 fail_fast: false,
83 allow_missing: false,
84 };
85
86 let result = evaluate_policy(&receipt, &policy);
87 assert!(result.passed);
88 assert_eq!(result.errors, 0);
89 assert_eq!(result.warnings, 0);
90 }
91
92 #[test]
93 fn evaluate_policy_with_failure() {
94 let receipt = json!({"tokens": 2000});
95 let policy = PolicyConfig {
96 rules: vec![PolicyRule {
97 name: "max_tokens".into(),
98 pointer: "/tokens".into(),
99 op: RuleOperator::Lte,
100 value: Some(json!(1000)),
101 values: None,
102 negate: false,
103 level: RuleLevel::Error,
104 message: Some("Too many tokens".into()),
105 }],
106 fail_fast: false,
107 allow_missing: false,
108 };
109
110 let result = evaluate_policy(&receipt, &policy);
111 assert!(!result.passed);
112 assert_eq!(result.errors, 1);
113 }
114
115 #[test]
116 fn evaluate_policy_warn_does_not_fail() {
117 let receipt = json!({"tokens": 2000});
118 let policy = PolicyConfig {
119 rules: vec![PolicyRule {
120 name: "token_warning".into(),
121 pointer: "/tokens".into(),
122 op: RuleOperator::Lte,
123 value: Some(json!(1000)),
124 values: None,
125 negate: false,
126 level: RuleLevel::Warn,
127 message: None,
128 }],
129 fail_fast: false,
130 allow_missing: false,
131 };
132
133 let result = evaluate_policy(&receipt, &policy);
134 assert!(result.passed); assert_eq!(result.warnings, 1);
136 }
137
138 #[test]
140 fn ratchet_policy_pass() {
141 let baseline = json!({"complexity": 10.0});
142 let current = json!({"complexity": 10.5}); let config = RatchetConfig {
144 rules: vec![RatchetRule {
145 pointer: "/complexity".into(),
146 max_increase_pct: Some(10.0),
147 max_value: None,
148 level: RuleLevel::Error,
149 description: None,
150 }],
151 fail_fast: false,
152 allow_missing_baseline: false,
153 allow_missing_current: false,
154 };
155
156 let result = evaluate_ratchet_policy(&config, &baseline, ¤t);
157 assert!(result.passed);
158 assert_eq!(result.errors, 0);
159 }
160
161 #[test]
162 fn ratchet_policy_fail_regression() {
163 let baseline = json!({"complexity": 10.0});
164 let current = json!({"complexity": 15.0}); let config = RatchetConfig {
166 rules: vec![RatchetRule {
167 pointer: "/complexity".into(),
168 max_increase_pct: Some(10.0),
169 max_value: None,
170 level: RuleLevel::Error,
171 description: None,
172 }],
173 fail_fast: false,
174 allow_missing_baseline: false,
175 allow_missing_current: false,
176 };
177
178 let result = evaluate_ratchet_policy(&config, &baseline, ¤t);
179 assert!(!result.passed);
180 assert_eq!(result.errors, 1);
181 }
182
183 #[test]
185 fn policy_config_from_toml() {
186 let toml = r#"
187fail_fast = false
188allow_missing = true
189
190[[rules]]
191name = "check_tokens"
192pointer = "/tokens"
193op = "lte"
194value = 500000
195"#;
196 let policy = PolicyConfig::from_toml(toml).unwrap();
197 assert!(!policy.fail_fast);
198 assert!(policy.allow_missing);
199 assert_eq!(policy.rules.len(), 1);
200 assert_eq!(policy.rules[0].name, "check_tokens");
201 }
202
203 #[test]
204 fn policy_config_default_is_empty() {
205 let policy = PolicyConfig::default();
206 assert!(policy.rules.is_empty());
207 assert!(!policy.fail_fast);
208 assert!(!policy.allow_missing);
209 }
210
211 #[test]
212 fn ratchet_config_from_toml() {
213 let toml = r#"
214fail_fast = true
215allow_missing_baseline = true
216
217[[rules]]
218pointer = "/complexity/avg"
219max_increase_pct = 5.0
220level = "error"
221"#;
222 let config = RatchetConfig::from_toml(toml).unwrap();
223 assert!(config.fail_fast);
224 assert!(config.allow_missing_baseline);
225 assert_eq!(config.rules.len(), 1);
226 }
227
228 #[test]
230 fn gate_result_from_empty_results() {
231 let result = GateResult::from_results(vec![]);
232 assert!(result.passed);
233 assert_eq!(result.errors, 0);
234 assert_eq!(result.warnings, 0);
235 }
236
237 #[test]
238 fn ratchet_gate_result_from_empty_results() {
239 let result = RatchetGateResult::from_results(vec![]);
240 assert!(result.passed);
241 assert_eq!(result.errors, 0);
242 assert_eq!(result.warnings, 0);
243 }
244
245 #[test]
247 fn rule_operator_display() {
248 assert_eq!(RuleOperator::Gt.to_string(), ">");
249 assert_eq!(RuleOperator::Lte.to_string(), "<=");
250 assert_eq!(RuleOperator::Eq.to_string(), "==");
251 assert_eq!(RuleOperator::In.to_string(), "in");
252 assert_eq!(RuleOperator::Exists.to_string(), "exists");
253 }
254
255 #[test]
257 fn rule_operator_default_is_eq() {
258 assert_eq!(RuleOperator::default(), RuleOperator::Eq);
259 }
260
261 #[test]
262 fn rule_level_default_is_error() {
263 assert_eq!(RuleLevel::default(), RuleLevel::Error);
264 }
265}