use lemma::parsing::ast::DateTimeValue;
use lemma::{Engine, LiteralValue, Target};
use std::collections::HashMap;
fn setup_engine(code: &str) -> Engine {
let mut engine = Engine::new();
engine
.load(code, lemma::SourceType::Labeled("test"))
.expect("Failed to add code");
engine
}
#[test]
fn test_inversion_simple_arithmetic() {
let code = r#"
spec pricing
fact price: [number]
fact quantity: [number]
rule total: price * quantity
"#;
let engine = setup_engine(code);
let now = DateTimeValue::now();
let result = engine.invert(
"pricing",
&now,
"total",
Target::value(LiteralValue::number(100.into())),
HashMap::new(),
);
assert!(result.is_ok(), "Inversion should succeed: {:?}", result);
let response = result.unwrap();
assert!(!response.is_empty(), "Should have at least one solution");
let first_solution = &response.solutions[0];
assert!(
first_solution.shape.is_some(),
"With multiple unknowns, solution should have a shape representing the constraint"
);
}
#[test]
fn test_inversion_veto_query() {
let code = r#"
spec shipping
fact weight: [number]
rule shipping_cost: 5
unless weight >= 10 then 10
unless weight >= 50 then 25
unless weight < 0 then veto "invalid"
unless weight > 100 then veto "too heavy"
"#;
let engine = setup_engine(code);
let now = DateTimeValue::now();
let result = engine.invert(
"shipping",
&now,
"shipping_cost",
Target::veto(Some("too heavy".to_string())),
HashMap::new(),
);
assert!(
result.is_ok(),
"Veto inversion should succeed: {:?}",
result
);
let response = result.unwrap();
assert!(
!response.is_empty(),
"Should have at least one solution for veto"
);
let first_domains = &response.domains[0];
let has_weight = first_domains.keys().any(|fp| fp.fact.contains("weight"));
assert!(has_weight, "Should have domain constraint on weight");
}