use anyhow::Result;
mod test_support;
#[test]
fn run_one_accepts_runner_and_model_overrides_without_todo_tasks() -> Result<()> {
let dir = test_support::temp_dir_outside_repo();
test_support::git_init(dir.path())?;
let (status, stdout, stderr) =
test_support::run_in_dir(dir.path(), &["init", "--force", "--non-interactive"]);
anyhow::ensure!(
status.success(),
"ralph init failed\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
let (status, stdout, stderr) = test_support::run_in_dir(
dir.path(),
&["run", "one", "--runner", "opencode", "--model", "gpt-5.3"],
);
anyhow::ensure!(
status.success(),
"expected success (NoTodo) with valid overrides\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
anyhow::ensure!(
stdout.contains("No todo tasks found") || stderr.contains("No todo tasks found"),
"expected NoTodo message\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
let (status, stdout, stderr) = test_support::run_in_dir(
dir.path(),
&[
"run",
"one",
"--runner",
"codex",
"--model",
"gpt-5.3-codex",
"--effort",
"high",
],
);
anyhow::ensure!(
status.success(),
"expected success (NoTodo) with valid codex overrides\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
let (status, stdout, stderr) = test_support::run_in_dir(
dir.path(),
&[
"run",
"one",
"--runner",
"codex",
"--model",
"gpt-5.3-codex",
"-e",
"high",
],
);
anyhow::ensure!(
status.success(),
"expected success (NoTodo) with -e effort override\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
let (status, stdout, stderr) = test_support::run_in_dir(
dir.path(),
&[
"run", "one", "--runner", "opencode", "--model", "gpt-5.3", "--effort", "high",
],
);
anyhow::ensure!(
status.success(),
"expected success (NoTodo) when effort is provided with opencode\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
let (status, stdout, stderr) = test_support::run_in_dir(
dir.path(),
&[
"run",
"one",
"--runner",
"gemini",
"--model",
"gemini-3-flash-preview",
],
);
anyhow::ensure!(
status.success(),
"expected success (NoTodo) with valid gemini overrides\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
anyhow::ensure!(
stdout.contains("No todo tasks found") || stderr.contains("No todo tasks found"),
"expected NoTodo message\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
let (status, stdout, stderr) = test_support::run_in_dir(
dir.path(),
&["run", "one", "--runner", "claude", "--model", "sonnet"],
);
anyhow::ensure!(
status.success(),
"expected success (NoTodo) with valid claude overrides\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
anyhow::ensure!(
stdout.contains("No todo tasks found") || stderr.contains("No todo tasks found"),
"expected NoTodo message\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
Ok(())
}
#[test]
fn run_one_accepts_repo_prompt_mode_and_alias() -> Result<()> {
let dir = test_support::temp_dir_outside_repo();
test_support::git_init(dir.path())?;
let (status, stdout, stderr) =
test_support::run_in_dir(dir.path(), &["init", "--force", "--non-interactive"]);
anyhow::ensure!(
status.success(),
"ralph init failed\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
let (status, stdout, stderr) =
test_support::run_in_dir(dir.path(), &["run", "one", "--repo-prompt", "plan"]);
anyhow::ensure!(
status.success(),
"expected success (NoTodo) with --repo-prompt plan\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
let (status, stdout, stderr) =
test_support::run_in_dir(dir.path(), &["run", "one", "-rp", "tools"]);
anyhow::ensure!(
status.success(),
"expected success (NoTodo) with -rp tools alias\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
Ok(())
}
#[test]
fn run_one_rejects_invalid_repo_prompt_value() -> Result<()> {
let dir = test_support::temp_dir_outside_repo();
test_support::git_init(dir.path())?;
let (status, stdout, stderr) =
test_support::run_in_dir(dir.path(), &["init", "--force", "--non-interactive"]);
anyhow::ensure!(
status.success(),
"ralph init failed\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
let (status, stdout, stderr) =
test_support::run_in_dir(dir.path(), &["run", "one", "--repo-prompt", "nope"]);
anyhow::ensure!(
!status.success(),
"expected failure for invalid repo-prompt\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
let combined = format!("{stdout}{stderr}");
anyhow::ensure!(
combined.contains("possible values") && combined.contains("tools"),
"expected helpful repo-prompt error\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
Ok(())
}
#[test]
fn run_one_rejects_invalid_runner_flag() -> Result<()> {
let dir = test_support::temp_dir_outside_repo();
test_support::git_init(dir.path())?;
let (status, stdout, stderr) = test_support::run_in_dir(
dir.path(),
&["run", "one", "--runner", "nope", "--model", "gpt-5.3"],
);
anyhow::ensure!(
!status.success(),
"expected failure\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
anyhow::ensure!(
stderr.contains(
"--runner must be 'codex', 'opencode', 'gemini', 'claude', 'cursor', 'kimi', or 'pi'"
),
"expected helpful runner error\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
Ok(())
}
#[test]
fn run_one_rejects_invalid_model_flag() -> Result<()> {
let dir = test_support::temp_dir_outside_repo();
test_support::git_init(dir.path())?;
let (status, stdout, stderr) =
test_support::run_in_dir(dir.path(), &["init", "--force", "--non-interactive"]);
anyhow::ensure!(
status.success(),
"ralph init failed\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
let (status, stdout, stderr) = test_support::run_in_dir(
dir.path(),
&[
"run",
"one",
"--runner",
"codex",
"--model",
"definitely-not-a-model",
],
);
anyhow::ensure!(
!status.success(),
"expected failure\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
anyhow::ensure!(
stderr.contains("not supported for codex runner"),
"expected helpful model error\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
Ok(())
}
#[test]
fn run_one_accepts_custom_model_for_opencode() -> Result<()> {
let dir = test_support::temp_dir_outside_repo();
test_support::git_init(dir.path())?;
let (status, stdout, stderr) =
test_support::run_in_dir(dir.path(), &["init", "--force", "--non-interactive"]);
anyhow::ensure!(
status.success(),
"ralph init failed\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
let (status, stdout, stderr) = test_support::run_in_dir(
dir.path(),
&[
"run",
"one",
"--runner",
"opencode",
"--model",
"gemini-3-pro-preview",
],
);
anyhow::ensure!(
status.success(),
"expected success (NoTodo) with custom opencode model\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
anyhow::ensure!(
stdout.contains("No todo tasks found") || stderr.contains("No todo tasks found"),
"expected NoTodo message\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
Ok(())
}
#[test]
fn run_one_rejects_invalid_effort_flag() -> Result<()> {
let dir = test_support::temp_dir_outside_repo();
test_support::git_init(dir.path())?;
let (status, stdout, stderr) = test_support::run_in_dir(
dir.path(),
&[
"run",
"one",
"--runner",
"codex",
"--model",
"gpt-5.3-codex",
"--effort",
"extreme",
],
);
anyhow::ensure!(
!status.success(),
"expected failure\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
anyhow::ensure!(
stderr.contains("unsupported reasoning effort"),
"expected helpful effort error\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
Ok(())
}