US-004: rezolva_rar_env (cerere>default cont>ancora globala) + MediuIndisponibil + cod RAR_MEDIU_INDISPONIBIL. US-005: camp rar_env pe POST /v1/prezentari + /valideaza (Literal), echo in SubmissionResult/ValidareResult/GET, build_key + INSERT env-aware. US-006: AccountSessions re-cheiat (account_id, rar_env); RarClient base_url per env; creds din slotul env; purge + recover_orphans scoped pe env (E1/1a, 1b/E6); claim_one propaga rar_env (1c/E8); keepalive pe ancora globala (M2). US-009: selector mediu la import (>=2 medii), eticheta la 1, banner la 0; commit seteaza rar_env pe submissions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
327 lines
12 KiB
Python
327 lines
12 KiB
Python
"""Teste US-006 (PRD 5.20) — sesiuni si trimitere worker per (cont, env).
|
|
|
|
Verifica:
|
|
- AccountSessions re-cheiat pe (account_id, rar_env): doua env ale aceluiasi cont
|
|
au sesiuni distincte.
|
|
- RarClient creat cu base_url-ul mediului (test -> rar_base_url_test,
|
|
prod -> rar_base_url_prod), nu ancora globala.
|
|
- Creds extrase din slotul accounts.rar_creds_{env}_enc corect per env.
|
|
- Purjarea creds efemere scoped pe (account_id, rar_env): login pe test NU sterge
|
|
creds efemere ale submission-urilor PROD ale aceluiasi cont (auto-fix E1/1a).
|
|
- recover_orphans per (cont, env): orfanii prod reconciliati contra endpoint prod,
|
|
nu contra test (auto-fix 1b/E6).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import tempfile
|
|
|
|
import pytest
|
|
from cryptography.fernet import Fernet
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Fixture DB
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@pytest.fixture()
|
|
def env(monkeypatch):
|
|
tmp = tempfile.mkdtemp()
|
|
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "t.db"))
|
|
monkeypatch.setenv("AUTOPASS_CREDS_KEY", Fernet.generate_key().decode())
|
|
monkeypatch.setenv("AUTOPASS_WORKER_USE_TEST_CREDS", "false")
|
|
monkeypatch.setenv("AUTOPASS_REQUIRE_API_KEY", "false")
|
|
from app.config import get_settings
|
|
from app import crypto
|
|
get_settings.cache_clear()
|
|
crypto.reset_cache()
|
|
from app.db import get_connection, init_db
|
|
init_db()
|
|
conn = get_connection()
|
|
settings = get_settings()
|
|
yield conn, settings
|
|
conn.close()
|
|
get_settings.cache_clear()
|
|
crypto.reset_cache()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_CONTENT = {
|
|
"vin": "WVWZZZ1KZAW000123", "nr_inmatriculare": "B999TST",
|
|
"data_prestatie": "2026-06-15", "odometru_final": "123456",
|
|
"prestatii": [{"cod_prestatie": "OE-1"}], "sistem_reparat": "null",
|
|
}
|
|
|
|
|
|
def _insert_sub(conn, account_id=1, rar_env="test", creds_enc=None, status="queued"):
|
|
"""Insereaza un submission cu env si creds explicite."""
|
|
content = _CONTENT.copy()
|
|
cur = conn.execute(
|
|
"INSERT INTO submissions "
|
|
"(idempotency_key, account_id, status, payload_json, rar_env, rar_creds_enc) "
|
|
"VALUES (?, ?, ?, ?, ?, ?)",
|
|
(f"k-{os.urandom(4).hex()}", account_id, status, json.dumps(content), rar_env, creds_enc),
|
|
)
|
|
return int(cur.lastrowid)
|
|
|
|
|
|
def _row(conn, sid):
|
|
return conn.execute("SELECT * FROM submissions WHERE id=?", (sid,)).fetchone()
|
|
|
|
|
|
# Captura base_url-urilor clientilor creati de AccountSessions
|
|
_created_clients: list = []
|
|
|
|
|
|
class FakeRarClient:
|
|
"""RarClient stub care captura base_url-ul pentru assertii."""
|
|
|
|
def __init__(self, settings=None, *, base_url=None, login_exc=None):
|
|
self.base_url = base_url
|
|
self._login_exc = login_exc
|
|
self.login_calls = 0
|
|
self.closed = False
|
|
_created_clients.append(self)
|
|
|
|
def login(self, email, password):
|
|
self.login_calls += 1
|
|
if self._login_exc:
|
|
raise self._login_exc
|
|
return f"TOK-{email}-{self.base_url}"
|
|
|
|
def get_nomenclator(self, token):
|
|
return []
|
|
|
|
def close(self):
|
|
self.closed = True
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# test_sesiune_separata_per_env
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_sesiune_separata_per_env(env, monkeypatch):
|
|
"""Doua submission-uri ale aceluiasi cont, env test + prod -> doua login-uri distincte.
|
|
|
|
Cheia sesiunii e (account_id, rar_env): sesiunile test si prod sunt independente.
|
|
"""
|
|
import app.worker.__main__ as w
|
|
|
|
_created_clients.clear()
|
|
monkeypatch.setattr(w, "RarClient", FakeRarClient)
|
|
|
|
conn, settings = env
|
|
# Cont secundar (contul 1 e default din schema)
|
|
conn.execute("INSERT INTO accounts (id, name) VALUES (2, 'Cont2')")
|
|
conn.commit()
|
|
|
|
sessions = w.AccountSessions(settings)
|
|
|
|
creds_test = {"email": "test@example.ro", "password": "ptest"}
|
|
creds_prod = {"email": "prod@example.ro", "password": "pprod"}
|
|
|
|
tok_test = sessions.get_token(conn, 2, creds_test, "test")
|
|
tok_prod = sessions.get_token(conn, 2, creds_prod, "prod")
|
|
|
|
# Doua login-uri distincte
|
|
assert len(_created_clients) == 2
|
|
assert _created_clients[0].login_calls == 1
|
|
assert _created_clients[1].login_calls == 1
|
|
|
|
# Tokenuri distincte (de la email-uri diferite)
|
|
assert tok_test != tok_prod
|
|
|
|
# Sesiunile active: doua intrari, ambele pt cont 2, env diferite
|
|
active = sessions.active()
|
|
assert len(active) == 2
|
|
envs_active = {env for _, env, _, _ in active}
|
|
assert envs_active == {"test", "prod"}
|
|
|
|
# Al doilea apel cu acelasi (cont, env) -> cache, NU re-login
|
|
tok_test2 = sessions.get_token(conn, 2, creds_test, "test")
|
|
assert tok_test2 == tok_test
|
|
assert len(_created_clients) == 2 # niciun client nou creat
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# test_base_url_dupa_submission
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_base_url_dupa_submission(env, monkeypatch):
|
|
"""Un submission prod foloseste rar_base_url_prod; un submission test foloseste rar_base_url_test."""
|
|
import app.worker.__main__ as w
|
|
|
|
_created_clients.clear()
|
|
monkeypatch.setattr(w, "RarClient", FakeRarClient)
|
|
|
|
conn, settings = env
|
|
sessions = w.AccountSessions(settings)
|
|
|
|
creds = {"email": "x@example.ro", "password": "pw"}
|
|
sessions.get_token(conn, 1, creds, "test")
|
|
sessions.get_token(conn, 1, creds, "prod")
|
|
|
|
urls = {c.base_url for c in _created_clients}
|
|
assert settings.rar_base_url_test in urls, f"URL test asteptat in {urls}"
|
|
assert settings.rar_base_url_prod in urls, f"URL prod asteptat in {urls}"
|
|
# Cele doua URL-uri trebuie sa fie diferite (sisteme RAR separate)
|
|
assert settings.rar_base_url_test != settings.rar_base_url_prod
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# test_creds_din_slotul_env
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_creds_din_slotul_env(env, monkeypatch):
|
|
"""Cand submissions.rar_creds_enc lipseste, worker ia din accounts.rar_creds_{env}_enc.
|
|
|
|
Prod ia din rar_creds_prod_enc, nu din slotul test (auto-fix 1c/E8 + fallback per-env).
|
|
"""
|
|
import app.worker.__main__ as w
|
|
from app.crypto import encrypt_creds
|
|
|
|
conn, settings = env
|
|
|
|
enc_test = encrypt_creds({"email": "test@rar.ro", "password": "ptest"})
|
|
enc_prod = encrypt_creds({"email": "prod@rar.ro", "password": "pprod"})
|
|
|
|
# Salveaza creds in ambele sloturi per-env
|
|
conn.execute(
|
|
"UPDATE accounts SET rar_creds_test_enc=?, rar_creds_prod_enc=? WHERE id=1",
|
|
(enc_test, enc_prod),
|
|
)
|
|
conn.commit()
|
|
|
|
# Fara creds efemere pe submission -> fallback la slotul per-env
|
|
creds_test = w._creds_from_account(conn, 1, "test")
|
|
creds_prod = w._creds_from_account(conn, 1, "prod")
|
|
|
|
assert creds_test is not None, "slotul test trebuia sa aiba creds"
|
|
assert creds_test["email"] == "test@rar.ro"
|
|
|
|
assert creds_prod is not None, "slotul prod trebuia sa aiba creds"
|
|
assert creds_prod["email"] == "prod@rar.ro"
|
|
|
|
# Crucialmente: prod NU ia creds din slotul test
|
|
assert creds_prod["email"] != creds_test["email"]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# test_purge_creds_doar_pe_env (auto-fix E1/1a)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_purge_creds_doar_pe_env(env, monkeypatch):
|
|
"""Dupa login pe env=test, creds efemere ale submission-urilor PROD raman neatinse.
|
|
|
|
Scopul purjarii: WHERE account_id=? AND rar_env=?. Altfel un login TEST sterge
|
|
creds ale submission-urilor PROD -> prod blocat (auto-fix E1/1a).
|
|
"""
|
|
import app.worker.__main__ as w
|
|
from app.crypto import encrypt_creds
|
|
|
|
_created_clients.clear()
|
|
monkeypatch.setattr(w, "RarClient", FakeRarClient)
|
|
|
|
conn, settings = env
|
|
|
|
enc = encrypt_creds({"email": "u@rar.ro", "password": "pw"})
|
|
|
|
# Doua submission-uri ale aceluiasi cont: unul test, unul prod (ambele cu creds efemere)
|
|
sid_test = _insert_sub(conn, account_id=1, rar_env="test", creds_enc=enc)
|
|
sid_prod = _insert_sub(conn, account_id=1, rar_env="prod", creds_enc=enc)
|
|
|
|
sessions = w.AccountSessions(settings)
|
|
|
|
# Login pe env=test
|
|
sessions.get_token(conn, 1, {"email": "u@rar.ro", "password": "pw"}, "test")
|
|
|
|
# Creds efemere ale submission-ului TEST trebuie sterse (purjare normala)
|
|
row_test = _row(conn, sid_test)
|
|
assert row_test["rar_creds_enc"] is None, "creds test trebuiau sterse dupa login test"
|
|
|
|
# Creds efemere ale submission-ului PROD trebuie PASTRATE (nu sunt pentru env=test)
|
|
row_prod = _row(conn, sid_prod)
|
|
assert row_prod["rar_creds_enc"] is not None, \
|
|
"creds prod NU trebuiau sterse la login test (auto-fix E1/1a)"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# test_reconcile_pe_env_corect (auto-fix 1b/E6)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_reconcile_pe_env_corect(env, monkeypatch):
|
|
"""Un orfan env=prod e reconciliat contra endpoint PROD, nu contra test.
|
|
|
|
auto-fix 1b/E6: recover_orphans filtreaza pe rar_env si foloseste clientul/token-ul
|
|
env-ului corect. Orfanii prod contra endpoint test -> no-match -> re-POST prod =
|
|
DUPLICAT real ireversibil.
|
|
"""
|
|
import app.worker.__main__ as w
|
|
|
|
conn, settings = env
|
|
|
|
# Submission prod orfan (sending de mult timp)
|
|
sid_prod = _insert_sub(conn, account_id=1, rar_env="prod", status="sending")
|
|
conn.execute(
|
|
"UPDATE submissions SET sending_since=datetime('now', '-1 hour') WHERE id=?", (sid_prod,)
|
|
)
|
|
conn.commit()
|
|
|
|
# Submission test orfan (de verificat ca NU e atins de recover_orphans(rar_env='prod'))
|
|
sid_test = _insert_sub(conn, account_id=1, rar_env="test", status="sending")
|
|
conn.execute(
|
|
"UPDATE submissions SET sending_since=datetime('now', '-1 hour') WHERE id=?", (sid_test,)
|
|
)
|
|
conn.commit()
|
|
|
|
# Clientul prod fake — "gaseste" prezentarea prod la RAR
|
|
class FakeProdRar:
|
|
def __init__(self):
|
|
self.get_finalizate_calls = 0
|
|
|
|
def get_finalizate(self, token):
|
|
self.get_finalizate_calls += 1
|
|
return [{"id": 9999, "vin": "WVWZZZ1KZAW000123",
|
|
"dataPrestatie": "2026-06-15", "odometruFinal": 123456}]
|
|
|
|
def post_prezentare(self, token, payload):
|
|
return {"id": 9999}
|
|
|
|
# Clientul test fake — nu gaseste nimic (sistemul test nu are prezentarea)
|
|
class FakeTestRar:
|
|
def __init__(self):
|
|
self.get_finalizate_calls = 0
|
|
|
|
def get_finalizate(self, token):
|
|
self.get_finalizate_calls += 1
|
|
return [] # nu e la RAR test
|
|
|
|
def post_prezentare(self, token, payload):
|
|
return {"id": 1111}
|
|
|
|
rar_prod = FakeProdRar()
|
|
rar_test = FakeTestRar()
|
|
|
|
# Apelam recover_orphans cu clientul PROD si env='prod' -> trebuie sa gaseasca orfanul prod
|
|
n = w.recover_orphans(conn, settings, rar_prod, "tok-prod", account_id=1, rar_env="prod")
|
|
assert n == 1, f"trebuia sa reconcilieze 1 orfan prod, a gasit {n}"
|
|
|
|
row_prod = _row(conn, sid_prod)
|
|
assert row_prod["status"] == "sent", f"orfanul prod trebuia marcat sent, e {row_prod['status']}"
|
|
assert row_prod["id_prezentare"] == 9999
|
|
|
|
# Submission-ul TEST nu trebuia atins de recover_orphans cu rar_env='prod'
|
|
row_test = _row(conn, sid_test)
|
|
assert row_test["status"] == "sending", \
|
|
f"orfanul test NU trebuia atins de recover cu env=prod, e {row_test['status']}"
|
|
|
|
# Confirmare ca clientul prod a interogat finalizate (reconciliere pe endpoint corect)
|
|
assert rar_prod.get_finalizate_calls == 1
|
|
# Clientul test NU trebuia folosit (recover_orphans cu env=prod NU atinge endpoint test)
|
|
assert rar_test.get_finalizate_calls == 0
|