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#[test]
26fn export_produces_jsonl() {
27 let tmp = init_tmp();
28 create_task(&tmp, "First");
29 create_task(&tmp, "Second");
30
31 let out = td().arg("export").current_dir(&tmp).output().unwrap();
32 let stdout = String::from_utf8(out.stdout).unwrap();
33 let lines: Vec<&str> = stdout.lines().collect();
34 assert_eq!(lines.len(), 2, "expected 2 JSONL lines, got: {stdout}");
35
36 // Each line should be valid JSON with an id field.
37 for line in &lines {
38 let v: serde_json::Value = serde_json::from_str(line).unwrap();
39 assert!(v["id"].is_string());
40 }
41}
42
43#[test]
44fn export_includes_labels_and_blockers() {
45 let tmp = init_tmp();
46 td().args(["create", "With labels", "-l", "bug"])
47 .current_dir(&tmp)
48 .assert()
49 .success();
50
51 let out = td().arg("export").current_dir(&tmp).output().unwrap();
52 let line = String::from_utf8(out.stdout).unwrap();
53 let v: serde_json::Value = serde_json::from_str(line.trim()).unwrap();
54 assert!(v["labels"].is_array());
55 assert!(v["blockers"].is_array());
56}
57
58#[test]
59fn import_round_trips_with_export() {
60 let tmp = init_tmp();
61 create_task(&tmp, "Alpha");
62
63 td().args(["create", "Bravo", "-l", "important"])
64 .current_dir(&tmp)
65 .assert()
66 .success();
67
68 // Export.
69 let export_out = td().arg("export").current_dir(&tmp).output().unwrap();
70 let exported = String::from_utf8(export_out.stdout).unwrap();
71
72 // Write to a file.
73 let export_file = tmp.path().join("backup.jsonl");
74 std::fs::write(&export_file, &exported).unwrap();
75
76 // Create a fresh directory, init, import.
77 let tmp2 = TempDir::new().unwrap();
78 td().arg("init").current_dir(&tmp2).assert().success();
79
80 td().args(["import", export_file.to_str().unwrap()])
81 .current_dir(&tmp2)
82 .assert()
83 .success()
84 .stderr(predicate::str::contains("import complete"));
85
86 // Verify tasks exist in the new database.
87 let out = td()
88 .args(["--json", "list"])
89 .current_dir(&tmp2)
90 .output()
91 .unwrap();
92 let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
93 let titles: Vec<&str> = v
94 .as_array()
95 .unwrap()
96 .iter()
97 .map(|t| t["title"].as_str().unwrap())
98 .collect();
99 assert!(titles.contains(&"Alpha"));
100 assert!(titles.contains(&"Bravo"));
101
102 // Verify labels survived.
103 let bravo = v
104 .as_array()
105 .unwrap()
106 .iter()
107 .find(|t| t["title"] == "Bravo")
108 .unwrap();
109 let labels = bravo["labels"].as_array().unwrap();
110 assert!(labels.contains(&serde_json::Value::String("important".into())));
111}
112
113#[test]
114fn export_import_preserves_effort() {
115 let tmp = init_tmp();
116
117 td().args(["create", "High effort", "-e", "high"])
118 .current_dir(&tmp)
119 .assert()
120 .success();
121
122 // Export.
123 let out = td().arg("export").current_dir(&tmp).output().unwrap();
124 let exported = String::from_utf8(out.stdout).unwrap();
125
126 // Verify effort is in the JSONL.
127 let v: serde_json::Value = serde_json::from_str(exported.trim()).unwrap();
128 assert_eq!(v["effort"].as_i64().unwrap(), 3);
129
130 // Round-trip into a fresh database.
131 let export_file = tmp.path().join("effort.jsonl");
132 std::fs::write(&export_file, &exported).unwrap();
133
134 let tmp2 = TempDir::new().unwrap();
135 td().arg("init").current_dir(&tmp2).assert().success();
136 td().args(["import", export_file.to_str().unwrap()])
137 .current_dir(&tmp2)
138 .assert()
139 .success();
140
141 let out2 = td()
142 .args(["--json", "list"])
143 .current_dir(&tmp2)
144 .output()
145 .unwrap();
146 let v2: serde_json::Value = serde_json::from_slice(&out2.stdout).unwrap();
147 assert_eq!(v2[0]["effort"].as_i64().unwrap(), 3);
148}