mod common;
use common::cli::{BrWorkspace, extract_json_payload, parse_list_issues, run_br};
use serde_json::Value;
use std::fs;
fn parse_created_id(stdout: &str) -> String {
let line = stdout.lines().next().unwrap_or("");
let normalized = line.strip_prefix("✓ Created ").unwrap_or(line);
normalized
.split(':')
.next()
.unwrap_or("")
.trim()
.to_string()
}
fn assert_quiet_command<const N: usize>(
workspace: &BrWorkspace,
args: [&str; N],
label: &str,
description: &str,
) {
let result = run_br(workspace, args, label);
assert!(
result.status.success(),
"{description} failed: {}",
result.stderr
);
assert!(
result.stdout.trim().is_empty(),
"{description} should produce no stdout: '{}'",
result.stdout
);
}
fn run_quiet_json<const N: usize>(
workspace: &BrWorkspace,
args: [&str; N],
label: &str,
description: &str,
) -> Value {
let result = run_br(workspace, args, label);
assert!(
result.status.success(),
"{description} failed: {}",
result.stderr
);
let payload = extract_json_payload(&result.stdout);
serde_json::from_str(&payload).expect(description)
}
#[test]
fn e2e_json_flag_list() {
let _log = common::test_log("e2e_json_flag_list");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(&workspace, ["create", "JSON test issue"], "create");
assert!(create.status.success(), "create failed: {}", create.stderr);
let list = run_br(&workspace, ["list", "--json"], "list_json");
assert!(list.status.success(), "list --json failed: {}", list.stderr);
let json = parse_list_issues(&list.stdout);
assert!(!json.is_empty(), "JSON list should not be empty");
assert!(
json.iter().any(|item| item["title"] == "JSON test issue"),
"issue should be in JSON output"
);
}
#[test]
fn e2e_json_flag_show() {
let _log = common::test_log("e2e_json_flag_show");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(&workspace, ["create", "Show JSON test"], "create");
assert!(create.status.success(), "create failed: {}", create.stderr);
let id = create
.stdout
.lines()
.next()
.unwrap_or("")
.strip_prefix("✓ Created ")
.and_then(|s| s.split(':').next())
.unwrap_or("")
.trim();
let show = run_br(&workspace, ["show", id, "--json"], "show_json");
assert!(show.status.success(), "show --json failed: {}", show.stderr);
let payload = extract_json_payload(&show.stdout);
let json: Vec<Value> = serde_json::from_str(&payload).expect("valid JSON");
assert_eq!(json[0]["title"], "Show JSON test");
}
#[test]
fn e2e_json_flag_ready() {
let _log = common::test_log("e2e_json_flag_ready");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(&workspace, ["create", "Ready JSON test"], "create");
assert!(create.status.success(), "create failed: {}", create.stderr);
let ready = run_br(&workspace, ["ready", "--json"], "ready_json");
assert!(
ready.status.success(),
"ready --json failed: {}",
ready.stderr
);
let payload = extract_json_payload(&ready.stdout);
let _json: Vec<Value> = serde_json::from_str(&payload).expect("valid JSON array");
}
#[test]
fn e2e_json_flag_blocked() {
let _log = common::test_log("e2e_json_flag_blocked");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let blocked = run_br(&workspace, ["blocked", "--json"], "blocked_json");
assert!(
blocked.status.success(),
"blocked --json failed: {}",
blocked.stderr
);
let payload = extract_json_payload(&blocked.stdout);
let json: Value = serde_json::from_str(&payload).expect("valid JSON");
assert!(json.is_array());
}
#[test]
fn e2e_json_flag_stats() {
let _log = common::test_log("e2e_json_flag_stats");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(&workspace, ["create", "Stats JSON test"], "create");
assert!(create.status.success(), "create failed: {}", create.stderr);
let stats = run_br(&workspace, ["stats", "--json"], "stats_json");
assert!(
stats.status.success(),
"stats --json failed: {}",
stats.stderr
);
let payload = extract_json_payload(&stats.stdout);
let json: Value = serde_json::from_str(&payload).expect("valid JSON");
assert!(
json.get("summary").is_some(),
"stats should have summary field: {json}"
);
let summary = &json["summary"];
assert!(
summary.get("total_issues").is_some() || summary.get("open_issues").is_some(),
"stats summary should have count fields: {summary}"
);
}
#[test]
fn e2e_robot_flag_list() {
let _log = common::test_log("e2e_robot_flag_list");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(&workspace, ["create", "Robot test issue"], "create");
assert!(create.status.success(), "create failed: {}", create.stderr);
let list = run_br(&workspace, ["list", "--json"], "list_json");
assert!(list.status.success(), "list --json failed: {}", list.stderr);
let payload = extract_json_payload(&list.stdout);
let json: Value = serde_json::from_str(&payload).expect("json mode should output valid JSON");
assert!(json.is_object(), "list should be JSON object envelope");
assert!(
json.get("issues").is_some(),
"list envelope should contain 'issues'"
);
}
#[test]
fn e2e_robot_flag_stderr_diagnostics() {
let _log = common::test_log("e2e_robot_flag_stderr_diagnostics");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(
&workspace,
["create", "Robot stderr test", "--no-auto-flush"],
"create",
);
assert!(create.status.success(), "create failed: {}", create.stderr);
let sync = run_br(&workspace, ["sync", "--flush-only", "--json"], "sync_json");
assert!(sync.status.success(), "sync --json failed: {}", sync.stderr);
let payload = extract_json_payload(&sync.stdout);
let json: Value =
serde_json::from_str(&payload).expect("json mode stdout should be valid JSON");
assert!(
json.get("exported_issues").is_some(),
"JSON output should have exported_issues field"
);
}
#[test]
fn e2e_robot_flag_sync_flush_outputs_json() {
let _log = common::test_log("e2e_robot_flag_sync_flush_outputs_json");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(
&workspace,
["create", "Robot sync flush test", "--no-auto-flush"],
"create",
);
assert!(create.status.success(), "create failed: {}", create.stderr);
let sync = run_br(
&workspace,
["sync", "--flush-only", "--robot"],
"sync_robot_flush",
);
assert!(
sync.status.success(),
"sync --robot failed: {}",
sync.stderr
);
let payload = extract_json_payload(&sync.stdout);
let json: Value = serde_json::from_str(&payload).expect("robot mode should output valid JSON");
assert_eq!(json["exported_issues"].as_u64(), Some(1));
}
#[test]
fn e2e_no_color_flag() {
let _log = common::test_log("e2e_no_color_flag");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(&workspace, ["create", "No-color test"], "create");
assert!(create.status.success(), "create failed: {}", create.stderr);
let list = run_br(&workspace, ["list", "--no-color"], "list_no_color");
assert!(
list.status.success(),
"list --no-color failed: {}",
list.stderr
);
assert!(
!list.stdout.contains("\x1b["),
"output should not contain ANSI escape codes with --no-color"
);
}
#[test]
fn e2e_no_color_env_var() {
let _log = common::test_log("e2e_no_color_env_var");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(&workspace, ["create", "NO_COLOR env test"], "create");
assert!(create.status.success(), "create failed: {}", create.stderr);
let list = run_br(&workspace, ["list"], "list_with_no_color_env");
assert!(list.status.success(), "list failed: {}", list.stderr);
assert!(
!list.stdout.contains("\x1b["),
"output should not contain ANSI escape codes with NO_COLOR env"
);
}
#[test]
fn e2e_env_output_format_json_defaults_count_to_structured_output() {
let _log = common::test_log("e2e_env_output_format_json_defaults_count_to_structured_output");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(&workspace, ["create", "Env default JSON count"], "create");
assert!(create.status.success(), "create failed: {}", create.stderr);
let count = common::cli::run_br_with_env(
&workspace,
["count"],
[("BR_OUTPUT_FORMAT", "json")],
"count_env_json",
);
assert!(
count.status.success(),
"count with BR_OUTPUT_FORMAT=json failed: {}",
count.stderr
);
let payload = extract_json_payload(&count.stdout);
let json: Value = serde_json::from_str(&payload).expect("valid JSON count output");
assert!(
json.is_object(),
"count env-json output should be an object"
);
assert_eq!(json["count"], 1);
}
#[test]
fn e2e_quiet_overrides_env_json_for_list() {
let _log = common::test_log("e2e_quiet_overrides_env_json_for_list");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(&workspace, ["create", "Quiet env JSON list"], "create");
assert!(create.status.success(), "create failed: {}", create.stderr);
let list = common::cli::run_br_with_env(
&workspace,
["list", "--quiet"],
[("BR_OUTPUT_FORMAT", "json")],
"list_quiet_env_json",
);
assert!(
list.status.success(),
"list --quiet with BR_OUTPUT_FORMAT=json failed: {}",
list.stderr
);
assert!(
list.stdout.trim().is_empty(),
"quiet list should not emit env-selected JSON: {}",
list.stdout
);
}
#[test]
fn e2e_no_db_flag_list() {
let _log = common::test_log("e2e_no_db_flag_list");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(&workspace, ["create", "No-DB list test"], "create");
assert!(create.status.success(), "create failed: {}", create.stderr);
let sync = run_br(&workspace, ["sync", "--flush-only"], "sync_flush");
assert!(sync.status.success(), "sync flush failed: {}", sync.stderr);
let list = run_br(&workspace, ["--no-db", "list", "--json"], "list_no_db");
assert!(
list.status.success(),
"list --no-db failed: {}",
list.stderr
);
let json = parse_list_issues(&list.stdout);
assert!(
json.iter().any(|item| item["title"] == "No-DB list test"),
"issue should be visible in no-db mode"
);
}
#[test]
fn e2e_no_db_flag_show() {
let _log = common::test_log("e2e_no_db_flag_show");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(&workspace, ["create", "No-DB show test"], "create");
assert!(create.status.success(), "create failed: {}", create.stderr);
let id = create
.stdout
.lines()
.next()
.unwrap_or("")
.strip_prefix("✓ Created ")
.and_then(|s| s.split(':').next())
.unwrap_or("")
.trim();
let sync = run_br(&workspace, ["sync", "--flush-only"], "sync_flush");
assert!(sync.status.success(), "sync flush failed: {}", sync.stderr);
let show = run_br(&workspace, ["--no-db", "show", id, "--json"], "show_no_db");
assert!(
show.status.success(),
"show --no-db failed: {}",
show.stderr
);
let payload = extract_json_payload(&show.stdout);
let json: Vec<Value> = serde_json::from_str(&payload).expect("valid JSON");
assert_eq!(json[0]["title"], "No-DB show test");
}
#[test]
fn e2e_no_db_show_bypasses_corrupt_db_and_preserves_relations() {
let _log = common::test_log("e2e_no_db_show_bypasses_corrupt_db_and_preserves_relations");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let parent = run_br(&workspace, ["create", "Parent issue"], "create_parent");
assert!(
parent.status.success(),
"create parent failed: {}",
parent.stderr
);
let parent_id = parent
.stdout
.lines()
.next()
.unwrap_or("")
.strip_prefix("✓ Created ")
.and_then(|s| s.split(':').next())
.unwrap_or("")
.trim()
.to_string();
let child = run_br(&workspace, ["create", "Child issue"], "create_child");
assert!(
child.status.success(),
"create child failed: {}",
child.stderr
);
let child_id = child
.stdout
.lines()
.next()
.unwrap_or("")
.strip_prefix("✓ Created ")
.and_then(|s| s.split(':').next())
.unwrap_or("")
.trim()
.to_string();
let dep = run_br(
&workspace,
[
"dep",
"add",
&child_id,
&parent_id,
"--type",
"parent-child",
],
"dep_add_parent_child",
);
assert!(dep.status.success(), "dep add failed: {}", dep.stderr);
let sync = run_br(&workspace, ["sync", "--flush-only"], "sync_flush");
assert!(sync.status.success(), "sync flush failed: {}", sync.stderr);
fs::write(
workspace.root.join(".beads").join("beads.db"),
b"not a sqlite db",
)
.expect("corrupt db for no-db regression");
let show_child = run_br(
&workspace,
["--no-db", "show", &child_id, "--json"],
"show_no_db_corrupt_child",
);
assert!(
show_child.status.success(),
"show --no-db on corrupt db failed: {}",
show_child.stderr
);
let child_payload = extract_json_payload(&show_child.stdout);
let child_json: Vec<Value> = serde_json::from_str(&child_payload).expect("valid child JSON");
assert_eq!(child_json[0]["parent"], parent_id);
let show_parent = run_br(
&workspace,
["--no-db", "show", &parent_id, "--json"],
"show_no_db_corrupt_parent",
);
assert!(
show_parent.status.success(),
"parent show --no-db on corrupt db failed: {}",
show_parent.stderr
);
let parent_payload = extract_json_payload(&show_parent.stdout);
let parent_json: Vec<Value> = serde_json::from_str(&parent_payload).expect("valid parent JSON");
assert_eq!(parent_json[0]["dependents"][0]["id"], child_id);
assert_eq!(
parent_json[0]["dependents"][0]["dependency_type"],
"parent-child"
);
}
#[test]
fn e2e_no_db_flag_ready() {
let _log = common::test_log("e2e_no_db_flag_ready");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(&workspace, ["create", "No-DB ready test"], "create");
assert!(create.status.success(), "create failed: {}", create.stderr);
let sync = run_br(&workspace, ["sync", "--flush-only"], "sync_flush");
assert!(sync.status.success(), "sync flush failed: {}", sync.stderr);
let ready = run_br(&workspace, ["--no-db", "ready", "--json"], "ready_no_db");
assert!(
ready.status.success(),
"ready --no-db failed: {}",
ready.stderr
);
let payload = extract_json_payload(&ready.stdout);
let _json: Vec<Value> = serde_json::from_str(&payload).expect("valid JSON");
}
#[test]
fn e2e_no_db_hard_delete_flushes_jsonl() {
let _log = common::test_log("e2e_no_db_hard_delete_flushes_jsonl");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(&workspace, ["create", "No-DB hard delete test"], "create");
assert!(create.status.success(), "create failed: {}", create.stderr);
let issue_id = create
.stdout
.lines()
.next()
.unwrap_or("")
.strip_prefix("✓ Created ")
.and_then(|s| s.split(':').next())
.unwrap_or("")
.trim()
.to_string();
assert!(!issue_id.is_empty(), "expected created issue id in stdout");
let sync = run_br(&workspace, ["sync", "--flush-only"], "sync_flush");
assert!(sync.status.success(), "sync flush failed: {}", sync.stderr);
let jsonl_path = workspace.root.join(".beads").join("issues.jsonl");
let before = fs::read_to_string(&jsonl_path).expect("read jsonl before delete");
assert!(
before.contains(&format!("\"id\":\"{issue_id}\"")),
"issue should be present in JSONL before delete"
);
let delete = run_br(
&workspace,
["--no-db", "delete", &issue_id, "--hard"],
"delete_no_db_hard",
);
assert!(
delete.status.success(),
"--no-db delete --hard failed: {}",
delete.stderr
);
let after = fs::read_to_string(&jsonl_path).expect("read jsonl after delete");
assert!(
!after.contains(&format!("\"id\":\"{issue_id}\"")),
"hard delete in --no-db mode should remove the issue from JSONL"
);
}
#[test]
fn e2e_allow_stale_flag() {
let _log = common::test_log("e2e_allow_stale_flag");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(&workspace, ["create", "Stale test"], "create");
assert!(create.status.success(), "create failed: {}", create.stderr);
let sync = run_br(&workspace, ["sync", "--flush-only"], "sync_flush");
assert!(sync.status.success(), "sync flush failed: {}", sync.stderr);
let jsonl_path = workspace.root.join(".beads").join("issues.jsonl");
let contents = fs::read_to_string(&jsonl_path).expect("read jsonl");
fs::write(&jsonl_path, format!("{}\n", contents.trim())).expect("write jsonl");
let list = run_br(&workspace, ["--allow-stale", "list"], "list_allow_stale");
assert!(
list.status.success(),
"list --allow-stale failed: {}",
list.stderr
);
}
#[test]
fn e2e_no_auto_import_flag() {
let _log = common::test_log("e2e_no_auto_import_flag");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(&workspace, ["create", "Auto-import test"], "create");
assert!(create.status.success(), "create failed: {}", create.stderr);
let sync = run_br(&workspace, ["sync", "--flush-only"], "sync_flush");
assert!(sync.status.success(), "sync flush failed: {}", sync.stderr);
let jsonl_path = workspace.root.join(".beads").join("issues.jsonl");
let contents = fs::read_to_string(&jsonl_path).expect("read jsonl");
fs::write(&jsonl_path, format!("{}\n", contents.trim())).expect("write jsonl");
let list = run_br(
&workspace,
["--no-auto-import", "list"],
"list_no_auto_import",
);
assert!(
list.status.success(),
"list --no-auto-import failed: {}",
list.stderr
);
}
#[test]
fn e2e_no_auto_flush_flag() {
let _log = common::test_log("e2e_no_auto_flush_flag");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(
&workspace,
["create", "No auto-flush test", "--no-auto-flush"],
"create",
);
assert!(create.status.success(), "create failed: {}", create.stderr);
let jsonl_path = workspace.root.join(".beads").join("issues.jsonl");
if jsonl_path.exists() {
let contents = fs::read_to_string(&jsonl_path).expect("read jsonl");
if contents.contains("No auto-flush test") {
}
}
let sync = run_br(&workspace, ["sync", "--flush-only"], "sync_flush");
assert!(sync.status.success(), "sync flush failed: {}", sync.stderr);
let contents = fs::read_to_string(&jsonl_path).expect("read jsonl");
assert!(
contents.contains("No auto-flush test"),
"issue should be in JSONL after explicit flush"
);
}
#[test]
fn e2e_no_auto_flush_from_project_config() {
let _log = common::test_log("e2e_no_auto_flush_from_project_config");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let config_path = workspace.root.join(".beads").join("config.yaml");
fs::write(&config_path, "sync:\n auto_flush: false\n").expect("write config");
let create = run_br(
&workspace,
["create", "No auto-flush from config"],
"create_with_config",
);
assert!(create.status.success(), "create failed: {}", create.stderr);
let jsonl_path = workspace.root.join(".beads").join("issues.jsonl");
if jsonl_path.exists() {
let contents = fs::read_to_string(&jsonl_path).expect("read jsonl");
assert!(
!contents.contains("No auto-flush from config"),
"issue should not be in JSONL before explicit flush when sync.auto_flush=false"
);
}
let sync = run_br(&workspace, ["sync", "--flush-only"], "sync_flush");
assert!(sync.status.success(), "sync flush failed: {}", sync.stderr);
let contents = fs::read_to_string(&jsonl_path).expect("read jsonl");
assert!(
contents.contains("No auto-flush from config"),
"issue should be in JSONL after explicit flush"
);
}
#[test]
fn e2e_no_auto_flush_config_hyphen_variant() {
let _log = common::test_log("e2e_no_auto_flush_config_hyphen_variant");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let config_path = workspace.root.join(".beads").join("config.yaml");
fs::write(&config_path, "sync:\n auto-flush: false\n").expect("write config");
let create = run_br(
&workspace,
["create", "Hyphen config no flush"],
"create_hyphen",
);
assert!(create.status.success(), "create failed: {}", create.stderr);
let jsonl_path = workspace.root.join(".beads").join("issues.jsonl");
if jsonl_path.exists() {
let contents = fs::read_to_string(&jsonl_path).expect("read jsonl");
assert!(
!contents.contains("Hyphen config no flush"),
"issue should not be in JSONL when sync.auto-flush=false (hyphen)"
);
}
let sync = run_br(&workspace, ["sync", "--flush-only"], "sync_flush");
assert!(sync.status.success(), "sync flush failed: {}", sync.stderr);
let contents = fs::read_to_string(&jsonl_path).expect("read jsonl");
assert!(
contents.contains("Hyphen config no flush"),
"issue should be in JSONL after explicit flush with hyphen config"
);
}
#[test]
fn e2e_lock_timeout_flag() {
let _log = common::test_log("e2e_lock_timeout_flag");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(
&workspace,
["--lock-timeout", "5000", "create", "Lock timeout test"],
"create_with_timeout",
);
assert!(
create.status.success(),
"create with --lock-timeout failed: {}",
create.stderr
);
let list = run_br(&workspace, ["list", "--json"], "list");
assert!(list.status.success(), "list failed: {}", list.stderr);
let json = parse_list_issues(&list.stdout);
assert!(
json.iter().any(|item| item["title"] == "Lock timeout test"),
"issue should be created with custom lock timeout"
);
}
#[test]
fn e2e_quiet_flag() {
let _log = common::test_log("e2e_quiet_flag");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(
&workspace,
["--quiet", "create", "Quiet test"],
"create_quiet",
);
assert!(
create.status.success(),
"create --quiet failed: {}",
create.stderr
);
}
#[test]
fn e2e_quiet_flag_list() {
let _log = common::test_log("e2e_quiet_flag_list");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(&workspace, ["create", "Quiet list test"], "create");
assert!(create.status.success(), "create failed: {}", create.stderr);
let list = run_br(&workspace, ["--quiet", "list"], "list_quiet");
assert!(
list.status.success(),
"list --quiet failed: {}",
list.stderr
);
}
#[test]
fn e2e_quiet_list_suppresses_truncation_note() {
let _log = common::test_log("e2e_quiet_list_suppresses_truncation_note");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
for title in ["Quiet list a", "Quiet list b"] {
let create = run_br(&workspace, ["create", title], "create");
assert!(create.status.success(), "create failed: {}", create.stderr);
}
let list = run_br(
&workspace,
["--quiet", "list", "--limit", "1"],
"list_quiet_limit",
);
assert!(
list.status.success(),
"list --quiet --limit 1 failed: {}",
list.stderr
);
assert!(
!list.stderr.contains("Output truncated"),
"quiet list should not emit truncation note: {}",
list.stderr
);
assert!(
!list.stderr.contains("Showing 1 of"),
"quiet list should not emit client-filter truncation note: {}",
list.stderr
);
}
#[test]
fn e2e_quiet_flag_dep_subcommands() {
let _log = common::test_log("e2e_quiet_flag_dep_subcommands");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create_a = run_br(&workspace, ["create", "Quiet dep A"], "create_a");
assert!(
create_a.status.success(),
"create A failed: {}",
create_a.stderr
);
let create_b = run_br(&workspace, ["create", "Quiet dep B"], "create_b");
assert!(
create_b.status.success(),
"create B failed: {}",
create_b.stderr
);
let id_a = create_a
.stdout
.lines()
.next()
.unwrap_or("")
.strip_prefix("✓ Created ")
.and_then(|rest| rest.split(':').next())
.unwrap_or("")
.trim()
.to_string();
let id_b = create_b
.stdout
.lines()
.next()
.unwrap_or("")
.strip_prefix("✓ Created ")
.and_then(|rest| rest.split(':').next())
.unwrap_or("")
.trim()
.to_string();
let add = run_br(
&workspace,
["--quiet", "dep", "add", &id_a, &id_b],
"dep_add_quiet",
);
assert!(
add.status.success(),
"dep add --quiet failed: {}",
add.stderr
);
assert!(
add.stdout.trim().is_empty(),
"dep add --quiet should produce no stdout: '{}'",
add.stdout
);
let cycles = run_br(&workspace, ["--quiet", "dep", "cycles"], "dep_cycles_quiet");
assert!(
cycles.status.success(),
"dep cycles --quiet failed: {}",
cycles.stderr
);
assert!(
cycles.stdout.trim().is_empty(),
"dep cycles --quiet should produce no stdout: '{}'",
cycles.stdout
);
let remove = run_br(
&workspace,
["--quiet", "dep", "remove", &id_a, &id_b],
"dep_remove_quiet",
);
assert!(
remove.status.success(),
"dep remove --quiet failed: {}",
remove.stderr
);
assert!(
remove.stdout.trim().is_empty(),
"dep remove --quiet should produce no stdout: '{}'",
remove.stdout
);
}
#[test]
fn e2e_quiet_flag_graph_subcommands() {
let _log = common::test_log("e2e_quiet_flag_graph_subcommands");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(
&workspace,
["create", "Quiet graph test"],
"create_graph_quiet",
);
assert!(create.status.success(), "create failed: {}", create.stderr);
let issue_id = create
.stdout
.lines()
.next()
.unwrap_or("")
.strip_prefix("✓ Created ")
.and_then(|rest| rest.split(':').next())
.unwrap_or("")
.trim()
.to_string();
assert!(!issue_id.is_empty(), "expected created issue id in stdout");
let graph_single = run_br(
&workspace,
["--quiet", "graph", &issue_id],
"graph_quiet_single",
);
assert!(
graph_single.status.success(),
"graph --quiet failed: {}",
graph_single.stderr
);
assert!(
graph_single.stdout.trim().is_empty(),
"graph --quiet should produce no stdout: '{}'",
graph_single.stdout
);
let graph_all = run_br(&workspace, ["--quiet", "graph", "--all"], "graph_quiet_all");
assert!(
graph_all.status.success(),
"graph --quiet --all failed: {}",
graph_all.stderr
);
assert!(
graph_all.stdout.trim().is_empty(),
"graph --quiet --all should produce no stdout: '{}'",
graph_all.stdout
);
}
#[test]
fn e2e_quiet_flag_comments_subcommands() {
let _log = common::test_log("e2e_quiet_flag_comments_subcommands");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(
&workspace,
["create", "Quiet comments test"],
"create_comments_quiet",
);
assert!(create.status.success(), "create failed: {}", create.stderr);
let issue_id = create
.stdout
.lines()
.next()
.unwrap_or("")
.strip_prefix("✓ Created ")
.and_then(|rest| rest.split(':').next())
.unwrap_or("")
.trim()
.to_string();
assert!(!issue_id.is_empty(), "expected created issue id in stdout");
let add = run_br(
&workspace,
[
"--quiet",
"comments",
"add",
&issue_id,
"hello quiet comments",
],
"comments_add_quiet",
);
assert!(
add.status.success(),
"comments add --quiet failed: {}",
add.stderr
);
assert!(
add.stdout.trim().is_empty(),
"comments add --quiet should produce no stdout: '{}'",
add.stdout
);
let list = run_br(
&workspace,
["--quiet", "comments", "list", &issue_id],
"comments_list_quiet",
);
assert!(
list.status.success(),
"comments list --quiet failed: {}",
list.stderr
);
assert!(
list.stdout.trim().is_empty(),
"comments list --quiet should produce no stdout: '{}'",
list.stdout
);
}
#[test]
fn e2e_quiet_flag_query_subcommands() {
let _log = common::test_log("e2e_quiet_flag_query_subcommands");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(
&workspace,
["create", "Quiet query test"],
"create_query_quiet",
);
assert!(create.status.success(), "create failed: {}", create.stderr);
let save = run_br(
&workspace,
["--quiet", "query", "save", "mine", "--status", "open"],
"query_save_quiet",
);
assert!(
save.status.success(),
"query save --quiet failed: {}",
save.stderr
);
assert!(
save.stdout.trim().is_empty(),
"query save --quiet should produce no stdout: '{}'",
save.stdout
);
let list = run_br(&workspace, ["--quiet", "query", "list"], "query_list_quiet");
assert!(
list.status.success(),
"query list --quiet failed: {}",
list.stderr
);
assert!(
list.stdout.trim().is_empty(),
"query list --quiet should produce no stdout: '{}'",
list.stdout
);
let delete = run_br(
&workspace,
["--quiet", "query", "delete", "mine"],
"query_delete_quiet",
);
assert!(
delete.status.success(),
"query delete --quiet failed: {}",
delete.stderr
);
assert!(
delete.stdout.trim().is_empty(),
"query delete --quiet should produce no stdout: '{}'",
delete.stdout
);
}
#[test]
fn e2e_quiet_flag_count_and_where() {
let _log = common::test_log("e2e_quiet_flag_count_and_where");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(
&workspace,
["create", "Quiet count/where test"],
"create_count_where_quiet",
);
assert!(create.status.success(), "create failed: {}", create.stderr);
assert_quiet_command(
&workspace,
["--quiet", "count"],
"count_quiet",
"count --quiet",
);
assert_quiet_command(
&workspace,
["--quiet", "stale", "--days", "0"],
"stale_quiet",
"stale --quiet",
);
assert_quiet_command(
&workspace,
["--quiet", "where"],
"where_quiet",
"where --quiet",
);
assert_quiet_command(
&workspace,
["--quiet", "info"],
"info_quiet",
"info --quiet",
);
assert_quiet_command(
&workspace,
["--quiet", "info", "--thanks"],
"info_thanks_quiet",
"info --thanks --quiet",
);
assert_quiet_command(
&workspace,
["--quiet", "info", "--whats-new"],
"info_whats_new_quiet",
"info --whats-new --quiet",
);
let info_json_value = run_quiet_json(
&workspace,
["--quiet", "info", "--json"],
"info_json_quiet",
"info --json should remain json under quiet",
);
assert!(
info_json_value.get("database_path").is_some(),
"info --json should include database_path: {info_json_value}"
);
let info_thanks_value = run_quiet_json(
&workspace,
["--quiet", "info", "--thanks", "--json"],
"info_thanks_json_quiet",
"info --thanks --json should remain json under quiet",
);
assert!(
info_thanks_value["thanks"]
.as_str()
.is_some_and(|message| message.contains("Thanks for using br")),
"info --thanks --json should preserve the thanks message: {info_thanks_value}"
);
}
#[test]
fn e2e_quiet_flag_defer_subcommands() {
let _log = common::test_log("e2e_quiet_flag_defer_subcommands");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(
&workspace,
["create", "Quiet defer test"],
"create_defer_quiet",
);
assert!(create.status.success(), "create failed: {}", create.stderr);
let issue_id = create
.stdout
.lines()
.next()
.unwrap_or("")
.strip_prefix("✓ Created ")
.and_then(|rest| rest.split(':').next())
.unwrap_or("")
.trim()
.to_string();
assert!(!issue_id.is_empty(), "expected created issue id in stdout");
let defer = run_br(&workspace, ["--quiet", "defer", &issue_id], "defer_quiet");
assert!(
defer.status.success(),
"defer --quiet failed: {}",
defer.stderr
);
assert!(
defer.stdout.trim().is_empty(),
"defer --quiet should produce no stdout: '{}'",
defer.stdout
);
let undefer = run_br(
&workspace,
["--quiet", "undefer", &issue_id],
"undefer_quiet",
);
assert!(
undefer.status.success(),
"undefer --quiet failed: {}",
undefer.stderr
);
assert!(
undefer.stdout.trim().is_empty(),
"undefer --quiet should produce no stdout: '{}'",
undefer.stdout
);
}
#[test]
fn e2e_quiet_flag_config_epic_label_and_q_subcommands() {
let _log = common::test_log("e2e_quiet_flag_config_epic_label_and_q_subcommands");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
assert_quiet_command(
&workspace,
["--quiet", "config", "path"],
"config_path_quiet",
"config path --quiet",
);
assert_quiet_command(
&workspace,
["--quiet", "epic", "status"],
"epic_status_quiet",
"epic status --quiet",
);
assert_quiet_command(
&workspace,
["--quiet", "epic", "close-eligible"],
"epic_close_eligible_quiet",
"epic close-eligible --quiet",
);
let create = run_br(
&workspace,
["create", "Quiet label test"],
"create_label_quiet",
);
assert!(create.status.success(), "create failed: {}", create.stderr);
let issue_id = parse_created_id(&create.stdout);
assert_quiet_command(
&workspace,
["--quiet", "label", "add", &issue_id, "triage"],
"label_add_quiet",
"label add --quiet",
);
assert_quiet_command(
&workspace,
["--quiet", "q", "Quiet quick capture"],
"q_quiet",
"q --quiet",
);
assert_quiet_command(
&workspace,
["--quiet", "delete", &issue_id],
"delete_quiet",
"delete --quiet",
);
}
#[test]
fn e2e_quiet_flag_sync_subcommands() {
let _log = common::test_log("e2e_quiet_flag_sync_subcommands");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(
&workspace,
["create", "Quiet sync test", "--no-auto-flush"],
"create_sync_quiet",
);
assert!(create.status.success(), "create failed: {}", create.stderr);
assert_quiet_command(
&workspace,
["--quiet", "sync", "--status"],
"sync_status_quiet",
"sync --status --quiet",
);
assert_quiet_command(
&workspace,
["--quiet", "sync", "--flush-only"],
"sync_flush_quiet",
"sync --flush-only --quiet",
);
assert_quiet_command(
&workspace,
["--quiet", "sync", "--import-only"],
"sync_import_quiet",
"sync --import-only --quiet",
);
let status_json = run_quiet_json(
&workspace,
["--quiet", "sync", "--status", "--json"],
"sync_status_quiet_json",
"sync --status --quiet --json should remain json",
);
assert!(
status_json.get("dirty_count").is_some(),
"sync --status --quiet --json should include dirty_count: {status_json}"
);
}
#[test]
fn e2e_verbose_flag() {
let _log = common::test_log("e2e_verbose_flag");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(
&workspace,
["-v", "create", "Verbose test"],
"create_verbose",
);
assert!(
create.status.success(),
"create -v failed: {}",
create.stderr
);
}
#[test]
fn e2e_very_verbose_flag() {
let _log = common::test_log("e2e_very_verbose_flag");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(
&workspace,
["-vv", "create", "Very verbose test"],
"create_very_verbose",
);
assert!(
create.status.success(),
"create -vv failed: {}",
create.stderr
);
}
#[test]
fn e2e_json_no_color_combined() {
let _log = common::test_log("e2e_json_no_color_combined");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(&workspace, ["create", "Combined flags test"], "create");
assert!(create.status.success(), "create failed: {}", create.stderr);
let list = run_br(
&workspace,
["list", "--json", "--no-color"],
"list_combined",
);
assert!(
list.status.success(),
"list --json --no-color failed: {}",
list.stderr
);
let _json = parse_list_issues(&list.stdout);
assert!(
!list.stdout.contains("\x1b["),
"no color codes in JSON output"
);
}
#[test]
fn e2e_no_db_json_combined() {
let _log = common::test_log("e2e_no_db_json_combined");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(&workspace, ["create", "No-DB JSON test"], "create");
assert!(create.status.success(), "create failed: {}", create.stderr);
let sync = run_br(&workspace, ["sync", "--flush-only"], "sync_flush");
assert!(sync.status.success(), "sync flush failed: {}", sync.stderr);
let list = run_br(&workspace, ["--no-db", "list", "--json"], "list_no_db_json");
assert!(
list.status.success(),
"list --no-db --json failed: {}",
list.stderr
);
let json = parse_list_issues(&list.stdout);
assert!(
json.iter().any(|item| item["title"] == "No-DB JSON test"),
"issue in no-db JSON output"
);
}
#[test]
fn e2e_quiet_json_combined() {
let _log = common::test_log("e2e_quiet_json_combined");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(&workspace, ["create", "Quiet JSON test"], "create");
assert!(create.status.success(), "create failed: {}", create.stderr);
let list = run_br(&workspace, ["--quiet", "list", "--json"], "list_quiet_json");
assert!(
list.status.success(),
"list --quiet --json failed: {}",
list.stderr
);
let _json = parse_list_issues(&list.stdout);
}
#[test]
fn e2e_global_flag_before_command() {
let _log = common::test_log("e2e_global_flag_before_command");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(&workspace, ["create", "Position test"], "create");
assert!(create.status.success(), "create failed: {}", create.stderr);
let list = run_br(&workspace, ["--json", "list"], "list_flag_before");
assert!(
list.status.success(),
"list with --json before command failed: {}",
list.stderr
);
let _json = parse_list_issues(&list.stdout);
}
#[test]
fn e2e_global_flag_after_command() {
let _log = common::test_log("e2e_global_flag_after_command");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(&workspace, ["create", "Position test 2"], "create");
assert!(create.status.success(), "create failed: {}", create.stderr);
let list = run_br(&workspace, ["list", "--json"], "list_flag_after");
assert!(
list.status.success(),
"list with --json after command failed: {}",
list.stderr
);
let _json = parse_list_issues(&list.stdout);
}
#[test]
fn e2e_json_stdout_is_clean_json() {
let _log = common::test_log("e2e_json_stdout_is_clean_json");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success());
let create = run_br(&workspace, ["create", "JSON clean test"], "create");
assert!(create.status.success());
for (cmd, label) in [
(vec!["list", "--json"], "list"),
(vec!["ready", "--json"], "ready"),
(vec!["blocked", "--json"], "blocked"),
(vec!["count", "--json"], "count"),
(vec!["stats", "--json"], "stats"),
] {
let output = run_br(&workspace, cmd.clone(), &format!("clean_json_{label}"));
assert!(output.status.success(), "{label} failed: {}", output.stderr);
let payload = extract_json_payload(&output.stdout);
let parsed: Result<Value, _> = serde_json::from_str(&payload);
assert!(
parsed.is_ok(),
"{label} --json stdout is not valid JSON:\n---stdout---\n{}\n---end---",
output.stdout
);
assert!(
!output.stdout.contains("\x1b["),
"{label} --json stdout contains ANSI escape codes"
);
}
}
#[test]
fn e2e_quiet_reduces_output() {
let _log = common::test_log("e2e_quiet_reduces_output");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success());
let create = run_br(&workspace, ["create", "Quiet reduction test"], "create");
assert!(create.status.success());
let normal = run_br(&workspace, ["list"], "list_normal");
assert!(normal.status.success());
let quiet = run_br(&workspace, ["--quiet", "list"], "list_quiet_cmp");
assert!(quiet.status.success());
assert!(
quiet.stdout.len() <= normal.stdout.len(),
"quiet output ({} bytes) should not exceed normal output ({} bytes)\nquiet: {}\nnormal: {}",
quiet.stdout.len(),
normal.stdout.len(),
quiet.stdout,
normal.stdout
);
}
#[test]
fn e2e_json_overrides_quiet() {
let _log = common::test_log("e2e_json_overrides_quiet");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success());
let create = run_br(&workspace, ["create", "Precedence test"], "create");
assert!(create.status.success());
let output = run_br(&workspace, ["--quiet", "list", "--json"], "quiet_json_prec");
assert!(output.status.success());
let json = parse_list_issues(&output.stdout);
assert!(
json.iter().any(|item| item["title"] == "Precedence test"),
"JSON output should contain the issue even with --quiet"
);
}
#[test]
fn e2e_no_color_across_commands() {
let _log = common::test_log("e2e_no_color_across_commands");
let workspace = BrWorkspace::new();
let init = run_br(&workspace, ["init"], "init");
assert!(init.status.success());
let id = {
let create = run_br(&workspace, ["create", "No-color multi test"], "create");
assert!(create.status.success());
create
.stdout
.lines()
.next()
.unwrap_or("")
.strip_prefix("✓ Created ")
.or_else(|| {
create
.stdout
.lines()
.next()
.unwrap_or("")
.strip_prefix("Created ")
})
.and_then(|s| s.split(':').next())
.unwrap_or("")
.trim()
.to_string()
};
for (cmd, label) in [
(vec!["list", "--no-color"], "list"),
(vec!["show", &id, "--no-color"], "show"),
(vec!["ready", "--no-color"], "ready"),
(vec!["stats", "--no-color"], "stats"),
(vec!["count", "--no-color"], "count"),
] {
let output = run_br(&workspace, cmd.clone(), &format!("no_color_{label}"));
assert!(output.status.success(), "{label} failed: {}", output.stderr);
assert!(
!output.stdout.contains("\x1b["),
"{label} --no-color stdout contains ANSI escape codes"
);
}
}