1use assert_cmd::Command;
2use predicates::prelude::*;
3use tempfile::TempDir;
4
5fn td() -> Command {
6 Command::cargo_bin("td").unwrap()
7}
8
9/// Initialise a temp directory and return it.
10fn init_tmp() -> TempDir {
11 let tmp = TempDir::new().unwrap();
12 td().arg("init").current_dir(&tmp).assert().success();
13 tmp
14}
15
16#[test]
17fn create_prints_id_and_title() {
18 let tmp = init_tmp();
19
20 td().args(["create", "My first task"])
21 .current_dir(&tmp)
22 .assert()
23 .success()
24 .stdout(predicate::str::contains("My first task"));
25}
26
27#[test]
28fn create_json_returns_task_object() {
29 let tmp = init_tmp();
30
31 td().args(["--json", "create", "Buy milk"])
32 .current_dir(&tmp)
33 .assert()
34 .success()
35 .stdout(predicate::str::contains(r#""title":"Buy milk"#))
36 .stdout(predicate::str::contains(r#""status":"open"#))
37 .stdout(predicate::str::contains(r#""priority":2"#));
38}
39
40#[test]
41fn create_with_priority_and_type() {
42 let tmp = init_tmp();
43
44 td().args(["--json", "create", "Urgent bug", "-p", "high", "-t", "bug"])
45 .current_dir(&tmp)
46 .assert()
47 .success()
48 .stdout(predicate::str::contains(r#""priority":1"#))
49 .stdout(predicate::str::contains(r#""type":"bug"#));
50}
51
52#[test]
53fn create_with_description() {
54 let tmp = init_tmp();
55
56 td().args([
57 "--json",
58 "create",
59 "Fix login",
60 "-d",
61 "The login page is broken",
62 ])
63 .current_dir(&tmp)
64 .assert()
65 .success()
66 .stdout(predicate::str::contains("The login page is broken"));
67}
68
69#[test]
70fn create_with_labels() {
71 let tmp = init_tmp();
72
73 td().args(["--json", "create", "Labelled task", "-l", "frontend,urgent"])
74 .current_dir(&tmp)
75 .assert()
76 .success();
77
78 // Verify labels are stored by checking the database directly.
79 let conn = rusqlite::Connection::open(tmp.path().join(".td/tasks.db")).unwrap();
80 let count: i64 = conn
81 .query_row("SELECT COUNT(*) FROM labels", [], |r| r.get(0))
82 .unwrap();
83 assert_eq!(count, 2);
84}
85
86#[test]
87fn create_requires_title() {
88 let tmp = init_tmp();
89
90 td().arg("create")
91 .current_dir(&tmp)
92 .assert()
93 .failure()
94 .stderr(predicate::str::contains("title required"));
95}
96
97#[test]
98fn create_subtask_under_parent() {
99 let tmp = init_tmp();
100
101 // Create parent, extract its id.
102 let parent_out = td()
103 .args(["--json", "create", "Parent task"])
104 .current_dir(&tmp)
105 .output()
106 .unwrap();
107 let parent: serde_json::Value = serde_json::from_slice(&parent_out.stdout).unwrap();
108 let parent_id = parent["id"].as_str().unwrap();
109
110 // Create child under parent.
111 let child_out = td()
112 .args(["--json", "create", "Child task", "--parent", parent_id])
113 .current_dir(&tmp)
114 .output()
115 .unwrap();
116 let child: serde_json::Value = serde_json::from_slice(&child_out.stdout).unwrap();
117 let child_id = child["id"].as_str().unwrap();
118
119 // Child id should start with parent id.
120 assert!(
121 child_id.starts_with(parent_id),
122 "child id '{child_id}' should start with parent id '{parent_id}'"
123 );
124 assert_eq!(child["parent"].as_str().unwrap(), parent_id);
125}
126
127#[test]
128fn create_with_effort() {
129 let tmp = init_tmp();
130
131 let out = td()
132 .args(["--json", "create", "Hard task", "-e", "high"])
133 .current_dir(&tmp)
134 .output()
135 .unwrap();
136 let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
137 assert_eq!(v["effort"].as_i64().unwrap(), 3);
138}
139
140#[test]
141fn create_with_priority_label() {
142 let tmp = init_tmp();
143
144 let out = td()
145 .args(["--json", "create", "Low prio", "-p", "low"])
146 .current_dir(&tmp)
147 .output()
148 .unwrap();
149 let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
150 assert_eq!(v["priority"].as_i64().unwrap(), 3);
151}
152
153#[test]
154fn create_rejects_invalid_priority() {
155 let tmp = init_tmp();
156
157 td().args(["create", "Bad", "-p", "urgent"])
158 .current_dir(&tmp)
159 .assert()
160 .failure()
161 .stderr(predicates::prelude::predicate::str::contains(
162 "invalid priority",
163 ));
164}
165
166#[test]
167fn create_rejects_invalid_effort() {
168 let tmp = init_tmp();
169
170 td().args(["create", "Bad", "-e", "huge"])
171 .current_dir(&tmp)
172 .assert()
173 .failure()
174 .stderr(predicates::prelude::predicate::str::contains(
175 "invalid effort",
176 ));
177}