"""Discord client 429-retry bounding.""" from __future__ import annotations import pytest from aioresponses import aioresponses import aiohttp from notify_bridge_core.notifications.discord.client import DiscordClient WEBHOOK = "https://discord.com/api/webhooks/123/abc" @pytest.mark.asyncio async def test_bounded_retries_on_persistent_429() -> None: """If every response is 429, the client gives up after _MAX_RETRIES.""" with aioresponses() as mocked: mocked.post(WEBHOOK, status=429, headers={"Retry-After": "0.001"}, repeat=True) async with aiohttp.ClientSession() as sess: client = DiscordClient(sess) result = await client.send(WEBHOOK, "hello") assert result["success"] is False # Either the custom "Rate limited" message or the bare HTTP 429 from the # final attempt — both indicate bounded retries without infinite recursion. assert "429" in result["error"] or "Rate limited" in result["error"] @pytest.mark.asyncio async def test_caps_retry_after() -> None: """A malicious Retry-After: 99999 must not pin the task for hours.""" with aioresponses() as mocked: # First call: absurd Retry-After. Second call: success. mocked.post(WEBHOOK, status=429, headers={"Retry-After": "99999"}) mocked.post(WEBHOOK, status=204) async with aiohttp.ClientSession() as sess: client = DiscordClient(sess) # Override the cap to something trivial so the test completes fast. client._MAX_RETRY_AFTER = 0.001 # type: ignore[attr-defined] result = await client.send(WEBHOOK, "hello") assert result["success"] is True