ito-domain 0.1.32

Domain models and repositories for Ito
Documentation
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()));
}