cli_rm.rs

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