Initial commit
This commit is contained in:
287
tests/test_encryption.py
Normal file
287
tests/test_encryption.py
Normal file
@@ -0,0 +1,287 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user