use lemma::parsing::ast::DateTimeValue;
use lemma::Engine;
use rust_decimal::Decimal;
use std::collections::HashMap;
use std::str::FromStr;
#[test]
fn test_employee_contract_comprehensive() {
let mut engine = Engine::new();
let base_contract = r#"
spec base_contract
data min_salary: 30000
data max_salary: 200000
data standard_vacation_days: 20 days
data probation_period: 90 days
data min_age: 18 years
"#;
let employment_terms = r#"
spec employment_terms
with base: base_contract
data salary: 75000
data bonus_percentage: 10%
data start_date: 2024-01-15
data vacation_days: 20 days
data employee_age: 28 years
rule total_compensation: salary + (salary * bonus_percentage)
rule is_salary_valid: salary >= base.min_salary and salary <= base.max_salary
rule vacation_days_ok: vacation_days >= base.standard_vacation_days
rule is_adult: employee_age >= base.min_age
rule probation_end_date: start_date + base.probation_period
rule contract_valid: is_salary_valid and vacation_days_ok and is_adult
unless not is_adult then veto "Employee must be 18 or older"
"#;
engine
.load(base_contract, lemma::SourceType::Labeled("test.lemma"))
.unwrap();
engine
.load(employment_terms, lemma::SourceType::Labeled("test.lemma"))
.unwrap();
let now = DateTimeValue::now();
let response = engine
.run("employment_terms", Some(&now), HashMap::new(), false)
.unwrap();
let total_comp = response
.results
.values()
.find(|r| r.rule.name == "total_compensation")
.unwrap();
match &total_comp.result {
lemma::OperationResult::Value(lit) => match &lit.value {
lemma::ValueKind::Number(n) => assert_eq!(*n, Decimal::from_str("82500").unwrap()),
other => panic!("Expected Number for total_compensation, got {:?}", other),
},
other => panic!("Expected Value for total_compensation, got {:?}", other),
}
let contract_valid = response
.results
.values()
.find(|r| r.rule.name == "contract_valid")
.unwrap();
assert_eq!(
contract_valid.result,
lemma::OperationResult::Value(Box::new(lemma::LiteralValue::from_bool(true)))
);
let _ = engine.remove("employment_terms", Some(&now));
let _ = engine.remove("base_contract", Some(&now));
}
#[test]
fn test_tax_calculation_with_percentages() {
let mut engine = Engine::new();
let tax_spec = r#"
spec tax_calculation
data income: 80000
data deductions: 10000
data tax_rate_low: 10%
data tax_rate_mid: 20%
data tax_rate_high: 30%
data bracket_low: 40000
data bracket_mid: 80000
rule taxable_income: income - deductions
rule in_low_bracket: taxable_income <= bracket_low
rule in_mid_bracket: taxable_income > bracket_low and taxable_income <= bracket_mid
rule in_high_bracket: taxable_income > bracket_mid
rule tax_rate: tax_rate_low
unless in_mid_bracket then tax_rate_mid
unless in_high_bracket then tax_rate_high
rule tax_amount: taxable_income * tax_rate
rule net_income: income - tax_amount
rule effective_rate: (tax_amount / income) * 100%
"#;
engine
.load(tax_spec, lemma::SourceType::Labeled("test.lemma"))
.unwrap();
let now = DateTimeValue::now();
let response = engine
.run("tax_calculation", Some(&now), HashMap::new(), false)
.unwrap();
let taxable = response
.results
.values()
.find(|r| r.rule.name == "taxable_income")
.unwrap();
match &taxable.result {
lemma::OperationResult::Value(lit) => match &lit.value {
lemma::ValueKind::Number(n) => assert_eq!(*n, Decimal::from_str("70000").unwrap()),
other => panic!("Expected Number for taxable_income, got {:?}", other),
},
other => panic!("Expected Value for taxable_income, got {:?}", other),
}
let in_mid = response
.results
.values()
.find(|r| r.rule.name == "in_mid_bracket")
.unwrap();
assert_eq!(
in_mid.result,
lemma::OperationResult::Value(Box::new(lemma::LiteralValue::from_bool(true)))
);
let tax_rate = response
.results
.values()
.find(|r| r.rule.name == "tax_rate")
.unwrap();
assert_eq!(
tax_rate.result,
lemma::OperationResult::Value(Box::new(lemma::LiteralValue::ratio(
Decimal::from_str("0.2").unwrap(),
Some("percent".to_string())
)))
);
let _ = engine.remove("tax_calculation", Some(&now));
}
#[test]
fn test_cli_data_values_integration() {
let mut engine = Engine::new();
let config_spec = r#"
spec dynamic_config
data threshold: number
data multiplier: number
data base_value: 100
rule calculated_value: base_value * multiplier
rule exceeds_threshold: calculated_value > threshold
rule status: "LOW"
unless exceeds_threshold then "HIGH"
"#;
engine
.load(config_spec, lemma::SourceType::Labeled("test.lemma"))
.unwrap();
let mut data = std::collections::HashMap::new();
data.insert("threshold".to_string(), "500".to_string());
data.insert("multiplier".to_string(), "2".to_string());
let now = DateTimeValue::now();
let response = engine
.run("dynamic_config", Some(&now), data, false)
.unwrap();
let calculated = response
.results
.values()
.find(|r| r.rule.name == "calculated_value")
.unwrap();
assert_eq!(calculated.result.value().unwrap().to_string(), "200");
let status = response
.results
.values()
.find(|r| r.rule.name == "status")
.unwrap();
assert_eq!(status.result.value().unwrap().to_string(), "LOW");
let mut data2 = std::collections::HashMap::new();
data2.insert("threshold".to_string(), "150".to_string());
data2.insert("multiplier".to_string(), "2".to_string());
let response2 = engine
.run("dynamic_config", Some(&now), data2, false)
.unwrap();
let status2 = response2
.results
.values()
.find(|r| r.rule.name == "status")
.unwrap();
assert_eq!(status2.result.value().unwrap().to_string(), "HIGH");
let _ = engine.remove("dynamic_config", Some(&now));
}
#[test]
fn test_date_arithmetic_comprehensive() {
let mut engine = Engine::new();
let timeline_spec = r#"
spec project_timeline
data project_start: 2024-01-15
data phase1_duration: 30 days
data phase2_duration: 45 days
data phase3_duration: 60 days
data today: 2024-02-15
rule phase1_end: project_start + phase1_duration
rule phase2_end: phase1_end + phase2_duration
rule phase3_end: phase2_end + phase3_duration
rule project_duration: phase1_duration + phase2_duration + phase3_duration
rule elapsed_time: today - project_start
rule days_remaining: phase3_end - today
rule is_phase1_complete: today > phase1_end
rule is_phase2_complete: today > phase2_end
rule is_on_schedule: elapsed_time <= phase1_duration + phase2_duration
"#;
engine
.load(timeline_spec, lemma::SourceType::Labeled("test.lemma"))
.unwrap();
let now = DateTimeValue::now();
let response = engine
.run("project_timeline", Some(&now), HashMap::new(), false)
.unwrap();
let phase1_complete = response
.results
.values()
.find(|r| r.rule.name == "is_phase1_complete")
.unwrap();
assert_eq!(phase1_complete.result.value().unwrap().to_string(), "true");
let phase2_complete = response
.results
.values()
.find(|r| r.rule.name == "is_phase2_complete")
.unwrap();
assert_eq!(
phase2_complete.result,
lemma::OperationResult::Value(Box::new(lemma::LiteralValue::from_bool(false)))
);
let _ = engine.remove("project_timeline", Some(&now));
}
#[test]
fn test_spec_ref_field_access_with_units() {
let mut engine = Engine::new();
let base_spec = r#"
spec base
data min_salary: 30000
data max_salary: 200000
"#;
let child_spec = r#"
spec child
with base_contract: base
data salary: 75000
rule is_valid: salary >= base_contract.min_salary and salary <= base_contract.max_salary
"#;
engine
.load(base_spec, lemma::SourceType::Labeled("test.lemma"))
.unwrap();
engine
.load(child_spec, lemma::SourceType::Labeled("test.lemma"))
.unwrap();
let now = DateTimeValue::now();
let response = engine
.run("child", Some(&now), HashMap::new(), false)
.unwrap();
let is_valid = response
.results
.values()
.find(|r| r.rule.name == "is_valid")
.unwrap();
assert_eq!(
is_valid.result,
lemma::OperationResult::Value(Box::new(lemma::LiteralValue::from_bool(true)))
);
}
#[test]
fn test_spec_ref_field_access_arithmetic() {
let mut engine = Engine::new();
let base_spec = r#"
spec base
data project_start: 2024-01-15
data probation_period: 90 days
"#;
let child_spec = r#"
spec child
with base_contract: base
rule probation_end: base_contract.project_start + base_contract.probation_period
"#;
engine
.load(base_spec, lemma::SourceType::Labeled("test.lemma"))
.unwrap();
engine
.load(child_spec, lemma::SourceType::Labeled("test.lemma"))
.unwrap();
let now = DateTimeValue::now();
let response = engine
.run("child", Some(&now), HashMap::new(), false)
.unwrap();
let probation_end = response
.results
.values()
.find(|r| r.rule.name == "probation_end")
.unwrap();
match &probation_end.result {
lemma::OperationResult::Value(lit) => match &lit.value {
lemma::ValueKind::Date(date) => {
assert_eq!(date.year, 2024);
assert_eq!(date.month, 4);
assert_eq!(date.day, 14);
}
other => panic!("Expected Date for probation_end, got {:?}", other),
},
other => panic!("Expected Value for probation_end, got {:?}", other),
}
}