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.
This commit is contained in:
2026-10-25 02:50:34 +02:00
parent 005784e291
commit ed51f5be7e
6 changed files with 133 additions and 55 deletions
+3 -3
View File
@@ -44,15 +44,15 @@ def test_reads_see_separate_session_spend_mid_transaction(db):
assert after - before == 7
def test_log_action_survives_caller_rollback(db):
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
quota.log_action(user.id, quota.QuotaAction.VIDEOS_SEARCH)
assert quota.claim_action(user.id, quota.QuotaAction.VIDEOS_SEARCH, 5) is not None
db.rollback()
# The zero-cost action event survived (per-user daily caps must still count it) ...
# 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