feat(5.20): US-013 retragere accounts.rar_creds_enc -> per-env + DROP cu garda
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>
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
"""Teste T1: accounts.rar_creds_enc durabile + worker re-login fallback + gate purjare.
|
||||
"""Teste T1: creds durabile per-cont (per-env, US-013) + worker re-login fallback + gate purjare.
|
||||
|
||||
Verify:
|
||||
(a) Serie web, worker restart (sesiune goala), token expirat -> re-login din accounts -> trimite.
|
||||
(b) Coada MIXTA API(efemer)+web(durabil): dupa login web, submission-urile API tot se trimit
|
||||
(purjarea nu le-a rupt prematur).
|
||||
|
||||
US-013: accounts.rar_creds_enc (legacy) a fost dropata; sloturile per-env sunt singura sursa.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -81,20 +83,23 @@ def test_creds_from_account_fallback(env, monkeypatch):
|
||||
conn = get_connection()
|
||||
try:
|
||||
enc = encrypt_creds({"email": "web@test.ro", "password": "webpass"})
|
||||
conn.execute("UPDATE accounts SET rar_creds_enc=? WHERE id=1", (enc,))
|
||||
# US-013: scrie in slotul per-env test (slotul legacy rar_creds_enc dropat)
|
||||
conn.execute(
|
||||
"UPDATE accounts SET rar_creds_test_enc=?, rar_test_enabled=1 WHERE id=1", (enc,)
|
||||
)
|
||||
|
||||
# Submission web fara creds_enc (ex: dupa ce s-au purjat)
|
||||
_insert(conn, account_id=1, creds_enc=None)
|
||||
|
||||
# _creds_from_account trebuie sa returneze creds
|
||||
creds = w._creds_from_account(conn, 1)
|
||||
# _creds_from_account trebuie sa returneze creds din slotul per-env test
|
||||
creds = w._creds_from_account(conn, 1, rar_env="test")
|
||||
assert creds == {"email": "web@test.ro", "password": "webpass"}
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def test_creds_from_account_no_creds(env):
|
||||
"""Cont fara rar_creds_enc -> None (canal API pur, neatins)."""
|
||||
"""Cont fara creds durabile (niciun slot per-env) -> None (canal API pur, neatins)."""
|
||||
import app.worker.__main__ as w
|
||||
from app.db import get_connection
|
||||
|
||||
@@ -117,7 +122,10 @@ def test_worker_relogin_dupa_restart(env, monkeypatch):
|
||||
conn = get_connection()
|
||||
try:
|
||||
enc = encrypt_creds({"email": "web@test.ro", "password": "webpass"})
|
||||
conn.execute("UPDATE accounts SET rar_creds_enc=? WHERE id=1", (enc,))
|
||||
# US-013: scrie in slotul per-env test
|
||||
conn.execute(
|
||||
"UPDATE accounts SET rar_creds_test_enc=?, rar_test_enabled=1 WHERE id=1", (enc,)
|
||||
)
|
||||
|
||||
# Submission web fara creds (creds deja purjate de primul login)
|
||||
_insert(conn, account_id=1, creds_enc=None)
|
||||
@@ -126,8 +134,8 @@ def test_worker_relogin_dupa_restart(env, monkeypatch):
|
||||
sessions = w.AccountSessions(w.get_settings())
|
||||
assert sessions.get_token(conn, 1, None) is None # fara creds directe
|
||||
|
||||
# Creds din account -> login posibil
|
||||
creds = w._creds_from_account(conn, 1)
|
||||
# Creds din account (per-env) -> login posibil
|
||||
creds = w._creds_from_account(conn, 1, rar_env="test")
|
||||
assert creds is not None
|
||||
token = sessions.get_token(conn, 1, creds)
|
||||
assert token == "TOK-web@test.ro"
|
||||
@@ -141,10 +149,10 @@ def test_coada_mixta_api_web(env, monkeypatch):
|
||||
"""(b) Coada mixta: dupa login web, submission-urile API (efemere) tot se trimit.
|
||||
|
||||
Scenariul:
|
||||
1. S1 = submission API cu creds efemere in submission.rar_creds_enc
|
||||
2. S2 = submission WEB fara creds (foloseste accounts.rar_creds_enc)
|
||||
3. Login cu creds S1 -> purjare S1.rar_creds_enc -> OK (worker are token)
|
||||
4. S2 tot se poate procesa (creds din accounts)
|
||||
1. S1 = submission API cu creds efemere in submissions.rar_creds_enc
|
||||
2. S2 = submission WEB fara creds (foloseste accounts.rar_creds_test_enc per-env)
|
||||
3. Login cu creds S1 -> purjare S1.rar_creds_enc (pe submissions) -> OK (worker are token)
|
||||
4. S2 tot se poate procesa (creds din accounts per-env, US-013)
|
||||
"""
|
||||
import app.worker.__main__ as w
|
||||
from app.crypto import encrypt_creds
|
||||
@@ -154,11 +162,13 @@ def test_coada_mixta_api_web(env, monkeypatch):
|
||||
|
||||
conn = get_connection()
|
||||
try:
|
||||
# Creds durabile pentru contul web
|
||||
# Creds durabile pentru contul web (slotul per-env, US-013)
|
||||
enc_web = encrypt_creds({"email": "web@test.ro", "password": "webpass"})
|
||||
conn.execute("UPDATE accounts SET rar_creds_enc=? WHERE id=1", (enc_web,))
|
||||
conn.execute(
|
||||
"UPDATE accounts SET rar_creds_test_enc=?, rar_test_enabled=1 WHERE id=1", (enc_web,)
|
||||
)
|
||||
|
||||
# S1: canal API cu creds efemere
|
||||
# S1: canal API cu creds efemere in submission
|
||||
enc_api = encrypt_creds({"email": "api@test.ro", "password": "apipass"})
|
||||
s1 = _insert(conn, account_id=1, creds_enc=enc_api, key_suffix="api1")
|
||||
# S2: canal web fara creds in submission
|
||||
@@ -166,24 +176,24 @@ def test_coada_mixta_api_web(env, monkeypatch):
|
||||
|
||||
sessions = w.AccountSessions(w.get_settings())
|
||||
|
||||
# Procesare S1: login cu creds API -> purjare rar_creds_enc pe TOATE submission-urile contului
|
||||
# Procesare S1: login cu creds API -> purjare submissions.rar_creds_enc
|
||||
creds_s1 = w._creds_for({"creds_enc": enc_api}, w.get_settings())
|
||||
assert creds_s1 is not None
|
||||
sessions.get_token(conn, 1, creds_s1) # login + purjare
|
||||
sessions.get_token(conn, 1, creds_s1) # login + purjare submissions.rar_creds_enc
|
||||
|
||||
# Verifica purjarea: S1.rar_creds_enc = NULL acum
|
||||
# Verifica purjarea: S1.rar_creds_enc (submissions) = NULL acum
|
||||
row_s1 = conn.execute("SELECT rar_creds_enc FROM submissions WHERE id=?", (s1,)).fetchone()
|
||||
assert row_s1["rar_creds_enc"] is None, "creds efemere trebuie sterse dupa login"
|
||||
|
||||
# S2 nu mai are creds in submission (nici nu a avut); fallback la accounts
|
||||
creds_s2 = w._creds_for({"creds_enc": None}, w.get_settings()) or w._creds_from_account(conn, 1)
|
||||
# S2 nu mai are creds in submission (nici nu a avut); fallback la accounts per-env
|
||||
creds_s2 = w._creds_for({"creds_enc": None}, w.get_settings()) or w._creds_from_account(conn, 1, rar_env="test")
|
||||
assert creds_s2 == {"email": "web@test.ro", "password": "webpass"}, \
|
||||
"S2 trebuie sa ia creds din accounts.rar_creds_enc"
|
||||
"S2 trebuie sa ia creds din accounts.rar_creds_test_enc (per-env)"
|
||||
|
||||
# accounts.rar_creds_enc NU a fost sters de purjare
|
||||
row_acc = conn.execute("SELECT rar_creds_enc FROM accounts WHERE id=1").fetchone()
|
||||
assert row_acc["rar_creds_enc"] is not None, \
|
||||
"accounts.rar_creds_enc trebuie sa ramana dupa purjare submissions"
|
||||
# accounts.rar_creds_test_enc NU a fost atins de purjarea submissions
|
||||
row_acc = conn.execute("SELECT rar_creds_test_enc FROM accounts WHERE id=1").fetchone()
|
||||
assert row_acc["rar_creds_test_enc"] is not None, \
|
||||
"accounts.rar_creds_test_enc trebuie sa ramana dupa purjare submissions"
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
@@ -199,29 +209,37 @@ def client(env):
|
||||
|
||||
|
||||
def test_endpoint_set_rar_creds(client, env):
|
||||
"""POST /v1/conturi/rar-creds seteaza creds criptate in accounts."""
|
||||
"""POST /v1/conturi/rar-creds seteaza creds criptate in slotul per-env (US-013)."""
|
||||
from app.crypto import decrypt_creds
|
||||
from app.db import get_connection
|
||||
|
||||
# Fara rar_target -> ancora globala (test in fixture, AUTOPASS_RAR_ENV implicit)
|
||||
r = client.post("/v1/conturi/rar-creds", json={"email": "u@test.ro", "password": "pass123"})
|
||||
assert r.status_code == 200
|
||||
assert r.json()["ok"] is True
|
||||
# Raspunsul include rar_env folosit
|
||||
assert "rar_env" in r.json()
|
||||
|
||||
conn = get_connection()
|
||||
try:
|
||||
row = conn.execute("SELECT rar_creds_enc FROM accounts WHERE id=1").fetchone()
|
||||
assert row["rar_creds_enc"] is not None
|
||||
creds = decrypt_creds(row["rar_creds_enc"])
|
||||
# Cel putin unul din sloturi trebuie sa fie populat
|
||||
row = conn.execute(
|
||||
"SELECT rar_creds_test_enc, rar_creds_prod_enc FROM accounts WHERE id=1"
|
||||
).fetchone()
|
||||
enc = row["rar_creds_test_enc"] or row["rar_creds_prod_enc"]
|
||||
assert enc is not None, "Cel putin un slot per-env trebuia populat"
|
||||
creds = decrypt_creds(enc)
|
||||
assert creds == {"email": "u@test.ro", "password": "pass123"}
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def test_endpoint_delete_rar_creds(client, env):
|
||||
"""DELETE /v1/conturi/rar-creds sterge creds durabile."""
|
||||
# Mai intai seteaza
|
||||
client.post("/v1/conturi/rar-creds", json={"email": "u@test.ro", "password": "pass123"})
|
||||
# Sterge
|
||||
"""DELETE /v1/conturi/rar-creds sterge creds durabile (ambele sloturi per-env, US-013)."""
|
||||
# Mai intai seteaza pe test si prod
|
||||
client.post("/v1/conturi/rar-creds", json={"email": "u@test.ro", "password": "pass123", "rar_target": "test"})
|
||||
client.post("/v1/conturi/rar-creds", json={"email": "u@prod.ro", "password": "pass456", "rar_target": "prod"})
|
||||
# Sterge fara env -> ambele sloturi NULL
|
||||
r = client.delete("/v1/conturi/rar-creds")
|
||||
assert r.status_code == 200
|
||||
assert r.json()["ok"] is True
|
||||
@@ -229,14 +247,17 @@ def test_endpoint_delete_rar_creds(client, env):
|
||||
from app.db import get_connection
|
||||
conn = get_connection()
|
||||
try:
|
||||
row = conn.execute("SELECT rar_creds_enc FROM accounts WHERE id=1").fetchone()
|
||||
assert row["rar_creds_enc"] is None
|
||||
row = conn.execute(
|
||||
"SELECT rar_creds_test_enc, rar_creds_prod_enc FROM accounts WHERE id=1"
|
||||
).fetchone()
|
||||
assert row["rar_creds_test_enc"] is None, "rar_creds_test_enc trebuia sters"
|
||||
assert row["rar_creds_prod_enc"] is None, "rar_creds_prod_enc trebuia sters"
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def test_gate_purjare_nu_sterge_accounts(env, monkeypatch):
|
||||
"""Gate purjare T1: stergerea submissions.rar_creds_enc NU atinge accounts.rar_creds_enc."""
|
||||
"""Gate purjare T1: stergerea submissions.rar_creds_enc NU atinge accounts per-env (US-013)."""
|
||||
import app.worker.__main__ as w
|
||||
from app.crypto import encrypt_creds
|
||||
from app.db import get_connection
|
||||
@@ -246,15 +267,18 @@ def test_gate_purjare_nu_sterge_accounts(env, monkeypatch):
|
||||
conn = get_connection()
|
||||
try:
|
||||
enc = encrypt_creds({"email": "u@test.ro", "password": "p"})
|
||||
conn.execute("UPDATE accounts SET rar_creds_enc=? WHERE id=1", (enc,))
|
||||
# US-013: scrie in slotul per-env test
|
||||
conn.execute(
|
||||
"UPDATE accounts SET rar_creds_test_enc=?, rar_test_enabled=1 WHERE id=1", (enc,)
|
||||
)
|
||||
_insert(conn, account_id=1, creds_enc=enc)
|
||||
|
||||
sessions = w.AccountSessions(w.get_settings())
|
||||
sessions.get_token(conn, 1, {"email": "u@test.ro", "password": "p"})
|
||||
|
||||
# accounts.rar_creds_enc trebuie sa fie intact
|
||||
row = conn.execute("SELECT rar_creds_enc FROM accounts WHERE id=1").fetchone()
|
||||
assert row["rar_creds_enc"] is not None, \
|
||||
"gate purjare: accounts.rar_creds_enc trebuie sa ramana intact"
|
||||
# accounts.rar_creds_test_enc NU trebuie atins de purjarea submissions
|
||||
row = conn.execute("SELECT rar_creds_test_enc FROM accounts WHERE id=1").fetchone()
|
||||
assert row["rar_creds_test_enc"] is not None, \
|
||||
"gate purjare: accounts.rar_creds_test_enc trebuie sa ramana intact"
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
Reference in New Issue
Block a user