Feature Testanforderungen: Anonymisierung
Version: 1.0 | Stand: 30. Dezember 2025
Übersicht
Dieses Dokument beschreibt alle Tests für das LLARS Offline Anonymisierungs-Tool.
Komponente: /Anonymize Frontend, Flask API, Flair NER Model
Permission: feature:anonymize:view
1. API Endpoints
Health Check
Endpoint: GET /api/anonymize/health
| ID |
Test |
Erwartung |
Art |
| ANON-H01 |
Health Check |
200, status: ok |
Integration |
| ANON-H02 |
Model geladen |
model_loaded: true |
Integration |
| ANON-H03 |
Model nicht geladen |
model_loaded: false |
Integration |
Pseudonymize Text
Endpoint: POST /api/anonymize/pseudonymize
| ID |
Test |
Erwartung |
Art |
| ANON-T01 |
Text mit Namen |
Namen ersetzt |
Integration |
| ANON-T02 |
Text ohne Entitäten |
Unverändert |
Integration |
| ANON-T03 |
Leerer Text |
400 Bad Request |
Integration |
| ANON-T04 |
Sehr langer Text |
Verarbeitet |
Integration |
| ANON-T05 |
Mehrere Entitäten |
Alle ersetzt |
Integration |
| ANON-T06 |
Konsistente Ersetzung |
Gleicher Name = gleicher Ersatz |
Integration |
Pseudonymize File
Endpoint: POST /api/anonymize/pseudonymize-file
| ID |
Test |
Erwartung |
Art |
| ANON-F01 |
TXT Upload |
Pseudonymisierte Datei |
Integration |
| ANON-F02 |
MD Upload |
Pseudonymisierte Datei |
Integration |
| ANON-F03 |
PDF Upload |
Nicht unterstützt / Error |
Integration |
| ANON-F04 |
Große Datei |
Verarbeitet |
Integration |
| ANON-F05 |
Leere Datei |
400 Bad Request |
Integration |
2. NER Entity Types
| Entity |
Beschreibung |
Ersetzung |
| PER |
Personennamen |
[PERSON_1], [PERSON_2], ... |
| LOC |
Orte |
[ORT_1], [ORT_2], ... |
| ORG |
Organisationen |
[ORG_1], [ORG_2], ... |
| MISC |
Sonstiges |
[MISC_1], [MISC_2], ... |
Entity Detection Tests
| ID |
Test |
Erwartung |
Art |
| ANON-E01 |
Deutsche Namen |
Erkannt und ersetzt |
Integration |
| ANON-E02 |
Ausländische Namen |
Erkannt und ersetzt |
Integration |
| ANON-E03 |
Städtenamen |
Erkannt (LOC) |
Integration |
| ANON-E04 |
Firmen |
Erkannt (ORG) |
Integration |
| ANON-E05 |
Titel + Name |
Titel + Pseudonym |
Integration |
| ANON-E06 |
Abkürzungen |
Teilweise erkannt |
Integration |
3. Recommender System
Komponente: SQLite-basierte Pseudonym-Datenbank
| ID |
Test |
Erwartung |
Art |
| ANON-R01 |
Pseudonym-Auswahl |
Plausibles Pseudonym |
Unit |
| ANON-R02 |
Geschlecht-Matching |
Männlich → männlich |
Unit |
| ANON-R03 |
Konsistenz |
Gleicher Input = gleicher Output |
Unit |
| ANON-R04 |
Keine Wiederverwendung |
Jedes Pseudonym einzigartig |
Unit |
4. Frontend Tests
Komponente: Anonymize.vue
| ID |
Test |
Erwartung |
Art |
| ANON-UI01 |
Seite lädt |
Editor sichtbar |
E2E |
| ANON-UI02 |
Text eingeben |
Input funktioniert |
E2E |
| ANON-UI03 |
Pseudonymisieren klicken |
Button vorhanden |
E2E |
| ANON-UI04 |
Ergebnis angezeigt |
Output sichtbar |
E2E |
| ANON-UI05 |
Highlighting |
Entities farbig |
E2E |
| ANON-UI06 |
Copy Button |
Text kopierbar |
E2E |
| ANON-UI07 |
Reset Button |
Eingabe zurücksetzen |
E2E |
| ANON-UI08 |
File Upload |
Drag & Drop |
E2E |
Permission Tests
| ID |
Test |
Erwartung |
Art |
| ANON-P01 |
Admin Zugriff |
Erlaubt |
E2E |
| ANON-P02 |
Researcher Zugriff |
Erlaubt |
E2E |
| ANON-P03 |
Evaluator Zugriff |
Erlaubt |
E2E |
| ANON-P04 |
Chatbot_Manager |
Nicht erlaubt |
E2E |
5. Test-Code
# tests/integration/anonymize/test_anonymize.py
import pytest
class TestAnonymizeAPI:
"""Anonymize API Tests"""
def test_ANON_H01_health_check(self, client):
"""Health Check funktioniert"""
response = client.get('/api/anonymize/health')
assert response.status_code == 200
assert response.json['status'] == 'ok'
def test_ANON_T01_text_with_names(self, authenticated_client):
"""Text mit Namen wird pseudonymisiert"""
response = authenticated_client.post('/api/anonymize/pseudonymize', json={
'text': 'Max Mustermann arbeitet bei der Firma XYZ in Berlin.'
})
assert response.status_code == 200
result = response.json['result']
# Namen sollten ersetzt sein
assert 'Max Mustermann' not in result
assert '[PERSON' in result or 'PERSON_' in result
def test_ANON_T02_text_without_entities(self, authenticated_client):
"""Text ohne Entitäten bleibt unverändert"""
original = 'Dies ist ein einfacher Text ohne Namen.'
response = authenticated_client.post('/api/anonymize/pseudonymize', json={
'text': original
})
assert response.status_code == 200
# Text sollte weitgehend unverändert sein
# (Kleinere Änderungen durch Tokenization möglich)
def test_ANON_T06_consistent_replacement(self, authenticated_client):
"""Gleicher Name wird konsistent ersetzt"""
response = authenticated_client.post('/api/anonymize/pseudonymize', json={
'text': 'Max sagte zu Max, dass Max kommt. Max freut sich.'
})
assert response.status_code == 200
result = response.json['result']
# Alle "Max" sollten durch denselben Ersatz ersetzt werden
# Zähle wie viele unterschiedliche Ersetzungen es gibt
import re
replacements = re.findall(r'\[PERSON_\d+\]', result)
if replacements:
assert len(set(replacements)) == 1 # Alle gleich
def test_ANON_E01_german_names(self, authenticated_client):
"""Deutsche Namen werden erkannt"""
response = authenticated_client.post('/api/anonymize/pseudonymize', json={
'text': 'Dr. Hans-Peter Müller und Frau Sabine Schmidt trafen sich.'
})
assert response.status_code == 200
result = response.json['result']
assert 'Hans-Peter Müller' not in result
assert 'Sabine Schmidt' not in result
def test_ANON_E03_city_names(self, authenticated_client):
"""Städtenamen werden erkannt"""
response = authenticated_client.post('/api/anonymize/pseudonymize', json={
'text': 'Er wohnt in München und arbeitet in Frankfurt.'
})
assert response.status_code == 200
result = response.json['result']
# Städte sollten als LOC erkannt werden
assert 'München' not in result or '[ORT' in result
def test_ANON_F01_file_upload(self, authenticated_client, test_text_file):
"""TXT File Upload funktioniert"""
with open(test_text_file, 'rb') as f:
response = authenticated_client.post(
'/api/anonymize/pseudonymize-file',
data={'file': (f, 'test.txt')},
content_type='multipart/form-data'
)
assert response.status_code == 200
assert 'result' in response.json
class TestAnonymizeRecommender:
"""Recommender System Tests"""
def test_ANON_R01_pseudonym_selection(self, recommender):
"""Pseudonyme sind plausibel"""
pseudonym = recommender.get_pseudonym('Max', gender='male')
assert pseudonym is not None
assert len(pseudonym) > 0
def test_ANON_R02_gender_matching(self, recommender):
"""Geschlecht wird berücksichtigt"""
male_pseudonym = recommender.get_pseudonym('Hans', gender='male')
female_pseudonym = recommender.get_pseudonym('Maria', gender='female')
# Pseudonyme sollten unterschiedlich sein
assert male_pseudonym != female_pseudonym
def test_ANON_R04_no_reuse(self, recommender):
"""Pseudonyme werden nicht wiederverwendet"""
pseudonyms = []
for i in range(10):
p = recommender.get_pseudonym(f'Name{i}', gender='male')
pseudonyms.append(p)
# Alle sollten unterschiedlich sein
assert len(set(pseudonyms)) == len(pseudonyms)
6. E2E Test-Code
// e2e/anonymize/anonymize.spec.ts
import { test, expect } from '../fixtures/auth'
test.describe('Anonymize Tool', () => {
test('ANON-UI01 to UI04: basic anonymization flow', async ({ authenticatedPage }) => {
await authenticatedPage.goto('/Anonymize')
// UI lädt
await expect(authenticatedPage.locator('.anonymize-editor')).toBeVisible()
// Text eingeben
const input = authenticatedPage.locator('textarea.input-text')
await input.fill('Max Mustermann aus Berlin arbeitet bei der Firma GmbH.')
// Pseudonymisieren
await authenticatedPage.click('button:has-text("Pseudonymisieren")')
// Ergebnis prüfen
await expect(authenticatedPage.locator('.result-text')).toBeVisible({
timeout: 30000
})
const result = await authenticatedPage.locator('.result-text').textContent()
expect(result).not.toContain('Max Mustermann')
})
test('ANON-UI05: entity highlighting', async ({ authenticatedPage }) => {
await authenticatedPage.goto('/Anonymize')
await authenticatedPage.fill('textarea.input-text', 'Max Mustermann lebt in Berlin.')
await authenticatedPage.click('button:has-text("Pseudonymisieren")')
// Entities sollten farbig hervorgehoben sein
await expect(authenticatedPage.locator('.entity-highlight')).toBeVisible({
timeout: 30000
})
})
test('ANON-P04: chatbot_manager no access', async ({ chatbotManagerPage }) => {
await chatbotManagerPage.goto('/Anonymize')
// Sollte Zugriff verweigern oder redirect
await expect(chatbotManagerPage).not.toHaveURL('/Anonymize')
})
})
7. Fixtures
# tests/conftest.py
import pytest
from pathlib import Path
@pytest.fixture
def test_text_file(tmp_path):
"""Temporäre Textdatei für Upload-Tests"""
file_path = tmp_path / "test.txt"
file_path.write_text("Max Mustermann und Maria Schmidt trafen sich in Berlin.")
return file_path
@pytest.fixture
def recommender(app):
"""Recommender System Instance"""
from app.services.anonymize.recommender import PseudonymRecommender
return PseudonymRecommender()
8. Model-Konfiguration
Flair NER Model
| Einstellung |
Wert |
| Model |
ner-german-large |
| Pfad |
app/models/anonymize/ner-german-large |
| Sprache |
Deutsch |
| GPU |
Optional (CUDA) |
SQLite Datenbank
| Einstellung |
Wert |
| Pfad |
app/data/anonymize/pseudonyms.db |
| Tabellen |
male_names, female_names, surnames |
| Input |
Output |
| Max Mustermann |
[PERSON_1] |
| Berlin |
[ORT_1] |
| Deutsche Bank AG |
[ORG_1] |
| Dr. Hans Müller |
Dr. [PERSON_2] |
| Er wohnt in München. |
Er wohnt in [ORT_2]. |
10. Checkliste für manuelle Tests
API
Entity Detection
Frontend
Permissions
11. Bekannte Einschränkungen
| Einschränkung |
Beschreibung |
| Nur Deutsch |
Model ist auf Deutsch trainiert |
| Kontextabhängig |
Manchmal falsche Klassifizierung |
| Keine PDFs |
Nur Textdateien unterstützt |
| Offline |
Keine Cloud-API |
Letzte Aktualisierung: 30. Dezember 2025