"""DB-backed tests for quota transaction ownership: quota accounting writes in its OWN session, so a spend is DURABLE even when the calling job rolls back (the units were really consumed at YouTube), and it NEVER commits the caller's session (the old behaviour flushed a job's partial state mid-run behind its own rollback). Uses the DB lane (`db` fixture) — skipped without Postgres. """ from sqlalchemy import func, select from app import quota from app.models import QuotaEvent, User def test_record_usage_survives_caller_rollback_and_leaves_caller_uncommitted(db): # The caller has uncommitted work of its own; then a YouTube call spends quota; then the caller # rolls back (a failed job). db.add(User(email="pending@example.com")) # caller's own, never committed quota.record_usage(10) # dedicated session — commits independently db.rollback() # the job fails and rolls back # Quota spend PERSISTED (real units were consumed) — the guard stays accurate ... assert quota.units_used_today(db) == 10 assert db.scalar(select(func.count()).select_from(QuotaEvent)) == 1 # ... but the caller's own partial work did NOT leak (quota never committed the caller's session). assert ( db.scalar(select(func.count()).select_from(User).where(User.email == "pending@example.com")) == 0 ) def test_record_usage_accumulates(db): quota.record_usage(10) quota.record_usage(5) assert quota.units_used_today(db) == 15 # atomic upsert: +10 then +5 def test_reads_see_separate_session_spend_mid_transaction(db): # Mimics measured()'s before/after diff: a read, a spend (in quota's SEPARATE session), a read. # The second read must reflect the spend even though the caller's own transaction is still open — # units_used_today is a scalar SELECT (READ COMMITTED sees other sessions' commits), NOT a cached # ORM row that db.get would return stale. before = quota.units_used_today(db) quota.record_usage(7) after = quota.units_used_today(db) assert after - before == 7 def test_a_claimed_slot_survives_caller_rollback(db): user = User(email="searcher@example.com") db.add(user) db.commit() # a committed user to attribute the (free) action to db.add(User(email="pending@example.com")) # uncommitted caller work assert quota.claim_action(user.id, quota.QuotaAction.VIDEOS_SEARCH, 5) is not None db.rollback() # The zero-cost claim event survived (per-user daily caps must still count it) ... events = db.execute(select(QuotaEvent)).scalars().all() assert len(events) == 1 assert events[0].user_id == user.id assert events[0].units == 0 # ... and the caller's uncommitted user is gone. assert db.scalar(select(func.count()).select_from(User)) == 1 def test_backfill_reserve_reached_tracks_the_boundary(db): # The shared yield gate for every backfill/explore/revalidation path: True once today's remaining # budget is at or below the backfill reserve. It reads in its OWN session, so it must reflect # record_usage's independently-committed spend — and the boundary is inclusive (<=). Defaults: # quota_daily_budget 9000, backfill_quota_reserve 2000 -> reached once 7000 units are used. assert quota.backfill_reserve_reached() is False # 0 used, remaining 9000 quota.record_usage(6999) # remaining 2001, just above the reserve assert quota.backfill_reserve_reached() is False quota.record_usage(1) # remaining 2000, exactly at the reserve assert quota.backfill_reserve_reached() is True # boundary is <=, so the reserve is reached