mod common;
use common::cli::{BrWorkspace, extract_json_payload, run_br, run_br_with_stdin};
use serde_json::Value;
use std::fs;
const START_MARKER: &str = "<!-- br-agent-instructions-v1 -->";
const END_MARKER: &str = "<!-- end-br-agent-instructions -->";
const LEGACY_START: &str = "<!-- bv-agent-instructions-v1 -->";
const LEGACY_END: &str = "<!-- end-bv-agent-instructions -->";
const USER_HEAD: &str = "# My project\n\nHand-written guidance that must survive.\n\n";
const USER_TAIL: &str = "\n## Appendix\n\nMore hand-written text after the block.\n";
fn agents_md(workspace: &BrWorkspace) -> String {
fs::read_to_string(workspace.root.join("AGENTS.md")).expect("read AGENTS.md")
}
fn json(stdout: &str) -> Value {
serde_json::from_str(&extract_json_payload(stdout)).expect("agents JSON")
}
fn managed_block_count(content: &str) -> usize {
content.matches(START_MARKER).count()
}
#[test]
fn e2e_agents_add_is_idempotent_and_preserves_user_text() {
let _log = common::test_log("e2e_agents_add_is_idempotent_and_preserves_user_text");
let workspace = BrWorkspace::new();
let check = run_br(&workspace, ["agents", "--json"], "agents_check_missing");
assert!(check.status.success(), "check failed: {}", check.stderr);
let check = json(&check.stdout);
assert_eq!(check["found"], false, "check: {check}");
assert_eq!(check["has_blurb"], false, "check: {check}");
assert_eq!(check["needs_blurb"], false, "check: {check}");
assert!(!workspace.root.join("AGENTS.md").exists());
let dry = run_br(
&workspace,
["agents", "--add", "--dry-run"],
"agents_add_dry_run",
);
assert!(dry.status.success(), "dry-run failed: {}", dry.stderr);
assert!(
dry.stdout.contains(START_MARKER),
"dry-run preview: {}",
dry.stdout
);
assert!(
!workspace.root.join("AGENTS.md").exists(),
"dry-run must not create AGENTS.md"
);
let json_no_force = run_br(
&workspace,
["agents", "--add", "--json"],
"agents_add_json_no_force",
);
assert!(
!json_no_force.status.success(),
"JSON add without --force must be refused: {}",
json_no_force.stdout
);
assert!(!workspace.root.join("AGENTS.md").exists());
let add = run_br(&workspace, ["agents", "--add", "--force"], "agents_add");
assert!(add.status.success(), "add failed: {}", add.stderr);
let created = agents_md(&workspace);
assert_eq!(managed_block_count(&created), 1, "created: {created}");
assert!(
created.contains(END_MARKER) && created.contains("br ready"),
"created: {created}"
);
let again = run_br(
&workspace,
["agents", "--add", "--force", "--json"],
"agents_add_again",
);
assert!(again.status.success(), "re-add failed: {}", again.stderr);
let again = json(&again.stdout);
assert_eq!(again["reason"], "already_current", "re-add: {again}");
assert_eq!(
agents_md(&workspace),
created,
"second add must not change the file"
);
let check = run_br(&workspace, ["agents", "--json"], "agents_check_present");
let check = json(&check.stdout);
assert_eq!(check["found"], true, "check: {check}");
assert_eq!(check["has_blurb"], true, "check: {check}");
assert_eq!(check["needs_blurb"], false, "check: {check}");
assert_eq!(check["needs_upgrade"], false, "check: {check}");
}
#[test]
fn e2e_agents_add_update_remove_keep_user_text_intact() {
let _log = common::test_log("e2e_agents_add_update_remove_keep_user_text_intact");
let workspace = BrWorkspace::new();
let path = workspace.root.join("AGENTS.md");
fs::write(&path, USER_HEAD).expect("seed AGENTS.md");
let declined = run_br_with_stdin(
&workspace,
["agents", "--add"],
"n\n",
"agents_add_declined",
);
assert!(
declined.status.success(),
"declined add: {}",
declined.stderr
);
assert_eq!(
agents_md(&workspace),
USER_HEAD,
"declined add must not write"
);
let add = run_br(
&workspace,
["agents", "--add", "--force"],
"agents_add_to_existing",
);
assert!(add.status.success(), "add failed: {}", add.stderr);
let with_block = agents_md(&workspace);
assert!(
with_block.starts_with(USER_HEAD),
"user text must stay first: {with_block}"
);
assert_eq!(managed_block_count(&with_block), 1);
fs::write(&path, format!("{with_block}{USER_TAIL}")).expect("append user tail");
let update = run_br(
&workspace,
["agents", "--update", "--force", "--json"],
"agents_update_current",
);
assert!(update.status.success(), "update failed: {}", update.stderr);
let update = json(&update.stdout);
assert_eq!(
update["reason"], "already_up_to_date",
"update on current block: {update}"
);
let after_update = agents_md(&workspace);
assert!(after_update.starts_with(USER_HEAD) && after_update.ends_with(USER_TAIL));
assert_eq!(managed_block_count(&after_update), 1);
let remove = run_br(
&workspace,
["agents", "--remove", "--force"],
"agents_remove",
);
assert!(remove.status.success(), "remove failed: {}", remove.stderr);
let removed = agents_md(&workspace);
assert!(
!removed.contains(START_MARKER) && !removed.contains(END_MARKER),
"removed: {removed}"
);
assert!(
removed.contains("Hand-written guidance that must survive."),
"removed: {removed}"
);
assert!(
removed.contains("More hand-written text after the block."),
"removed: {removed}"
);
let remove_again = run_br(
&workspace,
["agents", "--remove", "--force"],
"agents_remove_again",
);
assert!(
remove_again.status.success(),
"second remove: {}",
remove_again.stderr
);
assert_eq!(agents_md(&workspace), removed);
}
#[test]
fn e2e_agents_update_replaces_legacy_bv_block() {
let _log = common::test_log("e2e_agents_update_replaces_legacy_bv_block");
let workspace = BrWorkspace::new();
let legacy =
format!("{USER_HEAD}{LEGACY_START}\nOld bv instructions.\n{LEGACY_END}\n{USER_TAIL}");
fs::write(workspace.root.join("AGENTS.md"), &legacy).expect("seed legacy AGENTS.md");
let check = run_br(&workspace, ["agents", "--json"], "agents_check_legacy");
let check = json(&check.stdout);
assert_eq!(check["has_legacy_blurb"], true, "check: {check}");
assert_eq!(check["needs_upgrade"], true, "check: {check}");
let update = run_br(
&workspace,
["agents", "--update", "--force"],
"agents_update_legacy",
);
assert!(update.status.success(), "update failed: {}", update.stderr);
let upgraded = agents_md(&workspace);
assert!(
!upgraded.contains(LEGACY_START),
"legacy block must be gone: {upgraded}"
);
assert_eq!(managed_block_count(&upgraded), 1, "upgraded: {upgraded}");
assert!(
upgraded.starts_with(USER_HEAD),
"user head must survive: {upgraded}"
);
assert!(
upgraded.contains("More hand-written text after the block."),
"user tail must survive: {upgraded}"
);
let check = run_br(&workspace, ["agents", "--json"], "agents_check_upgraded");
let check = json(&check.stdout);
assert_eq!(check["has_blurb"], true, "check: {check}");
assert_eq!(check["needs_upgrade"], false, "check: {check}");
}