cli_query.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
 25// ── search ───────────────────────────────────────────────────────────
 26
 27#[test]
 28fn search_matches_title() {
 29    let tmp = init_tmp();
 30    create_task(&tmp, "Fix login page");
 31    create_task(&tmp, "Update docs");
 32
 33    td().args(["search", "login"])
 34        .current_dir(&tmp)
 35        .assert()
 36        .success()
 37        .stdout(predicate::str::contains("Fix login page"));
 38}
 39
 40#[test]
 41fn search_matches_description() {
 42    let tmp = init_tmp();
 43
 44    td().args(["create", "Vague title", "-d", "The frobnicator is broken"])
 45        .current_dir(&tmp)
 46        .assert()
 47        .success();
 48
 49    td().args(["search", "frobnicator"])
 50        .current_dir(&tmp)
 51        .assert()
 52        .success()
 53        .stdout(predicate::str::contains("Vague title"));
 54}
 55
 56#[test]
 57fn search_json_returns_array() {
 58    let tmp = init_tmp();
 59    create_task(&tmp, "Needle in haystack");
 60
 61    let out = td()
 62        .args(["--json", "search", "Needle"])
 63        .current_dir(&tmp)
 64        .output()
 65        .unwrap();
 66    let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
 67    assert!(v.is_array());
 68    assert_eq!(v[0]["title"].as_str().unwrap(), "Needle in haystack");
 69}
 70
 71// ── ready ────────────────────────────────────────────────────────────
 72
 73#[test]
 74fn ready_excludes_blocked_tasks() {
 75    let tmp = init_tmp();
 76    let a = create_task(&tmp, "Ready task");
 77    let b = create_task(&tmp, "Blocked task");
 78    let c = create_task(&tmp, "Blocker task");
 79
 80    td().args(["dep", "add", &b, &c])
 81        .current_dir(&tmp)
 82        .assert()
 83        .success();
 84
 85    let out = td()
 86        .args(["--json", "ready"])
 87        .current_dir(&tmp)
 88        .output()
 89        .unwrap();
 90    let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
 91    let titles: Vec<&str> = v
 92        .as_array()
 93        .unwrap()
 94        .iter()
 95        .map(|t| t["title"].as_str().unwrap())
 96        .collect();
 97
 98    assert!(titles.contains(&"Ready task"));
 99    assert!(titles.contains(&"Blocker task"));
100    assert!(!titles.contains(&"Blocked task"));
101
102    // Close the blocker — now the blocked task should become ready.
103    td().args(["done", &c]).current_dir(&tmp).assert().success();
104
105    let out = td()
106        .args(["--json", "ready"])
107        .current_dir(&tmp)
108        .output()
109        .unwrap();
110    let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
111    let titles: Vec<&str> = v
112        .as_array()
113        .unwrap()
114        .iter()
115        .map(|t| t["title"].as_str().unwrap())
116        .collect();
117    assert!(titles.contains(&"Blocked task"));
118    // a is still ready
119    assert!(titles.contains(&"Ready task"));
120}
121
122// ── stats ────────────────────────────────────────────────────────────
123
124#[test]
125fn stats_counts_tasks() {
126    let tmp = init_tmp();
127    let id = create_task(&tmp, "Open one");
128    create_task(&tmp, "Open two");
129    td().args(["done", &id])
130        .current_dir(&tmp)
131        .assert()
132        .success();
133
134    let out = td().args(["stats"]).current_dir(&tmp).output().unwrap();
135    let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
136    assert_eq!(v["total"].as_i64().unwrap(), 2);
137    assert_eq!(v["open"].as_i64().unwrap(), 1);
138    assert_eq!(v["closed"].as_i64().unwrap(), 1);
139}
140
141// ── compact ──────────────────────────────────────────────────────────
142
143#[test]
144fn compact_succeeds() {
145    let tmp = init_tmp();
146    create_task(&tmp, "Anything");
147
148    td().arg("compact")
149        .current_dir(&tmp)
150        .assert()
151        .success()
152        .stderr(predicate::str::contains("done"));
153}