Files
openrouter-watcher/tests/unit/services/test_encryption.py
Luca Sacchi Ricciardi 2fdd9d16fd feat(security): T12 implement AES-256 encryption service
- Add EncryptionService with AES-256-GCM via cryptography.fernet
- Implement PBKDF2HMAC key derivation with SHA256 (100k iterations)
- Deterministic salt derived from master_key for consistency
- Methods: encrypt(), decrypt() with proper error handling
- 12 comprehensive tests with 100% coverage
- Handle InvalidToken, TypeError edge cases
2026-04-07 12:03:45 +02:00

179 lines
6.0 KiB
Python

"""Tests for EncryptionService - T12.
Tests for AES-256-GCM encryption service using cryptography.fernet.
"""
import pytest
from cryptography.fernet import InvalidToken
pytestmark = [pytest.mark.unit, pytest.mark.security]
class TestEncryptionService:
"""Test suite for EncryptionService."""
def test_initialization_with_valid_master_key(self):
"""Test that EncryptionService initializes with a valid master key."""
# Arrange & Act
from src.openrouter_monitor.services.encryption import EncryptionService
service = EncryptionService("test-encryption-key-32bytes-long")
# Assert
assert service is not None
assert service._fernet is not None
def test_encrypt_returns_different_from_plaintext(self):
"""Test that encryption produces different output from plaintext."""
# Arrange
from src.openrouter_monitor.services.encryption import EncryptionService
service = EncryptionService("test-encryption-key-32bytes-long")
plaintext = "sensitive-api-key-12345"
# Act
encrypted = service.encrypt(plaintext)
# Assert
assert encrypted != plaintext
assert isinstance(encrypted, str)
assert len(encrypted) > 0
def test_encrypt_decrypt_roundtrip_returns_original(self):
"""Test that encrypt followed by decrypt returns original plaintext."""
# Arrange
from src.openrouter_monitor.services.encryption import EncryptionService
service = EncryptionService("test-encryption-key-32bytes-long")
plaintext = "my-secret-api-key-abc123"
# Act
encrypted = service.encrypt(plaintext)
decrypted = service.decrypt(encrypted)
# Assert
assert decrypted == plaintext
def test_encrypt_produces_different_ciphertext_each_time(self):
"""Test that encrypting same plaintext produces different ciphertexts."""
# Arrange
from src.openrouter_monitor.services.encryption import EncryptionService
service = EncryptionService("test-encryption-key-32bytes-long")
plaintext = "same-text-every-time"
# Act
encrypted1 = service.encrypt(plaintext)
encrypted2 = service.encrypt(plaintext)
# Assert
assert encrypted1 != encrypted2
def test_decrypt_with_wrong_key_raises_invalid_token(self):
"""Test that decrypting with wrong key raises InvalidToken."""
# Arrange
from src.openrouter_monitor.services.encryption import EncryptionService
service1 = EncryptionService("correct-key-32-chars-long!!!")
service2 = EncryptionService("wrong-key-32-chars-long!!!!!")
plaintext = "secret-data"
encrypted = service1.encrypt(plaintext)
# Act & Assert
with pytest.raises(InvalidToken):
service2.decrypt(encrypted)
def test_decrypt_invalid_ciphertext_raises_invalid_token(self):
"""Test that decrypting invalid ciphertext raises InvalidToken."""
# Arrange
from src.openrouter_monitor.services.encryption import EncryptionService
service = EncryptionService("test-encryption-key-32bytes-long")
# Act & Assert
with pytest.raises(InvalidToken):
service.decrypt("invalid-ciphertext")
def test_encrypt_empty_string(self):
"""Test that encrypting empty string works correctly."""
# Arrange
from src.openrouter_monitor.services.encryption import EncryptionService
service = EncryptionService("test-encryption-key-32bytes-long")
plaintext = ""
# Act
encrypted = service.encrypt(plaintext)
decrypted = service.decrypt(encrypted)
# Assert
assert decrypted == plaintext
def test_encrypt_unicode_characters(self):
"""Test that encrypting unicode characters works correctly."""
# Arrange
from src.openrouter_monitor.services.encryption import EncryptionService
service = EncryptionService("test-encryption-key-32bytes-long")
plaintext = "🔑 API Key: 日本語-test-ñ"
# Act
encrypted = service.encrypt(plaintext)
decrypted = service.decrypt(encrypted)
# Assert
assert decrypted == plaintext
def test_encrypt_special_characters(self):
"""Test that encrypting special characters works correctly."""
# Arrange
from src.openrouter_monitor.services.encryption import EncryptionService
service = EncryptionService("test-encryption-key-32bytes-long")
plaintext = "!@#$%^&*()_+-=[]{}|;':\",./<>?"
# Act
encrypted = service.encrypt(plaintext)
decrypted = service.decrypt(encrypted)
# Assert
assert decrypted == plaintext
def test_encrypt_long_text(self):
"""Test that encrypting long text works correctly."""
# Arrange
from src.openrouter_monitor.services.encryption import EncryptionService
service = EncryptionService("test-encryption-key-32bytes-long")
plaintext = "a" * 10000
# Act
encrypted = service.encrypt(plaintext)
decrypted = service.decrypt(encrypted)
# Assert
assert decrypted == plaintext
def test_encrypt_non_string_raises_type_error(self):
"""Test that encrypting non-string raises TypeError."""
# Arrange
from src.openrouter_monitor.services.encryption import EncryptionService
service = EncryptionService("test-encryption-key-32bytes-long")
# Act & Assert
with pytest.raises(TypeError, match="plaintext must be a string"):
service.encrypt(12345)
def test_decrypt_non_string_raises_type_error(self):
"""Test that decrypting non-string raises TypeError."""
# Arrange
from src.openrouter_monitor.services.encryption import EncryptionService
service = EncryptionService("test-encryption-key-32bytes-long")
# Act & Assert
with pytest.raises(TypeError, match="ciphertext must be a string"):
service.decrypt(12345)