#[allow(unused_imports)]
pub use evorule_reactor::{
compute_chain_hash, content_hash, fact_hash, fact_to_stable_json, HashError,
};
#[cfg(test)]
mod tests {
#![allow(deprecated, clippy::unwrap_used, clippy::panic, clippy::expect_used)]
use super::*;
use evorule_reactor::{Fact, FactId, IoType};
use evorule_tcb::JsonValue;
#[test]
fn test_fact_to_stable_json_format() {
let command = Fact::Command {
id: FactId(1),
instruction: JsonValue::object_from_pairs(&[
("type", JsonValue::string("increment")),
(
"params",
JsonValue::object_from_pairs(&[
("attr", JsonValue::string("x")),
("delta", JsonValue::Integer(5)),
]),
),
]),
};
let json_value = fact_to_stable_json(&command).unwrap();
assert_eq!(json_value.get("type").unwrap().as_str().unwrap(), "Command");
assert_eq!(json_value.get("id").unwrap().as_u64().unwrap(), 1);
assert!(json_value.get("instruction").is_some());
}
#[test]
fn test_fact_hash_all_variants() {
let test_facts = vec![
Fact::Command {
id: FactId(1),
instruction: JsonValue::empty_object(),
},
Fact::PayloadUpdate {
id: FactId(2),
path: "test.path".into(),
value: JsonValue::string("test_value"),
},
Fact::StateTransition {
id: FactId(3),
cause: FactId(1),
new_payload: JsonValue::empty_object(),
new_queue: vec![],
},
Fact::IoRequest {
id: FactId(4),
cause: FactId(3),
io_type: IoType::http_get(),
params: JsonValue::empty_object(),
},
Fact::IoResponse {
id: FactId(5),
request_id: FactId(4),
result: JsonValue::string("response"),
error: None,
},
Fact::IoResponse {
id: FactId(6),
request_id: FactId(4),
result: JsonValue::Null,
error: Some("timeout".to_string()),
},
Fact::Stable {
id: FactId(7),
version: 1,
},
Fact::Error {
id: FactId(8),
message: "test error".into(),
},
];
for fact in test_facts {
let hash = fact_hash(&fact).unwrap();
assert_eq!(hash.len(), 64);
let hash2 = fact_hash(&fact).unwrap();
assert_eq!(
hash,
hash2,
"fact_hash should be deterministic for {}",
fact.type_name()
);
}
}
#[test]
fn test_fact_hash_identity() {
let fact1 = Fact::Command {
id: FactId(1),
instruction: JsonValue::string("same"),
};
let fact2 = Fact::Command {
id: FactId(1),
instruction: JsonValue::string("same"),
};
assert_eq!(fact_hash(&fact1).unwrap(), fact_hash(&fact2).unwrap());
}
#[test]
fn test_fact_hash_different_ids() {
let fact1 = Fact::Command {
id: FactId(1),
instruction: JsonValue::string("same"),
};
let fact2 = Fact::Command {
id: FactId(2),
instruction: JsonValue::string("same"),
};
assert_ne!(fact_hash(&fact1).unwrap(), fact_hash(&fact2).unwrap());
}
#[test]
fn test_cross_validate_with_tier1() {
let test_facts = [
Fact::Command {
id: FactId(1),
instruction: JsonValue::empty_object(),
},
Fact::PayloadUpdate {
id: FactId(2),
path: "test.path".into(),
value: JsonValue::string("test_value"),
},
Fact::StateTransition {
id: FactId(3),
cause: FactId(1),
new_payload: JsonValue::empty_object(),
new_queue: vec![],
},
Fact::IoRequest {
id: FactId(4),
cause: FactId(3),
io_type: IoType::http_get(),
params: JsonValue::empty_object(),
},
Fact::IoResponse {
id: FactId(5),
request_id: FactId(4),
result: JsonValue::string("response"),
error: None,
},
Fact::Stable {
id: FactId(6),
version: 1,
},
Fact::Error {
id: FactId(7),
message: "test error".into(),
},
];
let cli_hashes: Vec<String> = test_facts.iter().map(|f| fact_hash(f).unwrap()).collect();
let tier1_hashes: Vec<String> = test_facts
.iter()
.map(|f| evorule_reactor::fact_hash(f).unwrap())
.collect();
assert_eq!(
cli_hashes, tier1_hashes,
"CLI re-export 的哈希与 tier1 直接计算的哈希不一致!\
这违反了两套 WAL 合并的单一真相源原则。"
);
let cli_chain = compute_chain_hash(&test_facts).unwrap();
let tier1_chain = evorule_reactor::compute_chain_hash(&test_facts).unwrap();
assert_eq!(
cli_chain, tier1_chain,
"CLI re-export 的链哈希与 tier1 直接计算的不一致!"
);
}
#[test]
fn test_hash_chain_stability() {
let facts = vec![
Fact::Command {
id: FactId(1),
instruction: JsonValue::empty_object(),
},
Fact::StateTransition {
id: FactId(2),
cause: FactId(1),
new_payload: JsonValue::empty_object(),
new_queue: vec![],
},
];
let result1 = compute_chain_hash(&facts).unwrap();
let result2 = compute_chain_hash(&facts).unwrap();
assert_eq!(result1, result2);
}
#[test]
fn test_compute_chain_hash_empty() {
let facts: Vec<Fact> = vec![];
let chain_hash = compute_chain_hash(&facts).unwrap();
assert_eq!(chain_hash, "genesis");
}
#[test]
fn test_compute_chain_hash_order_sensitive() {
let fact1 = Fact::Command {
id: FactId(1),
instruction: JsonValue::empty_object(),
};
let fact2 = Fact::StateTransition {
id: FactId(2),
cause: FactId(1),
new_payload: JsonValue::empty_object(),
new_queue: vec![],
};
let chain1 = compute_chain_hash(&[fact1.clone(), fact2.clone()]).unwrap();
let chain2 = compute_chain_hash(&[fact2, fact1]).unwrap();
assert_ne!(chain1, chain2, "链哈希应对 Fact 顺序敏感");
}
}