relay-knowledge 1.1.14

Graph-database-based knowledge graph project.
Documentation
use std::path::PathBuf;

use super::*;
use crate::env::{
    EnvErrorKind,
    platform::{HOME, TMPDIR},
};

#[test]
fn parses_platform_and_relay_overrides() {
    let config = EnvironmentConfig::from_pairs(
        PlatformKind::Unix,
        [
            (HOME, "/home/alice"),
            (TMPDIR, "/var/tmp"),
            (RELAY_KNOWLEDGE_HOME, "/opt/relay-runtime"),
            (RELAY_KNOWLEDGE_HTTP_BIND, "127.0.0.1:9000"),
            (RELAY_KNOWLEDGE_REMOTE_BASE_URL, "http://relay.example:8791"),
            (HTTPS_PROXY, "https://proxy.internal:8443"),
            (NO_PROXY, "localhost,.internal"),
            (SSL_VERIFY, "false"),
            (RELAY_KNOWLEDGE_QOS_MAX_CONNECTIONS, "512"),
            (RELAY_KNOWLEDGE_MCP_STREAMABLE_HTTP_ENABLED, "true"),
            (RELAY_KNOWLEDGE_MCP_ENDPOINT, "/mcp"),
            (RELAY_KNOWLEDGE_MCP_ALLOWED_ORIGINS, "http://localhost:8791"),
            (RELAY_KNOWLEDGE_MCP_ALLOWED_SCOPES, "docs,src"),
            (RELAY_KNOWLEDGE_MCP_ALLOW_UNSPECIFIED_SCOPE, "true"),
            (RELAY_KNOWLEDGE_MCP_MAX_LIMIT, "5"),
            (RELAY_KNOWLEDGE_MCP_MAX_CONTEXT_BYTES, "8192"),
            (RELAY_KNOWLEDGE_MCP_ALLOW_REMOTE_CLIENTS, "false"),
            (RELAY_KNOWLEDGE_AGENT_AUDIT_SINK_ENABLED, "true"),
            (RELAY_KNOWLEDGE_AGENT_AUDIT_QUEUE_DEPTH, "256"),
            (RELAY_KNOWLEDGE_SEMANTIC_BACKEND, "external"),
            (RELAY_KNOWLEDGE_VECTOR_BACKEND, "external"),
            (RELAY_KNOWLEDGE_TEXT_EMBEDDING_MODEL, "text-embed-3-small"),
            (RELAY_KNOWLEDGE_IMAGE_EMBEDDING_MODEL, "clip-vit-b32"),
            (RELAY_KNOWLEDGE_EMBEDDING_DIMENSION, "1536"),
            (RELAY_KNOWLEDGE_FILE_INDEX_SCAN_TIMEOUT_MS, "120000"),
            (RELAY_KNOWLEDGE_FILE_QUERY_TIMEOUT_MS, "600"),
            (RELAY_KNOWLEDGE_WATCHER_COMMIT_RECONCILE_INTERVAL_MS, "7000"),
            (RELAY_KNOWLEDGE_UPDATE_CHECK_ENABLED, "true"),
            (RELAY_KNOWLEDGE_UPDATE_SOURCES, "github,crates.io"),
            (RELAY_KNOWLEDGE_UPDATE_CHECK_INTERVAL_MS, "86400000"),
            (
                RELAY_KNOWLEDGE_UPDATE_GITHUB_REPO,
                "coolplayagent/relay-knowledge",
            ),
            (RELAY_OTEL_ENDPOINT, "http://collector.internal:4318"),
            (RELAY_OTEL_TRACES, "true"),
            (RELAY_OTEL_METRICS, "false"),
            (RELAY_OTEL_EXPORT_TIMEOUT_MS, "1500"),
            (RELAY_OTEL_SERVICE_ENVIRONMENT, "test"),
        ],
    )
    .expect("environment should parse");

    assert_eq!(config.platform.platform, PlatformKind::Unix);
    assert_eq!(config.platform.home_dir, Some(PathBuf::from("/home/alice")));
    assert_eq!(config.platform.temp_dir, Some(PathBuf::from("/var/tmp")));
    assert_eq!(config.paths.home, Some(PathBuf::from("/opt/relay-runtime")));
    assert_eq!(config.network.http_bind, Some("127.0.0.1:9000".to_owned()));
    assert_eq!(
        config.remote_cli.base_url,
        Some("http://relay.example:8791".to_owned())
    );
    assert_eq!(
        config.network.proxy,
        Some("https://proxy.internal:8443".to_owned())
    );
    assert_eq!(
        config.network.no_proxy,
        Some("localhost,.internal".to_owned())
    );
    assert_eq!(config.network.ssl_verify, Some(false));
    assert_eq!(config.network.qos_max_connections, Some(512));
    assert_eq!(config.agent.mcp_streamable_http_enabled, Some(true));
    assert_eq!(config.agent.mcp_endpoint, Some("/mcp".to_owned()));
    assert_eq!(
        config.agent.mcp_allowed_origins,
        Some("http://localhost:8791".to_owned())
    );
    assert_eq!(config.agent.mcp_allowed_scopes, Some("docs,src".to_owned()));
    assert_eq!(config.agent.mcp_allow_unspecified_scope, Some(true));
    assert_eq!(config.agent.mcp_max_limit, Some(5));
    assert_eq!(config.agent.mcp_max_context_bytes, Some(8192));
    assert_eq!(config.agent.mcp_allow_remote_clients, Some(false));
    assert_eq!(config.agent.audit_sink_enabled, Some(true));
    assert_eq!(config.agent.audit_queue_depth, Some(256));
    assert_eq!(
        config.retrieval.semantic_backend,
        Some("external".to_owned())
    );
    assert_eq!(config.retrieval.vector_backend, Some("external".to_owned()));
    assert_eq!(
        config.retrieval.text_embedding_model,
        Some("text-embed-3-small".to_owned())
    );
    assert_eq!(
        config.retrieval.image_embedding_model,
        Some("clip-vit-b32".to_owned())
    );
    assert_eq!(config.retrieval.embedding_dimension, Some(1536));
    assert_eq!(config.file_index.scan_timeout_ms, Some(120000));
    assert_eq!(config.file_index.query_timeout_ms, Some(600));
    assert_eq!(config.watcher.commit_reconcile_interval_ms, Some(7000));
    assert_eq!(config.updates.enabled, Some(true));
    assert_eq!(config.updates.sources, Some("github,crates.io".to_owned()));
    assert_eq!(config.updates.check_interval_ms, Some(86_400_000));
    assert_eq!(
        config.updates.github_repo,
        Some("coolplayagent/relay-knowledge".to_owned())
    );
    assert_eq!(
        config.telemetry.otel_endpoint,
        Some("http://collector.internal:4318".to_owned())
    );
    assert_eq!(config.telemetry.otel_traces, Some(true));
    assert_eq!(config.telemetry.otel_metrics, Some(false));
    assert_eq!(config.telemetry.export_timeout_ms, Some(1500));
    assert_eq!(
        config.telemetry.service_environment,
        Some("test".to_owned())
    );
}

#[test]
fn remote_cli_subset_ignores_unrelated_invalid_local_overrides() {
    let pairs = [
        (RELAY_KNOWLEDGE_REMOTE_BASE_URL, "https://relay.example"),
        (RELAY_KNOWLEDGE_DATA_DIR, ""),
        (RELAY_KNOWLEDGE_EMBEDDING_DIMENSION, "0"),
        (RELAY_KNOWLEDGE_QOS_MAX_CONNECTIONS, "32"),
    ];
    let remote = RemoteCliEnvironmentConfig::from_pairs(PlatformKind::Unix, pairs)
        .expect("remote subset should ignore unrelated local settings");

    assert_eq!(
        remote.remote_cli.base_url,
        Some("https://relay.example".to_owned())
    );
    assert_eq!(remote.network.qos_max_connections, Some(32));

    let error = EnvironmentConfig::from_pairs(PlatformKind::Unix, pairs)
        .expect_err("full environment should still reject local settings");
    assert_eq!(error.variable, RELAY_KNOWLEDGE_DATA_DIR);
}

#[test]
fn remote_cli_subset_validates_network_overrides() {
    let error = RemoteCliEnvironmentConfig::from_pairs(
        PlatformKind::Unix,
        [
            (RELAY_KNOWLEDGE_REMOTE_BASE_URL, "https://relay.example"),
            (RELAY_KNOWLEDGE_QOS_MAX_CONNECTIONS, "0"),
        ],
    )
    .expect_err("remote subset should validate network settings");

    assert_eq!(error.variable, RELAY_KNOWLEDGE_QOS_MAX_CONNECTIONS);
    assert_eq!(error.kind, EnvErrorKind::ZeroValue);
}

#[test]
fn disabled_update_checks_ignore_invalid_interval_override() {
    let config = EnvironmentConfig::from_pairs(
        PlatformKind::Unix,
        [
            (RELAY_KNOWLEDGE_UPDATE_CHECK_ENABLED, "false"),
            (RELAY_KNOWLEDGE_UPDATE_CHECK_INTERVAL_MS, "0"),
        ],
    )
    .expect("disabled update checks should ignore unused interval settings");

    assert_eq!(config.updates.enabled, Some(false));
    assert_eq!(config.updates.check_interval_ms, None);
}

#[test]
fn invalid_telemetry_values_report_the_exact_variable() {
    let zero_timeout =
        EnvironmentConfig::from_pairs(PlatformKind::Unix, [(RELAY_OTEL_EXPORT_TIMEOUT_MS, "0")])
            .expect_err("zero otel timeout should fail");
    assert_eq!(zero_timeout.variable, RELAY_OTEL_EXPORT_TIMEOUT_MS);
    assert_eq!(zero_timeout.kind, EnvErrorKind::ZeroValue);

    let invalid_boolean =
        EnvironmentConfig::from_pairs(PlatformKind::Unix, [(RELAY_OTEL_TRACES, "yes?")])
            .expect_err("invalid otel boolean should fail");
    assert_eq!(invalid_boolean.variable, RELAY_OTEL_TRACES);
    assert_eq!(
        invalid_boolean.kind,
        EnvErrorKind::InvalidBoolean {
            value: "yes?".to_owned()
        }
    );
}

#[test]
fn indexed_repository_retention_limit_rejects_zero() {
    let error = EnvironmentConfig::from_pairs(
        PlatformKind::Unix,
        [(RELAY_KNOWLEDGE_CODE_INDEX_MAX_INDEXED_REPOSITORIES, "0")],
    )
    .expect_err("zero indexed repository retention limit should fail");

    assert_eq!(
        error.variable,
        RELAY_KNOWLEDGE_CODE_INDEX_MAX_INDEXED_REPOSITORIES
    );
    assert_eq!(error.kind, EnvErrorKind::ZeroValue);
}

#[test]
fn https_proxy_takes_precedence_over_http_proxy() {
    let config = EnvironmentConfig::from_pairs(
        PlatformKind::Unix,
        [
            (HTTP_PROXY, "http://http-proxy:8080"),
            (HTTPS_PROXY, "http://https-proxy:8080"),
            (NO_PROXY, "localhost"),
        ],
    )
    .expect("environment should parse");

    assert_eq!(
        config.network.proxy,
        Some("http://https-proxy:8080".to_owned())
    );
    assert_eq!(config.network.no_proxy, Some("localhost".to_owned()));
}

#[test]
fn windows_environment_names_are_case_insensitive() {
    let config = EnvironmentConfig::from_pairs(
        PlatformKind::Windows,
        [
            ("home", "/home/alice"),
            ("appdata", "/roaming"),
            ("localappdata", "/local"),
            ("relay_knowledge_http_bind", "localhost:8791"),
            ("ssl_verify", "off"),
        ],
    )
    .expect("environment should parse");

    assert_eq!(config.platform.home_dir, Some(PathBuf::from("/home/alice")));
    assert_eq!(config.platform.app_data, Some(PathBuf::from("/roaming")));
    assert_eq!(
        config.platform.local_app_data,
        Some(PathBuf::from("/local"))
    );
    assert_eq!(config.network.http_bind, Some("localhost:8791".to_owned()));
    assert_eq!(config.network.ssl_verify, Some(false));
}