use lemma::{DataValueInput, DateTimeValue, Engine};
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
fn load_order_pipeline() -> (Engine, HashMap<String, String>) {
let mut engine = Engine::new();
engine
.load(
include_str!("../benches/specs/order_pipeline.lemma"),
lemma::SourceType::Path(Arc::new(PathBuf::from(
"engine/benches/specs/order_pipeline.lemma",
))),
)
.expect("load order_pipeline");
let json: serde_json::Value =
serde_json::from_str(include_str!("../benches/specs/order_pipeline.inputs.json"))
.expect("parse inputs");
let data = json
.as_object()
.expect("object inputs")
.iter()
.map(|(k, v)| (k.clone(), v.as_str().expect("string input").to_string()))
.collect();
(engine, data)
}
fn explanation_tree_has_rule_node(value: &serde_json::Value, rule_name: &str) -> bool {
match value {
serde_json::Value::Object(obj) => {
if obj.get("type").and_then(|t| t.as_str()) == Some("rule")
&& obj.get("rule").and_then(|r| r.as_str()) == Some(rule_name)
{
return true;
}
obj.values()
.any(|v| explanation_tree_has_rule_node(v, rule_name))
}
serde_json::Value::Array(arr) => arr
.iter()
.any(|v| explanation_tree_has_rule_node(v, rule_name)),
_ => false,
}
}
#[test]
fn targeted_eval_one_rule_explain_false() {
let (engine, data) = load_order_pipeline();
let now = DateTimeValue::now();
let response = engine
.run(
None,
"bench_order_pipeline",
Some(&now),
data,
false,
Some(&["grand_total".to_string()]),
)
.expect("run");
assert_eq!(response.results.len(), 1);
let grand_total = response.results.get("grand_total").expect("grand_total");
assert!(!grand_total.vetoed);
assert!(grand_total.display.is_some());
assert!(!response.results.contains_key("is_high_value"));
}
#[test]
fn explain_true_subset_full_embed() {
let (engine, data) = load_order_pipeline();
let now = DateTimeValue::now();
let response = engine
.run(
None,
"bench_order_pipeline",
Some(&now),
data,
true,
Some(&["grand_total".to_string()]),
)
.expect("run");
assert_eq!(response.results.len(), 1);
let grand_total = response.results.get("grand_total").expect("grand_total");
let explanation = grand_total
.explanation
.as_ref()
.expect("grand_total explanation");
let json: serde_json::Value = serde_json::to_value(explanation).expect("serialize");
assert!(
explanation_tree_has_rule_node(&json, "pre_tax_total"),
"expected embedded rule node for pre_tax_total, got: {json}"
);
}
#[test]
fn empty_rules_errors() {
let (engine, data) = load_order_pipeline();
let now = DateTimeValue::now();
let plan = engine
.get_plan(None, "bench_order_pipeline", Some(&now))
.expect("plan");
let data_values: HashMap<String, DataValueInput> = data
.into_iter()
.map(|(k, v)| (k, DataValueInput::convenience(v)))
.collect();
let err = engine
.run_plan(plan, Some(&now), data_values, false, Some(&[]))
.expect_err("empty rules");
assert!(err.to_string().contains("at least one rule required"));
}
#[test]
fn unknown_rule_errors() {
let (engine, data) = load_order_pipeline();
let now = DateTimeValue::now();
let err = engine
.run(
None,
"bench_order_pipeline",
Some(&now),
data,
false,
Some(&["not_a_real_rule".to_string()]),
)
.expect_err("unknown rule");
assert!(err.to_string().contains("not_a_real_rule"));
}
#[test]
fn all_local_rules_explain_false() {
let (engine, data) = load_order_pipeline();
let now = DateTimeValue::now();
let plan = engine
.get_plan(None, "bench_order_pipeline", Some(&now))
.expect("plan");
let rule_count = plan.local_rule_names().len();
let data_values: HashMap<String, DataValueInput> = data
.into_iter()
.map(|(k, v)| (k, DataValueInput::convenience(v)))
.collect();
let response = engine
.run_plan(plan, Some(&now), data_values, false, None)
.expect("run");
assert_eq!(response.results.len(), rule_count);
assert!(response.results.contains_key("grand_total"));
assert!(response.results.contains_key("is_high_value"));
}
#[test]
fn ratio_canonical_matches_materialized() {
let mut engine = Engine::new();
engine
.load(
r#"
spec ratio_canonical
rule rate: 23 percent
"#,
lemma::SourceType::Volatile,
)
.expect("load");
let now = DateTimeValue::now();
let response = engine
.run(
None,
"ratio_canonical",
Some(&now),
HashMap::new(),
false,
None,
)
.expect("run");
let rate = response.results.get("rate").expect("rate key canonical");
assert_eq!(rate.rule.name, "rate");
assert_eq!(rate.display.as_deref(), Some("23%"));
}