Files
orbit/scripts/test-protocol-compatibility.sh

108 lines
4.1 KiB
Bash
Executable File

#!/bin/sh
# Check that the server, SDK and machine-readable WebSocket schemas name the
# same current protocol line. This is a contract gate, not a historical pair
# compatibility test.
set -eu
TASK_ROOT=${ORBIT_ROOT_DIR:-.}
python3 - "$TASK_ROOT" <<'PY'
import json
import re
import sys
from pathlib import Path
root = Path(sys.argv[1]).resolve()
errors = []
def read(relative):
path = root / relative
if not path.is_file():
errors.append(f"missing {relative}")
return ""
return path.read_text()
def exact_constant(relative, name):
text = read(relative)
match = re.search(rf'\bconst\s+{re.escape(name)}\s*=\s*"([^"]+)"', text)
if not match:
errors.append(f"missing constant {name} in {relative}")
return ""
return match.group(1)
worker_server = exact_constant("internal/transport/httpserver/worker.go", "WorkerSubprotocol")
worker_sdk = exact_constant("sdk/go/worker_connection.go", "workerSubprotocol")
event_server = exact_constant("internal/transport/httpserver/event_codec.go", "EventSubprotocol")
event_sdk = exact_constant("sdk/go/event_connection.go", "eventSubprotocol")
if worker_server != worker_sdk or worker_server != "pulse.worker.v1":
errors.append(f"worker subprotocol mismatch: server={worker_server!r} sdk={worker_sdk!r}")
if event_server != event_sdk or event_server != "orbit.events.v1":
errors.append(f"event subprotocol mismatch: server={event_server!r} sdk={event_sdk!r}")
def version_literals(relative):
text = read(relative)
# Frame constructors and schema-facing values must stay on protocol line 1.
return set(re.findall(r'\bVersion\s*:\s*"([^"]+)"', text))
for relative in (
"internal/transport/httpserver/worker.go",
"internal/transport/httpserver/event_hub.go",
"sdk/go/worker_connection.go",
"sdk/go/event_connection.go",
):
values = version_literals(relative)
if values and values != {"1"}:
errors.append(f"unexpected frame versions in {relative}: {sorted(values)}")
def schema_protocol_versions(relative, expected_id):
path = root / relative
try:
document = json.loads(path.read_text())
except Exception as exc:
errors.append(f"cannot parse {relative}: {exc}")
return
if document.get("$id") != expected_id:
errors.append(f"schema id mismatch in {relative}: {document.get('$id')!r}")
values = []
def walk(value):
if isinstance(value, dict):
field = value.get("protocol_version")
if isinstance(field, dict) and "const" in field:
values.append(field["const"])
for child in value.values():
walk(child)
elif isinstance(value, list):
for child in value:
walk(child)
walk(document)
if not values or set(values) != {"1"}:
errors.append(f"schema protocol versions in {relative}: {sorted(set(values))}")
schema_protocol_versions("protocol/workerv1/worker.schema.json", "https://git.yuntuo.hk/k2safe/orbit/schema/worker.v1.json")
schema_protocol_versions("protocol/eventsv1/events.schema.json", "https://git.yuntuo.hk/k2safe/orbit/schema/events.v1.json")
worker_server_text = read("internal/transport/httpserver/worker.go")
worker_sdk_text = read("sdk/go/worker_connection.go")
event_server_text = read("internal/transport/httpserver/event_hub.go")
event_sdk_text = read("sdk/go/event_connection.go")
if 'GET /v1/worker/connect' not in worker_server_text or '"/v1/worker/connect"' not in worker_sdk_text:
errors.append("worker v1 route mismatch")
if 'GET /v1/events/connect' not in event_server_text or '"/v1/events/connect"' not in event_sdk_text:
errors.append("event v1 route mismatch")
if errors:
for error in errors:
print(error, file=sys.stderr)
raise SystemExit(1)
print(json.dumps({
"http_api": "/v1",
"worker_subprotocol": worker_server,
"event_subprotocol": event_server,
"worker_frame_version": "1",
"event_frame_version": "1",
"workflow_schema_version": 1,
"status": "current_contracts_match",
"historical_pairs": "not_verified",
}, separators=(",", ":")))
PY