Files
rar-autopass/tests/test_plans.py
Claude Agent 44f261e269 refactor: comentarii strict functionale, fara referinte PRD/stories
Curatare globala a comentariilor si docstring-urilor (app, tools, teste,
scripturi): eliminate referintele la PRD-uri, US-xxx, task-uri istorice si
review-uri; pastrata doar informatia functionala, formulata scurt. Regula
adaugata in CLAUDE.md (sectiunea Stil). Fara modificari de cod sau comportament.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 13:48:38 +00:00

360 lines
13 KiB
Python

"""app/plans.py — definitia planurilor + helperi tier/consum."""
from __future__ import annotations
import os
import tempfile
from datetime import datetime, timedelta, timezone
import pytest
@pytest.fixture()
def conn(monkeypatch):
tmp = tempfile.mkdtemp()
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "test_plans.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()
# ---------------------------------------------------------------------------
# PLANS — sursa de adevar
# ---------------------------------------------------------------------------
def test_plan_definitii_free():
from app.plans import PLANS, FREE_MONTHLY_LIMIT
p = PLANS["free"]
assert p["monthly_limit"] == FREE_MONTHLY_LIMIT
assert p["monthly_limit"] == 60
assert p["api_access"] is False
assert p["label"] == "Gratuit"
def test_plan_definitii_standard():
from app.plans import PLANS
p = PLANS["standard"]
assert p["monthly_limit"] is None
assert p["api_access"] is False
assert "label" in p
def test_plan_definitii_pro():
from app.plans import PLANS
p = PLANS["pro"]
assert p["monthly_limit"] is None
assert p["api_access"] is True
assert "label" in p
def test_plan_definitii_premium():
from app.plans import PLANS
p = PLANS["premium"]
assert p["monthly_limit"] is None
assert p["api_access"] is True
assert "label" in p
def test_toate_tierurile_prezente():
from app.plans import PLANS
assert set(PLANS.keys()) == {"free", "standard", "pro", "premium"}
def test_consumed_statuses_exportata():
from app.plans import CONSUMED_STATUSES
assert "queued" in CONSUMED_STATUSES
assert "sending" in CONSUMED_STATUSES
assert "sent" in CONSUMED_STATUSES
# statusuri blocate nu se numara
assert "error" not in CONSUMED_STATUSES
assert "needs_mapping" not in CONSUMED_STATUSES
assert "needs_data" not in CONSUMED_STATUSES
def test_free_monthly_limit_constanta():
"""FREE_MONTHLY_LIMIT e o singura constanta (DRY), referita din PLANS."""
from app.plans import FREE_MONTHLY_LIMIT, PLANS
assert isinstance(FREE_MONTHLY_LIMIT, int)
assert FREE_MONTHLY_LIMIT == 60
# PLANS["free"]["monthly_limit"] refera aceeasi valoare (nu hardcodat separat)
assert PLANS["free"]["monthly_limit"] == FREE_MONTHLY_LIMIT
# ---------------------------------------------------------------------------
# effective_tier
# ---------------------------------------------------------------------------
def _now_utc():
return datetime.now(timezone.utc)
def test_effective_tier_trial_activ_returneaza_pro():
from app.plans import effective_tier
now = _now_utc()
trial_until = (now + timedelta(days=15)).strftime("%Y-%m-%d %H:%M:%S")
account = {"tier": "free", "trial_until": trial_until}
assert effective_tier(account, now) == "pro"
def test_effective_tier_trial_expirat_returneaza_tier_baza():
from app.plans import effective_tier
now = _now_utc()
trial_until = (now - timedelta(days=1)).strftime("%Y-%m-%d %H:%M:%S")
account = {"tier": "free", "trial_until": trial_until}
assert effective_tier(account, now) == "free"
def test_effective_tier_fara_trial_returneaza_tier():
from app.plans import effective_tier
now = _now_utc()
account = {"tier": "standard", "trial_until": None}
assert effective_tier(account, now) == "standard"
def test_effective_tier_plan_platit_nu_downgradat_de_trial_expirat():
"""Un cont pro setat de admin NU e downgradat de expirarea trial-ului."""
from app.plans import effective_tier
now = _now_utc()
# tier=pro, trial_until in trecut: downgrade nu se produce (pro > free)
trial_until = (now - timedelta(days=5)).strftime("%Y-%m-%d %H:%M:%S")
account = {"tier": "pro", "trial_until": trial_until}
# tier de baza e pro, deci effective = pro (nu se coboara la free)
assert effective_tier(account, now) == "pro"
def test_effective_tier_trial_malformat_fallback_defensiv():
from app.plans import effective_tier
now = _now_utc()
account = {"tier": "free", "trial_until": "nu-e-o-data-valida"}
# malformat -> fallback la tier de baza, fara exceptie
assert effective_tier(account, now) == "free"
def test_effective_tier_trial_null_fallback():
from app.plans import effective_tier
now = _now_utc()
account = {"tier": "free", "trial_until": None}
assert effective_tier(account, now) == "free"
def test_effective_tier_injectat_determinist():
"""now injectabil: putem simula orice moment — teste deterministe fara datetime.now()."""
from app.plans import effective_tier
# trial_until fix
trial_until = "2026-07-10 12:00:00"
account = {"tier": "free", "trial_until": trial_until}
# inainte de expirare
now_before = datetime(2026, 7, 5, 12, 0, 0, tzinfo=timezone.utc)
assert effective_tier(account, now_before) == "pro"
# dupa expirare
now_after = datetime(2026, 7, 15, 12, 0, 0, tzinfo=timezone.utc)
assert effective_tier(account, now_after) == "free"
def test_effective_tier_premium_cu_trial_pro():
"""premium are api_access=True oricum; trial_until viitor nu strica."""
from app.plans import effective_tier
now = _now_utc()
trial_until = (now + timedelta(days=10)).strftime("%Y-%m-%d %H:%M:%S")
account = {"tier": "premium", "trial_until": trial_until}
# trial activ -> 'pro', dar premium e oricum superior (nu ne intereseaza downgrade)
# functia intoarce 'pro' cand trial e activ; consumatorul vede pro (care are api_access)
assert effective_tier(account, now) == "pro"
# ---------------------------------------------------------------------------
# monthly_usage
# ---------------------------------------------------------------------------
def _uid():
"""Cheie idempotenta unica per apel (pentru INSERT in teste)."""
import binascii
return binascii.hexlify(os.urandom(8)).decode()
def _insert_submission(conn, account_id, status, created_at_str):
"""Insereaza o submisie de test cu timestamp explicit."""
conn.execute(
"INSERT INTO submissions (idempotency_key, account_id, status, payload_json, created_at) "
"VALUES (?, ?, ?, '{}', ?)",
(_uid(), account_id, status, created_at_str),
)
def test_consum_lunar_numara_consumed_statuses(conn):
from app.plans import monthly_usage
from app.accounts import create_account
now = _now_utc()
now_str = now.strftime("%Y-%m-%d %H:%M:%S")
account_id = create_account(conn, "Test Consum", cui="RO1001")
# 3 statusuri consumate
_insert_submission(conn, account_id, "queued", now_str)
_insert_submission(conn, account_id, "sending", now_str)
_insert_submission(conn, account_id, "sent", now_str)
assert monthly_usage(conn, account_id, now) == 3
def test_consum_lunar_exclude_statusuri_blocate(conn):
from app.plans import monthly_usage
from app.accounts import create_account
now = _now_utc()
now_str = now.strftime("%Y-%m-%d %H:%M:%S")
account_id = create_account(conn, "Test Blocat", cui="RO1002")
# statusuri care NU se numara
for status in ("error", "needs_mapping", "needs_data"):
_insert_submission(conn, account_id, status, now_str)
assert monthly_usage(conn, account_id, now) == 0
def test_consum_lunar_scoped_pe_cont(conn):
from app.plans import monthly_usage
from app.accounts import create_account
now = _now_utc()
now_str = now.strftime("%Y-%m-%d %H:%M:%S")
acct_a = create_account(conn, "Cont A", cui="RO1003")
acct_b = create_account(conn, "Cont B", cui="RO1004")
_insert_submission(conn, acct_a, "sent", now_str)
_insert_submission(conn, acct_a, "sent", now_str)
_insert_submission(conn, acct_b, "sent", now_str)
assert monthly_usage(conn, acct_a, now) == 2
assert monthly_usage(conn, acct_b, now) == 1
def test_consum_lunar_luna_trecuta_nu_se_numara(conn):
from app.plans import monthly_usage
from app.accounts import create_account
now = _now_utc()
account_id = create_account(conn, "Test Luna Trecuta", cui="RO1005")
# Calculam o data din luna trecuta (prima zi a lunii curente - 1 zi)
first_of_month = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
last_of_prev_month = first_of_month - timedelta(days=1)
prev_str = last_of_prev_month.strftime("%Y-%m-%d %H:%M:%S")
_insert_submission(conn, account_id, "sent", prev_str)
# luna curenta: 0
assert monthly_usage(conn, account_id, now) == 0
def test_consum_lunar_granita_luna_noua(conn):
"""Submisii la granita intre luni sunt bucketate corect (timp local RO = UTC in container)."""
from app.plans import monthly_usage
from app.accounts import create_account
now = _now_utc()
now_str = now.strftime("%Y-%m-%d %H:%M:%S")
account_id = create_account(conn, "Test Granita", cui="RO1006")
# Prima secunda a lunii curente (calculata consistent cu 'localtime' = UTC in container)
first_of_month = now.replace(day=1, hour=0, minute=0, second=1, microsecond=0)
first_str = first_of_month.strftime("%Y-%m-%d %H:%M:%S")
# Ultima secunda a lunii trecute
last_of_prev_month = first_of_month - timedelta(seconds=2)
prev_str = last_of_prev_month.strftime("%Y-%m-%d %H:%M:%S")
_insert_submission(conn, account_id, "sent", first_str) # luna curenta
_insert_submission(conn, account_id, "sent", prev_str) # luna trecuta
_insert_submission(conn, account_id, "sent", now_str) # luna curenta
assert monthly_usage(conn, account_id, now) == 2
def test_consum_lunar_zero_pe_cont_gol(conn):
from app.plans import monthly_usage
from app.accounts import create_account
now = _now_utc()
account_id = create_account(conn, "Cont Gol", cui="RO1007")
assert monthly_usage(conn, account_id, now) == 0
def test_consum_lunar_nu_numara_cross_account(conn):
"""Verificare scoping: contul default (id=1) nu influenteaza alt cont."""
from app.plans import monthly_usage
from app.accounts import create_account
now = _now_utc()
now_str = now.strftime("%Y-%m-%d %H:%M:%S")
account_id = create_account(conn, "Cont Izolat", cui="RO1008")
# Inseram pentru contul default (id=1)
_insert_submission(conn, 1, "sent", now_str)
_insert_submission(conn, 1, "sent", now_str)
# Contul nou nu trebuie sa numere al celor de pe id=1
assert monthly_usage(conn, account_id, now) == 0
assert monthly_usage(conn, 1, now) == 2
# ---------------------------------------------------------------------------
# Enforcement — logica de limita + kill-switch config
# ---------------------------------------------------------------------------
def test_volume_la_limita_exacta(conn):
"""La exact FREE_MONTHLY_LIMIT submissions, usage == limita (nu inca depasit).
Enforcer-ul verifica usage + nr_cerut > limit, deci la usage=60, nr_cerut=1 ->
61 > 60 -> respins; dar usage=60 in sine (inainte de cerere) e valid.
"""
from app.plans import monthly_usage, FREE_MONTHLY_LIMIT
from app.accounts import create_account
now = _now_utc()
now_str = now.strftime("%Y-%m-%d %H:%M:%S")
account_id = create_account(conn, "Test La Limita", cui="RO2001")
for _ in range(FREE_MONTHLY_LIMIT):
_insert_submission(conn, account_id, "queued", now_str)
conn.commit()
usage = monthly_usage(conn, account_id, now)
assert usage == FREE_MONTHLY_LIMIT, (
f"La limita exacta: asteptat {FREE_MONTHLY_LIMIT}, primit {usage}"
)
# Simulam logica enforcer: 1 cerere noua depaseste limita
assert usage + 1 > FREE_MONTHLY_LIMIT, "O cerere noua trebuia sa depaseasca limita"
# La 0 cereri noi: nu depaseste
assert usage + 0 <= FREE_MONTHLY_LIMIT, "La 0 cereri noi, limita nu e depasita"
def test_enforce_plans_config_default_true(monkeypatch):
"""AUTOPASS_ENFORCE_PLANS implicit True — enforcement activ de la deploy.
Nu exista conturi legacy; enforcement DUR activ implicit.
Kill-switch oprit explicit cand e necesar.
"""
from app.config import Settings
# Creem Settings fresh (fara env var setata) -> default True
monkeypatch.delenv("AUTOPASS_ENFORCE_PLANS", raising=False)
s = Settings()
assert s.enforce_plans is True, (
"AUTOPASS_ENFORCE_PLANS trebuia sa fie True implicit (enforcement activ din start)"
)
def test_enforce_plans_kill_switch_false(monkeypatch):
"""AUTOPASS_ENFORCE_PLANS=false dezactiveaza enforcement."""
from app.config import Settings
monkeypatch.setenv("AUTOPASS_ENFORCE_PLANS", "false")
s = Settings()
assert s.enforce_plans is False