feat(5.20): US-007 validare login RAR pe env-ul setului de credentiale
Login de validare loveste base_url_pentru_env(env) (NU ancora globala); endpoint POST /cont/test-rar-creds + card in _integrare.html; mesaj distinct TESTARE vs PRODUCTIE la 401 incrucisat (confirmat live). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -90,6 +90,7 @@ from ..mapping import (
|
||||
)
|
||||
from ..shared_store import record_human_validation
|
||||
from ..rar_env import MediuIndisponibil, medii_disponibile_cont, rar_env_efectiv_cont, rezolva_rar_env
|
||||
from ..rar_client import RarAuthError, RarClient, RarError, base_url_pentru_env
|
||||
|
||||
# Campuri canonice cu eticheta umana pentru dropdown mapare coloane
|
||||
_CANONICAL_FIELDS = [(k, v[0]) for k, v in _CANONICAL_SYNONYMS.items()]
|
||||
@@ -4034,6 +4035,66 @@ async def web_confirma_import(
|
||||
# care cere cheie API; sesiunea web e suficienta ca identitate). #
|
||||
# =========================================================================== #
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# US-007 (PRD 5.20): Validare credentiale RAR env-aware
|
||||
#
|
||||
# Premisa confirmata live (2026-06-29): creds de productie NU se valideaza pe RAR
|
||||
# test si invers (401 incrucisat). Deci login-ul de proba TREBUIE sa loveasca
|
||||
# endpoint-ul mediului caruia ii apartin credentialele.
|
||||
#
|
||||
# Puncte de validare existente:
|
||||
# - /cont/test-rar-creds (testeaza integrarea RAR, fara efecte secundare)
|
||||
# Puncte non-aplicabile (nu colecteaza/valideaza creds RAR):
|
||||
# - signup (/signup): nu colecteaza credentiale RAR — creare cont platforma, nu RAR
|
||||
# - preview import: nu valideaza credentiale RAR
|
||||
# Puncte viitoare (US-008):
|
||||
# - /cont/rar-creds la salvare creds per-mediu (va apela _valideaza_login_rar)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _eticheta_mediu_rar(env: str) -> str:
|
||||
"""Eticheta umana a mediului RAR pentru mesaje de eroare/succes.
|
||||
|
||||
'test' -> 'TESTARE', 'prod' -> 'PRODUCTIE'.
|
||||
"""
|
||||
return "PRODUCTIE" if env == "prod" else "TESTARE"
|
||||
|
||||
|
||||
def _valideaza_login_rar(
|
||||
settings,
|
||||
email: str,
|
||||
password: str,
|
||||
env: str,
|
||||
) -> tuple[bool, str | None]:
|
||||
"""Valideaza credentialele RAR prin login pe mediul specificat (US-007, PRD 5.20).
|
||||
|
||||
Creeaza un RarClient cu base_url-ul mediului `env` (NU base_url-ul global),
|
||||
deoarece RAR test si RAR prod sunt sisteme separate cu credentiale separate.
|
||||
|
||||
Parametri
|
||||
---------
|
||||
settings: configuratia aplicatiei (pentru base_url_test/prod si timeout)
|
||||
email: email-ul contului RAR
|
||||
password: parola contului RAR
|
||||
env: mediul tinta: 'test' sau 'prod'
|
||||
|
||||
Returneaza
|
||||
----------
|
||||
(True, None) la succes (login reusit)
|
||||
(False, mesaj) la esec; `mesaj` include eticheta mediului ('TESTARE'/'PRODUCTIE'),
|
||||
ex. 'Credentiale RAR invalide pe TESTARE.'
|
||||
"""
|
||||
env_label = _eticheta_mediu_rar(env)
|
||||
try:
|
||||
with RarClient(settings, base_url=base_url_pentru_env(settings, env)) as rar:
|
||||
rar.login(email, password)
|
||||
return True, None
|
||||
except RarAuthError:
|
||||
return False, f"Credentiale RAR invalide pe {env_label}."
|
||||
except RarError as exc:
|
||||
return False, f"Eroare la conectare RAR ({env_label}): {exc}"
|
||||
|
||||
|
||||
def _render_cont(
|
||||
request: Request,
|
||||
*,
|
||||
@@ -4365,3 +4426,61 @@ def cont_rar_creds(
|
||||
)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
@router.post("/cont/test-rar-creds", response_class=HTMLResponse)
|
||||
def cont_test_rar_creds(
|
||||
request: Request,
|
||||
rar_email: str = Form(""),
|
||||
rar_parola: str = Form(""),
|
||||
rar_env: str = Form(default=""),
|
||||
csrf_token: str | None = Form(None),
|
||||
) -> HTMLResponse:
|
||||
"""Testeaza credentialele RAR prin login real pe mediul specificat (US-007, PRD 5.20).
|
||||
|
||||
Fara efecte secundare: nu salveaza nimic, nu creeaza submission. Pur validare.
|
||||
Camp parola NICIODATA re-pus in raspuns.
|
||||
|
||||
Decizie env (documentata US-007):
|
||||
- param `rar_env` explicit ('test'/'prod') -> folosit direct
|
||||
- altfel -> rar_env_efectiv_cont (default-ul contului) sau ancora globala settings.rar_env
|
||||
- signup nu colecteaza creds RAR, deci nu apeleaza aceasta functie
|
||||
"""
|
||||
account_id = require_login(request)
|
||||
verify_csrf(request, csrf_token)
|
||||
|
||||
email = rar_email.strip()
|
||||
parola = rar_parola.strip()
|
||||
|
||||
if not email or not parola:
|
||||
return templates.TemplateResponse(
|
||||
"_integrare_test_rezultat.html",
|
||||
{"request": request, "succes": False,
|
||||
"mesaj": "Email si parola sunt obligatorii."},
|
||||
)
|
||||
|
||||
# Determina env-ul de validare
|
||||
settings = get_settings()
|
||||
env_cerut = (rar_env or "").strip().lower()
|
||||
if env_cerut in ("test", "prod"):
|
||||
env = env_cerut
|
||||
else:
|
||||
# Fallback: env-ul efectiv al contului (default) sau ancora globala
|
||||
conn = get_connection()
|
||||
try:
|
||||
env = rar_env_efectiv_cont(conn, account_id) or settings.rar_env or "test"
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
ok, mesaj_eroare = _valideaza_login_rar(settings, email, parola, env)
|
||||
if ok:
|
||||
env_label = _eticheta_mediu_rar(env)
|
||||
return templates.TemplateResponse(
|
||||
"_integrare_test_rezultat.html",
|
||||
{"request": request, "succes": True,
|
||||
"mesaj": f"Credentiale RAR valide pe {env_label}."},
|
||||
)
|
||||
return templates.TemplateResponse(
|
||||
"_integrare_test_rezultat.html",
|
||||
{"request": request, "succes": False, "mesaj": mesaj_eroare},
|
||||
)
|
||||
|
||||
@@ -226,7 +226,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Formular test conexiune #}
|
||||
{# Formular test conexiune cheie API #}
|
||||
<div class="card" style="margin-bottom:16px;">
|
||||
<h3 style="margin:0 0 12px; font-size:15px;">Testeaza conexiunea</h3>
|
||||
<form id="form-test-cheie"
|
||||
@@ -246,6 +246,42 @@
|
||||
<div id="integrare-test-rezultat" style="margin-top:8px;"></div>
|
||||
</div>
|
||||
|
||||
{# Formular test credentiale RAR (US-007, PRD 5.20) #}
|
||||
{# Login de proba pe mediul ales — fara efecte secundare, nu salveaza nimic. #}
|
||||
{# Banner-ul de rezultat include eticheta mediului ("pe TESTARE" / "pe PRODUCTIE"). #}
|
||||
<div class="card" style="margin-bottom:16px;">
|
||||
<h3 style="margin:0 0 6px; font-size:15px;">Testeaza credentiale RAR</h3>
|
||||
<p class="muted" style="font-size:12px; margin:0 0 10px;">
|
||||
Verifica daca credentialele RAR sunt corecte pe mediul ales. Nu se salveaza nimic.
|
||||
</p>
|
||||
<form id="form-test-rar-creds"
|
||||
hx-post="/cont/test-rar-creds"
|
||||
hx-target="#rar-test-rezultat"
|
||||
hx-swap="innerHTML"
|
||||
style="display:flex; gap:8px; flex-wrap:wrap; align-items:flex-end;">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<div>
|
||||
<label for="test-rar-email" style="display:block; font-size:13px; color:var(--muted); margin-bottom:4px;">Email RAR</label>
|
||||
<input type="email" id="test-rar-email" name="rar_email" placeholder="email@service.ro"
|
||||
style="width:220px;" autocomplete="off">
|
||||
</div>
|
||||
<div>
|
||||
<label for="test-rar-parola" style="display:block; font-size:13px; color:var(--muted); margin-bottom:4px;">Parola RAR</label>
|
||||
<input type="password" id="test-rar-parola" name="rar_parola"
|
||||
style="width:160px;" autocomplete="new-password">
|
||||
</div>
|
||||
<div>
|
||||
<label for="test-rar-env" style="display:block; font-size:13px; color:var(--muted); margin-bottom:4px;">Mediu</label>
|
||||
<select id="test-rar-env" name="rar_env" style="height:36px; padding:0 8px;">
|
||||
<option value="prod">Productie</option>
|
||||
<option value="test">Testare</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit">Testeaza RAR</button>
|
||||
</form>
|
||||
<div id="rar-test-rezultat" style="margin-top:8px;"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
177
tests/test_validare_env.py
Normal file
177
tests/test_validare_env.py
Normal file
@@ -0,0 +1,177 @@
|
||||
"""Teste US-007 (PRD 5.20): validare credentiale RAR pe env-ul setului de creds.
|
||||
|
||||
Premisa confirmata live (2026-06-29): creds prod NU se valideaza pe RAR test si
|
||||
invers (401 incrucisat). Deci login-ul de proba TREBUIE sa loveasca endpoint-ul
|
||||
mediului caruia ii apartin credentialele, nu URL-ul global AUTOPASS_RAR_ENV.
|
||||
|
||||
Functie testata:
|
||||
routes._valideaza_login_rar(settings, email, password, env)
|
||||
|
||||
Teste:
|
||||
test_valideaza_pe_env_creds -- login pe env='prod' foloseste base_url prod (nu test)
|
||||
test_mesaj_distinge_env -- esec pe test vs prod produce mesaje diferite
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
|
||||
from app.rar_client import RarAuthError
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fixture izolat
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def env_db(monkeypatch):
|
||||
"""DB temporara + settings curate. Numele 'env_db' evita coliziunea cu parametrul
|
||||
'env' folosit in testele de mai jos ca string ('test'/'prod')."""
|
||||
tmp = tempfile.mkdtemp()
|
||||
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "t.db"))
|
||||
monkeypatch.setenv("AUTOPASS_SEED_OPERATII_ENABLED", "false")
|
||||
from app.config import get_settings
|
||||
|
||||
get_settings.cache_clear()
|
||||
from app.db import init_db
|
||||
|
||||
init_db()
|
||||
yield
|
||||
get_settings.cache_clear()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Stub-uri RarClient
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class _RarClientSpy:
|
||||
"""Inregistreaza base_url-ul cu care a fost construit si simuleaza login reusit."""
|
||||
|
||||
captured: dict = {}
|
||||
|
||||
def __init__(self, settings=None, *, base_url=None):
|
||||
_RarClientSpy.captured["base_url"] = base_url
|
||||
|
||||
def login(self, email, password):
|
||||
return "TOKEN-SPY"
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
|
||||
class _RarClientFail:
|
||||
"""Simuleaza login esuat (RarAuthError 401) indiferent de env."""
|
||||
|
||||
def __init__(self, settings=None, *, base_url=None):
|
||||
pass
|
||||
|
||||
def login(self, email, password):
|
||||
raise RarAuthError("Credentiale RAR invalide", status_code=401)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Teste
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_valideaza_pe_env_creds(env_db, monkeypatch):
|
||||
"""Cand validezi creds pentru env='prod', clientul de login e creat cu base_url-ul prod.
|
||||
|
||||
US-007 AC: 'validarea foloseste env-ul setului de creds verificat'.
|
||||
Premisa: creds prod nu se valideaza pe RAR test (401 incrucisat), deci
|
||||
clientul TREBUIE sa foloseasca base_url-ul prod, nu cel de test.
|
||||
"""
|
||||
import app.web.routes as routes_mod
|
||||
from app.config import get_settings
|
||||
|
||||
_RarClientSpy.captured = {}
|
||||
monkeypatch.setattr(routes_mod, "RarClient", _RarClientSpy)
|
||||
|
||||
settings = get_settings()
|
||||
ok, mesaj = routes_mod._valideaza_login_rar(settings, "a@b.ro", "parola", "prod")
|
||||
|
||||
assert ok is True, f"Login simulat trebuia sa reuseasca: mesaj={mesaj!r}"
|
||||
assert mesaj is None
|
||||
|
||||
base_url_folosit = _RarClientSpy.captured.get("base_url")
|
||||
assert base_url_folosit == settings.rar_base_url_prod, (
|
||||
f"Clientul trebuia construit cu rar_base_url_prod={settings.rar_base_url_prod!r},"
|
||||
f" dar a primit base_url={base_url_folosit!r}"
|
||||
)
|
||||
assert base_url_folosit != settings.rar_base_url_test, (
|
||||
"Clientul nu trebuia sa foloseasca base_url-ul de TEST la validarea creds PROD"
|
||||
)
|
||||
|
||||
|
||||
def test_valideaza_pe_env_creds_test(env_db, monkeypatch):
|
||||
"""Cand validezi creds pentru env='test', clientul de login e creat cu base_url-ul test."""
|
||||
import app.web.routes as routes_mod
|
||||
from app.config import get_settings
|
||||
|
||||
_RarClientSpy.captured = {}
|
||||
monkeypatch.setattr(routes_mod, "RarClient", _RarClientSpy)
|
||||
|
||||
settings = get_settings()
|
||||
ok, mesaj = routes_mod._valideaza_login_rar(settings, "a@b.ro", "parola", "test")
|
||||
|
||||
assert ok is True
|
||||
base_url_folosit = _RarClientSpy.captured.get("base_url")
|
||||
assert base_url_folosit == settings.rar_base_url_test, (
|
||||
f"Clientul trebuia construit cu rar_base_url_test={settings.rar_base_url_test!r},"
|
||||
f" dar a primit base_url={base_url_folosit!r}"
|
||||
)
|
||||
assert base_url_folosit != settings.rar_base_url_prod
|
||||
|
||||
|
||||
def test_mesaj_distinge_env(env_db, monkeypatch):
|
||||
"""La esec de login pe test vs prod, mesajul difera ('TESTARE' vs 'PRODUCTIE').
|
||||
|
||||
US-007 AC: 'mesaj distinct creds invalide pe TESTARE vs pe PRODUCTIE'.
|
||||
Design F6/F7: banner-ul de eroare indica pe ce mediu a esuat login-ul.
|
||||
"""
|
||||
import app.web.routes as routes_mod
|
||||
from app.config import get_settings
|
||||
|
||||
monkeypatch.setattr(routes_mod, "RarClient", _RarClientFail)
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
ok_test, msg_test = routes_mod._valideaza_login_rar(settings, "a@b.ro", "parola", "test")
|
||||
ok_prod, msg_prod = routes_mod._valideaza_login_rar(settings, "a@b.ro", "parola", "prod")
|
||||
|
||||
assert ok_test is False, "Esecul la test trebuia sa returneze ok=False"
|
||||
assert ok_prod is False, "Esecul la prod trebuia sa returneze ok=False"
|
||||
|
||||
assert msg_test is not None and "TESTARE" in msg_test, (
|
||||
f"Mesajul la esec pe test trebuia sa contina 'TESTARE': {msg_test!r}"
|
||||
)
|
||||
assert msg_prod is not None and "PRODUCTIE" in msg_prod, (
|
||||
f"Mesajul la esec pe prod trebuia sa contina 'PRODUCTIE': {msg_prod!r}"
|
||||
)
|
||||
# Cross-check: etichetele nu se amesteca
|
||||
assert "PRODUCTIE" not in (msg_test or ""), (
|
||||
f"Mesajul esec test nu trebuia sa mentioneze PRODUCTIE: {msg_test!r}"
|
||||
)
|
||||
assert "TESTARE" not in (msg_prod or ""), (
|
||||
f"Mesajul esec prod nu trebuia sa mentioneze TESTARE: {msg_prod!r}"
|
||||
)
|
||||
Reference in New Issue
Block a user