sqlitegraph 2.0.7

Embedded graph database with full ACID transactions, HNSW vector search, dual backend support, and comprehensive graph algorithms library
Documentation
//! TDD Tests for Lazy KV and Pub/Sub Initialization in V3Backend
//!
//! These tests verify that:
//! 1. KV store is not initialized until first use
//! 2. Publisher is not initialized until first use
//! 3. Graph operations work without KV/PubSub
//! 4. Lazy initialization works correctly

use crate::backend::native::v3::{V3Backend, KvValue};
use crate::backend::{EdgeSpec, NodeSpec, GraphBackend, SubscriptionFilter, PubSubEvent};
use crate::snapshot::SnapshotId;
use tempfile::TempDir;

/// Test that graph operations work without KV/PubSub initialization
#[test]
fn test_graph_ops_without_kv_or_pubsub() {
    let temp_dir = TempDir::new().unwrap();
    let db_path = temp_dir.path().join("test.graph");
    
    let backend = V3Backend::create(&db_path).unwrap();
    
    // Insert nodes - pure graph operation
    let node1 = backend.insert_node(NodeSpec {
        kind: "File".to_string(),
        name: "main.rs".to_string(),
        file_path: Some("src/main.rs".to_string()),
        data: serde_json::json!({"lines": 100}),
    }).unwrap();
    
    let node2 = backend.insert_node(NodeSpec {
        kind: "Function".to_string(),
        name: "main".to_string(),
        file_path: None,
        data: serde_json::json!({"public": true}),
    }).unwrap();
    
    // Insert edge - pure graph operation
    backend.insert_edge(EdgeSpec {
        from: node1,
        to: node2,
        edge_type: "contains".to_string(),
        data: serde_json::json!({}),
    }).unwrap();
    
    // Query operations
    let ids = backend.entity_ids().unwrap();
    assert_eq!(ids.len(), 2);
    
    let outgoing = backend.fetch_outgoing(node1).unwrap();
    assert_eq!(outgoing.len(), 1);
    assert_eq!(outgoing[0], node2);
    
    let incoming = backend.fetch_incoming(node2).unwrap();
    assert_eq!(incoming.len(), 1);
    assert_eq!(incoming[0], node1);
}

/// Test that KV store is NOT initialized on read operations (lazy write)
#[test]
fn test_kv_not_initialized_on_get() {
    let temp_dir = TempDir::new().unwrap();
    let db_path = temp_dir.path().join("test.graph");
    
    let backend = V3Backend::create(&db_path).unwrap();
    
    // Before any KV operation, kv_store should not be initialized
    assert!(!backend.is_kv_initialized(), "KV store should not be initialized on creation");
    
    // First KV get should NOT initialize it (reads return None for uninitialized)
    let snapshot_id = SnapshotId::current();
    let result = backend.kv_get_v3(snapshot_id, b"test_key");
    assert!(result.is_none(), "Should return None for non-existent key");
    
    // KV should still NOT be initialized (only writes initialize)
    assert!(!backend.is_kv_initialized(), "KV store should NOT be initialized after read-only get");
}

/// Test that KV store is lazily initialized on first kv_set
#[test]
fn test_kv_lazy_init_on_set() {
    let temp_dir = TempDir::new().unwrap();
    let db_path = temp_dir.path().join("test.graph");
    
    let backend = V3Backend::create(&db_path).unwrap();
    
    // Before any KV operation
    assert!(!backend.is_kv_initialized(), "KV store should not be initialized on creation");
    
    // First KV set should initialize it
    backend.kv_set_v3(
        b"my_key".to_vec(),
        KvValue::String("value".to_string()),
        None,
    );
    
    // Now KV should be initialized
    assert!(backend.is_kv_initialized(), "KV store should be initialized after first set");
}

/// Test that Publisher is lazily initialized on first subscribe
#[test]
fn test_pubsub_lazy_init_on_subscribe() {
    let temp_dir = TempDir::new().unwrap();
    let db_path = temp_dir.path().join("test.graph");
    
    let backend = V3Backend::create(&db_path).unwrap();
    
    // Before any PubSub operation
    assert!(!backend.is_pubsub_initialized(), "Publisher should not be initialized on creation");
    
    // First subscribe should initialize it
    let filter = SubscriptionFilter::all();
    let (sub_id, _receiver) = backend.subscribe(filter).unwrap();
    
    // Now PubSub should be initialized
    assert!(backend.is_pubsub_initialized(), "Publisher should be initialized after first subscribe");
    
    // Cleanup
    backend.unsubscribe(sub_id).unwrap();
}

/// Test that KV operations work correctly after lazy initialization
#[test]
fn test_kv_operations_after_lazy_init() {
    let temp_dir = TempDir::new().unwrap();
    let db_path = temp_dir.path().join("test.graph");
    
    let backend = V3Backend::create(&db_path).unwrap();
    let snapshot_id = SnapshotId::current();
    
    // Key shouldn't exist initially
    let value = backend.kv_get_v3(snapshot_id, b"counter");
    assert!(value.is_none());
    
    // Set a value
    backend.kv_set_v3(
        b"counter".to_vec(),
        KvValue::Integer(42),
        None,
    );
    
    // Read it back
    let value = backend.kv_get_v3(snapshot_id, b"counter");
    assert!(value.is_some());
    match value.unwrap() {
        KvValue::Integer(v) => assert_eq!(v, 42),
        _ => panic!("Expected integer value"),
    }
    
    // Delete it
    backend.kv_delete_v3(b"counter");
    
    // Should be gone
    let value = backend.kv_get_v3(snapshot_id, b"counter");
    assert!(value.is_none());
}

/// Test that PubSub operations work correctly after lazy initialization
#[test]
fn test_pubsub_operations_after_lazy_init() {
    let temp_dir = TempDir::new().unwrap();
    let db_path = temp_dir.path().join("test.graph");
    
    let backend = V3Backend::create(&db_path).unwrap();
    
    // Subscribe
    let filter = SubscriptionFilter::all();
    let (sub_id, receiver) = backend.subscribe(filter).unwrap();
    
    // Unsubscribe
    let removed = backend.unsubscribe(sub_id).unwrap();
    assert!(removed);
    
    // Unsubscribe again should return false
    let removed = backend.unsubscribe(sub_id).unwrap();
    assert!(!removed);
}

/// Test that multiple graph operations don't trigger KV/PubSub initialization
#[test]
fn test_multiple_graph_ops_no_kv_pubsub_init() {
    let temp_dir = TempDir::new().unwrap();
    let db_path = temp_dir.path().join("test.graph");
    
    let backend = V3Backend::create(&db_path).unwrap();
    
    // Perform many graph operations
    for i in 0..10 {
        let node = backend.insert_node(NodeSpec {
            kind: "Node".to_string(),
            name: format!("node_{}", i),
            file_path: None,
            data: serde_json::json!({}),
        }).unwrap();
        
        if i > 0 {
            backend.insert_edge(EdgeSpec {
                from: (i as i64),
                to: node,
                edge_type: "links".to_string(),
                data: serde_json::json!({}),
            }).unwrap();
        }
    }
    
    // Verify no KV or PubSub was initialized
    assert!(!backend.is_kv_initialized(), "KV should not be initialized after graph ops");
    assert!(!backend.is_pubsub_initialized(), "PubSub should not be initialized after graph ops");
}

/// Test that interleaved graph and KV operations work correctly
#[test]
fn test_interleaved_graph_and_kv_ops() {
    let temp_dir = TempDir::new().unwrap();
    let db_path = temp_dir.path().join("test.graph");
    
    let backend = V3Backend::create(&db_path).unwrap();
    let snapshot_id = SnapshotId::current();
    
    // Insert some nodes
    let node1 = backend.insert_node(NodeSpec {
        kind: "File".to_string(),
        name: "a.rs".to_string(),
        file_path: None,
        data: serde_json::json!({}),
    }).unwrap();
    
    // Set some KV data
    backend.kv_set_v3(
        b"metadata".to_vec(),
        KvValue::Json(
            serde_json::json!({"author": "test"})
        ),
        None,
    );
    
    // More graph operations
    let node2 = backend.insert_node(NodeSpec {
        kind: "File".to_string(),
        name: "b.rs".to_string(),
        file_path: None,
        data: serde_json::json!({}),
    }).unwrap();
    
    backend.insert_edge(EdgeSpec {
        from: node1,
        to: node2,
        edge_type: "depends_on".to_string(),
        data: serde_json::json!({}),
    }).unwrap();
    
    // Read KV back
    let value = backend.kv_get_v3(snapshot_id, b"metadata");
    assert!(value.is_some());
    
    // Verify graph structure
    let outgoing = backend.fetch_outgoing(node1).unwrap();
    assert_eq!(outgoing.len(), 1);
}