diff --git a/pilot/webhook_server.py b/pilot/webhook_server.py index 5a7ecfd..e756cd9 100644 --- a/pilot/webhook_server.py +++ b/pilot/webhook_server.py @@ -186,7 +186,9 @@ def _fetch_current_labels(repo: str, index: str) -> list: opt-in is lost — the review posts without its usage block. Reading the labels again at review start closes that window. """ - url = f"{GITEA_API}/repos/{repo}/issues/{index}/labels" + # GITEA_API is the bare host (no /api/v1) — every caller appends the + # version prefix itself; see ai_review._get / _post. + url = f"{GITEA_API}/api/v1/repos/{repo}/issues/{index}/labels" req = urllib.request.Request(url, headers={ "Authorization": f"token {BOT_TOKEN}", "Accept": "application/json", diff --git a/tests/pilot/test_webhook_server.py b/tests/pilot/test_webhook_server.py index 33d3461..fd0386a 100644 --- a/tests/pilot/test_webhook_server.py +++ b/tests/pilot/test_webhook_server.py @@ -162,6 +162,27 @@ def test_run_review_keeps_usage_off_when_label_absent(monkeypatch): assert seen["report_usage"] is False +def test_fetch_current_labels_hits_the_versioned_api_path(monkeypatch): + # GITEA_API is the bare host; the /api/v1 prefix is the caller's job. + # Getting this wrong 404s silently and the opt-in is lost — which is + # exactly what shipped the first time. + seen = {} + + class _Resp: + def read(self): return b'[{"name": "AI-USAGE"}]' + def __enter__(self): return self + def __exit__(self, *a): return False + + def _urlopen(req, timeout=None): + seen["url"] = req.full_url + return _Resp() + + monkeypatch.setattr(ws.urllib.request, "urlopen", _urlopen) + out = ws._fetch_current_labels("o/r", "9") + assert out == [{"name": "AI-USAGE"}] + assert seen["url"] == f"{ws.GITEA_API}/api/v1/repos/o/r/issues/9/labels" + + def test_label_reread_failure_is_not_fatal(monkeypatch): # A dead API must not take the review down with it — the re-read is a # best-effort upgrade of an opt-in flag, nothing more.