nils-api-grpc 0.5.0

CLI crate for nils-api-grpc in the nils-cli workspace.
use std::path::{Path, PathBuf};

use nils_test_support::bin::resolve;
use nils_test_support::cmd::{CmdOptions, CmdOutput, run_with};
use nils_test_support::fs::{write_executable, write_json};
use pretty_assertions::assert_eq;
use tempfile::TempDir;

fn api_grpc_bin() -> PathBuf {
    resolve("api-grpc")
}

fn run_api_grpc(cwd: &Path, args: &[&str], envs: &[(&str, &str)]) -> CmdOutput {
    let mut options = CmdOptions::default().with_cwd(cwd);
    for key in [
        "GRPCURL_BIN",
        "GRPC_URL",
        "GRPC_ENV_DEFAULT",
        "GRPC_TOKEN_NAME",
        "GRPC_HISTORY_ENABLED",
        "GRPC_HISTORY_FILE",
        "GRPC_HISTORY_LOG_URL_ENABLED",
        "GRPC_JWT_VALIDATE_ENABLED",
        "ACCESS_TOKEN",
        "SERVICE_TOKEN",
        "HTTP_PROXY",
        "http_proxy",
        "HTTPS_PROXY",
        "https_proxy",
        "ALL_PROXY",
        "all_proxy",
    ] {
        options = options.with_env_remove(key);
    }
    options = options.with_env("NO_PROXY", "127.0.0.1,localhost");
    options = options.with_env("no_proxy", "127.0.0.1,localhost");

    for (k, v) in envs {
        options = options.with_env(k, v);
    }

    run_with(&api_grpc_bin(), args, &options)
}

fn write_health_request(path: &Path, with_expect: bool) {
    let expect = if with_expect {
        serde_json::json!({"status": 0, "jq": ".ok == true"})
    } else {
        serde_json::json!({"status": 0})
    };
    write_json(
        path,
        &serde_json::json!({
            "method": "health.HealthService/Check",
            "body": {"service":"payments"},
            "expect": expect
        }),
    );
}

#[test]
fn call_success_prints_response_and_writes_history() {
    let tmp = TempDir::new().expect("tmp");
    let root = tmp.path();
    std::fs::create_dir_all(root.join("setup/grpc")).expect("mkdir setup");
    std::fs::create_dir_all(root.join("requests")).expect("mkdir requests");

    write_health_request(&root.join("requests/health.grpc.json"), true);

    let script = root.join("grpcurl-ok.sh");
    write_executable(&script, "#!/bin/sh\necho '{\"ok\":true}'\nexit 0\n");

    let script_str = script.to_string_lossy().to_string();
    let out = run_api_grpc(
        root,
        &[
            "call",
            "--config-dir",
            "setup/grpc",
            "--url",
            "127.0.0.1:50051",
            "requests/health.grpc.json",
        ],
        &[("GRPCURL_BIN", &script_str)],
    );

    assert_eq!(out.code, 0, "stderr={}", out.stderr_text());
    assert!(out.stdout_text().contains("\"ok\":true"));

    let history = std::fs::read_to_string(root.join("setup/grpc/.grpc_history")).expect("history");
    assert!(history.contains("api-grpc call"));
    assert!(history.contains("--config-dir 'setup/grpc'"));
    assert!(history.contains("--url '127.0.0.1:50051'"));
    assert!(history.contains("requests/health.grpc.json"));
}

#[test]
fn history_tail_command_only_omits_metadata_lines() {
    let tmp = TempDir::new().expect("tmp");
    let root = tmp.path();
    let setup = root.join("setup/grpc");
    std::fs::create_dir_all(&setup).expect("mkdir setup");

    std::fs::write(
        setup.join(".grpc_history"),
        "# stamp exit=0 setup_dir=.\napi-grpc call \\\n  --config-dir 'setup/grpc' \\\n  requests/one.grpc.json \\\n| jq .\n\n# stamp exit=0 setup_dir=.\napi-grpc call \\\n  --config-dir 'setup/grpc' \\\n  requests/two.grpc.json \\\n| jq .\n\n",
    )
    .expect("write history");

    let out = run_api_grpc(
        root,
        &[
            "history",
            "--config-dir",
            "setup/grpc",
            "--tail",
            "1",
            "--command-only",
        ],
        &[],
    );
    assert_eq!(out.code, 0, "stderr={}", out.stderr_text());
    assert!(out.stdout_text().contains("requests/two.grpc.json"));
    assert!(!out.stdout_text().contains("stamp exit"));
}

#[test]
fn call_expect_failure_non_json_prints_response_preview_to_stderr() {
    let tmp = TempDir::new().expect("tmp");
    let root = tmp.path();
    std::fs::create_dir_all(root.join("setup/grpc")).expect("mkdir setup");
    std::fs::create_dir_all(root.join("requests")).expect("mkdir requests");

    write_health_request(&root.join("requests/fail.grpc.json"), true);

    let script = root.join("grpcurl-text.sh");
    write_executable(&script, "#!/bin/sh\necho 'boom-body'\nexit 0\n");
    let script_str = script.to_string_lossy().to_string();

    let out = run_api_grpc(
        root,
        &[
            "call",
            "--config-dir",
            "setup/grpc",
            "--url",
            "127.0.0.1:50051",
            "--no-history",
            "requests/fail.grpc.json",
        ],
        &[("GRPCURL_BIN", &script_str)],
    );
    assert_eq!(out.code, 1);
    let stderr = out.stderr_text();
    assert!(stderr.contains("gRPC expect.jq requires a JSON response body"));
    assert!(stderr.contains("Response body (non-JSON; first 8192 bytes):"));
    assert!(stderr.contains("boom-body"));
}

#[test]
fn report_run_writes_markdown_report() {
    let tmp = TempDir::new().expect("tmp");
    let root = tmp.path();
    std::fs::create_dir_all(root.join("setup/grpc")).expect("mkdir setup");
    std::fs::create_dir_all(root.join("requests")).expect("mkdir requests");
    std::fs::create_dir_all(root.join("out")).expect("mkdir out");

    write_health_request(&root.join("requests/health.grpc.json"), true);
    let script = root.join("grpcurl-ok.sh");
    write_executable(&script, "#!/bin/sh\necho '{\"ok\":true}'\nexit 0\n");
    let script_str = script.to_string_lossy().to_string();

    let out = run_api_grpc(
        root,
        &[
            "report",
            "--case",
            "grpc-health",
            "--request",
            "requests/health.grpc.json",
            "--run",
            "--url",
            "127.0.0.1:50051",
            "--config-dir",
            "setup/grpc",
            "--out",
            "out/grpc-health.md",
        ],
        &[("GRPCURL_BIN", &script_str)],
    );

    assert_eq!(out.code, 0, "stderr={}", out.stderr_text());
    assert!(out.stdout_text().contains("out/grpc-health.md"));

    let markdown = std::fs::read_to_string(root.join("out/grpc-health.md")).expect("report file");
    assert!(markdown.contains("Test Case: grpc-health"));
    assert!(markdown.contains("Result: PASS"));
    assert!(markdown.contains("api-grpc call"));
    assert!(markdown.contains("### Assertions"));
}

#[test]
fn report_from_cmd_with_response_file_generates_report() {
    let tmp = TempDir::new().expect("tmp");
    let root = tmp.path();
    std::fs::create_dir_all(root.join("setup/grpc")).expect("mkdir setup");
    std::fs::create_dir_all(root.join("requests")).expect("mkdir requests");
    std::fs::create_dir_all(root.join("responses")).expect("mkdir responses");
    std::fs::create_dir_all(root.join("out")).expect("mkdir out");

    write_health_request(&root.join("requests/health.grpc.json"), true);
    std::fs::write(root.join("responses/health.json"), "{\"ok\":true}\n").expect("response file");

    let snippet = "api-grpc call --config-dir setup/grpc --env local requests/health.grpc.json";
    let out = run_api_grpc(
        root,
        &[
            "report-from-cmd",
            "--response",
            "responses/health.json",
            "--out",
            "out/from-cmd.md",
            snippet,
        ],
        &[],
    );
    assert_eq!(out.code, 0, "stderr={}", out.stderr_text());

    let markdown = std::fs::read_to_string(root.join("out/from-cmd.md")).expect("report file");
    assert!(markdown.contains("Test Case: health"));
    assert!(markdown.contains("Result: (response provided; request not executed)"));
    assert!(markdown.contains("### gRPC Request"));
}