//! Integration tests for the vacuous-matcher lint on the run path
//! (WOR-1294).
//!
//! A `contains` with an empty operand and a regex matching the empty
//! string can never fail, so the assertion checks nothing. The run path
//! lints the resolved suite after it parses and prints each finding to
//! stderr prefixed `lint:`; the run still executes unless `--deny-lints`
//! is set, in which case the process exits 2 before any server is
//! spawned. These tests drive a real run against `mcptest mock` over
//! stdio, mirroring the WOR-1280 expect-mapping suite.

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

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

/// Absolute path to the built `mcptest` binary, used as the mock server
/// command inside generated suites.
fn mcptest_bin() -> &'static str {
    env!("CARGO_BIN_EXE_mcptest")
}

/// Write a mock manifest with a single `ping` tool that returns a fixed
/// text block, and return the manifest path.
fn write_mock_manifest(dir: &std::path::Path) -> std::path::PathBuf {
    let manifest = r#"mock_server:
  name: lint-fixture
  tools:
    - name: ping
      description: "Return a fixed pong line."
      input_schema:
        type: object
      response:
        isError: false
        content:
          - type: text
            text: "pong alpha beta"
"#;
    let path = dir.join("mock.yml");
    std::fs::write(&path, manifest).expect("write mock manifest");
    path
}

/// Render a suite whose single tool test uses the given `expect:` block
/// (caller provides the YAML lines under `    expect:`) against the mock.
fn write_suite(dir: &std::path::Path, expect_block: &str) -> std::path::PathBuf {
    let manifest = write_mock_manifest(dir);
    let suite = format!(
        r#"servers:
  fixture:
    command: ['{bin}', 'mock', '--tools-from', '{manifest}']
tools:
  - name: pinged
    server: fixture
    tool: ping
    expect:
{expect_block}
"#,
        bin = mcptest_bin(),
        manifest = manifest.display(),
    );
    let path = dir.join("suite.yml");
    std::fs::write(&path, suite).expect("write suite");
    path
}

/// An expect block with one vacuous `contains: {}`, one vacuous regex,
/// and one real assertion so the test still passes when it runs.
fn vacuous_expect_block() -> &'static str {
    "      - target: result\n        matcher: { contains: {} }\n      - target: \"result.content[0].text\"\n        matcher: { regex: \".*\" }\n      - target: \"result.content[0].text\"\n        matcher: { contains: \"pong\" }\n"
}

#[test]
fn vacuous_matchers_warn_on_stderr_and_the_run_still_executes() {
    let dir = tempfile::tempdir().unwrap();
    let suite = write_suite(dir.path(), vacuous_expect_block());
    mcptest()
        .args(["run", "--config"])
        .arg(&suite)
        .assert()
        .code(0)
        .stdout(predicate::str::contains("1 passed"))
        .stderr(
            predicate::str::contains(
                "lint: test 'pinged' target 'result': contains {} matches every value; \
                 the assertion can never fail",
            )
            .and(predicate::str::contains("matches the empty string")),
        );
}

#[test]
fn deny_lints_exits_2_before_anything_runs() {
    let dir = tempfile::tempdir().unwrap();
    let suite = write_suite(dir.path(), vacuous_expect_block());
    mcptest()
        .args(["run", "--deny-lints", "--config"])
        .arg(&suite)
        .assert()
        .code(2)
        .stdout(predicate::str::contains("passed").not())
        .stderr(
            predicate::str::contains("lint: test 'pinged'")
                .and(predicate::str::contains("--deny-lints"))
                .and(predicate::str::contains("refusing to run")),
        );
}

#[test]
fn clean_suite_produces_no_lint_output_even_with_deny_lints() {
    let dir = tempfile::tempdir().unwrap();
    let suite = write_suite(
        dir.path(),
        "      - target: \"result.content[0].text\"\n        matcher: { contains: \"pong\" }\n      - target: \"result.content[0].text\"\n        matcher: { regex: \"pong \\\\w+\" }\n",
    );
    mcptest()
        .args(["run", "--deny-lints", "--config"])
        .arg(&suite)
        .assert()
        .code(0)
        .stdout(predicate::str::contains("1 passed"))
        .stderr(predicate::str::contains("lint:").not());
}
