Files
rar-autopass/tests/test_worker_keepalive_rar.py
Claude Agent c05fa00007 fix(worker): keepalive RAR ca dashboard-ul sa nu afiseze fals "RAR inaccesibil"
Dashboard-ul deduce starea RAR din vechimea ultimului login reusit (>30h ->
"indisponibil?"). Cand coada e goala, worker-ul nu are de ce sa se logheze,
deci timestamp-ul devine stale si banner-ul "Blocat: RAR inaccesibil —
declaratiile NU pleaca" apare fals, desi RAR raspunde.

Worker-ul face acum un login de proba o data pe zi (interval configurabil,
24h < pragul de 30h) cand coada e goala: pe succes reimprospateaza
last_rar_login_ok; pe esec real last_rar_login_ok ramane vechi -> dashboard
degradeaza corect. Forteaza login real (invalideaza sesiunea) ca proba sa fie
autentica. Gating: cel mult o sondare pe interval, sa nu hartuiasca RAR jos.

_keepalive_target sare conturile ale caror creds NU se decripteaza sub cheia
curenta (start.sh both genereaza cheie efemera noua la fiecare pornire ->
creds durabile vechi dau decrypt None) si cade pe creds <test> in dev.

Teste: tests/test_worker_keepalive_rar.py (6).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 06:48:32 +00:00

186 lines
6.7 KiB
Python

"""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