use std::fs;
use serde_json::Value;
use super::*;
#[test]
fn test_thread_create_then_delegate() {
let main = setup_repo("main.rs", "fn main() {}");
heddle(&["thread", "create", "modulo-race"], Some(main.path())).unwrap();
let delegate_out = heddle(
&[
"--json",
"delegate",
"--parent",
"modulo-race",
"task:anthropic:claude-sonnet-4-6",
],
Some(main.path()),
)
.expect(
"delegate must succeed once `thread create` writes a record; \
pre-fix this errored with `Thread 'modulo-race' not found`",
);
let delegated: Value = serde_json::from_str(&delegate_out).unwrap();
let children = delegated["delegated"].as_array().unwrap();
assert!(
!children.is_empty(),
"delegate should create at least one child thread, got: {delegated}"
);
}
#[test]
fn test_thread_create_writes_record() {
let main = setup_repo("main.rs", "fn main() {}");
let name = "ref-only-thread";
heddle(&["thread", "create", name], Some(main.path())).unwrap();
let encoded: String =
name.as_bytes()
.iter()
.fold(String::with_capacity(name.len() * 2), |mut acc, b| {
use std::fmt::Write as _;
let _ = write!(&mut acc, "{:02x}", b);
acc
});
let record_path = main
.path()
.join(".heddle")
.join("thread_records")
.join(format!("{encoded}.toml"));
assert!(
record_path.exists(),
"thread create should write a record file at {}",
record_path.display()
);
let show_out = heddle(&["--json", "thread", "show", name], Some(main.path())).expect(
"thread show should succeed after thread create — it routes \
through find_thread_summary which reads the record store",
);
let summary: Value = serde_json::from_str(&show_out).unwrap();
assert_eq!(summary["name"], name);
assert_eq!(
summary["thread_state"], "active",
"ref-only thread should be Active, got: {summary}"
);
}
#[test]
fn test_thread_create_then_show_via_record() {
let main = setup_repo("main.rs", "fn main() {}");
let name = "show-via-record";
heddle(&["thread", "create", name], Some(main.path())).unwrap();
let show_out = heddle(&["--json", "thread", "show", name], Some(main.path())).unwrap();
let summary: Value = serde_json::from_str(&show_out).unwrap();
assert_eq!(summary["name"], name);
assert_eq!(
summary["thread_mode"], "lightweight",
"no-worktree create records the closest existing variant; \
got: {summary}"
);
assert_eq!(summary["thread_state"], "active");
assert!(
summary["path"].is_null(),
"create does not materialize a worktree, so path must be null; \
got: {summary}"
);
assert_eq!(
summary["execution_path"], "",
"create does not materialize a worktree, so execution_path must \
be empty; got: {summary}"
);
assert!(
summary["base_state"].is_string(),
"base_state should be set from current HEAD; got: {summary}"
);
}
#[test]
fn test_thread_create_then_switch_then_capture_then_delegate() {
let main = setup_repo("main.rs", "fn main() {}");
let parent = "feature/parent";
heddle(&["thread", "create", parent], Some(main.path())).unwrap();
heddle(&["thread", "switch", parent], Some(main.path())).unwrap();
fs::write(main.path().join("lib.rs"), "pub fn lib() {}").unwrap();
heddle(&["capture", "-m", "feat: add lib"], Some(main.path())).unwrap();
let track = head_track(main.path());
assert_eq!(track, parent, "HEAD should still be attached to {parent}");
let delegate_out = heddle(
&[
"--json",
"delegate",
"--parent",
parent,
"task:anthropic:claude-sonnet-4-6",
],
Some(main.path()),
)
.unwrap();
let delegated: Value = serde_json::from_str(&delegate_out).unwrap();
let children = delegated["delegated"].as_array().unwrap();
assert!(
!children.is_empty(),
"delegate from a switched-and-captured thread should produce \
a child, got: {delegated}"
);
}