# Sicheres E2E-Testen der Login-/Lockout-Logik (ohne echten User zu zerstören) Der Bonuspitfall: Tests der Login-/Brute-Force-Logik dürfen den **echten Benutzeraccount** (`mehlzoerwer`) nicht verändern, sonst ist der echte Login danach defekt (Skill-Pitfall "E2E-Test mutiert die DB"). ## Muster (validiert 2026-08-04) 1. **IP-Lockout-Zweig testen** → mit einem **fremden/leeren Benutzernamen** laufen lassen. Fremder User trifft nur den In-Memory-`_IP_FAIL`-Zähler, KEIN DB-Update auf den echten Account: ```bash for i in $(seq 1 6); do curl -sk -X POST https://ssh..de/ \ -d "username=foo&password=wrong$i" done tail portal.log # Versuch 6 → "IP-SPERRE aktiv für " ``` Prüfen, dass der echte User danach `failed=0, lockout_until=0` ist. 2. **DB-User-Lockout-Zweig testen** → **temporären Test-User** in die DB, nach dem Test wieder löschen: ```bash ./venv/bin/python -c " import sqlite3 from werkzeug.security import generate_password_hash c=sqlite3.connect('portal.db') c.execute(\"INSERT OR REPLACE INTO users(username,pw_hash,pw_changed,failed,lockout_until)\ VALUES('testuser',?,1,0,0)\", (generate_password_hash('TestPass123!'),)) c.commit(); c.close()" ``` Dann 5x falsches PW für `testuser` → DB zeigt `failed=5, lockout_until=`, Log zeigt "USER-SPERRE aktiv". 3. **WICHTIG — In-Memory-IP-Sperre leert nur beim Service-Restart**: Nach Test 1 ist die Test-IP noch 15 min in `_IP_FAIL` gesperrt. Vor dem Testen des zweiten Zweigs `systemctl restart sshportal` (leert den In-Memory-Zähler), sonst kommen die neuen Requests gar nicht bis zur DB. 4. **Aufräumen** (immer, in einer Transaktion committen): ```bash c.execute('DELETE FROM users WHERE username=?', ('testuser',)) c.execute('UPDATE users SET failed=0, lockout_until=0 WHERE username=?', ('mehlzoerwer',)) c.commit() ``` ## Fallstricke - `sqlite3`-CLI fehlt oft auf dem VPS → über das venv-Python lesen (`./venv/bin/python -c "..."`), nicht `sqlite3 portal.db`. - DB-Schema kann Alt-Spalten haben → `dict(row)` schlägt beim Print fehl ("length 11; 2 is required"); explizite Spalten im SELECT wählen. - Desktop-`dict(rows)`-Print ist nur Reporting, nicht Teil des Deletes — ein Traceback dort heißt NICHT, dass der Delete fehlgeschlagen ist.