From 7ea70c5c5db653c9e3c40c9e144900344adf2406 Mon Sep 17 00:00:00 2001 From: operator Date: Sat, 10 Oct 2026 15:38:14 +0000 Subject: [PATCH] feat(gateway): expose /api/v1/queue on chromebox-gateway and configure Cloudflare ingress --- bin/chromebox-gateway.py | 38 ++++++++++++++++++-- tests/test_chromebox_gateway_queue.py | 51 +++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 tests/test_chromebox_gateway_queue.py diff --git a/bin/chromebox-gateway.py b/bin/chromebox-gateway.py index 2b1e200..50daf85 100755 --- a/bin/chromebox-gateway.py +++ b/bin/chromebox-gateway.py @@ -240,8 +240,42 @@ class Handler(BaseHTTPRequestHandler): self.wfile.write(body) def do_GET(self): - if urlparse(self.path).path == "/health": - self._json(200, {"status": "ok", "ops": sorted(ALLOWLIST)}) + p = urlparse(self.path).path + if p == "/health": + self._json(200, {"status": "ok", "ops": sorted(ALLOWLIST), "endpoints": ["/health", "/api/v1/queue", "/api/v1/op"]}) + return + if p == "/api/v1/queue": + auth = self.headers.get("Authorization", "") + token = auth[7:] if auth.startswith("Bearer ") else "" + identity = check_token(token) + if not identity: + self._json(401, {"error": "unauthorized"}) + return + if not rate_ok(identity): + audit({"identity": identity, "op": "queue", "result": "rate_limited"}) + self._json(429, {"error": "rate_limited"}) + return + + tasks_dir = os.path.join(os.path.dirname(BIN_DIR), "fleet", "tasks") + try: + if BIN_DIR not in sys.path: + sys.path.insert(0, BIN_DIR) + import runtime_reconcile as rec + tasks = rec.list_tasks(tasks_dir) + counts = {"pending": 0, "claimed": 0, "done": 0} + for t in tasks: + q = t.get("queue") + if q in counts: + counts[q] += 1 + self._json(200, { + "ok": True, + "tasks": tasks, + "counts": counts, + }) + audit({"identity": identity, "op": "queue", "result": "ok"}) + except Exception as e: + audit({"identity": identity, "op": "queue", "result": "error", "detail": str(e)[:120]}) + self._json(500, {"ok": False, "error": str(e)}) return self._json(404, {"error": "not_found"}) diff --git a/tests/test_chromebox_gateway_queue.py b/tests/test_chromebox_gateway_queue.py new file mode 100644 index 0000000..11f3cd9 --- /dev/null +++ b/tests/test_chromebox_gateway_queue.py @@ -0,0 +1,51 @@ +"""Test for chromebox-gateway /api/v1/queue endpoint.""" +import os +import sys +import json +import unittest +from unittest.mock import patch, MagicMock + +REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +BIN_DIR = os.path.join(REPO_ROOT, "bin") +if BIN_DIR not in sys.path: + sys.path.insert(0, BIN_DIR) + +import importlib.util +spec = importlib.util.spec_from_file_location("chromebox_gateway", os.path.join(BIN_DIR, "chromebox-gateway.py")) +cbg = importlib.util.module_from_spec(spec) +spec.loader.exec_module(cbg) + +class TestChromeboxGatewayQueue(unittest.TestCase): + def test_health_endpoints_list(self): + handler = MagicMock() + handler.path = "/health" + cbg.Handler.do_GET(handler) + handler._json.assert_called_once() + args, _ = handler._json.call_args + self.assertEqual(args[0], 200) + self.assertIn("/api/v1/queue", args[1].get("endpoints", [])) + + @patch.object(cbg, "check_token", return_value=None) + def test_queue_unauthorized(self, mock_tok): + handler = MagicMock() + handler.path = "/api/v1/queue" + handler.headers = {"Authorization": "Bearer invalid"} + cbg.Handler.do_GET(handler) + handler._json.assert_called_once_with(401, {"error": "unauthorized"}) + + @patch.object(cbg, "check_token", return_value="master") + @patch.object(cbg, "rate_ok", return_value=True) + def test_queue_authorized_success(self, mock_rate, mock_tok): + handler = MagicMock() + handler.path = "/api/v1/queue" + handler.headers = {"Authorization": "Bearer master-token"} + cbg.Handler.do_GET(handler) + handler._json.assert_called_once() + code, body = handler._json.call_args[0] + self.assertEqual(code, 200) + self.assertTrue(body.get("ok")) + self.assertIn("counts", body) + self.assertIn("tasks", body) + +if __name__ == "__main__": + unittest.main()