Initial commit

This commit is contained in:
Dom
2026-03-05 01:20:14 +01:00
commit 2163e574c1
184 changed files with 354881 additions and 0 deletions

287
tests/test_encryption.py Normal file
View 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