use crate::brain::agent::service::announcement_loop::{
OutgoingTextRing, TextLoopAction, near_duplicate,
};
use crate::brain::agent::service::helpers::{normalize_loop_text, normalized_call_signature};
use serde_json::json;
#[test]
fn normalize_strips_digits_punctuation_and_lowercases() {
assert_eq!(
normalize_loop_text("Echo \"Hello, World!\" 42 times -- really?!"),
"echo hello world times really"
);
}
#[test]
fn normalize_collapses_whitespace_and_caps_length() {
assert_eq!(normalize_loop_text("a b\n\nc"), "a b c");
let long = "word ".repeat(500);
assert!(normalize_loop_text(&long).chars().count() <= 400);
}
#[test]
fn normalize_keeps_cyrillic_letters() {
assert_eq!(
normalize_loop_text("Отправляю 6 подтверждений в ДДС!"),
"отправляю подтверждений в ддс"
);
}
#[test]
fn counter_variations_collide_after_normalization() {
let a = "bash: echo \"Отправляю 1 подтверждение в ДДС\"";
let b = "bash: echo \"Отправляю 2 подтверждение в ДДС\"";
assert_eq!(normalize_loop_text(a), normalize_loop_text(b));
let c = "bash: echo \"Sending confirmation 3 of 6 to DDS with the PDF attachment\"";
let d = "bash: echo \"Sending confirmation 4 of 6 to DDS with the PDF attachment\"";
assert_eq!(normalize_loop_text(c), normalize_loop_text(d));
}
#[test]
fn different_commands_stay_apart_after_normalization() {
assert_ne!(
normalize_loop_text("cargo build --release"),
normalize_loop_text("cargo test --all-features")
);
assert_ne!(
normalize_loop_text("git status"),
normalize_loop_text("ls -la")
);
}
#[test]
fn counter_variant_tool_search_queries_collide() {
let a = normalized_call_signature(
"tool_search",
&json!({"query": "send a document file to telegram"}),
);
let b = normalized_call_signature(
"tool_search",
&json!({"query": "send a document file to telegram 2"}),
);
assert_eq!(a, b);
}
#[test]
fn signature_collapses_counters_punctuation_and_case() {
let a = normalized_call_signature("bash", &json!({"command": "echo \"Attempt 1 of 6\""}));
let b = normalized_call_signature("bash", &json!({"command": "echo attempt 2 of 6"}));
assert_eq!(a, b);
}
#[test]
fn different_tool_search_queries_stay_apart() {
let a = normalized_call_signature(
"tool_search",
&json!({"query": "send a document file to telegram"}),
);
let b = normalized_call_signature("tool_search", &json!({"query": "schedule a cron job"}));
assert_ne!(a, b);
}
#[test]
fn signature_is_namespaced_by_tool_name() {
let a = normalized_call_signature("tool_search", &json!({"query": "send"}));
let b = normalized_call_signature("telegram_send", &json!({"query": "send"}));
assert_ne!(a, b);
}
#[test]
fn read_file_chunk_offsets_would_collide_hence_excluded() {
let a = normalized_call_signature(
"read_file",
&json!({"path": "src/main.rs", "start_line": 100, "line_count": 50}),
);
let b = normalized_call_signature(
"read_file",
&json!({"path": "src/main.rs", "start_line": 150, "line_count": 50}),
);
assert_eq!(a, b);
}
#[test]
fn counter_variations_collide() {
let a = "echo \"Отправляю 1 подтверждение в ДДС\"";
let b = "echo \"Отправляю 2 подтверждение в ДДС\"";
assert!(near_duplicate(a, b));
let c = "Sending confirmation 3 of 6 to DDS with the PDF attachment";
let d = "Sending confirmation 4 of 6 to DDS with the PDF attachment";
assert!(near_duplicate(c, d));
}
#[test]
fn different_commands_do_not_collide() {
assert!(!near_duplicate(
"cargo build --release",
"cargo test --all-features"
));
assert!(!near_duplicate("git status", "ls -la"));
}
#[test]
fn short_texts_require_exact_normalized_match() {
assert!(near_duplicate("echo hi", "echo hi!"));
assert!(!near_duplicate("echo hi", "echo yo"));
}
#[test]
fn similar_but_different_reports_do_not_collide() {
let a = "Deployed backend v1.2 to prod, all checks green";
let b = "Deployed frontend v3.4 to staging, all checks green";
assert!(!near_duplicate(a, b));
}
#[test]
fn empty_or_digit_only_text_is_not_a_duplicate() {
assert!(!near_duplicate("", ""));
assert!(!near_duplicate("12345", "12345"));
}
#[test]
fn trip_sequence_nudges_then_aborts() {
let mut ring = OutgoingTextRing::default();
let texts = [
"Отправляю 1 подтверждение по документу в ДДС и продолжаю обработку, ожидайте",
"Отправляю 2 подтверждение по документу в ДДС и продолжаю обработку, ожидайте",
"Отправляю 3 подтверждение по документу в ДДС и продолжаю обработку, ожидайте",
"Отправляю 4 подтверждение по документу в ДДС и продолжаю обработку, ожидайте",
];
assert_eq!(ring.record_and_check(texts[0]), TextLoopAction::Continue);
assert_eq!(ring.record_and_check(texts[1]), TextLoopAction::Continue);
assert_eq!(ring.record_and_check(texts[2]), TextLoopAction::Nudge);
assert_eq!(ring.record_and_check(texts[3]), TextLoopAction::Abort);
}
#[test]
fn varied_genuine_texts_never_trip() {
let mut ring = OutgoingTextRing::default();
let texts = [
"Deployed backend v1.2 to prod, all 42 checks green",
"Deployed frontend v3.4 to staging, all 51 checks green",
"Ran cargo clippy across the workspace, zero warnings",
"Merged the session-store refactor after review",
"Scheduled the nightly backup rotation job",
"Summarized the standup notes and posted them",
];
for t in texts {
assert_eq!(
ring.record_and_check(t),
TextLoopAction::Continue,
"text: {t}"
);
}
}
#[test]
fn ring_rotation_still_trips_after_old_entries_drop() {
let mut ring = OutgoingTextRing::default();
for t in [
"First unrelated answer about the config audit",
"Second unrelated answer with the benchmark numbers",
"Third unrelated answer summarizing the migration",
"Fourth unrelated answer about the flaky test fix",
"Fifth unrelated answer closing out the review",
"Sixth unrelated answer about the onboarding flow",
"Seventh unrelated answer covering the rate limits",
"Eighth unrelated answer wrapping up the retrospective",
] {
assert_eq!(ring.record_and_check(t), TextLoopAction::Continue);
}
let loopy = "Отправляю подтверждение по документу в ДДС и продолжаю обработку, ожидайте ";
let first = format!("{loopy}1");
let second = format!("{loopy}2");
let third = format!("{loopy}3");
assert_eq!(ring.record_and_check(&first), TextLoopAction::Continue);
assert_eq!(ring.record_and_check(&second), TextLoopAction::Continue);
assert_eq!(ring.record_and_check(&third), TextLoopAction::Nudge);
}
const LUNA_FIXTURE: &str = include_str!("fixtures/luna_echo_loop.txt");
fn fixture_lines(prefix: &str) -> Vec<String> {
LUNA_FIXTURE
.lines()
.filter(|l| !l.trim_start().starts_with('#') && l.starts_with(prefix))
.map(|l| l[prefix.len()..].to_string())
.collect()
}
#[test]
fn luna_bash_echoes_all_normalize_identically() {
let cmds = fixture_lines("bash|");
assert!(cmds.len() >= 6, "fixture must carry the full echo loop");
let mut normalized = cmds.iter().map(|c| normalize_loop_text(c));
let first = normalized.next().unwrap();
assert!(!first.is_empty());
for n in normalized {
assert_eq!(n, first, "counter variant escaped the tool-layer net");
}
}
#[test]
fn luna_announcements_trip_the_ring() {
let texts = fixture_lines("text|");
assert!(texts.len() >= 6, "fixture must carry the announcement loop");
let mut ring = OutgoingTextRing::default();
let mut saw_nudge = false;
let mut saw_abort = false;
for t in &texts {
match ring.record_and_check(t) {
TextLoopAction::Nudge => saw_nudge = true,
TextLoopAction::Abort => {
assert!(saw_nudge, "abort fired before the nudge did");
saw_abort = true;
}
TextLoopAction::Continue => {
assert!(!saw_nudge, "loop continued after the nudge");
}
}
}
assert!(saw_nudge, "the fixture never tripped the nudge");
assert!(saw_abort, "the fixture never escalated to abort");
}
#[test]
fn subset_announcement_counts_as_near_duplicate() {
assert!(near_duplicate(
"Sending the zip to this thread now:",
"Sending the zip:"
));
}
#[test]
fn overlap_clause_spares_unrelated_texts() {
assert!(!near_duplicate(
"Sending the zip to this thread now:",
"Running the test suite again"
));
}
const ZIP_SEND_FIXTURE: &str = include_str!("fixtures/zip_send_announcement_loop.txt");
fn zip_send_fixture_lines(prefix: &str) -> Vec<String> {
ZIP_SEND_FIXTURE
.lines()
.filter(|l| !l.trim_start().starts_with('#') && l.starts_with(prefix))
.map(|l| l[prefix.len()..].to_string())
.collect()
}
#[test]
fn deepseek_zip_send_announcements_nudge_then_abort() {
let texts = zip_send_fixture_lines("text|");
assert_eq!(texts.len(), 8, "fixture must carry all eight announcements");
let mut ring = OutgoingTextRing::default();
let expected = [
TextLoopAction::Continue,
TextLoopAction::Continue,
TextLoopAction::Continue,
TextLoopAction::Nudge,
TextLoopAction::Continue,
TextLoopAction::Continue,
TextLoopAction::Continue,
TextLoopAction::Abort,
];
for (t, want) in texts.iter().zip(&expected) {
assert_eq!(ring.record_and_check(t), *want, "text: {t}");
}
}
#[test]
fn deepseek_zip_send_tool_queries_are_reworded_not_identical() {
let queries = zip_send_fixture_lines("tool_search|");
assert!(
queries.len() >= 7,
"fixture must carry the re-activation spiral"
);
let mut seen = std::collections::HashSet::new();
for q in &queries {
let n = normalize_loop_text(q);
assert!(
!seen.contains(&n),
"query repeat would invalidate the #961 shape: {n}"
);
seen.insert(n);
}
}