Files
vortex/scripts/verify.sh
T
Prad NukalaandClaude Fable 5 760d9db800 feat: guided bridge login wizard in TUI via bridgev2 provisioning API
Bootstrap now enables each bridge's provisioning API (persisted shared
secret, matrix-token auth) and publishes /shared/bridges.json. The TUI
bridge screen shows per-bridge login state and launches a step-driven
wizard: flow picker, sequential input prompts (phone/cookies/password),
in-terminal QR rendering with long-poll wait, cancel support. Discord
(legacy bridge, no v3 API) keeps the bot-DM path via 'm'. verify.sh
gains a provisioning API smoke check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 12:36:26 -04:00

116 lines
4.8 KiB
Bash

#!/bin/bash
# Smoke tests: stack health, appservice round-trip, MCP tools + draft gating.
set -euo pipefail
cd "$(dirname "$0")/.."
set -a; source .env; set +a
HS=http://localhost:8008
pass() { echo "PASS: $*"; }
fail() { echo "FAIL: $*"; exit 1; }
echo "== 1/5 containers"
docker compose ps --format '{{.Service}} {{.State}} {{.Health}}' | tee /tmp/vortex-ps.txt
for svc in postgres synapse mcp; do
grep -q "^$svc running" /tmp/vortex-ps.txt || fail "$svc not running"
done
IFS=',' read -ra BRIDGES <<< "${BRIDGES_ENABLED:-}"
for b in "${BRIDGES[@]}"; do
grep -q "^$b running" /tmp/vortex-ps.txt || fail "bridge $b not running"
done
for oneshot in bootstrap provision; do
st=$(docker compose ps -a --format '{{.Service}} {{.ExitCode}}' $oneshot | awk '{print $2}')
[ "$st" = 0 ] || fail "$oneshot exit code $st"
done
pass "all services up, one-shots exited 0"
echo "== 2/5 synapse health + login"
curl -sf $HS/health >/dev/null || fail "synapse /health"
TOKEN=$(curl -sf -X POST $HS/_matrix/client/v3/login -d "{
\"type\":\"m.login.password\",
\"identifier\":{\"type\":\"m.id.user\",\"user\":\"$MATRIX_USER\"},
\"password\":\"$MATRIX_PASSWORD\",\"initial_device_display_name\":\"verify\"}" |
python3 -c 'import json,sys;print(json.load(sys.stdin)["access_token"])')
[ -n "$TOKEN" ] || fail "login"
pass "synapse healthy, admin login works"
echo "== 3/5 bridge bot round-trip"
FIRST_BRIDGE=$(echo "${BRIDGES_ENABLED}" | cut -d, -f1)
BOT="@${FIRST_BRIDGE}bot:${MATRIX_SERVER_NAME}"
python3 - "$HS" "$TOKEN" "$BOT" <<'EOF' || fail "bridge bot did not respond"
import json, sys, time, urllib.request
hs, token, bot = sys.argv[1:4]
def api(method, path, body=None):
req = urllib.request.Request(hs + path, method=method,
data=json.dumps(body).encode() if body is not None else None,
headers={"Authorization": "Bearer " + token})
return json.load(urllib.request.urlopen(req))
room = api("POST", "/_matrix/client/v3/createRoom",
{"invite": [bot], "is_direct": True, "preset": "trusted_private_chat"})["room_id"]
api("PUT", f"/_matrix/client/v3/rooms/{room}/send/m.room.message/{time.time_ns()}",
{"msgtype": "m.text", "body": "help"})
deadline = time.time() + 30
since = None
while time.time() < deadline:
q = f"/_matrix/client/v3/sync?timeout=5000" + (f"&since={since}" if since else "")
resp = api("GET", q)
since = resp["next_batch"]
events = resp.get("rooms", {}).get("join", {}).get(room, {}).get("timeline", {}).get("events", [])
for e in events:
if e["sender"] == bot and e["type"] == "m.room.message":
print("bot replied:", e["content"]["body"][:80].replace("\n", " "))
sys.exit(0)
sys.exit(1)
EOF
pass "appservice round-trip via $BOT"
echo "== 4/5 bridge provisioning API (guided TUI login)"
docker compose exec -T mcp python - "$TOKEN" <<'EOF' || fail "provisioning API"
import json, sys, urllib.request, urllib.parse
tok = sys.argv[1]
bridges = json.load(open("/shared/bridges.json"))
net, info = next((n, i) for n, i in bridges.items() if i["kind"] == "v2")
url = f"{info['base_url']}/_matrix/provision/v3/login/flows?user_id=%40admin%3Alocalhost"
req = urllib.request.Request(url, headers={"Authorization": "Bearer " + tok})
flows = json.load(urllib.request.urlopen(req))["flows"]
assert flows, "no login flows"
print(f"{net} login flows: {[f['id'] for f in flows]}")
EOF
pass "provisioning API reachable with matrix-token auth"
echo "== 5/5 MCP tools + draft gating"
docker compose exec -T mcp python - <<'EOF' || fail "mcp checks"
import asyncio, json, os
from fastmcp import Client
async def main():
port = os.environ.get("MCP_HTTP_PORT", "8765")
async with Client(f"http://localhost:{port}/mcp") as c:
tools = {t.name for t in await c.list_tools()}
assert {"list_rooms", "read_messages", "create_draft", "send_draft"} <= tools, tools
rooms = (await c.call_tool("list_rooms")).data
assert isinstance(rooms, list), rooms
print(f"list_rooms: {len(rooms)} rooms")
room_id = rooms[0]["room_id"] if rooms else "!dummy:localhost"
d = (await c.call_tool("create_draft",
{"room_id": room_id, "body": "verify draft"})).data
did = d["draft_id"]
drafts = (await c.call_tool("list_drafts")).data
assert any(x["draft_id"] == did for x in drafts)
try:
await c.call_tool("send_draft", {"draft_id": did})
raise SystemExit("send_draft should have been blocked")
except Exception as e:
assert "disabled" in str(e).lower() or "human" in str(e).lower(), e
print("send_draft correctly blocked:", str(e)[:80])
await c.call_tool("discard_draft", {"draft_id": did})
print("draft lifecycle OK")
asyncio.run(main())
EOF
pass "mcp list_rooms + draft lifecycle + send gating"
echo
echo "ALL CHECKS PASSED"