mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
8a40200880
## This PR The original [combined remote plugin analytics PR #26281](https://github.com/openai/codex/pull/26281) mixed reusable analytics test infrastructure, two manual smoke workflows, a metadata refactor, and the final identity behavior. This PR adds the account-mutating validation workflow separately so its cleanup and recovery guarantees can be reviewed without the final analytics behavior change. - Add a manually invoked remote plugin install/uninstall smoke workflow. - Require explicit account-mutation confirmation and an initially uninstalled plugin. - Validate the current `codex_plugin_installed` contract, where `plugin_id` is the backend ID. - Restore and verify the original uninstalled state, with a dedicated recovery command. This baseline intentionally does not require `codex_plugin_uninstalled`, because production does not emit that event yet. The final PR will update this smoke to require local `plugin_id`, `remote_plugin_id`, and uninstall emission. Review this PR as the net diff against #27099. ## Testing - `just test -p codex-app-server-test-client` (3 focused capture/validation tests passed) - The live workflow was previously exercised on the green combined reference branch, and the original uninstalled account state was restored. - CI is green across the required platform matrix. ## Split Overview ```text main ├── #27093 Debug analytics capture │ └── #27099 Non-mutating plugin smoke │ └── #27100 Remote install/uninstall smoke ← you are here └── #27102 Plugin telemetry metadata refactor After #27093, #27099, #27100, and #27102 merge: └── Final PR: add remote_plugin_id to plugin analytics ``` Review order and dependencies: 1. [#27093 Add debug-only analytics event capture](https://github.com/openai/codex/pull/27093) (based on `main`) 2. [#27099 Add a plugin analytics smoke workflow](https://github.com/openai/codex/pull/27099) (stacked on #27093) 3. [#27100 Add a remote plugin analytics mutation smoke workflow](https://github.com/openai/codex/pull/27100) **(this PR, stacked on #27099)** 4. [#27102 Centralize plugin telemetry metadata construction](https://github.com/openai/codex/pull/27102) (independent, based on `main`) 5. Final remote-ID behavior PR (created after PRs 1-4 merge) The original [#26281](https://github.com/openai/codex/pull/26281) remains open as the green aggregate reference until the final PR is published.
94 lines
2.9 KiB
Rust
94 lines
2.9 KiB
Rust
use super::PluginEventIdentity;
|
|
use super::read_events_for_remote_plugin;
|
|
use super::validate_mutation_events;
|
|
use serde_json::Value;
|
|
use serde_json::json;
|
|
use std::fs;
|
|
use std::path::PathBuf;
|
|
use std::process;
|
|
use std::time::SystemTime;
|
|
|
|
const REMOTE_PLUGIN_ID: &str = "plugins~Plugin_test";
|
|
|
|
#[test]
|
|
fn reads_and_validates_remote_plugin_mutation_events() {
|
|
let path = unique_capture_path("valid");
|
|
let installed = mutation_event("codex_plugin_installed");
|
|
let unrelated = json!({
|
|
"event_type": "codex_plugin_installed",
|
|
"event_params": {
|
|
"plugin_id": "plugins~Plugin_other"
|
|
}
|
|
});
|
|
let contents = [
|
|
json!({"events": [unrelated]}),
|
|
json!({"events": [installed]}),
|
|
]
|
|
.into_iter()
|
|
.map(|payload| serde_json::to_string(&payload).expect("serialize capture payload"))
|
|
.collect::<Vec<_>>()
|
|
.join("\n");
|
|
fs::write(&path, contents).expect("write capture file");
|
|
|
|
let events = read_events_for_remote_plugin(&path, REMOTE_PLUGIN_ID)
|
|
.expect("read matching plugin events");
|
|
let validated =
|
|
validate_mutation_events(events, expected_identity()).expect("validate mutation events");
|
|
|
|
assert_eq!(validated, vec![installed]);
|
|
fs::remove_file(path).expect("remove capture file");
|
|
}
|
|
|
|
#[test]
|
|
fn rejects_duplicate_mutation_events() {
|
|
let installed = mutation_event("codex_plugin_installed");
|
|
let error = validate_mutation_events(vec![installed.clone(), installed], expected_identity())
|
|
.expect_err("duplicate install events should fail validation");
|
|
|
|
assert!(error.to_string().contains("found 2"));
|
|
}
|
|
|
|
#[test]
|
|
fn rejects_missing_capability_metadata() {
|
|
let mut installed = mutation_event("codex_plugin_installed");
|
|
installed["event_params"]["has_skills"] = Value::Null;
|
|
let error = validate_mutation_events(vec![installed], expected_identity())
|
|
.expect_err("missing capability metadata should fail validation");
|
|
|
|
assert!(error.to_string().contains("has_skills"));
|
|
}
|
|
|
|
fn mutation_event(event_type: &str) -> Value {
|
|
json!({
|
|
"event_type": event_type,
|
|
"event_params": {
|
|
"plugin_id": REMOTE_PLUGIN_ID,
|
|
"plugin_name": "sample",
|
|
"marketplace_name": "openai-curated-remote",
|
|
"has_skills": true,
|
|
"mcp_server_count": 0,
|
|
"connector_ids": [],
|
|
"product_client_id": "test-client"
|
|
}
|
|
})
|
|
}
|
|
|
|
fn expected_identity() -> PluginEventIdentity<'static> {
|
|
PluginEventIdentity {
|
|
plugin_id: REMOTE_PLUGIN_ID,
|
|
plugin_name: "sample",
|
|
marketplace_name: "openai-curated-remote",
|
|
}
|
|
}
|
|
|
|
fn unique_capture_path(name: &str) -> PathBuf {
|
|
let nonce = SystemTime::now()
|
|
.duration_since(SystemTime::UNIX_EPOCH)
|
|
.expect("system clock should be after Unix epoch")
|
|
.as_nanos();
|
|
std::env::temp_dir().join(format!(
|
|
"codex-plugin-analytics-capture-{name}-{}-{nonce}.jsonl",
|
|
process::id()
|
|
))
|
|
}
|