test: add current protocol compatibility gate
This commit is contained in:
107
scripts/test-protocol-compatibility.sh
Executable file
107
scripts/test-protocol-compatibility.sh
Executable file
@@ -0,0 +1,107 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user