Codex Handbook
app-server/tests/suite/v2/marketplace_remove.rs 115 lines
use std::time::Duration;use anyhow::Context;use anyhow::Result;use app_test_support::TestAppServer;use app_test_support::to_response;use codex_app_server_protocol::JSONRPCResponse;use codex_app_server_protocol::MarketplaceRemoveParams;use codex_app_server_protocol::MarketplaceRemoveResponse;use codex_app_server_protocol::RequestId;use codex_config::MarketplaceConfigUpdate;use codex_config::record_user_marketplace;use codex_core_plugins::installed_marketplaces::marketplace_install_root;use pretty_assertions::assert_eq;use tempfile::TempDir;use tokio::time::timeout;const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);fn configured_marketplace_update() -> MarketplaceConfigUpdate<'static> {    MarketplaceConfigUpdate {        last_updated: "2026-04-13T00:00:00Z",        last_revision: None,        source_type: "git",        source: "https://github.com/owner/repo.git",        ref_name: Some("main"),        sparse_paths: &[],    }}fn write_installed_marketplace(codex_home: &std::path::Path, marketplace_name: &str) -> Result<()> {    let root = marketplace_install_root(codex_home).join(marketplace_name);    std::fs::create_dir_all(root.join(".agents/plugins"))?;    std::fs::write(root.join(".agents/plugins/marketplace.json"), "{}")?;    Ok(())}fn canonicalize_path_with_existing_parent(path: &std::path::Path) -> Result<std::path::PathBuf> {    let parent = path        .parent()        .with_context(|| format!("path {} should have a parent", path.display()))?;    let file_name = path        .file_name()        .with_context(|| format!("path {} should have a file name", path.display()))?;    Ok(parent.canonicalize()?.join(file_name))}#[tokio::test]async fn marketplace_remove_deletes_config_and_installed_root() -> Result<()> {    let codex_home = TempDir::new()?;    record_user_marketplace(codex_home.path(), "debug", &configured_marketplace_update())?;    write_installed_marketplace(codex_home.path(), "debug")?;    let installed_root = marketplace_install_root(codex_home.path()).join("debug");    let mut mcp = TestAppServer::new(codex_home.path()).await?;    timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;    let request_id = mcp        .send_marketplace_remove_request(MarketplaceRemoveParams {            marketplace_name: "debug".to_string(),        })        .await?;    let response: JSONRPCResponse = timeout(        DEFAULT_TIMEOUT,        mcp.read_stream_until_response_message(RequestId::Integer(request_id)),    )    .await??;    let response: MarketplaceRemoveResponse = to_response(response)?;    assert_eq!(response.marketplace_name, "debug");    let removed_installed_root = response        .installed_root        .context("marketplace/remove should return removed installed root")?;    assert_eq!(        canonicalize_path_with_existing_parent(removed_installed_root.as_path())?,        canonicalize_path_with_existing_parent(&installed_root)?,    );    let config = std::fs::read_to_string(codex_home.path().join("config.toml"))?;    assert!(!config.contains("[marketplaces.debug]"));    assert!(        !marketplace_install_root(codex_home.path())            .join("debug")            .exists()    );    Ok(())}#[tokio::test]async fn marketplace_remove_rejects_unknown_marketplace() -> Result<()> {    let codex_home = TempDir::new()?;    let mut mcp = TestAppServer::new(codex_home.path()).await?;    timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;    let request_id = mcp        .send_marketplace_remove_request(MarketplaceRemoveParams {            marketplace_name: "debug".to_string(),        })        .await?;    let err = timeout(        DEFAULT_TIMEOUT,        mcp.read_stream_until_error_message(RequestId::Integer(request_id)),    )    .await??;    assert_eq!(err.error.code, -32600);    assert_eq!(        err.error.message,        "marketplace `debug` is not configured or installed",    );    Ok(())}