use lemma::DateTimeValue;
use lemma::Engine;
use rust_decimal::Decimal;
use std::collections::HashMap;
use std::str::FromStr;
fn decimal_lit(d: &str) -> Decimal {
Decimal::from_str(d).unwrap()
}
fn load_examples() -> Engine {
let mut engine = Engine::new();
let examples = [
"../cli/tests/integrations/examples/01_simple_data.lemma",
"../cli/tests/integrations/examples/02_rules_and_unless.lemma",
"../cli/tests/integrations/examples/03_spec_references.lemma",
"../cli/tests/integrations/examples/04_unit_conversions.lemma",
"../cli/tests/integrations/examples/05_date_handling.lemma",
"../cli/tests/integrations/examples/06_tax_calculation.lemma",
"../cli/tests/integrations/examples/07_shipping_policy.lemma",
"../cli/tests/integrations/examples/08_rule_references.lemma",
"../cli/tests/integrations/examples/09_stress_test.lemma",
"../cli/tests/integrations/examples/10_compensation_policy.lemma",
"../cli/tests/integrations/examples/11_spec_composition.lemma",
];
for path in examples {
let content = std::fs::read_to_string(path)
.unwrap_or_else(|e| panic!("Failed to read {}: {}", path, e));
engine
.load(
&content,
lemma::SourceType::Path(std::sync::Arc::new(std::path::PathBuf::from(path))),
)
.unwrap_or_else(|errs| {
panic!(
"Failed to parse {}: {}",
path,
errs.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join("; ")
)
});
}
engine
}
#[test]
fn test_02_rules_and_unless() {
let engine = load_examples();
let now = DateTimeValue::now();
let mut data = std::collections::HashMap::new();
data.insert("base_price".to_string(), "100.00".to_string());
data.insert("quantity".to_string(), "10".to_string());
data.insert("is_premium".to_string(), "true".to_string());
data.insert("customer_age".to_string(), "17".to_string());
let response = engine
.run(None, "rules_and_unless", Some(&now), data, true, None)
.expect("Evaluation failed");
assert_eq!(response.spec_name, "rules_and_unless");
let final_total = response.results.get("final_total").unwrap();
assert!(!final_total.vetoed);
let lit = final_total
.explanation
.as_ref()
.expect("explanation")
.result
.value()
.expect("value");
match &lit.value {
lemma::ValueKind::Number(n) => assert_eq!(
lemma::ValueKind::Number(n.clone())
.as_decimal_magnitude()
.unwrap(),
decimal_lit("800")
),
other => panic!("Expected Number for final_total, got {:?}", other),
}
let age_validation = response.results.get("age_validation").unwrap();
assert!(age_validation.vetoed);
assert_eq!(
age_validation.veto_reason.as_deref(),
Some("Customer must be 18 or older")
);
}
#[test]
fn test_03_spec_references() {
let engine = load_examples();
let now = DateTimeValue::now();
let response = engine
.run(
None,
"specific_employee",
Some(&now),
HashMap::new(),
true,
None,
)
.expect("Evaluation failed");
assert_eq!(response.spec_name, "specific_employee");
let salary_with_bonus = response.results.get("salary_with_bonus").unwrap();
assert!(!salary_with_bonus.vetoed);
let lit = salary_with_bonus
.explanation
.as_ref()
.expect("explanation")
.result
.value()
.expect("value");
match &lit.value {
lemma::ValueKind::Number(n) => assert_eq!(
lemma::ValueKind::Number(n.clone())
.as_decimal_magnitude()
.unwrap(),
decimal_lit("99000")
),
other => panic!("Expected Number for salary_with_bonus, got {:?}", other),
}
let employee_summary = response.results.get("employee_summary").unwrap();
assert_eq!(employee_summary.text.as_deref(), Some("Alice Smith"));
}
#[test]
fn test_04_unit_conversions() {
let engine = load_examples();
let now = DateTimeValue::now();
let response = engine
.run(
None,
"unit_conversions",
Some(&now),
HashMap::new(),
true,
None,
)
.expect("Evaluation failed");
assert_eq!(response.spec_name, "unit_conversions");
let duration_hours = response.results.get("duration_hours").unwrap();
assert!(!duration_hours.vetoed);
assert_eq!(
duration_hours
.quantity
.as_ref()
.and_then(|m| m.get("hours"))
.map(String::as_str),
Some("1.5")
);
let duration_seconds = response.results.get("duration_seconds").unwrap();
assert!(!duration_seconds.vetoed);
assert_eq!(
duration_seconds
.quantity
.as_ref()
.and_then(|m| m.get("seconds"))
.map(String::as_str),
Some("5400")
);
let is_quick_processing = response.results.get("is_quick_processing").unwrap();
assert_eq!(
is_quick_processing.display.clone().expect("display"),
lemma::LiteralValue::from_bool(true).to_string(),
);
}
#[test]
fn test_05_date_handling() {
let engine = load_examples();
let now = DateTimeValue::now();
let mut data = std::collections::HashMap::new();
data.insert("current_date".to_string(), "2024-06-15".to_string());
let response = engine
.run(None, "date_handling", Some(&now), data, true, None)
.expect("Evaluation failed");
assert_eq!(response.spec_name, "date_handling");
let probation_end = response.results.get("probation_end_date").unwrap();
let date = probation_end.date.as_ref().expect("date");
assert_eq!(date.year, 2024);
assert_eq!(date.month, 5);
assert_eq!(date.day, 30);
let is_probation_complete = response.results.get("is_probation_complete").unwrap();
assert_eq!(
is_probation_complete.display.clone().expect("display"),
lemma::LiteralValue::from_bool(true).to_string(),
);
}
#[test]
fn test_08_rule_references() {
let engine = load_examples();
let now = DateTimeValue::now();
let response = engine
.run(
None,
"rule_references",
Some(&now),
HashMap::new(),
true,
None,
)
.expect("Evaluation failed");
assert_eq!(response.spec_name, "rule_references");
assert_eq!(
response.results.get("can_drive_legally").unwrap().boolean,
Some(true)
);
let driving_status = response.results.get("driving_status").unwrap();
assert_eq!(driving_status.text.as_deref(), Some("Can drive legally"));
let response = engine
.run(
None,
"eligibility_check",
Some(&now),
HashMap::new(),
true,
None,
)
.expect("Evaluation failed");
assert_eq!(response.spec_name, "eligibility_check");
let can_travel = response.results.get("can_travel_internationally").unwrap();
assert!(can_travel.vetoed);
assert_eq!(
can_travel.veto_reason.as_deref(),
Some("Valid travel documents required")
);
let eligibility_message = response.results.get("eligibility_message").unwrap();
assert!(eligibility_message.vetoed);
assert_eq!(
eligibility_message.veto_reason.as_deref(),
Some("Valid travel documents required")
);
}