|
| 1 | +"""The hold-list endpoint serves exactly one signed file, read-only.""" |
| 2 | + |
| 3 | +from __future__ import annotations |
| 4 | + |
| 5 | +import json |
| 6 | +import threading |
| 7 | +import urllib.error |
| 8 | +import urllib.request |
| 9 | + |
| 10 | +import pytest |
| 11 | +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey |
| 12 | + |
| 13 | +from infermatrix_copilot.kb_service.holds_server import make_handler |
| 14 | +from infermatrix_copilot.knowledge_service import gate_verifier as gv |
| 15 | +from infermatrix_copilot.knowledge_service.signing import sign |
| 16 | + |
| 17 | + |
| 18 | +@pytest.fixture |
| 19 | +def server(tmp_path): |
| 20 | + from http.server import ThreadingHTTPServer |
| 21 | + |
| 22 | + holds = tmp_path / "public" / "holds.json" |
| 23 | + srv = ThreadingHTTPServer(("127.0.0.1", 0), make_handler(holds)) |
| 24 | + thread = threading.Thread(target=srv.serve_forever, daemon=True) |
| 25 | + thread.start() |
| 26 | + yield srv, holds, f"http://127.0.0.1:{srv.server_address[1]}" |
| 27 | + srv.shutdown() |
| 28 | + srv.server_close() |
| 29 | + |
| 30 | + |
| 31 | +def _status(url, method="GET"): |
| 32 | + try: |
| 33 | + with urllib.request.urlopen(urllib.request.Request(url, method=method), timeout=5) as response: |
| 34 | + return response.status, response.read(), response.headers |
| 35 | + except urllib.error.HTTPError as exc: |
| 36 | + return exc.code, b"", exc.headers |
| 37 | + |
| 38 | + |
| 39 | +def test_the_signed_hold_list_is_served_and_verifies_through_the_gate(server): |
| 40 | + import time |
| 41 | + |
| 42 | + srv, holds, base = server |
| 43 | + key = Ed25519PrivateKey.generate() |
| 44 | + holds.parent.mkdir(parents=True) |
| 45 | + payload = {"issued_at": time.time(), "sequence": 3, "global": False, "repos": ["demo"], "prs": [7]} |
| 46 | + holds.write_text(json.dumps(sign("kb-holds", payload, key))) |
| 47 | + status, body, headers = _status(base + "/holds.json") |
| 48 | + assert status == 200 and headers["Cache-Control"] == "no-store" |
| 49 | + # exactly what the kb-gate verifier does with it |
| 50 | + envelope = gv.fetch_holds(base + "/holds.json") |
| 51 | + assert gv.verify_holds(envelope, key.public_key(), now=time.time())["prs"] == [7] |
| 52 | + |
| 53 | + |
| 54 | +def test_nothing_else_is_served(server, tmp_path): |
| 55 | + srv, holds, base = server |
| 56 | + holds.parent.mkdir(parents=True) |
| 57 | + holds.write_text("{}") |
| 58 | + (tmp_path / "kb.db").write_text("secret ledger") |
| 59 | + for path in ("/", "/kb.db", "/../kb.db", "/public/", "/public/holds.json", "/holds.json/../kb.db"): |
| 60 | + assert _status(base + path)[0] == 404, path |
| 61 | + assert _status(base + "/holds.json", method="POST")[0] in (404, 405, 501) |
| 62 | + |
| 63 | + |
| 64 | +def test_a_missing_hold_list_is_unavailable_not_empty(server): |
| 65 | + assert _status(server[2] + "/holds.json")[0] == 503 |
0 commit comments