diff --git a/codex-rs/core/tests/suite/approvals.rs b/codex-rs/core/tests/suite/approvals.rs index c16cc86fd..a8396dd57 100644 --- a/codex-rs/core/tests/suite/approvals.rs +++ b/codex-rs/core/tests/suite/approvals.rs @@ -1,5 +1,6 @@ #![allow(clippy::unwrap_used, clippy::expect_used)] +use anyhow::Context; use anyhow::Result; use codex_config::types::ApprovalsReviewer; use codex_core::CodexThread; @@ -51,6 +52,7 @@ use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; use tempfile::TempDir; +use test_case::test_case; use wiremock::Mock; use wiremock::MockServer; use wiremock::Request; @@ -570,6 +572,15 @@ struct ScenarioSpec { expectation: Expectation, } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum ScenarioGroup { + DangerFullAccess, + ReadOnly, + WorkspaceWrite, + ApplyPatch, + UnifiedExec, +} + struct CommandResult { exit_code: Option, stdout: String, @@ -1659,17 +1670,52 @@ fn scenarios() -> Vec { ] } +#[test_case(ScenarioGroup::DangerFullAccess ; "danger_full_access")] +#[test_case(ScenarioGroup::ReadOnly ; "read_only")] +#[test_case(ScenarioGroup::WorkspaceWrite ; "workspace_write")] +#[test_case(ScenarioGroup::ApplyPatch ; "apply_patch")] +#[test_case(ScenarioGroup::UnifiedExec ; "unified_exec")] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn approval_matrix_covers_all_modes() -> Result<()> { +async fn approval_matrix_covers_group(group: ScenarioGroup) -> Result<()> { + run_scenario_group(group).await +} + +async fn run_scenario_group(group: ScenarioGroup) -> Result<()> { skip_if_no_network!(Ok(())); - for scenario in scenarios() { - run_scenario(&scenario).await?; + let scenarios = scenarios() + .into_iter() + .filter(|scenario| scenario_group(scenario) == group) + .collect::>(); + assert!(!scenarios.is_empty(), "expected scenarios for {group:?}"); + + for scenario in scenarios { + run_scenario(&scenario) + .await + .with_context(|| format!("approval scenario failed: {}", scenario.name))?; } Ok(()) } +fn scenario_group(scenario: &ScenarioSpec) -> ScenarioGroup { + match &scenario.action { + ActionKind::ApplyPatchFunction { .. } | ActionKind::ApplyPatchShell { .. } => { + ScenarioGroup::ApplyPatch + } + ActionKind::RunUnifiedExecCommand { .. } => ScenarioGroup::UnifiedExec, + ActionKind::WriteFile { .. } + | ActionKind::FetchUrlNoProxy { .. } + | ActionKind::FetchUrl { .. } + | ActionKind::RunCommand { .. } => match &scenario.sandbox_policy { + SandboxPolicy::DangerFullAccess => ScenarioGroup::DangerFullAccess, + SandboxPolicy::ReadOnly { .. } => ScenarioGroup::ReadOnly, + SandboxPolicy::WorkspaceWrite { .. } => ScenarioGroup::WorkspaceWrite, + SandboxPolicy::ExternalSandbox { .. } => ScenarioGroup::WorkspaceWrite, + }, + } +} + async fn run_scenario(scenario: &ScenarioSpec) -> Result<()> { eprintln!("running approval scenario: {}", scenario.name); let server = start_mock_server().await;