"""Teste US-001 (PRD 3.1): coloana accounts.active + helper-e cont in app/accounts.py.""" 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_accounts.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 test_create_account_returneaza_id(conn): from app.accounts import create_account acct_id = create_account(conn, "Service X") assert isinstance(acct_id, int) # AUTOINCREMENT peste default id=1 -> primul cont creat are id>=2 (nu atinge default). assert acct_id >= 2 def test_create_account_activ_implicit(conn): from app.accounts import create_account acct_id = create_account(conn, "Service X") row = conn.execute("SELECT active FROM accounts WHERE id=?", (acct_id,)).fetchone() assert row["active"] == 1 def test_create_account_inactiv(conn): from app.accounts import create_account acct_id = create_account(conn, "Service X", active=False) row = conn.execute("SELECT active FROM accounts WHERE id=?", (acct_id,)).fetchone() assert row["active"] == 0 def test_create_account_name_gol_ridica_eroare(conn): from app.accounts import create_account with pytest.raises(ValueError): create_account(conn, " ") # nu a inserat nimic peste default n = conn.execute("SELECT COUNT(*) AS n FROM accounts").fetchone()["n"] assert n == 1 def test_create_account_cui_duplicat_respins(conn): from app.accounts import create_account first = create_account(conn, "Service A", cui="RO123") with pytest.raises(ValueError) as exc: create_account(conn, "Service B", cui="RO123") # mesaj cu cauza + fix care numeste contul existent (A4) msg = str(exc.value) assert "RO123" in msg assert str(first) in msg def test_create_cui_null_multiplu_permis(conn): from app.accounts import create_account a = create_account(conn, "Fara CUI 1") b = create_account(conn, "Fara CUI 2") assert a != b def test_create_cui_normalizat(conn): from app.accounts import create_account create_account(conn, "Service A", cui=" ro123 ") # normalizat la RO123 -> duplicat respins indiferent de spatii/caz with pytest.raises(ValueError): create_account(conn, "Service B", cui="RO123") row = conn.execute("SELECT cui FROM accounts WHERE name='Service A'").fetchone() assert row["cui"] == "RO123" def test_set_active_comuta(conn): from app.accounts import create_account, set_active acct_id = create_account(conn, "Service X") set_active(conn, acct_id, False) assert conn.execute("SELECT active FROM accounts WHERE id=?", (acct_id,)).fetchone()["active"] == 0 set_active(conn, acct_id, True) assert conn.execute("SELECT active FROM accounts WHERE id=?", (acct_id,)).fetchone()["active"] == 1 def test_set_active_idempotent(conn): from app.accounts import create_account, set_active acct_id = create_account(conn, "Service X") # deja activ set_active(conn, acct_id, True) # nu trebuie sa arunce assert conn.execute("SELECT active FROM accounts WHERE id=?", (acct_id,)).fetchone()["active"] == 1 def test_set_active_inexistent_ridica(conn): from app.accounts import set_active with pytest.raises(ValueError): set_active(conn, 9999, True) def test_list_accounts_ordonat_fara_creds(conn): from app.accounts import create_account, list_accounts create_account(conn, "Service B") create_account(conn, "Service A") rows = list_accounts(conn) ids = [r["id"] for r in rows] assert ids == sorted(ids) for r in rows: assert "rar_creds_enc" not in r assert set(r.keys()) == {"id", "name", "cui", "email", "active", "status", "created_at", "tier", "trial_until"} # --------------------------------------------------------------------------- # US-001 (PRD 5.12): accounts.email + validari companie/email/CUI # --------------------------------------------------------------------------- def test_create_account_fara_email_ridica(conn): """create_account cu email="" ridica ValueError (email gol nu e acceptat).""" from app.accounts import create_account with pytest.raises(ValueError, match="email"): create_account(conn, "Service X", cui="RO100", email="") def test_create_account_fara_cui_ridica(conn): """create_account cu cui="" ridica ValueError (CUI gol nu e acceptat).""" from app.accounts import create_account with pytest.raises(ValueError, match="[Cc][Uu][Ii]|cod unic"): create_account(conn, "Service X", cui="", email="test@test.com") def test_email_normalizat_lowercase_trim(conn): """email e normalizat: trim + lower.""" from app.accounts import create_account acct_id = create_account(conn, "Service X", cui="RO200", email=" Test@EXAMPLE.Com ") row = conn.execute("SELECT email FROM accounts WHERE id=?", (acct_id,)).fetchone() assert row["email"] == "test@example.com" def test_migrare_adauga_coloana_email_idempotent(conn): """_migrate e idempotent: ruleaza de doua ori fara eroare si coloana email exista.""" from app.db import _migrate _migrate(conn) # a doua rulare (prima e in init_db) cols = {r["name"] for r in conn.execute("PRAGMA table_info(accounts)").fetchall()} assert "email" in cols def test_account_is_complete_false_pe_legacy_incomplet(conn): """account_is_complete() returneaza False pe cont fara email sau fara CUI.""" from app.accounts import create_account, account_is_complete # cont fara email si fara CUI acct_id = create_account(conn, "Service Legacy") row = conn.execute("SELECT * FROM accounts WHERE id=?", (acct_id,)).fetchone() assert account_is_complete(row) is False # cont fara email, cu CUI acct_id2 = create_account(conn, "Service Cu CUI", cui="RO300") row2 = conn.execute("SELECT * FROM accounts WHERE id=?", (acct_id2,)).fetchone() assert account_is_complete(row2) is False # cont complet (cu email si CUI si name) acct_id3 = create_account(conn, "Service Complet", cui="RO301", email="x@y.com") row3 = conn.execute("SELECT * FROM accounts WHERE id=?", (acct_id3,)).fetchone() assert account_is_complete(row3) is True # contul sistem id=1 e EXCEPTAT (returneaza True indiferent) row_sys = conn.execute("SELECT * FROM accounts WHERE id=1").fetchone() assert account_is_complete(row_sys) is True # --------------------------------------------------------------------------- # 5.17 US-001/US-008: schema tier + trial_until + set_tier + CLI set-tier # --------------------------------------------------------------------------- def test_migrare_tier_trial_defensiva(conn): """_migrate adauga tier + trial_until pe conturi existente, e idempotenta.""" from app.db import _migrate cols_before = {r["name"] for r in conn.execute("PRAGMA table_info(accounts)").fetchall()} assert "tier" in cols_before assert "trial_until" in cols_before # a doua rulare: idempotenta (nu arunca) _migrate(conn) cols_after = {r["name"] for r in conn.execute("PRAGMA table_info(accounts)").fetchall()} assert "tier" in cols_after assert "trial_until" in cols_after def test_cont_nou_tier_free_si_trial_30z(conn): """create_account seteaza tier='free' si trial_until = acum + ~30 zile.""" from datetime import datetime, timezone, timedelta from app.accounts import create_account before = datetime.now(timezone.utc) acct_id = create_account(conn, "Service Trial", cui="RO_T1") after = datetime.now(timezone.utc) row = conn.execute("SELECT tier, trial_until FROM accounts WHERE id=?", (acct_id,)).fetchone() assert row["tier"] == "free" assert row["trial_until"] is not None tu = datetime.fromisoformat(row["trial_until"].replace(" ", "T")) if tu.tzinfo is None: tu = tu.replace(tzinfo=timezone.utc) # trial_until trebuie sa fie intre now+29z si now+31z assert tu >= before + timedelta(days=29) assert tu <= after + timedelta(days=31) def test_cont_nou_effective_tier_pro_in_trial(conn): """Cont nou are effective_tier='pro' (trial activ).""" from datetime import datetime, timezone from app.accounts import create_account from app.plans import effective_tier acct_id = create_account(conn, "Service Pro Trial", cui="RO_T2") row = conn.execute("SELECT * FROM accounts WHERE id=?", (acct_id,)).fetchone() now = datetime.now(timezone.utc) assert effective_tier(row, now) == "pro" def test_set_tier_valid(conn): """set_tier seteaza tier-ul corect.""" from app.accounts import create_account, set_tier acct_id = create_account(conn, "Service Tier", cui="RO_T3") set_tier(conn, acct_id, "pro") row = conn.execute("SELECT tier FROM accounts WHERE id=?", (acct_id,)).fetchone() assert row["tier"] == "pro" def test_set_tier_cu_trial(conn): """set_tier cu trial_until seteaza ambele campuri.""" from app.accounts import create_account, set_tier acct_id = create_account(conn, "Service Tier Trial", cui="RO_T4") set_tier(conn, acct_id, "standard", trial_until="2026-12-31 00:00:00") row = conn.execute("SELECT tier, trial_until FROM accounts WHERE id=?", (acct_id,)).fetchone() assert row["tier"] == "standard" assert row["trial_until"] == "2026-12-31 00:00:00" def test_set_tier_no_trial_sterge_trial_until(conn): """set_tier cu trial_until=None sterge trial-ul existent.""" from app.accounts import create_account, set_tier acct_id = create_account(conn, "Service No Trial", cui="RO_T5") # mai intai setam un trial set_tier(conn, acct_id, "pro", trial_until="2026-12-31 00:00:00") # acum stergem trial-ul set_tier(conn, acct_id, "pro", trial_until=None) row = conn.execute("SELECT tier, trial_until FROM accounts WHERE id=?", (acct_id,)).fetchone() assert row["tier"] == "pro" assert row["trial_until"] is None def test_set_tier_invalid_respins(conn): """set_tier cu tier invalid ridica ValueError cu mesaj clar.""" from app.accounts import create_account, set_tier acct_id = create_account(conn, "Service Tier Invalid", cui="RO_T6") with pytest.raises(ValueError, match="tier invalid"): set_tier(conn, acct_id, "gold") def test_set_tier_protejeaza_id1(conn): """set_tier pe contul de sistem id=1 ridica ValueError (protejat).""" from app.accounts import set_tier with pytest.raises(ValueError): set_tier(conn, 1, "pro") def test_set_tier_cont_inexistent_ridica(conn): """set_tier pe cont inexistent ridica ValueError.""" from app.accounts import set_tier with pytest.raises(ValueError, match="inexistent"): set_tier(conn, 9999, "pro") def test_list_accounts_include_tier_trial(conn): """list_accounts include coloanele tier si trial_until.""" from app.accounts import create_account, list_accounts create_account(conn, "Service List", cui="RO_L1") rows = list_accounts(conn) for r in rows: assert "tier" in r assert "trial_until" in r def test_default_account_tier_free_fara_trial(conn): """Contul implicit id=1 (creat de schema) are tier='free' si trial_until=NULL.""" row = conn.execute("SELECT tier, trial_until FROM accounts WHERE id=1").fetchone() assert row["tier"] == "free" assert row["trial_until"] is None def test_cli_set_tier(monkeypatch): """CLI set-tier seteaza tier-ul unui cont (--no-trial).""" tmp = tempfile.mkdtemp() monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "test_cli_tier.db")) from app.config import get_settings get_settings.cache_clear() from tools.account import main from app.db import init_db, get_connection from app.accounts import create_account init_db() conn_tmp = get_connection() acct_id = create_account(conn_tmp, "CLI Test", cui="RO_CLI1") conn_tmp.close() rc = main(["set-tier", "--account", str(acct_id), "--tier", "pro", "--no-trial"]) assert rc == 0 conn_tmp2 = get_connection() row = conn_tmp2.execute("SELECT tier, trial_until FROM accounts WHERE id=?", (acct_id,)).fetchone() conn_tmp2.close() assert row["tier"] == "pro" assert row["trial_until"] is None get_settings.cache_clear() def test_cli_set_tier_cu_trial_days(monkeypatch): """CLI set-tier cu --trial-days 14 seteaza trial_until = acum + 14z.""" from datetime import datetime, timezone, timedelta tmp = tempfile.mkdtemp() monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "test_cli_tier2.db")) from app.config import get_settings get_settings.cache_clear() from tools.account import main from app.db import init_db, get_connection from app.accounts import create_account init_db() conn_tmp = get_connection() acct_id = create_account(conn_tmp, "CLI Trial", cui="RO_CLI2") conn_tmp.close() before = datetime.now(timezone.utc) rc = main(["set-tier", "--account", str(acct_id), "--tier", "pro", "--trial-days", "14"]) assert rc == 0 after = datetime.now(timezone.utc) conn_tmp2 = get_connection() row = conn_tmp2.execute("SELECT tier, trial_until FROM accounts WHERE id=?", (acct_id,)).fetchone() conn_tmp2.close() assert row["tier"] == "pro" assert row["trial_until"] is not None tu = datetime.fromisoformat(row["trial_until"].replace(" ", "T")).replace(tzinfo=timezone.utc) assert tu >= before + timedelta(days=13) assert tu <= after + timedelta(days=15) get_settings.cache_clear() def test_cli_set_tier_invalid(monkeypatch): """CLI set-tier cu tier invalid: exit code != 0.""" tmp = tempfile.mkdtemp() monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "test_cli_tier3.db")) from app.config import get_settings get_settings.cache_clear() from tools.account import main from app.db import init_db, get_connection from app.accounts import create_account init_db() conn_tmp = get_connection() acct_id = create_account(conn_tmp, "CLI Invalid", cui="RO_CLI3") conn_tmp.close() rc = main(["set-tier", "--account", str(acct_id), "--tier", "diamond"]) assert rc != 0 get_settings.cache_clear()