//! Integration tests for `mcptest scan`, the static config security scan.

use std::io::Write;

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

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

const INSECURE: &str = r#"{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/"],
      "autoApprove": ["*"]
    },
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {}
    },
    "payments": {
      "command": "node",
      "args": ["stripe-server.js"],
      "env": { "STRIPE_API_KEY": "sk-live-abcdef0123456789ABCDEF0123456789" }
    }
  }
}"#;

const CLEAN: &str = r#"{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "./workspace"]
    }
  }
}"#;

fn write_config(body: &str) -> tempfile::NamedTempFile {
    let mut f = tempfile::Builder::new()
        .suffix(".json")
        .tempfile()
        .expect("temp file");
    f.write_all(body.as_bytes()).expect("write");
    f
}

#[test]
fn reports_findings_and_exits_zero_without_fail_on() {
    let cfg = write_config(INSECURE);
    mcptest()
        .arg("scan")
        .arg(cfg.path())
        .assert()
        .success()
        .stdout(predicate::str::contains("CFG-001"))
        .stdout(predicate::str::contains("CFG-002"))
        .stdout(predicate::str::contains("[red]"));
}

#[test]
fn never_prints_the_literal_secret() {
    let cfg = write_config(INSECURE);
    mcptest()
        .arg("scan")
        .arg(cfg.path())
        .assert()
        .stdout(predicate::str::contains("sk-live-abcdef0123456789ABCDEF0123456789").not());
}

#[test]
fn fail_on_high_exits_nonzero_on_insecure_config() {
    let cfg = write_config(INSECURE);
    mcptest()
        .arg("scan")
        .arg(cfg.path())
        .arg("--fail-on")
        .arg("high")
        .assert()
        .failure()
        .code(1);
}

#[test]
fn clean_config_passes_fail_on_critical() {
    let cfg = write_config(CLEAN);
    mcptest()
        .arg("scan")
        .arg(cfg.path())
        .arg("--fail-on")
        .arg("critical")
        .assert()
        .success();
}

#[test]
fn json_format_emits_rule_ids() {
    let cfg = write_config(INSECURE);
    mcptest()
        .arg("scan")
        .arg(cfg.path())
        .arg("--format")
        .arg("json")
        .assert()
        .success()
        .stdout(predicate::str::contains("\"rule_id\": \"CFG-001\""));
}

#[test]
fn waivers_exclude_findings_from_the_gate() {
    let cfg = write_config(INSECURE);
    // Waive the critical secret and the high broad-filesystem finding; the
    // remaining findings are medium, so --fail-on high should now pass.
    let mut w = tempfile::Builder::new()
        .suffix(".yaml")
        .tempfile()
        .expect("temp file");
    w.write_all(
        b"waivers:\n  - rule_id: CFG-001\n    reason: placeholder secret in this fixture\n  - rule_id: CFG-002\n    reason: scoped at deploy time\n",
    )
    .expect("write");
    mcptest()
        .arg("scan")
        .arg(cfg.path())
        .arg("--fail-on")
        .arg("high")
        .arg("--waivers")
        .arg(w.path())
        .assert()
        .success()
        .stderr(predicate::str::contains("waived"));
}

#[test]
fn html_format_is_self_contained_and_redacts() {
    let cfg = write_config(INSECURE);
    mcptest()
        .arg("scan")
        .arg(cfg.path())
        .arg("--format")
        .arg("html")
        .assert()
        .success()
        .stdout(predicate::str::contains("<!DOCTYPE html>"))
        .stdout(predicate::str::contains("CFG-001"))
        .stdout(predicate::str::contains("sk-live-abcdef0123456789ABCDEF0123456789").not());
}

#[test]
fn sarif_format_is_sarif_210() {
    let cfg = write_config(INSECURE);
    mcptest()
        .arg("scan")
        .arg(cfg.path())
        .arg("--format")
        .arg("sarif")
        .assert()
        .success()
        .stdout(predicate::str::contains("\"version\": \"2.1.0\""))
        .stdout(predicate::str::contains("mcptest-config-scan"));
}
