""" Tests pour le module de chiffrement. Exigences : 17.4 """ import base64 import json import pytest from cryptography.fernet import InvalidToken from pipeline_mco_pmsi.security.encryption import AuditEncryptor, EncryptionKey class TestEncryptionKey: """Tests pour EncryptionKey.""" def test_generate_key(self): """Test génération de clé.""" key = EncryptionKey.generate() assert key.key is not None assert len(key.key) > 0 def test_generate_unique_keys(self): """Test que chaque génération produit une clé unique.""" key1 = EncryptionKey.generate() key2 = EncryptionKey.generate() assert key1.key != key2.key def test_from_string(self): """Test création de clé depuis string.""" key = EncryptionKey.generate() key_string = key.to_string() # Recréer depuis string key2 = EncryptionKey.from_string(key_string) assert key2.key == key.key def test_to_string(self): """Test conversion de clé en string.""" key = EncryptionKey.generate() key_string = key.to_string() assert isinstance(key_string, str) assert len(key_string) > 0 def test_roundtrip_string_conversion(self): """Test conversion aller-retour string.""" key = EncryptionKey.generate() key_string = key.to_string() key2 = EncryptionKey.from_string(key_string) assert key.key == key2.key class TestAuditEncryptor: """Tests pour AuditEncryptor.""" @pytest.fixture def encryption_key(self): """Fixture pour clé de chiffrement.""" return EncryptionKey.generate() @pytest.fixture def encryptor(self, encryption_key): """Fixture pour encryptor.""" return AuditEncryptor(encryption_key) @pytest.fixture def sample_audit_data(self): """Fixture pour données d'audit de test.""" return { "stay_id": "STAY001", "codes": [ {"code": "I10", "label": "Hypertension essentielle", "type": "dp"}, {"code": "E11.9", "label": "Diabète de type 2", "type": "das"}, ], "timestamp": "2026-02-11T10:30:00", "user": "tim_user123", } def test_encrypt_audit_data(self, encryptor, sample_audit_data): """Test chiffrement de données d'audit.""" encrypted = encryptor.encrypt_audit_data(sample_audit_data) assert isinstance(encrypted, bytes) assert len(encrypted) > 0 # Les données chiffrées ne doivent pas contenir le texte original assert b"STAY001" not in encrypted assert b"I10" not in encrypted def test_decrypt_audit_data(self, encryptor, sample_audit_data): """Test déchiffrement de données d'audit.""" encrypted = encryptor.encrypt_audit_data(sample_audit_data) decrypted = encryptor.decrypt_audit_data(encrypted) assert decrypted == sample_audit_data def test_roundtrip_encryption(self, encryptor, sample_audit_data): """Test chiffrement/déchiffrement aller-retour.""" encrypted = encryptor.encrypt_audit_data(sample_audit_data) decrypted = encryptor.decrypt_audit_data(encrypted) assert decrypted == sample_audit_data assert decrypted["stay_id"] == "STAY001" assert len(decrypted["codes"]) == 2 def test_encrypt_to_base64(self, encryptor, sample_audit_data): """Test chiffrement vers base64.""" encrypted_b64 = encryptor.encrypt_to_base64(sample_audit_data) assert isinstance(encrypted_b64, str) assert len(encrypted_b64) > 0 # Vérifier que c'est du base64 valide base64.b64decode(encrypted_b64) def test_decrypt_from_base64(self, encryptor, sample_audit_data): """Test déchiffrement depuis base64.""" encrypted_b64 = encryptor.encrypt_to_base64(sample_audit_data) decrypted = encryptor.decrypt_from_base64(encrypted_b64) assert decrypted == sample_audit_data def test_roundtrip_base64(self, encryptor, sample_audit_data): """Test chiffrement/déchiffrement base64 aller-retour.""" encrypted_b64 = encryptor.encrypt_to_base64(sample_audit_data) decrypted = encryptor.decrypt_from_base64(encrypted_b64) assert decrypted == sample_audit_data def test_different_keys_cannot_decrypt(self, sample_audit_data): """Test qu'une clé différente ne peut pas déchiffrer.""" key1 = EncryptionKey.generate() key2 = EncryptionKey.generate() encryptor1 = AuditEncryptor(key1) encryptor2 = AuditEncryptor(key2) encrypted = encryptor1.encrypt_audit_data(sample_audit_data) # Tentative de déchiffrement avec une clé différente with pytest.raises(InvalidToken): encryptor2.decrypt_audit_data(encrypted) def test_tampered_data_fails_decryption(self, encryptor, sample_audit_data): """Test que des données altérées échouent au déchiffrement.""" encrypted = encryptor.encrypt_audit_data(sample_audit_data) # Altérer les données tampered = encrypted[:-1] + b"X" with pytest.raises(InvalidToken): encryptor.decrypt_audit_data(tampered) def test_encrypt_empty_data(self, encryptor): """Test chiffrement de données vides.""" empty_data = {} encrypted = encryptor.encrypt_audit_data(empty_data) decrypted = encryptor.decrypt_audit_data(encrypted) assert decrypted == empty_data def test_encrypt_complex_nested_data(self, encryptor): """Test chiffrement de données complexes imbriquées.""" complex_data = { "stay_id": "STAY001", "metadata": { "admission_date": "2026-01-15", "discharge_date": "2026-01-20", "specialty": "Cardiologie", }, "codes": [ { "code": "I10", "evidence": [ {"document_id": "DOC001", "text": "HTA connue"}, {"document_id": "DOC002", "text": "Traitement antihypertenseur"}, ], } ], "versions": { "cim10": {"version": "2026", "hash": "abc123"}, "ccam": {"version": "V81", "hash": "def456"}, }, } encrypted = encryptor.encrypt_audit_data(complex_data) decrypted = encryptor.decrypt_audit_data(encrypted) assert decrypted == complex_data assert decrypted["metadata"]["specialty"] == "Cardiologie" assert len(decrypted["codes"][0]["evidence"]) == 2 def test_encrypt_unicode_data(self, encryptor): """Test chiffrement de données avec caractères Unicode.""" unicode_data = { "stay_id": "STAY001", "diagnosis": "Œdème pulmonaire aigu", "notes": "Patient présentant dyspnée sévère", "symbols": "→ ≥ ≤ ± °", } encrypted = encryptor.encrypt_audit_data(unicode_data) decrypted = encryptor.decrypt_audit_data(encrypted) assert decrypted == unicode_data assert decrypted["diagnosis"] == "Œdème pulmonaire aigu" def test_encrypt_large_data(self, encryptor): """Test chiffrement de grandes quantités de données.""" large_data = { "stay_id": "STAY001", "documents": [ { "document_id": f"DOC{i:03d}", "content": "Lorem ipsum dolor sit amet " * 100, } for i in range(50) ], } encrypted = encryptor.encrypt_audit_data(large_data) decrypted = encryptor.decrypt_audit_data(encrypted) assert decrypted == large_data assert len(decrypted["documents"]) == 50 def test_encrypted_data_is_different_each_time(self, encryptor, sample_audit_data): """Test que le chiffrement produit des résultats différents à chaque fois.""" # Fernet utilise un IV aléatoire, donc même données = chiffrement différent encrypted1 = encryptor.encrypt_audit_data(sample_audit_data) encrypted2 = encryptor.encrypt_audit_data(sample_audit_data) # Les données chiffrées doivent être différentes assert encrypted1 != encrypted2 # Mais les deux doivent déchiffrer vers les mêmes données decrypted1 = encryptor.decrypt_audit_data(encrypted1) decrypted2 = encryptor.decrypt_audit_data(encrypted2) assert decrypted1 == decrypted2 == sample_audit_data class TestEncryptionIntegration: """Tests d'intégration pour le chiffrement.""" def test_key_persistence_and_reuse(self): """Test persistance et réutilisation de clé.""" # Générer une clé key = EncryptionKey.generate() key_string = key.to_string() # Simuler sauvegarde/chargement de clé # (dans un vrai système, on sauvegarderait dans un fichier sécurisé) # Chiffrer avec la clé originale encryptor1 = AuditEncryptor(key) data = {"test": "data"} encrypted = encryptor1.encrypt_audit_data(data) # Charger la clé depuis string et déchiffrer loaded_key = EncryptionKey.from_string(key_string) encryptor2 = AuditEncryptor(loaded_key) decrypted = encryptor2.decrypt_audit_data(encrypted) assert decrypted == data def test_multiple_encryptions_with_same_key(self): """Test multiples chiffrements avec la même clé.""" key = EncryptionKey.generate() encryptor = AuditEncryptor(key) data_list = [ {"stay_id": "STAY001", "code": "I10"}, {"stay_id": "STAY002", "code": "E11.9"}, {"stay_id": "STAY003", "code": "J18.9"}, ] # Chiffrer toutes les données encrypted_list = [encryptor.encrypt_audit_data(data) for data in data_list] # Déchiffrer toutes les données decrypted_list = [ encryptor.decrypt_audit_data(enc) for enc in encrypted_list ] assert decrypted_list == data_list