use crate::harness::*;
#[test]
fn attachments_are_invisible_by_default() {
let h = Home::new();
attachment_scenario(&h);
let out = h.run(&["search", "glowlantern", &at(ATTACH_SESS)]);
assert!(out.success, "zero-match exits 0: {}", out.stderr);
assert!(
out.stdout.contains("no matching exchanges"),
"default scan must not see attachment payloads:\n{}",
out.stdout
);
let labeled = h.run(&[
"search",
"glowlantern",
&at(ATTACH_SESS),
"-t",
"harness.meta.attachment",
]);
assert!(
labeled.stdout.contains("no matching exchanges"),
"the selector alone does not imply the gate:\n{}",
labeled.stdout
);
}
#[test]
fn attachments_flag_surfaces_payloads_under_meta_attachment() {
let h = Home::new();
attachment_scenario(&h);
let out = h.run(&["search", "glowlantern", &at(ATTACH_SESS), "--attachments"]);
assert!(out.success, "stderr: {}", out.stderr);
assert!(
out.stdout.contains("harness.meta.attachment")
&& out.stdout.contains("L2")
&& out.stdout.contains("glowlantern"),
"flag surfaces the payload under harness.meta.attachment:\n{}",
out.stdout
);
let hook = h.run(&[
"search",
"mistgate",
&at(ATTACH_SESS),
"--attachments",
"-t",
"harness.meta.hook",
]);
assert!(
hook.stdout.contains("harness.meta.hook"),
"--attachments is a superset of --additional-context:\n{}",
hook.stdout
);
let out3 = h.run(&[
"search",
"glowlantern",
&at(ATTACH_SESS),
"--attachments",
"-t",
"user",
]);
assert!(
out3.stdout.contains("no matching exchanges"),
"-t user excludes meta.attachment even with the flag:\n{}",
out3.stdout
);
let outj = h.run(&[
"search",
"glowlantern",
&at(ATTACH_SESS),
"--attachments",
"--format",
"json",
]);
assert!(
outj.stdout.contains(r#""label":"harness.meta.attachment""#),
"JSON label: {}",
outj.stdout
);
}
#[test]
fn count_by_attachment_censuses_payload_types_and_implies_the_gate() {
let h = Home::new();
attachment_scenario(&h);
let out = h.run(&["search", "", &at(ATTACH_SESS), "--count-by", "attachment"]);
assert!(out.success, "stderr: {}", out.stderr);
for key in [
"edited_text_file",
"compact_file_reference",
"hook_additional_context",
] {
assert!(out.stdout.contains(key), "missing {key}:\n{}", out.stdout);
}
assert!(
out.stderr.contains("2 record(s) have no attachment"),
"non-attachment records are excluded AND disclosed on stderr:\n{}",
out.stderr
);
let outj = h.run(&[
"search",
"",
&at(ATTACH_SESS),
"--count-by",
"attachment",
"--format",
"json",
]);
let rows: Vec<serde_json::Value> = outj
.stdout
.lines()
.map(|l| serde_json::from_str(l).unwrap())
.collect();
for key in [
"edited_text_file",
"compact_file_reference",
"hook_additional_context",
] {
assert!(
rows.iter().any(|r| r["kind"] == "census"
&& r["axis"] == "attachment"
&& r["key"] == key
&& r["records"] == 1),
"census row for {key}: {}",
outj.stdout
);
}
let summary = rows.last().unwrap();
assert_eq!(summary["axis"], "attachment", "summary: {}", outj.stdout);
assert_eq!(summary["excluded_records"], 2, "user + assistant excluded");
}
#[test]
fn show_renders_an_addressed_attachment_flag_free() {
let h = Home::new();
attachment_scenario(&h);
let out = h.run(&["show", &at(ATTACH_SESS), "--line", "3"]);
assert!(out.success, "stderr: {}", out.stderr);
assert!(
out.stdout.contains("compact_file_reference") && out.stdout.contains("harbor.md"),
"addressed attachment renders flag-free:\n{}",
out.stdout
);
}
#[test]
fn image_bearing_rows_teach_extraction_once_and_in_input_form() {
let h = image_home();
let out = h.run(&["search", "screenshot", at(SESS).as_str()]);
assert!(out.success, "stderr: {}", out.stderr);
assert!(
out.stdout.contains("read the image(s): csift image @"),
"inline hint under the first image row:\n{}",
out.stdout
);
assert!(
!out.stdout.contains("--id #"),
"the hint never uses the display form (#N is not valid --id input):\n{}",
out.stdout
);
assert!(
out.stdout.contains("extractable, not decorative"),
"footer capability note:\n{}",
out.stdout
);
let both = h.run(&["search", "", at(SESS).as_str(), "-t", "user.message"]);
assert_eq!(
both.stdout.matches("read the image(s):").count(),
1,
"once per run:\n{}",
both.stdout
);
let plain = h.run(&["search", "got it", at(SESS).as_str()]);
assert!(
!plain.stdout.contains("read the image(s):")
&& !plain.stdout.contains("extractable, not decorative"),
"no image, no hint machinery:\n{}",
plain.stdout
);
let sib = h.run(&["search", "got it", at(SESS).as_str(), "--siblings"]);
assert_eq!(
sib.stdout.matches("read the image(s):").count(),
1,
"sibling image row teaches too, once:\n{}",
sib.stdout
);
let shown = h.run(&["show", at(SESS).as_str(), "--line", "1"]);
assert!(
shown.stdout.contains("read the image(s): csift image @"),
"show teaches extraction too:\n{}",
shown.stdout
);
let sh = h.run(&["search", "--help"]);
assert!(
sh.stdout.contains("SEE ALSO") && sh.stdout.contains("csift image"),
"search --help names image:\n{}",
sh.stdout
);
let vh = h.run(&["verbatim", "--help"]);
assert!(
vh.stdout.contains("SEE ALSO") && vh.stdout.contains("csift image"),
"verbatim --help names image:\n{}",
vh.stdout
);
}