//! Verification suite for the central redaction policy.
//!
//! Why this file exists: the redaction library lives in `mcptest-core`, but
//! the contract that protects users is "no CLI surface leaks a secret-shaped
//! value." That contract can only be verified end-to-end. Every assertion
//! here runs the actual `mcptest` binary through `assert_cmd` and pins both
//! the negative claim (the raw secret must not appear) and the positive
//! claim (a `[REDACTED:<hint>]` token must appear in its place) so a future
//! reporter cannot silently regress one without flagging the other.

use assert_cmd::Command;
use predicates::prelude::*;
use tempfile::tempdir;

fn mcptest() -> Command {
    Command::cargo_bin("mcptest").expect("binary builds")
}

#[test]
fn run_verbose_does_not_leak_token_prefixed_var() {
    let dir = tempdir().unwrap();
    let secret = "ghp_abcdefghijklmnopqrstuvwx";

    mcptest()
        .current_dir(dir.path())
        .arg("--no-env-file")
        .arg("--verbose")
        .args(["--var", &format!("SECRET={secret}")])
        .arg("run")
        .assert()
        .success()
        .stdout(predicate::str::contains(secret).not());
}

#[test]
fn doctor_dotenv_token_value_renders_as_redacted_token() {
    let dir = tempdir().unwrap();
    let secret = "ghp_abcdefghijklmnop1234";
    std::fs::write(dir.path().join(".env"), format!("API_TOKEN={secret}\n")).unwrap();

    mcptest()
        .current_dir(dir.path())
        .arg("doctor")
        .assert()
        .success()
        .stdout(predicate::str::contains(secret).not())
        .stdout(predicate::str::contains("[REDACTED:token]"));
}

#[test]
fn doctor_dotenv_hex_blob_value_renders_as_redacted_length() {
    let dir = tempdir().unwrap();
    // 40-char hex string. Looks like a long API token.
    let secret = "a1b2c3d4e5f60718293a4b5c6d7e8f9012345678";
    assert_eq!(secret.len(), 40);
    std::fs::write(dir.path().join(".env"), format!("BUILD_SHA={secret}\n")).unwrap();

    mcptest()
        .current_dir(dir.path())
        .arg("doctor")
        .assert()
        .success()
        .stdout(predicate::str::contains(secret).not())
        .stdout(predicate::str::contains("[REDACTED:length=40]"));
}

#[test]
fn doctor_with_show_secrets_does_show_raw_value() {
    // The audit is one-way: redaction by default, raw on opt-in. This
    // pins the escape hatch so the redaction-by-default rule does not
    // accidentally swallow `--show-secrets`.
    let dir = tempdir().unwrap();
    let secret = "ghp_visiblevisible1234";
    std::fs::write(dir.path().join(".env"), format!("API_TOKEN={secret}\n")).unwrap();

    mcptest()
        .current_dir(dir.path())
        .arg("--show-secrets")
        .arg("doctor")
        .assert()
        .success()
        .stdout(predicate::str::contains(secret));
}

#[test]
#[ignore = "upload reporter does not exist yet (see )"]
fn upload_reporter_does_not_leak_token_in_json_payload() {
    // Placeholder: once the JSON / upload reporter lands,
    // exercise it here against a YAML test whose `headers:` carries a
    // `ghp_...` token and assert that the rendered payload contains
    // `[REDACTED:header]` rather than the raw value.
}

/// Write a tiny JSON `Report` to `dir/report.json` whose first test name
/// is the supplied token-shaped value. The fixture mirrors the shape that
/// `mcptest run` will eventually save so the assertions exercise the same
/// `mcptest report` dispatch path users hit.
fn write_token_named_report(dir: &std::path::Path, secret: &str) -> std::path::PathBuf {
    let body = format!(
        r#"{{
            "schema_version": "0.1",
            "mcptest_version": "0.0.0",
            "generated_at": "2026-05-15T22:14:03Z",
            "run_id": "01JCQQYV4S3H6KX7M8RN2T5XZA",
            "summary": {{
                "total": 1, "passed": 0, "failed": 1, "skipped": 0,
                "duration_ms": 50
            }},
            "tests": [{{
                "name": "{secret}",
                "status": "fail",
                "duration_ms": 50,
                "failure": {{
                    "message": "boom",
                    "diff": null
                }},
                "file": "tests/example.yml",
                "line": 1
            }}]
        }}"#
    );
    let path = dir.join("report.json");
    std::fs::write(&path, body).unwrap();
    path
}

#[test]
fn report_pretty_does_not_leak_token_shaped_test_name() {
    let dir = tempdir().unwrap();
    let secret = "ghp_abcdefghijklmnop1234";
    let path = write_token_named_report(dir.path(), secret);

    mcptest()
        .arg("report")
        .arg(&path)
        .args(["--format", "pretty"])
        .assert()
        .success()
        .stdout(predicate::str::contains(secret).not())
        .stdout(predicate::str::contains("[REDACTED:token]"));
}

#[test]
fn report_json_does_not_leak_token_shaped_test_name() {
    let dir = tempdir().unwrap();
    let secret = "ghp_abcdefghijklmnop1234";
    let path = write_token_named_report(dir.path(), secret);

    mcptest()
        .arg("report")
        .arg(&path)
        .args(["--format", "json"])
        .assert()
        .success()
        .stdout(predicate::str::contains(secret).not())
        .stdout(predicate::str::contains("[REDACTED:token]"));
}

#[test]
fn report_junit_does_not_leak_token_shaped_test_name() {
    let dir = tempdir().unwrap();
    let secret = "ghp_abcdefghijklmnop1234";
    let path = write_token_named_report(dir.path(), secret);

    mcptest()
        .arg("report")
        .arg(&path)
        .args(["--format", "junit"])
        .assert()
        .success()
        .stdout(predicate::str::contains(secret).not())
        .stdout(predicate::str::contains("[REDACTED:token]"));
}

#[test]
fn report_sarif_does_not_leak_token_shaped_test_name() {
    let dir = tempdir().unwrap();
    let secret = "ghp_abcdefghijklmnop1234";
    let path = write_token_named_report(dir.path(), secret);

    mcptest()
        .arg("report")
        .arg(&path)
        .args(["--format", "sarif"])
        .assert()
        .success()
        .stdout(predicate::str::contains(secret).not())
        .stdout(predicate::str::contains("[REDACTED:token]"));
}
