#![forbid(unsafe_code)]
use std::fs;
use std::path::PathBuf;
use std::process::Command;
use tempfile::tempdir;
const BINARY: &str = env!("CARGO_BIN_EXE_codex-config-plan");
fn fixture_auth_command() -> PathBuf {
std::env::current_exe().expect("current test executable")
}
fn preview_digest(
root: &std::path::Path,
config: &std::path::Path,
port: &str,
auth: &std::path::Path,
) -> String {
let output = Command::new(BINARY)
.env("CODEX_HOME", root)
.arg("--config")
.arg(config)
.args(["--port", port, "--auth-command"])
.arg(auth)
.args(["--auth-arg", "CONTINUITY_ROUTER_TOKEN"])
.output()
.expect("run preview");
assert!(output.status.success());
let preview: serde_json::Value = serde_json::from_slice(&output.stdout).expect("preview JSON");
preview["machine_plan"]["preview_digest"]
.as_str()
.expect("preview digest")
.to_owned()
}
#[test]
fn preview_is_redacted_and_never_writes() {
let root = tempdir().expect("temporary config root");
let config = root.path().join("config.toml");
let original = b"# isolated fixture\nmodel = \"native\"\n";
fs::write(&config, original).expect("fixture config");
let auth_command = fixture_auth_command();
let output = Command::new(BINARY)
.env("CODEX_HOME", root.path())
.arg("--config")
.arg(&config)
.args(["--port", "49170", "--auth-command"])
.arg(&auth_command)
.args(["--auth-arg", "CONTINUITY_ROUTER_TOKEN"])
.output()
.expect("run config plan");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).expect("UTF-8 output");
assert!(stdout.contains("\"status\":\"preview\""));
assert!(stdout.contains("[REDACTED_AUTH_COMMAND]"));
assert!(!stdout.contains(auth_command.to_string_lossy().as_ref()));
let preview: serde_json::Value = serde_json::from_str(&stdout).expect("preview JSON");
let human_diff = preview["human_diff"].as_str().expect("human diff");
assert!(human_diff.contains("- model = \"native\""));
assert!(human_diff.contains("+ model = \"router-auto\""));
assert_eq!(
preview["machine_plan"]["operation"],
"replace_routing_settings_and_append_provider"
);
assert_eq!(fs::read(&config).expect("unchanged config"), original);
assert!(
!root
.path()
.join("config.toml.continuity-router-backup")
.exists()
);
}
#[test]
fn apply_and_separate_restore_are_confirmation_gated() {
let root = tempdir().expect("temporary config root");
let config = root.path().join("config.toml");
let original = b"# isolated fixture\nmodel = \"native\"\n";
fs::write(&config, original).expect("fixture config");
let auth_command = fixture_auth_command();
let digest = preview_digest(root.path(), &config, "49171", &auth_command);
let refused = Command::new(BINARY)
.env("CODEX_HOME", root.path())
.arg("--config")
.arg(&config)
.args(["--port", "49171", "--auth-command"])
.arg(&auth_command)
.args(["--auth-arg", "CONTINUITY_ROUTER_TOKEN", "--apply"])
.output()
.expect("run refused apply");
assert!(!refused.status.success());
assert_eq!(fs::read(&config).expect("unchanged config"), original);
let applied = Command::new(BINARY)
.env("CODEX_HOME", root.path())
.arg("--config")
.arg(&config)
.args(["--port", "49171", "--auth-command"])
.arg(&auth_command)
.args([
"--auth-arg",
"CONTINUITY_ROUTER_TOKEN",
"--apply",
"--preview-digest",
&digest,
"--confirm",
"APPLY_USER_CONFIG",
])
.output()
.expect("run confirmed apply");
assert!(applied.status.success());
assert_ne!(fs::read(&config).expect("installed config"), original);
let installed: toml::Value =
toml::from_str(&fs::read_to_string(&config).expect("installed config text"))
.expect("installed TOML");
assert_eq!(installed["web_search"].as_str(), Some("disabled"));
let auth = &installed["model_providers"]["continuity_router"]["auth"];
assert_eq!(
auth["command"].as_str(),
Some(auth_command.to_string_lossy().as_ref())
);
assert_eq!(
auth["args"]
.as_array()
.expect("auth args")
.iter()
.map(toml::Value::as_str)
.collect::<Vec<_>>(),
vec![Some("CONTINUITY_ROUTER_TOKEN")]
);
let restored = Command::new(BINARY)
.env("CODEX_HOME", root.path())
.arg("--config")
.arg(&config)
.args(["--restore", "--confirm", "RESTORE_USER_CONFIG"])
.output()
.expect("run confirmed restore");
assert!(restored.status.success());
assert_eq!(fs::read(&config).expect("restored config"), original);
}
#[test]
fn apply_refuses_wrong_or_stale_preview_digest_without_writing() {
let root = tempdir().expect("temporary config root");
let config = root.path().join("config.toml");
let original = b"model = \"native\"\n";
fs::write(&config, original).expect("fixture config");
let auth_command = fixture_auth_command();
let output = Command::new(BINARY)
.env("CODEX_HOME", root.path())
.arg("--config")
.arg(&config)
.args(["--port", "49174", "--auth-command"])
.arg(&auth_command)
.args([
"--auth-arg",
"CONTINUITY_ROUTER_TOKEN",
"--apply",
"--preview-digest",
"0000000000000000000000000000000000000000000000000000000000000000",
"--confirm",
"APPLY_USER_CONFIG",
])
.output()
.expect("run refused apply");
assert!(!output.status.success());
assert_eq!(fs::read(&config).expect("unchanged config"), original);
assert!(
!root
.path()
.join("config.toml.continuity-router-backup")
.exists()
);
}
#[test]
fn preview_can_bind_a_future_helper_but_apply_requires_it_to_exist() {
let root = tempdir().expect("temporary config root");
let config = root.path().join("config.toml");
let original = b"model = \"native\"\n";
fs::write(&config, original).expect("fixture config");
let future_helper = root.path().join("future-auth-helper");
let digest = preview_digest(root.path(), &config, "49175", &future_helper);
let output = Command::new(BINARY)
.env("CODEX_HOME", root.path())
.arg("--config")
.arg(&config)
.args(["--port", "49175", "--auth-command"])
.arg(&future_helper)
.args([
"--auth-arg",
"CONTINUITY_ROUTER_TOKEN",
"--apply",
"--preview-digest",
&digest,
"--confirm",
"APPLY_USER_CONFIG",
])
.output()
.expect("run unavailable-helper refusal");
assert!(!output.status.success());
assert_eq!(fs::read(&config).expect("unchanged config"), original);
assert!(
!root
.path()
.join("config.toml.continuity-router-backup")
.exists()
);
}
#[test]
fn project_config_target_is_refused_before_access() {
let project = std::env::current_dir()
.expect("current directory")
.join(".codex/config.toml");
let output = Command::new(BINARY)
.arg("--config")
.arg(&project)
.args(["--port", "49172", "--auth-command"])
.arg(fixture_auth_command())
.args(["--auth-arg", "CONTINUITY_ROUTER_TOKEN"])
.output()
.expect("run project refusal");
assert!(!output.status.success());
assert!(
String::from_utf8(output.stderr)
.expect("UTF-8 error")
.contains("project .codex/config.toml targets are prohibited")
);
}
#[test]
fn absolute_target_outside_approved_user_config_root_is_refused() {
let directory = tempdir().expect("temporary directory");
let approved = directory.path().join("approved");
let outside = directory.path().join("outside");
fs::create_dir(&approved).expect("approved root");
fs::create_dir(&outside).expect("outside root");
let outside_config = outside.join("config.toml");
let output = Command::new(BINARY)
.env("CODEX_HOME", &approved)
.arg("--config")
.arg(&outside_config)
.args(["--port", "49173", "--auth-command"])
.arg(fixture_auth_command())
.output()
.expect("run containment refusal");
assert!(!output.status.success());
assert!(
String::from_utf8(output.stderr)
.expect("UTF-8 error")
.contains("approved user config root")
);
}