//! Integration tests for the conformance lane of `mcptest run` (WOR-1484).
//!
//! A suite that declares a `conformance:` block has the SEP conformance probes
//! run as a lane during `mcptest run`: the lane connects the suite's server,
//! captures the MCP handshake evidence, runs the universal and extended probes,
//! and folds each probe into the single report and exit code alongside the tool
//! tests. These tests pair the lane with a clean `mcptest mock --tools-from`
//! server, which negotiates a protocol version, advertises a tools capability,
//! and serves tools/list, so every applicable MUST probe passes and a
//! well-behaved mock exits 0. One passing tool test keeps selection
//! non-empty so the run reaches the post-drive lanes (WOR-1490). Everything is
//! offline and key-free.

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 the generated suite.
fn mcptest_bin() -> &'static str {
    env!("CARGO_BIN_EXE_mcptest")
}

/// Write a clean mock manifest exposing one `echo` tool. `mcptest mock` requires
/// a `--tools-from` manifest (or a preset), so this gives the conformance lane a
/// well-behaved, protocol-correct server to probe.
fn write_manifest(dir: &std::path::Path) -> std::path::PathBuf {
    let manifest = 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"
"#;
    let path = dir.join("echo.yaml");
    std::fs::write(&path, manifest).expect("write mock manifest");
    path
}

/// Write a suite that runs a clean `mcptest mock --tools-from` server, asserts
/// one passing tool test against the `echo` tool so the run reaches the lanes,
/// and, when `conformance` is true, declares a `conformance:` block so the lane
/// runs the probes.
fn write_suite(dir: &std::path::Path, conformance: bool) -> std::path::PathBuf {
    let manifest = write_manifest(dir);
    let conformance_block = if conformance { "conformance: {}\n" } else { "" };
    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" }}
{conformance_block}"#,
        bin = mcptest_bin(),
        manifest = manifest.display(),
        conformance_block = conformance_block,
    );
    let path = dir.join("suite.yml");
    std::fs::write(&path, suite).expect("write suite");
    path
}

/// With a `conformance:` block, the lane probes the well-behaved built-in mock.
/// Every MUST probe passes, so the run exits 0; the `conformance/` rows appear
/// on stdout (including the universal handshake MUSTs) and the tool test still
/// passes.
#[test]
fn the_conformance_lane_probes_the_server_and_a_clean_mock_passes() {
    let dir = tempfile::tempdir().unwrap();
    let suite = write_suite(dir.path(), true);
    mcptest()
        .args(["run", "--config", suite.to_str().unwrap()])
        .assert()
        .success()
        .stdout(predicate::str::contains("conformance/initialize-handshake"))
        .stdout(predicate::str::contains(
            "conformance/protocol-version-negotiated",
        ))
        .stdout(predicate::str::contains("conformance/tools-list-served"));
}

/// Without a `conformance:` block, the lane is a no-op: the run sees only the
/// tool test, which passes, so the exit code is zero and no `conformance/` row
/// appears. This pins the opt-in: an existing suite without the block is
/// unchanged.
#[test]
fn no_conformance_block_leaves_the_run_untouched() {
    let dir = tempfile::tempdir().unwrap();
    let suite = write_suite(dir.path(), false);
    mcptest()
        .args(["run", "--config", suite.to_str().unwrap()])
        .assert()
        .success()
        .stdout(predicate::str::contains("conformance/").not());
}
