fix(search): a search that never reached YouTube gets its slot back, and a busy one waits a bounded time

The slot was kept as soon as search.list was about to be called, so a search refused before it went out (no connection, no token) cost the user a slot for nothing. It is kept now only once the client recorded the call's units, or the request went out and its answer never came (YouTube may have counted it): YouTubeError says whether a transport failure was sent, and the client counts what it spent.

The process lock around the budget check and the call is waited for at most ten seconds; past that the search is refused as busy (search_busy, a retry can help) and gets its slot back, instead of parking a request worker behind every slow call before it. The lock test now pins one hold around both steps, and the busy test fails on a wait without a bound.
This commit is contained in:
2026-10-25 13:51:25 +01:00
parent 84db570951
commit 2e1934689f
6 changed files with 196 additions and 23 deletions
+42
View File
@@ -193,3 +193,45 @@ def test_any_other_token_refusal_keeps_the_grant(monkeypatch):
c._access_token()
assert tok.scopes == "s"
assert refusal_reason(caught.value) == "unavailable"
@pytest.mark.parametrize(
("failure", "sent"),
[
(httpx.ConnectError("refused"), False),
(httpx.ConnectTimeout("slow connect"), False),
(httpx.ProxyError("proxy down"), False),
(httpx.ReadTimeout("no answer"), True),
(httpx.RemoteProtocolError("dropped"), True),
],
)
def test_a_transport_failure_says_whether_the_request_went_out(failure, sent):
"""A search that never reached YouTube spent nothing and gets its slot back; one whose answer
never came may have been counted, and keeps it (`routes/search`)."""
def request(*_a, **_k):
raise failure
c = yt.YouTubeClient.__new__(yt.YouTubeClient)
c._http = SimpleNamespace(request=request)
with pytest.raises(YouTubeError) as caught:
c._send("GET", "https://example.invalid")
assert caught.value.reason == "transport"
assert caught.value.sent is sent
def test_a_call_youtube_answered_is_spent_a_refused_one_too(monkeypatch):
resp = httpx.Response(
400,
json={"error": {"errors": [{"reason": "invalidPageToken"}]}},
request=httpx.Request("GET", f"{yt.API_BASE}/search"),
)
recorded: list[int] = []
c = yt.YouTubeClient.__new__(yt.YouTubeClient)
c.db, c.spent = None, 0
monkeypatch.setattr(yt.sysconfig, "get_str", lambda d, key: "a-key")
monkeypatch.setattr(yt.quota, "record_usage", recorded.append)
monkeypatch.setattr(c, "_send", lambda *a, **k: resp)
with pytest.raises(YouTubeError):
c._get("search", {}, cost=100)
assert (recorded, c.spent) == ([100], 100)