#![cfg(all(feature = "mcp", feature = "source-csv", feature = "sink-jsonl"))]
use assert_cmd::Command;
use serde_json::Value;
use std::fs;
use tempfile::TempDir;
fn mcp(args: &[&str], input: &str) -> Vec<Value> {
let out = Command::cargo_bin("faucet")
.unwrap()
.arg("mcp")
.args(args)
.write_stdin(input.to_string())
.assert()
.success()
.get_output()
.stdout
.clone();
String::from_utf8(out)
.unwrap()
.lines()
.filter(|l| !l.trim().is_empty())
.map(|l| serde_json::from_str(l).expect("valid JSON-RPC line"))
.collect()
}
#[test]
fn initialize_and_tools_list_readonly() {
let responses = mcp(
&[],
"{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{}}\n\
{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/list\"}\n",
);
assert_eq!(responses.len(), 2);
assert_eq!(responses[0]["result"]["protocolVersion"], "2024-11-05");
assert_eq!(responses[0]["result"]["serverInfo"]["name"], "faucet");
let names: Vec<String> = responses[1]["result"]["tools"]
.as_array()
.unwrap()
.iter()
.map(|t| t["name"].as_str().unwrap().to_string())
.collect();
assert!(names.contains(&"list_connectors".to_string()));
assert!(names.contains(&"validate_config".to_string()));
assert!(names.contains(&"preview".to_string()));
assert!(!names.contains(&"run_pipeline".to_string()));
}
#[test]
fn allow_mutations_exposes_run_pipeline() {
let responses = mcp(
&["--allow-mutations"],
"{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\"}\n",
);
let names: Vec<String> = responses[0]["result"]["tools"]
.as_array()
.unwrap()
.iter()
.map(|t| t["name"].as_str().unwrap().to_string())
.collect();
assert!(names.contains(&"run_pipeline".to_string()));
}
#[test]
fn validate_and_run_pipeline_end_to_end() {
let dir = TempDir::new().unwrap();
let csv = dir.path().join("in.csv");
let out = dir.path().join("out.jsonl");
fs::write(&csv, "id,name\n1,alice\n2,bob\n").unwrap();
let cfg = format!(
"version: 1\nname: mcp_e2e\npipeline:\n source:\n type: csv\n config:\n path: {}\n sink:\n type: jsonl\n config:\n path: {}\n",
csv.display(),
out.display()
);
let validate = serde_json::json!({
"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": { "name": "validate_config", "arguments": { "config": cfg } }
});
let run = serde_json::json!({
"jsonrpc": "2.0", "id": 2, "method": "tools/call",
"params": { "name": "run_pipeline", "arguments": { "config": cfg } }
});
let input = format!("{validate}\n{run}\n");
let responses = mcp(&["--allow-mutations"], &input);
assert_eq!(responses.len(), 2);
assert_eq!(responses[0]["result"]["isError"], false);
let vtext = responses[0]["result"]["content"][0]["text"]
.as_str()
.unwrap();
assert!(vtext.contains("\"valid\": true"));
assert_eq!(responses[1]["result"]["isError"], false);
let rtext = responses[1]["result"]["content"][0]["text"]
.as_str()
.unwrap();
assert!(rtext.contains("\"records_written\": 2"));
assert_eq!(fs::read_to_string(&out).unwrap().lines().count(), 2);
}
#[test]
fn run_pipeline_rejected_without_allow_mutations() {
let responses = mcp(
&[],
"{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\"name\":\"run_pipeline\",\"arguments\":{\"config\":\"version: 1\"}}}\n",
);
assert_eq!(responses[0]["result"]["isError"], true);
assert!(
responses[0]["result"]["content"][0]["text"]
.as_str()
.unwrap()
.contains("--allow-mutations")
);
}
#[test]
fn get_connector_schema_returns_json_schema() {
let responses = mcp(
&[],
"{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\"name\":\"get_connector_schema\",\"arguments\":{\"kind\":\"source\",\"name\":\"csv\"}}}\n",
);
assert_eq!(responses[0]["result"]["isError"], false);
let text = responses[0]["result"]["content"][0]["text"]
.as_str()
.unwrap();
assert!(text.contains("properties"));
}