Curatare globala a comentariilor si docstring-urilor (app, tools, teste, scripturi): eliminate referintele la PRD-uri, US-xxx, task-uri istorice si review-uri; pastrata doar informatia functionala, formulata scurt. Regula adaugata in CLAUDE.md (sectiunea Stil). Fara modificari de cod sau comportament. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
151 lines
5.4 KiB
Python
151 lines
5.4 KiB
Python
"""Teste analytics device-mix — validare premisa mobil, fara PII.
|
|
|
|
Semnal: la acces dashboard -> eveniment 'device_mix' in app_events cu cod 'desktop'/'mobil'.
|
|
Zero PII: nu se stocheaza UA brut, IP sau VIN.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import tempfile
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# Fixture #
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
@pytest.fixture()
|
|
def client(monkeypatch):
|
|
tmp = tempfile.mkdtemp()
|
|
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "device_mix.db"))
|
|
monkeypatch.setenv("AUTOPASS_LOG_DIR", os.path.join(tmp, "logs"))
|
|
monkeypatch.setenv("AUTOPASS_WEB_AUTH_REQUIRED", "false")
|
|
from app.config import get_settings
|
|
get_settings.cache_clear()
|
|
from app.main import app
|
|
with TestClient(app) as c:
|
|
yield c
|
|
get_settings.cache_clear()
|
|
|
|
|
|
def _events_device_mix():
|
|
from app.db import get_connection
|
|
conn = get_connection()
|
|
try:
|
|
return conn.execute(
|
|
"SELECT * FROM app_events WHERE tip='device_mix' ORDER BY id"
|
|
).fetchall()
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# test_device_mix_inregistrat #
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
UA_DESKTOP = (
|
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
|
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
|
"Chrome/125.0 Safari/537.36"
|
|
)
|
|
UA_MOBIL_ANDROID = (
|
|
"Mozilla/5.0 (Linux; Android 13; Pixel 7) "
|
|
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
|
"Chrome/125.0 Mobile Safari/537.36"
|
|
)
|
|
UA_MOBIL_IPHONE = (
|
|
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) "
|
|
"AppleWebKit/605.1.15 (KHTML, like Gecko) "
|
|
"Version/17.0 Mobile/15E148 Safari/604.1"
|
|
)
|
|
|
|
|
|
def test_device_mix_inregistrat_desktop(client):
|
|
"""Acces dashboard cu UA desktop -> eveniment device_mix cod='desktop'."""
|
|
r = client.get("/", headers={"User-Agent": UA_DESKTOP})
|
|
assert r.status_code == 200
|
|
|
|
events = _events_device_mix()
|
|
assert len(events) >= 1, "Trebuie cel putin un eveniment device_mix dupa acces dashboard"
|
|
# ultimul eveniment clasificat ca desktop
|
|
ev = events[-1]
|
|
assert ev["tip"] == "device_mix"
|
|
assert ev["cod"] == "desktop", f"Clasificare gresita: {ev['cod']!r}"
|
|
|
|
|
|
def test_device_mix_inregistrat_mobil_android(client):
|
|
"""Acces dashboard cu UA Android Mobile -> eveniment device_mix cod='mobil'."""
|
|
r = client.get("/", headers={"User-Agent": UA_MOBIL_ANDROID})
|
|
assert r.status_code == 200
|
|
|
|
events = _events_device_mix()
|
|
assert len(events) >= 1
|
|
ev = events[-1]
|
|
assert ev["tip"] == "device_mix"
|
|
assert ev["cod"] == "mobil", f"Clasificare gresita Android: {ev['cod']!r}"
|
|
|
|
|
|
def test_device_mix_inregistrat_mobil_iphone(client):
|
|
"""Acces dashboard cu UA iPhone -> eveniment device_mix cod='mobil'."""
|
|
r = client.get("/", headers={"User-Agent": UA_MOBIL_IPHONE})
|
|
assert r.status_code == 200
|
|
|
|
events = _events_device_mix()
|
|
assert len(events) >= 1
|
|
ev = events[-1]
|
|
assert ev["tip"] == "device_mix"
|
|
assert ev["cod"] == "mobil", f"Clasificare gresita iPhone: {ev['cod']!r}"
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# test_device_mix_fara_pii #
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
def test_device_mix_fara_pii(client):
|
|
"""Evenimentul device_mix nu contine UA brut, IP sau alte PII."""
|
|
r = client.get("/", headers={"User-Agent": UA_MOBIL_ANDROID})
|
|
assert r.status_code == 200
|
|
|
|
events = _events_device_mix()
|
|
assert len(events) >= 1
|
|
ev = events[-1]
|
|
|
|
# Campul mesaj: doar eticheta grosiera, nu UA brut
|
|
mesaj = ev["mesaj"] or ""
|
|
assert UA_MOBIL_ANDROID not in mesaj, "UA brut nu trebuie stocat in mesaj"
|
|
assert "Android" not in mesaj, "Fragment UA nu trebuie stocat in mesaj"
|
|
assert "Mozilla" not in mesaj, "Fragment UA nu trebuie stocat in mesaj"
|
|
|
|
# context_json: daca exista, nu contine UA brut / IP
|
|
ctx_raw = ev["context_json"]
|
|
if ctx_raw:
|
|
ctx = json.loads(ctx_raw)
|
|
ctx_str = json.dumps(ctx)
|
|
assert UA_MOBIL_ANDROID not in ctx_str, "UA brut nu trebuie in context_json"
|
|
assert "Mozilla" not in ctx_str, "Fragment UA nu trebuie in context_json"
|
|
# IP-uri tipice nu apar (testclient trimite 127.0.0.1/testclient)
|
|
for ip_fragment in ["127.0.0.1", "testclient", "192.168."]:
|
|
assert ip_fragment not in ctx_str, f"IP {ip_fragment!r} nu trebuie in context_json"
|
|
|
|
# codul este doar eticheta grosiera
|
|
assert ev["cod"] in ("desktop", "mobil"), f"Cod neasteptat: {ev['cod']!r}"
|
|
|
|
|
|
def test_device_mix_fara_pii_desktop(client):
|
|
"""Evenimentul device_mix pentru desktop nu contine UA brut."""
|
|
r = client.get("/", headers={"User-Agent": UA_DESKTOP})
|
|
assert r.status_code == 200
|
|
|
|
events = _events_device_mix()
|
|
assert len(events) >= 1
|
|
ev = events[-1]
|
|
|
|
mesaj = ev["mesaj"] or ""
|
|
assert UA_DESKTOP not in mesaj, "UA brut desktop nu trebuie in mesaj"
|
|
assert "Windows NT" not in mesaj, "Fragment UA nu trebuie in mesaj"
|
|
assert ev["cod"] == "desktop"
|