use super::*;
use crate::participant::Event;
use crate::participant_config::InvocationOverrides;
use crate::store::Store;
use crate::task::{NewTaskOpts, Task};
use std::path::Path;
use std::process::Command;
use tempfile::tempdir;
fn raw_git(path: &Path, args: &[&str]) -> std::process::Output {
let mut cmd = Command::new("git");
cmd.current_dir(path).args(args);
for var in crate::git::GIT_ENV_VARS {
cmd.env_remove(var);
}
cmd.output().expect("spawn git")
}
fn init_repo(path: &Path) {
let run = |args: &[&str]| assert!(raw_git(path, args).status.success());
run(&["init", "-q", "-b", "main"]);
run(&["config", "user.email", "t@e.com"]);
run(&["config", "user.name", "t"]);
run(&["config", "commit.gpgsign", "false"]);
run(&["commit", "--allow-empty", "-m", "init"]);
}
fn git_store() -> (tempfile::TempDir, Store) {
let td = tempdir().unwrap();
init_repo(td.path());
let store = Store::init(td.path(), false, None).unwrap();
(td, store)
}
fn stealth_store() -> (tempfile::TempDir, Store) {
let td = tempdir().unwrap();
let store = Store::init(
td.path(),
true,
Some(td.path().join("t").to_string_lossy().into_owned()),
)
.unwrap();
(td, store)
}
fn task(store: &Store, id: &str) -> Task {
let t = Task::new(
NewTaskOpts {
title: "x".into(),
..Default::default()
},
id.into(),
);
store.save_task(&t).unwrap();
t
}
#[test]
fn amended_message_subject_only() {
assert_eq!(amended_message("create [bl-1]", " [--no-sync]"), "create [bl-1] [--no-sync]");
}
#[test]
fn amended_message_preserves_body() {
assert_eq!(
amended_message("subj [bl-1]\n\nbody line", " [--skip=j]"),
"subj [bl-1] [--skip=j]\n\nbody line"
);
}
#[test]
fn amended_message_empty_input() {
assert_eq!(amended_message("", " [--skip=j]"), " [--skip=j]");
}
#[test]
fn state_head_is_none_in_stealth() {
let (_td, store) = stealth_store();
assert!(state_head(&store).unwrap().is_none());
}
#[test]
fn state_head_resolves_in_git() {
let (_td, store) = git_store();
let sha = state_head(&store).unwrap().expect("git store has a state HEAD");
assert_eq!(sha.len(), 40, "full sha: {sha}");
}
#[test]
fn log_overrides_noop_on_empty_tokens() {
let (_td, store) = git_store();
let before = state_head(&store).unwrap().unwrap();
log_overrides(&store, &[]).unwrap();
assert_eq!(state_head(&store).unwrap().unwrap(), before, "no amend without tokens");
}
#[test]
fn log_overrides_noop_in_stealth() {
let (_td, store) = stealth_store();
log_overrides(&store, &["--skip=jira".into()]).unwrap();
}
#[test]
fn log_overrides_amends_state_subject() {
let (_td, store) = git_store();
let sd = store.state_worktree_dir();
log_overrides(&store, &["--no-sync".into(), "--skip=jira".into()]).unwrap();
let subj =
String::from_utf8(raw_git(&sd, &["log", "-1", "--format=%s"]).stdout).unwrap();
assert!(subj.contains("[--no-sync] [--skip=jira]"), "got: {subj}");
}
#[test]
fn log_overrides_errors_when_amend_fails() {
let (_td, store) = git_store();
let sd = store.state_worktree_dir();
raw_git(&sd, &["checkout", "--orphan", "void"]);
let err = log_overrides(&store, &["--skip=jira".into()]).unwrap_err();
assert!(format!("{err}").contains("override log"), "got: {err}");
}
#[test]
fn finish_ok_when_no_plugins() {
let (_td, store) = git_store();
let t = task(&store, "bl-1234");
let rb = state_head(&store).unwrap();
let out = finish(
&store,
Some(&t),
&t,
Event::Update,
"alice",
&InvocationOverrides::default(),
&[],
Rollback::State(rb.as_deref()),
)
.unwrap();
assert!(out.skipped.is_empty());
}
#[test]
fn finish_rolls_state_back_on_dispatch_error() {
let (_td, store) = git_store();
let t = task(&store, "bl-1234");
let rb = state_head(&store).unwrap();
let sd = store.state_worktree_dir();
raw_git(&sd, &["commit", "--allow-empty", "-m", "advance"]);
std::fs::write(store.config_path(), "not json").unwrap();
let err = finish(
&store,
None,
&t,
Event::Update,
"a",
&InvocationOverrides::default(),
&[],
Rollback::State(rb.as_deref()),
)
.unwrap_err();
let _ = format!("{err}");
assert_eq!(
state_head(&store).unwrap().as_deref(),
rb.as_deref(),
"required failure rewinds the state branch"
);
}
#[test]
fn finish_error_with_state_none_is_noop_rollback() {
let (_td, store) = stealth_store();
let t = task(&store, "bl-1234");
std::fs::write(store.config_path(), "not json").unwrap();
let err = finish(
&store,
None,
&t,
Event::Update,
"a",
&InvocationOverrides::default(),
&[],
Rollback::State(None),
)
.unwrap_err();
let _ = format!("{err}");
}
#[test]
fn finish_error_with_dropclaim_attempts_unclaim() {
let (_td, store) = git_store();
let t = task(&store, "bl-1234");
std::fs::write(store.config_path(), "not json").unwrap();
let err = finish(
&store,
None,
&t,
Event::Claim,
"a",
&InvocationOverrides::default(),
&[],
Rollback::DropClaim,
)
.unwrap_err();
let _ = format!("{err}");
}