From e8b5fd31a5455945e85ed37f6a5b86f55d3cad89 Mon Sep 17 00:00:00 2001 From: timmy Date: Wed, 5 Aug 2026 23:18:32 +0000 Subject: [PATCH] fix: handle nullable Gitea issue metadata (#50) --- src/main.py | 11 ++--------- src/models.py | 16 ++++++++-------- tests/test_context_timeout.py | 34 +++++++++++++++++++++++++++++++++- 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/src/main.py b/src/main.py index 6b0a4e8..e53208e 100644 --- a/src/main.py +++ b/src/main.py @@ -86,7 +86,7 @@ async def context() -> JSONResponse: if isinstance(r, dict) ] issue_models = [ - Issue(id=i["id"], number=i["number"], title=i["title"], state=i["state"], labels=[l.get("name", "") for l in i.get("labels", [])], assignees=[a.get("login", "") for a in i.get("assignees", [])], url=i["html_url"]) + Issue(id=i["id"], number=i["number"], title=i["title"], state=i["state"], labels=[l.get("name", "") for l in (i.get("labels") or [])], assignees=[a.get("login", "") for a in (i.get("assignees") or [])], url=i["html_url"]) for i in issues_data[:50] if isinstance(i, dict) ] @@ -96,14 +96,7 @@ async def context() -> JSONResponse: if isinstance(p, dict) ] ctx = compute(user_model, repo_models, issue_models, pr_models) - return JSONResponse({ - "user": ctx.user.dict(), - "repos": [r.dict() for r in ctx.repos], - "issues": [i.dict() for i in ctx.issues], - "pull_requests": [p.dict() for p in ctx.pull_requests], - "view": ctx.view, - "deltas": [d.dict() for d in ctx.deltas], - }) + return JSONResponse(ctx.model_dump()) @app.get("/api/v1/events") diff --git a/src/models.py b/src/models.py index 357c9b8..a3ca99e 100644 --- a/src/models.py +++ b/src/models.py @@ -36,17 +36,17 @@ class PullRequest(BaseModel): url: str +class SuggestionDelta(BaseModel): + panel: str + action: str + target: str = "" + priority: str = "medium" + + class ViewContext(BaseModel): user: User repos: list[Repo] = [] issues: list[Issue] = [] pull_requests: list[PullRequest] = [] view: str = "dashboard" - deltas: list[dict] = [] - - -class SuggestionDelta(BaseModel): - panel: str - action: str - target: str = "" - priority: str = "medium" + deltas: list[SuggestionDelta] = [] diff --git a/tests/test_context_timeout.py b/tests/test_context_timeout.py index 9cb9c64..4bd150b 100644 --- a/tests/test_context_timeout.py +++ b/tests/test_context_timeout.py @@ -1,4 +1,5 @@ import asyncio +import json import pytest @@ -30,4 +31,35 @@ async def test_context_returns_fallback_when_gitea_exceeds_deadline(monkeypatch) assert response.headers["retry-after"] == "1" assert b'"repos":[]' in response.body assert b'"error":"Gitea context request timed out after 0.01s"' in response.body - assert cancelled.is_set() \ No newline at end of file + assert cancelled.is_set() + + +@pytest.mark.anyio +async def test_context_normalizes_nullable_issue_collections(monkeypatch): + async def user(): + return {"id": 1, "login": "timmy"} + + async def empty_collection(): + return [] + + async def issue_collection(): + return [{ + "id": 50, + "number": 50, + "title": "Nullable metadata", + "state": "open", + "labels": None, + "assignees": None, + "html_url": "https://forge.example/issues/50", + }] + + monkeypatch.setattr(main, "current_user", user) + monkeypatch.setattr(main, "repos", empty_collection) + monkeypatch.setattr(main, "issues", issue_collection) + monkeypatch.setattr(main, "pull_requests", empty_collection) + + response = await main.context() + issue = json.loads(response.body)["issues"][0] + + assert issue["labels"] == [] + assert issue["assignees"] == [] \ No newline at end of file