Change-detectors, tautologies, source-reading tests, redundant duplicates, mock-echo tests and dead/unrunnable tests. Per-test rationale in the lane ledger (category + reason for every removal).
93 lines
4.2 KiB
Python
93 lines
4.2 KiB
Python
"""A rate-limit retry names the provider's reset window on the status line (#26889).
|
|
|
|
"Rate limited. Waiting 60s" hides the one fact that decides whether to wait or
|
|
switch models: a per-minute throttle and a 13-minute plan window look identical
|
|
without it. The hint is derived from the same ``reset_at`` the credential pool
|
|
uses, so the relative ``resets_in_seconds`` a Codex/ChatGPT usage-limit body
|
|
carries must land there too.
|
|
"""
|
|
|
|
import time
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
|
|
from agent.agent_runtime_helpers import extract_api_error_context
|
|
from agent.turn_recovery import compute_error_backoff, reset_hint
|
|
|
|
|
|
def _codex_429(**fields):
|
|
err = Exception("HTTP 429: The usage limit has been reached")
|
|
err.body = {"error": {"type": "usage_limit_reached", "message": "The usage limit has been reached", **fields}}
|
|
return err
|
|
|
|
|
|
def test_relative_resets_in_seconds_becomes_reset_at_when_no_epoch_is_given():
|
|
ctx = extract_api_error_context(_codex_429(resets_in_seconds=756))
|
|
assert abs(ctx["reset_at"] - (time.time() + 756)) < 5
|
|
# An explicit epoch wins over the relative field (same precedence as the credential pool).
|
|
ctx = extract_api_error_context(_codex_429(resets_at=1_900_000_000, resets_in_seconds=756))
|
|
assert ctx["reset_at"] == 1_900_000_000
|
|
# No signal at all → no hint, not a crash.
|
|
assert reset_hint(Exception("HTTP 429")) == "" and reset_hint(_codex_429(resets_in_seconds=-5)) == ""
|
|
|
|
|
|
def test_rate_limit_retry_status_names_the_reset_window():
|
|
agent = MagicMock()
|
|
from agent.status_output import StatusOutputMixin
|
|
for name in ("_emit_diagnostic_wait", "_buffer_diagnostic_status"):
|
|
setattr(agent, name, getattr(StatusOutputMixin, name).__get__(agent))
|
|
agent._client_log_context.return_value = ""
|
|
|
|
compute_error_backoff(
|
|
agent, _codex_429(resets_in_seconds=756), retry_count=1, max_retries=3,
|
|
is_rate_limited=True, is_zai_coding_overload=False,
|
|
base_url="https://example.test/v1", model="test/model",
|
|
)
|
|
|
|
text = agent._buffer_status.call_args.args[0]
|
|
assert "~13m" in text
|
|
|
|
|
|
def test_live_wait_line_names_the_reset_window_too():
|
|
"""The buffered line replays only if every retry fails; the line rendered during the wait is
|
|
``_emit_diagnostic_wait`` → ``_emit_wait_notice``, and it must carry the same hint."""
|
|
agent = MagicMock()
|
|
from agent.status_output import StatusOutputMixin
|
|
for name in ("_emit_diagnostic_wait", "_buffer_diagnostic_status"):
|
|
setattr(agent, name, getattr(StatusOutputMixin, name).__get__(agent))
|
|
agent._client_log_context.return_value = ""
|
|
|
|
def run(err):
|
|
compute_error_backoff(
|
|
agent, err, retry_count=1, max_retries=3, is_rate_limited=True, is_zai_coding_overload=False,
|
|
base_url="https://example.test/v1", model="test/model",
|
|
)
|
|
return agent._emit_wait_notice.call_args.args[0]
|
|
|
|
live = run(_codex_429(resets_in_seconds=756))
|
|
assert "~13m" in live
|
|
# No reset known → no reset window is claimed.
|
|
assert "resets in" not in run(Exception("HTTP 429")).lower()
|
|
|
|
|
|
@pytest.mark.parametrize("headers, expected_seconds", [
|
|
({"x-ratelimit-reset-requests": "6m0s"}, 360),
|
|
({"x-ratelimit-reset-tokens": "1.5s"}, 1.5),
|
|
({"x-ratelimit-reset-requests": "20ms"}, 0.02),
|
|
({"anthropic-ratelimit-requests-reset": None}, 900), # filled below with now+900 ISO-8601 'Z'
|
|
({"retry-after": "30", "x-ratelimit-reset-requests": "6m0s"}, 30), # Retry-After still wins
|
|
])
|
|
def test_vendor_reset_headers_feed_reset_at(headers, expected_seconds):
|
|
from datetime import datetime, timedelta, timezone
|
|
if "anthropic-ratelimit-requests-reset" in headers:
|
|
when = datetime.now(timezone.utc) + timedelta(seconds=900)
|
|
headers["anthropic-ratelimit-requests-reset"] = when.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
err = Exception("HTTP 429: rate limit")
|
|
err.response = MagicMock(headers=headers)
|
|
assert abs(extract_api_error_context(err)["reset_at"] - (time.time() + expected_seconds)) < 5
|
|
# A reset already in the past is ignored rather than reported as "resets in ~0s".
|
|
past = Exception("HTTP 429")
|
|
past.response = MagicMock(headers={"anthropic-ratelimit-tokens-reset": "2020-01-01T00:00:00Z"})
|
|
assert "reset_at" not in extract_api_error_context(past)
|