Aduce toate suprafetele dashboard-ului la grila tabelului Trimiteri, muta
navigarea intr-un meniu de cont (hamburger) si da panoului admin actiuni
reale de ciclu de viata. 9 stories, 3 valuri. UI pur (reskin + reasezare)
cu O SINGURA exceptie backend: modelul de stare a contului.
- US-001 sectiunea "Ajutor" eliminata din Acasa (wayfinding redundant).
- US-002 Nomenclator la grila standard (_submissions.html ca referinta).
- US-003 macro autosend compact (Manual<->Auto). Semantica de PREZENTA
`auto_send` (bifat->true, absent->false) NEALTERATA — compatibil cu ambele
parsere (Form(bool) la /mapari, bool(form.get()) la import). Zero backend.
- US-004 accounts.status (pending/active/blocked/archived/deleted), migrare
defensiva idempotenta derivata din `active`, gate worker claim_one pe
status='active' (echivalenta active=1 <=> status='active' pastrata).
- US-005 tabel Mapari compact + panou Ajutor (<details>, proza o singura data),
coloana "In coada".
- US-006 meniu hamburger dropdown (Cont/Integrare/Nomenclator/Admin/logout) +
context is_authenticated/is_admin/csrf_token defensiv in base.html.
- US-007 tab-bar redus la Acasa+Mapari; rutele /_fragments/{cont,integrare,
nomenclator} + deep-link ?tab= raman valide.
- US-008 rute admin block/archive/delete + bulk pe lista account_id,
require_admin + CSRF + PRG, dev id=1 sarit in bulk.
- US-009 admin UI: selectie bife + master + bara bulk + kebab per-rand,
grupare pe stare (bloc nou blocate/arhivate), nota "cont dev implicit" scoasa.
Stergere = SOFT: tombstone (status='deleted'), dar PII purjata IMEDIAT
(rar_creds_enc + chei API revocate + CUI eliberat pentru re-inregistrare),
GDPR/L.142.
VERIFY: 671 teste pass (+40). E2E browser (Playwright) a prins 2 bug-uri
invizibile la TestClient: bara bulk cu display:flex inline invingea [hidden]
(mutat in CSS .bulk-bar[hidden]); conturi arhivate cadeau sub "in asteptare"
(grupare pe status). /code-review high a prins 2 bug-uri reale: soft delete
pastra creds RAR + CUI la nesfarsit fara purjare accounts (GDPR neonorat);
apostrof in numele firmei rupea confirm() inline din kebab — ambele reparate,
plus cleanup boilerplate rute (_lifecycle_route).
Backend trimitere (worker masina stari/idempotenta/mapping) neatins, cu
exceptia gate-ului de cont. Design: docs/design/5.5-uniformizare-ui.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
161 lines
6.0 KiB
Python
161 lines
6.0 KiB
Python
"""Teste US-004 (PRD 5.5): model de stare a contului `accounts.status` + helperi.
|
|
|
|
Acopera: derivarea status din active la migrare, invariantul active=1 <=> status='active',
|
|
helperii set_status/delete_account, protectia contului de sistem id=1, excluderea 'deleted'
|
|
din listare.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import tempfile
|
|
|
|
import pytest
|
|
|
|
|
|
@pytest.fixture()
|
|
def conn(monkeypatch):
|
|
tmp = tempfile.mkdtemp()
|
|
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "test_status.db"))
|
|
from app.config import get_settings
|
|
get_settings.cache_clear()
|
|
from app.db import get_connection, init_db
|
|
init_db()
|
|
c = get_connection()
|
|
yield c
|
|
c.close()
|
|
get_settings.cache_clear()
|
|
|
|
|
|
def _status(conn, acct_id):
|
|
return conn.execute("SELECT status, active FROM accounts WHERE id=?", (acct_id,)).fetchone()
|
|
|
|
|
|
def test_create_account_activ_status_active(conn):
|
|
from app.accounts import create_account
|
|
acct_id = create_account(conn, "Service X", active=True)
|
|
row = _status(conn, acct_id)
|
|
assert row["status"] == "active" and row["active"] == 1
|
|
|
|
|
|
def test_create_account_inactiv_status_pending(conn):
|
|
from app.accounts import create_account
|
|
acct_id = create_account(conn, "Service Y", active=False)
|
|
row = _status(conn, acct_id)
|
|
assert row["status"] == "pending" and row["active"] == 0
|
|
|
|
|
|
def test_default_account_id1_active(conn):
|
|
row = _status(conn, 1)
|
|
assert row["status"] == "active" and row["active"] == 1
|
|
|
|
|
|
def test_set_status_mentine_invariant_active(conn):
|
|
from app.accounts import create_account, set_status
|
|
acct_id = create_account(conn, "Service Z", active=True)
|
|
for st, exp_active in [("blocked", 0), ("archived", 0), ("active", 1), ("pending", 0)]:
|
|
set_status(conn, acct_id, st)
|
|
row = _status(conn, acct_id)
|
|
assert row["status"] == st and row["active"] == exp_active
|
|
|
|
|
|
def test_set_status_invalid_ridica(conn):
|
|
from app.accounts import create_account, set_status
|
|
acct_id = create_account(conn, "Service W", active=True)
|
|
with pytest.raises(ValueError):
|
|
set_status(conn, acct_id, "inexistent")
|
|
|
|
|
|
def test_set_status_cont_inexistent_ridica(conn):
|
|
from app.accounts import set_status
|
|
with pytest.raises(ValueError):
|
|
set_status(conn, 9999, "blocked")
|
|
|
|
|
|
def test_set_active_mirror_status(conn):
|
|
from app.accounts import create_account, set_active
|
|
acct_id = create_account(conn, "Service M", active=True)
|
|
set_active(conn, acct_id, False)
|
|
assert _status(conn, acct_id)["status"] == "pending"
|
|
set_active(conn, acct_id, True)
|
|
assert _status(conn, acct_id)["status"] == "active"
|
|
|
|
|
|
def test_delete_account_soft(conn):
|
|
from app.accounts import create_account, delete_account, list_accounts
|
|
acct_id = create_account(conn, "Service D", active=True)
|
|
delete_account(conn, acct_id)
|
|
assert _status(conn, acct_id)["status"] == "deleted"
|
|
# exclus din listare
|
|
assert all(a["id"] != acct_id for a in list_accounts(conn))
|
|
|
|
|
|
def test_delete_purjeaza_pii_si_elibereaza_cui(conn):
|
|
"""Stergerea soft purjeaza creds RAR + revoca cheile API + elibereaza CUI (re-inregistrabil)."""
|
|
from app.accounts import create_account, delete_account, list_accounts
|
|
acct_id = create_account(conn, "Service GDPR", cui="RO12345", active=True)
|
|
conn.execute("UPDATE accounts SET rar_creds_enc='secret_enc' WHERE id=?", (acct_id,))
|
|
conn.execute("INSERT INTO api_keys (account_id, key_hash, active) VALUES (?, 'h', 1)", (acct_id,))
|
|
conn.commit()
|
|
|
|
delete_account(conn, acct_id)
|
|
|
|
row = conn.execute("SELECT status, rar_creds_enc, cui FROM accounts WHERE id=?",
|
|
(acct_id,)).fetchone()
|
|
assert row["status"] == "deleted"
|
|
assert row["rar_creds_enc"] is None, "creds RAR trebuie purjate la stergere"
|
|
assert row["cui"] is None, "CUI trebuie eliberat la stergere"
|
|
key_active = conn.execute("SELECT active FROM api_keys WHERE account_id=?", (acct_id,)).fetchone()
|
|
assert key_active["active"] == 0, "cheile API trebuie revocate"
|
|
# CUI eliberat -> se poate re-inregistra acelasi CUI
|
|
new_id = create_account(conn, "Service Nou", cui="RO12345", active=True)
|
|
assert new_id != acct_id
|
|
assert all(a["id"] != acct_id for a in list_accounts(conn))
|
|
|
|
|
|
def test_dev_id1_protejat_de_status_negativ(conn):
|
|
from app.accounts import set_status, delete_account
|
|
for verb in ("blocked", "archived", "deleted"):
|
|
with pytest.raises(ValueError):
|
|
set_status(conn, 1, verb)
|
|
with pytest.raises(ValueError):
|
|
delete_account(conn, 1)
|
|
# ramane activ
|
|
assert _status(conn, 1)["status"] == "active"
|
|
|
|
|
|
def test_migrare_deriva_status_din_active(conn):
|
|
"""DB veche fara coloana status -> _migrate o adauga si o deriva din active.
|
|
|
|
Pornim de la schema reala (fixtura `conn` a rulat init_db), reconstruim tabela accounts
|
|
FARA coloana status (simuleaza DB pre-5.5), apoi rulam _migrate.
|
|
"""
|
|
from app.db import _migrate
|
|
|
|
# Reconstruim accounts fara `status` (rebuild de tabela — singura cale in SQLite vechi).
|
|
conn.executescript(
|
|
"""
|
|
PRAGMA foreign_keys=OFF;
|
|
CREATE TABLE accounts_legacy (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, cui TEXT,
|
|
active INTEGER NOT NULL DEFAULT 1, rar_creds_enc TEXT, created_at TEXT
|
|
);
|
|
INSERT INTO accounts_legacy (id, name, cui, active, rar_creds_enc, created_at)
|
|
SELECT id, name, cui, active, rar_creds_enc, created_at FROM accounts;
|
|
DROP TABLE accounts;
|
|
ALTER TABLE accounts_legacy RENAME TO accounts;
|
|
"""
|
|
)
|
|
conn.execute("INSERT INTO accounts (name, active) VALUES ('Activ', 1)")
|
|
conn.execute("INSERT INTO accounts (name, active) VALUES ('Inactiv', 0)")
|
|
conn.commit()
|
|
assert "status" not in {r["name"] for r in conn.execute("PRAGMA table_info(accounts)")}
|
|
|
|
_migrate(conn)
|
|
conn.commit()
|
|
|
|
rows = {r["name"]: r["status"] for r in conn.execute("SELECT name, status FROM accounts")}
|
|
assert rows["default"] == "active" # id=1
|
|
assert rows["Activ"] == "active"
|
|
assert rows["Inactiv"] == "pending"
|