use super::*;
use crate::agent::{Agent, RunOpts};
use std::process::Command;
#[path = "turn/fixture.rs"]
mod fixture;
use fixture::Fixture;
fn assert_exec(fixture: &Fixture, reason: &str) {
fixture.dispatch();
let events = fixture.store.get_events(fixture.task.as_str()).unwrap();
assert!(
events
.iter()
.any(|event| event.metadata.as_ref().is_some_and(|metadata| {
metadata["codex_app_server_fallback"] == true
&& metadata["reason"]
.as_str()
.is_some_and(|detail| detail.contains(reason))
}))
);
assert_eq!(
fixture
.store
.get_task(fixture.task.as_str())
.unwrap()
.unwrap()
.status,
crate::types::TaskStatus::Done
);
assert!(
std::fs::read_to_string(&fixture.log)
.unwrap()
.contains("STEERED")
);
}
#[test]
fn retry_uses_exec_when_real_home_lacks_sessions() {
let mut fixture = Fixture::new("complete");
let real = fixture.home.path().join("real-codex");
std::fs::remove_dir(real.join("sessions")).unwrap();
let local = fixture.home.path().join("codex-home/sessions");
std::fs::create_dir_all(&local).unwrap();
std::fs::write(
local.join("rollout-2026-10-15T10-00-00-saved-session.jsonl"),
"local",
)
.unwrap();
let opts = retry_options();
crate::agent::codex::cli_config::set_test_codex_home(Some(real));
let built = crate::agent::codex::CodexAgent.build_command("retry prompt", &opts);
crate::agent::codex::cli_config::set_test_codex_home(None);
let built = built.unwrap();
assert!(!built.get_args().any(|arg| arg == "resume"));
let mut command = Command::new(fixture.command.get_program());
command.args(built.get_args());
for (key, value) in fixture.command.get_envs() {
if let Some(value) = value {
command.env(key, value);
}
}
fixture.command = command;
assert_exec(&fixture, "requires existing sessions and state_5.sqlite");
assert!(local.is_dir());
assert!(!fixture.home.path().join("real-codex/sessions").exists());
}
fn retry_options() -> RunOpts {
RunOpts {
dir: None,
output: None,
result_file: None,
model: None,
budget: false,
read_only: true,
sandbox: false,
context_files: vec![],
session_id: Some("saved-session".into()),
env: None,
env_forward: None,
}
}
#[test]
fn uses_exec_when_real_home_lacks_state_database() {
let fixture = Fixture::new("complete");
let database = fixture.home.path().join("real-codex/state_5.sqlite");
std::fs::remove_file(&database).unwrap();
assert_exec(&fixture, "requires existing sessions and state_5.sqlite");
assert!(!database.exists());
}
#[test]
fn refresh_falls_back_when_local_entry_collides_with_real_entry() {
for directory in [false, true] {
let fixture = Fixture::new("complete");
let home = Home::for_command(&fixture.command).unwrap();
let local = home.path.join("later");
let real = fixture.home.path().join("real-codex/later");
if directory {
std::fs::create_dir(&local).unwrap();
std::fs::create_dir(&real).unwrap();
} else {
std::fs::write(&local, "local").unwrap();
std::fs::write(&real, "real").unwrap();
}
assert_exec(&fixture, "local state invisible to the real home");
assert!(!local.symlink_metadata().unwrap().file_type().is_symlink());
if !directory {
assert_eq!(std::fs::read_to_string(local).unwrap(), "local");
assert_eq!(std::fs::read_to_string(real).unwrap(), "real");
}
}
}
#[test]
fn rejects_non_regular_config_entries_in_persistent_mirror() {
for name in ["config.toml-dir", "config.toml-link"] {
let fixture = Fixture::new("complete");
let home = Home::for_command(&fixture.command).unwrap();
let path = home.path.join(name);
if name.ends_with("-dir") {
std::fs::create_dir(&path).unwrap();
} else if name.ends_with("-link") {
std::os::unix::fs::symlink(home.path.join("missing"), &path).unwrap();
} else {
std::fs::write(&path, "synthetic user config").unwrap();
}
assert_exec(&fixture, "contains non-regular user configuration");
assert!(path.symlink_metadata().is_ok());
}
}