use keyhog_core::{SuccessPolicy, SuccessSpec};
use keyhog_verifier::testing::{
TestApi, TestVerificationCache as VerificationCache, VerifierTestApi, VerifierTestCache,
};
use std::collections::HashMap;
use std::time::Duration;
#[test]
fn retryable_http_status_matches_429_and_500_through_504_only() {
assert!(TestApi.retryable_http_status_for_test(429));
assert!(TestApi.retryable_http_status_for_test(500));
assert!(TestApi.retryable_http_status_for_test(502));
assert!(TestApi.retryable_http_status_for_test(504));
assert!(!TestApi.retryable_http_status_for_test(505));
assert!(!TestApi.retryable_http_status_for_test(499));
assert!(!TestApi.retryable_http_status_for_test(200));
assert!(!TestApi.retryable_http_status_for_test(403));
assert!(!TestApi.retryable_http_status_for_test(400));
}
#[test]
fn success_spec_is_explicit_only_with_authoritative_evidence() {
assert!(!TestApi.success_spec_is_explicit_for_test(&SuccessSpec::default()));
assert!(!TestApi.success_spec_is_explicit_for_test(&SuccessSpec {
status: Some(200),
policy: Some(SuccessPolicy::StatusWithErrorBackstop),
..Default::default()
}));
assert!(TestApi.success_spec_is_explicit_for_test(&SuccessSpec {
status: Some(202),
policy: Some(SuccessPolicy::StatusAuthoritative),
..Default::default()
}));
assert!(TestApi.success_spec_is_explicit_for_test(&SuccessSpec {
json_path: Some("$.ok".into()),
policy: Some(SuccessPolicy::BodyPositive),
..Default::default()
}));
assert!(TestApi.success_spec_is_explicit_for_test(&SuccessSpec {
body_contains: Some("login".into()),
policy: Some(SuccessPolicy::BodyPositive),
..Default::default()
}));
assert!(!TestApi.success_spec_is_explicit_for_test(&SuccessSpec {
body_not_contains: Some("error".into()),
policy: Some(SuccessPolicy::BodyPositive),
..Default::default()
}));
}
#[test]
fn explicit_success_body_with_error_field_stays_live() {
let body = r#"{"errors":["scope warning"],"login":"octocat"}"#;
assert!(
TestApi.resolve_live_verdict_for_test(true, true, body),
"an explicit matched success contract must remain Live"
);
}
#[test]
fn no_success_contract_runs_the_error_backstop() {
let body = r#"{"errors":["invalid token"]}"#;
assert!(
!TestApi.resolve_live_verdict_for_test(true, false, body),
"with no explicit contract the populated-error backstop flips to Dead"
);
assert!(
TestApi.resolve_live_verdict_for_test(true, false, r#"{"status":"ok"}"#),
"a benign body with no contract stays Live"
);
assert!(
!TestApi.resolve_live_verdict_for_test(false, true, "irrelevant"),
"a not-live response is never resurrected"
);
}
#[test]
fn plaintext_negated_error_words_do_not_indicate_error() {
assert!(!TestApi.body_indicates_error_for_test("no errors"));
assert!(!TestApi.body_indicates_error_for_test("never expired"));
assert!(!TestApi.body_indicates_error_for_test("0 errors"));
assert!(!TestApi.body_indicates_error_for_test("error_rate"));
}
#[test]
fn plaintext_real_error_words_still_indicate_error() {
assert!(TestApi.body_indicates_error_for_test("request error occurred"));
assert!(TestApi.body_indicates_error_for_test("authentication error"));
assert!(TestApi.body_indicates_error_for_test("token expired"));
}
fn oversized_metadata() -> HashMap<String, String> {
let mut m = HashMap::new();
m.insert("arn".to_string(), "arn:aws:iam::1:user/x".to_string());
for i in 0..20 {
m.insert(format!("n{i:02}"), "v".to_string());
}
m
}
fn round_trip(metadata: HashMap<String, String>) -> HashMap<String, String> {
let cache = VerificationCache::new(Duration::from_secs(3600));
cache.put(
"cred",
"detector",
keyhog_core::VerificationResult::Live,
metadata,
);
let (_result, stored) = cache
.get("cred", "detector")
.expect("entry must be retrievable");
stored
}
#[test]
fn identity_metadata_survives_the_cap_deterministically() {
let first = round_trip(oversized_metadata());
let second = round_trip(oversized_metadata());
assert_eq!(first.len(), 16);
assert!(
first.contains_key("arn"),
"arn is an identity field, kept first"
);
assert!(!first.contains_key("n19"), "n19 is dropped past the cap");
assert_eq!(
first.keys().collect::<std::collections::BTreeSet<_>>(),
second.keys().collect::<std::collections::BTreeSet<_>>(),
"the retained key set must be identical across runs"
);
}
#[test]
fn inflight_cap_bypass_increments_a_visible_counter() {
let a = TestApi.record_inflight_cap_bypass_for_test(10_000);
let b = TestApi.record_inflight_cap_bypass_for_test(10_000);
assert_eq!(
b,
a + 1,
"each dedup-cap bypass bumps the surfaced counter by one"
);
assert!(a >= 1);
}