feat(5.16+5.17): tipografie/antet branded + tipuri cont, planuri si enforcement
PRD 5.16 — propagare design finalizata (system font stack, fara IBM Plex self-hostat): - US-001/002/008: tokeni --font-ui/--font-mono (system stack) + scala --fs-*; zero @font-face si zero /static/fonts/; landing aliniat la acelasi stack - US-003: RAR online = dot compact in antet + meniu burger; banda rosie DOAR pe blocat (invariant zero-silent-failures pastrat) - US-010: antet "ROMFAST AUTOPASS" + nume service + /login brandeit 2 coloane + badge plan; meniu burger cu separatoare; gate strict pe is_authenticated - US-011: selector tema pill icon+eticheta (reuse THEMES) - US-004/005/006/007: bug-fix editor prestatii (picker cod+denumire, add_extra in mod operatii, cod ales se salveaza fara "+", Renunta inchide via closest) - US-012/013: landing Autentificare->/login; wizard import colapsat + 4 pasi pe tokeni - fix VERIFY E2E: contoare duplicate pe 390px (inline display:flex batea @media) -> CSS + test-lock PRD 5.17 — tipuri de cont + trial Pro 30z + enforcement DUR: - US-001/002/008: accounts.tier + trial_until (migrare aditiva defensiva); app/plans.py sursa unica (PLANS, FREE_MONTHLY_LIMIT=60, effective_tier(now injectabil), monthly_usage, CONSUMED_STATUSES); create_account trial Pro 30z; CLI set-tier (protejat id=1, audit) - US-003/004/005: enforce volum 60/luna INAINTE de build_key pe ambele canale (PLAN_LIMITA_LUNARA, 3 niveluri + log_event); gate API Pro+ (PLAN_FARA_API 403 actionabil); valideaza/nomenclator raman permise; downgrade lazy; flag AUTOPASS_ENFORCE_PLANS (kill-switch) - US-006: badge plan antet + linie burger + consum N/60 + warn>=80% + 6 stari + copy RO pluralizat + banner one-time trial->Gratuit + pagina Cont Regresie: 1380 passed, 0 failed, 1 deselected (live). E2E browser pe 390/1280 confirmat. Backend trimitere (worker/masina stari/idempotenta/contract RAR) NEATINS. Lucrul 5.18 (corpus kNN) ramane separat, necomis. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -738,3 +738,141 @@ def test_strip_sanatate_fara_hex_hardcodat():
|
||||
f"Hex literal de culoare in _status.html — strip sanatate va arata gresit pe "
|
||||
f"tema hartie (luminoasa) / light. Folositi var(--token). Gasite: {hex_in_culori}"
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# PRD 5.16 US-010: Titlu ROMFAST AUTOPASS + account_name in antet
|
||||
# ============================================================
|
||||
|
||||
def test_titlu_romfast_autopass(client):
|
||||
"""US-010 (PRD 5.16): titlul din antet si tag-ul <title> sunt 'ROMFAST AUTOPASS',
|
||||
nu 'Gateway RAR AUTOPASS'."""
|
||||
_create_account_user("titlutest@test.com", name="Service Titlu")
|
||||
_login(client, "titlutest@test.com")
|
||||
html = client.get("/?tab=acasa").text
|
||||
|
||||
assert "ROMFAST AUTOPASS" in html, \
|
||||
"Titlul 'ROMFAST AUTOPASS' lipseste din antet (US-010 PRD 5.16)"
|
||||
assert "Gateway RAR AUTOPASS" not in html, \
|
||||
"Titlul vechi 'Gateway RAR AUTOPASS' inca prezent — inlocuieste cu 'ROMFAST AUTOPASS'"
|
||||
|
||||
|
||||
def test_header_arata_nume_service_logat(client):
|
||||
"""US-010 (PRD 5.16): cand utilizatorul e logat, antetul afiseaza numele service-ului
|
||||
(accounts.name) ca sub-titlu cu clasa .h-sub."""
|
||||
_create_account_user("numeservice@test.com", name="Service Auto Cluj SRL")
|
||||
_login(client, "numeservice@test.com")
|
||||
html = client.get("/?tab=acasa").text
|
||||
|
||||
assert "Service Auto Cluj SRL" in html, \
|
||||
"Numele service-ului nu apare in antet (US-010 PRD 5.16) — verifica .h-sub"
|
||||
assert "h-sub" in html, \
|
||||
"Clasa .h-sub lipseste din antet (US-010 PRD 5.16) — sub-titlul account_name lipseste"
|
||||
|
||||
|
||||
def test_login_branded_nu_schelet(client):
|
||||
"""US-010 (PRD 5.16): /login are layout 2-coloane branduit cu clasa .login-shell,
|
||||
titlul 'ROMFAST AUTOPASS', si formular cu POST /login (CSRF intact)."""
|
||||
resp = client.get("/login")
|
||||
assert resp.status_code == 200
|
||||
html = resp.text
|
||||
|
||||
assert "login-shell" in html, \
|
||||
"Clasa .login-shell lipseste din /login (US-010 PRD 5.16) — layout 2-coloane nenimplementat"
|
||||
assert "login-aside" in html, \
|
||||
"Clasa .login-aside lipseste — coloana stanga de brand lipseste (US-010)"
|
||||
assert "ROMFAST AUTOPASS" in html, \
|
||||
"Titlul 'ROMFAST AUTOPASS' lipseste din /login (US-010 PRD 5.16)"
|
||||
# Formular intact: POST /login cu csrf_token
|
||||
assert 'action="/login"' in html, "Actiunea formularului /login s-a schimbat — CSRF route invalida"
|
||||
assert 'name="csrf_token"' in html, "csrf_token lipseste din formular — securitate compromisa"
|
||||
assert 'name="parola"' in html, "Campul 'parola' lipseste din formular"
|
||||
|
||||
|
||||
# ============================================================
|
||||
# PRD 5.17 T7 (US-007): landing copy — limita 60 + trial Pro
|
||||
# PRD 5.16 US-012: Autentificare → /login
|
||||
# ============================================================
|
||||
|
||||
def _citeste_landing() -> str:
|
||||
"""Returneaza continutul landing.html (template static; variabilele Jinja2 nu
|
||||
afecteaza copy-ul de limita/plan/buton verificat mai jos)."""
|
||||
from pathlib import Path
|
||||
p = Path(__file__).parent.parent / "app" / "web" / "templates" / "landing.html"
|
||||
assert p.exists(), f"landing.html negasit la {p}"
|
||||
return p.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_landing_limita_60():
|
||||
"""5.17 T7 (US-007): limita planului Gratuit este 60 de prestatii/luna in landing,
|
||||
nu 100. Verifica meta description, announce bar, hero badge, cardul Gratuit si
|
||||
CTA-ul final."""
|
||||
html = _citeste_landing()
|
||||
|
||||
assert "100 de prestații" not in html, \
|
||||
"'100 de prestații' inca prezent in landing — limita trebuie sa fie 60 (5.17 T7)"
|
||||
assert "100 prestații" not in html, \
|
||||
"'100 prestații' inca prezent in landing — limita trebuie sa fie 60 (5.17 T7)"
|
||||
assert "60 de prestații" in html, \
|
||||
"'60 de prestații' lipseste din landing — verifica meta, announce bar, cardul Gratuit (5.17 T7)"
|
||||
assert "60 prestații" in html, \
|
||||
"'60 prestații' lipseste din hero badge in landing (5.17 T7)"
|
||||
assert "60 de prezentări" in html, \
|
||||
"'60 de prezentări' lipseste din CTA-ul final al landing-ului (5.17 T7)"
|
||||
|
||||
|
||||
def test_landing_trial_pro_nu_premium():
|
||||
"""5.17 T7 (US-007): trial-ul de 30 de zile este pe Pro, NU pe Premium.
|
||||
Verifica sectiunea PRICING (subtitle) si sectiunea AUTH (lista beneficii)."""
|
||||
html = _citeste_landing()
|
||||
|
||||
assert "Pro gratuit 30 de zile" in html, \
|
||||
"'Pro gratuit 30 de zile' lipseste din landing — verifica sectiunile PRICING + AUTH (5.17 T7)"
|
||||
assert "Premium gratuit 30 de zile" not in html, \
|
||||
"'Premium gratuit 30 de zile' inca in landing — trial-ul e pe Pro, nu Premium (5.17 T7)"
|
||||
|
||||
|
||||
def test_landing_autentificare_link_login():
|
||||
"""5.16 US-012: butonul 'Autentificare' din header-ul landing este un link <a href='/login'>
|
||||
cu clasa auth-login-link, NU un buton care deschide modalul de login.
|
||||
CSS-ul responsive (.lp-hactions) trebuie sa foloseasca noul selector, nu cel vechi."""
|
||||
html = _citeste_landing()
|
||||
|
||||
# Link real catre /login in header (cu clasa de identificare)
|
||||
assert 'href="/login"' in html, \
|
||||
"href='/login' lipseste din landing — 'Autentificare' din header trebuie sa fie link (5.16 US-012)"
|
||||
assert "auth-login-link" in html, \
|
||||
"Clasa auth-login-link lipseste — header 'Autentificare' nu a fost convertit la <a> (5.16 US-012)"
|
||||
# CSS-ul responsive ascunde linkul pe <430px prin noul selector (nu cel vechi cu atribute)
|
||||
assert "a.auth-login-link" in html, \
|
||||
"Selectorul CSS 'a.auth-login-link' lipseste — CSS responsive neactualizat (5.16 US-012)"
|
||||
# Selectorul CSS vechi cu [data-act="auth"][data-tab="login"] nu mai exista in CSS
|
||||
assert '[data-act="auth"][data-tab="login"]' not in html, \
|
||||
"Selectorul CSS vechi [data-act='auth'][data-tab='login'] inca prezent (5.16 US-012)"
|
||||
|
||||
|
||||
def test_contoare_desktop_ascunse_pe_mobil_fara_inline_display():
|
||||
"""US-002 (PRD 5.16): pe <=560px se vad DOAR contoarele compacte, nu si cele 5 carduri mari.
|
||||
|
||||
Regresie prinsa la VERIFY E2E (390px): un inline `style="display:flex"` pe `.contoare-desktop`
|
||||
batea regula `@media (max-width:560px) { .contoare-desktop { display:none } }` (inline > stylesheet)
|
||||
-> contoare DUPLICATE pe mobil. Lock: `display:flex` sta in CSS (nu inline pe element), iar media
|
||||
query-ul ascunde cardurile mari pe mobil.
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
tdir = Path(__file__).parent.parent / "app" / "web" / "templates"
|
||||
base = (tdir / "base.html").read_text(encoding="utf-8")
|
||||
status = (tdir / "_status.html").read_text(encoding="utf-8")
|
||||
|
||||
# _status.html: containerul de carduri NU mai are inline display (altfel bate media query-ul).
|
||||
assert 'class="contoare-desktop" style="display:flex' not in status, (
|
||||
"containerul .contoare-desktop are inline display:flex -> media query-ul nu-l mai poate ascunde pe mobil"
|
||||
)
|
||||
# base.html: regula CSS default (display:flex) + ascunderea pe <=560px.
|
||||
assert re.search(r"\.contoare-desktop\s*\{[^}]*display:\s*flex", base), (
|
||||
"lipseste regula CSS .contoare-desktop { display:flex } in base.html"
|
||||
)
|
||||
assert re.search(r"\.contoare-desktop\s*\{[^}]*display:\s*none", base), (
|
||||
"lipseste ascunderea .contoare-desktop { display:none } (media <=560px) in base.html"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user