//! Integration tests for `mcptest run --stdin` (WOR-1256).
//!
//! The piped suite must take the same loading path as a file-backed one
//! (schema validation included) and be mutually exclusive with a suite
//! path, whether positional or via the global `--config` flag.

use predicates::prelude::*;
use serde_json::Value;

/// Absolute path to the built `mcptest` binary under test.
fn mcptest_bin() -> String {
    assert_cmd::cargo::cargo_bin("mcptest")
        .to_string_lossy()
        .into_owned()
}

/// A one-test suite against a `mcptest mock` stdio server whose
/// `get_status` tool returns "status: degraded".
fn passing_suite(dir: &std::path::Path) -> String {
    let bin = mcptest_bin();
    let manifest = dir.join("server.yml");
    std::fs::write(
        &manifest,
        "mock_server:\n  name: status-service\n  tools:\n    - name: get_status\n      description: Return status. Read-only.\n      input_schema: { type: object, additionalProperties: false, properties: {} }\n      response:\n        content:\n          - type: text\n            text: 'status: degraded'\n",
    )
    .unwrap();
    format!(
        "servers:\n  status:\n    command: ['{bin}', 'mock', '--tools-from', '{manifest}']\ntools:\n  - name: status check\n    server: status\n    tool: get_status\n    args: {{}}\n    expect:\n      - target: \"result.content[0].text\"\n        matcher: {{ contains: \"degraded\" }}\n",
        manifest = manifest.display(),
    )
}

#[test]
fn stdin_run_pipes_a_suite_and_returns_a_verdict() {
    let dir = tempfile::tempdir().unwrap();
    let suite = passing_suite(dir.path());
    let assert = assert_cmd::Command::cargo_bin("mcptest")
        .unwrap()
        .args(["run", "--stdin", "--reporter", "json", "--output", "-"])
        .write_stdin(suite)
        .current_dir(dir.path())
        .assert()
        .success();
    let stdout = String::from_utf8(assert.get_output().stdout.clone()).unwrap();
    let report: Value = serde_json::from_str(&stdout).expect("stdout is a JSON report");
    assert_eq!(report["summary"]["total"], 1);
    assert_eq!(report["summary"]["passed"], 1);
    assert!(!report["run_id"].as_str().unwrap().is_empty());
}

#[test]
fn stdin_suite_still_goes_through_schema_validation() {
    let dir = tempfile::tempdir().unwrap();
    // `tools` must be a list; the schema gate must reject the piped doc
    // exactly like a file-backed one.
    assert_cmd::Command::cargo_bin("mcptest")
        .unwrap()
        .args(["run", "--stdin"])
        .write_stdin("servers: {}\ntools: not-a-list\n")
        .current_dir(dir.path())
        .assert()
        .code(2)
        .stderr(predicate::str::contains("config validation failed"));
}

#[test]
fn stdin_conflicts_with_a_positional_suite_path() {
    assert_cmd::Command::cargo_bin("mcptest")
        .unwrap()
        .args(["run", "--stdin", "suite.yml"])
        .assert()
        .failure()
        .stderr(predicate::str::contains("cannot be used"));
}

#[test]
fn stdin_conflicts_with_watch_mode() {
    assert_cmd::Command::cargo_bin("mcptest")
        .unwrap()
        .args(["--watch", "run", "--stdin"])
        .write_stdin("servers: {}\n")
        .assert()
        .code(2)
        .stderr(predicate::str::contains("cannot be combined with --watch"));
}

#[test]
fn stdin_conflicts_with_the_global_config_flag() {
    assert_cmd::Command::cargo_bin("mcptest")
        .unwrap()
        .args(["--config", "suite.yml", "run", "--stdin"])
        .write_stdin("servers: {}\n")
        .assert()
        .code(2)
        .stderr(predicate::str::contains("--stdin conflicts with --config"));
}
