use super::{
apply_path_confidence_penalties, apply_post_ml_penalties_with_encoded_text_lift,
is_degenerate_repeat_at, longest_repeat_run_len, max_repeat_run, FIXTURE_PATH_COMPONENTS,
};
fn post_match_policy() -> keyhog_core::DetectorPostMatchConfidenceSpec {
keyhog_core::detector_spec_by_id("generic-secret")
.and_then(|detector| detector.match_confidence)
.map(|confidence| confidence.post_match)
.expect("embedded generic-secret post-match policy")
}
fn is_degenerate_repeat(credential: &str) -> bool {
is_degenerate_repeat_at(credential, post_match_policy().degenerate_run_min_length)
}
#[test]
fn fixture_path_components_load_the_union_superset() {
for expected in [
"test", "tests", "example", "examples", "fixtures", "samples",
] {
assert!(
FIXTURE_PATH_COMPONENTS
.iter()
.any(|c| c.eq_ignore_ascii_case(expected)),
"rules/example-path-components.toml missing `{expected}`"
);
}
}
#[test]
fn path_haircut_fires_for_fixture_dirs_and_halves_confidence() {
let scored = apply_path_confidence_penalties(
0.8,
Some("src/samples/config.rs"),
true,
post_match_policy().fixture_path_multiplier,
);
assert!(
(scored - 0.4).abs() < 1e-9,
"expected 0.8 × 0.5 = 0.4 for a samples/ path, got {scored}"
);
let scored = apply_path_confidence_penalties(
0.8,
Some("a/fixtures/b.rs"),
true,
post_match_policy().fixture_path_multiplier,
);
assert!((scored - 0.4).abs() < 1e-9, "fixtures/ path, got {scored}");
}
#[test]
fn path_haircut_does_not_fire_for_ordinary_source() {
let scored = apply_path_confidence_penalties(
0.8,
Some("src/handlers/auth.rs"),
true,
post_match_policy().fixture_path_multiplier,
);
assert!(
(scored - 0.8).abs() < 1e-9,
"ordinary source path must not be haircut, got {scored}"
);
}
#[test]
fn path_haircut_is_disabled_when_penalize_is_false() {
let scored = apply_path_confidence_penalties(
0.8,
Some("a/fixtures/b.rs"),
false,
post_match_policy().fixture_path_multiplier,
);
assert!(
(scored - 0.8).abs() < 1e-9,
"penalize=false must keep full confidence, got {scored}"
);
}
#[test]
fn path_haircut_sanitizes_nan_even_without_a_path() {
assert_eq!(
apply_path_confidence_penalties(
f64::NAN,
None,
true,
post_match_policy().fixture_path_multiplier,
),
0.0
);
}
#[test]
fn generic_detector_penalty_policy_is_pinned() {
let policy = post_match_policy();
assert_eq!(policy.placeholder_multiplier, 0.05);
assert_eq!(policy.low_diversity_multiplier, 0.1);
assert_eq!(policy.degenerate_repeat_multiplier, 0.1);
assert_eq!(policy.data_envelope_multiplier, Some(0.02));
}
#[test]
fn generic_degenerate_low_diversity_value_takes_both_shape_penalties() {
let value = "!".repeat(16);
let scored = apply_post_ml_penalties_with_encoded_text_lift(
1.0,
&value,
false,
false,
false,
post_match_policy(),
);
assert!(
(scored - 0.01).abs() < 1e-9,
"expected 0.01 (0.1 × 0.1), got {scored}"
);
}
#[test]
fn run_len_of_empty_is_zero() {
assert_eq!(longest_repeat_run_len(""), 0);
}
#[test]
fn run_len_of_single_char_is_one() {
assert_eq!(longest_repeat_run_len("a"), 1);
}
#[test]
fn run_len_with_no_repeats_is_one() {
assert_eq!(longest_repeat_run_len("abcdefg"), 1);
}
#[test]
fn run_len_of_all_identical_is_the_length() {
assert_eq!(longest_repeat_run_len("aaaa"), 4);
}
#[test]
fn run_len_finds_a_run_at_the_start() {
assert_eq!(longest_repeat_run_len("XXXXabc"), 4);
}
#[test]
fn run_len_finds_a_run_in_the_middle() {
assert_eq!(longest_repeat_run_len("abYYYYcd"), 4);
}
#[test]
fn run_len_finds_a_run_at_the_end() {
assert_eq!(longest_repeat_run_len("abcZZZZ"), 4);
}
#[test]
fn run_len_returns_the_longest_of_several_runs() {
assert_eq!(longest_repeat_run_len("aaXbbbbYcc"), 4);
}
#[test]
fn run_len_is_byte_based_not_char_based() {
assert_eq!(longest_repeat_run_len("\u{e9}\u{e9}\u{e9}"), 1);
}
#[test]
fn generic_degenerate_run_min_length_is_ten() {
assert_eq!(post_match_policy().degenerate_run_min_length, 10);
}
#[test]
fn custom_detector_repeat_limit_is_applied_exactly() {
assert!(!is_degenerate_repeat_at("aaaa", 5));
assert!(is_degenerate_repeat_at("aaaaa", 5));
}
#[test]
fn a_run_of_nine_is_not_degenerate() {
assert!(!is_degenerate_repeat(&"a".repeat(9)));
}
#[test]
fn a_run_of_exactly_ten_is_degenerate() {
assert!(is_degenerate_repeat(&"a".repeat(10)));
}
#[test]
fn a_run_of_eleven_is_degenerate() {
assert!(is_degenerate_repeat(&"a".repeat(11)));
}
#[test]
fn an_empty_credential_is_not_degenerate() {
assert!(!is_degenerate_repeat(""));
}
#[test]
fn a_high_entropy_body_is_not_degenerate() {
assert!(!is_degenerate_repeat("aB3dE7gH1jK4mN6pQ8rS"));
}
#[test]
fn a_prefixed_placeholder_with_a_long_run_is_degenerate() {
assert!(is_degenerate_repeat("AKIAXXXXXXXXXXXXXXXX"));
}
#[test]
fn a_prefixed_value_with_only_a_nine_run_is_not_degenerate() {
assert!(!is_degenerate_repeat("AKIAXXXXXXXXX")); }
#[test]
fn a_ten_zero_run_is_degenerate() {
assert!(is_degenerate_repeat("key=0000000000"));
}
#[test]
fn a_long_value_whose_longest_run_is_short_is_not_degenerate() {
assert!(!is_degenerate_repeat(
"abababababababababababababababababababab"
));
}
#[test]
fn ratio_of_empty_is_zero() {
assert_eq!(max_repeat_run(""), 0.0);
}
#[test]
fn ratio_of_all_identical_is_one() {
assert_eq!(max_repeat_run("aaaa"), 1.0);
}
#[test]
fn ratio_is_longest_run_over_length() {
assert_eq!(max_repeat_run("aaXX"), 0.5); assert_eq!(max_repeat_run("abcd"), 0.25); }
#[test]
fn ratio_of_single_char_is_one() {
assert_eq!(max_repeat_run("a"), 1.0);
}