use serde_json::{Value, json};
use super::Config;
type TestResult = Result<(), Box<dyn std::error::Error>>;
fn base() -> Value {
json!({
"listen":"127.0.0.1:0",
"allowed_origins":["https://agent.example"]
})
}
#[test]
fn a_config_naming_permitted_subjects_or_lys_userinfo_is_refused_so_it_cannot_run_open()
-> TestResult {
serde_json::from_value::<Config>(base())?.validate()?;
for (key, field) in [
("permitted_subjects", json!(["permitted"])),
(
"lys_userinfo",
json!("https://identity.example/oauth/userinfo"),
),
("sign_in", json!({"timeout_ms":1000,"max_in_flight":2})),
] {
let mut value = base();
value[key] = field;
let refused = serde_json::from_value::<Config>(value)
.err()
.ok_or_else(|| format!("a config naming {key} was taken"))?;
assert!(
refused
.to_string()
.contains(&format!("unknown field `{key}`")),
"{key}: {refused}"
);
}
Ok(())
}
#[test]
fn io_limits_have_independent_defaults_and_knobs() -> TestResult {
let mut value = base();
value["header_timeout_ms"] = json!(100);
value["body_timeout_ms"] = json!(150);
value["max_connections"] = json!(8);
value["resource_metadata_url"] = json!("https://identity.example/metadata");
let config: Config = serde_json::from_value(value)?;
config.validate()?;
assert_eq!(config.header_timeout_ms.get(), 100);
assert_eq!(config.body_timeout_ms.get(), 150);
assert_eq!(config.max_connections.get(), 8);
assert_eq!(
config.resource_metadata_url.as_deref(),
Some("https://identity.example/metadata")
);
let defaults: Config = serde_json::from_value(base())?;
defaults.validate()?;
assert_eq!(defaults.header_timeout_ms.get(), 2000);
assert_eq!(defaults.body_timeout_ms.get(), 5000);
assert_eq!(defaults.max_connections.get(), 64);
assert!(defaults.resource_metadata_url.is_none());
assert_eq!(defaults.path, "/mcp");
Ok(())
}
#[test]
fn unsafe_binding_urls_and_limits_refuse_by_name() -> TestResult {
let mut cases = Vec::new();
for (key, field) in [
("listen", json!("0.0.0.0:9000")),
("path", json!("mcp")),
("path", json!("/mcp/")),
("path", json!("/mcp//studio")),
("path", json!("/mcp?studio")),
("path", json!("/mcp studio")),
("allowed_origins", json!(["https://agent.example/path"])),
(
"resource_metadata_url",
json!("http://identity.example/metadata"),
),
(
"resource_metadata_url",
json!("https://user:secret@identity.example/metadata"),
),
(
"resource_metadata_url",
json!("https://identity.example/metadata\r\nInjected: true"),
),
] {
let mut value = base();
value[key] = field;
cases.push(value);
}
for value in cases {
let config: Config = serde_json::from_value(value)?;
assert_eq!(
config.validate().err().map(|error| error.code),
Some("mcp_http_config_invalid")
);
}
for key in ["max_connections", "header_timeout_ms", "body_timeout_ms"] {
let mut value = base();
value[key] = json!(0);
assert!(serde_json::from_value::<Config>(value).is_err());
}
Ok(())
}