relay-knowledge 1.1.10

Graph-database-based knowledge graph project.
Documentation
use std::{path::PathBuf, time::Duration};

use super::*;
use crate::env::{
    PlatformKind, RELAY_KNOWLEDGE_CODE_INDEX_MAX_IN_FLIGHT, RELAY_KNOWLEDGE_STORAGE_TOPOLOGY,
};

#[test]
fn file_index_root_ids_use_canonical_paths_when_available() {
    let suffix = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .expect("time should be valid")
        .as_nanos();
    let root = std::env::temp_dir().join(format!(
        "relay-knowledge-runtime-root-{}-{suffix}",
        std::process::id()
    ));
    std::fs::create_dir_all(&root).expect("fixture root should be created");

    let direct = FileIndexRootConfig::new("local-files", root.clone());
    let dotted = FileIndexRootConfig::new("local-files", root.join("."));
    assert_eq!(direct.root_id, dotted.root_id);
    assert_eq!(direct.root_path, dotted.root_path);

    std::fs::remove_dir_all(root).expect("fixture root should be removed");
}

#[test]
fn file_index_root_ids_normalize_nonexistent_trailing_separators() {
    let plain = FileIndexRootConfig::new("local-files", PathBuf::from("/opt/docs"));
    let trailing = FileIndexRootConfig::new("local-files", PathBuf::from("/opt/docs/"));

    assert_eq!(plain.root_id, trailing.root_id);
    assert_eq!(plain.root_path, trailing.root_path);
}

#[test]
fn file_index_roots_from_environment_must_be_absolute() {
    let environment = EnvironmentConfig::from_pairs(
        PlatformKind::Unix,
        [("RELAY_KNOWLEDGE_FILE_INDEX_ROOTS", "docs;/opt/docs")],
    )
    .expect("environment should parse");

    let error = FileIndexRuntimeConfig::from_environment(&environment)
        .expect_err("relative file index roots should be rejected");

    assert_eq!(
        error,
        FileIndexRuntimeConfigError::RelativeRoot("docs".to_owned())
    );
    assert!(error.to_string().contains("absolute path"));
}

#[test]
fn file_index_roots_accept_windows_drive_and_unc_paths() {
    assert!(is_absolute_file_index_root(
        r"D:\Documents",
        PlatformKind::Windows
    ));
    assert!(is_absolute_file_index_root(
        r"\\server\share\Documents",
        PlatformKind::Windows
    ));
    assert!(!is_absolute_file_index_root(
        r"D:Documents",
        PlatformKind::Windows
    ));
}

#[tokio::test]
async fn resolves_code_index_worker_concurrency_from_environment() {
    let environment = EnvironmentConfig::from_pairs(
        PlatformKind::Unix,
        [(RELAY_KNOWLEDGE_CODE_INDEX_MAX_IN_FLIGHT, "4")],
    )
    .expect("environment should parse");

    let runtime = RuntimeConfiguration::from_environment(&environment)
        .await
        .expect("runtime should compose");

    assert_eq!(runtime.workers.code_index_max_in_flight, 4);
}

#[tokio::test]
async fn caps_code_index_worker_concurrency_from_environment() {
    let environment = EnvironmentConfig::from_pairs(
        PlatformKind::Unix,
        [(RELAY_KNOWLEDGE_CODE_INDEX_MAX_IN_FLIGHT, "99")],
    )
    .expect("environment should parse");

    let runtime = RuntimeConfiguration::from_environment(&environment)
        .await
        .expect("runtime should compose");

    assert_eq!(
        runtime.workers.code_index_max_in_flight,
        WorkerRuntimeConfig::MAX_CODE_INDEX_MAX_IN_FLIGHT
    );
}

#[tokio::test]
async fn resolves_storage_topology_from_environment() {
    let default_environment = storage_topology_test_environment(None);
    let default_runtime = RuntimeConfiguration::from_environment(&default_environment)
        .await
        .expect("runtime should compose");

    assert_eq!(
        default_runtime.storage.topology,
        StorageTopology::SingleSqlite
    );

    let partitioned_environment = storage_topology_test_environment(Some("partitioned_sqlite"));
    let partitioned_runtime = RuntimeConfiguration::from_environment(&partitioned_environment)
        .await
        .expect("runtime should compose");

    assert_eq!(
        partitioned_runtime.storage.topology,
        StorageTopology::PartitionedSqlite
    );
}

#[tokio::test]
async fn rejects_invalid_storage_topology_from_environment() {
    let environment = storage_topology_test_environment(Some("distributed_sqlite"));

    let error = RuntimeConfiguration::from_environment(&environment)
        .await
        .expect_err("invalid storage topology should be rejected");

    assert!(error.to_string().contains("single_sqlite"));
    assert!(error.to_string().contains("partitioned_sqlite"));
}

fn storage_topology_test_environment(topology: Option<&str>) -> EnvironmentConfig {
    let suffix = topology.unwrap_or("default");
    let root = std::env::temp_dir().join(format!(
        "relay-knowledge-runtime-storage-{suffix}-{}",
        std::process::id()
    ));
    let mut pairs = vec![(
        "RELAY_KNOWLEDGE_HOME".to_owned(),
        root.display().to_string(),
    )];
    if let Some(topology) = topology {
        pairs.push((
            RELAY_KNOWLEDGE_STORAGE_TOPOLOGY.to_owned(),
            topology.to_owned(),
        ));
    }

    EnvironmentConfig::from_pairs(PlatformKind::current(), pairs).expect("environment should parse")
}

#[tokio::test]
async fn resolves_mcp_agent_runtime_from_environment() {
    let environment = EnvironmentConfig::from_pairs(
        PlatformKind::Unix,
        [
            ("RELAY_KNOWLEDGE_MCP_STREAMABLE_HTTP_ENABLED", "true"),
            ("RELAY_KNOWLEDGE_MCP_ENDPOINT", "/relay-mcp"),
            (
                "RELAY_KNOWLEDGE_MCP_ALLOWED_ORIGINS",
                "http://localhost:3000",
            ),
            ("RELAY_KNOWLEDGE_MCP_ALLOWED_SCOPES", "docs,src"),
            ("RELAY_KNOWLEDGE_MCP_ALLOW_UNSPECIFIED_SCOPE", "true"),
            ("RELAY_KNOWLEDGE_MCP_MAX_LIMIT", "3"),
            ("RELAY_KNOWLEDGE_MCP_MAX_CONTEXT_BYTES", "4096"),
            ("RELAY_KNOWLEDGE_MCP_ALLOW_REMOTE_CLIENTS", "true"),
            ("RELAY_KNOWLEDGE_AGENT_AUDIT_SINK_ENABLED", "true"),
            ("RELAY_KNOWLEDGE_AGENT_AUDIT_QUEUE_DEPTH", "128"),
        ],
    )
    .expect("environment should parse");

    let runtime = RuntimeConfiguration::from_environment(&environment)
        .await
        .expect("runtime should compose");

    assert!(runtime.agent.mcp_streamable_http_enabled);
    assert_eq!(runtime.agent.mcp_endpoint, "/relay-mcp");
    assert_eq!(runtime.agent.mcp_allowed_origins, ["http://localhost:3000"]);
    assert_eq!(runtime.agent.access_policy.allowed_scopes, ["docs", "src"]);
    assert!(runtime.agent.access_policy.allow_unspecified_scope);
    assert_eq!(runtime.agent.access_policy.max_limit, 3);
    assert_eq!(runtime.agent.access_policy.max_context_bytes, 4096);
    assert!(runtime.agent.access_policy.allow_remote_clients);
    assert!(runtime.agent.audit_sink_enabled);
    assert_eq!(runtime.agent.audit_queue_depth, 128);
}

#[tokio::test]
async fn resolves_retrieval_read_model_runtime_from_environment() {
    let environment = EnvironmentConfig::from_pairs(
        PlatformKind::Unix,
        [
            ("RELAY_KNOWLEDGE_SEMANTIC_BACKEND", "external"),
            ("RELAY_KNOWLEDGE_VECTOR_BACKEND", "external"),
            ("RELAY_KNOWLEDGE_LLM_PROVIDER", "openai_compatible"),
            (
                "RELAY_KNOWLEDGE_EMBEDDING_BASE_URL",
                "https://embeddings.example/v1",
            ),
            ("RELAY_KNOWLEDGE_EMBEDDING_API_KEY", "secret-key"),
            ("RELAY_KNOWLEDGE_TEXT_EMBEDDING_MODEL", "text-embed-3-small"),
            ("RELAY_KNOWLEDGE_IMAGE_EMBEDDING_MODEL", "clip-vit-b32"),
            ("RELAY_KNOWLEDGE_EMBEDDING_DIMENSION", "1536"),
            ("RELAY_KNOWLEDGE_EMBEDDING_BATCH_SIZE", "16"),
            ("RELAY_KNOWLEDGE_EMBEDDING_TIMEOUT_MS", "9000"),
            ("RELAY_KNOWLEDGE_EMBEDDING_MAX_CONCURRENCY", "2"),
            ("RELAY_KNOWLEDGE_RERANK_BACKEND", "external"),
            ("RELAY_KNOWLEDGE_RERANK_MODEL", "bge-reranker-v2"),
            ("RELAY_KNOWLEDGE_RERANK_TIMEOUT_MS", "700"),
            ("RELAY_KNOWLEDGE_RERANK_CANDIDATE_MULTIPLIER", "5"),
            ("RELAY_KNOWLEDGE_RERANK_MAX_CANDIDATES", "80"),
        ],
    )
    .expect("environment should parse");

    let runtime = RuntimeConfiguration::from_environment(&environment)
        .await
        .expect("runtime should compose");

    assert_eq!(
        runtime.retrieval.semantic_mode,
        ReadModelBackendMode::External
    );
    assert_eq!(
        runtime.retrieval.vector_mode,
        ReadModelBackendMode::External
    );
    assert_eq!(runtime.retrieval.vector_model.name, "text-embed-3-small");
    assert_eq!(runtime.retrieval.image_model.name, "clip-vit-b32");
    assert_eq!(runtime.retrieval.vector_model.dimension, 1536);
    let remote = runtime
        .retrieval
        .remote_embedding
        .expect("remote embedding config should be present");
    assert_eq!(remote.provider, EmbeddingProviderKind::OpenAiCompatible);
    assert_eq!(remote.redacted_base_url(), "https://embeddings.example");
    assert_eq!(remote.batch_size, 16);
    assert_eq!(remote.timeout, Duration::from_millis(9000));
    assert_eq!(remote.max_concurrency, 2);
    assert_eq!(runtime.retrieval.rerank.mode, RerankMode::External);
    assert_eq!(
        runtime.retrieval.rerank.model.as_deref(),
        Some("bge-reranker-v2")
    );
    assert_eq!(runtime.retrieval.rerank.timeout, Duration::from_millis(700));
    assert_eq!(runtime.retrieval.rerank.candidate_multiplier, 5);
    assert_eq!(runtime.retrieval.rerank.max_candidates, 80);
}

#[tokio::test]
async fn rejects_external_backend_without_remote_model_metadata() {
    let environment = EnvironmentConfig::from_pairs(
        PlatformKind::Unix,
        [
            ("RELAY_KNOWLEDGE_VECTOR_BACKEND", "external"),
            (
                "RELAY_KNOWLEDGE_EMBEDDING_BASE_URL",
                "https://embeddings.example/v1",
            ),
            ("RELAY_KNOWLEDGE_EMBEDDING_API_KEY", "secret-key"),
        ],
    )
    .expect("environment should parse");

    let error = RuntimeConfiguration::from_environment(&environment)
        .await
        .expect_err("external backend should require explicit model metadata");

    assert!(matches!(
        error,
        RuntimeConfigurationError::Retrieval(RetrievalRuntimeConfigError::MissingRemoteValue(
            RELAY_KNOWLEDGE_TEXT_EMBEDDING_MODEL
        ))
    ));
}

#[tokio::test]
async fn rejects_blank_retrieval_model_overrides() {
    let environment = EnvironmentConfig::from_pairs(
        PlatformKind::Unix,
        [("RELAY_KNOWLEDGE_TEXT_EMBEDDING_MODEL", "   ")],
    )
    .expect("environment should parse");

    let error = RuntimeConfiguration::from_environment(&environment)
        .await
        .expect_err("blank model name should fail");

    assert!(matches!(
        error,
        RuntimeConfigurationError::Retrieval(RetrievalRuntimeConfigError::EmptyModelName(
            RELAY_KNOWLEDGE_TEXT_EMBEDDING_MODEL
        ))
    ));
}

#[tokio::test]
async fn rejects_unknown_rerank_backend_mode() {
    let environment = EnvironmentConfig::from_pairs(
        PlatformKind::Unix,
        [("RELAY_KNOWLEDGE_RERANK_BACKEND", "remote")],
    )
    .expect("environment should parse");

    let error = RuntimeConfiguration::from_environment(&environment)
        .await
        .expect_err("unknown rerank backend should fail");

    assert!(matches!(
        error,
        RuntimeConfigurationError::Retrieval(RetrievalRuntimeConfigError::InvalidRerankBackend(_))
    ));
}

#[tokio::test]
async fn rejects_invalid_mcp_endpoint() {
    let environment = EnvironmentConfig::from_pairs(
        PlatformKind::Unix,
        [("RELAY_KNOWLEDGE_MCP_ENDPOINT", "mcp")],
    )
    .expect("environment should parse");

    let error = RuntimeConfiguration::from_environment(&environment)
        .await
        .expect_err("invalid endpoint should fail");

    assert!(matches!(
        error,
        RuntimeConfigurationError::Agent(AgentRuntimeConfigError::InvalidEndpoint(_))
    ));
}

#[tokio::test]
async fn rejects_worker_endpoint_without_http_host() {
    for endpoint in ["https://worker.local", "http://", "http://:8792"] {
        let environment = EnvironmentConfig::from_pairs(
            PlatformKind::Unix,
            [("RELAY_KNOWLEDGE_WORKER_OCR_ENDPOINT", endpoint)],
        )
        .expect("environment should parse");

        let error = RuntimeConfiguration::from_environment(&environment)
            .await
            .expect_err("invalid worker endpoint should fail");

        assert!(matches!(
            error,
            RuntimeConfigurationError::Workers(WorkerRuntimeConfigError::InvalidEndpoint(_))
        ));
    }
}