feat: track logical call statistics across governed calls

This commit is contained in:
2026-09-09 10:04:39 -04:00
parent 300ced5dbd
commit 87c261bf73
13 changed files with 650 additions and 10 deletions
+58
View File
@@ -5,6 +5,7 @@
"""
import asyncio
import dataclasses
import pytest
@@ -776,3 +777,60 @@ class TestRateLimitPushback:
await mw(_REQ)
assert ei.value.reason == "retry_exhausted"
assert len(transport.calls) == 3
class TestLogicalAttemptCounting:
"""尝试登记在 transport 调用**之前**(1.3.5 设计 §4)。
登记点若挪到成功之后,失败与取消的尝试就会从计数里消失——而那正是
诊断时最需要看见的几次。
"""
def _ctx(self, clock):
from polygateway.types import _CallContext
return _CallContext(now=clock)
async def test_single_success_counts_one(self):
mw, _, _, _, _, clock = _harness([_src("a")], [_ok()])
ctx = self._ctx(clock)
await mw(dataclasses.replace(_REQ, call_context=ctx))
assert ctx.snapshot().attempts == 1
async def test_failed_retries_are_counted(self):
"""两次可重试失败 + 一次成功 = 3 次尝试,不是 1 次。"""
mw, _, _, transport, _, clock = _harness(
[_src("a")], [TransientError("t1"), TransientError("t2"), _ok()]
)
ctx = self._ctx(clock)
await mw(dataclasses.replace(_REQ, call_context=ctx))
assert ctx.snapshot().attempts == 3 == len(transport.calls)
async def test_budget_free_429_still_counts_as_an_attempt(self):
"""429 免的是重试预算,不是"没发生过"——它确实打到了网关。"""
mw, _, _, transport, _, clock = _harness(
[_src("a")],
[
TransientError("t1", status_code=429, retry_after_s=1.0),
TransientError("t2", status_code=429, retry_after_s=1.0),
_ok(),
],
)
ctx = self._ctx(clock)
await mw(dataclasses.replace(_REQ, call_context=ctx))
assert ctx.snapshot().attempts == 3 == len(transport.calls)
async def test_retry_exhausted_counts_every_attempt(self):
mw, _, _, transport, _, clock = _harness(
[_src("a")], [TransientError(str(i)) for i in range(5)], max_attempts=3
)
ctx = self._ctx(clock)
with pytest.raises(AllSourcesExhausted):
await mw(dataclasses.replace(_REQ, call_context=ctx))
assert ctx.snapshot().attempts == 3 == len(transport.calls)
async def test_absent_context_does_not_break_the_call(self):
"""库内现场构造的 `ChatRequest` 没有上下文,不得因此报错(设计 §3.5)。"""
mw, _, _, _, _, _ = _harness([_src("a")], [_ok()])
resp = await mw(_REQ)
assert resp.content == "ok" and _REQ.call_context is None