feat: track logical call statistics across governed calls
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user