klieo 3.3.1

Open-source Rust agent framework — typed agents, durable inter-agent comms, local-first.
Documentation
//! Integration test for the `App::graph_rag` one-line facade.
//!
//! Verifies that `.graph_rag()` (or `.graph_rag_with`) installs a working
//! `GraphAwareLongTerm` as the App's long-term memory port and that a
//! `remember -> recall` round trip flows through it.
//!
//! Uses the deterministic, non-zero-vector `FakeEmbedder` rather than the
//! default fastembed model so the test runs offline (no ~100 MB download)
//! while still exercising real cosine ranking.

#![cfg(feature = "graph-rag")]

use std::sync::Arc;

use klieo::embed_common::FakeEmbedder;
use klieo::{App, GraphRagConfig};
use klieo_core::memory::{Fact, Scope};

const TEST_EMBEDDER_ID: &str = "test-fake";
const TEST_EMBEDDER_DIM: usize = 384;

fn test_embedder() -> Arc<FakeEmbedder> {
    Arc::new(FakeEmbedder::new(TEST_EMBEDDER_DIM))
}

fn test_cfg() -> GraphRagConfig {
    GraphRagConfig::in_memory().embedder(test_embedder(), TEST_EMBEDDER_ID)
}

#[tokio::test]
async fn shutdown_drains_projector() {
    let app = App::local()
        .model("dummy-model")
        .graph_rag_with(test_cfg())
        .build()
        .await
        .expect("graph-rag App should build with an in-memory backend");

    app.shutdown().await;
    // A second call is a no-op: the projector handle was already taken.
    app.shutdown().await;
}

#[tokio::test]
async fn shutdown_does_not_cancel_shared_parent() {
    use tokio_util::sync::CancellationToken;

    let parent = CancellationToken::new();
    let app = App::local()
        .model("dummy-model")
        .cancel_token(parent.clone())
        .graph_rag_with(test_cfg())
        .build()
        .await
        .expect("graph-rag App should build with an in-memory backend");

    app.shutdown().await;

    assert!(
        !parent.is_cancelled(),
        "shutting down the projector must not cancel the App's parent token"
    );
}

#[tokio::test]
async fn in_memory_graph_rag_builds_and_recalls() {
    let cfg = GraphRagConfig::in_memory().embedder(test_embedder(), TEST_EMBEDDER_ID);
    let app = App::local()
        .model("dummy-model")
        .graph_rag_with(cfg)
        .build()
        .await
        .expect("graph-rag App should build with an in-memory backend");

    let long_term = app.memory().long_term.clone();
    let scope = Scope::Workspace("facade-test".to_string());

    let target = "klieo wires GraphRAG into App::build";
    long_term
        .remember(scope.clone(), Fact::new(target))
        .await
        .expect("remember through the graph-aware long-term port should succeed");
    long_term
        .remember(
            scope.clone(),
            Fact::new("an entirely unrelated note about gardening"),
        )
        .await
        .expect("remember of a distractor fact should succeed");

    let hits = long_term
        .recall(scope, "klieo wires GraphRAG into App::build", 5)
        .await
        .expect("recall through the graph-aware long-term port should succeed");

    assert_eq!(
        hits.first().map(|f| f.text.as_str()),
        Some(target),
        "the fact matching the query must rank first under real cosine ranking; got {hits:?}"
    );
}

#[tokio::test]
async fn graph_rag_without_provenance_still_recalls() {
    let cfg = GraphRagConfig::in_memory()
        .embedder(test_embedder(), TEST_EMBEDDER_ID)
        .without_provenance();
    let app = App::local()
        .model("dummy-model")
        .graph_rag_with(cfg)
        .build()
        .await
        .expect("graph-rag App should build without the provenance wrapper");

    let long_term = app.memory().long_term.clone();
    let scope = Scope::Workspace("facade-test-no-prov".to_string());

    long_term
        .remember(scope.clone(), Fact::new("provenance can be disabled"))
        .await
        .expect("remember should succeed without provenance");

    let hits = long_term
        .recall(scope, "provenance can be disabled", 5)
        .await
        .expect("recall should succeed without provenance");

    assert!(
        hits.iter().any(|f| f.text.contains("provenance")),
        "fact must round-trip even with the provenance wrapper disabled; got {hits:?}"
    );
}

#[tokio::test]
async fn graph_rag_exposes_metrics_when_built() {
    let cfg = GraphRagConfig::in_memory().embedder(test_embedder(), TEST_EMBEDDER_ID);
    let app = App::local()
        .model("dummy-model")
        .graph_rag_with(cfg)
        .build()
        .await
        .expect("graph-rag App should build with an in-memory backend");

    assert!(
        app.graph_rag_metrics().is_some(),
        "an App built with graph_rag must expose a recall-metrics snapshot"
    );
}

#[tokio::test]
async fn graph_rag_without_projector_still_exposes_metrics() {
    let cfg = GraphRagConfig::in_memory()
        .embedder(test_embedder(), TEST_EMBEDDER_ID)
        .without_projector();
    let app = App::local()
        .model("dummy-model")
        .graph_rag_with(cfg)
        .build()
        .await
        .expect("graph-rag App should build with the projector disabled");

    assert!(
        app.graph_rag_metrics().is_some(),
        "metrics are exposed even when the episode-to-graph projector is off"
    );
}