use std::fs;
use repo::{Repository, ThreadManager};
use serde_json::Value;
use tempfile::TempDir;
use super::heddle;
fn setup_thread(name: &str) -> TempDir {
let temp = TempDir::new().unwrap();
heddle(&["init"], Some(temp.path())).unwrap();
fs::write(temp.path().join("base.txt"), "base").unwrap();
heddle(&["capture", "-m", "init"], Some(temp.path())).unwrap();
heddle(&["thread", "create", name], Some(temp.path())).unwrap();
heddle(&["thread", "switch", name], Some(temp.path())).unwrap();
temp
}
#[test]
fn thread_show_without_arg_resolves_current_thread() {
let repo = setup_thread("probe");
let omitted = heddle(&["--json", "thread", "show"], Some(repo.path()))
.expect("thread show should succeed without a positional when HEAD is attached");
let with_arg = heddle(&["--json", "thread", "show", "probe"], Some(repo.path()))
.expect("thread show with explicit positional should still succeed");
let omitted: Value = serde_json::from_str(&omitted).unwrap();
let with_arg: Value = serde_json::from_str(&with_arg).unwrap();
assert_eq!(
omitted["name"], with_arg["name"],
"omitted positional should resolve to the same thread as explicit"
);
assert_eq!(omitted["name"].as_str(), Some("probe"));
}
#[test]
fn thread_captures_without_arg_resolves_current_thread() {
let repo = setup_thread("probe");
heddle(&["--json", "thread", "captures"], Some(repo.path()))
.expect("thread captures should succeed without a positional when HEAD is attached");
}
#[test]
fn thread_show_without_arg_errors_when_no_current_thread() {
let temp = TempDir::new().unwrap();
heddle(&["init"], Some(temp.path())).unwrap();
fs::write(temp.path().join("base.txt"), "base").unwrap();
heddle(&["capture", "-m", "init"], Some(temp.path())).unwrap();
let repo = Repository::open(temp.path()).unwrap();
let head = repo
.head()
.unwrap()
.expect("repo should have a current state after snapshot");
fs::write(
temp.path().join(".heddle").join("HEAD"),
format!("{}\n", head.to_string_full()),
)
.unwrap();
drop(repo);
let err = heddle(&["thread", "show"], Some(temp.path()))
.expect_err("thread show should fail when HEAD has no attached thread");
assert!(
err.contains("no thread specified and no current thread"),
"expected the explicit fallback error message; got: {err}"
);
assert!(
err.contains("pass <THREAD> explicitly"),
"expected guidance on how to recover; got: {err}"
);
}
#[test]
fn thread_show_without_arg_resolves_via_execution_path_when_detached() {
let temp = TempDir::new().unwrap();
heddle(&["init"], Some(temp.path())).unwrap();
fs::write(temp.path().join("base.txt"), "base").unwrap();
heddle(&["capture", "-m", "init"], Some(temp.path())).unwrap();
heddle(&["thread", "create", "feat/probe"], Some(temp.path())).unwrap();
heddle(&["thread", "switch", "feat/probe"], Some(temp.path())).unwrap();
let repo = Repository::open(temp.path()).unwrap();
let manager = ThreadManager::new(repo.heddle_dir());
let mut thread = manager
.load("feat/probe")
.unwrap()
.expect("thread record exists after create");
thread.execution_path = temp.path().to_path_buf();
manager.save(&thread).unwrap();
let head = repo
.head()
.unwrap()
.expect("repo should have a current state after snapshot");
fs::write(
temp.path().join(".heddle").join("HEAD"),
format!("{}\n", head.to_string_full()),
)
.unwrap();
drop(repo);
let with_arg = heddle(
&["--json", "thread", "show", "feat/probe"],
Some(temp.path()),
)
.expect("thread show with positional should succeed");
let with_arg: Value = serde_json::from_str(&with_arg).unwrap();
assert_eq!(with_arg["name"].as_str(), Some("feat/probe"));
let omitted = heddle(&["--json", "thread", "show"], Some(temp.path())).expect(
"thread show without positional must resolve via execution_path when HEAD is detached",
);
let omitted: Value = serde_json::from_str(&omitted).unwrap();
assert_eq!(
omitted["name"].as_str(),
Some("feat/probe"),
"execution-path fallback should resolve to the seeded thread; got {omitted}"
);
}
#[test]
fn thread_refresh_without_arg_does_not_require_positional() {
let repo = setup_thread("probe");
let result = heddle(&["thread", "refresh"], Some(repo.path()));
if let Err(err) = result {
assert!(
!err.contains("required arguments were not provided"),
"thread refresh should not require <THREAD> at the clap layer; got: {err}"
);
assert!(
!err.contains("<THREAD>"),
"thread refresh should not surface <THREAD> as a missing argument; got: {err}"
);
}
}