Files
codex/codex-rs/core-plugins/src/remote_metadata_tests.rs
T
Charlie Marsh aa39ded153 Trace MCP refresh causes and plugin metadata changes (#60505)
## What changed

- Track and log `McpRefreshReason` values for runtime refreshes, including invalidations received during thread startup and forced refreshes. Restore claimed reasons when a refresh is cancelled before publication, while preserving concurrent invalidations.
- Correlate installed-plugin metadata invalidations with downstream MCP refreshes using a shared change ID. Log changed field names by plugin ID, additions, removals, and reordering without logging field values.
- Record whether background installed-plugin bundle updates were requested by startup, plugin listing, or installation.

## Testing

Add coverage for metadata change attribution across membership changes and reordering, and for retaining refresh causes across cancellation and concurrent invalidation.

GitOrigin-RevId: 3d4485ccfcc1d8788eb9909a060ac200c467af13
2026-11-10 02:45:30 +00:00

219 lines
7.8 KiB
Rust

//! Regression coverage for the fields that affect plugin behavior.
use super::*;
use crate::remote::REMOTE_GLOBAL_MARKETPLACE_NAME;
use crate::remote::group_remote_installed_plugins_by_marketplaces;
use codex_app_server_protocol::PluginAuthPolicy;
use codex_app_server_protocol::PluginAvailability;
use codex_app_server_protocol::PluginInstallPolicy;
use serde_json::json;
fn image_url(renewal: &str) -> String {
format!("https://files.openai.com/plugins/icon.png?sig={renewal}")
}
fn plugin(renewal: &str) -> RemoteInstalledPlugin {
RemoteInstalledPlugin {
marketplace_name: REMOTE_GLOBAL_MARKETPLACE_NAME.to_string(),
id: "plugin-test".to_string(),
version: Some("1.0.0".to_string()),
name: "test".to_string(),
canonical_app_id: None,
installed_at: None,
enabled: true,
install_policy: PluginInstallPolicy::Available,
install_policy_source: None,
must_show_installation_interstitial: None,
auth_policy: PluginAuthPolicy::OnUse,
availability: PluginAvailability::Available,
disabled_reason: None,
eligible_plan_types: None,
interface: Some(
serde_json::from_value(json!({
"capabilities": [], "screenshots": [],
"logoUrl": image_url(renewal), "logoUrlDark": image_url(renewal),
"composerIconUrl": image_url(renewal), "screenshotUrls": [image_url(renewal)]
}))
.unwrap(),
),
keywords: vec![],
}
}
fn catalog(plugins: &[RemoteInstalledPlugin]) -> Vec<RemoteMarketplace> {
group_remote_installed_plugins_by_marketplaces(plugins, &[REMOTE_GLOBAL_MARKETPLACE_NAME])
}
#[test]
fn behavioral_metadata_changes_still_invalidate() {
let changes: &[fn(&mut RemoteInstalledPlugin)] = &[
|plugin| plugin.id.push_str("-other"),
|plugin| plugin.name.push_str("-other"),
|plugin| plugin.version = Some("2.0.0".to_string()),
|plugin| {
plugin.installed_at =
chrono::DateTime::from_timestamp(/*secs*/ 1, /*nsecs*/ 0)
},
|plugin| plugin.enabled = false,
|plugin| plugin.install_policy = PluginInstallPolicy::NotAvailable,
|plugin| {
plugin.install_policy_source =
Some(codex_app_server_protocol::PluginInstallPolicySource::WorkspaceSetting)
},
|plugin| plugin.must_show_installation_interstitial = Some(true),
|plugin| plugin.auth_policy = PluginAuthPolicy::OnInstall,
|plugin| plugin.availability = PluginAvailability::DisabledByAdmin,
|plugin| plugin.eligible_plan_types = Some(vec!["enterprise".to_string()]),
];
for change in changes {
let previous = [plugin("old")];
let mut current = [plugin("new")];
change(&mut current[0]);
assert!(!installed_plugin_metadata_eq(&previous, &current));
assert!(!remote_catalog_metadata_eq(
&catalog(&previous),
&catalog(&current)
));
}
let previous = [plugin("old")];
let mut current = [plugin("new")];
current[0].canonical_app_id = Some("new-app".to_string());
assert!(!installed_plugin_metadata_eq(&previous, &current));
assert!(!installed_plugin_metadata_eq(&previous, &[]));
assert!(!remote_catalog_metadata_eq(&catalog(&previous), &[]));
}
#[test]
fn display_metadata_does_not_invalidate() {
let previous = [plugin("old")];
let mut current = [plugin("new")];
current[0].keywords.push("new keyword".to_string());
let interface = current[0].interface.as_mut().unwrap();
interface.logo_url = Some("https://another-host.test/different-image.svg".to_string());
interface.display_name = Some("New display name".to_string());
interface.short_description = Some("New description".to_string());
interface.brand_color = Some("#123456".to_string());
interface.default_prompt = Some(vec!["New starter prompt".to_string()]);
interface.website_url = Some("https://another-host.test".to_string());
assert_ne!(previous, current);
assert!(installed_plugin_metadata_eq(&previous, &current));
let mut current_catalog = catalog(&current);
current_catalog[0].display_name = "New marketplace title".to_string();
assert!(remote_catalog_metadata_eq(
&catalog(&previous),
&current_catalog
));
current[0].interface = None;
assert!(installed_plugin_metadata_eq(&previous, &current));
assert!(remote_catalog_metadata_eq(
&catalog(&previous),
&catalog(&current)
));
}
#[test]
fn installed_ordering_remains_significant() {
let mut other = plugin("old");
other.id.push_str("-other");
other.name.push_str("-other");
let previous = [plugin("old"), other];
let mut current = previous.clone();
current.reverse();
assert!(!installed_plugin_metadata_eq(&previous, &current));
assert_eq!(
installed_plugin_metadata_changes(&previous, &current),
BTreeMap::from([
(previous[0].id.clone(), vec!["position"]),
(previous[1].id.clone(), vec!["position"]),
])
);
}
#[test]
fn store_badge_changes_do_not_invalidate() {
let mut previous = [plugin("old")];
previous[0].interface.as_mut().unwrap().capabilities =
vec!["tools".to_string(), "skills".to_string()];
for badges in [vec!["skills", "tools"], vec!["updated badge"], vec![]] {
let mut current = previous.clone();
current[0].interface.as_mut().unwrap().capabilities =
badges.into_iter().map(str::to_string).collect();
assert_ne!(previous, current);
assert!(installed_plugin_metadata_eq(&previous, &current));
assert!(remote_catalog_metadata_eq(
&catalog(&previous),
&catalog(&current)
));
}
}
#[test]
fn catalog_display_reordering_does_not_invalidate() {
let mut first = plugin("old");
first.interface.as_mut().unwrap().display_name = Some("Alpha".to_string());
let mut second = plugin("old");
second.id.push_str("-second");
second.name.push_str("-second");
second.interface.as_mut().unwrap().display_name = Some("Beta".to_string());
let previous = [first, second];
let mut current = previous.clone();
current[0].interface.as_mut().unwrap().display_name = Some("Zulu".to_string());
let previous_catalog = catalog(&previous);
let current_catalog = catalog(&current);
assert_ne!(
previous_catalog[0].plugins[0].id,
current_catalog[0].plugins[0].id
);
assert!(installed_plugin_metadata_eq(&previous, &current));
assert!(remote_catalog_metadata_eq(
&previous_catalog,
&current_catalog
));
current[0].version = Some("2.0.0".to_string());
assert!(!remote_catalog_metadata_eq(
&previous_catalog,
&catalog(&current)
));
assert!(!remote_catalog_metadata_eq(
&previous_catalog,
&catalog(&current[1..])
));
current[0] = current[1].clone();
assert!(!remote_catalog_metadata_eq(
&previous_catalog,
&catalog(&current)
));
}
#[test]
fn diagnostics_attribute_fields_by_plugin_id_across_membership_changes() {
let previous = [
plugin("old"),
RemoteInstalledPlugin {
id: "removed".into(),
..plugin("old")
},
];
let mut updated = previous[0].clone();
updated.enabled = !updated.enabled;
updated.version = Some("2".into());
let id = updated.id.clone();
let current = [
RemoteInstalledPlugin {
id: "added".into(),
..plugin("new")
},
updated,
];
assert_eq!(
installed_plugin_metadata_changes(&previous, &current),
std::collections::BTreeMap::from([
("added".into(), vec!["added"]),
("removed".into(), vec!["removed"]),
(id, vec!["version", "enabled"]),
])
);
}