Codex Handbook
codex-mcp/src/plugin_config_tests.rs 304 lines
use super::PluginMcpConfigParseOutcome;use super::PluginMcpServerParseError;use super::PluginMcpServerPlacement;use super::parse_plugin_mcp_config;use codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID;use codex_config::McpServerConfig;use codex_config::McpServerEnvVar;use codex_config::McpServerOAuthConfig;use codex_config::McpServerTransportConfig;use pretty_assertions::assert_eq;use std::collections::BTreeMap;use std::collections::HashMap;use std::path::Path;use std::path::PathBuf;fn plugin_root() -> PathBuf {    std::env::current_dir()        .expect("current directory")        .join("plugin-root")}fn stdio_server(    command: &str,    environment_id: &str,    cwd: &Path,    env_vars: Vec<McpServerEnvVar>,) -> McpServerConfig {    McpServerConfig {        transport: McpServerTransportConfig::Stdio {            command: command.to_string(),            args: Vec::new(),            env: None,            env_vars,            cwd: Some(cwd.to_path_buf()),        },        environment_id: environment_id.to_string(),        enabled: true,        required: false,        supports_parallel_tool_calls: false,        disabled_reason: None,        startup_timeout_sec: None,        tool_timeout_sec: None,        default_tools_approval_mode: None,        enabled_tools: None,        disabled_tools: None,        scopes: None,        oauth: None,        oauth_resource: None,        tools: HashMap::new(),    }}#[test]fn declared_placement_preserves_local_plugin_normalization() {    let plugin_root = plugin_root();    let expected_stdio = stdio_server(        "demo-mcp",        "configured-environment",        &plugin_root.join("scripts"),        Vec::new(),    );    let expected_http = McpServerConfig {        transport: McpServerTransportConfig::StreamableHttp {            url: "https://example.com/mcp".to_string(),            bearer_token_env_var: None,            http_headers: None,            env_http_headers: None,        },        environment_id: DEFAULT_MCP_SERVER_ENVIRONMENT_ID.to_string(),        enabled: true,        required: false,        supports_parallel_tool_calls: false,        disabled_reason: None,        startup_timeout_sec: None,        tool_timeout_sec: None,        default_tools_approval_mode: None,        enabled_tools: None,        disabled_tools: None,        scopes: None,        oauth: Some(McpServerOAuthConfig {            client_id: Some("client-id".to_string()),        }),        oauth_resource: None,        tools: HashMap::new(),    };    let outcome = parse_plugin_mcp_config(        &plugin_root,        r#"{            "demo": {                "type": "stdio",                "command": "demo-mcp",                "environment_id": "configured-environment",                "cwd": "scripts"            },            "hosted": {                "type": "http",                "url": "https://example.com/mcp",                "oauth": {"clientId": "client-id", "callbackPort": 9876}            }        }"#,        PluginMcpServerPlacement::Declared,    )    .expect("parse plugin MCP config");    assert_eq!(        outcome,        PluginMcpConfigParseOutcome {            servers: BTreeMap::from([                ("demo".to_string(), expected_stdio),                ("hosted".to_string(), expected_http),            ]),            errors: Vec::new(),        }    );}#[test]fn environment_placement_forces_authority_and_defaults_null_cwd() {    let plugin_root = plugin_root();    let outcome = parse_plugin_mcp_config(        &plugin_root,        r#"{            "$schema":"https://example.com/plugin-mcp.schema.json",            "mcpServers":{"demo":{                "command":"demo-mcp",                "environment_id":"local",                "cwd":null,                "env_vars":["EXECUTOR_TOKEN", {"name":"OTHER_TOKEN"}]            }}        }"#,        PluginMcpServerPlacement::Environment {            environment_id: "executor-1",        },    )    .expect("parse plugin MCP config");    assert_eq!(        outcome,        PluginMcpConfigParseOutcome {            servers: BTreeMap::from([(                "demo".to_string(),                stdio_server(                    "demo-mcp",                    "executor-1",                    &plugin_root,                    vec![                        McpServerEnvVar::Config {                            name: "EXECUTOR_TOKEN".to_string(),                            source: Some("remote".to_string()),                        },                        McpServerEnvVar::Config {                            name: "OTHER_TOKEN".to_string(),                            source: Some("remote".to_string()),                        },                    ],                ),            )]),            errors: Vec::new(),        }    );}#[test]fn environment_placement_resolves_relative_cwd_beneath_plugin_root() {    let plugin_root = plugin_root();    let outcome = parse_plugin_mcp_config(        &plugin_root,        r#"{"demo":{"command":"demo-mcp","cwd":"scripts"}}"#,        PluginMcpServerPlacement::Environment {            environment_id: "executor-1",        },    )    .expect("parse plugin MCP config");    assert_eq!(        outcome,        PluginMcpConfigParseOutcome {            servers: BTreeMap::from([(                "demo".to_string(),                stdio_server(                    "demo-mcp",                    "executor-1",                    &plugin_root.join("scripts"),                    Vec::new(),                ),            )]),            errors: Vec::new(),        }    );}#[test]fn environment_placement_rejects_relative_cwd_that_escapes_package() {    let plugin_root = plugin_root();    let outcome = parse_plugin_mcp_config(        &plugin_root,        r#"{"demo":{"command":"demo-mcp","cwd":"../outside"}}"#,        PluginMcpServerPlacement::Environment {            environment_id: "executor-1",        },    )    .expect("parse plugin MCP config");    assert_eq!(        outcome,        PluginMcpConfigParseOutcome {            servers: BTreeMap::new(),            errors: vec![PluginMcpServerParseError {                name: "demo".to_string(),                message: format!(                    "relative cwd `../outside` must remain within plugin root `{}`",                    plugin_root.display()                ),            }],        }    );}#[test]fn environment_placement_rejects_orchestrator_env_vars() {    let plugin_root = plugin_root();    let outcome = parse_plugin_mcp_config(        &plugin_root,        r#"{"demo":{"command":"demo-mcp","env_vars":[{"name":"TOKEN","source":"local"}]}}"#,        PluginMcpServerPlacement::Environment {            environment_id: "executor-1",        },    )    .expect("parse plugin MCP config");    assert_eq!(        outcome,        PluginMcpConfigParseOutcome {            servers: BTreeMap::new(),            errors: vec![PluginMcpServerParseError {                name: "demo".to_string(),                message:                    "env_vars entry `TOKEN` cannot use source `local` in an executor-owned plugin"                        .to_string(),            }],        }    );}#[test]fn local_environment_placement_preserves_local_env_vars() {    let plugin_root = plugin_root();    let outcome = parse_plugin_mcp_config(        &plugin_root,        r#"{"demo":{"command":"demo-mcp","env_vars":["TOKEN",{"name":"OTHER","source":"local"}]}}"#,        PluginMcpServerPlacement::Environment {            environment_id: DEFAULT_MCP_SERVER_ENVIRONMENT_ID,        },    )    .expect("parse plugin MCP config");    assert_eq!(        outcome,        PluginMcpConfigParseOutcome {            servers: BTreeMap::from([(                "demo".to_string(),                stdio_server(                    "demo-mcp",                    DEFAULT_MCP_SERVER_ENVIRONMENT_ID,                    &plugin_root,                    vec![                        McpServerEnvVar::Name("TOKEN".to_string()),                        McpServerEnvVar::Config {                            name: "OTHER".to_string(),                            source: Some("local".to_string()),                        },                    ],                ),            )]),            errors: Vec::new(),        }    );}#[test]fn local_environment_placement_rejects_remote_env_vars() {    let plugin_root = plugin_root();    let outcome = parse_plugin_mcp_config(        &plugin_root,        r#"{"demo":{"command":"demo-mcp","env_vars":[{"name":"TOKEN","source":"remote"}]}}"#,        PluginMcpServerPlacement::Environment {            environment_id: DEFAULT_MCP_SERVER_ENVIRONMENT_ID,        },    )    .expect("parse plugin MCP config");    assert_eq!(        outcome,        PluginMcpConfigParseOutcome {            servers: BTreeMap::new(),            errors: vec![PluginMcpServerParseError {                name: "demo".to_string(),                message: "env_vars entry `TOKEN` cannot use source `remote` in a local environment"                    .to_string(),            }],        }    );}