Codex Handbook
plugin/src/provider_tests.rs 126 lines
use super::PluginResourceLocator;use super::ResolvedPlugin;use super::ResolvedPluginError;use crate::manifest::PluginManifest;use crate::manifest::PluginManifestHooks;use crate::manifest::PluginManifestInterface;use crate::manifest::PluginManifestPaths;use codex_utils_absolute_path::AbsolutePathBuf;use pretty_assertions::assert_eq;fn absolute(path: impl AsRef<std::path::Path>) -> AbsolutePathBuf {    AbsolutePathBuf::from_absolute_path_checked(path.as_ref()).expect("absolute test path")}fn resource(environment_id: &str, path: AbsolutePathBuf) -> PluginResourceLocator {    PluginResourceLocator::Environment {        environment_id: environment_id.to_string(),        path,    }}#[test]fn environment_descriptor_binds_every_manifest_resource() {    let root = absolute(std::env::current_dir().expect("cwd").join("plugin-root"));    let manifest_path = root.join(".codex-plugin/plugin.json");    let skills = root.join("skills");    let mcp_servers = root.join(".mcp.json");    let apps = root.join(".app.json");    let hooks = root.join("hooks/hooks.json");    let composer_icon = root.join("assets/composer.svg");    let logo = root.join("assets/logo.svg");    let screenshot = root.join("assets/screenshot.png");    let manifest = PluginManifest {        name: "demo".to_string(),        version: None,        description: None,        keywords: Vec::new(),        paths: PluginManifestPaths {            skills: Some(skills.clone()),            mcp_servers: Some(mcp_servers.clone()),            apps: Some(apps.clone()),            hooks: Some(PluginManifestHooks::Paths(vec![hooks.clone()])),        },        interface: Some(PluginManifestInterface {            composer_icon: Some(composer_icon.clone()),            logo: Some(logo.clone()),            screenshots: vec![screenshot.clone()],            ..PluginManifestInterface::default()        }),    };    let plugin = ResolvedPlugin::from_environment(        "selected-demo".to_string(),        "executor-1".to_string(),        root,        manifest_path.clone(),        manifest,    )    .expect("valid descriptor");    assert_eq!(        plugin.manifest_path(),        &resource("executor-1", manifest_path)    );    assert_eq!(        plugin.manifest(),        &PluginManifest {            name: "demo".to_string(),            version: None,            description: None,            keywords: Vec::new(),            paths: PluginManifestPaths {                skills: Some(resource("executor-1", skills)),                mcp_servers: Some(resource("executor-1", mcp_servers)),                apps: Some(resource("executor-1", apps)),                hooks: Some(PluginManifestHooks::Paths(vec![resource(                    "executor-1",                    hooks,                )])),            },            interface: Some(PluginManifestInterface {                composer_icon: Some(resource("executor-1", composer_icon)),                logo: Some(resource("executor-1", logo)),                screenshots: vec![resource("executor-1", screenshot)],                ..PluginManifestInterface::default()            }),        }    );}#[test]fn environment_descriptor_rejects_resources_outside_package_root() {    let cwd = std::env::current_dir().expect("cwd");    let root = absolute(cwd.join("plugin-root"));    let outside = absolute(cwd.join("outside/.mcp.json"));    let manifest = PluginManifest {        name: "demo".to_string(),        version: None,        description: None,        keywords: Vec::new(),        paths: PluginManifestPaths {            skills: None,            mcp_servers: Some(outside.clone()),            apps: None,            hooks: None,        },        interface: None,    };    let err = ResolvedPlugin::from_environment(        "selected-demo".to_string(),        "executor-1".to_string(),        root.clone(),        root.join(".codex-plugin/plugin.json"),        manifest,    )    .expect_err("outside resource should fail");    assert_eq!(        err,        ResolvedPluginError::ResourceOutsideRoot {            root,            path: outside,        }    );}