use super::*;
use crate::audit::event::{EventContext, SCHEMA_VERSION};
fn make_event(
entity: &str,
entity_id: &str,
scope: Option<&str>,
op: &str,
from: Option<&str>,
to: Option<&str>,
) -> AuditEvent {
AuditEvent {
v: SCHEMA_VERSION,
ts: "2026-02-08T14:30:00.000Z".to_string(),
entity: entity.to_string(),
entity_id: entity_id.to_string(),
scope: scope.map(String::from),
op: op.to_string(),
from: from.map(String::from),
to: to.map(String::from),
actor: "cli".to_string(),
by: "@test".to_string(),
meta: None,
count: 1,
ctx: EventContext {
session_id: "test".to_string(),
harness_session_id: None,
branch: None,
worktree: None,
commit: None,
},
}
}
#[test]
fn empty_events_produce_empty_state() {
let state = materialize_state(&[]);
assert!(state.entities.is_empty());
assert_eq!(state.event_count, 0);
}
#[test]
fn single_create_event() {
let events = vec![make_event(
"task",
"1.1",
Some("change-1"),
"create",
None,
Some("pending"),
)];
let state = materialize_state(&events);
let key = EntityKey {
entity: "task".to_string(),
entity_id: "1.1".to_string(),
scope: Some("change-1".to_string()),
};
assert_eq!(state.entities.get(&key), Some(&"pending".to_string()));
assert_eq!(state.event_count, 1);
}
#[test]
fn status_change_updates_state() {
let events = vec![
make_event(
"task",
"1.1",
Some("change-1"),
"create",
None,
Some("pending"),
),
make_event(
"task",
"1.1",
Some("change-1"),
"status_change",
Some("pending"),
Some("in-progress"),
),
];
let state = materialize_state(&events);
let key = EntityKey {
entity: "task".to_string(),
entity_id: "1.1".to_string(),
scope: Some("change-1".to_string()),
};
assert_eq!(state.entities.get(&key), Some(&"in-progress".to_string()));
assert_eq!(state.event_count, 2);
}
#[test]
fn multiple_entities_tracked_independently() {
let events = vec![
make_event(
"task",
"1.1",
Some("change-1"),
"create",
None,
Some("pending"),
),
make_event(
"task",
"1.2",
Some("change-1"),
"create",
None,
Some("pending"),
),
make_event(
"task",
"1.1",
Some("change-1"),
"status_change",
Some("pending"),
Some("complete"),
),
];
let state = materialize_state(&events);
let key1 = EntityKey {
entity: "task".to_string(),
entity_id: "1.1".to_string(),
scope: Some("change-1".to_string()),
};
let key2 = EntityKey {
entity: "task".to_string(),
entity_id: "1.2".to_string(),
scope: Some("change-1".to_string()),
};
assert_eq!(state.entities.get(&key1), Some(&"complete".to_string()));
assert_eq!(state.entities.get(&key2), Some(&"pending".to_string()));
}
#[test]
fn archive_event_without_to_uses_sentinel() {
let events = vec![make_event("change", "009-02", None, "archive", None, None)];
let state = materialize_state(&events);
let key = EntityKey {
entity: "change".to_string(),
entity_id: "009-02".to_string(),
scope: None,
};
assert_eq!(state.entities.get(&key), Some(&"archived".to_string()));
}
#[test]
fn reconciled_events_update_state() {
let events = vec![
make_event(
"task",
"1.1",
Some("change-1"),
"create",
None,
Some("pending"),
),
make_event(
"task",
"1.1",
Some("change-1"),
"reconciled",
Some("pending"),
Some("complete"),
),
];
let state = materialize_state(&events);
let key = EntityKey {
entity: "task".to_string(),
entity_id: "1.1".to_string(),
scope: Some("change-1".to_string()),
};
assert_eq!(state.entities.get(&key), Some(&"complete".to_string()));
}
#[test]
fn reconciled_event_without_to_tombstones_state() {
let events = vec![
make_event(
"task",
"1.1",
Some("change-1"),
"create",
None,
Some("pending"),
),
make_event(
"task",
"1.1",
Some("change-1"),
"reconciled",
Some("pending"),
None,
),
];
let state = materialize_state(&events);
let key = EntityKey {
entity: "task".to_string(),
entity_id: "1.1".to_string(),
scope: Some("change-1".to_string()),
};
assert_eq!(state.entities.get(&key), None);
}
#[test]
fn global_entities_have_no_scope() {
let events = vec![make_event(
"config",
"worktrees.enabled",
None,
"set",
None,
Some("true"),
)];
let state = materialize_state(&events);
let key = EntityKey {
entity: "config".to_string(),
entity_id: "worktrees.enabled".to_string(),
scope: None,
};
assert_eq!(state.entities.get(&key), Some(&"true".to_string()));
}
#[test]
fn last_event_wins() {
let events = vec![
make_event("task", "1.1", Some("ch"), "status_change", None, Some("a")),
make_event(
"task",
"1.1",
Some("ch"),
"status_change",
Some("a"),
Some("b"),
),
make_event(
"task",
"1.1",
Some("ch"),
"status_change",
Some("b"),
Some("c"),
),
];
let state = materialize_state(&events);
let key = EntityKey {
entity: "task".to_string(),
entity_id: "1.1".to_string(),
scope: Some("ch".to_string()),
};
assert_eq!(state.entities.get(&key), Some(&"c".to_string()));
}