use super::fixtures::*;
use super::stubs::STUB_SHA;
use crate::prompt::run;
use crate::prompt::step::StepMeta;
use std::ffi::OsStr;
#[test]
fn run_happy_path_writes_branch_worktree_and_two_commits() {
let repo = scaffold_repo(VALID_PER_REPO_PROVIDERS_YAML, Some("system body"));
let harness = scaffold_harness_root();
let adapter = StubAdapter::happy(&happy_response_bytes());
let git = StubGit::ok();
let (clock, id) = (FixedClock::default(), FixedIdGen);
let (sleeper, tool_executor) = (StubSleeper::default(), StubToolExecutor::ok());
let branch = run(
repo.path(),
"hello",
&valid_deps(
&adapter,
&sleeper,
&git,
&clock,
&id,
&tool_executor,
harness.path(),
),
)
.unwrap();
assert_eq!(branch, "ct-1-deadbeef");
let worktree = worktree_path(repo.path());
let repo_git = crate::workspace::repo_git(repo.path());
let goal = std::fs::read_to_string(worktree.join("goal.md")).unwrap();
assert_eq!(goal, "hello");
let soul = std::fs::read_to_string(worktree.join("soul.md")).unwrap();
assert_eq!(soul, "system body");
assert!(!worktree.join("steps").exists());
let step_dir = repo.path().join("steps/ct-1-deadbeef/001");
let request: serde_json::Value =
serde_json::from_slice(&std::fs::read(step_dir.join("request.json")).unwrap()).unwrap();
assert_eq!(request["model"], "claude-sonnet-5");
assert_eq!(
request["system"][0]["text"].as_str().unwrap(),
"<goal>\nhello\n</goal>\n\nsystem body"
);
assert_eq!(request["messages"][0]["role"], "user");
assert_eq!(
request["messages"][0]["content"][0]["text"],
"---\nfrom: user\ndeposited_at: iso-1\n---\nhello"
);
assert_eq!(request["max_tokens"], 4096);
assert!(request["stream"].is_null());
let lines = parse_jsonl(&std::fs::read(step_dir.join("response.json")).unwrap());
assert!(lines.len() >= 2, "expected event stream, got {lines:?}");
assert_eq!(lines.first().unwrap()["type"], "message_start");
let text = lines
.iter()
.find(|e| e["type"] == "content_delta")
.expect("expected a content_delta");
assert_eq!(text["delta"]["text_delta"], "hi there");
assert_eq!(lines.last().unwrap()["type"], "end");
let finish = lines.iter().find(|e| e["type"] == "finish").unwrap();
assert_eq!(finish["reason"], "stop");
let meta: StepMeta =
serde_json::from_slice(&std::fs::read(step_dir.join("meta.json")).unwrap()).unwrap();
assert_eq!(meta.commit, STUB_SHA);
assert_eq!(meta.started_at, "iso-2");
assert_eq!(meta.ended_at, "iso-3");
let invocations = adapter.observed.borrow().clone();
assert_eq!(invocations.len(), 2, "version guard + model call");
let (binary, args, stdin) = invocations[0].clone();
assert_eq!(binary, OsStr::new("bz"));
assert_eq!(args, vec!["--version"]);
assert!(stdin.is_empty());
let (binary, args, stdin) = invocations[1].clone();
assert_eq!(binary, OsStr::new("bz"));
assert_eq!(args, vec!["--json", "--provider", "anthropic"]);
let wire: serde_json::Value = serde_json::from_slice(&stdin).unwrap();
assert_eq!(wire, request);
let runs = git.runs.borrow();
assert_eq!(runs.len(), 17);
for (dest, _args) in &runs[0..7] {
assert_eq!(dest, &repo_git, "control + spawn run against repo.git");
}
assert_eq!(
runs[0].1,
vec!["rev-parse", "--verify", "refs/heads/config/default"]
);
assert_eq!(runs[1].1, vec!["show", &format!("{STUB_SHA}:version")]);
assert_eq!(
runs[2].1,
vec!["show", &format!("{STUB_SHA}:providers.yaml")]
);
assert_eq!(
runs[3].1,
vec!["show", &format!("{STUB_SHA}:workflow.yaml")]
);
assert_eq!(
runs[4].1,
vec!["show", &format!("{STUB_SHA}:manifest.yaml")]
);
assert_eq!(
runs[5].1,
vec!["show", &format!("{STUB_SHA}:souls/worker.md")]
);
let args6 = &runs[6].1;
assert_eq!(
args6[..4],
["worktree", "add", "-b", "agents/ct-1-deadbeef"]
);
assert_eq!(args6[4], worktree.to_string_lossy().to_string());
assert_eq!(args6[5], "config/default");
for (dest, _args) in &runs[7..17] {
assert_eq!(dest, &worktree, "post-spawn git runs inside the worktree");
}
assert_eq!(
runs[7].1,
vec![
"rm",
"-r",
"-q",
"--ignore-unmatch",
"--",
"manifest.yaml",
"workflow.yaml",
"providers.yaml",
"version",
"souls"
]
);
assert_eq!(runs[8].1, vec!["add", "goal.md", "soul.md"]);
assert_eq!(runs[9].1[0], "commit");
assert!(runs[9].1[2].contains("step 001: dispatch"));
assert!(runs[9].1[2].contains("[ct-1-deadbeef]"));
assert_eq!(runs[10].1, vec!["status", "--porcelain", "--", "messages"]);
assert_eq!(runs[11].1, vec!["add", "messages/001-user.md"]);
assert!(runs[12].1[2].contains("transcript 001: user"));
assert_eq!(runs[13].1, vec!["rev-parse", "HEAD"]);
assert_eq!(runs[14].1, vec!["add", "messages/002-claude-sonnet-5.json"]);
assert_eq!(runs[15].1[0], "commit");
assert!(runs[15].1[2].contains("transcript 002: claude-sonnet-5"));
assert!(runs[15].1[2].contains("[ct-1-deadbeef]"));
let entry = worktree.join("messages/002-claude-sonnet-5.json");
let blocks: serde_json::Value =
serde_json::from_slice(&std::fs::read(&entry).unwrap()).unwrap();
assert_eq!(blocks[0]["type"], "text");
assert_eq!(blocks[0]["text"], "hi there");
assert!(!step_dir.join("staging.json").exists());
assert_eq!(runs[16].0, worktree);
assert_eq!(runs[16].1, vec!["rev-parse", "HEAD"]);
}
#[test]
fn run_under_adapter_override_skips_version_guard_and_uses_the_override() {
let repo = scaffold_repo(VALID_PER_REPO_PROVIDERS_YAML, Some("body"));
let harness = scaffold_harness_root_with_adapter("/opt/alt-bz");
let adapter = StubAdapter::scripted([StubAdapter::reply_ok(&happy_response_bytes())]);
let git = StubGit::ok();
let (clock, id) = (FixedClock::default(), FixedIdGen);
let (sleeper, tool_executor) = (StubSleeper::default(), StubToolExecutor::ok());
run(
repo.path(),
"hi",
&valid_deps(
&adapter,
&sleeper,
&git,
&clock,
&id,
&tool_executor,
harness.path(),
),
)
.unwrap();
let invocations = adapter.observed.borrow().clone();
assert_eq!(invocations.len(), 1);
assert_eq!(invocations[0].0, OsStr::new("/opt/alt-bz"));
assert_eq!(invocations[0].1, vec!["--json", "--provider", "anthropic"]);
}
#[test]
fn run_under_injected_adapter_target_skips_version_guard_and_uses_the_target() {
let repo = scaffold_repo(VALID_PER_REPO_PROVIDERS_YAML, Some("body"));
let harness = scaffold_harness_root();
let adapter = StubAdapter::scripted([StubAdapter::reply_ok(&happy_response_bytes())]);
let git = StubGit::ok();
let (clock, id) = (FixedClock::default(), FixedIdGen);
let (sleeper, tool_executor) = (StubSleeper::default(), StubToolExecutor::ok());
let injected = std::path::PathBuf::from("/opt/host-bz");
let mut deps = valid_deps(
&adapter,
&sleeper,
&git,
&clock,
&id,
&tool_executor,
harness.path(),
);
deps.adapter_target = Some(&injected);
run(repo.path(), "hi", &deps).unwrap();
let invocations = adapter.observed.borrow().clone();
assert_eq!(invocations.len(), 1);
assert_eq!(invocations[0].0, OsStr::new("/opt/host-bz"));
assert_eq!(invocations[0].1, vec!["--json", "--provider", "anthropic"]);
}