1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
//! `keyhog explain` migration for retired hot-id aliases.
//!
//! Moved out of `src/subcommands/explain.rs` per the `no_inline_tests_in_src`
//! gate (the inline module also carried a `.expect(...)`, which tripped
//! `subcommands_explain_no_unwrap_expect`). The tested helpers
//! `canonical_for_hot_id` / `explain_not_found` are reached through the hidden
//! testing namespace so the helper functions do not become public CLI API.
use keyhog::testing::{CliTestApi as _, API};
use keyhog_core::DetectorSpec;
use std::path::Path;
fn embedded() -> Vec<DetectorSpec> {
// The default `detectors` sentinel: `validate_detector_path_for_scan`
// exempts it from the "explicit path must exist" check, so when no
// `detectors/` dir is present in the test cwd it falls back to the embedded
// corpus (the production default). An explicit non-existent path now errors
// by design, so we must use the sentinel, not a bogus directory name.
API.load_detectors_or_embedded(Path::new("detectors"))
.expect("embedded detector corpus must load")
}
/// Every retired id migration names an existing canonical detector. The CLI
/// rejects the retired id instead of executing it as an alias.
#[test]
fn hot_ids_resolve_to_real_detectors() {
let detectors = embedded();
let ids: std::collections::HashSet<String> =
detectors.iter().map(|d| d.id.to_lowercase()).collect();
// The hot patterns with a canonical registry equivalent.
for hot in [
"hot-github_pat",
"hot-openai_key",
"hot-aws_key",
"hot-aws_session_key",
"hot-sendgrid_key",
"hot-slack_bot_token",
"hot-slack_user_token",
"hot-square_secret",
] {
let canon = API
.canonical_for_hot_id(hot)
.unwrap_or_else(|| panic!("{hot} must map to a canonical detector"));
assert!(
ids.contains(canon),
"{hot} maps to '{canon}', which is not in the embedded registry \
(detector renamed? update canonical_for_hot_id)"
);
}
assert_eq!(
API.canonical_for_hot_id("HOT-GITHUB_PAT"),
Some("github-classic-pat"),
"retired-id migrations should be ASCII-case-insensitive without lowercasing the request"
);
assert_eq!(
API.canonical_for_hot_id("hot-square_secret"),
Some("square-access-token"),
"Square hot ids must resolve to Square payments, not Squarespace"
);
}