Compare commits

...
Author SHA1 Message Date
timmy 902db45741 fix: normalize nullable user profile fields (#73)
CI / lint (pull_request) Successful in 7s
CI / build-frontend (pull_request) Successful in 4s
2026-08-06 05:03:04 +00:00
rockachopa af7f2ff92a Merge pull request 'Bound readiness probe latency' (#72) from timmy/71-bound-readiness-probe-latency into main
CI / lint (push) Successful in 7s
Release / release-candidate (push) Successful in 4s
CI / build-frontend (push) Successful in 4s
2026-08-06 04:18:48 +00:00
timmy dea3024f83 fix: bound readiness probe latency (#71)
CI / lint (pull_request) Successful in 8s
CI / build-frontend (pull_request) Successful in 4s
2026-08-06 04:17:59 +00:00
3 changed files with 70 additions and 3 deletions

No files matched your search

+15 -3
View File
@@ -15,6 +15,7 @@ from src.views import router as frontend_router
app = FastAPI(title="Stackchain Dashboard")
CONTEXT_TIMEOUT_SECONDS = 5.0
EVENT_STREAM_TIMEOUT_SECONDS = 5.0
READINESS_TIMEOUT_SECONDS = 5.0
FRONTEND_DIR = Path(__file__).resolve().parent.parent / "frontend"
app.add_middleware(
@@ -39,15 +40,26 @@ def health() -> dict[str, str]:
async def readiness():
"""Return readiness after verifying the configured Gitea connection."""
try:
user = await current_user()
user = await asyncio.wait_for(
current_user(), timeout=READINESS_TIMEOUT_SECONDS
)
except Exception as exc:
timed_out = isinstance(exc, TimeoutError)
error_message = (
f"Gitea readiness check timed out after {READINESS_TIMEOUT_SECONDS:g}s"
if timed_out
else str(exc)
)
return JSONResponse(
{
"status": "not_ready",
"service": "stackchain-dashboard",
"error": str(exc),
"error": error_message,
},
status_code=503,
headers={
"Retry-After": str(max(1, math.ceil(READINESS_TIMEOUT_SECONDS)))
} if timed_out else None,
)
return {
"status": "ready",
@@ -83,7 +95,7 @@ async def context() -> JSONResponse:
}, headers={
"Retry-After": str(max(1, math.ceil(CONTEXT_TIMEOUT_SECONDS)))
} if isinstance(e, TimeoutError) else None)
user_model = User(id=user_data["id"], login=user_data["login"], full_name=user_data.get("full_name", ""), email=user_data.get("email", ""))
user_model = User(id=user_data["id"], login=user_data["login"], full_name=user_data.get("full_name") or "", email=user_data.get("email") or "")
repo_models = [
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]
+26
View File
@@ -141,3 +141,29 @@ async def test_context_normalizes_null_collection_payloads(monkeypatch):
assert context["repos"] == []
assert context["issues"] == []
assert context["pull_requests"] == []
@pytest.mark.anyio
async def test_context_normalizes_nullable_user_profile_fields(monkeypatch):
async def user():
return {
"id": 1,
"login": "timmy",
"full_name": None,
"email": None,
}
async def empty_collection():
return []
monkeypatch.setattr(main, "current_user", 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["user"]["full_name"] == ""
assert context["user"]["email"] == ""
+29
View File
@@ -1,3 +1,6 @@
import asyncio
import json
import pytest
from src import main
@@ -37,3 +40,29 @@ async def test_readiness_endpoint_returns_503_when_gitea_is_unavailable(monkeypa
assert response.status_code == 503
assert b'"status":"not_ready"' in response.body
assert b'"error":"connection refused"' in response.body
@pytest.mark.anyio
async def test_readiness_endpoint_times_out_and_cancels_stalled_gitea_check(monkeypatch):
cancelled = asyncio.Event()
async def hanging_user():
try:
await asyncio.sleep(0.05)
return {"login": "too-late"}
finally:
cancelled.set()
monkeypatch.setattr(main, "READINESS_TIMEOUT_SECONDS", 0.01, raising=False)
monkeypatch.setattr(main, "current_user", hanging_user)
response = await main.readiness()
assert response.status_code == 503
assert response.headers["retry-after"] == "1"
assert json.loads(response.body) == {
"status": "not_ready",
"service": "stackchain-dashboard",
"error": "Gitea readiness check timed out after 0.01s",
}
assert cancelled.is_set()