relay-knowledge 1.1.17

Graph-database-based knowledge graph project.
Documentation
//! Direct contracts for the remote CLI transport owner.

use super::*;
use crate::{
    api::{InterfaceKind, RequestContext},
    domain::{
        BusinessKnowledgeQueryKind, CodeQueryKind, CodebaseViewKind, FreshnessPolicy,
        SoftwareGlobalKind,
    },
    interfaces::cli::repo::view::RepoViewCommand,
};

#[test]
fn status_error_maps_http_429_to_qos_rejected() {
    let error = status_error(
        StatusCode::TOO_MANY_REQUESTS,
        std::borrow::Cow::Borrowed("request budget exhausted"),
    );

    assert_eq!(error.error_kind, ErrorKind::QosRejected);
    assert!(error.message.contains("request budget exhausted"));
}

#[test]
fn repository_update_is_remote_and_never_falls_back_to_local_state() {
    let action = CliAction::Repo(RepoCommand::Update {
        alias: "core".to_owned(),
        base_ref: None,
        head_ref: None,
    });

    assert!(supports(&action));
    assert!(blocks_local_fallback(&action));
}

#[test]
fn repository_index_forwards_historical_reuse_to_remote_request() {
    let request = remote_index_request("core", "HEAD", false, true, OutputFormat::Json)
        .expect("remote index request should map");

    assert!(request.reuse_historical);
    assert_eq!(request.mode, CodeIndexMode::Full);
}

#[tokio::test]
async fn every_repository_remote_command_rejects_an_empty_alias_before_transport() {
    let actions = vec![
        RepoCommand::Index {
            alias: String::new(),
            ref_selector: "HEAD".to_owned(),
            dry_run: false,
            reuse_historical: false,
        },
        RepoCommand::ScopePreview {
            alias: String::new(),
            ref_selector: "HEAD".to_owned(),
        },
        RepoCommand::Update {
            alias: String::new(),
            base_ref: Some("HEAD~1".to_owned()),
            head_ref: Some("HEAD".to_owned()),
        },
        RepoCommand::Query {
            alias: String::new(),
            query: "catalog schema".to_owned(),
            kind: CodeQueryKind::Hybrid,
            limit: 10,
            ref_selector: "HEAD".to_owned(),
            path_filters: Vec::new(),
            language_filters: Vec::new(),
            freshness: FreshnessPolicy::AllowStale,
            exclude_generated: true,
        },
        RepoCommand::Graph {
            alias: String::new(),
            focus_path: "src/lib.rs".to_owned(),
            depth: 2,
            ref_selector: "HEAD".to_owned(),
            path_filters: Vec::new(),
            node_limit: 50,
            edge_limit: 100,
        },
        RepoCommand::Context {
            alias: String::new(),
            query: "catalog schema".to_owned(),
            limit: 10,
            ref_selector: "HEAD".to_owned(),
            path_filters: Vec::new(),
            language_filters: Vec::new(),
            freshness: FreshnessPolicy::AllowStale,
            max_context_bytes: 16_384,
            include_code: true,
            exclude_generated: true,
        },
        RepoCommand::FeatureFlags {
            alias: String::new(),
            query: Some("catalog".to_owned()),
            limit: 10,
            ref_selector: "HEAD".to_owned(),
            path_filters: Vec::new(),
            language_filters: Vec::new(),
            freshness: FreshnessPolicy::AllowStale,
        },
        RepoCommand::FrameworkGraph {
            alias: String::new(),
            query: Some("router".to_owned()),
            frameworks: Vec::new(),
            kinds: Vec::new(),
            limit: 10,
            ref_selector: "HEAD".to_owned(),
            path_filters: Vec::new(),
            freshness: FreshnessPolicy::AllowStale,
        },
        RepoCommand::Impact {
            alias: String::new(),
            base_ref: "HEAD~1".to_owned(),
            head_ref: "HEAD".to_owned(),
            limit: 10,
        },
        RepoCommand::Report {
            alias: String::new(),
        },
        RepoCommand::Software {
            alias: String::new(),
            ref_selector: "HEAD".to_owned(),
            kind: SoftwareGlobalKind::All,
            freshness: FreshnessPolicy::AllowStale,
            limit: 10,
        },
        RepoCommand::Business {
            alias: String::new(),
            ref_selector: "HEAD".to_owned(),
            domain: Some("architecture".to_owned()),
            query: Some("catalog".to_owned()),
            kind: BusinessKnowledgeQueryKind::All,
            freshness: FreshnessPolicy::AllowStale,
            limit: 10,
        },
        RepoCommand::View(RepoViewCommand {
            alias: String::new(),
            kind: CodebaseViewKind::ArchitectureLayers,
            limit: 10,
            ref_selector: "HEAD".to_owned(),
            path_filters: Vec::new(),
            language_filters: Vec::new(),
            freshness: FreshnessPolicy::AllowStale,
            changed_paths: Vec::new(),
        }),
        RepoCommand::Status {
            alias: String::new(),
        },
    ];
    let context = RequestContext::with_ids(
        InterfaceKind::Cli,
        "req-empty-remote-alias",
        "trace-empty-remote-alias",
    );

    for command in actions {
        let error = run_remote(
            &NetworkEnvOverrides::default(),
            "http://127.0.0.1:9",
            &CliAction::Repo(command.clone()),
            context.clone(),
            OutputFormat::Json,
        )
        .await
        .expect_err("an empty repository alias must fail before transport");
        let rendered = error.render_stderr();
        assert!(
            rendered.contains("\"error_kind\":\"invalid_argument\""),
            "unexpected error for {command:?}: {rendered}"
        );
        assert!(
            rendered.contains("repository"),
            "repository identity should be named for {command:?}: {rendered}"
        );
    }
}

#[tokio::test]
async fn remote_command_families_map_connection_failures_without_local_fallback() {
    let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
        .await
        .expect("ephemeral listener should bind");
    let unavailable_address = listener
        .local_addr()
        .expect("ephemeral listener address should resolve");
    drop(listener);
    let base_url = format!("http://{unavailable_address}");
    let actions = vec![
        RepoCommand::Index {
            alias: "fixture".to_owned(),
            ref_selector: "HEAD".to_owned(),
            dry_run: true,
            reuse_historical: true,
        },
        RepoCommand::ScopePreview {
            alias: "fixture".to_owned(),
            ref_selector: "HEAD".to_owned(),
        },
        RepoCommand::Update {
            alias: "fixture".to_owned(),
            base_ref: Some("HEAD~1".to_owned()),
            head_ref: Some("HEAD".to_owned()),
        },
        RepoCommand::Graph {
            alias: "fixture".to_owned(),
            focus_path: "src/lib.rs".to_owned(),
            depth: 2,
            ref_selector: "HEAD".to_owned(),
            path_filters: vec!["src".to_owned()],
            node_limit: 50,
            edge_limit: 100,
        },
        RepoCommand::Context {
            alias: "fixture".to_owned(),
            query: "catalog schema".to_owned(),
            limit: 10,
            ref_selector: "HEAD".to_owned(),
            path_filters: Vec::new(),
            language_filters: Vec::new(),
            freshness: FreshnessPolicy::AllowStale,
            max_context_bytes: 16_384,
            include_code: true,
            exclude_generated: true,
        },
        RepoCommand::FeatureFlags {
            alias: "fixture".to_owned(),
            query: Some("catalog".to_owned()),
            limit: 10,
            ref_selector: "HEAD".to_owned(),
            path_filters: Vec::new(),
            language_filters: Vec::new(),
            freshness: FreshnessPolicy::AllowStale,
        },
        RepoCommand::FrameworkGraph {
            alias: "fixture".to_owned(),
            query: Some("router".to_owned()),
            frameworks: Vec::new(),
            kinds: Vec::new(),
            limit: 10,
            ref_selector: "HEAD".to_owned(),
            path_filters: Vec::new(),
            freshness: FreshnessPolicy::AllowStale,
        },
        RepoCommand::Impact {
            alias: "fixture".to_owned(),
            base_ref: "HEAD~1".to_owned(),
            head_ref: "HEAD".to_owned(),
            limit: 10,
        },
        RepoCommand::Report {
            alias: "fixture".to_owned(),
        },
        RepoCommand::Business {
            alias: "fixture".to_owned(),
            ref_selector: "HEAD".to_owned(),
            domain: Some("architecture".to_owned()),
            query: Some("catalog".to_owned()),
            kind: BusinessKnowledgeQueryKind::All,
            freshness: FreshnessPolicy::AllowStale,
            limit: 10,
        },
        RepoCommand::View(RepoViewCommand {
            alias: "fixture".to_owned(),
            kind: CodebaseViewKind::ArchitectureLayers,
            limit: 10,
            ref_selector: "HEAD".to_owned(),
            path_filters: Vec::new(),
            language_filters: Vec::new(),
            freshness: FreshnessPolicy::AllowStale,
            changed_paths: Vec::new(),
        }),
        RepoCommand::Status {
            alias: "fixture".to_owned(),
        },
    ];
    let context = RequestContext::with_ids(
        InterfaceKind::Cli,
        "req-remote-unavailable",
        "trace-remote-unavailable",
    );

    for command in actions {
        let error = run_remote(
            &NetworkEnvOverrides::default(),
            &base_url,
            &CliAction::Repo(command.clone()),
            context.clone(),
            OutputFormat::Json,
        )
        .await
        .expect_err("an unavailable service must not fall back to local repository state");
        let rendered = error.render_stderr();
        assert!(
            rendered.contains("\"error_kind\":\"storage_unavailable\""),
            "unexpected transport mapping for {command:?}: {rendered}"
        );
        assert!(
            rendered.contains("remote service request failed"),
            "transport context should be retained for {command:?}: {rendered}"
        );
    }
}

#[tokio::test]
async fn non_remote_actions_do_not_consume_network_capacity() {
    let context =
        RequestContext::with_ids(InterfaceKind::Cli, "req-local-action", "trace-local-action");
    let process_action = run_remote(
        &NetworkEnvOverrides::default(),
        "not a URL",
        &CliAction::Version,
        context.clone(),
        OutputFormat::Json,
    )
    .await
    .expect("a process-only action should bypass remote configuration");
    assert_eq!(process_action, None);

    let unsupported_repo_action = run_remote(
        &NetworkEnvOverrides::default(),
        "http://127.0.0.1:9",
        &CliAction::Repo(RepoCommand::Register {
            root_path: "/tmp/repository".to_owned(),
            alias: "fixture".to_owned(),
            path_filters: Vec::new(),
            language_filters: Vec::new(),
        }),
        context,
        OutputFormat::Json,
    )
    .await
    .expect("an unsupported remote repository action should not send a request");
    assert_eq!(unsupported_repo_action, None);
}

#[test]
fn remote_urls_are_normalized_and_repository_segments_are_encoded() {
    assert_eq!(
        normalize_base_url("  https://example.test/control///  ", OutputFormat::Json)
            .expect("HTTP base URL should normalize"),
        "https://example.test/control"
    );
    let repository = repository_url(
        "https://example.test/control",
        "org/repository",
        "scope/preview",
        OutputFormat::Json,
    )
    .expect("repository URL should build");
    assert_eq!(
        repository.as_str(),
        "https://example.test/control/api/v1/code/repositories/org%2Frepository/scope/preview"
    );
    let repositories = repositories_url("https://example.test/control", OutputFormat::Json)
        .expect("repository collection URL should build");
    assert_eq!(
        repositories.as_str(),
        "https://example.test/control/api/v1/code/repositories"
    );

    for invalid in [
        "relative/path",
        "ftp://example.test",
        "https://example.test?tenant=core",
        "https://example.test#fragment",
    ] {
        let error = normalize_base_url(invalid, OutputFormat::Json)
            .expect_err("invalid remote base URL should fail");
        assert!(
            error
                .render_stderr()
                .contains("\"error_kind\":\"invalid_argument\"")
        );
    }
    assert!(
        repository_url("https://example.test", "   ", "status", OutputFormat::Json,)
            .expect_err("blank aliases should fail")
            .render_stderr()
            .contains("remote repository alias must not be empty")
    );
}