From 2c7d4803f1d30677b78e038ca509620aa1dfcf0b Mon Sep 17 00:00:00 2001 From: claude Date: Sat, 22 Aug 2026 01:24:10 +0000 Subject: [PATCH] feat(webhook): is_repo_enabled reads .pr-review.json:enabled --- pilot/webhook_server.py | 29 +++++++++++++++++++++++++- tests/pilot/test_webhook_server.py | 33 ++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/pilot/webhook_server.py b/pilot/webhook_server.py index 22880eb..4853c41 100644 --- a/pilot/webhook_server.py +++ b/pilot/webhook_server.py @@ -34,14 +34,16 @@ Env: (optional) request-body cap, default 10 MiB """ +import base64 import hashlib import hmac import json import os import threading +import urllib.parse from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer -from ai_review import review_pr +from ai_review import gitea_get, review_pr try: import feedback_harvest # optional — absent in CI-step pod, present in @@ -109,6 +111,31 @@ def _labels_have_ai_review(labels) -> bool: return _labels_have(labels, AI_REVIEW_LABEL) +def is_repo_enabled(api: str, repo: str, ref: str, token: str) -> bool: + """True iff `.pr-review.json` on `ref` has `"enabled": true`. + + Reads from the given ref (typically the PR's base ref). False on any + failure: 404, parse error, missing file, missing `enabled`, wrong type. + The bool-coerce of `.get("enabled") is True` rejects the common + gotchas (`null`, `1`, `"yes"`, missing field all yield False). + """ + code, raw = gitea_get( + api, repo, + "contents/.pr-review.json?ref=" + urllib.parse.quote(ref, safe=""), + token, + ) + if code != 200: + return False + try: + data = json.loads(raw) + content_b64 = data.get("content", "").replace("\n", "") + decoded = base64.b64decode(content_b64).decode("utf-8", errors="replace") + cfg = json.loads(decoded) + except (json.JSONDecodeError, ValueError): + return False + return isinstance(cfg, dict) and cfg.get("enabled") is True + + def _verify_signature(raw_body: bytes, headers) -> bool: if not WEBHOOK_SECRET: return False # refuse to run without a configured secret diff --git a/tests/pilot/test_webhook_server.py b/tests/pilot/test_webhook_server.py index 27d87b5..fead2b2 100644 --- a/tests/pilot/test_webhook_server.py +++ b/tests/pilot/test_webhook_server.py @@ -1,4 +1,5 @@ """Unit tests for the webhook receiver's gating, dedupe and limits. No network.""" +import base64 import os import sys import threading @@ -134,3 +135,35 @@ def test_missing_label_is_ignored(monkeypatch): def test_review_slots_bound_matches_config(): assert ws.MAX_CONCURRENT >= 1 assert ws._review_slots._value <= ws.MAX_CONCURRENT + + +# --------------------------------------------------------------------------- +# is_repo_enabled — reads `.pr-review.json` from the PR base ref and parses +# its `enabled` flag. False on any failure (404, parse error, missing field). +# --------------------------------------------------------------------------- + + +def test_is_repo_enabled_returns_false_when_404(monkeypatch): + monkeypatch.setattr( + "webhook_server.gitea_get", + lambda *a, **kw: (404, b'{"message":"not found"}'), + ) + assert ws.is_repo_enabled("api", "owner/repo", "main", "tok") is False + + +def test_is_repo_enabled_returns_true_when_enabled(monkeypatch): + body = b'{"content":"' + base64.b64encode(b'{"enabled": true}').decode().encode() + b'"}' + monkeypatch.setattr("webhook_server.gitea_get", lambda *a, **kw: (200, body)) + assert ws.is_repo_enabled("api", "owner/repo", "main", "tok") is True + + +def test_is_repo_enabled_returns_false_when_disabled(monkeypatch): + body = b'{"content":"' + base64.b64encode(b'{"enabled": false}').decode().encode() + b'"}' + monkeypatch.setattr("webhook_server.gitea_get", lambda *a, **kw: (200, body)) + assert ws.is_repo_enabled("api", "owner/repo", "main", "tok") is False + + +def test_is_repo_enabled_returns_false_when_field_missing(monkeypatch): + body = b'{"content":"' + base64.b64encode(b'{}').decode().encode() + b'"}' + monkeypatch.setattr("webhook_server.gitea_get", lambda *a, **kw: (200, body)) + assert ws.is_repo_enabled("api", "owner/repo", "main", "tok") is False