use crate::brain::agent::BgTaskMeta;
use crate::channels::telegram::resume::{
BubbleWire, NotifySender, background_task_title, build_bg_echo_bubble, build_bg_receipt_card,
build_notify_receipt_card, split_bg_echo_parts, split_notify_header, strip_system_framing,
};
use uuid::Uuid;
const SENDER: &str = "6c1c9cb9-8243-4def-abe5-d926d0ca8bed";
#[test]
fn strips_notify_header_and_returns_sender() {
let ctx = format!("[session-notify from={SENDER}]\n\nhello from the other topic");
let (sender, body) = split_bg_echo_parts(&ctx);
assert_eq!(
sender,
Some(NotifySender::Session(Uuid::parse_str(SENDER).unwrap()))
);
assert_eq!(body, "hello from the other topic");
}
#[test]
fn cli_sender_label_is_carried_verbatim() {
let ctx = "[session-notify from=cli:oc-deploy]\n\nbuild green";
let (sender, body) = split_bg_echo_parts(ctx);
assert_eq!(sender, Some(NotifySender::CliTooling("oc-deploy")));
assert_eq!(body, "build green");
}
#[test]
fn cli_label_survives_surrounding_whitespace() {
let ctx = "[session-notify from=cli: CI runner ]\n\nbody";
let (sender, body) = split_notify_header(ctx);
assert_eq!(sender, Some(NotifySender::CliTooling("CI runner")));
assert_eq!(body, "body");
}
#[test]
fn empty_cli_label_is_malformed_framing() {
let ctx = "[session-notify from=cli:]\n\nbody";
let (sender, body) = split_notify_header(ctx);
assert_eq!(sender, None, "an empty cli: label is not a sender");
assert_eq!(body, ctx, "malformed header passes whole text through");
}
#[test]
fn split_notify_header_rejects_malformed_framing() {
let bad_uuid = "[session-notify from=not-a-uuid]\n\nbody";
let (sender, body) = split_notify_header(bad_uuid);
assert_eq!(sender, None);
assert_eq!(
body, bad_uuid,
"malformed header must pass whole text through"
);
let no_close = "[session-notify from=6c1c9cb9-8243-4def-abe5-d926d0ca8bed";
let (sender, body) = split_notify_header(no_close);
assert_eq!(sender, None);
assert_eq!(body, no_close);
}
#[test]
fn strips_terminated_system_framing() {
let ctx = "[System: the background task you started has finished.\nStatus: exit 0]\nreal tail";
let (sender, body) = split_bg_echo_parts(ctx);
assert_eq!(sender, None, "background tasks carry no sender");
assert!(!body.contains("[System:"), "scaffolding must not render");
assert!(body.contains("Status: exit 0"), "inner content survives");
assert!(body.contains("real tail"));
}
#[test]
fn system_framing_without_closing_brace_passes_through() {
let ctx = "[System: task finished]\nsome output";
let inner = strip_system_framing(ctx);
assert_eq!(
inner, ctx,
"the ']' must terminate the block to be stripped"
);
}
#[test]
fn strips_system_framing_even_after_notify_header() {
let ctx =
format!("[session-notify from={SENDER}]\n\n[System: the push you asked for]\npushed body");
let (sender, body) = split_bg_echo_parts(&ctx);
assert!(sender.is_some());
assert!(
!body.contains("[System:"),
"System framing stripped after header"
);
assert!(body.contains("pushed body"));
}
#[test]
fn absent_framing_passes_through_untouched() {
let (sender, body) = split_bg_echo_parts("plain text, no framing");
assert_eq!(sender, None);
assert_eq!(body, "plain text, no framing");
}
#[test]
fn bubble_wraps_in_blockquote_with_bold_header() {
let (wire, html) = build_bg_echo_bubble("some output", "📨 Ops / Push to session");
let BubbleWire::Markdown(markdown) = &wire else {
panic!("plain echo bubble rides the markdown outbox wire");
};
assert!(markdown.contains("📨 Ops / Push to session"));
assert!(markdown.contains("some output"));
assert!(html.starts_with("<blockquote expandable>"));
assert!(html.ends_with("</blockquote>"));
assert!(html.contains("<b>📨 Ops / Push to session</b>"));
assert!(html.contains("some output"));
}
#[test]
fn background_task_title_is_preserved() {
let (_, html) = build_bg_echo_bubble("finished ok", "⚙️ background task result");
assert!(html.contains("<b>⚙️ background task result</b>"));
}
#[test]
fn rich_markdown_keeps_fences() {
let ctx = "# Heading\n```rust\nfn main() {}\n```";
let (wire, _) = build_bg_echo_bubble(ctx, "📨 Team");
let BubbleWire::Markdown(markdown) = wire else {
panic!("plain echo bubble rides the markdown outbox wire");
};
assert!(markdown.contains("```rust"));
assert!(markdown.contains("# Heading"));
}
#[test]
fn long_output_is_truncated_before_conversion_and_stays_wellformed() {
let big = format!("{{}}\n{}", "y".repeat(10_000));
let (wire, html) = build_bg_echo_bubble(&big, "⚙️ background task result");
let BubbleWire::Markdown(markdown) = &wire else {
panic!("plain echo bubble rides the markdown outbox wire");
};
assert!(markdown.contains("(truncated)"));
assert!(html.contains("(truncated)"));
assert!(html.starts_with("<blockquote expandable>"));
assert!(html.ends_with("</blockquote>"));
}
#[test]
fn background_title_names_the_task_from_display_text() {
assert_eq!(
background_task_title("🔧 background task finished: grep-errors"),
"🔧 background task finished: grep-errors"
);
assert_eq!(
background_task_title("🔧 background task failed: cleanup-unified"),
"🔧 background task failed: cleanup-unified"
);
}
#[test]
fn blank_display_text_falls_back_to_generic_title() {
assert_eq!(background_task_title(" "), "⚙️ background task result");
assert_eq!(background_task_title(""), "⚙️ background task result");
}
#[test]
fn overlong_task_label_is_capped_in_title() {
let long = format!("🔧 background task finished: {}", "x".repeat(500));
let t = background_task_title(&long);
assert!(t.chars().count() <= 120, "header must stay readable");
assert!(t.starts_with("🔧 background task finished:"));
}
#[test]
fn html_fallback_escapes_dynamic_title() {
let (_, html) = build_bg_echo_bubble("body", "📨 Ops <script> / Push");
assert!(html.contains("<b>📨 Ops <script> / Push</b>"));
assert!(!html.contains("<script>"), "title must not inject raw HTML");
}
#[test]
fn bubble_md_and_classic_stay_separate() {
let (wire, classic) = build_bg_echo_bubble("body", "T");
let BubbleWire::Markdown(md) = wire else {
panic!("plain echo bubble rides the markdown outbox wire");
};
assert!(
classic.contains("blockquote expandable"),
"fallback stays classic-dialect"
);
assert!(
!md.contains('<'),
"markdown leg stays tag-free for the rich parser"
);
}
fn meta(success: bool, label: &str, secs: f32, tail: &str) -> BgTaskMeta {
BgTaskMeta {
success,
label: label.to_string(),
elapsed_secs: secs,
tail: tail.to_string(),
}
}
#[test]
fn bg_receipt_card_matches_the_locked_p3f_shape() {
let (wire, classic) = build_bg_receipt_card(&meta(
true,
"gh run watch 33117665576",
1646.0,
"line one\nline two",
));
let BubbleWire::Html(rich) = &wire else {
panic!("chrome card rides the HTML rich wire (#85)");
};
assert!(
rich.starts_with(
"<details><summary><sub>✅ <code>gh run watch 33117665576</code> 🕒 27m 26s</sub></summary>"
),
"summary = icon + monospace roster label + clock + duration, whole line subbed: {rich}"
);
assert!(
rich.contains("<pre>line one\nline two</pre>"),
"body is ONE pre block with the tail verbatim: {rich}"
);
assert!(rich.ends_with("</details>"));
assert!(
!rich.contains("exit"),
"no exit code / wording in the bubble"
);
assert!(
!rich.contains("<details open"),
"collapsible ships collapsed"
);
assert!(classic.starts_with("<blockquote expandable>"));
assert!(classic.contains("gh run watch 33117665576"));
assert!(classic.contains("line one"));
}
#[test]
fn bg_receipt_card_failure_uses_the_cross_icon() {
let (wire, _) = build_bg_receipt_card(&meta(false, "cargo test", 3.0, "boom"));
let BubbleWire::Html(rich) = wire else {
panic!("chrome card rides the HTML rich wire (#85)");
};
assert!(
rich.starts_with("<details><summary><sub>❌ <code>cargo test</code> 🕒 3s</sub></summary>")
);
}
#[test]
fn bg_receipt_card_strips_backticks_from_the_label() {
let (wire, _) = build_bg_receipt_card(&meta(true, "cat `file`.md", 1.0, "ok"));
let BubbleWire::Html(rich) = wire else {
panic!("chrome card rides the HTML rich wire (#85)");
};
assert!(
rich.contains("<code>cat file.md</code>"),
"label backticks stripped so the code span stays intact: {rich}"
);
}
#[test]
fn bg_receipt_card_fence_outgrows_backtick_runs_in_the_tail() {
let tail = "look:\n```\nnested fence\n```\ndone";
let (wire, classic) = build_bg_receipt_card(&meta(true, "cat README.md", 2.0, tail));
let BubbleWire::Html(rich) = wire else {
panic!("chrome card rides the HTML rich wire (#85)");
};
assert!(
rich.contains("<pre>look:\n```\nnested fence\n```\ndone</pre>"),
"pre containment ships the tail verbatim, fence-free: {rich}"
);
assert!(
classic.contains("<pre><code>look:\n```\nnested fence\n```\ndone</code></pre>"),
"classic leg: the compose-time arms-race fence survives markdown_to_html \
now that parser has fence length-matching (#94): {classic}"
);
}
#[test]
fn bg_receipt_card_rich_leg_escapes_the_tail() {
let (wire, _) = build_bg_receipt_card(&meta(true, "render", 1.0, "<b>&raw</b>"));
let BubbleWire::Html(rich) = wire else {
panic!("chrome card rides the HTML rich wire (#85)");
};
assert!(
rich.contains("<pre><b>&raw</b></pre>"),
"pre content is escaped: {rich}"
);
assert!(
!rich.contains("<b>&raw"),
"tail cannot open a tag inside the fold"
);
}
#[test]
fn bg_receipt_card_empty_tail_is_a_flat_one_liner() {
for tail in ["", " ", "\n\t"] {
let (wire, classic) = build_bg_receipt_card(&meta(true, "gh run watch 1", 61.0, tail));
let BubbleWire::Markdown(md) = &wire else {
panic!("empty-tail card must ride the flat markdown wire (#38 guard)");
};
assert!(!md.contains("<details>"), "no wrapper to reject: {md}");
assert!(
!classic.contains("<details>"),
"no wrapper to leak: {classic}"
);
assert!(
md.starts_with("✅ `gh run watch 1` 🕒 "),
"flat markdown card: {md}"
);
assert!(!md.contains('\n'), "one line only: {md}");
assert!(
classic.starts_with("<b>✅ gh run watch 1 🕒 "),
"flat classic card: {classic}"
);
}
}
#[test]
fn bg_receipt_card_empty_tail_escapes_the_label_on_the_classic_leg() {
let (wire, classic) = build_bg_receipt_card(&meta(false, "grep <b>", 1.0, ""));
let BubbleWire::Markdown(md) = wire else {
panic!("empty-tail card must ride the flat markdown wire (#38 guard)");
};
assert!(
classic.contains("<b>"),
"classic leg escapes the label: {classic}"
);
assert!(
!classic.contains("<b>grep"),
"label cannot open a tag: {classic}"
);
assert!(
md.starts_with("❌ `grep <b>` 🕒 "),
"markdown leg keeps the label verbatim: {md}"
);
}
#[tokio::test]
async fn notify_receipt_card_matches_the_locked_n4_shape() {
let body = "RECEIPT CONTRACT DELIVERED — swap verified, all three clauses journal-anchored.\n\n\
| Clause | Anchor |\n|---|---|\n| Build | run 1 |";
let (wire, classic) = build_notify_receipt_card("Compiler", body).await;
let BubbleWire::Html(rich) = &wire else {
panic!("notify card rides the HTML rich wire (#85)");
};
assert!(
rich.starts_with(
"<details><summary><sub>📨 From <b>Compiler</b>: RECEIPT CONTRACT DELIVERED — swap \
verified, a…</sub></summary>"
),
"summary = 📨 + From + bold sender + colon + 45-char first-line preview, whole line subbed: {rich}"
);
assert!(
rich.contains("<pre>"),
"markdown body renders through the HTML dialect (table as grid/pre): {rich}"
);
assert!(!rich.contains("```"), "notify body is never fenced");
assert!(rich.ends_with("</details>"));
assert!(
!rich.contains("<details open"),
"collapsible ships collapsed"
);
assert!(classic.starts_with("<blockquote expandable>"));
assert!(classic.contains("Compiler"));
}
#[tokio::test]
async fn notify_receipt_card_sanitizes_angle_brackets_in_sender() {
let (wire, _) = build_notify_receipt_card("Ops <script>", "body line").await;
let BubbleWire::Html(rich) = wire else {
panic!("notify card rides the HTML rich wire (#85)");
};
assert!(
rich.contains("<b>Ops ‹script›</b>: body line"),
"angle brackets neutralized so the sender can't open a tag: {rich}"
);
assert!(!rich.contains("<script>"));
}
#[tokio::test]
async fn notify_receipt_card_empty_body_is_a_flat_one_liner() {
for body in ["", " \n\t "] {
let (wire, classic) = build_notify_receipt_card("Compiler", body).await;
let BubbleWire::Markdown(md) = &wire else {
panic!("empty-body card must ride the flat markdown wire (#38 guard)");
};
assert!(!md.contains("<details>"), "no wrapper to reject: {md}");
assert!(
!classic.contains("<details>"),
"no wrapper to leak: {classic}"
);
assert_eq!(md, "📨 From **Compiler**", "flat markdown card");
assert_eq!(classic, "📨 From <b>Compiler</b>", "flat classic card");
}
}
#[tokio::test]
async fn notify_preview_truncates_the_first_line_only() {
let body = format!("{}\nsecond line stays in the body", "x".repeat(80));
let (wire, _) = build_notify_receipt_card("Worker", &body).await;
let BubbleWire::Html(rich) = wire else {
panic!("notify card rides the HTML rich wire (#85)");
};
let preview = format!("{}…", "x".repeat(45));
assert!(
rich.contains(&format!(": {preview}</sub>")),
"preview = first line truncated to 45 chars + ellipsis: {rich}"
);
assert!(
rich.contains("second line stays in the body"),
"the full body survives inside the fold"
);
}
#[tokio::test]
async fn notify_receipt_card_keeps_tables_native_inside_the_fold() {
let body = "| a | b |\n|---|---|\n| 1 | 2 |";
let (wire, _) = build_notify_receipt_card("Compiler", body).await;
let BubbleWire::Html(rich) = wire else {
panic!("notify card rides the HTML rich wire (#85)");
};
assert!(
rich.contains("<details><summary>"),
"wrapper intact: {rich}"
);
assert!(
rich.contains("<pre>a | b</pre>") || rich.contains("a | b"),
"body keeps the rendered table grid: {rich}"
);
assert!(rich.contains("</details>"), "wrapper closes");
}
#[tokio::test]
async fn notify_receipt_card_keeps_a_4719_char_body_whole_on_the_rich_leg() {
const TOTAL: usize = 4719;
let head = "a".repeat(2000);
let inside = "INSIDE-CAP";
let outside = "OUTSIDE-CAP!";
let tail = "TAIL-SENTINEL-#490-DISCLOSURES-HERE";
let outside_at = 3300usize;
let tail_at = TOTAL - tail.len();
let gap1 = "b".repeat(outside_at - (head.len() + inside.len()));
let gap2 = "c".repeat(tail_at - (outside_at + outside.len()));
let body = format!("{head}{inside}{gap1}{outside}{gap2}{tail}");
assert_eq!(
body.chars().count(),
TOTAL,
"fixture is the measured length"
);
let (wire, classic) = build_notify_receipt_card("Compiler", &body).await;
let BubbleWire::Html(rich) = &wire else {
panic!("notify card rides the HTML rich wire (#85)");
};
let last40: String = body.chars().skip(TOTAL - 40).collect();
assert!(
rich.contains(&last40),
"rich payload must carry the body's LAST 40 chars: {last40:?}"
);
assert!(
rich.contains(tail),
"the tail sentinel survives on the rich leg — this is the #490 defect"
);
assert!(
!rich.contains("(truncated)"),
"no cut fires under the rich budget, so the rich leg carries no marker"
);
assert!(
classic.contains(inside),
"content inside the classic budget survives on the fallback"
);
assert!(
!classic.contains(outside),
"content past the classic budget is dropped on the fallback, proving the cap still binds at ~3200"
);
assert!(
classic.contains(tail),
"the classic leg now keeps its tail too; only the MIDDLE is dropped"
);
assert!(
classic.contains("(truncated 1559 chars)"),
"the classic leg states the count it dropped (4719 in, 3160 kept as head+tail: 1559 went)"
);
assert_eq!(
classic.matches("(truncated)").count(),
1,
"the classic fallback carries EXACTLY one truncation marker (the #490 re-cap printed a second)"
);
}
#[test]
fn a_firing_cap_keeps_the_tail_and_states_the_dropped_count_on_both_legs() {
const TOTAL: usize = 5000; let head = "HEAD-SENTINEL";
let tail = "TAIL-SENTINEL-NEEDS-FROM-YOU";
let body = format!(
"{head}{}{tail}",
"m".repeat(TOTAL - head.len() - tail.len())
);
assert_eq!(body.chars().count(), TOTAL);
let (wire, html) = build_bg_echo_bubble(&body, "⚙️ background task result");
let BubbleWire::Markdown(markdown) = &wire else {
panic!("plain echo bubble rides the markdown outbox wire");
};
for (leg, payload) in [("markdown", markdown), ("classic html", &html)] {
assert!(
payload.contains(head),
"{leg}: the opening survives: {}",
&payload[..payload.len().min(120)]
);
assert!(
payload.contains(tail),
"{leg}: the TAIL survives a firing cap — the #490 defect was this being dropped"
);
assert!(
payload.contains("(truncated 1840 chars)"),
"{leg}: the marker states the dropped COUNT"
);
}
assert!(html.starts_with("<blockquote expandable>"));
assert!(html.ends_with("</blockquote>"));
}
#[tokio::test]
async fn above_the_rich_budget_the_tail_still_survives() {
const TOTAL: usize = 40000;
let head = "HEAD-SENTINEL";
let tail = "TAIL-SENTINEL-DISCLOSURES-BLOCK";
let body = format!(
"{head}{}{tail}",
"m".repeat(TOTAL - head.len() - tail.len())
);
assert_eq!(body.chars().count(), TOTAL);
let (wire, classic) = build_notify_receipt_card("Compiler", &body).await;
let BubbleWire::Html(rich) = &wire else {
panic!("notify card rides the HTML rich wire (#85)");
};
assert!(rich.contains(head), "the opening survives the rich cut");
assert!(
rich.contains(tail),
"the tail survives even above the rich budget — 40000 in, 29960 kept, 10040 dropped"
);
assert!(
rich.contains("(truncated 10040 chars)"),
"the rich leg states its own dropped count"
);
assert!(
rich.contains("(truncated)"),
"the card's headline suffix also flags the cut"
);
assert_eq!(
rich.matches("(truncated ").count(),
1,
"one count, adjacent to the material it describes — not repeated as chrome"
);
assert!(
classic.contains(tail),
"classic fallback keeps its tail too"
);
}
#[tokio::test]
async fn notify_verdict_reports_what_each_leg_would_drop() {
use crate::utils::echo_budget::{
ECHO_BODY_CAP_CHARS, ECHO_BODY_CAP_CHARS_RICH, sender_over_cap_signal,
};
const TOTAL: usize = 4719;
const DROPPED_ON_CLASSIC: usize = 1559;
let body: String = "x".repeat(TOTAL);
let (wire, classic) = build_notify_receipt_card("Compiler", &body).await;
assert!(
matches!(wire, BubbleWire::Html(_)),
"the notify card rides the rich HTML wire — the leg whose budget #490 split"
);
let signal = sender_over_cap_signal(&body);
let expected = format!(
" Payload {TOTAL} chars \u{2014} rich leg ({ECHO_BODY_CAP_CHARS_RICH} budget) drops 0; \
classic fallback ({ECHO_BODY_CAP_CHARS} budget) drops {DROPPED_ON_CLASSIC}."
);
assert_eq!(
signal, expected,
"the verdict must expose 0 dropped on the rich leg and the classic count, naming both legs"
);
assert!(
classic.contains(&format!("(truncated {DROPPED_ON_CLASSIC} chars)")),
"the verdict's classic count must match the marker the fallback renders"
);
assert_eq!(sender_over_cap_signal("short body"), "");
assert_eq!(
sender_over_cap_signal(&"y".repeat(ECHO_BODY_CAP_CHARS)),
"",
"a body exactly AT the classic budget is not over it"
);
}