#![allow(clippy::unwrap_used)]
use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant};
use chrono::Utc;
use rust_supabase_sdk::auth::Session;
use rust_supabase_sdk::{InMemorySessionStore, SessionStore};
use serde_json::json;
fn make_session(token: &str) -> Session {
Session {
access_token: token.into(),
token_type: "bearer".into(),
expires_in: 3600,
expires_at: Utc::now().timestamp() + 3600,
refresh_token: format!("refresh-{token}"),
user: serde_json::from_value(json!({
"id": "u1",
"aud": "auth",
"role": "auth",
"created_at": "2024-01-01T00:00:00Z"
}))
.unwrap(),
}
}
#[test]
fn store_survives_8_readers_4_writers_2_clearers() {
let store: Arc<InMemorySessionStore> = Arc::new(InMemorySessionStore::new());
let deadline = Instant::now() + Duration::from_millis(200);
let mut handles = Vec::new();
for _ in 0..8 {
let s = Arc::clone(&store);
handles.push(thread::spawn(move || {
let mut reads = 0u64;
while Instant::now() < deadline {
if let Some(sess) = s.get() {
assert_eq!(sess.refresh_token, format!("refresh-{}", sess.access_token));
}
reads += 1;
}
reads
}));
}
for i in 0..4 {
let s = Arc::clone(&store);
handles.push(thread::spawn(move || {
let mut writes = 0u64;
while Instant::now() < deadline {
s.set(make_session(&format!("tok-{i}-{writes}")));
writes += 1;
}
writes
}));
}
for _ in 0..2 {
let s = Arc::clone(&store);
handles.push(thread::spawn(move || {
let mut clears = 0u64;
while Instant::now() < deadline {
s.clear();
clears += 1;
thread::sleep(Duration::from_micros(50));
}
clears
}));
}
let total: u64 = handles.into_iter().map(|h| h.join().unwrap()).sum();
assert!(total > 0, "no operations were performed");
}
#[test]
fn store_recovers_from_poisoned_lock() {
let store: Arc<InMemorySessionStore> = Arc::new(InMemorySessionStore::new());
store.set(make_session("initial"));
let s = Arc::clone(&store);
let handle = thread::spawn(move || {
s.set(make_session("from-panicking-thread"));
panic!("intentional panic to exercise impl");
});
assert!(handle.join().is_err());
let snapshot = store.get();
assert!(snapshot.is_some(), "store became unreadable after panicking thread");
store.set(make_session("after-panic"));
assert_eq!(store.get().unwrap().access_token, "after-panic");
store.clear();
assert!(store.get().is_none());
}
#[test]
fn last_writer_wins_under_contention() {
let store: Arc<InMemorySessionStore> = Arc::new(InMemorySessionStore::new());
let mut handles = Vec::new();
for i in 0..16 {
let s = Arc::clone(&store);
handles.push(thread::spawn(move || {
for j in 0..100 {
s.set(make_session(&format!("t-{i}-{j}")));
}
}));
}
for h in handles {
h.join().unwrap();
}
let final_sess = store.get().unwrap();
assert_eq!(
final_sess.refresh_token,
format!("refresh-{}", final_sess.access_token)
);
assert!(final_sess.access_token.starts_with("t-"));
}
#[test]
fn get_returns_independent_snapshot() {
let store = InMemorySessionStore::new();
store.set(make_session("v1"));
let snap1 = store.get().unwrap();
store.set(make_session("v2"));
let snap2 = store.get().unwrap();
assert_eq!(snap1.access_token, "v1");
assert_eq!(snap2.access_token, "v2");
}
#[test]
fn readers_do_not_block_each_other() {
let store: Arc<InMemorySessionStore> = Arc::new(InMemorySessionStore::new());
store.set(make_session("v1"));
let start = Instant::now();
let mut handles = Vec::new();
for _ in 0..32 {
let s = Arc::clone(&store);
handles.push(thread::spawn(move || {
for _ in 0..1000 {
let _ = s.get();
}
}));
}
for h in handles {
h.join().unwrap();
}
let elapsed = start.elapsed();
assert!(
elapsed < Duration::from_secs(2),
"readers serialized somehow — took {elapsed:?}"
);
}