Files
rar-autopass/tests/test_deploy.py
Claude Agent 6fb92466cb feat(T6/T7): supervizare worker (healthcheck+autoheal) + backup online + cheie partajata
T6 — worker supravegheat:
- app/worker/healthcheck.py: probe pe heartbeat-ul din DB (beat invechit -> exit 1).
  Prinde worker-ul agatat (proces viu, beat inghetat) pe care restart:always nu-l
  vede. Cablat ca healthcheck pe serviciul worker in compose.
- sidecar autoheal: restarteaza efectiv containerul unhealthy (compose simplu doar
  marcheaza, nu restarteaza la unhealthy).

T7 — deploy:
- tools/backup.py: backup ONLINE via Connection.backup (WAL nu se copiaza sigur cu
  cp); --keep N roteste snapshot-urile.
- .env.example documenteaza env-urile; volum persistent numit deja in compose.

Fix critic (split api/worker in 2 containere): AUTOPASS_CREDS_KEY trebuie PARTAJATA
api<->worker, altfel worker nu decripteaza creds-urile criptate de API -> submission
blocate. Acum impusa in compose (${...:?} -> fail explicit daca lipseste).
.gitignore: exceptie !.env.example.

5 teste noi (tests/test_deploy.py). 100 pass total.

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

118 lines
3.5 KiB
Python

"""Teste T6/T7: liveness probe worker + backup online SQLite."""
from __future__ import annotations
import os
import sqlite3
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()
yield get_connection, get_settings()
get_settings.cache_clear()
# --------------------------------------------------------------------------- #
# T6 — worker liveness probe #
# --------------------------------------------------------------------------- #
def _set_beat(conn, dt: datetime | None):
conn.execute(
"UPDATE worker_heartbeat SET last_beat=? WHERE id=1",
(dt.isoformat(timespec="seconds") if dt else None,),
)
def test_healthy_when_beat_fresh(env):
from app.worker.healthcheck import worker_healthy
get_connection, settings = env
conn = get_connection()
try:
_set_beat(conn, datetime.now(timezone.utc))
assert worker_healthy(conn, settings) is True
finally:
conn.close()
def test_unhealthy_when_beat_stale(env):
from app.worker.healthcheck import worker_healthy
get_connection, settings = env
conn = get_connection()
try:
stale = datetime.now(timezone.utc) - timedelta(seconds=settings.worker_heartbeat_stale_s + 60)
_set_beat(conn, stale)
assert worker_healthy(conn, settings) is False
finally:
conn.close()
def test_unhealthy_when_never_started(env):
from app.worker.healthcheck import worker_healthy
get_connection, settings = env
conn = get_connection()
try:
_set_beat(conn, None) # never started
assert worker_healthy(conn, settings) is False
finally:
conn.close()
# --------------------------------------------------------------------------- #
# T7 — backup online #
# --------------------------------------------------------------------------- #
def test_backup_db_roundtrip(env):
from tools.backup import backup_db
get_connection, settings = env
conn = get_connection()
try:
conn.execute(
"INSERT INTO submissions (idempotency_key, status, payload_json) VALUES ('bk1','queued','{}')"
)
finally:
conn.close()
dest = settings.db_path.parent / "backups" / "snap.db"
out = backup_db(settings.db_path, dest)
assert out.exists()
# Copia contine randul scris (backup consistent, nu fisier gol).
bk = sqlite3.connect(out)
try:
n = bk.execute("SELECT COUNT(*) FROM submissions WHERE idempotency_key='bk1'").fetchone()[0]
finally:
bk.close()
assert n == 1
def test_backup_prune_keeps_newest(env):
from tools.backup import prune
get_connection, settings = env
bdir = settings.db_path.parent / "backups"
bdir.mkdir(parents=True, exist_ok=True)
# Creeaza 5 snapshot-uri cu nume-timestamp crescator.
names = [f"autopass-2026010{i}-000000.db" for i in range(1, 6)]
for n in names:
(bdir / n).write_bytes(b"x")
removed = prune(bdir, keep=2)
remaining = sorted(p.name for p in bdir.glob("autopass-*.db"))
assert len(remaining) == 2
assert remaining == names[-2:] # cele mai noi doua
assert len(removed) == 3