File size: 10,327 Bytes
4e96b7a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 | """
Test per sistema anonimizzazione.
"""
import sys
import os
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'src')))
import pytest
from unittest.mock import Mock, patch
from anonymizer import NERAnonimizer
class TestNERAnonimizer:
"""Test classe NERAnonimizer"""
def test_init(self):
"""Test inizializzazione"""
anonymizer = NERAnonimizer()
assert anonymizer.regex_patterns is not None
assert anonymizer._ner_pipe is None
@patch('anonymizer.pipeline')
def test_ner_pipe_lazy_loading(self, mock_pipeline, mock_streamlit):
"""Test lazy loading del modello NER"""
anonymizer = NERAnonimizer()
# Prima chiamata - dovrebbe caricare il modello
pipe = anonymizer.ner_pipe
assert mock_pipeline.called
# Seconda chiamata - dovrebbe usare cache
mock_pipeline.reset_mock()
pipe2 = anonymizer.ner_pipe
assert not mock_pipeline.called
assert pipe == pipe2
def test_mask_with_regex_basic(self, sample_text):
"""Test mascheramento regex base"""
anonymizer = NERAnonimizer()
masked_text, entities = anonymizer.mask_with_regex(sample_text)
# Verifica che abbia trovato entità
assert len(entities) > 0
# Verifica che le entità siano nel formato corretto
for placeholder, original in entities.items():
assert placeholder.startswith('[')
assert placeholder.endswith(']')
assert '_' in placeholder
assert original in sample_text
assert placeholder in masked_text
def test_mask_with_regex_iban(self):
"""Test mascheramento IBAN specifico"""
anonymizer = NERAnonimizer()
text = "Il mio IBAN è IT60 X054 2811 1010 0000 0123 456 per i pagamenti"
masked_text, entities = anonymizer.mask_with_regex(text)
# Dovrebbe trovare l'IBAN
iban_entities = [k for k in entities.keys() if k.startswith('[IBAN_')]
assert len(iban_entities) == 1
iban_placeholder = iban_entities[0]
assert entities[iban_placeholder] == "IT60 X054 2811 1010 0000 0123 456"
assert iban_placeholder in masked_text
def test_mask_with_regex_email(self):
"""Test mascheramento email"""
anonymizer = NERAnonimizer()
text = "Contattami su mario.rossi@example.com o test@domain.co.uk"
masked_text, entities = anonymizer.mask_with_regex(text)
# Dovrebbe trovare 2 email
email_entities = [k for k in entities.keys() if k.startswith('[EMAIL_')]
assert len(email_entities) == 2
email_values = [entities[k] for k in email_entities]
assert "mario.rossi@example.com" in email_values
assert "test@domain.co.uk" in email_values
def test_mask_with_regex_cf(self):
"""Test mascheramento codice fiscale"""
anonymizer = NERAnonimizer()
text = "Il codice fiscale è RSSMRA80A01H501Z"
masked_text, entities = anonymizer.mask_with_regex(text)
cf_entities = [k for k in entities.keys() if k.startswith('[CF_')]
assert len(cf_entities) == 1
assert entities[cf_entities[0]] == "RSSMRA80A01H501Z"
def test_mask_with_regex_empty_text(self, sample_empty_text):
"""Test con testo vuoto"""
anonymizer = NERAnonimizer()
masked_text, entities = anonymizer.mask_with_regex(sample_empty_text)
assert masked_text == sample_empty_text
assert len(entities) == 0
def test_mask_with_regex_no_entities(self, sample_text_no_entities):
"""Test con testo senza entità"""
anonymizer = NERAnonimizer()
masked_text, entities = anonymizer.mask_with_regex(sample_text_no_entities)
assert masked_text == sample_text_no_entities
assert len(entities) == 0
def test_mask_with_ner_success(self, mock_ner_pipeline, mock_streamlit):
"""Test mascheramento NER con successo"""
anonymizer = NERAnonimizer()
anonymizer._ner_pipe = mock_ner_pipeline
text = "Mario Rossi lavora in ACME SpA"
masked_text, entities = anonymizer.mask_with_ner(text)
# Verifica chiamata al modello
assert mock_ner_pipeline.called
# Verifica entità trovate
assert len(entities) == 2
per_entities = [k for k in entities.keys() if k.startswith('[PER_')]
org_entities = [k for k in entities.keys() if k.startswith('[ORG_')]
assert len(per_entities) == 1
assert len(org_entities) == 1
def test_mask_with_ner_no_model(self, mock_streamlit):
"""Test NER senza modello caricato"""
anonymizer = NERAnonimizer()
anonymizer._ner_pipe = None
text = "Mario Rossi lavora in ACME SpA"
masked_text, entities = anonymizer.mask_with_ner(text)
# Dovrebbe ritornare testo invariato
assert masked_text == text
assert len(entities) == 0
def test_mask_with_ner_low_confidence(self, mock_streamlit):
"""Test NER con confidence bassa"""
anonymizer = NERAnonimizer()
# Mock con score basso
mock_pipe = Mock()
mock_pipe.return_value = [
{
'entity_group': 'PER',
'score': 0.3, # Sotto threshold (0.5)
'start': 0,
'end': 11,
'word': 'Mario Rossi'
}
]
anonymizer._ner_pipe = mock_pipe
text = "Mario Rossi"
masked_text, entities = anonymizer.mask_with_ner(text)
# Non dovrebbe mascherare con confidence bassa
assert masked_text == text
assert len(entities) == 0
def test_anonymize_complete_pipeline(self, sample_text, mock_ner_pipeline, mock_streamlit):
"""Test pipeline completa di anonimizzazione"""
anonymizer = NERAnonimizer()
anonymizer._ner_pipe = mock_ner_pipeline
anonymized_text, all_entities = anonymizer.anonymize(sample_text)
# Verifica che sia diverso dall'originale
assert anonymized_text != sample_text
# Verifica che contenga placeholder
assert '[' in anonymized_text and ']' in anonymized_text
# Verifica che abbia trovato entità da entrambi i sistemi
assert len(all_entities) > 0
# Verifica mix di entità regex e NER
regex_entities = [k for k in all_entities.keys()
if any(k.startswith(f'[{t}_') for t in ['IBAN', 'EMAIL', 'CF', 'CARD', 'PHONE'])]
ner_entities = [k for k in all_entities.keys()
if any(k.startswith(f'[{t}_') for t in ['PER', 'ORG'])]
assert len(regex_entities) > 0 # Dovrebbe trovare entità regex
assert len(ner_entities) > 0 # Dovrebbe trovare entità NER
def test_anonymize_empty_text(self, sample_empty_text):
"""Test anonimizzazione testo vuoto"""
anonymizer = NERAnonimizer()
anonymized_text, entities = anonymizer.anonymize(sample_empty_text)
assert anonymized_text == sample_empty_text
assert len(entities) == 0
def test_anonymize_preserves_structure(self, mock_streamlit):
"""Test che l'anonimizzazione preservi la struttura del testo"""
anonymizer = NERAnonimizer()
text = """Documento importante
Dati cliente:
- Nome: Mario Rossi
- Email: mario@test.com
Fine documento."""
anonymized_text, entities = anonymizer.anonymize(text)
# Dovrebbe preservare newline e struttura
assert '\n' in anonymized_text
assert 'Documento importante' in anonymized_text
assert 'Fine documento.' in anonymized_text
def test_placeholder_uniqueness(self, sample_text, mock_ner_pipeline, mock_streamlit):
"""Test che i placeholder siano unici"""
anonymizer = NERAnonimizer()
anonymizer._ner_pipe = mock_ner_pipeline
anonymized_text, entities = anonymizer.anonymize(sample_text)
# Tutti i placeholder dovrebbero essere unici
placeholders = list(entities.keys())
assert len(placeholders) == len(set(placeholders))
# Ogni placeholder dovrebbe apparire nel testo
for placeholder in placeholders:
assert placeholder in anonymized_text
class TestAnonymizerEdgeCases:
"""Test casi limite"""
def test_already_masked_text(self, mock_streamlit):
"""Test testo già parzialmente mascherato"""
anonymizer = NERAnonimizer()
text = "Contatta [EMAIL_0] per info su [CF_0]"
masked_text, entities = anonymizer.mask_with_regex(text)
# Non dovrebbe ri-mascherare placeholder esistenti
assert masked_text == text
assert len(entities) == 0
def test_overlapping_patterns(self, mock_streamlit):
"""Test pattern che si sovrappongono"""
anonymizer = NERAnonimizer()
# Testo con potenziali sovrapposizioni
text = "Email test@domain.com nel sito https://test@domain.com"
masked_text, entities = anonymizer.mask_with_regex(text)
# Dovrebbe gestire correttamente le sovrapposizioni
assert len(entities) >= 1
assert all(placeholder in masked_text for placeholder in entities.keys())
def test_special_characters(self, mock_streamlit):
"""Test caratteri speciali"""
anonymizer = NERAnonimizer()
text = "Email: test@domain.com; IBAN: IT60X05428111010000001234567!"
masked_text, entities = anonymizer.mask_with_regex(text)
# Dovrebbe trovare entità anche con caratteri speciali intorno
email_found = any('EMAIL' in k for k in entities.keys())
iban_found = any('IBAN' in k for k in entities.keys())
assert email_found
assert iban_found |