Files
codex/codex-rs/cli/tests/daemon_telemetry.rs
T
Celia Chen a44afa527c Track credential storage outcomes and redact sensitive errors (#49384)
## Why

Successful file fallback can hide a secure-store failure. Storage diagnostics need to distinguish that fallback from explicit file storage while keeping credential values out of error output.

## What changed

- Record credential load, save, delete, and fallback-file cleanup outcomes, including secure-store failures and fallback reasons.
- Preserve the initiating client's attribution across app-server requests, login tasks, sessions, turns, and blocking token persistence.
- Preserve storage error context and typed causes while redacting credential JSON data errors and payload-bearing keyring errors.
- Include sanitized fallback completion warnings in the default `codex login` file logs.

## Testing

Add coverage for storage policy and metric attribution, credential redaction, login diagnostics, concurrent app-server clients, device-code login, and token refresh during a turn.

GitOrigin-RevId: 6d5651c1718d184016fa77befa02aa528750b2ae
2026-09-29 22:24:09 +00:00

74 lines
2.8 KiB
Rust

//! Foreground updates honor app-server consent and never claim unconfirmed installation.
use anyhow::Context as _;
use pretty_assertions::assert_eq;
use serde_json::Value;
use wiremock::Mock;
use wiremock::MockServer;
use wiremock::ResponseTemplate;
#[tokio::test]
async fn foreground_update_respects_consent_and_reports_unconfirmed() -> anyhow::Result<()> {
for (analytics_config, analytics_default_enabled, expected_requests) in [
("", false, 0),
("", true, 1),
("analytics.enabled = true\n", false, 1),
("analytics.enabled = false\n", true, 0),
] {
let codex = codex_utils_cargo_bin::cargo_bin("codex")?;
let server = MockServer::start().await;
Mock::given(wiremock::matchers::path("/metrics"))
.respond_with(ResponseTemplate::new(200))
.mount(&server)
.await;
let home = tempfile::tempdir()?;
std::fs::write(
home.path().join("config.toml"),
format!(
"cli_auth_credentials_store = \"file\"\n{analytics_config}[otel]\nmetrics_exporter = {{ otlp-http = {{ endpoint = \"{}/metrics\", protocol = \"json\" }} }}\n",
server.uri(),
),
)?;
// No managed installation exists, so the command cannot confirm an applied update.
let mut command = tokio::process::Command::new(&codex);
command
.current_dir(home.path())
.env("CODEX_HOME", home.path())
.env_remove(codex_app_server_daemon::telemetry::HANDOFF_ENV)
.arg("app-server");
if analytics_default_enabled {
command.arg("--analytics-default-enabled");
}
let output = command
.args(["daemon", "update", "--from-cli", "--yes"])
.output()
.await?;
assert!(!output.status.success());
let requests = server
.received_requests()
.await
.context("metric requests")?;
assert_eq!(requests.len(), expected_requests);
if expected_requests == 0 {
continue;
}
let body: Value = serde_json::from_slice(&requests[0].body)?;
let metric = body["resourceMetrics"]
.as_array()
.into_iter()
.flatten()
.flat_map(|resource| resource["scopeMetrics"].as_array().into_iter().flatten())
.flat_map(|scope| scope["metrics"].as_array().into_iter().flatten())
.find(|metric| metric["name"] == "codex.daemon.update")
.context("daemon update metric")?;
assert!(
metric["sum"]["dataPoints"][0]["attributes"]
.as_array()
.context("metric attributes")?
.iter()
.any(|tag| tag["key"] == "outcome" && tag["value"]["stringValue"] == "unconfirmed")
);
}
Ok(())
}