mod common;
use common::cli::{BrWorkspace, run_br};
use std::fs;
fn init_and_create_issue(workspace: &BrWorkspace) {
let init = run_br(workspace, ["init", "--prefix", "bd"], "init");
assert!(init.status.success(), "init failed: {}", init.stderr);
let create = run_br(
workspace,
["create", "--title", "test issue", "--no-auto-flush"],
"create",
);
assert!(create.status.success(), "create failed: {}", create.stderr);
}
#[test]
fn manifest_is_valid_json_after_flush() {
let workspace = BrWorkspace::new();
init_and_create_issue(&workspace);
let flush = run_br(&workspace, ["sync", "--flush-only", "--manifest"], "flush");
assert!(flush.status.success(), "flush failed: {}", flush.stderr);
let manifest_path = workspace.root.join(".beads").join(".manifest.json");
assert!(
manifest_path.exists(),
"manifest should exist after --manifest flush"
);
let content = fs::read_to_string(&manifest_path).expect("read manifest");
let parsed: serde_json::Value =
serde_json::from_str(&content).expect("manifest should be valid JSON");
assert!(parsed.is_object(), "manifest should be a JSON object");
assert!(
parsed.get("export_time").is_some(),
"manifest should have export_time"
);
assert!(
parsed.get("issues_count").is_some(),
"manifest should have issues_count"
);
assert!(
parsed.get("content_hash").is_some(),
"manifest should have content_hash"
);
}
#[test]
fn manifest_write_leaves_no_temp_files() {
let workspace = BrWorkspace::new();
init_and_create_issue(&workspace);
let flush = run_br(&workspace, ["sync", "--flush-only", "--manifest"], "flush");
assert!(flush.status.success(), "flush failed: {}", flush.stderr);
let beads_dir = workspace.root.join(".beads");
let temp_files: Vec<_> = fs::read_dir(&beads_dir)
.expect("read .beads dir")
.filter_map(Result::ok)
.filter(|e| e.file_name().to_string_lossy().ends_with(".tmp"))
.collect();
assert!(
temp_files.is_empty(),
"no .tmp files should remain after successful flush, found: {:?}",
temp_files
.iter()
.map(std::fs::DirEntry::file_name)
.collect::<Vec<_>>()
);
}
#[test]
fn pre_existing_manifest_survives_if_no_manifest_flag() {
let workspace = BrWorkspace::new();
init_and_create_issue(&workspace);
let manifest_path = workspace.root.join(".beads").join(".manifest.json");
let sentinel = r#"{"sentinel": true}"#;
fs::write(&manifest_path, sentinel).expect("write sentinel manifest");
let flush = run_br(&workspace, ["sync", "--flush-only"], "flush-no-manifest");
assert!(flush.status.success(), "flush failed: {}", flush.stderr);
let content = fs::read_to_string(&manifest_path).expect("read manifest");
assert_eq!(
content, sentinel,
"manifest should be untouched when --manifest is not passed"
);
}
#[test]
fn manifest_overwrite_replaces_old_content_atomically() {
let workspace = BrWorkspace::new();
init_and_create_issue(&workspace);
let manifest_path = workspace.root.join(".beads").join(".manifest.json");
let old_content = r#"{"old": "manifest", "issues_count": 0}"#;
fs::write(&manifest_path, old_content).expect("write old manifest");
let flush = run_br(
&workspace,
["sync", "--flush-only", "--manifest"],
"flush-overwrite",
);
assert!(flush.status.success(), "flush failed: {}", flush.stderr);
let new_content = fs::read_to_string(&manifest_path).expect("read manifest");
assert_ne!(
new_content, old_content,
"manifest should be updated after flush"
);
let parsed: serde_json::Value =
serde_json::from_str(&new_content).expect("new manifest should be valid JSON");
assert!(
parsed.get("issues_count").is_some(),
"new manifest should have issues_count"
);
let count = parsed["issues_count"]
.as_u64()
.expect("issues_count should be u64");
assert!(
count >= 1,
"should have exported at least 1 issue, got {count}"
);
}