use crawlex::frontier::dedupe::DedupeProbe;
use crawlex::frontier::Dedupe;
use url::Url;
#[test]
fn first_insert_is_new_second_is_dupe() {
let d = Dedupe::new(1000, 0.001);
assert!(d.insert_if_new("https://example.com/a"));
assert!(!d.insert_if_new("https://example.com/a"));
}
#[test]
fn distinct_keys_are_all_new() {
let d = Dedupe::new(1000, 0.001);
for i in 0..500 {
let key = format!("https://example.com/p/{i}");
assert!(d.insert_if_new(&key), "key {i} unexpectedly marked dupe");
}
}
#[test]
fn exact_recent_wipe_does_not_corrupt_bloom() {
let d = Dedupe::new(200_000, 0.01);
let known = "https://pre-wipe.example/zero";
assert!(d.insert_if_new(known));
for i in 0..100_500 {
let _ = d.insert_if_new(&format!("https://bulk.example/{i}"));
}
assert!(!d.insert_if_new(known), "bloom lost a pre-wipe key");
}
#[test]
fn false_positive_rate_stays_reasonable() {
let d = Dedupe::new(10_000, 0.01);
for i in 0..10_000 {
let _ = d.insert_if_new(&format!("A-{i}"));
}
let mut fps = 0usize;
for i in 0..10_000 {
if !d.insert_if_new(&format!("B-{i}")) {
fps += 1;
}
}
let rate = fps as f64 / 10_000.0;
assert!(
rate < 0.02,
"fp rate {:.4} exceeded 2% ceiling (raw {})",
rate,
fps
);
}
#[test]
fn url_probe_uses_exact_canonical_identity_before_bloom_answer_is_terminal() {
let d = Dedupe::new(1024, 0.01);
let first = Url::parse("https://www.example.com/store/index.html?utm_source=x&a=1").unwrap();
let alias = Url::parse("http://example.com/store/?a=1&utm_medium=y").unwrap();
let probe = d.probe_url(&first);
assert!(matches!(probe, DedupeProbe::New(_)));
d.mark_seen(probe.identity());
assert!(matches!(
d.probe_url(&alias),
DedupeProbe::RecentDuplicate(_)
));
}