ainl-memory 0.1.9-alpha

AINL graph-memory substrate - agent memory as execution graph
Documentation
//! validate_graph tests.

use ainl_memory::{
    AgentGraphSnapshot, AinlMemoryNode, GraphStore, SnapshotEdge, SqliteGraphStore,
    SNAPSHOT_SCHEMA_VERSION,
};
use std::borrow::Cow;
use uuid::Uuid;

#[test]
fn test_validate_clean_graph() {
    let path = std::env::temp_dir().join(format!("ainl_val_clean_{}.db", Uuid::new_v4()));
    let _ = std::fs::remove_file(&path);
    let store = SqliteGraphStore::open(&path).unwrap();
    let ag = "val-clean";
    let mut a = AinlMemoryNode::new_episode(Uuid::new_v4(), 2_200_000_000, vec![], None, None);
    a.agent_id = ag.into();
    let mut b = AinlMemoryNode::new_episode(Uuid::new_v4(), 2_200_000_001, vec![], None, None);
    b.agent_id = ag.into();
    store.write_node(&a).unwrap();
    store.write_node(&b).unwrap();
    store.insert_graph_edge(a.id, b.id, "x").unwrap();
    let r = store.validate_graph(ag).unwrap();
    assert!(r.is_valid);
    assert!(r.dangling_edges.is_empty());
    assert_eq!(r.node_count, 2);
    assert_eq!(r.edge_count, 1);
}

#[test]
fn test_validate_detects_dangling() {
    let path = std::env::temp_dir().join(format!("ainl_val_dang_{}.db", Uuid::new_v4()));
    let _ = std::fs::remove_file(&path);
    let ag = "val-dang";
    let mut a = AinlMemoryNode::new_episode(Uuid::new_v4(), 2_300_000_000, vec![], None, None);
    a.agent_id = ag.to_string();
    let aid = a.id;
    let ghost = Uuid::new_v4();
    let snap = AgentGraphSnapshot {
        agent_id: ag.to_string(),
        exported_at: chrono::Utc::now(),
        schema_version: Cow::Borrowed(SNAPSHOT_SCHEMA_VERSION),
        nodes: vec![a],
        edges: vec![SnapshotEdge {
            source_id: aid,
            target_id: ghost,
            edge_type: "broken".to_string(),
            weight: 1.0,
            metadata: None,
        }],
    };
    let mut store = SqliteGraphStore::open(&path).unwrap();
    store.import_graph(&snap, true).unwrap();
    let r = store.validate_graph(ag).unwrap();
    assert!(!r.is_valid);
    assert_eq!(r.dangling_edges.len(), 1);
    assert_eq!(r.dangling_edge_details.len(), 1);
    assert_eq!(r.dangling_edge_details[0].edge_type, "broken");
    assert_eq!(r.cross_agent_boundary_edges, 0);
}

#[test]
fn test_validate_cross_agent_boundary_counts() {
    let path = std::env::temp_dir().join(format!("ainl_val_cross_{}.db", Uuid::new_v4()));
    let _ = std::fs::remove_file(&path);
    let store = SqliteGraphStore::open(&path).unwrap();
    let a1 = "cross-a1";
    let a2 = "cross-a2";
    let mut n1 = AinlMemoryNode::new_episode(Uuid::new_v4(), 2_400_000_000, vec![], None, None);
    n1.agent_id = a1.into();
    let mut n2 = AinlMemoryNode::new_episode(Uuid::new_v4(), 2_400_000_001, vec![], None, None);
    n2.agent_id = a2.into();
    store.write_node(&n1).unwrap();
    store.write_node(&n2).unwrap();
    store.insert_graph_edge(n1.id, n2.id, "handoff").unwrap();
    let r = store.validate_graph(a1).unwrap();
    assert!(r.is_valid);
    assert_eq!(r.cross_agent_boundary_edges, 1);
    assert!(r.dangling_edge_details.is_empty());
}

#[test]
fn test_insert_graph_edge_checked_rejects_missing_target() {
    let path = std::env::temp_dir().join(format!("ainl_val_chk_{}.db", Uuid::new_v4()));
    let _ = std::fs::remove_file(&path);
    let store = SqliteGraphStore::open(&path).unwrap();
    let mut a = AinlMemoryNode::new_episode(Uuid::new_v4(), 2_500_000_000, vec![], None, None);
    a.agent_id = "chk".into();
    store.write_node(&a).unwrap();
    let ghost = Uuid::new_v4();
    let err = store
        .insert_graph_edge_checked(a.id, ghost, "x")
        .expect_err("missing target");
    assert!(err.contains("missing target"));
}

#[test]
fn test_import_strict_rejects_dangling() {
    let path = std::env::temp_dir().join(format!("ainl_val_strict_{}.db", Uuid::new_v4()));
    let _ = std::fs::remove_file(&path);
    let ag = "val-strict";
    let mut a = AinlMemoryNode::new_episode(Uuid::new_v4(), 2_300_000_100, vec![], None, None);
    a.agent_id = ag.to_string();
    let aid = a.id;
    let snap = AgentGraphSnapshot {
        agent_id: ag.to_string(),
        exported_at: chrono::Utc::now(),
        schema_version: Cow::Borrowed(SNAPSHOT_SCHEMA_VERSION),
        nodes: vec![a],
        edges: vec![SnapshotEdge {
            source_id: aid,
            target_id: Uuid::new_v4(),
            edge_type: "broken".to_string(),
            weight: 1.0,
            metadata: None,
        }],
    };
    let mut store = SqliteGraphStore::open(&path).unwrap();
    let err = store
        .import_graph(&snap, false)
        .expect_err("strict import must fail");
    assert!(
        err.to_lowercase().contains("foreign"),
        "unexpected error: {err}"
    );
}