Signup: - /signup aliniat ca format la formularul din landing (campuri, etichete, placeholder-uri, select plan, checkbox GDPR, buton). Eticheta `name` = "Companie" (corecta: backendul salveaza nume de firma), uniform si in landing. - Consimtamant GDPR validat server-side (functional, nu doar client-side) + salvat cu marca temporala (accounts.consent_at). - Plan ales la signup salvat in accounts.requested_plan (intentie, NU drept): tier ramane sursa de adevar pentru gate-ul API; coloana pregateste integrarea platilor. - landing: valorile `plan` = coduri tier (free/standard/pro/premium), data-plan sincronizat pe butoanele de pret; checkbox consimtamant primeste name. Schema/DB: - accounts: coloane noi requested_plan + consent_at (cu migrare aditiva in db.py). Panou admin: - Coloane noi: Plan curent (plan EFECTIV acum + zile trial ramase) si Plan cerut. - Buton "Aplica" (POST /admin/set-tier): aloca plan real si INCHEIE trial-ul (efect imediat; altfel trial-ul Pro universal de 30z masca alegerea). - Control "Trial Pro N zile" (POST /admin/set-trial via accounts.set_trial): acorda/prelungeste trial fara a schimba tier-ul de baza. Teste: signup (consent obligatoriu, requested_plan persistat, tier ramane free), panou admin (set-tier incheie trial, free opreste Pro imediat, set-trial, validari + CSRF). Call-site-urile existente POST /signup actualizate cu consent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
210 lines
7.3 KiB
Python
210 lines
7.3 KiB
Python
"""Teste US-001 (PRD 5.12): companie/email/CUI obligatorii la signup.
|
|
|
|
TDD strict: testele se scriu INAINTE de implementare (RED), dupa implementare trec (GREEN).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import re
|
|
import tempfile
|
|
|
|
import pytest
|
|
from starlette.testclient import TestClient
|
|
|
|
|
|
@pytest.fixture()
|
|
def client(monkeypatch):
|
|
tmp = tempfile.mkdtemp()
|
|
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "test_signup_us001.db"))
|
|
monkeypatch.setenv("AUTOPASS_SIGNUP_RATE_MAX", "100")
|
|
from app.config import get_settings
|
|
get_settings.cache_clear()
|
|
from app.web import ratelimit
|
|
ratelimit._hits.clear()
|
|
from app.main import app
|
|
with TestClient(app) as c:
|
|
yield c
|
|
get_settings.cache_clear()
|
|
|
|
|
|
def _csrf(html: str) -> str:
|
|
m = re.search(r'name="csrf_token"\s+value="([^"]+)"', html)
|
|
if not m:
|
|
m = re.search(r'value="([^"]+)"\s+name="csrf_token"', html)
|
|
assert m, "csrf_token negasit in HTML"
|
|
return m.group(1)
|
|
|
|
|
|
def test_signup_html_cui_obligatoriu_ui(client):
|
|
"""GET /signup: campul CUI NU contine '(optional)' si are atribut required (US-001 UI)."""
|
|
resp = client.get("/signup")
|
|
assert resp.status_code == 200
|
|
# (a) NU trebuie sa apara textul "(optional)" langa CUI
|
|
assert "(optional)" not in resp.text, "Campul CUI afiseaza '(optional)' — trebuie sa fie obligatoriu"
|
|
# (b) input[name=cui] trebuie sa aiba atribut required
|
|
assert 'name="cui"' in resp.text
|
|
# cautam required pe aceeasi linie cu name="cui" sau intr-un bloc care contine name="cui" required
|
|
import re
|
|
# fie pe aceeasi linie: <input ... name="cui" ... required ...>
|
|
# fie in orice forma cu required si name="cui" in acelasi tag
|
|
cui_input_match = re.search(r'<input[^>]*name="cui"[^>]*>', resp.text)
|
|
assert cui_input_match, "input name='cui' negasit in HTML"
|
|
assert "required" in cui_input_match.group(0), (
|
|
f"input[name='cui'] NU are atribut required: {cui_input_match.group(0)}"
|
|
)
|
|
|
|
|
|
def test_signup_fara_cui_422(client):
|
|
"""POST /signup fara CUI -> 422, formular re-randat cu eroare, fara cont creat."""
|
|
resp = client.get("/signup")
|
|
token = _csrf(resp.text)
|
|
|
|
resp = client.post("/signup", data={
|
|
"name": "Service Fara CUI",
|
|
"cui": "",
|
|
"email": "fara_cui@test.com",
|
|
"parola": "parolasecreta123",
|
|
"consent": "1",
|
|
"csrf_token": token,
|
|
})
|
|
# trebuie sa returneze 422 (sau sa randeze formularul cu eroare)
|
|
assert resp.status_code == 422
|
|
# cheia API nu trebuie sa apara
|
|
assert "rfak_" not in resp.text
|
|
# campul name trebuie sa fie pastrat (form re-render cu valorile existente)
|
|
assert "Service Fara CUI" in resp.text
|
|
|
|
# verifica ca nu s-a creat niciun cont
|
|
from app.db import get_connection
|
|
conn = get_connection()
|
|
try:
|
|
n = conn.execute(
|
|
"SELECT COUNT(*) AS n FROM accounts WHERE name='Service Fara CUI'"
|
|
).fetchone()["n"]
|
|
assert n == 0, "Cont creat desi CUI lipsea"
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def test_signup_scrie_email_pe_account(client):
|
|
"""POST /signup valid -> accounts.email = emailul utilizatorului."""
|
|
resp = client.get("/signup")
|
|
token = _csrf(resp.text)
|
|
|
|
resp = client.post("/signup", data={
|
|
"name": "Service Cu Email",
|
|
"cui": "RO9999001",
|
|
"email": "cu_email@test.com",
|
|
"parola": "parolasecreta123",
|
|
"consent": "1",
|
|
"csrf_token": token,
|
|
})
|
|
assert resp.status_code == 200
|
|
assert "rfak_" in resp.text
|
|
|
|
from app.db import get_connection
|
|
conn = get_connection()
|
|
try:
|
|
acct = conn.execute(
|
|
"SELECT * FROM accounts WHERE name='Service Cu Email'"
|
|
).fetchone()
|
|
assert acct is not None
|
|
# emailul trebuie scris pe cont (normalizat: lower + trim)
|
|
assert acct["email"] == "cu_email@test.com"
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def test_signup_email_duplicat_mesaj_email(client):
|
|
"""POST /signup cu email existent dar CUI nou -> mesaj despre EMAIL, NU despre CUI/firma.
|
|
|
|
Bug: 'email deja folosit' contine 'deja folosit' -> era prins de conditia CUI duplicat
|
|
si afisa gresit 'Aceasta firma (CUI X) e deja inregistrata' (CUI nou, NU cauza reala).
|
|
Fix: verifica intai email-ul, apoi CUI-ul.
|
|
"""
|
|
from tests.conftest import make_test_cui
|
|
|
|
# primul signup cu email E + CUI C1
|
|
resp = client.get("/signup")
|
|
token = _csrf(resp.text)
|
|
resp1 = client.post("/signup", data={
|
|
"name": "Firma Prima SRL",
|
|
"cui": make_test_cui("email-dup-c1"),
|
|
"email": "emaildup@test.com",
|
|
"parola": "parolasecreta123",
|
|
"consent": "1",
|
|
"csrf_token": token,
|
|
})
|
|
assert resp1.status_code == 200
|
|
assert "rfak_" in resp1.text, "Primul signup trebuia sa reuseasca"
|
|
|
|
# al doilea signup cu ACELASI email dar CUI NOU
|
|
resp = client.get("/signup")
|
|
token2 = _csrf(resp.text)
|
|
cui_nou = make_test_cui("email-dup-c2")
|
|
resp2 = client.post("/signup", data={
|
|
"name": "Firma A Doua SRL",
|
|
"cui": cui_nou,
|
|
"email": "emaildup@test.com",
|
|
"parola": "parolasecreta456",
|
|
"consent": "1",
|
|
"csrf_token": token2,
|
|
})
|
|
|
|
assert resp2.status_code in (200, 422)
|
|
assert "rfak_" not in resp2.text, "Nu trebuia creata cheie API la email duplicat"
|
|
|
|
body_lower = resp2.text.lower()
|
|
# mesajul trebuie sa se refere la EMAIL
|
|
assert "email" in body_lower, (
|
|
f"Mesajul de eroare nu mentioneaza 'email': {resp2.text[:500]}"
|
|
)
|
|
# mesajul NU trebuie sa afiseze pattern-ul gresit cu firma si CUI-ul nou
|
|
# (CUI-ul apare legitim si in campul pre-completat al formularului, dar nu in mesajul de eroare)
|
|
wrong_pattern = f"(cui {cui_nou.lower()}) e deja inregistrata"
|
|
assert wrong_pattern not in body_lower, (
|
|
f"Mesajul arata gresit pattern-ul CUI-duplicat desi problema e emailul: {resp2.text[:500]}"
|
|
)
|
|
# nu trebuie sa apara mesajul specific CUI-duplicat
|
|
assert "e deja inregistrata" not in body_lower, (
|
|
f"Mesajul arata 'e deja inregistrata' (mesaj CUI) la eroare de email: {resp2.text[:500]}"
|
|
)
|
|
|
|
|
|
def test_signup_cui_existent_mesaj_prietenos(client):
|
|
"""POST /signup cu CUI existent -> mesaj prietenos, NU mesaj tehnic cu 'activate --account'."""
|
|
resp = client.get("/signup")
|
|
token = _csrf(resp.text)
|
|
|
|
# primul signup
|
|
client.post("/signup", data={
|
|
"name": "Firma Existenta SRL",
|
|
"cui": "RO8888001",
|
|
"email": "firma1@test.com",
|
|
"parola": "parolasecreta123",
|
|
"consent": "1",
|
|
"csrf_token": token,
|
|
})
|
|
|
|
# al doilea signup cu acelasi CUI
|
|
resp = client.get("/signup")
|
|
token2 = _csrf(resp.text)
|
|
resp2 = client.post("/signup", data={
|
|
"name": "Alt Utilizator SRL",
|
|
"cui": "RO8888001",
|
|
"email": "firma2@test.com",
|
|
"parola": "parolasecreta456",
|
|
"consent": "1",
|
|
"csrf_token": token2,
|
|
})
|
|
|
|
assert resp2.status_code in (200, 422)
|
|
# NU trebuie sa apara mesajul tehnic cu referinta la CLI
|
|
assert "activate --account" not in resp2.text
|
|
# trebuie sa apara un mesaj prietenos cu CUI-ul
|
|
assert "RO8888001" in resp2.text
|
|
# trebuie sa contina cuvant cheie de tip "firma" sau "inregistrata"
|
|
body_lower = resp2.text.lower()
|
|
assert any(kw in body_lower for kw in ["firma", "inregistrat", "cont", "acces"])
|