Per ADR-006: stdio and HTTP transports are mutually exclusive because they have opposite stdout discipline (stdio: JSON-RPC sink; HTTP: log channel). Changes: - Add clap ArgGroup with multiple(false) to enforce --stdio XOR --bind - Default to stdio mode when neither flag is specified - Change --bind from required String to Option<String> - Add ADR-006 reference to help text and doc comments - Add unit tests for CLI argument validation Acceptance criteria: - pdftract mcp → launches in stdio mode (default) - pdftract mcp --stdio → launches in stdio mode - pdftract mcp --bind ADDR → launches in HTTP+SSE mode - pdftract mcp --stdio --bind ADDR → exits 2 with clap conflict error - pdftract mcp --help shows mutual exclusivity note - Unit test verifies ArgGroup conflict on dual-transport invocation Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
108 lines
3.8 KiB
Rust
108 lines
3.8 KiB
Rust
//! Unit tests for MCP CLI argument parsing.
|
|
//!
|
|
//! These tests verify that the CLI correctly enforces the mutual exclusion
|
|
//! between --stdio and --bind transport modes per ADR-006.
|
|
|
|
use std::process::{Command, Stdio};
|
|
|
|
/// Helper to get the pdftract binary path.
|
|
fn pdftract_bin() -> String {
|
|
env!("CARGO_BIN_EXE_pdftract").to_string()
|
|
}
|
|
|
|
/// Test that `pdftract mcp --stdio --bind` is rejected at parse time.
|
|
#[test]
|
|
fn test_stdio_and_bind_mutually_exclusive() {
|
|
let output = Command::new(pdftract_bin())
|
|
.arg("mcp")
|
|
.arg("--stdio")
|
|
.arg("--bind")
|
|
.arg("127.0.0.1:8080")
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::piped())
|
|
.output()
|
|
.expect("Failed to execute pdftract mcp --stdio --bind");
|
|
|
|
// Should fail with exit code 2 (clap's error exit code)
|
|
assert_eq!(output.status.code(), Some(2), "Expected exit code 2, got {:?}", output.status.code());
|
|
|
|
// Error message should mention both flags
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
assert!(stderr.contains("--stdio"), "Error message should mention --stdio");
|
|
assert!(stderr.contains("--bind"), "Error message should mention --bind");
|
|
assert!(stderr.contains("cannot be used"), "Error message should mention conflict");
|
|
}
|
|
|
|
/// Test that `pdftract mcp` (no flags) parses successfully.
|
|
#[test]
|
|
fn test_default_to_stdio() {
|
|
let output = Command::new(pdftract_bin())
|
|
.arg("mcp")
|
|
.arg("--help")
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::piped())
|
|
.output()
|
|
.expect("Failed to execute pdftract mcp --help");
|
|
|
|
// Should succeed
|
|
assert!(output.status.success(), "pdftract mcp --help should succeed");
|
|
|
|
// Help text should mention the default behavior
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
assert!(stdout.contains("default"), "Help should mention default transport mode");
|
|
assert!(stdout.contains("stdio"), "Help should mention stdio transport");
|
|
}
|
|
|
|
/// Test that `pdftract mcp --stdio` parses successfully.
|
|
#[test]
|
|
fn test_stdio_flag_valid() {
|
|
let output = Command::new(pdftract_bin())
|
|
.arg("mcp")
|
|
.arg("--stdio")
|
|
.arg("--help")
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::piped())
|
|
.output()
|
|
.expect("Failed to execute pdftract mcp --stdio --help");
|
|
|
|
// Note: --help overrides the subcommand, so this succeeds
|
|
// In actual use, --stdio would start the stdio server
|
|
assert!(output.status.success(), "pdftract mcp --stdio --help should succeed");
|
|
}
|
|
|
|
/// Test that `pdftract mcp --bind ADDR` parses successfully.
|
|
#[test]
|
|
fn test_bind_flag_valid() {
|
|
let output = Command::new(pdftract_bin())
|
|
.arg("mcp")
|
|
.arg("--bind")
|
|
.arg("127.0.0.1:9999")
|
|
.arg("--help")
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::piped())
|
|
.output()
|
|
.expect("Failed to execute pdftract mcp --bind ADDR --help");
|
|
|
|
// Note: --help overrides the subcommand, so this succeeds
|
|
// In actual use, --bind would start the HTTP server
|
|
assert!(output.status.success(), "pdftract mcp --bind ADDR --help should succeed");
|
|
}
|
|
|
|
/// Test that the help text mentions ADR-006 and the mutual exclusion rationale.
|
|
#[test]
|
|
fn test_help_mentions_adr_006() {
|
|
let output = Command::new(pdftract_bin())
|
|
.arg("mcp")
|
|
.arg("--help")
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::piped())
|
|
.output()
|
|
.expect("Failed to execute pdftract mcp --help");
|
|
|
|
assert!(output.status.success(), "pdftract mcp --help should succeed");
|
|
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
// Help text should mention ADR-006 and the rationale
|
|
assert!(stdout.contains("ADR-006"), "Help should mention ADR-006");
|
|
assert!(stdout.contains("mutually exclusive"), "Help should mention mutual exclusion");
|
|
}
|