Codex Handbook
core/src/tools/handlers/request_plugin_install_tests.rs 226 lines
use super::*;use crate::plugins::test_support::load_plugins_config;use crate::plugins::test_support::write_curated_plugin_sha;use crate::plugins::test_support::write_openai_curated_marketplace;use crate::plugins::test_support::write_plugins_feature_config;use codex_config::CONFIG_TOML_FILE;use codex_config::config_toml::ConfigToml;use codex_config::types::ToolSuggestConfig;use codex_config::types::ToolSuggestDisabledTool;use codex_config::types::ToolSuggestDiscoverable;use codex_config::types::ToolSuggestDiscoverableType;use codex_core_plugins::PluginInstallRequest;use codex_core_plugins::PluginsManager;use codex_core_plugins::startup_sync::curated_plugins_repo_path;use codex_rmcp_client::ElicitationResponse;use codex_tools::DiscoverablePluginInfo;use codex_utils_absolute_path::AbsolutePathBuf;use core_test_support::PathExt;use pretty_assertions::assert_eq;use rmcp::model::ElicitationAction;use serde_json::json;use tempfile::tempdir;#[tokio::test]async fn verified_plugin_install_completed_requires_installed_plugin() {    let codex_home = tempdir().expect("tempdir should succeed");    let curated_root = curated_plugins_repo_path(codex_home.path());    write_openai_curated_marketplace(&curated_root, &["sample"]);    write_curated_plugin_sha(codex_home.path());    write_plugins_feature_config(codex_home.path());    let config = load_plugins_config(codex_home.path()).await;    let plugins_manager = PluginsManager::new(codex_home.path().to_path_buf());    assert!(!verified_plugin_install_completed(        "sample@openai-curated",        &config,        &plugins_manager,    ));    plugins_manager        .install_plugin(PluginInstallRequest {            plugin_name: "sample".to_string(),            marketplace_path: AbsolutePathBuf::try_from(                curated_root.join(".agents/plugins/marketplace.json"),            )            .expect("marketplace path"),        })        .await        .expect("plugin should install");    let refreshed_config = load_plugins_config(codex_home.path()).await;    assert!(verified_plugin_install_completed(        "sample@openai-curated",        &refreshed_config,        &plugins_manager,    ));}#[test]fn remote_plugin_install_suggestions_skip_core_installed_verification() {    assert!(is_remote_plugin_install_suggestion(        "snowflake@openai-curated-remote"    ));    assert!(!is_remote_plugin_install_suggestion(        "snowflake@openai-curated"    ));    assert!(!is_remote_plugin_install_suggestion("Plugin_123"));}#[test]fn request_plugin_install_response_persists_only_decline_always_mode() {    assert!(request_plugin_install_response_requests_persistent_disable(        &ElicitationResponse {            action: ElicitationAction::Decline,            content: None,            meta: Some(json!({                REQUEST_PLUGIN_INSTALL_PERSIST_KEY: REQUEST_PLUGIN_INSTALL_PERSIST_ALWAYS_VALUE            })),        }    ));    assert!(        !request_plugin_install_response_requests_persistent_disable(&ElicitationResponse {            action: ElicitationAction::Accept,            content: None,            meta: Some(json!({                REQUEST_PLUGIN_INSTALL_PERSIST_KEY: REQUEST_PLUGIN_INSTALL_PERSIST_ALWAYS_VALUE            })),        })    );    assert!(        !request_plugin_install_response_requests_persistent_disable(&ElicitationResponse {            action: ElicitationAction::Decline,            content: None,            meta: Some(json!({ REQUEST_PLUGIN_INSTALL_PERSIST_KEY: "session" })),        })    );    assert!(        !request_plugin_install_response_requests_persistent_disable(&ElicitationResponse {            action: ElicitationAction::Decline,            content: None,            meta: None,        })    );}#[tokio::test]async fn persist_disabled_install_request_writes_connector_config() {    let codex_home = tempdir().expect("tempdir should succeed");    let tool = connector_tool("connector_calendar", "Google Calendar");    persist_disabled_install_request(&codex_home.path().abs(), &tool)        .await        .expect("persist connector disable");    let contents =        std::fs::read_to_string(codex_home.path().join(CONFIG_TOML_FILE)).expect("read config");    let parsed: ConfigToml = toml::from_str(&contents).expect("parse config");    assert_eq!(        parsed.tool_suggest,        Some(ToolSuggestConfig {            discoverables: Vec::new(),            disabled_tools: vec![ToolSuggestDisabledTool::connector("connector_calendar")],        })    );}#[tokio::test]async fn persist_disabled_install_request_writes_plugin_config() {    let codex_home = tempdir().expect("tempdir should succeed");    let tool = DiscoverableTool::Plugin(Box::new(DiscoverablePluginInfo {        id: "slack@openai-curated".to_string(),        remote_plugin_id: None,        name: "Slack".to_string(),        description: None,        has_skills: true,        mcp_server_names: Vec::new(),        app_connector_ids: Vec::new(),    }));    persist_disabled_install_request(&codex_home.path().abs(), &tool)        .await        .expect("persist plugin disable");    let contents =        std::fs::read_to_string(codex_home.path().join(CONFIG_TOML_FILE)).expect("read config");    let parsed: ConfigToml = toml::from_str(&contents).expect("parse config");    assert_eq!(        parsed.tool_suggest,        Some(ToolSuggestConfig {            discoverables: Vec::new(),            disabled_tools: vec![ToolSuggestDisabledTool::plugin("slack@openai-curated")],        })    );}#[tokio::test]async fn persist_disabled_install_request_dedupes_existing_disabled_tools() {    let codex_home = tempdir().expect("tempdir should succeed");    let tool = connector_tool("connector_calendar", "Google Calendar");    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[tool_suggest]discoverables = [  { type = "plugin", id = "sample@openai-curated" }][[tool_suggest.disabled_tools]]type = "connector"id = " connector_calendar "[[tool_suggest.disabled_tools]]type = "connector"id = "connector_calendar"[[tool_suggest.disabled_tools]]type = "connector"id = "   "[[tool_suggest.disabled_tools]]type = "plugin"id = "slack@openai-curated""#,    )    .expect("write config");    persist_disabled_install_request(&codex_home.path().abs(), &tool)        .await        .expect("persist connector disable");    let contents =        std::fs::read_to_string(codex_home.path().join(CONFIG_TOML_FILE)).expect("read config");    let parsed: ConfigToml = toml::from_str(&contents).expect("parse config");    assert_eq!(        parsed.tool_suggest,        Some(ToolSuggestConfig {            discoverables: vec![ToolSuggestDiscoverable {                kind: ToolSuggestDiscoverableType::Plugin,                id: "sample@openai-curated".to_string(),            }],            disabled_tools: vec![                ToolSuggestDisabledTool::connector("connector_calendar"),                ToolSuggestDisabledTool::plugin("slack@openai-curated"),            ],        })    );}fn connector_tool(id: &str, name: &str) -> DiscoverableTool {    DiscoverableTool::Connector(Box::new(AppInfo {        id: id.to_string(),        name: name.to_string(),        description: None,        logo_url: None,        logo_url_dark: None,        distribution_channel: None,        branding: None,        app_metadata: None,        labels: None,        install_url: None,        is_accessible: false,        is_enabled: true,        plugin_display_names: Vec::new(),    }))}