Files
codex/codex-rs/capabilities/src/plugins/store_tests.rs
T
Ahmed IbrahimandCodex 9d91be7713 Extract plugins, apps, and skills into codex-capabilities
Move capability-owned apps, plugins, and skills logic into a new workspace crate and keep codex-core as the integration layer.

Co-authored-by: Codex <noreply@openai.com>
2026-03-20 17:19:04 +00:00

193 lines
6.2 KiB
Rust

use super::*;
use pretty_assertions::assert_eq;
use tempfile::tempdir;
fn write_plugin(root: &Path, dir_name: &str, manifest_name: &str) {
let plugin_root = root.join(dir_name);
fs::create_dir_all(plugin_root.join(".codex-plugin")).unwrap();
fs::create_dir_all(plugin_root.join("skills")).unwrap();
fs::write(
plugin_root.join(".codex-plugin/plugin.json"),
format!(r#"{{"name":"{manifest_name}"}}"#),
)
.unwrap();
fs::write(plugin_root.join("skills/SKILL.md"), "skill").unwrap();
fs::write(plugin_root.join(".mcp.json"), r#"{"mcpServers":{}}"#).unwrap();
}
#[test]
fn install_copies_plugin_into_default_marketplace() {
let tmp = tempdir().unwrap();
write_plugin(tmp.path(), "sample-plugin", "sample-plugin");
let plugin_id = PluginId::new("sample-plugin".to_string(), "debug".to_string()).unwrap();
let result = PluginStore::new(tmp.path().to_path_buf())
.install(
AbsolutePathBuf::try_from(tmp.path().join("sample-plugin")).unwrap(),
plugin_id.clone(),
)
.unwrap();
let installed_path = tmp.path().join("plugins/cache/debug/sample-plugin/local");
assert_eq!(
result,
PluginInstallResult {
plugin_id,
plugin_version: "local".to_string(),
installed_path: AbsolutePathBuf::try_from(installed_path.clone()).unwrap(),
}
);
assert!(installed_path.join(".codex-plugin/plugin.json").is_file());
assert!(installed_path.join("skills/SKILL.md").is_file());
}
#[test]
fn install_uses_manifest_name_for_destination_and_key() {
let tmp = tempdir().unwrap();
write_plugin(tmp.path(), "source-dir", "manifest-name");
let plugin_id = PluginId::new("manifest-name".to_string(), "market".to_string()).unwrap();
let result = PluginStore::new(tmp.path().to_path_buf())
.install(
AbsolutePathBuf::try_from(tmp.path().join("source-dir")).unwrap(),
plugin_id.clone(),
)
.unwrap();
assert_eq!(
result,
PluginInstallResult {
plugin_id,
plugin_version: "local".to_string(),
installed_path: AbsolutePathBuf::try_from(
tmp.path().join("plugins/cache/market/manifest-name/local"),
)
.unwrap(),
}
);
}
#[test]
fn plugin_root_derives_path_from_key_and_version() {
let tmp = tempdir().unwrap();
let store = PluginStore::new(tmp.path().to_path_buf());
let plugin_id = PluginId::new("sample".to_string(), "debug".to_string()).unwrap();
assert_eq!(
store.plugin_root(&plugin_id, "local").as_path(),
tmp.path().join("plugins/cache/debug/sample/local")
);
}
#[test]
fn install_with_version_uses_requested_cache_version() {
let tmp = tempdir().unwrap();
write_plugin(tmp.path(), "sample-plugin", "sample-plugin");
let plugin_id =
PluginId::new("sample-plugin".to_string(), "openai-curated".to_string()).unwrap();
let plugin_version = "0123456789abcdef".to_string();
let result = PluginStore::new(tmp.path().to_path_buf())
.install_with_version(
AbsolutePathBuf::try_from(tmp.path().join("sample-plugin")).unwrap(),
plugin_id.clone(),
plugin_version.clone(),
)
.unwrap();
let installed_path = tmp.path().join(format!(
"plugins/cache/openai-curated/sample-plugin/{plugin_version}"
));
assert_eq!(
result,
PluginInstallResult {
plugin_id,
plugin_version,
installed_path: AbsolutePathBuf::try_from(installed_path.clone()).unwrap(),
}
);
assert!(installed_path.join(".codex-plugin/plugin.json").is_file());
}
#[test]
fn active_plugin_version_reads_version_directory_name() {
let tmp = tempdir().unwrap();
write_plugin(
&tmp.path().join("plugins/cache/debug"),
"sample-plugin/local",
"sample-plugin",
);
let store = PluginStore::new(tmp.path().to_path_buf());
let plugin_id = PluginId::new("sample-plugin".to_string(), "debug".to_string()).unwrap();
assert_eq!(
store.active_plugin_version(&plugin_id),
Some("local".to_string())
);
assert_eq!(
store.active_plugin_root(&plugin_id).unwrap().as_path(),
tmp.path().join("plugins/cache/debug/sample-plugin/local")
);
}
#[test]
fn plugin_root_rejects_path_separators_in_key_segments() {
let err = PluginId::parse("../../etc@debug").unwrap_err();
assert_eq!(
err.to_string(),
"invalid plugin name: only ASCII letters, digits, `_`, and `-` are allowed in `../../etc@debug`"
);
let err = PluginId::parse("sample@../../etc").unwrap_err();
assert_eq!(
err.to_string(),
"invalid marketplace name: only ASCII letters, digits, `_`, and `-` are allowed in `sample@../../etc`"
);
}
#[test]
fn install_rejects_manifest_names_with_path_separators() {
let tmp = tempdir().unwrap();
write_plugin(tmp.path(), "source-dir", "../../etc");
let err = PluginStore::new(tmp.path().to_path_buf())
.install(
AbsolutePathBuf::try_from(tmp.path().join("source-dir")).unwrap(),
PluginId::new("source-dir".to_string(), "debug".to_string()).unwrap(),
)
.unwrap_err();
assert_eq!(
err.to_string(),
"invalid plugin name: only ASCII letters, digits, `_`, and `-` are allowed"
);
}
#[test]
fn install_rejects_marketplace_names_with_path_separators() {
let err = PluginId::new("sample-plugin".to_string(), "../../etc".to_string()).unwrap_err();
assert_eq!(
err.to_string(),
"invalid marketplace name: only ASCII letters, digits, `_`, and `-` are allowed"
);
}
#[test]
fn install_rejects_manifest_names_that_do_not_match_marketplace_plugin_name() {
let tmp = tempdir().unwrap();
write_plugin(tmp.path(), "source-dir", "manifest-name");
let err = PluginStore::new(tmp.path().to_path_buf())
.install(
AbsolutePathBuf::try_from(tmp.path().join("source-dir")).unwrap(),
PluginId::new("different-name".to_string(), "debug".to_string()).unwrap(),
)
.unwrap_err();
assert_eq!(
err.to_string(),
"plugin manifest name `manifest-name` does not match marketplace plugin name `different-name`"
);
}