use std::fs;
use serde_json::Value;
use super::*;
#[test]
fn test_thread_create_then_child_start() {
let main = setup_repo("main.rs", "fn main() {}");
heddle(&["thread", "create", "modulo-race"], Some(main.path())).unwrap();
let child_out = heddle(
&[
"--output",
"json",
"start",
"modulo-race/task",
"--parent-thread",
"modulo-race",
"--task",
"task:anthropic:claude-sonnet-4-6",
],
Some(main.path()),
)
.expect(
"child start must succeed once `thread create` writes a record; \
pre-fix this errored with `Thread 'modulo-race' not found`",
);
let child: Value = serde_json::from_str(&child_out).unwrap();
assert_eq!(child["name"], "modulo-race/task");
let child_show: Value = serde_json::from_str(
&heddle(
&["--output", "json", "thread", "show", "modulo-race/task"],
Some(main.path()),
)
.unwrap(),
)
.unwrap();
assert_eq!(child_show["parent_thread"], "modulo-race");
}
#[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(
&["--output", "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(
&["--output", "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"], "materialized",
"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!(
summary["execution_path"].is_null(),
"create does not materialize a worktree, so execution_path must \
be null; 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_child_start() {
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 child_out = heddle(
&[
"--output",
"json",
"start",
"feature/parent/task",
"--parent-thread",
parent,
"--task",
"task:anthropic:claude-sonnet-4-6",
],
Some(main.path()),
)
.unwrap();
let child: Value = serde_json::from_str(&child_out).unwrap();
assert_eq!(child["name"], "feature/parent/task");
let child_show: Value = serde_json::from_str(
&heddle(
&["--output", "json", "thread", "show", "feature/parent/task"],
Some(main.path()),
)
.unwrap(),
)
.unwrap();
assert_eq!(child_show["parent_thread"], parent);
}