Add end-to-end integration test
Assisted-by: Claude Opus 4.6 via pi
diff --git a/Cargo.lock b/Cargo.lock
index 1c9ee71..76b8add 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -73,6 +73,21 @@ version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
+[[package]]
+name = "assert_cmd"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c5bcfa8749ac45dd12cb11055aeeb6b27a3895560d60d71e3c23bf979e60514"
+dependencies = [
+ "anstyle",
+ "bstr",
+ "libc",
+ "predicates",
+ "predicates-core",
+ "predicates-tree",
+ "wait-timeout",
+]
+
[[package]]
name = "atoi"
version = "2.0.0"
@@ -118,6 +133,17 @@ dependencies = [
"generic-array",
]
+[[package]]
+name = "bstr"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
+dependencies = [
+ "memchr",
+ "regex-automata",
+ "serde",
+]
+
[[package]]
name = "bumpalo"
version = "3.20.2"
@@ -293,6 +319,12 @@ dependencies = [
"zeroize",
]
+[[package]]
+name = "difflib"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
+
[[package]]
name = "digest"
version = "0.10.7"
@@ -1018,6 +1050,33 @@ dependencies = [
"zerocopy",
]
+[[package]]
+name = "predicates"
+version = "3.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe"
+dependencies = [
+ "anstyle",
+ "difflib",
+ "predicates-core",
+]
+
+[[package]]
+name = "predicates-core"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144"
+
+[[package]]
+name = "predicates-tree"
+version = "1.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2"
+dependencies = [
+ "predicates-core",
+ "termtree",
+]
+
[[package]]
name = "prettyplease"
version = "0.2.37"
@@ -1116,10 +1175,12 @@ name = "ranger-cli"
version = "0.1.0"
dependencies = [
"anyhow",
+ "assert_cmd",
"clap",
"ranger-lib",
"serde",
"serde_json",
+ "tempfile",
"tokio",
"xdg",
]
@@ -1156,6 +1217,12 @@ dependencies = [
"bitflags",
]
+[[package]]
+name = "regex-automata"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
+
[[package]]
name = "rsa"
version = "0.9.10"
@@ -1612,6 +1679,12 @@ dependencies = [
"windows-sys 0.61.2",
]
+[[package]]
+name = "termtree"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683"
+
[[package]]
name = "thiserror"
version = "2.0.18"
@@ -1803,6 +1876,15 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+[[package]]
+name = "wait-timeout"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
diff --git a/crates/ranger-cli/Cargo.toml b/crates/ranger-cli/Cargo.toml
index 6fe1697..7d6f82f 100644
--- a/crates/ranger-cli/Cargo.toml
+++ b/crates/ranger-cli/Cargo.toml
@@ -15,3 +15,8 @@ serde = { version = "*", features = ["derive"] }
serde_json = "*"
anyhow = "*"
xdg = "*"
+
+[dev-dependencies]
+assert_cmd = "*"
+serde_json = "*"
+tempfile = "*"
diff --git a/crates/ranger-cli/tests/cli.rs b/crates/ranger-cli/tests/cli.rs
new file mode 100644
index 0000000..dd4e6d3
--- /dev/null
+++ b/crates/ranger-cli/tests/cli.rs
@@ -0,0 +1,135 @@
+use assert_cmd::cargo::cargo_bin_cmd;
+use assert_cmd::Command;
+use tempfile::tempdir;
+
+fn ranger(db_path: &str) -> Command {
+ let mut cmd = Command::from(cargo_bin_cmd!("ranger"));
+ cmd.env("RANGER_DB", db_path);
+ cmd
+}
+
+#[test]
+fn full_workflow() {
+ let dir = tempdir().unwrap();
+ let db = dir.path().join("test.db");
+ let db_path = db.to_str().unwrap();
+
+ // Create a backlog
+ let output = ranger(db_path)
+ .args(["backlog", "create", "Ranger"])
+ .output()
+ .unwrap();
+ assert!(output.status.success());
+ let stdout = String::from_utf8(output.stdout).unwrap();
+ assert!(stdout.contains("Ranger"));
+
+ // List backlogs (JSON) and extract key
+ let output = ranger(db_path)
+ .args(["backlog", "list", "--json"])
+ .output()
+ .unwrap();
+ assert!(output.status.success());
+ let backlogs: serde_json::Value =
+ serde_json::from_slice(&output.stdout).unwrap();
+ let backlog_key = backlogs[0]["key"].as_str().unwrap().to_string();
+ let bl_prefix = &backlog_key[..4];
+
+ // Create tasks
+ let output = ranger(db_path)
+ .args(["task", "create", "First task", "--backlog", bl_prefix, "--state", "queued"])
+ .output()
+ .unwrap();
+ assert!(output.status.success());
+
+ let output = ranger(db_path)
+ .args(["task", "create", "Second task", "--backlog", bl_prefix, "--tag", "urgent"])
+ .output()
+ .unwrap();
+ assert!(output.status.success());
+
+ // List tasks (JSON) and verify ordering
+ let output = ranger(db_path)
+ .args(["task", "list", "--backlog", bl_prefix, "--json"])
+ .output()
+ .unwrap();
+ assert!(output.status.success());
+ let tasks: serde_json::Value =
+ serde_json::from_slice(&output.stdout).unwrap();
+ let tasks = tasks.as_array().unwrap();
+ assert_eq!(tasks.len(), 2);
+ assert_eq!(tasks[0]["title"], "First task");
+ assert_eq!(tasks[1]["title"], "Second task");
+
+ let t1_key = tasks[0]["key"].as_str().unwrap().to_string();
+ let t2_key = tasks[1]["key"].as_str().unwrap().to_string();
+
+ // Edit task state
+ let output = ranger(db_path)
+ .args(["task", "edit", &t1_key[..4], "--state", "in_progress"])
+ .output()
+ .unwrap();
+ assert!(output.status.success());
+ let stdout = String::from_utf8(output.stdout).unwrap();
+ assert!(stdout.contains("in_progress"));
+
+ // Add a comment
+ let output = ranger(db_path)
+ .args(["comment", "add", &t1_key[..4], "Started working on this"])
+ .output()
+ .unwrap();
+ assert!(output.status.success());
+
+ // List comments
+ let output = ranger(db_path)
+ .args(["comment", "list", &t1_key[..4]])
+ .output()
+ .unwrap();
+ assert!(output.status.success());
+ let stdout = String::from_utf8(output.stdout).unwrap();
+ assert!(stdout.contains("Started working on this"));
+
+ // Add a blocker
+ let output = ranger(db_path)
+ .args(["blocker", "add", &t2_key[..4], &t1_key[..4]])
+ .output()
+ .unwrap();
+ assert!(output.status.success());
+
+ // List tags
+ let output = ranger(db_path)
+ .args(["tag", "list"])
+ .output()
+ .unwrap();
+ assert!(output.status.success());
+ let stdout = String::from_utf8(output.stdout).unwrap();
+ assert!(stdout.contains("urgent"));
+
+ // Show task (JSON) — verify all data present
+ let output = ranger(db_path)
+ .args(["task", "show", &t2_key[..4], "--json"])
+ .output()
+ .unwrap();
+ assert!(output.status.success());
+ let detail: serde_json::Value =
+ serde_json::from_slice(&output.stdout).unwrap();
+ assert_eq!(detail["task"]["title"], "Second task");
+ assert_eq!(detail["tags"][0]["name"], "urgent");
+ assert_eq!(detail["blockers"].as_array().unwrap().len(), 1);
+
+ // Delete a task
+ let output = ranger(db_path)
+ .args(["task", "delete", &t2_key[..4]])
+ .output()
+ .unwrap();
+ assert!(output.status.success());
+
+ // Verify deletion
+ let output = ranger(db_path)
+ .args(["task", "list", "--backlog", bl_prefix, "--json"])
+ .output()
+ .unwrap();
+ assert!(output.status.success());
+ let tasks: serde_json::Value =
+ serde_json::from_slice(&output.stdout).unwrap();
+ assert_eq!(tasks.as_array().unwrap().len(), 1);
+}