use keyhog_scanner::testing::compiler_prefix::{
extract_literal_prefixes, strip_leading_boundary_guard, strip_leading_inline_flags,
};
use keyhog_scanner::testing::compiler_prefix::{
expand_leading_charclass_prefixes, expand_leading_literal_alternation_with_tail,
extract_literal_prefix, leading_literal_run, regex_has_required_literal_run,
strip_leading_zero_width_assertions, MAX_CHARCLASS_PREFIX_EXPANSION,
MIN_DISTINCTIVE_INFIX_CHARS,
};
#[test]
fn plain_alternation_prefix_extracts_both_arms() {
assert_eq!(
extract_literal_prefixes("(AKIA|ASIA)[0-9A-Z]{16}"),
vec!["AKIA".to_string(), "ASIA".to_string()]
);
}
#[test]
fn simple_literal_prefix_extracts() {
assert_eq!(
extract_literal_prefixes("AIza[0-9A-Za-z_-]{35}"),
vec!["AIza".to_string()]
);
}
#[test]
fn boundary_guard_idiom_extracts_the_inner_literal() {
assert_eq!(
extract_literal_prefixes("(?:^|[^A-Za-z0-9_])(sk-[a-zA-Z0-9]{20,})"),
vec!["sk-".to_string()]
);
assert_eq!(
extract_literal_prefixes("(?:^|[^A-Za-z0-9_])(dn_[a-zA-Z0-9_-]{20,})"),
vec!["dn_".to_string()]
);
}
#[test]
fn negative_case_flag_then_alternation_extracts() {
assert_eq!(
extract_literal_prefixes("(?-i)(AKIA|ASIA)[0-9A-Z]{16}"),
vec!["AKIA".to_string(), "ASIA".to_string()]
);
}
#[test]
fn negative_case_flag_then_boundary_guard_extracts() {
assert_eq!(
extract_literal_prefixes("(?-i)(?:^|[^A-Za-z0-9_])(sk-[a-zA-Z0-9]{20,})"),
vec!["sk-".to_string()]
);
}
#[test]
fn strip_inline_flags_handles_positive_and_negative_forms() {
assert_eq!(strip_leading_inline_flags("(?i)abc"), "abc");
assert_eq!(strip_leading_inline_flags("(?-i)abc"), "abc");
assert_eq!(strip_leading_inline_flags("(?im)abc"), "abc");
assert_eq!(strip_leading_inline_flags("(?im-sx)abc"), "abc");
assert_eq!(strip_leading_inline_flags("(?:abc)"), "(?:abc)");
assert_eq!(strip_leading_inline_flags("(?-i:abc)"), "(?-i:abc)");
assert_eq!(strip_leading_inline_flags("abc"), "abc");
}
#[test]
fn strip_boundary_guard_only_for_real_guards() {
assert_eq!(
strip_leading_boundary_guard("(?:^|[^A-Za-z0-9_])(sk-x)"),
Some("(sk-x)")
);
assert_eq!(strip_leading_boundary_guard("(?:foo|bar)x"), None);
assert_eq!(strip_leading_boundary_guard("AKIA[0-9]{16}"), None);
}
#[test]
fn boundary_guard_with_escaped_class_bracket_extracts_inner_literal() {
assert_eq!(
strip_leading_boundary_guard(r"(?:^|[^\]])(ghp_token)"),
Some("(ghp_token)")
);
assert_eq!(
extract_literal_prefixes(r"(?:^|[^\]])(ghp_[A-Za-z0-9]{36})"),
vec!["ghp_".to_string()]
);
}
mod charclass_prefix_expansion_tests {
use super::*;
#[test]
fn deno_kv_class_expands_to_one_prefix_per_member() {
let got = expand_leading_charclass_prefixes("dd[npc]_[a-f0-9]{64}");
assert_eq!(
got,
Some(vec!["ddn_".to_string(), "ddp_".to_string(), "ddc_".to_string()]),
"the class members must expand IN ORDER, each extended past the class to the trailing `_`"
);
}
#[test]
fn plural_extractor_routes_deno_kv_through_the_expansion() {
let got = extract_literal_prefixes("dd[npc]_[a-f0-9]{64}");
assert_eq!(
got,
vec!["ddn_".to_string(), "ddp_".to_string(), "ddc_".to_string()]
);
}
#[test]
fn clean_leading_prefix_is_untouched_by_the_fallback() {
assert_eq!(extract_literal_prefixes("AKIA[0-9A-Z]{16}"), vec!["AKIA"]);
assert_eq!(
extract_literal_prefixes("(?-i)cs_[a-z0-9]{32}"),
vec!["cs_"]
);
}
#[test]
fn ranges_negation_and_perl_classes_are_not_enumerated() {
assert_eq!(expand_leading_charclass_prefixes("dd[a-f]_[0-9]{8}"), None);
assert_eq!(expand_leading_charclass_prefixes("dd[^np]_[0-9]{8}"), None);
assert_eq!(expand_leading_charclass_prefixes("[a-z]{20}"), None);
}
#[test]
fn oversized_class_is_declined() {
let wide: String = ('a'..='z')
.take(MAX_CHARCLASS_PREFIX_EXPANSION + 1)
.collect();
let pattern = format!("xy[{wide}]_[0-9]{{8}}");
assert_eq!(expand_leading_charclass_prefixes(&pattern), None);
}
#[test]
fn sub_floor_branch_refuses_the_whole_expansion() {
assert_eq!(expand_leading_charclass_prefixes("[npc]x[0-9]{8}"), None);
}
#[test]
fn underscore_member_is_a_valid_literal() {
let got = expand_leading_charclass_prefixes("sk[_x]live[0-9]{8}");
assert_eq!(
got,
Some(vec!["sk_live".to_string(), "skxlive".to_string()])
);
}
}
mod zero_width_assertion_strip_tests {
use super::*;
#[test]
fn leading_word_boundary_is_stripped() {
assert_eq!(strip_leading_zero_width_assertions(r"\bser\.x"), r"ser\.x");
assert_eq!(strip_leading_zero_width_assertions(r"\BAKIA"), "AKIA");
assert_eq!(strip_leading_zero_width_assertions(r"\Atoken"), "token");
assert_eq!(strip_leading_zero_width_assertions("^AKIA"), "AKIA");
}
#[test]
fn multiple_assertions_are_all_stripped_order_free() {
assert_eq!(strip_leading_zero_width_assertions(r"^\bAKIA"), "AKIA");
assert_eq!(strip_leading_zero_width_assertions(r"\b\BAKIA"), "AKIA");
}
#[test]
fn non_assertion_is_untouched() {
assert_eq!(strip_leading_zero_width_assertions(r"\d{4}"), r"\d{4}");
assert_eq!(strip_leading_zero_width_assertions("AKIA"), "AKIA");
}
#[test]
fn flagsmith_word_boundary_prefix_is_recovered() {
assert_eq!(
extract_literal_prefixes(r"\bser\.[a-zA-Z0-9]{40,}"),
vec!["ser.".to_string()]
);
assert_eq!(
extract_literal_prefix(r"\bser\.[a-zA-Z0-9]{40,}"),
Some("ser.".to_string())
);
}
}
mod literal_alternation_tail_tests {
use super::*;
#[test]
fn locationiq_alternation_carries_the_trailing_dot() {
assert_eq!(
extract_literal_prefixes(r"(?:pk|sk)\.[a-f0-9]{32,}"),
vec!["pk.".to_string(), "sk.".to_string()]
);
}
#[test]
fn capturing_group_alternation_also_extends() {
assert_eq!(
expand_leading_literal_alternation_with_tail(r"(pk|sk)\.[a-f0-9]{8}"),
Some(vec!["pk.".to_string(), "sk.".to_string()])
);
}
#[test]
fn no_trailing_literal_is_declined() {
assert_eq!(
expand_leading_literal_alternation_with_tail("(?:pk|sk)[a-f0-9]{8}"),
None
);
}
#[test]
fn structured_branch_is_declined() {
assert_eq!(
expand_leading_literal_alternation_with_tail(r"(?:ab[0-9]|cd)\.x"),
None
);
}
#[test]
fn already_sufficient_alternation_keeps_the_existing_path() {
assert_eq!(
extract_literal_prefixes("(AKIA|ASIA)[0-9]{12}"),
vec!["AKIA".to_string(), "ASIA".to_string()]
);
}
#[test]
fn leading_literal_run_unescapes_dot_and_stops_at_class() {
assert_eq!(leading_literal_run(r"\.[a-f0-9]"), ".");
assert_eq!(leading_literal_run("_x[0-9]"), "_x");
assert_eq!(leading_literal_run(".abc"), "");
}
}
mod required_literal_run_tests {
use super::*;
#[test]
fn terraform_atlasv1_infix_is_a_required_run() {
assert!(regex_has_required_literal_run(
r"[a-zA-Z0-9]{14}\.atlasv1\.[a-zA-Z0-9]{67,}",
MIN_DISTINCTIVE_INFIX_CHARS
));
}
#[test]
fn short_or_absent_required_literal_is_not_distinctive() {
assert!(!regex_has_required_literal_run("[a-f0-9]{32}", 8));
assert!(!regex_has_required_literal_run(
r"sk[a-z]{4}_live_[0-9]{20}",
8
));
}
#[test]
fn literal_inside_alternation_is_not_required() {
assert!(!regex_has_required_literal_run(
r"[0-9]{4}(?:atlasv1x|betaaaaa)?[0-9]{4}",
8
));
assert!(!regex_has_required_literal_run(
r"(?:atlasv1x|betbbbbb)[0-9]{4}",
8
));
}
#[test]
fn literal_inside_required_group_is_counted() {
assert!(regex_has_required_literal_run(
r"[0-9]{4}(?:\.atlasv1\.)[0-9]{4}",
8
));
}
#[test]
fn optional_group_literal_is_not_required() {
assert!(!regex_has_required_literal_run(
r"[0-9]{4}(?:\.atlasv1\.)?[0-9]{4}",
8
));
}
}