sqlitegraph 3.9.0

Embedded graph database with explicit SQLite, native-v3, and combined backend modes; HNSW vector search; and graph algorithms
Documentation
use super::*;
use tempfile::TempDir;

#[test]
fn test_v3_snapshot_import() {
    use std::io::Write;

    let temp_dir = TempDir::new().unwrap();
    let db_path = temp_dir.path().join("test.graph");
    let backend = V3Backend::create(&db_path).unwrap();

    let snapshot_path = temp_dir.path().join("snapshot.json");
    let mut file = std::fs::File::create(&snapshot_path).unwrap();
    writeln!(
        file,
        r#"{{"type":"entity","id":1,"kind":"Class","name":"NodeA","file_path":null,"data":{{"score":42}}}}"#
    )
    .unwrap();
    writeln!(
        file,
        r#"{{"type":"entity","id":2,"kind":"Class","name":"NodeB","file_path":"src/b.rs","data":{{"score":7}}}}"#
    )
    .unwrap();
    writeln!(
        file,
        r#"{{"type":"entity","id":3,"kind":"Method","name":"NodeC","file_path":null,"data":{{}}}}"#
    )
    .unwrap();
    writeln!(
        file,
        r#"{{"type":"edge","id":10,"from_id":1,"to_id":2,"edge_type":"calls","data":{{"weight":2.0}}}}"#
    )
    .unwrap();
    writeln!(
        file,
        r#"{{"type":"edge","id":11,"from_id":2,"to_id":3,"edge_type":"defines","data":{{}}}}"#
    )
    .unwrap();
    writeln!(file, r#"{{"type":"label","entity_id":1,"label":"public"}}"#).unwrap();
    writeln!(
        file,
        r#"{{"type":"property","entity_id":1,"key":"lang","value":"\"rust\""}}"#
    )
    .unwrap();
    drop(file);

    let meta = backend.snapshot_import(temp_dir.path()).unwrap();
    assert_eq!(meta.entities_imported, 3, "three entities should import");
    assert_eq!(meta.edges_imported, 2, "two edges should import");

    let header = backend.header.read();
    assert_eq!(header.node_count, 3, "header node_count should be 3");
    assert_eq!(header.edge_count, 2, "header edge_count should be 2");
    drop(header);

    let ids = backend.entity_ids().unwrap();
    assert_eq!(ids.len(), 3, "entity_ids should report 3 nodes");

    use crate::snapshot::SnapshotId;
    let node = backend.get_node(SnapshotId::current(), ids[0]).unwrap();
    assert_eq!(node.kind, "Class");
    assert_eq!(node.name, "NodeA");
    assert_eq!(node.data.get("score").and_then(|v| v.as_i64()), Some(42));
    assert_eq!(
        node.data
            .get("_labels")
            .and_then(|v| v.as_array())
            .map(|a| a.len()),
        Some(1),
        "merged label should be present in data"
    );
    assert_eq!(
        node.data.get("lang").and_then(|v| v.as_str()),
        Some("rust"),
        "merged property should be present in data"
    );
}

#[test]
fn test_v3_snapshot_import_missing_file() {
    let temp_dir = TempDir::new().unwrap();
    let db_path = temp_dir.path().join("test.graph");
    let backend = V3Backend::create(&db_path).unwrap();
    let result = backend.snapshot_import(temp_dir.path());
    assert!(result.is_err(), "import without snapshot.json should fail");
}

#[test]
fn test_snapshot_creation() {
    let temp_dir = TempDir::new().unwrap();
    let db_path = temp_dir.path().join("test.graph");
    let backend = V3Backend::create(&db_path).unwrap();

    let snapshot_name = "snapshot_v1";
    let lsn_v1 = backend.create_snapshot(snapshot_name).unwrap();
    assert!(lsn_v1 > 0, "Snapshot LSN should be positive");

    backend
        .insert_node(NodeSpec {
            kind: "Agent".to_string(),
            name: "agent1".to_string(),
            file_path: None,
            data: serde_json::json!({"version": 1}),
        })
        .unwrap();

    let snapshot_name_v2 = "snapshot_v2";
    let lsn_v2 = backend.create_snapshot(snapshot_name_v2).unwrap();
    assert!(lsn_v2 > lsn_v1, "Snapshot LSNs should increment");

    backend
        .insert_node(NodeSpec {
            kind: "Agent".to_string(),
            name: "agent2".to_string(),
            file_path: None,
            data: serde_json::json!({"version": 2}),
        })
        .unwrap();

    let retrieved_lsn = backend.get_snapshot_lsn(snapshot_name);
    assert_eq!(retrieved_lsn, Some(lsn_v1), "Should retrieve stored LSN");

    let retrieved_lsn_v2 = backend.get_snapshot_lsn(snapshot_name_v2);
    assert_eq!(retrieved_lsn_v2, Some(lsn_v2), "Should retrieve second LSN");

    let snapshots = backend.list_snapshots();
    assert_eq!(snapshots.len(), 2, "Should have 2 snapshots");
    assert!(
        snapshots
            .iter()
            .any(|(name, lsn)| name == snapshot_name && *lsn == lsn_v1)
    );

    backend.delete_snapshot(snapshot_name).unwrap();
    let snapshots_after_delete = backend.list_snapshots();
    assert_eq!(
        snapshots_after_delete.len(),
        1,
        "Should have 1 snapshot after delete"
    );
    assert!(
        !snapshots_after_delete
            .iter()
            .any(|(name, _)| name == snapshot_name)
    );

    let result = backend.delete_snapshot("nonexistent");
    assert!(
        result.is_err(),
        "Deleting non-existent snapshot should fail"
    );
}

#[test]
fn test_snapshot_version_increments() {
    let temp_dir = TempDir::new().unwrap();
    let db_path = temp_dir.path().join("test.graph");
    let backend = V3Backend::create(&db_path).unwrap();

    let version_0 = backend.current_snapshot_version();
    assert_eq!(version_0, 1, "Initial version should be 1");

    backend
        .insert_node(NodeSpec {
            kind: "Node".to_string(),
            name: "node1".to_string(),
            file_path: None,
            data: serde_json::json!({}),
        })
        .unwrap();

    let version_1 = backend.current_snapshot_version();
    assert_eq!(version_1, 2, "Version should increment after insert");

    let node_id = backend.entity_ids().unwrap()[0];
    backend
        .update_node(
            node_id,
            NodeSpec {
                kind: "Node".to_string(),
                name: "node1".to_string(),
                file_path: None,
                data: serde_json::json!({"updated": true}),
            },
        )
        .unwrap();

    let version_2 = backend.current_snapshot_version();
    assert_eq!(version_2, 3, "Version should increment after update");

    backend.delete_entity(node_id).unwrap();

    let version_3 = backend.current_snapshot_version();
    assert_eq!(version_3, 4, "Version should increment after delete");
}

#[test]
fn test_current_snapshot_validation() {
    use crate::SnapshotId;

    let current = SnapshotId::current();
    assert!(V3Backend::require_current_snapshot(current).is_ok());

    let historical = SnapshotId::from_lsn(12345);
    assert!(V3Backend::require_current_snapshot(historical).is_ok());

    let invalid = SnapshotId::invalid();
    assert!(V3Backend::require_current_snapshot(invalid).is_err());
}