""" Unit tests for in-memory code storage. Tests code storage, verification, expiration, and cleanup. """ import time import pytest from gondulf.storage import CodeStore class TestCodeStore: """Tests for CodeStore class.""" def test_store_and_verify_success(self): """Test storing and verifying a valid code.""" store = CodeStore(ttl_seconds=60) store.store("test@example.com", "123456") assert store.verify("test@example.com", "123456") is True def test_verify_wrong_code_fails(self): """Test verification fails with wrong code.""" store = CodeStore(ttl_seconds=60) store.store("test@example.com", "123456") assert store.verify("test@example.com", "wrong") is False def test_verify_nonexistent_key_fails(self): """Test verification fails for nonexistent key.""" store = CodeStore(ttl_seconds=60) assert store.verify("nonexistent@example.com", "123456") is False def test_verify_removes_code_after_success(self): """Test that successful verification removes code (single-use).""" store = CodeStore(ttl_seconds=60) store.store("test@example.com", "123456") # First verification succeeds assert store.verify("test@example.com", "123456") is True # Second verification fails (code removed) assert store.verify("test@example.com", "123456") is False def test_verify_expired_code_fails(self): """Test verification fails for expired code.""" store = CodeStore(ttl_seconds=1) store.store("test@example.com", "123456") # Wait for expiration time.sleep(1.1) assert store.verify("test@example.com", "123456") is False def test_verify_removes_expired_code(self): """Test that expired codes are removed from storage.""" store = CodeStore(ttl_seconds=1) store.store("test@example.com", "123456") # Wait for expiration time.sleep(1.1) # Verification fails and removes code store.verify("test@example.com", "123456") # Code should be gone from storage assert store.size() == 0 def test_get_valid_code(self): """Test getting a valid code without removing it.""" store = CodeStore(ttl_seconds=60) store.store("test@example.com", "123456") assert store.get("test@example.com") == "123456" # Code should still be in storage assert store.get("test@example.com") == "123456" def test_get_nonexistent_code(self): """Test getting nonexistent code returns None.""" store = CodeStore(ttl_seconds=60) assert store.get("nonexistent@example.com") is None def test_get_expired_code(self): """Test getting expired code returns None.""" store = CodeStore(ttl_seconds=1) store.store("test@example.com", "123456") # Wait for expiration time.sleep(1.1) assert store.get("test@example.com") is None def test_delete_code(self): """Test explicitly deleting a code.""" store = CodeStore(ttl_seconds=60) store.store("test@example.com", "123456") store.delete("test@example.com") assert store.get("test@example.com") is None def test_delete_nonexistent_code(self): """Test deleting nonexistent code doesn't raise error.""" store = CodeStore(ttl_seconds=60) # Should not raise store.delete("nonexistent@example.com") def test_cleanup_expired_codes(self): """Test manual cleanup of expired codes.""" store = CodeStore(ttl_seconds=1) # Store multiple codes store.store("test1@example.com", "code1") store.store("test2@example.com", "code2") store.store("test3@example.com", "code3") assert store.size() == 3 # Wait for expiration time.sleep(1.1) # Cleanup should remove all expired codes removed = store.cleanup_expired() assert removed == 3 assert store.size() == 0 def test_cleanup_expired_partial(self): """Test cleanup removes only expired codes, not valid ones.""" store = CodeStore(ttl_seconds=2) # Store first code store.store("test1@example.com", "code1") # Wait 1 second time.sleep(1) # Store second code (will expire later) store.store("test2@example.com", "code2") # Wait for first code to expire time.sleep(1.1) # Cleanup should remove only first code removed = store.cleanup_expired() assert removed == 1 assert store.size() == 1 assert store.get("test2@example.com") == "code2" def test_size(self): """Test size() returns correct count.""" store = CodeStore(ttl_seconds=60) assert store.size() == 0 store.store("test1@example.com", "code1") assert store.size() == 1 store.store("test2@example.com", "code2") assert store.size() == 2 store.delete("test1@example.com") assert store.size() == 1 def test_clear(self): """Test clear() removes all codes.""" store = CodeStore(ttl_seconds=60) store.store("test1@example.com", "code1") store.store("test2@example.com", "code2") store.store("test3@example.com", "code3") assert store.size() == 3 store.clear() assert store.size() == 0 def test_custom_ttl(self): """Test custom TTL is respected.""" store = CodeStore(ttl_seconds=2) store.store("test@example.com", "123456") # Code valid after 1 second time.sleep(1) assert store.get("test@example.com") == "123456" # Code expired after 2+ seconds time.sleep(1.1) assert store.get("test@example.com") is None def test_multiple_keys(self): """Test storing multiple different keys.""" store = CodeStore(ttl_seconds=60) store.store("test1@example.com", "code1") store.store("test2@example.com", "code2") store.store("test3@example.com", "code3") assert store.verify("test1@example.com", "code1") is True assert store.verify("test2@example.com", "code2") is True assert store.verify("test3@example.com", "code3") is True def test_overwrite_existing_code(self): """Test storing new code with same key overwrites old code.""" store = CodeStore(ttl_seconds=60) store.store("test@example.com", "old_code") store.store("test@example.com", "new_code") assert store.verify("test@example.com", "old_code") is False assert store.verify("test@example.com", "new_code") is True def test_store_dict_value(self): """Test storing dict values for authorization code metadata.""" store = CodeStore(ttl_seconds=60) metadata = { "client_id": "https://client.example.com", "redirect_uri": "https://client.example.com/callback", "state": "xyz123", "me": "https://user.example.com", "scope": "profile", "code_challenge": "abc123", "code_challenge_method": "S256", "created_at": 1234567890, "expires_at": 1234568490, "used": False } store.store("auth_code_123", metadata) retrieved = store.get("auth_code_123") assert retrieved is not None assert isinstance(retrieved, dict) assert retrieved["client_id"] == "https://client.example.com" assert retrieved["used"] is False def test_store_dict_with_custom_ttl(self): """Test storing dict values with custom TTL.""" store = CodeStore(ttl_seconds=60) metadata = {"client_id": "https://client.example.com", "used": False} store.store("auth_code_123", metadata, ttl=120) retrieved = store.get("auth_code_123") assert retrieved is not None assert isinstance(retrieved, dict) def test_dict_value_expiration(self): """Test dict values expire correctly.""" store = CodeStore(ttl_seconds=1) metadata = {"client_id": "https://client.example.com"} store.store("auth_code_123", metadata) # Wait for expiration time.sleep(1.1) assert store.get("auth_code_123") is None def test_delete_dict_value(self): """Test deleting dict values.""" store = CodeStore(ttl_seconds=60) metadata = {"client_id": "https://client.example.com"} store.store("auth_code_123", metadata) assert store.get("auth_code_123") is not None store.delete("auth_code_123") assert store.get("auth_code_123") is None