Files
codex/codex-rs/app-server/src/effective_plugin_change_tests.rs
T
Matthew Zeng bb998aeb8e Refresh runtimes for remote plugin state changes (#41396)
## Why

Enabling, disabling, or reinstalling a cached remote plugin can change its
effective MCP servers, hooks, and skills without materializing a new bundle.
These transitions need to invalidate plugin consumers just like bundle updates
and removals do.

## What changed

- Report a unified set of changed remote plugins and their affected
  capabilities for materializations, removals, and installed-state changes.
- Derive enablement and reinstall changes by comparing installed snapshots,
  while keeping materialization metadata separate for hook trust handling.
- Preserve removal notifications even when stale cache cleanup fails, and use
  all reported changes to refresh loaded plugin state and runtime consumers.

## Testing

Added coverage for cached plugin enablement, disablement, removal, and
reinstallation, including removal when cache cleanup cannot complete.

GitOrigin-RevId: 86d9c1356f7cf70b3c85effebc3e5986b931bdba
2026-08-28 19:12:34 +00:00

64 lines
2.2 KiB
Rust

use super::*;
use codex_plugin::PluginId;
use pretty_assertions::assert_eq;
#[test]
fn only_workspace_listed_materializations_are_eligible() {
let materialization =
|name: &str,
scope: RemotePluginScope,
discoverability: Option<RemotePluginShareDiscoverability>| {
RemotePluginMaterialization {
plugin_id: PluginId::new(name.to_string(), "test".to_string())
.expect("valid plugin id"),
scope,
discoverability,
authenticated_account_id: Some("account-123".to_string()),
}
};
let mut materializations = vec![
materialization(
"eligible",
RemotePluginScope::Workspace,
Some(RemotePluginShareDiscoverability::Listed),
),
materialization(
"unlisted",
RemotePluginScope::Workspace,
Some(RemotePluginShareDiscoverability::Unlisted),
),
materialization(
"private",
RemotePluginScope::Workspace,
Some(RemotePluginShareDiscoverability::Private),
),
materialization("workspace-missing", RemotePluginScope::Workspace, None),
materialization("global", RemotePluginScope::Global, None),
materialization("user", RemotePluginScope::User, None),
];
let mut wrong_account = materialization(
"wrong-account",
RemotePluginScope::Workspace,
Some(RemotePluginShareDiscoverability::Listed),
);
wrong_account.authenticated_account_id = Some("account-456".to_string());
materializations.push(wrong_account);
let plugin_ids = workspace_listed_plugin_ids(materializations, "account-123");
assert_eq!(plugin_ids, BTreeSet::from(["eligible@test".to_string()]));
}
#[test]
fn hook_trusted_hash_edit_targets_only_escaped_leaf() {
assert_eq!(
hook_trusted_hash_edit(r#"plugin."quoted"\path"#, "sha256:current"),
ConfigEdit {
key_path: r#"hooks.state."plugin.\"quoted\"\\path".trusted_hash"#.to_string(),
value: serde_json::json!("sha256:current"),
merge_strategy: MergeStrategy::Replace,
}
);
}