1//! Integration tests for the migration system.
2
3use assert_cmd::Command;
4use tempfile::TempDir;
5
6fn td() -> Command {
7 Command::cargo_bin("td").unwrap()
8}
9
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 fresh_init_sets_latest_version() {
18 let tmp = init_tmp();
19 let conn = rusqlite::Connection::open(tmp.path().join(".td/tasks.db")).unwrap();
20 let version: u32 = conn
21 .pragma_query_value(None, "user_version", |row| row.get(0))
22 .unwrap();
23 // Version should be 2 (migration 0001 + 0002).
24 assert_eq!(version, 2);
25}
26
27#[test]
28fn legacy_db_is_migrated_on_open() {
29 let tmp = TempDir::new().unwrap();
30 let td_dir = tmp.path().join(".td");
31 std::fs::create_dir_all(&td_dir).unwrap();
32
33 // Create a v0 database with the old schema (no effort column).
34 let conn = rusqlite::Connection::open(td_dir.join("tasks.db")).unwrap();
35 conn.execute_batch(
36 "CREATE TABLE tasks (
37 id TEXT PRIMARY KEY,
38 title TEXT NOT NULL,
39 description TEXT DEFAULT '',
40 type TEXT DEFAULT 'task',
41 priority INTEGER DEFAULT 2,
42 status TEXT DEFAULT 'open',
43 parent TEXT DEFAULT '',
44 created TEXT NOT NULL,
45 updated TEXT NOT NULL
46 );
47 CREATE TABLE labels (
48 task_id TEXT, label TEXT,
49 PRIMARY KEY (task_id, label),
50 FOREIGN KEY (task_id) REFERENCES tasks(id)
51 );
52 CREATE TABLE blockers (
53 task_id TEXT, blocker_id TEXT,
54 PRIMARY KEY (task_id, blocker_id),
55 FOREIGN KEY (task_id) REFERENCES tasks(id)
56 );
57 INSERT INTO tasks (id, title, created, updated)
58 VALUES ('td-legacy', 'Old task', '2024-01-01T00:00:00Z', '2024-01-01T00:00:00Z');",
59 )
60 .unwrap();
61 drop(conn);
62
63 // Opening via td (list) should migrate and succeed.
64 td().args(["--json", "list"])
65 .current_dir(&tmp)
66 .assert()
67 .success();
68
69 // Verify the task survived migration and got default effort.
70 let out = td()
71 .args(["--json", "show", "td-legacy"])
72 .current_dir(&tmp)
73 .output()
74 .unwrap();
75 let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
76 assert_eq!(v["title"].as_str().unwrap(), "Old task");
77 assert_eq!(v["effort"].as_i64().unwrap(), 2); // default medium
78
79 // Verify version is now latest.
80 let conn = rusqlite::Connection::open(td_dir.join("tasks.db")).unwrap();
81 let version: u32 = conn
82 .pragma_query_value(None, "user_version", |row| row.get(0))
83 .unwrap();
84 assert_eq!(version, 2);
85}
86
87#[test]
88fn effort_column_exists_after_init() {
89 let tmp = init_tmp();
90 let conn = rusqlite::Connection::open(tmp.path().join(".td/tasks.db")).unwrap();
91
92 // Verify the effort column is present by inserting a row that sets it.
93 conn.execute(
94 "INSERT INTO tasks (id, title, effort, created, updated) VALUES ('td-test', 'Test', 3, '2024-01-01T00:00:00Z', '2024-01-01T00:00:00Z')",
95 [],
96 )
97 .unwrap();
98
99 let effort: i32 = conn
100 .query_row("SELECT effort FROM tasks WHERE id = 'td-test'", [], |r| {
101 r.get(0)
102 })
103 .unwrap();
104 assert_eq!(effort, 3);
105}