From 867a7a5cbd190d2441277bc80624545608d888a9 Mon Sep 17 00:00:00 2001 From: Alex Gorevski Date: Thu, 19 Feb 2026 13:28:24 -0800 Subject: [PATCH] test(gateway): add edge-case idempotency store tests Add five new idempotency store tests covering: different-key acceptance, max_keys clamping to minimum of 1, rapid duplicate rejection, TTL-based key expiry and re-acceptance, and eviction preserving newest entries. Addresses audit finding on weak gateway idempotency test coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/config/schema.rs | 6 ++++-- src/gateway/mod.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/config/schema.rs b/src/config/schema.rs index 96b2f0c..d7b5af2 100644 --- a/src/config/schema.rs +++ b/src/config/schema.rs @@ -3373,14 +3373,16 @@ async fn sync_directory(path: &Path) -> Result<()> { } #[cfg(not(unix))] -fn sync_directory(_path: &Path) -> Result<()> { +async fn sync_directory(_path: &Path) -> Result<()> { Ok(()) } #[cfg(test)] mod tests { use super::*; - use std::{fs::Permissions, os::unix::fs::PermissionsExt, path::PathBuf}; + #[cfg(unix)] + use std::{fs::Permissions, os::unix::fs::PermissionsExt}; + use std::path::PathBuf; use tokio::sync::{Mutex, MutexGuard}; use tokio::test; use tokio_stream::wrappers::ReadDirStream; diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index b42b4e1..a7f6777 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -2089,4 +2089,52 @@ mod tests { &signature_header )); } + + // ══════════════════════════════════════════════════════════ + // IdempotencyStore Edge-Case Tests + // ══════════════════════════════════════════════════════════ + + #[test] + fn idempotency_store_allows_different_keys() { + let store = IdempotencyStore::new(Duration::from_secs(60), 100); + assert!(store.record_if_new("key-a")); + assert!(store.record_if_new("key-b")); + assert!(store.record_if_new("key-c")); + assert!(store.record_if_new("key-d")); + } + + #[test] + fn idempotency_store_max_keys_clamped_to_one() { + let store = IdempotencyStore::new(Duration::from_secs(60), 0); + assert!(store.record_if_new("only-key")); + assert!(!store.record_if_new("only-key")); + } + + #[test] + fn idempotency_store_rapid_duplicate_rejected() { + let store = IdempotencyStore::new(Duration::from_secs(300), 100); + assert!(store.record_if_new("rapid")); + assert!(!store.record_if_new("rapid")); + } + + #[test] + fn idempotency_store_accepts_after_ttl_expires() { + let store = IdempotencyStore::new(Duration::from_millis(1), 100); + assert!(store.record_if_new("ttl-key")); + std::thread::sleep(Duration::from_millis(10)); + assert!(store.record_if_new("ttl-key")); + } + + #[test] + fn idempotency_store_eviction_preserves_newest() { + let store = IdempotencyStore::new(Duration::from_secs(300), 1); + assert!(store.record_if_new("old-key")); + std::thread::sleep(Duration::from_millis(2)); + assert!(store.record_if_new("new-key")); + + let keys = store.keys.lock(); + assert_eq!(keys.len(), 1); + assert!(!keys.contains_key("old-key")); + assert!(keys.contains_key("new-key")); + } }