Files
siftlode/backend/tests/test_quota_ownership.py
T
peter ed51f5be7e fix(search): the API-sourced live search takes its slot under the lock too
Checked first and logged after search.list, requests sent together at the
last free slot all passed, and each spent 100 units of the shared budget.
Every live search now claims its slot (quota.claim_action) whatever the
source, and gives it back when it showed nothing. The cap counts the
zero-unit claims only, so search.list's own spend event is accounting,
not a second slot. log_action, the unlocked writer, goes.

Tests: the API source reads one page per request, is refused before the
call when the budget cannot pay, counts one slot per search and refunds a
failed one; the lock test now proves the count happens after the lock.
2026-10-25 02:50:34 +02:00

74 lines
3.5 KiB
Python

"""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