Toate citirile pe coloana legacy accounts.rar_creds_enc mutate pe sloturile per-env (rar_creds_test_enc/rar_creds_prod_enc): worker fallback+keepalive, are_creds (web) si are_creds_rar (integrare, +are_creds_test/_prod), write-back API la reactivare, purjare la stergere cont, _get_acasa_context/_fetch_cont_env_state. Contract API (aditiv): POST /v1/conturi/rar-creds primeste rar_target optional (test/prod), scrie in slotul corect + activeaza mediul; DELETE primeste ?env (sterge un slot sau ambele). Documentat in docs/api-rar-contract.md. DROP cu garda in db.py (schema.sql fara coloana pe DB fresh): - 6a: eliminat ADD COLUMN rar_creds_enc (fara ping-pong re-ADD dupa DROP) - 6b: try/except fail-safe (nu crapa boot-ul) + garda sqlite_version >= 3.35 - 6c: re-backfill old->new imediat inainte de assert (ancora globala) - garda orfane: DROP anulat daca vreun creds legacy nu a aterizat in slot per-env - backup criptat accounts_rar_creds_enc_backup inainte de DROP - 6d: verificare prin PRAGMA table_info (NU grep — submissions are aceeasi coloana) Garda one-way, idempotenta la boot repetat (verificat). submissions.rar_creds_enc ramane neatinsa. tests/test_retragere_creds_enc.py: niciun read pe coloana veche, conturi rar-creds env-aware, are_creds per-env, DROP blocat de garda la lipsa copiere. 9 teste existente actualizate pe sloturi per-env. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
196 lines
7.3 KiB
Python
196 lines
7.3 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, rar_env=None) -> None:
|
|
self.invalidated.append(account_id)
|
|
|
|
def get_token(self, conn, account_id: int, creds, rar_env="test") -> 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:
|
|
"""Creeaza cont cu creds in slotul per-env (US-013 — legacy rar_creds_enc dropata)."""
|
|
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"})
|
|
# US-013: scrie in slotul per-env; rar_env din fixture = valoarea default (test sau prod).
|
|
# Folosim rar_creds_test_enc si rar_creds_prod_enc (ambele) pentru robustete.
|
|
conn.execute(
|
|
"UPDATE accounts SET rar_creds_test_enc=?, rar_test_enabled=1, "
|
|
"rar_creds_prod_enc=?, rar_prod_enabled=1 WHERE id=?",
|
|
(enc, 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")
|
|
# US-013: scrie in slotul per-env (rar_env = settings.rar_env, implicit in fixture)
|
|
bad_slot = f"rar_creds_{settings.rar_env}_enc"
|
|
conn.execute(f"UPDATE accounts SET {bad_slot}='gAAAAA-token-invalid' WHERE id=?", (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"})
|
|
good_slot = f"rar_creds_{settings.rar_env}_enc"
|
|
conn.execute(f"UPDATE accounts SET {good_slot}=? 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
|