use super::test_support::{fixture_tenant, fixture_user, now, test_config};
use super::*;
use crate::testing::{MemoryRefreshTokenStore, mock_random::MockRng};
#[tokio::test]
async fn status_check_true_allows_rotation() {
let store = MemoryRefreshTokenStore::new();
let config = test_config();
let rng = MockRng::new(331);
let ts = now();
let (plaintext, _) = issue_refresh_token(
IssueRequest {
user_id: &fixture_user(),
tenant_id: &fixture_tenant(),
device_info: None,
family_id: None,
device_id: None,
},
&config,
&store,
&rng,
ts,
)
.await
.unwrap();
let (session, new_token) = refresh_session_with_status_check(
&plaintext,
&store,
&config,
&rng,
ts,
None,
|_user_id| async { true },
)
.await
.expect("status=true must allow refresh");
assert!(session.auth_state.is_authenticated());
assert!(
new_token.is_some(),
"rotation must run when status check passes"
);
}
#[tokio::test]
async fn status_check_false_refuses_and_preserves_token() {
let store = MemoryRefreshTokenStore::new();
let config = test_config();
let rng = MockRng::new(332);
let ts = now();
let (plaintext, original_record) = issue_refresh_token(
IssueRequest {
user_id: &fixture_user(),
tenant_id: &fixture_tenant(),
device_info: None,
family_id: None,
device_id: None,
},
&config,
&store,
&rng,
ts,
)
.await
.unwrap();
let err = refresh_session_with_status_check(
&plaintext,
&store,
&config,
&rng,
ts,
None,
|_user_id| async { false },
)
.await
.expect_err("status=false must refuse refresh");
assert!(
matches!(err, RefreshError::AccountInactive),
"expected AccountInactive, got {err:?}"
);
let token_hash = hash_token(&plaintext, config.hash_pepper.as_deref());
let still_there = store
.find_token(&token_hash)
.await
.unwrap()
.expect("refused refresh must NOT consume the token");
assert!(
!still_there.revoked,
"refused refresh must NOT revoke the token"
);
assert_eq!(still_there.id, original_record.id);
}
#[tokio::test]
async fn revoked_token_bypasses_status_check() {
let store = MemoryRefreshTokenStore::new();
let config = test_config();
let rng = MockRng::new(333);
let ts = now();
let (plaintext, _) = issue_refresh_token(
IssueRequest {
user_id: &fixture_user(),
tenant_id: &fixture_tenant(),
device_info: None,
family_id: None,
device_id: None,
},
&config,
&store,
&rng,
ts,
)
.await
.unwrap();
let _ = refresh_session(&plaintext, &store, &config, &rng, ts, None)
.await
.unwrap();
let err = refresh_session_with_status_check(
&plaintext,
&store,
&config,
&rng,
ts,
None,
|_user_id| async { false },
)
.await
.expect_err("revoked token must error");
assert!(
matches!(err, RefreshError::Revoked),
"revoked-token compromise signal must take priority over status check, got {err:?}"
);
}