"""Teste keepalive RAR — login de proba periodic ca dashboard-ul sa nu afiseze fals "RAR inaccesibil" doar din lipsa de trafic. Comportament asteptat (_maybe_keepalive): - login vechi/lipsa + creds durabile -> sondeaza (get_token apelat) si forteaza login real (invalidate inainte); - login proaspat (sub interval) -> NU sondeaza; - interval=0 -> dezactivat; - fara cont cu creds durabile -> nu sondeaza; - gating: dupa o incercare, nu re-sondeaza in cadrul intervalului (nu hartui RAR). """ from __future__ import annotations import os import tempfile from datetime import datetime, timedelta, timezone import pytest @pytest.fixture() def env(monkeypatch): tmp = tempfile.mkdtemp() monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "t.db")) from app.config import get_settings get_settings.cache_clear() from app.db import get_connection, init_db init_db() conn = get_connection() yield conn, get_settings() conn.close() get_settings.cache_clear() class _FakeSessions: """Imita AccountSessions: get_token reusit reimprospateaza heartbeat-ul (ca realul).""" def __init__(self, conn, *, fail: bool = False): self._conn = conn self._fail = fail self.invalidated: list[int] = [] self.tokens: list[int] = [] def invalidate(self, account_id: int) -> None: self.invalidated.append(account_id) def get_token(self, conn, account_id: int, creds) -> str | None: self.tokens.append(account_id) if self._fail: raise RuntimeError("RAR jos") from app.db import write_heartbeat write_heartbeat(conn, rar_login_ok=True, detail=f"login proba (cont {account_id})") return "tok" def _set_last_login(conn, *, ago_s: float | None): """Seteaza last_rar_login_ok la now-ago_s (None = niciun login).""" from app.db import write_heartbeat write_heartbeat(conn, detail="poll") # asigura randul heartbeat if ago_s is None: conn.execute("UPDATE worker_heartbeat SET last_rar_login_ok=NULL WHERE id=1") else: ts = (datetime.now(timezone.utc) - timedelta(seconds=ago_s)).isoformat() conn.execute("UPDATE worker_heartbeat SET last_rar_login_ok=? WHERE id=1", (ts,)) conn.commit() def _account_cu_creds(conn) -> int: from app.accounts import create_account from app.crypto import encrypt_creds acct = create_account(conn, "Service Cu Creds", email="svc@example.com") enc = encrypt_creds({"email": "svc@example.com", "password": "secret"}) conn.execute("UPDATE accounts SET rar_creds_enc=? WHERE id=?", (enc, acct)) conn.commit() return acct def test_login_vechi_sondeaza_si_reimprospateaza(env): """Login mai vechi decat intervalul + creds durabile -> proba reala, heartbeat reimprospatat.""" from app.worker.__main__ import _maybe_keepalive from app.db import read_heartbeat conn, settings = env settings.worker_rar_keepalive_interval_s = 86400 acct = _account_cu_creds(conn) _set_last_login(conn, ago_s=100000) # > 24h sessions = _FakeSessions(conn) _maybe_keepalive(conn, settings, sessions, {"last_attempt": 0.0}) assert sessions.tokens == [acct] # a sondat contul cu creds assert sessions.invalidated == [acct] # a fortat login real (nu token din cache) last = read_heartbeat(conn)["last_rar_login_ok"] age = (datetime.now(timezone.utc) - datetime.fromisoformat(last)).total_seconds() assert age < 60 # heartbeat reimprospatat de proba def test_login_proaspat_nu_sondeaza(env): """Login sub interval -> niciun login de proba.""" from app.worker.__main__ import _maybe_keepalive conn, settings = env settings.worker_rar_keepalive_interval_s = 86400 _account_cu_creds(conn) _set_last_login(conn, ago_s=3600) # 1h < 24h sessions = _FakeSessions(conn) _maybe_keepalive(conn, settings, sessions, {"last_attempt": 0.0}) assert sessions.tokens == [] def test_interval_zero_dezactivat(env): """interval=0 -> keepalive dezactivat, nicio proba chiar cu login vechi.""" from app.worker.__main__ import _maybe_keepalive conn, settings = env settings.worker_rar_keepalive_interval_s = 0 _account_cu_creds(conn) _set_last_login(conn, ago_s=100000) sessions = _FakeSessions(conn) _maybe_keepalive(conn, settings, sessions, {"last_attempt": 0.0}) assert sessions.tokens == [] def test_fara_creds_durabile_nu_sondeaza(env): """Niciun cont cu creds durabile + fara test-creds -> nimic de sondat.""" from app.worker.__main__ import _maybe_keepalive conn, settings = env settings.worker_rar_keepalive_interval_s = 86400 settings.worker_use_test_creds = False _set_last_login(conn, ago_s=100000) sessions = _FakeSessions(conn) _maybe_keepalive(conn, settings, sessions, {"last_attempt": 0.0}) assert sessions.tokens == [] def test_target_sare_creds_nedecriptabile(env): """Cont cu creds criptate sub alta cheie (decrypt -> None) e sarit; alege contul valid. Reproduce bug-ul real: start.sh both genereaza o cheie efemera noua la fiecare pornire, deci creds-urile durabile vechi nu se mai decripteaza. """ from app.worker.__main__ import _keepalive_target from app.accounts import create_account from app.crypto import encrypt_creds conn, settings = env settings.worker_use_test_creds = False # Cont cu creds GUNOI (nedecriptabile sub cheia curenta), id mai mic. bad = create_account(conn, "Cont Cheie Veche", email="old@example.com") conn.execute("UPDATE accounts SET rar_creds_enc=? WHERE id=?", ("gAAAAA-token-invalid", bad)) # Cont cu creds valide, id mai mare. good = create_account(conn, "Cont Valid", email="good@example.com") enc = encrypt_creds({"email": "good@example.com", "password": "pw"}) conn.execute("UPDATE accounts SET rar_creds_enc=? WHERE id=?", (enc, good)) conn.commit() acct_id, creds = _keepalive_target(conn, settings) assert acct_id == good # a sarit contul nedecriptabil assert creds and creds["email"] == "good@example.com" def test_gating_nu_hartuieste_pe_esec(env): """Pe esec (RAR jos) login-ul ramane vechi; a doua trecere imediata NU re-sondeaza.""" from app.worker.__main__ import _maybe_keepalive conn, settings = env settings.worker_rar_keepalive_interval_s = 86400 _account_cu_creds(conn) _set_last_login(conn, ago_s=100000) state = {"last_attempt": 0.0} sessions = _FakeSessions(conn, fail=True) _maybe_keepalive(conn, settings, sessions, state) # incearca, esueaza _maybe_keepalive(conn, settings, sessions, state) # gating: nu re-incearca assert sessions.tokens == [sessions.invalidated[0]] # o singura proba assert len(sessions.tokens) == 1