Compare commits

...
Author SHA1 Message Date
timmy 49ce2d9945 Merge pull request 'Skip malformed Gitea issue entries' (#82) from timmy/81-skip-malformed-gitea-issue-entries into main
CI / lint (push) Successful in 8s
Release / release-candidate (push) Successful in 3s
CI / build-frontend (push) Successful in 4s
Closes #81
2026-08-06 06:48:38 +00:00
timmy 5f86b58b88 fix: skip malformed issue entries (#81)
CI / lint (pull_request) Successful in 8s
CI / build-frontend (pull_request) Successful in 4s
2026-08-06 06:47:47 +00:00
rockachopa 888c03e4c1 Merge pull request 'Skip malformed Gitea repository entries' (#80) from timmy/79-skip-malformed-repository-entries into main
CI / lint (push) Successful in 9s
Release / release-candidate (push) Successful in 4s
CI / build-frontend (push) Successful in 5s
2026-08-06 06:19:24 +00:00
timmy bce46843b3 fix: skip malformed repository entries (#79)
CI / lint (pull_request) Successful in 9s
CI / build-frontend (pull_request) Successful in 5s
2026-08-06 06:18:22 +00:00
rockachopa 89b14872e6 Merge pull request 'Return not-ready for invalid Gitea current-user payload' (#78) from timmy/77-return-not-ready-invalid-current-user into main
CI / lint (push) Successful in 7s
Release / release-candidate (push) Successful in 3s
CI / build-frontend (push) Successful in 4s
2026-08-06 05:48:27 +00:00
timmy 090a059c17 fix: reject invalid readiness user payload (#77)
CI / lint (pull_request) Successful in 7s
CI / build-frontend (pull_request) Successful in 4s
2026-08-06 05:47:38 +00:00
timmy b2f8eaf89a Merge pull request 'Handle null Gitea current-user payload' (#76) from timmy/75-handle-null-gitea-current-user-payload into main
CI / lint (push) Successful in 7s
Release / release-candidate (push) Successful in 3s
CI / build-frontend (push) Successful in 4s
2026-08-06 05:24:39 +00:00
timmy 8001933692 fix: handle null Gitea current-user payload (#75)
CI / lint (pull_request) Successful in 9s
CI / build-frontend (pull_request) Successful in 4s
2026-08-06 05:23:58 +00:00
timmy d9ad68b72b Merge pull request 'Normalize nullable Gitea user profile fields' (#74) from timmy/73-normalize-nullable-user-profile-fields into main
CI / lint (push) Successful in 7s
Release / release-candidate (push) Successful in 4s
CI / build-frontend (push) Successful in 5s
2026-08-06 05:03:45 +00:00
3 changed files with 109 additions and 0 deletions

No files matched your search

+9
View File
@@ -43,6 +43,8 @@ async def readiness():
user = await asyncio.wait_for(
current_user(), timeout=READINESS_TIMEOUT_SECONDS
)
if not isinstance(user, dict) or not user.get("login"):
raise ValueError("Gitea current-user response did not include a login")
except Exception as exc:
timed_out = isinstance(exc, TimeoutError)
error_message = (
@@ -75,6 +77,8 @@ async def context() -> JSONResponse:
asyncio.gather(current_user(), repos(), issues(), pull_requests()),
timeout=CONTEXT_TIMEOUT_SECONDS,
)
if not isinstance(user_data, dict):
raise ValueError("Gitea current-user response was not an object")
except Exception as e:
error_message = (
f"Gitea context request timed out after {CONTEXT_TIMEOUT_SECONDS:g}s"
@@ -100,11 +104,16 @@ async def context() -> JSONResponse:
Repo(id=r["id"], name=r["name"], full_name=r["full_name"], description=r.get("description") or "", url=r["html_url"], updated_at=r.get("updated_at", ""))
for r in (repo_data or [])[:50]
if isinstance(r, dict)
and all(field in r for field in ("id", "name", "full_name", "html_url"))
]
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") or [])], assignees=[a.get("login", "") for a in (i.get("assignees") or [])], url=i["html_url"])
for i in (issues_data or [])[:50]
if isinstance(i, dict)
and all(
field in i
for field in ("id", "number", "title", "state", "html_url")
)
]
pr_models = [
PullRequest(id=p["id"], number=p["number"], title=p["title"], state=p["state"], user=(p.get("user") or {}).get("login", ""), url=p["html_url"])
+83
View File
@@ -167,3 +167,86 @@ async def test_context_normalizes_nullable_user_profile_fields(monkeypatch):
assert response.status_code == 200
assert context["user"]["full_name"] == ""
assert context["user"]["email"] == ""
@pytest.mark.anyio
async def test_context_returns_fallback_for_null_current_user_payload(monkeypatch):
async def null_user():
return None
async def empty_collection():
return []
monkeypatch.setattr(main, "current_user", null_user)
monkeypatch.setattr(main, "repos", empty_collection)
monkeypatch.setattr(main, "issues", empty_collection)
monkeypatch.setattr(main, "pull_requests", empty_collection)
response = await main.context()
context = json.loads(response.body)
assert response.status_code == 200
assert context["repos"] == []
assert context["error"] == "Gitea current-user response was not an object"
@pytest.mark.anyio
async def test_context_skips_malformed_repository_entries(monkeypatch):
async def user():
return {"id": 1, "login": "timmy"}
async def repository_collection():
return [
{"id": 79, "name": "missing-required-fields"},
{
"id": 1,
"name": "dashboard",
"full_name": "stackchain/dashboard",
"description": "Operations dashboard",
"html_url": "https://forge.example/stackchain/dashboard",
},
]
async def empty_collection():
return []
monkeypatch.setattr(main, "current_user", user)
monkeypatch.setattr(main, "repos", repository_collection)
monkeypatch.setattr(main, "issues", empty_collection)
monkeypatch.setattr(main, "pull_requests", empty_collection)
response = await main.context()
repositories = json.loads(response.body)["repos"]
assert [repository["name"] for repository in repositories] == ["dashboard"]
@pytest.mark.anyio
async def test_context_skips_malformed_issue_entries(monkeypatch):
async def user():
return {"id": 1, "login": "timmy"}
async def issue_collection():
return [
{"id": 81, "title": "missing-required-fields"},
{
"id": 1,
"number": 81,
"title": "Valid issue",
"state": "open",
"html_url": "https://forge.example/stackchain/dashboard/issues/81",
},
]
async def empty_collection():
return []
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()
issues = json.loads(response.body)["issues"]
assert [issue["title"] for issue in issues] == ["Valid issue"]
+17
View File
@@ -42,6 +42,23 @@ async def test_readiness_endpoint_returns_503_when_gitea_is_unavailable(monkeypa
assert b'"error":"connection refused"' in response.body
@pytest.mark.anyio
async def test_readiness_endpoint_returns_503_for_null_current_user_payload(monkeypatch):
async def null_user():
return None
monkeypatch.setattr(main, "current_user", null_user)
response = await main.readiness()
assert response.status_code == 503
assert json.loads(response.body) == {
"status": "not_ready",
"service": "stackchain-dashboard",
"error": "Gitea current-user response did not include a login",
}
@pytest.mark.anyio
async def test_readiness_endpoint_times_out_and_cancels_stalled_gitea_check(monkeypatch):
cancelled = asyncio.Event()