Add end-to-end integration test
Assisted-by: Claude Opus 4.6 via pi
change xzyppnnnsnrrkrmywklkrxoxrzptutkp
commit 940ab47bd945ffcb4c6ddb491e4f644a54e0a910
author Alpha Chen <alpha@kejadlen.dev>
date
parent mpsnpwrt
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);
+}