use serde_json::{Map, Value};
use crate::entry::AuditEntry;
pub fn reconstruct_state(entries: &[AuditEntry]) -> Option<Value> {
let mut state: Map<String, Value> = Map::new();
let mut seen_any = false;
for entry in entries {
match &entry.after {
Some(Value::Object(after_map)) => {
for (k, v) in after_map {
state.insert(k.clone(), v.clone());
}
seen_any = true;
}
Some(v) => {
return Some(v.clone());
}
None => {}
}
}
if seen_any {
Some(Value::Object(state))
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::NaiveDateTime;
use serde_json::json;
use uuid::Uuid;
fn entry_with_after(after: Option<Value>) -> AuditEntry {
AuditEntry {
id: Uuid::new_v4(),
tenant_id: None,
actor_kind: "system".to_string(),
actor_id: None,
action: "test.event".to_string(),
target_kind: None,
target_id: None,
before: None,
after,
reason: None,
correlation_id: None,
created_at: NaiveDateTime::default(),
}
}
#[test]
fn reconstruct_state_empty_returns_none() {
assert_eq!(reconstruct_state(&[]), None);
}
#[test]
fn reconstruct_state_all_none_returns_none() {
let entries = vec![
entry_with_after(None),
entry_with_after(None),
entry_with_after(None),
];
assert_eq!(reconstruct_state(&entries), None);
}
#[test]
fn reconstruct_state_object_merges_shallow() {
let entries = vec![
entry_with_after(Some(json!({ "quantity": 10, "status": "available" }))),
entry_with_after(Some(json!({ "quantity": 5 }))),
entry_with_after(Some(json!({ "status": "reserved", "owner": "u_42" }))),
];
let result = reconstruct_state(&entries).expect("non-empty");
assert_eq!(
result,
json!({ "quantity": 5, "status": "reserved", "owner": "u_42" })
);
}
#[test]
fn reconstruct_state_nested_object_replaced_wholesale() {
let entries = vec![
entry_with_after(Some(json!({ "config": { "retries": 3, "timeout": 30 } }))),
entry_with_after(Some(json!({ "config": { "retries": 5 } }))),
];
let result = reconstruct_state(&entries).expect("non-empty");
assert_eq!(result, json!({ "config": { "retries": 5 } }));
}
#[test]
fn reconstruct_state_non_object_replaces_state() {
let entries = vec![
entry_with_after(Some(json!({ "quantity": 10 }))),
entry_with_after(Some(json!("DELETED"))), ];
let result = reconstruct_state(&entries).expect("non-empty");
assert_eq!(result, json!("DELETED"));
}
}