relay-knowledge 1.1.10

Graph-database-based knowledge graph project.
Documentation
use super::*;
use crate::project::PROJECT_NAME;

#[test]
fn cli_spec_uses_canonical_binary_name_and_kebab_case_command_paths() {
    let spec = cli_spec::cli_spec();
    let value = serde_json::to_value(spec).expect("CLI spec should serialize");
    let commands = value["commands"].as_array().expect("commands should exist");

    assert_eq!(value["binary"], PROJECT_NAME);
    assert!(commands.len() > 10);

    for command in commands {
        let path = command_path(command);
        let usage = command["usage"].as_str().expect("usage should be a string");

        for segment in &path {
            assert_cli_path_segment(segment);
        }
        assert_usage_invokes_path(usage, &path);

        for example in command["examples"]
            .as_array()
            .expect("examples should be an array")
        {
            assert_invocation_uses_project_name(
                example.as_str().expect("example should be a string"),
            );
        }
    }
}

#[test]
fn text_help_uses_canonical_cli_name_for_root_namespaces_and_commands() {
    let root = cli_spec::render_help(&[], OutputFormat::Text).expect("root help should render");
    let repo = cli_spec::render_help(&["repo".to_owned()], OutputFormat::Text)
        .expect("repo help should render");
    let query = cli_spec::render_help(&["repo".to_owned(), "query".to_owned()], OutputFormat::Text)
        .expect("repo query help should render");

    assert!(root.contains(&format!("Usage: {PROJECT_NAME} <command>")));
    assert!(root.contains(&format!(
        "Use `{PROJECT_NAME} help <command> --format json`"
    )));
    assert!(repo.contains(&format!("Usage: {PROJECT_NAME} repo <subcommand>")));
    assert!(query.contains(&format!("Usage: {PROJECT_NAME} repo query")));
}

#[test]
fn repo_index_worker_help_is_public_and_machine_readable() {
    let command = cli_spec::render_help(
        &["repo".to_owned(), "index-worker".to_owned()],
        OutputFormat::Json,
    )
    .expect("repo index-worker help should render");
    let namespace = cli_spec::render_help(&["repo".to_owned()], OutputFormat::Json)
        .expect("repo namespace help should render");
    let command_json: serde_json::Value =
        serde_json::from_str(command.trim()).expect("command help should be JSON");
    let namespace_json: serde_json::Value =
        serde_json::from_str(namespace.trim()).expect("namespace help should be JSON");

    assert_eq!(
        command_json["path"],
        serde_json::json!(["repo", "index-worker"])
    );
    assert_eq!(command_json["operation"], "code.repo.index_worker");
    assert_eq!(command_json["effect"], "writes-indexes");
    assert!(
        command_json["options"]
            .as_array()
            .expect("options")
            .iter()
            .any(|option| option["flag"] == "--task-id")
    );
    assert!(
        namespace_json["commands"]
            .as_array()
            .expect("namespace commands")
            .iter()
            .any(|command| command["path"] == serde_json::json!(["repo", "index-worker"]))
    );
}

#[test]
fn parse_diagnostics_use_canonical_cli_name_in_suggestions_and_usage() {
    let flag_style = CliCommand::parse(["--ingest", "--source", "docs", "--content", "x"])
        .expect_err("flag-style command should fail")
        .render_stderr();
    let typo = CliCommand::parse(["repo", "qurey", "core", "--query", "rust"])
        .expect_err("misspelled command should fail")
        .render_stderr();
    let json_error = CliCommand::parse(["--format", "json", "query", "--query", "SQLite"])
        .expect_err("removed query flag should fail")
        .render_stderr();
    let json: serde_json::Value =
        serde_json::from_str(&json_error).expect("diagnostic should be JSON");

    assert!(flag_style.contains(&format!("Try: {PROJECT_NAME} ingest")));
    assert!(typo.contains("did you mean 'repo query'"));
    assert!(typo.contains(&format!("Try: {PROJECT_NAME} repo query")));
    assert!(typo.contains(&format!("Usage: {PROJECT_NAME} repo <subcommand>")));
    assert_eq!(json["suggestion"], format!("{PROJECT_NAME} query SQLite"));
    assert_eq!(
        json["usage"],
        format!(
            "{PROJECT_NAME} query <text> [--source <scope>] [--limit <n>] [--freshness <policy>]"
        )
    );
}

fn command_path(command: &serde_json::Value) -> Vec<String> {
    command["path"]
        .as_array()
        .expect("path should be an array")
        .iter()
        .map(|segment| {
            segment
                .as_str()
                .expect("path segment should be a string")
                .to_owned()
        })
        .collect()
}

fn assert_cli_path_segment(segment: &str) {
    assert!(!segment.is_empty());
    assert!(!segment.starts_with('-'));
    assert!(!segment.ends_with('-'));
    assert!(!segment.contains("--"));
    assert!(
        segment
            .chars()
            .all(|character| character.is_ascii_lowercase()
                || character.is_ascii_digit()
                || character == '-')
    );
}

fn assert_usage_invokes_path(usage: &str, path: &[String]) {
    assert_invocation_uses_project_name(usage);

    let tokens = usage.split_whitespace().collect::<Vec<_>>();
    assert_eq!(tokens.first().copied(), Some(PROJECT_NAME));

    for (index, segment) in path.iter().enumerate() {
        let usage_segment = tokens
            .get(index + 1)
            .unwrap_or_else(|| panic!("usage is missing path segment '{segment}': {usage}"));
        assert!(
            usage_segment
                .split('|')
                .any(|candidate| candidate == segment.as_str()),
            "usage segment '{usage_segment}' should include path segment '{segment}' in {usage}"
        );
    }
}

fn assert_invocation_uses_project_name(invocation: &str) {
    assert!(
        invocation
            .split_whitespace()
            .any(|token| token == PROJECT_NAME),
        "invocation should include canonical CLI name '{PROJECT_NAME}': {invocation}"
    );
    assert!(!invocation.contains("relay_knowledge"));
    assert!(!invocation.contains("RelayKnowledge"));
}