cli_rm.rs

  1use assert_cmd::Command;
  2use predicates::prelude::*;
  3use tempfile::TempDir;
  4
  5fn td(home: &TempDir) -> Command {
  6    let mut cmd = Command::cargo_bin("td").unwrap();
  7    cmd.env("HOME", home.path());
  8    cmd
  9}
 10
 11fn init_tmp() -> TempDir {
 12    let tmp = TempDir::new().unwrap();
 13    td(&tmp)
 14        .args(["init", "main"])
 15        .current_dir(&tmp)
 16        .assert()
 17        .success();
 18    tmp
 19}
 20
 21fn create_task(dir: &TempDir, title: &str) -> String {
 22    let out = td(dir)
 23        .args(["--json", "create", title])
 24        .current_dir(dir)
 25        .output()
 26        .unwrap();
 27    let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
 28    v["id"].as_str().unwrap().to_string()
 29}
 30
 31fn get_task_json(dir: &TempDir, id: &str) -> serde_json::Value {
 32    let out = td(dir)
 33        .args(["--json", "show", id])
 34        .current_dir(dir)
 35        .output()
 36        .unwrap();
 37    serde_json::from_slice(&out.stdout).unwrap()
 38}
 39
 40#[test]
 41fn rm_deletes_task() {
 42    let tmp = init_tmp();
 43    let id = create_task(&tmp, "Delete me");
 44
 45    td(&tmp)
 46        .args(["rm", &id])
 47        .current_dir(&tmp)
 48        .assert()
 49        .success()
 50        .stdout(predicate::str::contains("deleted"));
 51
 52    let task = get_task_json(&tmp, &id);
 53    assert_eq!(task["status"].as_str().unwrap(), "closed");
 54}
 55
 56#[test]
 57fn rm_deletes_multiple_ids() {
 58    let tmp = init_tmp();
 59    let id1 = create_task(&tmp, "First");
 60    let id2 = create_task(&tmp, "Second");
 61
 62    td(&tmp)
 63        .args(["rm", &id1, &id2])
 64        .current_dir(&tmp)
 65        .assert()
 66        .success();
 67
 68    assert_eq!(get_task_json(&tmp, &id1)["status"], "closed");
 69    assert_eq!(get_task_json(&tmp, &id2)["status"], "closed");
 70}
 71
 72#[test]
 73fn rm_requires_recursive_for_parent_task() {
 74    let tmp = init_tmp();
 75    let parent = create_task(&tmp, "Parent");
 76    td(&tmp)
 77        .args(["create", "Child", "--parent", &parent])
 78        .current_dir(&tmp)
 79        .assert()
 80        .success();
 81
 82    td(&tmp)
 83        .args(["rm", &parent])
 84        .current_dir(&tmp)
 85        .assert()
 86        .failure()
 87        .stderr(predicate::str::contains("use --recursive"));
 88}
 89
 90#[test]
 91fn rm_recursive_deletes_subtree() {
 92    let tmp = init_tmp();
 93    let parent = create_task(&tmp, "Parent");
 94
 95    let out = td(&tmp)
 96        .args(["--json", "create", "Child", "--parent", &parent])
 97        .current_dir(&tmp)
 98        .output()
 99        .unwrap();
100    let child: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
101    let child_id = child["id"].as_str().unwrap().to_string();
102
103    let out = td(&tmp)
104        .args(["--json", "create", "Grandchild", "--parent", &child_id])
105        .current_dir(&tmp)
106        .output()
107        .unwrap();
108    let grandchild: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
109    let grandchild_id = grandchild["id"].as_str().unwrap().to_string();
110
111    td(&tmp)
112        .args(["rm", "--recursive", &parent])
113        .current_dir(&tmp)
114        .assert()
115        .success();
116
117    assert_eq!(get_task_json(&tmp, &parent)["status"], "closed");
118    assert_eq!(get_task_json(&tmp, &child_id)["status"], "closed");
119    assert_eq!(get_task_json(&tmp, &grandchild_id)["status"], "closed");
120}
121
122#[test]
123fn rm_detaches_dependents_and_warns() {
124    let tmp = init_tmp();
125    let dependent = create_task(&tmp, "Dependent");
126    let blocker = create_task(&tmp, "Blocker");
127
128    td(&tmp)
129        .args(["dep", "add", &dependent, &blocker])
130        .current_dir(&tmp)
131        .assert()
132        .success();
133
134    td(&tmp)
135        .args(["rm", &blocker])
136        .current_dir(&tmp)
137        .assert()
138        .success()
139        .stderr(predicate::str::contains("warning"))
140        .stderr(predicate::str::contains(&dependent));
141
142    let dependent_task = get_task_json(&tmp, &dependent);
143    let blockers = dependent_task["blockers"].as_array().unwrap();
144    assert!(blockers.is_empty());
145}
146
147#[test]
148fn rm_force_suppresses_unblocked_warning() {
149    let tmp = init_tmp();
150    let dependent = create_task(&tmp, "Dependent");
151    let blocker = create_task(&tmp, "Blocker");
152
153    td(&tmp)
154        .args(["dep", "add", &dependent, &blocker])
155        .current_dir(&tmp)
156        .assert()
157        .success();
158
159    td(&tmp)
160        .args(["rm", "--force", &blocker])
161        .current_dir(&tmp)
162        .assert()
163        .success()
164        .stderr(predicate::str::is_empty());
165}
166
167#[test]
168fn rm_json_includes_deleted_and_unblocked_ids() {
169    let tmp = init_tmp();
170    let dependent = create_task(&tmp, "Dependent");
171    let blocker = create_task(&tmp, "Blocker");
172
173    td(&tmp)
174        .args(["dep", "add", &dependent, &blocker])
175        .current_dir(&tmp)
176        .assert()
177        .success();
178
179    let out = td(&tmp)
180        .args(["--json", "rm", &blocker])
181        .current_dir(&tmp)
182        .output()
183        .unwrap();
184    assert!(out.status.success());
185
186    let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
187    let requested = v["requested_ids"].as_array().unwrap();
188    let deleted = v["deleted_ids"].as_array().unwrap();
189    let unblocked = v["unblocked_ids"].as_array().unwrap();
190
191    assert_eq!(requested, &vec![serde_json::Value::String(blocker.clone())]);
192    assert_eq!(deleted, &vec![serde_json::Value::String(blocker)]);
193    assert_eq!(unblocked, &vec![serde_json::Value::String(dependent)]);
194}