diff --git a/src/glitchup_bot/glitchtip_client/client.py b/src/glitchup_bot/glitchtip_client/client.py index d6a43b2..fa712b8 100644 --- a/src/glitchup_bot/glitchtip_client/client.py +++ b/src/glitchup_bot/glitchtip_client/client.py @@ -1,4 +1,5 @@ import logging +from urllib.parse import parse_qs, urlparse import httpx @@ -88,9 +89,15 @@ class GlitchTipClient: if 'rel="next"' not in part or 'results="true"' not in part or "cursor=" not in part: continue - start = part.index("cursor=") + len("cursor=") - end = part.find(">", start) - return part[start:end] if end != -1 else part[start:] + url_start = part.find("<") + url_end = part.find(">", url_start + 1) + if url_start == -1 or url_end == -1: + continue + + parsed = urlparse(part[url_start + 1 : url_end].strip()) + cursor_values = parse_qs(parsed.query).get("cursor") + if cursor_values: + return cursor_values[0] return None diff --git a/tests/test_glitchtip_client.py b/tests/test_glitchtip_client.py index 7f0d22e..99897da 100644 --- a/tests/test_glitchtip_client.py +++ b/tests/test_glitchtip_client.py @@ -44,6 +44,18 @@ async def test_list_issues_retries_without_query_params_on_422() -> None: ] +def test_parse_next_cursor_extracts_only_cursor_value() -> None: + link_header = ( + '; rel="next"; results="true"; cursor="..."' + ) + + cursor = GlitchTipClient._parse_next_cursor(link_header) + + assert cursor == "cD0yMDI2LTAzLTI2KzIxJTNBNTklM0EyMS4xNjkxNDklMkIwMCUzQTAw&limit=100" + + @pytest.mark.asyncio async def test_list_issues_preserves_non_422_errors() -> None: def handler(request: httpx.Request) -> httpx.Response: