feat(signup+admin): aliniere formular signup la landing + plan cerut, GDPR, control tier/trial in panou
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>
This commit is contained in:
@@ -40,7 +40,8 @@ def _signup(client, name, email, password="parola_test_001"):
|
||||
tok = _csrf(client, "/signup")
|
||||
resp = client.post("/signup", data={"name": name, "cui": make_test_cui(email),
|
||||
"email": email, "parola": password,
|
||||
"csrf_token": tok}, follow_redirects=True)
|
||||
"consent": "1", "csrf_token": tok},
|
||||
follow_redirects=True)
|
||||
assert resp.status_code == 200
|
||||
from app.db import get_connection
|
||||
conn = get_connection()
|
||||
|
||||
@@ -62,6 +62,7 @@ def _signup(client: TestClient, name: str, email: str, password: str = "parola_t
|
||||
"cui": make_test_cui(email),
|
||||
"email": email,
|
||||
"parola": password,
|
||||
"consent": "1",
|
||||
"csrf_token": token,
|
||||
}, follow_redirects=True)
|
||||
assert resp.status_code == 200, f"signup esuat: {resp.text[:300]}"
|
||||
@@ -261,3 +262,158 @@ def test_activare_cont_incomplet_refuzata(client):
|
||||
assert not _get_account_active(incomplete_id), (
|
||||
"Contul incomplet (fara email/CUI) a fost activat — gate pe account_is_complete nu functioneaza"
|
||||
)
|
||||
|
||||
|
||||
def _get_tier_trial(account_id: int) -> tuple[str, str | None]:
|
||||
"""Citeste (tier, trial_until) din DB."""
|
||||
from app.db import get_connection
|
||||
conn = get_connection()
|
||||
try:
|
||||
row = conn.execute(
|
||||
"SELECT tier, trial_until FROM accounts WHERE id=?", (account_id,)
|
||||
).fetchone()
|
||||
return (row["tier"], row["trial_until"]) if row else ("", None)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def _get_tier(account_id: int) -> str:
|
||||
"""Citeste accounts.tier din DB."""
|
||||
return _get_tier_trial(account_id)[0]
|
||||
|
||||
|
||||
def test_set_tier_din_admin_incheie_trial(client):
|
||||
"""POST /admin/set-tier -> tier actualizat, trial_until=NULL (trial incheiat), 303.
|
||||
|
||||
Contul nou are trial Pro 30z; alocarea manuala trebuie sa-l incheie ca alegerea
|
||||
sa aiba efect imediat (decizie user 2026-06-29)."""
|
||||
target_id = _signup(client, "Firma Upgrade SRL", "upgrade@test.ro")
|
||||
tier0, trial0 = _get_tier_trial(target_id)
|
||||
assert tier0 == "free", "cont nou trebuie sa porneasca pe free"
|
||||
assert trial0, "cont nou trebuie sa aiba trial_until setat (trial Pro 30z)"
|
||||
|
||||
admin_id = _signup(client, "Admin Tier SA", "admintier@test.ro")
|
||||
_make_admin(admin_id)
|
||||
_login(client, "admintier@test.ro")
|
||||
|
||||
csrf = _get_csrf(client, "/admin")
|
||||
resp = client.post("/admin/set-tier", data={
|
||||
"account_id": str(target_id),
|
||||
"tier": "pro",
|
||||
"csrf_token": csrf,
|
||||
})
|
||||
assert resp.status_code == 303, f"asteptat 303 PRG, primit {resp.status_code}"
|
||||
tier1, trial1 = _get_tier_trial(target_id)
|
||||
assert tier1 == "pro", "tier-ul nu a fost mutat pe pro"
|
||||
assert trial1 is None, "trial_until trebuie sters la alocarea manuala (efect imediat)"
|
||||
|
||||
|
||||
def test_set_tier_free_opreste_pro_imediat(client):
|
||||
"""Setarea pe 'free' pe un cont in trial -> efectiv 'free' acum (trial incheiat).
|
||||
|
||||
Fara stergerea trial-ului, effective_tier ar fi ramas 'pro' inca ~30 zile."""
|
||||
from datetime import datetime, timezone
|
||||
from app.plans import effective_tier
|
||||
|
||||
target_id = _signup(client, "Firma Abuz Trial SRL", "abuztrial@test.ro")
|
||||
admin_id = _signup(client, "Admin Stop SA", "adminstop@test.ro")
|
||||
_make_admin(admin_id)
|
||||
_login(client, "adminstop@test.ro")
|
||||
|
||||
csrf = _get_csrf(client, "/admin")
|
||||
resp = client.post("/admin/set-tier", data={
|
||||
"account_id": str(target_id),
|
||||
"tier": "free",
|
||||
"csrf_token": csrf,
|
||||
})
|
||||
assert resp.status_code == 303
|
||||
|
||||
tier1, trial1 = _get_tier_trial(target_id)
|
||||
assert tier1 == "free" and trial1 is None
|
||||
eff = effective_tier({"tier": tier1, "trial_until": trial1}, datetime.now(timezone.utc))
|
||||
assert eff == "free", "dupa setarea pe free, planul efectiv trebuie sa fie free imediat"
|
||||
|
||||
|
||||
def test_set_tier_invalid_respins(client):
|
||||
"""Tier invalid -> nu schimba nimic (re-randare cu eroare sau ignorat)."""
|
||||
target_id = _signup(client, "Firma Tier Invalid SRL", "tierinvalid@test.ro")
|
||||
admin_id = _signup(client, "Admin TI SA", "adminti@test.ro")
|
||||
_make_admin(admin_id)
|
||||
_login(client, "adminti@test.ro")
|
||||
|
||||
csrf = _get_csrf(client, "/admin")
|
||||
resp = client.post("/admin/set-tier", data={
|
||||
"account_id": str(target_id),
|
||||
"tier": "platinum", # invalid
|
||||
"csrf_token": csrf,
|
||||
})
|
||||
assert resp.status_code in (200, 422), f"tier invalid ar trebui respins, primit {resp.status_code}"
|
||||
assert _get_tier(target_id) == "free", "tier invalid nu trebuie aplicat"
|
||||
|
||||
|
||||
def test_set_tier_fara_csrf_respins(client):
|
||||
"""POST /admin/set-tier fara token CSRF valid -> respins, tier neschimbat."""
|
||||
target_id = _signup(client, "Firma CSRF Tier SRL", "csrftier@test.ro")
|
||||
admin_id = _signup(client, "Admin CSRF SA", "admincsrf@test.ro")
|
||||
_make_admin(admin_id)
|
||||
_login(client, "admincsrf@test.ro")
|
||||
|
||||
resp = client.post("/admin/set-tier", data={
|
||||
"account_id": str(target_id),
|
||||
"tier": "pro",
|
||||
"csrf_token": "token-fals",
|
||||
})
|
||||
assert resp.status_code in (400, 403), f"CSRF invalid trebuie respins, primit {resp.status_code}"
|
||||
assert _get_tier(target_id) == "free", "tier schimbat desi CSRF era invalid"
|
||||
|
||||
|
||||
def test_set_trial_din_admin(client):
|
||||
"""POST /admin/set-trial -> trial_until setat, tier de baza neschimbat, efectiv pro, 303."""
|
||||
from datetime import datetime, timezone
|
||||
from app.plans import effective_tier
|
||||
|
||||
target_id = _signup(client, "Firma Trial SRL", "trialnou@test.ro")
|
||||
# incheie intai orice trial (set-tier free) ca sa pornim de la baza curata
|
||||
admin_id = _signup(client, "Admin Trial SA", "admintrialacord@test.ro")
|
||||
_make_admin(admin_id)
|
||||
_login(client, "admintrialacord@test.ro")
|
||||
csrf = _get_csrf(client, "/admin")
|
||||
client.post("/admin/set-tier", data={
|
||||
"account_id": str(target_id), "tier": "free", "csrf_token": csrf,
|
||||
})
|
||||
assert _get_tier_trial(target_id) == ("free", None)
|
||||
|
||||
# acorda trial Pro 15 zile
|
||||
csrf = _get_csrf(client, "/admin")
|
||||
resp = client.post("/admin/set-trial", data={
|
||||
"account_id": str(target_id),
|
||||
"trial_days": "15",
|
||||
"csrf_token": csrf,
|
||||
})
|
||||
assert resp.status_code == 303, f"asteptat 303 PRG, primit {resp.status_code}"
|
||||
tier1, trial1 = _get_tier_trial(target_id)
|
||||
assert tier1 == "free", "tier-ul de baza NU trebuie schimbat de acordarea de trial"
|
||||
assert trial1, "trial_until trebuie setat"
|
||||
eff = effective_tier({"tier": tier1, "trial_until": trial1}, datetime.now(timezone.utc))
|
||||
assert eff == "pro", "trial activ trebuie sa ridice planul efectiv la pro"
|
||||
|
||||
|
||||
def test_set_trial_zile_invalide_respins(client):
|
||||
"""trial_days <= 0 -> 422, trial neschimbat."""
|
||||
target_id = _signup(client, "Firma Trial Invalid SRL", "trialinvalid@test.ro")
|
||||
admin_id = _signup(client, "Admin TInv SA", "admintinv@test.ro")
|
||||
_make_admin(admin_id)
|
||||
_login(client, "admintinv@test.ro")
|
||||
# porneste de la trial sters
|
||||
csrf = _get_csrf(client, "/admin")
|
||||
client.post("/admin/set-tier", data={
|
||||
"account_id": str(target_id), "tier": "free", "csrf_token": csrf,
|
||||
})
|
||||
csrf = _get_csrf(client, "/admin")
|
||||
resp = client.post("/admin/set-trial", data={
|
||||
"account_id": str(target_id),
|
||||
"trial_days": "0",
|
||||
"csrf_token": csrf,
|
||||
})
|
||||
assert resp.status_code == 422
|
||||
assert _get_tier_trial(target_id) == ("free", None), "trial nu trebuie setat la zile invalide"
|
||||
|
||||
@@ -65,6 +65,7 @@ def test_signup_fara_cui_422(client):
|
||||
"cui": "",
|
||||
"email": "fara_cui@test.com",
|
||||
"parola": "parolasecreta123",
|
||||
"consent": "1",
|
||||
"csrf_token": token,
|
||||
})
|
||||
# trebuie sa returneze 422 (sau sa randeze formularul cu eroare)
|
||||
@@ -96,6 +97,7 @@ def test_signup_scrie_email_pe_account(client):
|
||||
"cui": "RO9999001",
|
||||
"email": "cu_email@test.com",
|
||||
"parola": "parolasecreta123",
|
||||
"consent": "1",
|
||||
"csrf_token": token,
|
||||
})
|
||||
assert resp.status_code == 200
|
||||
@@ -131,6 +133,7 @@ def test_signup_email_duplicat_mesaj_email(client):
|
||||
"cui": make_test_cui("email-dup-c1"),
|
||||
"email": "emaildup@test.com",
|
||||
"parola": "parolasecreta123",
|
||||
"consent": "1",
|
||||
"csrf_token": token,
|
||||
})
|
||||
assert resp1.status_code == 200
|
||||
@@ -145,6 +148,7 @@ def test_signup_email_duplicat_mesaj_email(client):
|
||||
"cui": cui_nou,
|
||||
"email": "emaildup@test.com",
|
||||
"parola": "parolasecreta456",
|
||||
"consent": "1",
|
||||
"csrf_token": token2,
|
||||
})
|
||||
|
||||
@@ -179,6 +183,7 @@ def test_signup_cui_existent_mesaj_prietenos(client):
|
||||
"cui": "RO8888001",
|
||||
"email": "firma1@test.com",
|
||||
"parola": "parolasecreta123",
|
||||
"consent": "1",
|
||||
"csrf_token": token,
|
||||
})
|
||||
|
||||
@@ -190,6 +195,7 @@ def test_signup_cui_existent_mesaj_prietenos(client):
|
||||
"cui": "RO8888001",
|
||||
"email": "firma2@test.com",
|
||||
"parola": "parolasecreta456",
|
||||
"consent": "1",
|
||||
"csrf_token": token2,
|
||||
})
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ def _do_signup(c: TestClient, name: str, email: str, parola: str = "parolasecret
|
||||
"cui": make_test_cui(email),
|
||||
"email": email,
|
||||
"parola": parola,
|
||||
"consent": "1",
|
||||
"csrf_token": token,
|
||||
})
|
||||
|
||||
|
||||
@@ -40,7 +40,8 @@ def _signup(client, name, email, password="parola_test_001"):
|
||||
from tests.conftest import make_test_cui
|
||||
tok = _csrf(client, "/signup")
|
||||
client.post("/signup", data={"name": name, "cui": make_test_cui(email), "email": email,
|
||||
"parola": password, "csrf_token": tok}, follow_redirects=True)
|
||||
"parola": password, "consent": "1", "csrf_token": tok},
|
||||
follow_redirects=True)
|
||||
from app.db import get_connection
|
||||
conn = get_connection()
|
||||
try:
|
||||
|
||||
@@ -48,6 +48,7 @@ def test_signup_creeaza_cont_user_si_cheie(client):
|
||||
"cui": "RO12345678",
|
||||
"email": "test@example.com",
|
||||
"parola": "parolasecreta",
|
||||
"consent": "1",
|
||||
"csrf_token": token,
|
||||
})
|
||||
assert resp.status_code == 200
|
||||
@@ -87,6 +88,7 @@ def test_signup_email_duplicat_eroare(client):
|
||||
"cui": make_test_cui("dup@example.com"),
|
||||
"email": "dup@example.com",
|
||||
"parola": "parolasecreta",
|
||||
"consent": "1",
|
||||
"csrf_token": token,
|
||||
})
|
||||
|
||||
@@ -102,6 +104,7 @@ def test_signup_email_duplicat_eroare(client):
|
||||
"cui": make_test_cui("dup-b@example.com"),
|
||||
"email": "dup@example.com",
|
||||
"parola": "altaparola123",
|
||||
"consent": "1",
|
||||
"csrf_token": token,
|
||||
})
|
||||
assert resp2.status_code in (200, 422)
|
||||
@@ -139,6 +142,72 @@ def test_signup_parola_scurta_eroare(client):
|
||||
conn.close()
|
||||
|
||||
|
||||
def test_signup_fara_consent_eroare(client):
|
||||
"""Consimtamant GDPR lipsa -> 422, fara creare cont; mesaj despre Termeni/GDPR.
|
||||
|
||||
Checkbox-ul de consimtamant trebuie validat server-side (functional, nu doar client-side):
|
||||
fara el contul nu se creeaza si planul/datele introduse se pastreaza in re-render.
|
||||
"""
|
||||
from tests.conftest import make_test_cui
|
||||
resp = client.get("/signup")
|
||||
token = _csrf(resp.text)
|
||||
|
||||
resp = client.post("/signup", data={
|
||||
"name": "Service Fara Consent",
|
||||
"cui": make_test_cui("fara-consent@test.com"),
|
||||
"email": "fara-consent@test.com",
|
||||
"parola": "parolasecreta123",
|
||||
# fara "consent"
|
||||
"csrf_token": token,
|
||||
})
|
||||
assert resp.status_code == 422
|
||||
assert "rfak_" not in resp.text
|
||||
assert "GDPR" in resp.text or "Termeni" in resp.text
|
||||
|
||||
from app.db import get_connection
|
||||
conn = get_connection()
|
||||
try:
|
||||
acct = conn.execute(
|
||||
"SELECT * FROM accounts WHERE name='Service Fara Consent'"
|
||||
).fetchone()
|
||||
assert acct is None, "Cont creat desi consimtamantul lipsea"
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def test_signup_salveaza_requested_plan_si_consent(client):
|
||||
"""POST /signup cu plan ales -> accounts.requested_plan = codul ales, consent_at setat,
|
||||
iar tier RAMANE 'free' (planul cerut NU acorda drepturi)."""
|
||||
from tests.conftest import make_test_cui
|
||||
resp = client.get("/signup")
|
||||
token = _csrf(resp.text)
|
||||
|
||||
resp = client.post("/signup", data={
|
||||
"name": "Service Plan Pro",
|
||||
"cui": make_test_cui("plan-pro@test.com"),
|
||||
"email": "plan-pro@test.com",
|
||||
"parola": "parolasecreta123",
|
||||
"plan": "pro",
|
||||
"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 Plan Pro'"
|
||||
).fetchone()
|
||||
assert acct is not None
|
||||
assert acct["requested_plan"] == "pro", "Planul cerut nu a fost salvat"
|
||||
assert acct["tier"] == "free", "tier NU trebuie urcat din planul cerut (doar dupa plata)"
|
||||
assert acct["consent_at"], "consent_at trebuie setat la signup cu consimtamant"
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def test_cheie_afisata_o_data(client):
|
||||
"""Cheia rfak_ apare in raspunsul POST /signup; GET /signup nu o contine."""
|
||||
from tests.conftest import make_test_cui
|
||||
@@ -150,6 +219,7 @@ def test_cheie_afisata_o_data(client):
|
||||
"cui": make_test_cui("cheie@test.com"),
|
||||
"email": "cheie@test.com",
|
||||
"parola": "parolasecreta",
|
||||
"consent": "1",
|
||||
"csrf_token": token,
|
||||
})
|
||||
assert resp_post.status_code == 200
|
||||
|
||||
Reference in New Issue
Block a user