#![allow(clippy::unwrap_used, clippy::panic, unused_must_use)]
use super::core::{
CiphertextFeed, ExpiryCause, PasRefreshFeed, PersistFeed, RefetchFeed, SvCore, SvDecision,
SvStep, UserinfoFeed,
};
use super::super::sv_cache::CheckResult;
use crate::session_liveness::EncryptedRefreshToken;
fn assert_done(step: SvStep) -> SvDecision {
match step {
SvStep::Done(d) => d,
other => panic!("expected SvStep::Done, got {other:?}"),
}
}
fn stub_ciphertext() -> EncryptedRefreshToken {
EncryptedRefreshToken::from_stored("test-ciphertext-stub".to_owned())
}
fn ct_available() -> CiphertextFeed {
CiphertextFeed::Available { ciphertext: stub_ciphertext() }
}
fn pas_refreshed() -> PasRefreshFeed {
PasRefreshFeed::Refreshed { access_token: "test-access-token".to_owned() }
}
#[test]
fn cache_fresh_short_circuits_with_no_io() {
let (mut core, first) = SvCore::start();
assert!(matches!(first, SvStep::QueryCache));
let step = core.feed_check(CheckResult::Fresh);
assert_eq!(assert_done(step), SvDecision::FreshFromCache);
}
#[test]
fn ciphertext_absent_expires_with_distinct_cause() {
let (mut core, _) = SvCore::start();
core.feed_check(CheckResult::Stale);
let step = core.feed_ciphertext(CiphertextFeed::Absent);
assert_eq!(
assert_done(step),
SvDecision::Expired(ExpiryCause::RefreshCiphertextAbsent),
);
}
#[test]
fn ciphertext_lookup_failure_expires_distinctly() {
let (mut core, _) = SvCore::start();
core.feed_check(CheckResult::Unknown);
let step = core.feed_ciphertext(CiphertextFeed::LookupFailed);
assert_eq!(
assert_done(step),
SvDecision::Expired(ExpiryCause::RefreshLoadFailed),
);
}
#[test]
fn cipher_missing_expires_distinctly() {
let (mut core, _) = SvCore::start();
core.feed_check(CheckResult::Stale);
let step = core.feed_ciphertext(CiphertextFeed::NoCipherConfigured);
assert_eq!(
assert_done(step),
SvDecision::Expired(ExpiryCause::CipherMissing),
);
}
#[test]
fn pas_refresh_rejected_fails_closed() {
let (mut core, _) = SvCore::start();
core.feed_check(CheckResult::Stale);
core.feed_ciphertext(ct_available());
let step = core.feed_pas_refresh(PasRefreshFeed::Rejected);
assert_eq!(
assert_done(step),
SvDecision::Expired(ExpiryCause::PasRefreshRejected),
);
}
#[test]
fn pas_refresh_transient_fails_closed_s_l6() {
let (mut core, _) = SvCore::start();
core.feed_check(CheckResult::Stale);
core.feed_ciphertext(ct_available());
let step = core.feed_pas_refresh(PasRefreshFeed::Transient);
assert_eq!(
assert_done(step),
SvDecision::Expired(ExpiryCause::PasRefreshTransient),
);
}
#[test]
fn cipher_failure_during_pas_refresh_fails_closed() {
let (mut core, _) = SvCore::start();
core.feed_check(CheckResult::Stale);
core.feed_ciphertext(ct_available());
let step = core.feed_pas_refresh(PasRefreshFeed::CipherFailed);
assert_eq!(
assert_done(step),
SvDecision::Expired(ExpiryCause::CipherFailed),
);
}
#[test]
fn userinfo_missing_sv_fails_closed() {
let (mut core, _) = SvCore::start();
core.feed_check(CheckResult::Stale);
core.feed_ciphertext(ct_available());
core.feed_pas_refresh(pas_refreshed());
let step = core.feed_userinfo(UserinfoFeed::MissingSv);
assert_eq!(
assert_done(step),
SvDecision::Expired(ExpiryCause::UserinfoMissingSv),
);
}
#[test]
fn userinfo_rejected_fails_closed() {
let (mut core, _) = SvCore::start();
core.feed_check(CheckResult::Stale);
core.feed_ciphertext(ct_available());
core.feed_pas_refresh(pas_refreshed());
let step = core.feed_userinfo(UserinfoFeed::Rejected);
assert_eq!(
assert_done(step),
SvDecision::Expired(ExpiryCause::UserinfoRejected),
);
}
#[test]
fn userinfo_transient_fails_closed() {
let (mut core, _) = SvCore::start();
core.feed_check(CheckResult::Stale);
core.feed_ciphertext(ct_available());
core.feed_pas_refresh(pas_refreshed());
let step = core.feed_userinfo(UserinfoFeed::Transient);
assert_eq!(
assert_done(step),
SvDecision::Expired(ExpiryCause::UserinfoTransient),
);
}
#[test]
fn persist_failure_expires_without_recording_cache() {
let (mut core, _) = SvCore::start();
core.feed_check(CheckResult::Stale);
core.feed_ciphertext(ct_available());
core.feed_pas_refresh(pas_refreshed());
core.feed_userinfo(UserinfoFeed::Ok { new_sv: 42 });
let step = core.feed_persist(PersistFeed::Failed);
assert_eq!(
assert_done(step),
SvDecision::Expired(ExpiryCause::PersistFailed),
);
}
#[test]
fn persist_ok_emits_record_cache_with_same_sv() {
let (mut core, _) = SvCore::start();
core.feed_check(CheckResult::Stale);
core.feed_ciphertext(ct_available());
core.feed_pas_refresh(pas_refreshed());
core.feed_userinfo(UserinfoFeed::Ok { new_sv: 99 });
let step = core.feed_persist(PersistFeed::Ok);
match step {
SvStep::RecordCache { sv } => assert_eq!(sv, 99),
other => panic!("expected RecordCache {{ sv: 99 }}, got {other:?}"),
}
}
#[test]
fn refetch_missing_expires() {
let (mut core, _) = SvCore::start();
core.feed_check(CheckResult::Stale);
core.feed_ciphertext(ct_available());
core.feed_pas_refresh(pas_refreshed());
core.feed_userinfo(UserinfoFeed::Ok { new_sv: 1 });
core.feed_persist(PersistFeed::Ok);
core.feed_record();
let step = core.feed_refetch(RefetchFeed::Missing);
assert_eq!(
assert_done(step),
SvDecision::Expired(ExpiryCause::ReFetchMissing),
);
}
#[test]
fn full_happy_path_yields_refreshed() {
let (mut core, first) = SvCore::start();
assert!(matches!(first, SvStep::QueryCache));
assert!(matches!(
core.feed_check(CheckResult::Stale),
SvStep::LoadCiphertext
));
assert!(matches!(
core.feed_ciphertext(ct_available()),
SvStep::PasRefresh { .. }
));
assert!(matches!(
core.feed_pas_refresh(pas_refreshed()),
SvStep::UserInfo { .. }
));
assert!(matches!(
core.feed_userinfo(UserinfoFeed::Ok { new_sv: 7 }),
SvStep::PersistSv { new_sv: 7 }
));
assert!(matches!(
core.feed_persist(PersistFeed::Ok),
SvStep::RecordCache { sv: 7 }
));
assert!(matches!(core.feed_record(), SvStep::ReFetch));
assert_eq!(
assert_done(core.feed_refetch(RefetchFeed::Found)),
SvDecision::Refreshed,
);
}