BE-AGT-004 parse exhausted recovery hints
This commit is contained in:
@@ -18,6 +18,7 @@ from app.services.agent_status import (
|
||||
AgentStatusError,
|
||||
HEARTBEAT_TIMEOUT_SECONDS,
|
||||
DEFAULT_RECOVERY_HOURS,
|
||||
parse_exhausted_recovery_at,
|
||||
transition_to_busy,
|
||||
transition_to_idle,
|
||||
transition_to_offline,
|
||||
@@ -170,6 +171,55 @@ class TestTransitionToOffline:
|
||||
assert result.status == AgentStatus.OFFLINE
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Recovery time parsing
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestParseExhaustedRecoveryAt:
|
||||
def test_parses_retry_after_seconds_header(self):
|
||||
recovery = parse_exhausted_recovery_at(
|
||||
now=NOW,
|
||||
headers={"Retry-After": "120"},
|
||||
)
|
||||
assert recovery == NOW + timedelta(seconds=120)
|
||||
|
||||
def test_parses_retry_after_http_date_header(self):
|
||||
recovery = parse_exhausted_recovery_at(
|
||||
now=NOW,
|
||||
headers={"Retry-After": "Wed, 01 Apr 2026 12:05:00 GMT"},
|
||||
)
|
||||
assert recovery == datetime(2026, 4, 1, 12, 5, 0, tzinfo=timezone.utc)
|
||||
|
||||
def test_parses_reset_in_minutes_from_message(self):
|
||||
recovery = parse_exhausted_recovery_at(
|
||||
now=NOW,
|
||||
message="rate limit exceeded, reset in 7 mins",
|
||||
)
|
||||
assert recovery == NOW + timedelta(minutes=7)
|
||||
|
||||
def test_parses_retry_after_seconds_from_message(self):
|
||||
recovery = parse_exhausted_recovery_at(
|
||||
now=NOW,
|
||||
message="429 too many requests; retry after 45 seconds",
|
||||
)
|
||||
assert recovery == NOW + timedelta(seconds=45)
|
||||
|
||||
def test_parses_resets_at_iso_timestamp_from_message(self):
|
||||
recovery = parse_exhausted_recovery_at(
|
||||
now=NOW,
|
||||
message="quota exhausted, resets at 2026-04-01T14:30:00Z",
|
||||
)
|
||||
assert recovery == datetime(2026, 4, 1, 14, 30, 0, tzinfo=timezone.utc)
|
||||
|
||||
def test_falls_back_to_default_when_unparseable(self):
|
||||
recovery = parse_exhausted_recovery_at(
|
||||
now=NOW,
|
||||
headers={"Retry-After": "not-a-date"},
|
||||
message="please try later maybe soon",
|
||||
)
|
||||
assert recovery == NOW + timedelta(hours=DEFAULT_RECOVERY_HOURS)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# * → Exhausted (API quota)
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -210,6 +260,28 @@ class TestTransitionToExhausted:
|
||||
)
|
||||
assert result.status == AgentStatus.EXHAUSTED
|
||||
|
||||
def test_parses_recovery_from_headers_when_timestamp_not_explicitly_provided(self, db):
|
||||
agent = _make_agent(db, status=AgentStatus.BUSY)
|
||||
result = transition_to_exhausted(
|
||||
db,
|
||||
agent,
|
||||
reason=ExhaustReason.RATE_LIMIT,
|
||||
headers={"Retry-After": "90"},
|
||||
now=NOW,
|
||||
)
|
||||
assert result.recovery_at == NOW + timedelta(seconds=90)
|
||||
|
||||
def test_parses_recovery_from_message_when_timestamp_not_explicitly_provided(self, db):
|
||||
agent = _make_agent(db, status=AgentStatus.BUSY)
|
||||
result = transition_to_exhausted(
|
||||
db,
|
||||
agent,
|
||||
reason=ExhaustReason.BILLING,
|
||||
message="billing quota exhausted, resets at 2026-04-01T15:00:00Z",
|
||||
now=NOW,
|
||||
)
|
||||
assert result.recovery_at == datetime(2026, 4, 1, 15, 0, 0, tzinfo=timezone.utc)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Heartbeat timeout check
|
||||
|
||||
Reference in New Issue
Block a user