Files
codex/codex-rs/exec/src/cli_tests.rs
T
mldangelo-oai 6b4daafdb4 Add per-turn Cyber access program selection to exec and the SDK (#49939)
## Why

`codex exec` and the TypeScript SDK need a way to request a Cyber access program for an individual turn. Resuming with an API key can also leave compaction using a saved program that the current credentials cannot access.

## What changed

- Add `--cyber-access-program` with `standard`, `daybreak_blue`, and `daybreak_red`, and expose the equivalent `cyberAccessProgram` turn option and `CyberAccessProgram` type in the TypeScript SDK.
- Apply explicit selections to new, resumed, and prompted fork turns. Override automatic Daybreak selection for that turn without changing the saved choice. Reject the flag for review and forks without a prompt.
- Filter Cyber programs by the built-in OpenAI provider when starting turns and restoring previous-turn settings for compaction.
- Allow previous-model compaction fallback with API-key authentication, comparing both model and program so a program change can trigger a retry with the current pair even when the model is unchanged.

## Testing

Add CLI help, argument parsing, exec, and SDK coverage for per-turn selection and unsupported commands. Extend core tests for API-key compaction retries, denied explicit selections, custom-provider filtering, remote compaction, and WebSocket response reuse.

GitOrigin-RevId: 586f25b621924f3bc509c9a39b39fe2020ff9cd2
2026-10-01 08:57:53 +00:00

154 lines
4.2 KiB
Rust

use super::*;
use pretty_assertions::assert_eq;
#[test]
fn resume_parses_prompt_after_global_flags() {
const PROMPT: &str = "echo resume-with-global-flags-after-subcommand";
let cli = Cli::parse_from([
"codex-exec",
"resume",
"--last",
"--json",
"--model",
"gpt-5.2-codex",
"--cyber-access-program",
"daybreak_blue",
"--dangerously-bypass-approvals-and-sandbox",
"--skip-git-repo-check",
"--ephemeral",
"--ignore-user-config",
"--ignore-rules",
PROMPT,
]);
assert!(cli.ephemeral);
assert!(cli.ignore_user_config);
assert!(cli.ignore_rules);
assert_eq!(
cli.cyber_access_program,
Some(CyberAccessProgramCliArg::DaybreakBlue)
);
let Some(Command::Resume(args)) = cli.command else {
panic!("expected resume command");
};
let effective_prompt = args.prompt.clone().or_else(|| {
if args.last {
args.session_id.clone()
} else {
None
}
});
assert_eq!(effective_prompt.as_deref(), Some(PROMPT));
}
#[test]
fn resume_accepts_output_flags_after_subcommand() {
const PROMPT: &str = "echo resume-with-output-file";
let cli = Cli::parse_from([
"codex-exec",
"resume",
"session-123",
"-o",
"/tmp/resume-output.md",
"--output-schema",
"/tmp/schema.json",
PROMPT,
]);
assert_eq!(
cli.last_message_file,
Some(PathBuf::from("/tmp/resume-output.md"))
);
assert_eq!(cli.output_schema, Some(PathBuf::from("/tmp/schema.json")));
let Some(Command::Resume(args)) = cli.command else {
panic!("expected resume command");
};
assert_eq!(args.session_id.as_deref(), Some("session-123"));
assert_eq!(args.prompt.as_deref(), Some(PROMPT));
}
#[test]
fn fork_parses_prompt_after_global_flags() {
const PROMPT: &str = "continue on the fork";
let cli = Cli::parse_from([
"codex-exec",
"fork",
"session-123",
"--json",
"--model",
"gpt-5.2-codex",
"--thread-source",
"automated_review",
"--skip-git-repo-check",
"--ephemeral",
PROMPT,
]);
assert!(cli.json);
assert!(cli.ephemeral);
assert_eq!(
cli.thread_source,
Some(ThreadSource::Feature("automated_review".to_string()))
);
let Some(Command::Fork(args)) = cli.command else {
panic!("expected fork command");
};
assert_eq!(args.session_id, "session-123");
assert_eq!(args.prompt.as_deref(), Some(PROMPT));
}
#[test]
fn parses_config_isolation_flags() {
let cli = Cli::parse_from([
"codex-exec",
"--ignore-user-config",
"--ignore-rules",
"summarize",
]);
assert!(cli.ignore_user_config);
assert!(cli.ignore_rules);
}
#[test]
fn approve_for_me_flag_applies_to_resume_when_passed_at_exec_root() {
for flag in ["--approve-for-me", "--not-so-yolo"] {
let cli = Cli::parse_from(["codex-exec", flag, "resume", "--last"]);
assert!(cli.auto_review);
}
}
#[test]
fn approve_for_me_flag_conflicts_with_other_sandbox_modes() {
for conflicting_args in [
vec!["--sandbox", "read-only"],
vec!["--dangerously-bypass-approvals-and-sandbox"],
] {
let mut args = vec!["codex-exec", "--approve-for-me"];
args.extend(conflicting_args);
args.push("summarize");
let error = Cli::try_parse_from(args).expect_err("flags should conflict");
assert_eq!(error.kind(), clap::error::ErrorKind::ArgumentConflict);
}
}
#[test]
fn worktree_flag_is_accepted_after_fork_subcommand() {
let cli = Cli::try_parse_from(["codex-exec", "fork", "session-id", "--worktree"])
.expect("worktree should be a global exec argument");
assert!(cli.worktree);
assert!(matches!(cli.command, Some(Command::Fork(_))));
}
#[test]
fn worktree_flag_is_accepted_before_fork_subcommand() {
let cli = Cli::try_parse_from(["codex-exec", "--worktree", "fork", "session-id"])
.expect("worktree should be accepted before the fork subcommand");
assert!(cli.worktree);
assert!(matches!(cli.command, Some(Command::Fork(_))));
}