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}