//! Integration tests for the declarable `coverage:` suite block (WOR-1488).
//!
//! A suite can declare its own coverage gate (minimum percentages per
//! dimension) instead of passing `--coverage`/`--coverage-threshold`. Declaring
//! the block also enables coverage instrumentation, so a single `mcptest run`
//! measures coverage and gates on it. These tests are offline and key-free: the
//! server is an `mcptest mock` and each suite pairs a tool test with a
//! `coverage:` block so the run drives the tool and folds the coverage verdict
//! into the single report and exit code.

use assert_cmd::Command;

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

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

/// Write a mock manifest. `extra_tool` adds a second, never-called tool so the
/// run's tool coverage falls to 50% and a `tools: 100` gate trips.
fn write_manifest(dir: &std::path::Path, extra_tool: bool) -> std::path::PathBuf {
    let mut manifest = String::from(
        r#"mock_server:
  name: echo
  tools:
    - name: echo
      description: "Echo a fixed line."
      input_schema:
        type: object
      response:
        isError: false
        content:
          - type: text
            text: "hello"
"#,
    );
    if extra_tool {
        manifest.push_str(
            r#"    - name: unused
      description: "A declared tool the suite never calls."
      input_schema:
        type: object
      response:
        isError: false
        content:
          - type: text
            text: "unused"
"#,
        );
    }
    let path = dir.join("echo.yaml");
    std::fs::write(&path, manifest).expect("write mock manifest");
    path
}

/// Render a suite with one tool test calling `echo` plus a `coverage:` block.
fn write_suite(
    dir: &std::path::Path,
    extra_tool: bool,
    coverage_block: &str,
) -> std::path::PathBuf {
    let manifest = write_manifest(dir, extra_tool);
    let suite = format!(
        r#"servers:
  mock:
    command: ['{bin}', 'mock', '--tools-from', '{manifest}']
tools:
  - name: echo round-trips
    server: mock
    tool: echo
    expect:
      - target: result.content[0].text
        matcher: {{ exact: "hello" }}
{coverage_block}"#,
        bin = mcptest_bin(),
        manifest = manifest.display(),
    );
    let path = dir.join("suite.yml");
    std::fs::write(&path, suite).expect("write suite");
    path
}

/// The mock declares only `echo` and the suite calls it, so tool coverage is
/// 100%. A `coverage: { tools: 100 }` gate is met, so the run exits 0 with no
/// `--coverage` flag: the suite block enabled instrumentation and the gate on
/// its own.
#[test]
fn a_met_coverage_block_passes_the_run_without_the_flag() {
    let dir = tempfile::tempdir().unwrap();
    let suite = write_suite(dir.path(), false, "coverage:\n  tools: 100\n");
    mcptest()
        .args(["run", "--config", suite.to_str().unwrap()])
        .assert()
        .success();
}

/// The mock declares `echo` and `unused` but the suite calls only `echo`, so
/// tool coverage is 50%. A `coverage: { tools: 100 }` gate is unmet, so the run
/// exits with the coverage-floor code (6) even though the tool test itself
/// passed.
#[test]
fn an_unmet_coverage_block_gates_the_run_with_the_coverage_exit_code() {
    let dir = tempfile::tempdir().unwrap();
    let suite = write_suite(dir.path(), true, "coverage:\n  tools: 100\n");
    mcptest()
        .args(["run", "--config", suite.to_str().unwrap()])
        .assert()
        .code(6);
}

/// An empty `coverage: {}` block enables instrumentation but gates no
/// dimension, so even with an uncalled tool the run still passes.
#[test]
fn an_empty_coverage_block_measures_without_gating() {
    let dir = tempfile::tempdir().unwrap();
    let suite = write_suite(dir.path(), true, "coverage: {}\n");
    mcptest()
        .args(["run", "--config", suite.to_str().unwrap()])
        .assert()
        .success();
}
