grpctestify 1.8.6

gRPC testing utility written in Rust
Documentation
#![cfg(not(miri))]

use std::path::Path;
use std::process::{Command, Output};

fn get_binary() -> String {
    env!("CARGO_BIN_EXE_grpctestify").to_string()
}

fn run_cli(args: &[&str]) -> Output {
    run_cli_internal(args, None)
}

fn run_cli_internal(args: &[&str], path_override: Option<&Path>) -> Output {
    let binary = get_binary();
    let runner = std::env::var("CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUNNER")
        .ok()
        .or_else(|| std::env::var("CROSS_RUNNER").ok());

    let mut cmd = if let Some(runner) = runner {
        let mut parts = runner.split_whitespace();
        let prog = parts.next().expect("runner must not be empty");
        let mut c = Command::new(prog);
        c.args(parts).arg(&binary);
        c
    } else {
        Command::new(&binary)
    };

    if let Some(path) = path_override {
        let current_path = std::env::var("PATH").unwrap_or_default();
        let merged_path = format!("{}:{}", path.display(), current_path);
        cmd.env("PATH", merged_path);
    }

    cmd.current_dir(env!("CARGO_MANIFEST_DIR"))
        .args(args)
        .output()
        .expect("failed to run command")
}

#[cfg(unix)]
fn run_cli_with_path(args: &[&str], path: &Path) -> Output {
    run_cli_internal(args, Some(path))
}

#[cfg(unix)]
fn make_executable(path: &std::path::Path) {
    use std::os::unix::fs::PermissionsExt;
    let mut perms = std::fs::metadata(path).expect("metadata").permissions();
    perms.set_mode(0o755);
    std::fs::set_permissions(path, perms).expect("set perms");
}

#[test]
fn gen_grpcurl_defaults_to_stdout() {
    let out = run_cli(&[
        "gen",
        "grpcurl",
        "-plaintext",
        "-H",
        "x-api-key: wrong-key",
        "-d",
        "{\"action\":\"delete\"}",
        "localhost:4770",
        "auth.AuthService/CheckAccess",
    ]);

    assert!(
        out.status.success(),
        "stderr: {}",
        String::from_utf8_lossy(&out.stderr)
    );
    let stdout = String::from_utf8_lossy(&out.stdout);
    assert!(stdout.contains("--- ADDRESS ---\nlocalhost:4770"));
    assert!(stdout.contains("--- ENDPOINT ---\nauth.AuthService/CheckAccess"));
    assert!(stdout.contains("--- REQUEST_HEADERS ---\nx-api-key: wrong-key"));
    assert!(stdout.contains("--- REQUEST ---"));
}

#[test]
fn gen_grpcurl_does_not_emit_meta_or_plaintext_option() {
    let out = run_cli(&[
        "gen",
        "grpcurl",
        "-plaintext",
        "-d",
        "{\"name\":\"Alex\"}",
        "localhost:4770",
        "helloworld.Greeter/SayHello",
    ]);

    assert!(
        out.status.success(),
        "stderr: {}",
        String::from_utf8_lossy(&out.stderr)
    );

    let stdout = String::from_utf8_lossy(&out.stdout);
    assert!(!stdout.contains("--- META ---"));
    assert!(!stdout.contains("plaintext: true"));
}

#[test]
fn gen_grpcurl_places_options_after_endpoint() {
    let out = run_cli(&[
        "gen",
        "grpcurl",
        "-gzip",
        "-d",
        "{\"name\":\"Alex\"}",
        "localhost:4770",
        "helloworld.Greeter/SayHello",
    ]);

    assert!(
        out.status.success(),
        "stderr: {}",
        String::from_utf8_lossy(&out.stderr)
    );

    let stdout = String::from_utf8_lossy(&out.stdout);
    let endpoint_pos = stdout.find("--- ENDPOINT ---").expect("ENDPOINT section");
    let options_pos = stdout.find("--- OPTIONS ---").expect("OPTIONS section");
    let request_pos = stdout.find("--- REQUEST ---").expect("REQUEST section");

    assert!(endpoint_pos < options_pos);
    assert!(options_pos < request_pos);
    assert!(stdout.contains("compression: gzip"));
}

#[test]
fn gen_grpcurl_writes_to_output_file() {
    let dir = tempfile::tempdir().expect("tempdir");
    let out_file = dir.path().join("generated.gctf");

    let out = run_cli(&[
        "gen",
        "-o",
        out_file.to_str().expect("utf8 path"),
        "grpcurl",
        "-plaintext",
        "localhost:4770",
        "auth.AuthService/CheckAccess",
    ]);

    assert!(
        out.status.success(),
        "stderr: {}",
        String::from_utf8_lossy(&out.stderr)
    );
    assert!(String::from_utf8_lossy(&out.stdout).trim().is_empty());

    let generated = std::fs::read_to_string(&out_file).expect("read generated file");
    assert!(generated.contains("--- ADDRESS ---\nlocalhost:4770"));
    assert!(generated.contains("--- ENDPOINT ---\nauth.AuthService/CheckAccess"));
}

#[test]
fn gen_grpcurl_permuted_arguments_generate_stable_output() {
    let a = run_cli(&[
        "gen",
        "grpcurl",
        "-plaintext",
        "-H",
        "x-api-key: wrong-key",
        "-d",
        "{\"action\":\"delete\"}",
        "localhost:4770",
        "auth.AuthService/CheckAccess",
    ]);
    let b = run_cli(&[
        "gen",
        "grpcurl",
        "localhost:4770",
        "-d",
        "{\"action\":\"delete\"}",
        "auth.AuthService/CheckAccess",
        "-H",
        "x-api-key: wrong-key",
        "-plaintext",
    ]);

    assert!(a.status.success());
    assert!(b.status.success());
    assert_eq!(a.stdout, b.stdout);
}

#[test]
fn gen_grpcurl_writes_proto_section() {
    let out = run_cli(&[
        "gen",
        "grpcurl",
        "-import-path",
        "proto",
        "-import-path",
        "third_party",
        "-proto",
        "a.proto",
        "-proto",
        "b.proto",
        "localhost:4770",
        "auth.AuthService/CheckAccess",
    ]);

    assert!(
        out.status.success(),
        "stderr: {}",
        String::from_utf8_lossy(&out.stderr)
    );
    let stdout = String::from_utf8_lossy(&out.stdout);
    assert!(stdout.contains("--- PROTO ---"));
    assert!(stdout.contains("files: a.proto,b.proto"));
    assert!(stdout.contains("import_paths: proto,third_party"));
}

#[test]
fn gen_grpcurl_invalid_header_fails() {
    let out = run_cli(&[
        "gen",
        "grpcurl",
        "-H",
        "broken_header",
        "localhost:4770",
        "auth.AuthService/CheckAccess",
    ]);

    assert!(!out.status.success());
    let stderr = String::from_utf8_lossy(&out.stderr);
    assert!(stderr.contains("Invalid header"));
}

#[test]
#[cfg(unix)]
fn gen_grpcurl_execute_appends_response_section() {
    let dir = tempfile::tempdir().expect("tempdir");
    let grpcurl = dir.path().join("grpcurl");
    std::fs::write(
        &grpcurl,
        "#!/bin/sh\necho '{\"ok\":true,\"source\":\"fake\"}'\nexit 0\n",
    )
    .expect("write fake grpcurl");
    make_executable(&grpcurl);

    let out = run_cli_with_path(
        &[
            "gen",
            "grpcurl",
            "-e",
            "-plaintext",
            "localhost:4770",
            "auth.AuthService/CheckAccess",
        ],
        dir.path(),
    );

    assert!(
        out.status.success(),
        "stderr: {}",
        String::from_utf8_lossy(&out.stderr)
    );
    let stdout = String::from_utf8_lossy(&out.stdout);
    assert!(stdout.contains("--- RESPONSE ---"));
    assert!(stdout.contains("\"ok\": true"));
    assert!(stdout.contains("\"source\": \"fake\""));
}

#[test]
#[cfg(unix)]
fn gen_grpcurl_execute_appends_error_section_on_failure() {
    let dir = tempfile::tempdir().expect("tempdir");
    let grpcurl = dir.path().join("grpcurl");
    std::fs::write(
        &grpcurl,
        "#!/bin/sh\necho 'permission denied' 1>&2\nexit 7\n",
    )
    .expect("write fake grpcurl");
    make_executable(&grpcurl);

    let out = run_cli_with_path(
        &[
            "gen",
            "grpcurl",
            "-e",
            "localhost:4770",
            "auth.AuthService/CheckAccess",
        ],
        dir.path(),
    );

    assert!(
        out.status.success(),
        "stderr: {}",
        String::from_utf8_lossy(&out.stderr)
    );
    let stdout = String::from_utf8_lossy(&out.stdout);
    assert!(stdout.contains("--- ERROR ---"));
    assert!(stdout.contains("permission denied"));
}