From d0f32d04f76ac9fbfaa9f3b366cd8a874dc80e5c Mon Sep 17 00:00:00 2001 From: Felipe Cardoso Date: Mon, 5 Jan 2026 18:29:02 +0100 Subject: [PATCH] fix(tests): reduce TTL durations to improve test reliability - Adjusted TTL durations and sleep intervals across memory and cache tests for consistent expiration behavior. - Prevented test flakiness caused by timing discrepancies in token expiration and cache cleanup. --- backend/app/models/memory/fact.py | 5 ++++- backend/tests/api/test_auth_password_reset.py | 11 ++++++----- .../services/memory/cache/test_embedding_cache.py | 8 ++++---- .../unit/services/memory/cache/test_hot_cache.py | 8 ++++---- .../unit/services/memory/working/test_storage.py | 12 ++++++------ 5 files changed, 24 insertions(+), 20 deletions(-) diff --git a/backend/app/models/memory/fact.py b/backend/app/models/memory/fact.py index c7875a7..d8620e7 100644 --- a/backend/app/models/memory/fact.py +++ b/backend/app/models/memory/fact.py @@ -18,7 +18,10 @@ from sqlalchemy import ( Text, text, ) -from sqlalchemy.dialects.postgresql import JSONB, UUID as PGUUID +from sqlalchemy.dialects.postgresql import ( + JSONB, + UUID as PGUUID, +) from sqlalchemy.orm import relationship from app.models.base import Base, TimestampMixin, UUIDMixin diff --git a/backend/tests/api/test_auth_password_reset.py b/backend/tests/api/test_auth_password_reset.py index 108dbe8..19ad169 100755 --- a/backend/tests/api/test_auth_password_reset.py +++ b/backend/tests/api/test_auth_password_reset.py @@ -188,13 +188,14 @@ class TestPasswordResetConfirm: @pytest.mark.asyncio async def test_password_reset_confirm_expired_token(self, client, async_test_user): """Test password reset confirmation with expired token.""" - import time as time_module + import asyncio - # Create token that expires immediately - token = create_password_reset_token(async_test_user.email, expires_in=1) + # Create token that expires at current second (expires_in=0) + # Token expires when exp < current_time, so we need to cross a second boundary + token = create_password_reset_token(async_test_user.email, expires_in=0) - # Wait for token to expire - time_module.sleep(2) + # Wait for token to expire (need to cross second boundary) + await asyncio.sleep(1.1) response = await client.post( "/api/v1/auth/password-reset/confirm", diff --git a/backend/tests/unit/services/memory/cache/test_embedding_cache.py b/backend/tests/unit/services/memory/cache/test_embedding_cache.py index 74229d0..0afd0e1 100644 --- a/backend/tests/unit/services/memory/cache/test_embedding_cache.py +++ b/backend/tests/unit/services/memory/cache/test_embedding_cache.py @@ -160,11 +160,11 @@ class TestEmbeddingCache: async def test_ttl_expiration(self) -> None: """Should expire entries after TTL.""" - cache = EmbeddingCache(max_size=100, default_ttl_seconds=0.1) + cache = EmbeddingCache(max_size=100, default_ttl_seconds=0.05) await cache.put("content", [0.1, 0.2]) - time.sleep(0.2) + time.sleep(0.06) result = await cache.get("content") @@ -226,13 +226,13 @@ class TestEmbeddingCache: def test_cleanup_expired(self) -> None: """Should remove expired entries.""" - cache = EmbeddingCache(max_size=100, default_ttl_seconds=0.1) + cache = EmbeddingCache(max_size=100, default_ttl_seconds=0.05) # Use synchronous put for setup cache._put_memory("hash1", "default", [0.1]) cache._put_memory("hash2", "default", [0.2], ttl_seconds=10) - time.sleep(0.2) + time.sleep(0.06) count = cache.cleanup_expired() diff --git a/backend/tests/unit/services/memory/cache/test_hot_cache.py b/backend/tests/unit/services/memory/cache/test_hot_cache.py index 5a59211..dcb14d3 100644 --- a/backend/tests/unit/services/memory/cache/test_hot_cache.py +++ b/backend/tests/unit/services/memory/cache/test_hot_cache.py @@ -212,12 +212,12 @@ class TestHotMemoryCache: def test_ttl_expiration(self) -> None: """Should expire entries after TTL.""" - cache = HotMemoryCache[str](max_size=100, default_ttl_seconds=0.1) + cache = HotMemoryCache[str](max_size=100, default_ttl_seconds=0.05) cache.put_by_id("test", "1", "value") # Wait for expiration - time.sleep(0.2) + time.sleep(0.06) result = cache.get_by_id("test", "1") @@ -289,12 +289,12 @@ class TestHotMemoryCache: def test_cleanup_expired(self) -> None: """Should remove expired entries.""" - cache = HotMemoryCache[str](max_size=100, default_ttl_seconds=0.1) + cache = HotMemoryCache[str](max_size=100, default_ttl_seconds=0.05) cache.put_by_id("test", "1", "value1") cache.put_by_id("test", "2", "value2", ttl_seconds=10) - time.sleep(0.2) + time.sleep(0.06) count = cache.cleanup_expired() diff --git a/backend/tests/unit/services/memory/working/test_storage.py b/backend/tests/unit/services/memory/working/test_storage.py index 3fa9f70..f75799e 100644 --- a/backend/tests/unit/services/memory/working/test_storage.py +++ b/backend/tests/unit/services/memory/working/test_storage.py @@ -78,13 +78,13 @@ class TestInMemoryStorageTTL: @pytest.mark.asyncio async def test_ttl_expiration(self, storage: InMemoryStorage) -> None: """Test that expired keys return None.""" - await storage.set("key1", "value1", ttl_seconds=1) + await storage.set("key1", "value1", ttl_seconds=0.1) # Key exists initially assert await storage.get("key1") == "value1" # Wait for expiration - await asyncio.sleep(1.1) + await asyncio.sleep(0.15) # Key should be expired assert await storage.get("key1") is None @@ -93,10 +93,10 @@ class TestInMemoryStorageTTL: @pytest.mark.asyncio async def test_remove_ttl_on_update(self, storage: InMemoryStorage) -> None: """Test that updating without TTL removes expiration.""" - await storage.set("key1", "value1", ttl_seconds=1) + await storage.set("key1", "value1", ttl_seconds=0.1) await storage.set("key1", "value2") # No TTL - await asyncio.sleep(1.1) + await asyncio.sleep(0.15) # Key should still exist (TTL removed) assert await storage.get("key1") == "value2" @@ -180,10 +180,10 @@ class TestInMemoryStorageCapacity: """Test that expired keys are cleaned up for capacity.""" storage = InMemoryStorage(max_keys=2) - await storage.set("key1", "value1", ttl_seconds=1) + await storage.set("key1", "value1", ttl_seconds=0.1) await storage.set("key2", "value2") - await asyncio.sleep(1.1) + await asyncio.sleep(0.15) # Should succeed because key1 is expired and will be cleaned await storage.set("key3", "value3")