mirror of
https://github.com/telemt/telemt.git
synced 2026-10-31 12:52:19 +03:00
112 lines
5.0 KiB
Rust
112 lines
5.0 KiB
Rust
use super::*;
|
|
use std::sync::atomic::Ordering;
|
|
|
|
async fn fixture() -> (tempfile::TempDir, Arc<QuotaStateOwner>) {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let owner = QuotaStateOwner::new(dir.path().join("quota.json"), Arc::new(QuotaStore::default()));
|
|
let mut state = QuotaStateFile::default();
|
|
state.last_reset_epoch_secs = 123;
|
|
state.users.insert("alice".into(), quota_user_state(UserQuotaSnapshot {
|
|
used_bytes: 700,
|
|
last_reset_epoch_secs: 123,
|
|
}));
|
|
state.users.insert("bob".into(), QuotaUserState {
|
|
used_bytes: 321,
|
|
last_reset_epoch_secs: 111,
|
|
schedule: QuotaScheduleCheckpoint {
|
|
quota_period: Some(crate::config::UserQuotaPeriod::Monthly),
|
|
period_cursor_epoch_secs: 1_793_491_200,
|
|
},
|
|
});
|
|
write_state_file(owner.path(), state).await.unwrap();
|
|
(dir, owner)
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn creation_removes_only_retired_row_and_returns_owned_gate() {
|
|
let (_dir, owner) = fixture().await;
|
|
let before = read_state_file(owner.path()).await.unwrap().unwrap();
|
|
let guard = owner.lock().await;
|
|
let guard = owner.prepare_user_creation_locked("alice", guard).await.unwrap();
|
|
assert!(owner.mutation.try_lock().is_err());
|
|
let after = read_state_file(owner.path()).await.unwrap().unwrap();
|
|
assert!(!after.users.contains_key("alice"));
|
|
assert_eq!(serde_json::to_value(&after.users["bob"]).unwrap(),
|
|
serde_json::to_value(&before.users["bob"]).unwrap());
|
|
assert_eq!(after.last_reset_epoch_secs, before.last_reset_epoch_secs);
|
|
drop(guard);
|
|
assert!(owner.mutation.try_lock().is_ok());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn creation_without_checkpoint_does_not_require_a_write() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let path = dir.path().join("missing/quota.json");
|
|
let owner = QuotaStateOwner::new(path.clone(), Arc::new(QuotaStore::default()));
|
|
let guard = owner.lock().await;
|
|
let _guard = owner.prepare_user_creation_locked("alice", guard).await.unwrap();
|
|
assert!(!path.exists());
|
|
assert!(owner.mutation.try_lock().is_err());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn creation_retries_durability_after_ambiguous_removal() {
|
|
let (_dir, owner) = fixture().await;
|
|
let (hook, _) = test_io::Hook::install(owner.path(), false);
|
|
hook.fail_after.store(true, Ordering::Relaxed);
|
|
let guard = owner.lock().await;
|
|
assert!(owner.prepare_user_creation_locked("alice", guard).await.is_err());
|
|
assert!(!read_state_file(owner.path()).await.unwrap().unwrap().users.contains_key("alice"));
|
|
// The absent row does not prove that the previous directory sync succeeded.
|
|
let guard = owner.lock().await;
|
|
assert!(owner.prepare_user_creation_locked("alice", guard).await.is_err());
|
|
assert_eq!(hook.writes.load(Ordering::Relaxed), 2);
|
|
hook.fail_after.store(false, Ordering::Relaxed);
|
|
let guard = owner.lock().await;
|
|
let _guard = owner.prepare_user_creation_locked("alice", guard).await.unwrap();
|
|
assert_eq!(hook.writes.load(Ordering::Relaxed), 3);
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn creation_waiter_abort_keeps_gate_until_durable_removal_finishes() {
|
|
let (_dir, owner) = fixture().await;
|
|
let (hook, started) = test_io::Hook::install(owner.path(), true);
|
|
let operation = owner.clone();
|
|
let waiter = tokio::spawn(async move {
|
|
let guard = operation.lock().await;
|
|
operation.prepare_user_creation_locked("alice", guard).await
|
|
});
|
|
started.await.unwrap();
|
|
waiter.abort();
|
|
assert!(waiter.await.unwrap_err().is_cancelled());
|
|
assert!(owner.mutation.try_lock().is_err());
|
|
assert!(read_state_file(owner.path()).await.unwrap().unwrap().users.contains_key("alice"));
|
|
hook.release();
|
|
let _guard = tokio::time::timeout(std::time::Duration::from_secs(2), owner.lock()).await.unwrap();
|
|
assert!(!read_state_file(owner.path()).await.unwrap().unwrap().users.contains_key("alice"));
|
|
assert_eq!(hook.writes.load(Ordering::Relaxed), 1);
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn creation_write_failure_keeps_retired_checkpoint_for_safe_retry() {
|
|
let (_dir, owner) = fixture().await;
|
|
let retired = owner.path().with_extension("retired");
|
|
let (hook, started) = test_io::Hook::install(owner.path(), true);
|
|
let operation = owner.clone();
|
|
let writer = tokio::spawn(async move {
|
|
let guard = operation.lock().await;
|
|
operation.prepare_user_creation_locked("alice", guard).await
|
|
});
|
|
started.await.unwrap();
|
|
std::fs::rename(owner.path(), &retired).unwrap();
|
|
std::fs::create_dir(owner.path()).unwrap();
|
|
hook.release();
|
|
assert!(writer.await.unwrap().is_err());
|
|
std::fs::remove_dir(owner.path()).unwrap();
|
|
std::fs::rename(retired, owner.path()).unwrap();
|
|
assert!(read_state_file(owner.path()).await.unwrap().unwrap().users.contains_key("alice"));
|
|
let guard = owner.lock().await;
|
|
let _guard = owner.prepare_user_creation_locked("alice", guard).await.unwrap();
|
|
assert!(!read_state_file(owner.path()).await.unwrap().unwrap().users.contains_key("alice"));
|
|
}
|