Expand runtime event graph service

This commit is contained in:
Jan Petykiewicz 2026-04-14 19:37:53 -07:00
commit 6ebe5fffeb
14 changed files with 1803 additions and 254 deletions

View file

@ -1,4 +1,5 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
use rrt_runtime::{RuntimeState, RuntimeSummary, StepCommand};
@ -461,6 +462,8 @@ pub struct FixtureDocument {
pub commands: Vec<StepCommand>,
#[serde(default)]
pub expected_summary: ExpectedRuntimeSummary,
#[serde(default)]
pub expected_state_fragment: Option<Value>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@ -483,6 +486,8 @@ pub struct RawFixtureDocument {
pub commands: Vec<StepCommand>,
#[serde(default)]
pub expected_summary: ExpectedRuntimeSummary,
#[serde(default)]
pub expected_state_fragment: Option<Value>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@ -493,6 +498,54 @@ pub struct FixtureValidationReport {
pub issues: Vec<String>,
}
pub fn compare_expected_state_fragment(expected: &Value, actual: &Value) -> Vec<String> {
let mut mismatches = Vec::new();
compare_expected_state_fragment_at_path("$", expected, actual, &mut mismatches);
mismatches
}
fn compare_expected_state_fragment_at_path(
path: &str,
expected: &Value,
actual: &Value,
mismatches: &mut Vec<String>,
) {
match (expected, actual) {
(Value::Object(expected_map), Value::Object(actual_map)) => {
for (key, expected_value) in expected_map {
let next_path = format!("{path}.{key}");
match actual_map.get(key) {
Some(actual_value) => compare_expected_state_fragment_at_path(
&next_path,
expected_value,
actual_value,
mismatches,
),
None => mismatches.push(format!("{next_path} missing in actual state")),
}
}
}
(Value::Array(expected_items), Value::Array(actual_items)) => {
for (index, expected_item) in expected_items.iter().enumerate() {
let next_path = format!("{path}[{index}]");
match actual_items.get(index) {
Some(actual_item) => compare_expected_state_fragment_at_path(
&next_path,
expected_item,
actual_item,
mismatches,
),
None => mismatches.push(format!("{next_path} missing in actual state")),
}
}
}
_ if expected != actual => mismatches.push(format!(
"{path} mismatch: expected {expected:?}, got {actual:?}"
)),
_ => {}
}
}
pub fn validate_fixture_document(document: &FixtureDocument) -> FixtureValidationReport {
let mut issues = Vec::new();
@ -609,4 +662,36 @@ mod tests {
assert_eq!(mismatches.len(), 1);
assert!(mismatches[0].contains("calendar mismatch"));
}
#[test]
fn compares_expected_state_fragment_recursively() {
let expected = serde_json::json!({
"world_flags": {
"sandbox": false
},
"companies": [
{
"company_id": 1
}
]
});
let actual = serde_json::json!({
"world_flags": {
"sandbox": false,
"runtime.effect_fired": true
},
"companies": [
{
"company_id": 1,
"current_cash": 250000
}
]
});
let mismatches = compare_expected_state_fragment(&expected, &actual);
assert!(
mismatches.is_empty(),
"unexpected mismatches: {mismatches:?}"
);
}
}