use slop_detector::{analyze, analyze_bundle, Container, EvidenceReport, Finding};
const SHARED: &str = "Our platform unifies ingestion, storage, and search behind one binding \
contract for every downstream team.";
fn q005(report: &EvidenceReport) -> Vec<&Finding> {
report
.quality_patterns
.iter()
.filter(|f| f.rule_id == "SD-Q005")
.collect()
}
fn docs(pairs: &[(&str, &str)]) -> Vec<(String, String)> {
pairs
.iter()
.map(|(p, t)| (p.to_string(), t.to_string()))
.collect()
}
#[test]
fn shared_block_across_two_files_reports_one_cross_file_entry() {
let a = format!("Deck variant one opens differently.\n\n{SHARED}\n");
let b = format!("{SHARED}\n\nDeck variant two closes differently.\n");
let bundle = analyze_bundle(&docs(&[("a.txt", &a), ("b.txt", &b)]));
assert_eq!(bundle.files.len(), 2);
assert_eq!(bundle.cross_file_duplication.len(), 1, "{bundle:?}");
let entry = &bundle.cross_file_duplication[0];
assert_eq!(entry.occurrences.len(), 2);
let paths: Vec<&str> = entry.occurrences.iter().map(|o| o.path.as_str()).collect();
assert_eq!(paths, ["a.txt", "b.txt"]);
for occ in &entry.occurrences {
let text = if occ.path == "a.txt" { &a } else { &b };
let slice = &text[occ.span.0..occ.span.1];
assert!(slice.starts_with("Our platform unifies"), "{occ:?}");
assert!(slice.ends_with("downstream team"), "{occ:?}");
}
assert!(entry.snippet.starts_with("Our platform unifies"));
assert!(!entry.snippet_truncated);
for f in &bundle.files {
assert_eq!(q005(&f.report).len(), 0, "{}", f.path);
}
}
#[test]
fn disjoint_files_report_no_cross_file_duplication() {
let bundle = analyze_bundle(&docs(&[
(
"a.txt",
"The first document talks about invoices and renewal dates for the quarter.",
),
(
"b.txt",
"The second document covers hiring plans and office moves for next year.",
),
]));
assert!(bundle.cross_file_duplication.is_empty(), "{bundle:?}");
}
#[test]
fn three_way_share_groups_into_one_entry() {
let mk = |head: &str| format!("{head}\n\n{SHARED}\n");
let a = mk("Variant a.");
let b = mk("Variant b.");
let c = mk("Variant c.");
let bundle = analyze_bundle(&docs(&[("a.txt", &a), ("b.txt", &b), ("c.txt", &c)]));
assert_eq!(bundle.cross_file_duplication.len(), 1, "{bundle:?}");
let entry = &bundle.cross_file_duplication[0];
let paths: Vec<&str> = entry.occurrences.iter().map(|o| o.path.as_str()).collect();
assert_eq!(paths, ["a.txt", "b.txt", "c.txt"]);
}
#[test]
fn within_file_repeats_do_not_leak_to_bundle_level() {
let a = format!("{SHARED} Filler sentence with several distinct words here.\n\n{SHARED}\n");
let b = "A wholly different second file with no repeated material at all.".to_string();
let bundle = analyze_bundle(&docs(&[("a.txt", &a), ("b.txt", &b)]));
assert!(bundle.cross_file_duplication.is_empty(), "{bundle:?}");
assert_eq!(q005(&bundle.files[0].report).len(), 1, "{bundle:?}");
assert_eq!(q005(&bundle.files[1].report).len(), 0);
}
#[test]
fn fenced_duplicate_reports_cross_file_with_container_annotation() {
let block = "let a = 1; let b = 2; let c = 3; let d = 4; let e = 5; let f = 6;";
let a = format!("Alpha file opens here.\n\n```\n{block}\n```\n");
let b = format!("Totally different beta preamble.\n\n```\n{block}\n```\n");
let bundle = analyze_bundle(&docs(&[("a.txt", &a), ("b.txt", &b)]));
assert_eq!(bundle.cross_file_duplication.len(), 1, "{bundle:?}");
let entry = &bundle.cross_file_duplication[0];
assert_eq!(entry.occurrences.len(), 2);
for occ in &entry.occurrences {
assert_eq!(occ.container, Container::FencedCode, "{occ:?}");
let text = if occ.path == "a.txt" { &a } else { &b };
assert!(
text[occ.span.0..occ.span.1].starts_with("let a = 1"),
"{occ:?}"
);
}
}
#[test]
fn cross_file_prefix_decoy_does_not_mask_the_later_pair() {
let decoy = "alpha beta gamma delta epsilon zeta eta theta wrong ending.";
let full = "alpha beta gamma delta epsilon zeta eta theta iota kappa.";
let bundle = analyze_bundle(&docs(&[("d.txt", decoy), ("a.txt", full), ("b.txt", full)]));
assert_eq!(bundle.cross_file_duplication.len(), 1, "{bundle:?}");
let entry = &bundle.cross_file_duplication[0];
let paths: Vec<&str> = entry.occurrences.iter().map(|o| o.path.as_str()).collect();
assert_eq!(paths, ["a.txt", "b.txt"], "the decoy file is not a copy");
for occ in &entry.occurrences {
assert_eq!(
&full[occ.span.0..occ.span.1],
"alpha beta gamma delta epsilon zeta eta theta iota kappa",
"the full 10-word run, not the 8-word prefix"
);
}
}
#[test]
fn cross_file_lower_forward_candidate_with_longer_total_run_wins() {
let full = "alpha beta gamma delta epsilon zeta eta theta iota kappa lambda mu nu";
let mut a = format!("{full}. ");
for i in 0..33 {
a.push_str(&format!(
"alpha beta gamma delta epsilon zeta eta theta aone{i}. "
));
}
for i in 0..33 {
a.push_str(&format!(
"beta gamma delta epsilon zeta eta theta iota btwo{i}. "
));
}
for i in 0..33 {
a.push_str(&format!(
"gamma delta epsilon zeta eta theta iota kappa cthree{i}. "
));
}
for i in 0..33 {
a.push_str(&format!(
"delta epsilon zeta eta theta iota kappa lambda dfour{i}. "
));
}
a.push_str("epsilon zeta eta theta iota kappa lambda mu nu tail.");
let b = format!("{full} tail.");
let bundle = analyze_bundle(&docs(&[("a.txt", &a), ("b.txt", &b)]));
assert_eq!(bundle.cross_file_duplication.len(), 1, "{bundle:?}");
let entry = &bundle.cross_file_duplication[0];
assert_eq!(entry.occurrences.len(), 2);
for occ in &entry.occurrences {
let text = if occ.path == "a.txt" { &a } else { &b };
assert_eq!(
&text[occ.span.0..occ.span.1],
full,
"the entry must span the maximal 13-word total run: {occ:?}"
);
}
}
#[test]
fn duplicate_path_labels_never_panic_and_snippets_are_correct() {
let short = "A short unrelated first file.";
let long = format!(
"Filler paragraph one has distinct words here. Filler paragraph two also has \
other distinct words. Filler paragraph three keeps the shared block's offset \
beyond the first file's length.\n\n{SHARED}\n"
);
let other = format!("{SHARED}\n");
let bundle = analyze_bundle(&docs(&[
("dup.txt", short),
("dup.txt", &long),
("other.txt", &other),
]));
assert_eq!(bundle.cross_file_duplication.len(), 1, "{bundle:?}");
let entry = &bundle.cross_file_duplication[0];
assert!(
entry.snippet.starts_with("Our platform unifies"),
"{entry:?}"
);
assert_eq!(entry.occurrences.len(), 2);
assert_eq!(entry.occurrences[0].path, "dup.txt");
assert_eq!(entry.occurrences[1].path, "other.txt");
let (s, e) = entry.occurrences[0].span;
assert!(
e > short.len(),
"the span must come from the second dup.txt"
);
assert!(long[s..e].starts_with("Our platform unifies"));
let (s, e) = entry.occurrences[1].span;
assert!(other[s..e].starts_with("Our platform unifies"));
}
#[test]
fn passage_shared_by_more_than_21_files_keeps_every_occurrence() {
let pairs: Vec<(String, String)> = (0..23)
.map(|i| {
(
format!("f{i:02}.txt"),
format!("Variant number{i}.\n\n{SHARED}\n"),
)
})
.collect();
let bundle = analyze_bundle(&pairs);
assert_eq!(bundle.cross_file_duplication.len(), 1, "{bundle:?}");
let entry = &bundle.cross_file_duplication[0];
assert_eq!(
entry.occurrences.len(),
23,
"every occurrence must be retained: {entry:?}"
);
let paths: Vec<&str> = entry.occurrences.iter().map(|o| o.path.as_str()).collect();
let expected: Vec<String> = (0..23).map(|i| format!("f{i:02}.txt")).collect();
assert_eq!(paths, expected, "occurrences in file order");
for (occ, (_, text)) in entry.occurrences.iter().zip(&pairs) {
assert!(text[occ.span.0..occ.span.1].starts_with("Our platform unifies"));
}
}
#[test]
fn per_file_reports_equal_single_document_analyze() {
let a = format!("We delve into the numbers.\n\n{SHARED}\n");
let b = format!("{SHARED}\n\nRest assured, the totals held.\n");
let bundle = analyze_bundle(&docs(&[("a.txt", &a), ("b.txt", &b)]));
for (path, text) in [("a.txt", &a), ("b.txt", &b)] {
let single = analyze(text);
let in_bundle = &bundle.files.iter().find(|f| f.path == path).unwrap().report;
assert_eq!(in_bundle, &single, "{path}");
assert_eq!(
serde_json::to_string(in_bundle).unwrap(),
serde_json::to_string(&single).unwrap(),
"{path}: byte-identical serialization"
);
}
}
#[test]
fn bundle_report_is_deterministic() {
let a = format!("Variant one.\n\n{SHARED}\n");
let b = format!("Variant two.\n\n{SHARED}\n");
let d = docs(&[("a.txt", &a), ("b.txt", &b)]);
let x = serde_json::to_string(&analyze_bundle(&d)).unwrap();
let y = serde_json::to_string(&analyze_bundle(&d)).unwrap();
assert_eq!(x, y);
}