locus-core-rs 0.4.2

Core STTP parsing, validation, storage contracts, and application services for Rust
Documentation
use chrono::{DateTime, Utc};
use locus_core_rs::domain::contracts::NodeStore;
use locus_core_rs::domain::models::{
    AvecState, NodeDeleteStatus, NodeQuery, NodeUpsertStatus, SessionPurgeRequest, SttpNode,
};
use locus_core_rs::storage::InMemoryNodeStore;

fn build_test_node(session_id: &str, sync_key: &str) -> SttpNode {
    SttpNode {
        raw: "raw".to_string(),
        session_id: session_id.to_string(),
        tier: "raw".to_string(),
        timestamp: DateTime::parse_from_rfc3339("2026-03-05T06:30:00Z")
            .expect("timestamp should parse")
            .with_timezone(&Utc),
        compression_depth: 1,
        parent_node_id: None,
        sync_key: sync_key.to_string(),
        updated_at: DateTime::parse_from_rfc3339("2026-03-05T06:30:00Z")
            .expect("timestamp should parse")
            .with_timezone(&Utc),
        source_metadata: None,
        context_summary: None,
        semantic_tags: None,
        semantic_links: None,
        embedding: None,
        embedding_model: None,
        embedding_dimensions: None,
        embedded_at: None,
        user_avec: AvecState::zero(),
        model_avec: AvecState::zero(),
        compression_avec: None,
        rho: 0.9,
        kappa: 0.9,
        psi: 2.0,
    }
}

#[tokio::test(flavor = "current_thread")]
async fn delete_node_by_sync_key_removes_row() {
    let store = InMemoryNodeStore::new();
    let session = "evict-session";
    store
        .upsert_node_async(build_test_node(session, "sync-42"))
        .await
        .expect("upsert");

    let result = store
        .delete_nodes_async(locus_core_rs::domain::models::NodeDeleteRequest {
            tenant_id: "default".to_string(),
            session_id: session.to_string(),
            sync_keys: vec!["sync-42".to_string()],
            node_ids: vec![],
            dry_run: false,
        })
        .await
        .expect("delete");

    assert_eq!(result.deleted, 1);
    assert_eq!(result.records[0].status, NodeDeleteStatus::Deleted);

    let remaining = store
        .query_nodes_async(NodeQuery {
            limit: 10,
            session_id: Some(session.to_string()),
            ..Default::default()
        })
        .await
        .expect("query");
    assert!(remaining.is_empty());
}

#[tokio::test(flavor = "current_thread")]
async fn dry_run_delete_does_not_remove_rows() {
    let store = InMemoryNodeStore::new();
    let session = "evict-session";
    store
        .upsert_node_async(build_test_node(session, "sync-42"))
        .await
        .expect("upsert");

    let result = store
        .delete_nodes_async(locus_core_rs::domain::models::NodeDeleteRequest {
            tenant_id: "default".to_string(),
            session_id: session.to_string(),
            sync_keys: vec!["sync-42".to_string()],
            node_ids: vec![],
            dry_run: true,
        })
        .await
        .expect("delete");

    assert_eq!(result.deleted, 1);
    assert_eq!(result.records[0].status, NodeDeleteStatus::Skipped);

    let remaining = store
        .query_nodes_async(NodeQuery {
            limit: 10,
            session_id: Some(session.to_string()),
            ..Default::default()
        })
        .await
        .expect("query");
    assert_eq!(remaining.len(), 1);
}

#[tokio::test(flavor = "current_thread")]
async fn delete_by_node_id_removes_row() {
    let store = InMemoryNodeStore::new();
    let session = "evict-session";
    let upsert = store
        .upsert_node_async(build_test_node(session, "sync-42"))
        .await
        .expect("upsert");
    assert_eq!(upsert.status, NodeUpsertStatus::Created);

    let result = store
        .delete_nodes_async(locus_core_rs::domain::models::NodeDeleteRequest {
            tenant_id: "default".to_string(),
            session_id: session.to_string(),
            sync_keys: vec![],
            node_ids: vec![upsert.node_id],
            dry_run: false,
        })
        .await
        .expect("delete");

    assert_eq!(result.deleted, 1);
}

#[tokio::test(flavor = "current_thread")]
async fn session_purge_removes_matching_nodes() {
    let store = InMemoryNodeStore::new();
    let session = "purge-session";
    store
        .upsert_node_async(build_test_node(session, "one"))
        .await
        .expect("upsert one");
    store
        .upsert_node_async(build_test_node(session, "two"))
        .await
        .expect("upsert two");

    let result = store
        .purge_session_async(SessionPurgeRequest {
            tenant_id: "default".to_string(),
            session_id: session.to_string(),
            tiers: None,
            dry_run: false,
            include_calibration: false,
            include_checkpoints: false,
        })
        .await
        .expect("purge");

    assert_eq!(result.deleted, 2);
}