Codex Handbook
core-plugins/src/test_support.rs 165 lines
use std::fs;use std::path::Path;use crate::OPENAI_API_CURATED_MARKETPLACE_NAME;use crate::OPENAI_CURATED_MARKETPLACE_NAME;use crate::PluginsConfigInput;use codex_config::LoaderOverrides;use codex_config::NoopThreadConfigLoader;use codex_config::loader::load_config_layers_state;use codex_exec_server::LOCAL_FS;use codex_utils_absolute_path::AbsolutePathBuf;use toml::Value;pub(crate) const TEST_CURATED_PLUGIN_SHA: &str = "0123456789abcdef0123456789abcdef01234567";pub(crate) const TEST_CURATED_PLUGIN_CACHE_VERSION: &str = "01234567";pub(crate) fn write_file(path: &Path, contents: &str) {    fs::create_dir_all(path.parent().expect("file should have a parent")).unwrap();    fs::write(path, contents).unwrap();}pub(crate) fn write_curated_plugin(root: &Path, plugin_name: &str) {    let plugin_root = root.join("plugins").join(plugin_name);    write_file(        &plugin_root.join(".codex-plugin/plugin.json"),        &format!(            r#"{{  "name": "{plugin_name}",  "description": "Plugin that includes skills, MCP servers, and app connectors"}}"#        ),    );    write_file(        &plugin_root.join("skills/SKILL.md"),        "---\nname: sample\ndescription: sample\n---\n",    );    write_file(        &plugin_root.join(".mcp.json"),        r#"{  "mcpServers": {    "sample-docs": {      "type": "http",      "url": "https://sample.example/mcp"    }  }}"#,    );    write_file(        &plugin_root.join(".app.json"),        r#"{  "apps": {    "calendar": {      "id": "connector_calendar"    }  }}"#,    );}pub(crate) fn write_openai_curated_marketplace(root: &Path, plugin_names: &[&str]) {    write_curated_marketplace(        root,        "marketplace.json",        OPENAI_CURATED_MARKETPLACE_NAME,        /*display_name*/ None,        plugin_names,    );}pub(crate) fn write_openai_api_curated_marketplace(root: &Path, plugin_names: &[&str]) {    write_curated_marketplace(        root,        "api_marketplace.json",        OPENAI_API_CURATED_MARKETPLACE_NAME,        Some("OpenAI Curated"),        plugin_names,    );}fn write_curated_marketplace(    root: &Path,    manifest_name: &str,    marketplace_name: &str,    display_name: Option<&str>,    plugin_names: &[&str],) {    let plugins = plugin_names        .iter()        .map(|plugin_name| {            format!(                r#"{{      "name": "{plugin_name}",      "source": {{        "source": "local",        "path": "./plugins/{plugin_name}"      }}    }}"#            )        })        .collect::<Vec<_>>()        .join(",\n");    let interface = display_name        .map(|display_name| {            format!(                r#"  "interface": {{    "displayName": "{display_name}"  }},"#            )        })        .unwrap_or_default();    write_file(        &root.join(".agents/plugins").join(manifest_name),        &format!(            r#"{{  "name": "{marketplace_name}",{interface}  "plugins": [{plugins}  ]}}"#        ),    );    for plugin_name in plugin_names {        write_curated_plugin(root, plugin_name);    }}pub(crate) fn write_curated_plugin_sha_with(codex_home: &Path, sha: &str) {    write_file(&codex_home.join(".tmp/plugins.sha"), &format!("{sha}\n"));}pub(crate) async fn load_plugins_config(codex_home: &Path, cwd: &Path) -> PluginsConfigInput {    let codex_home = AbsolutePathBuf::try_from(codex_home).expect("codex home should be absolute");    let cwd = AbsolutePathBuf::try_from(cwd).expect("cwd should be absolute");    let config_layer_stack = load_config_layers_state(        LOCAL_FS.as_ref(),        codex_home.as_path(),        Some(cwd),        &[],        LoaderOverrides::without_managed_config_for_tests(),        &NoopThreadConfigLoader,    )    .await    .expect("config should load");    let effective_config = config_layer_stack.effective_config();    PluginsConfigInput::new(        config_layer_stack,        feature_enabled(&effective_config, "plugins", /*default_enabled*/ true),        feature_enabled(            &effective_config,            "remote_plugin",            /*default_enabled*/ false,        ),        "https://chatgpt.com/backend-api/".to_string(),    )}fn feature_enabled(config: &Value, key: &str, default_enabled: bool) -> bool {    config        .get("features")        .and_then(Value::as_table)        .and_then(|features| features.get(key))        .and_then(Value::as_bool)        .unwrap_or(default_enabled)}