diff --git a/src/release_engine.py b/src/release_engine.py index f471174..e86a74b 100644 --- a/src/release_engine.py +++ b/src/release_engine.py @@ -147,6 +147,8 @@ class GiteaClient: return json.loads(raw) if raw else None except error.HTTPError as exc: detail = exc.read().decode(errors="replace") + if exc.code == 401: + detail += " (authentication rejected; refresh the cron GITEA_TOKEN)" raise RuntimeError(f"Gitea {method} {path} failed: HTTP {exc.code}: {detail}") from exc @staticmethod diff --git a/tests/test_release_engine.py b/tests/test_release_engine.py index 9062a49..3286f40 100644 --- a/tests/test_release_engine.py +++ b/tests/test_release_engine.py @@ -1,4 +1,6 @@ import json +import threading +from http.server import BaseHTTPRequestHandler, HTTPServer from pathlib import Path import pytest @@ -6,6 +8,7 @@ import pytest from src.release_engine import ( CommandResult, EngineConfig, + GiteaClient, Issue, ReleaseEngine, RunState, @@ -15,6 +18,28 @@ from src.release_engine import ( ) +def test_stale_token_error_explains_how_to_restore_cron_authentication(): + class UnauthorizedHandler(BaseHTTPRequestHandler): + def do_GET(self): + self.send_response(401) + self.end_headers() + self.wfile.write(b'{"message":"user does not exist [uid: 0, name: ]"}') + + def log_message(self, format, *args): + pass + + server = HTTPServer(("127.0.0.1", 0), UnauthorizedHandler) + thread = threading.Thread(target=server.handle_request) + thread.start() + try: + client = GiteaClient(f"http://127.0.0.1:{server.server_port}", "stale-token") + with pytest.raises(RuntimeError, match="refresh the cron GITEA_TOKEN"): + client.list_open_issues("stackchain/stackchain-dashboard") + finally: + thread.join(timeout=2) + server.server_close() + + class FakeClient: def __init__(self, issues, verified_assignee="timmy", existing_pr=None): self.issues = issues