#![allow(clippy::unwrap_used, clippy::panic, clippy::expect_used)]
use evorule_reactor::{Fact, FactIdGenerator, Reactor};
use evorule_tcb::JsonValue;
use std::collections::BTreeMap;
use std::time::Duration;
use tokio::time::timeout;
fn serde_to_tcb(v: serde_json::Value) -> JsonValue {
match v {
serde_json::Value::Null => JsonValue::Null,
serde_json::Value::Bool(b) => JsonValue::Bool(b),
serde_json::Value::Number(n) => {
if let Some(i) = n.as_i64() {
JsonValue::Integer(i)
} else {
JsonValue::string(n.to_string())
}
}
serde_json::Value::String(s) => JsonValue::string(s),
serde_json::Value::Array(arr) => {
JsonValue::Array(arr.into_iter().map(serde_to_tcb).collect())
}
serde_json::Value::Object(obj) => {
let mut map = BTreeMap::new();
for (k, v) in obj {
map.insert(k, serde_to_tcb(v));
}
JsonValue::Object(map)
}
}
}
fn load_json_rule(json_str: &str) -> Vec<JsonValue> {
let json: serde_json::Value =
serde_json::from_str(json_str).expect("Failed to parse JSON rule");
json.get("transform")
.and_then(|v| v.as_array())
.map(|arr| arr.iter().cloned().map(serde_to_tcb).collect())
.unwrap_or_default()
}
#[tokio::test]
#[allow(clippy::too_many_lines)]
async fn test_vip_order_processing() {
let json_rule = r#"{
"transform": [
{
"type": "branch",
"params": {
"domain": {
"type": "instruction",
"instruction_type": "process_order"
},
"on_true": [
{
"type": "branch",
"params": {
"domain": {
"type": "exists",
"path": "__exec__.payload.is_vip"
},
"on_true": [
{
"type": "branch",
"params": {
"domain": {
"type": "exists",
"path": "__exec__.payload.__io_results__.call_service"
},
"on_true": [
{
"type": "set",
"params": {
"attr": "discount",
"operation": "set",
"value": 10
}
},
{
"type": "set",
"params": {
"attr": "vip_notification",
"operation": "set",
"value": "__exec__.payload.__io_results__.call_service"
}
}
],
"on_false": [
{
"type": "io_request",
"params": {
"io_type": "call_service",
"service_name": "notify_vip",
"prompt": "VIP 客户享受 9 折优惠"
}
}
]
}
}
],
"on_false": [
{
"type": "set",
"params": {
"attr": "discount",
"operation": "set",
"value": 0
}
}
]
}
}
]
}
}
]
}"#;
let core_eval = load_json_rule(json_rule);
let _initial_payload = JsonValue::object_from_pairs(&[
("is_vip", JsonValue::Bool(true)),
("inventory", JsonValue::Integer(100)),
]);
let reactor = Reactor::builder(core_eval).max_rounds(100).build();
let (tx, mut rx, _event_tx, _handle, facts_log) = reactor.spawn();
let mut gen = FactIdGenerator::new();
tx.send(Fact::PayloadUpdate {
id: gen.next_id(),
path: "is_vip".to_string(),
value: JsonValue::Bool(true),
})
.unwrap();
tx.send(Fact::PayloadUpdate {
id: gen.next_id(),
path: "inventory".to_string(),
value: JsonValue::Integer(100),
})
.unwrap();
let instruction = JsonValue::object_from_pairs(&[
("type", JsonValue::string("process_order")),
(
"params",
JsonValue::object_from_pairs(&[
("order_id", JsonValue::string("ORD-001")),
("amount", JsonValue::Integer(1000)),
]),
),
]);
tx.send(Fact::Command {
id: gen.next_id(),
instruction,
})
.unwrap();
let request_id = timeout(Duration::from_secs(5), async {
while let Ok(fact) = rx.recv().await {
match fact {
Fact::IoRequest {
id,
io_type,
params,
..
} => {
println!("I/O Request: {} - {:?}", io_type, params);
return Some(id);
}
Fact::Error { message, .. } => {
panic!("Error during execution: {}", message);
}
_ => {}
}
}
None
})
.await
.unwrap()
.expect("IoRequest not received");
tx.send(Fact::IoResponse {
id: gen.next_id(),
request_id,
result: JsonValue::string("VIP notification sent"),
error: None,
})
.unwrap();
let final_state = timeout(Duration::from_secs(5), async {
while let Ok(fact) = rx.recv().await {
match fact {
Fact::Stable { .. } => return Some(facts_log.snapshot().0),
Fact::Error { message, .. } => {
panic!("Error during execution: {}", message);
}
_ => {}
}
}
None
})
.await
.unwrap()
.expect("Timeout waiting for Stable after IoResponse");
let discount = final_state
.get("discount")
.and_then(|v| v.as_i64())
.expect("discount should exist");
assert_eq!(discount, 10, "VIP customer should have 10% discount");
let facts = facts_log.history();
println!("\n=== 审计链记录 ===");
for (i, fact) in facts.iter().enumerate() {
println!("Fact {}: {:?}", i, fact);
}
let has_command = facts.iter().any(|f| matches!(f, Fact::Command { .. }));
let has_io_request = facts.iter().any(|f| matches!(f, Fact::IoRequest { .. }));
let has_io_response = facts.iter().any(|f| matches!(f, Fact::IoResponse { .. }));
assert!(has_command, "Should have Command fact");
assert!(has_io_request, "Should have IoRequest fact");
assert!(has_io_response, "Should have IoResponse fact");
println!("\n✓ 审计链完整,包含 Command → IoRequest → IoResponse");
}
#[tokio::test]
async fn test_normal_order_processing() {
let json_rule = r#"{
"transform": [
{
"type": "branch",
"params": {
"domain": {
"type": "instruction",
"instruction_type": "process_order"
},
"on_true": [
{
"type": "branch",
"params": {
"domain": {
"type": "exists",
"path": "__exec__.payload.is_vip"
},
"on_true": [
{
"type": "set",
"params": {
"attr": "discount",
"operation": "set",
"value": 10
}
}
],
"on_false": [
{
"type": "set",
"params": {
"attr": "discount",
"operation": "set",
"value": 0
}
}
]
}
}
]
}
}
]
}"#;
let core_eval = load_json_rule(json_rule);
let reactor = Reactor::builder(core_eval).max_rounds(100).build();
let (tx, mut rx, _event_tx, _handle, facts_log) = reactor.spawn();
let mut gen = FactIdGenerator::new();
let mut params = BTreeMap::new();
params.insert("order_id".to_string(), JsonValue::string("ORD-002"));
params.insert("amount".to_string(), JsonValue::Integer(500));
let mut instr = BTreeMap::new();
instr.insert("type".to_string(), JsonValue::string("process_order"));
instr.insert("params".to_string(), JsonValue::Object(params));
let instruction = JsonValue::Object(instr);
tx.send(Fact::Command {
id: gen.next_id(),
instruction,
})
.unwrap();
let result = timeout(Duration::from_secs(5), async {
let mut final_state = None;
while let Ok(fact) = rx.recv().await {
match &fact {
Fact::Stable { .. } => {
final_state = Some(facts_log.snapshot().0);
break;
}
Fact::Error { message, .. } => {
panic!("Error during execution: {}", message);
}
_ => {}
}
}
final_state
})
.await
.expect("Timeout waiting for execution");
assert!(result.is_some(), "Should have final state");
let final_state = result.unwrap();
let discount = final_state
.get("discount")
.and_then(|v| v.as_i64())
.expect("discount should exist");
assert_eq!(discount, 0, "Normal customer should have 0% discount");
let facts = facts_log.history();
println!("\n=== 审计链记录 ===");
for (i, fact) in facts.iter().enumerate() {
println!("Fact {}: {:?}", i, fact);
}
println!("\n✓ 普通客户无折扣,审计链完整");
}