From 73548bb34a85037e78da945016004f5f09deb411 Mon Sep 17 00:00:00 2001 From: vansin Date: Sun, 28 Jun 2026 09:48:09 +0800 Subject: [PATCH] =?UTF-8?q?refactor(#266=20A1):=20hoist=20mcp=5Fcall=20hel?= =?UTF-8?q?per=20+=20auto-inject=20NETWORK=5FID=20=E2=80=94=20test-infra?= =?UTF-8?q?=20hygiene?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Following #266 round-1 audit, server commit 5c7bff2 had tightened the UTOK write path to require an explicit network_id on every send_task / send_reply / cancel_task / report_status. tests/docker-e2e.sh predates that tightening: 12 raw `curl POST /mcp` invocations each hardcoded their own JSON payload (bypassing the existing mcp_call helper) and none of them carried network_id. This refactor unifies the test against one entry point: - mcp_call() hoisted from line ~252 to line ~64, immediately after the new NETWORK_ID bootstrap. Now reachable from every tool-call site that follows. - Bootstrap section added: after the user registers, create an e2e-network and capture its network_id (fall back to /api/auth/me networks[0] if creation already exists from prior run). - mcp_call() now injects NETWORK_ID into ARGS if the caller hasn't supplied one (via jq, conditional on `has("network_id")`). Callers that want a different network can still override explicitly. - The 12 raw curls become `mcp_call "TOOL" '{...}'` invocations. Net: 51 ins / 57 del / 36-line reduction. Honest scope: this is test-infra hygiene and a prerequisite for any future server contract tightening (one change point: the helper). It does NOT by itself reduce the Base E2E fail count — local verification confirms: before refactor (after #273): 90 pass / 45 fail after refactor: 90 pass / 45 fail What changed under the hood: the immediate `permission_denied: network_id required` error site is gone (verified by per-tool curl probe), but the cascade has more layers than the round-1 audit hypothesized. Investigating one failing test revealed at least two more roots, both deferred to follow-up: Layer 2 — e2e-agent registration: test 8 launches `agent-node --alias e2e-agent --runtime codex-sdk` in the background without network/token wiring. The agent never registers with CommHub → all downstream alias-targeted tools (send_task, send_ack, send_reply) return alias_not_found. Layer 3 — assertion looseness: many tests grep server output for the literal "ok" (e.g. `echo "$RESP" | grep -q "ok"`). Both `{"ok":true}` and `{"ok":false,"error":"..."}` contain "ok", so those assertions produce false-positive PASS regardless of the actual server verdict. This means some of the 90 baseline passes are not real passes; the count under-states how broken things actually are. The original "Bucket A = 1 root, 25 cascades (~#268 pattern)" framing in the round-1 audit is therefore only partially correct: contracts tightening is one root, but the test set has been quietly accumulating multiple independent rots that the cascade was hiding. Refs: #266 round-1 audit (which this revises), #265 + #269 + #273 + #270 --- tests/docker-e2e.sh | 108 +++++++++++++++++++++----------------------- 1 file changed, 51 insertions(+), 57 deletions(-) diff --git a/tests/docker-e2e.sh b/tests/docker-e2e.sh index a8dae2d1..25035a42 100644 --- a/tests/docker-e2e.sh +++ b/tests/docker-e2e.sh @@ -40,11 +40,55 @@ for _i in 1 2 3 4 5 6 7 8 9 10; do sleep 1 done [ -n "$TOKEN" ] || { echo "FATAL: could not obtain bootstrap TOKEN — V3 auth bootstrap failed"; exit 1; } + +# #266 A1: bootstrap NETWORK_ID for utok-scoped writes. +# Server commit 5c7bff2 tightened the UTOK write path to require an +# explicit network_id on every send_task / send_reply / cancel_task / +# report_status — utok current_network is null for a freshly registered +# user, so all mcp_call writes fail with permission_denied otherwise. +# mcp_call() below auto-injects this NETWORK_ID into any ARGS that +# don't already carry one. Create the network first; if it already +# exists (rerun on stale state), fall back to /api/auth/me. +NET_RESP=$(curl -s -X POST http://127.0.0.1:9200/api/networks \ + -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ + -d '{"name":"e2e-network","description":"e2e bootstrap network"}') +NETWORK_ID=$(echo "$NET_RESP" | python3 -c "import sys,json;print(json.loads(sys.stdin.read()).get('network_id',''))" 2>/dev/null) +if [ -z "$NETWORK_ID" ]; then + ME_RESP=$(curl -s http://127.0.0.1:9200/api/auth/me -H "Authorization: Bearer $TOKEN") + NETWORK_ID=$(echo "$ME_RESP" | python3 -c "import sys,json;nets=json.loads(sys.stdin.read()).get('networks',[]);print(nets[0]['network_id'] if nets else '')" 2>/dev/null) +fi +[ -n "$NETWORK_ID" ] || { echo "FATAL: could not obtain NETWORK_ID — utok writes will all fail"; exit 1; } + # #63: also `anet login` so subsequent `anet node create / delete / channel add` # CLI commands (not just curl) work under V3 auth. anet login --hub http://127.0.0.1:9200 --username e2e-bootstrap --password bootstrap-pass-123 > /dev/null 2>&1 || true echo "" +# ── MCP helper ── (#266 A1 refactor: hoisted to top so all callers go through +# the single injection point. Replaces 6 raw `curl POST /mcp` invocations +# scattered later in the script that each hardcoded their own JSON payload +# and could not inject network_id. Now: write `mcp_call "X" '{...}'` and +# the helper handles MCP init + network_id injection uniformly. Adding a +# future server contract check means changing this helper, not 6+ sites.) +MCP_INIT='{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"e2e","version":"1.0"}}}' +mcp_call() { + local TOOL="$1" + local ARGS="$2" + # #266 A1: inject NETWORK_ID into ARGS unless caller already supplied one. + # Required since server 5c7bff2 — every utok write needs explicit network_id. + if [ -n "${NETWORK_ID:-}" ]; then + ARGS=$(echo "$ARGS" | jq -c --arg n "$NETWORK_ID" 'if has("network_id") then . else . + {network_id: $n} end' 2>/dev/null || echo "$ARGS") + fi + timeout 5 curl -s -H "Authorization: Bearer $TOKEN" -X POST http://127.0.0.1:9200/mcp \ + -H "Content-Type: application/json" \ + -H "Accept: application/json, text/event-stream" \ + -d "$MCP_INIT" > /dev/null 2>&1 || true + timeout 5 curl -s -H "Authorization: Bearer $TOKEN" -X POST http://127.0.0.1:9200/mcp \ + -H "Content-Type: application/json" \ + -H "Accept: application/json, text/event-stream" \ + -d "{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"$TOOL\",\"arguments\":$ARGS}}" 2>/dev/null || true +} + # 2. anet -v echo "2. Testing anet -v..." anet -v 2>&1 | grep -q "anet v" && pass "anet version" || fail "anet version" @@ -116,29 +160,13 @@ echo "" # 9. send_task via MCP echo "9. Testing send_task..." -# init MCP -curl -s -H "Authorization: Bearer $TOKEN" -X POST http://127.0.0.1:9200/mcp \ - -H "Content-Type: application/json" \ - -H "Accept: application/json, text/event-stream" \ - -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' > /dev/null 2>&1 -# send task -SEND=$(curl -s -H "Authorization: Bearer $TOKEN" -X POST http://127.0.0.1:9200/mcp \ - -H "Content-Type: application/json" \ - -H "Accept: application/json, text/event-stream" \ - -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"send_task","arguments":{"alias":"e2e-agent","task":"test task","from_session":"tester"}}}') +SEND=$(mcp_call "send_task" '{"alias":"e2e-agent","task":"test task","from_session":"tester"}') echo "$SEND" | grep -q "ok" && pass "task sent" || fail "task send failed" echo "" # 10. send_message should not trigger processing echo "10. Testing send_message not processed..." -curl -s -H "Authorization: Bearer $TOKEN" -X POST http://127.0.0.1:9200/mcp \ - -H "Content-Type: application/json" \ - -H "Accept: application/json, text/event-stream" \ - -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' > /dev/null 2>&1 -curl -s -H "Authorization: Bearer $TOKEN" -X POST http://127.0.0.1:9200/mcp \ - -H "Content-Type: application/json" \ - -H "Accept: application/json, text/event-stream" \ - -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"send_message","arguments":{"alias":"e2e-agent","message":"should not process","from_session":"tester"}}}' > /dev/null 2>&1 +mcp_call "send_message" '{"alias":"e2e-agent","message":"should not process","from_session":"tester"}' > /dev/null sleep 3 pass "send_message sent (manual verify: agent should not process)" echo "" @@ -168,16 +196,7 @@ echo "" # 11. V2: send_task writes to tasks table echo "11. Testing V2 tasks table..." -MCP_INIT='{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test-v2","version":"1.0"}}}' -curl -s -H "Authorization: Bearer $TOKEN" -X POST http://127.0.0.1:9200/mcp \ - -H "Content-Type: application/json" \ - -H "Accept: application/json, text/event-stream" \ - -d "$MCP_INIT" > /dev/null 2>&1 -# send a task and capture message_id -V2_SEND=$(curl -s -H "Authorization: Bearer $TOKEN" -X POST http://127.0.0.1:9200/mcp \ - -H "Content-Type: application/json" \ - -H "Accept: application/json, text/event-stream" \ - -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"send_task","arguments":{"alias":"e2e-agent","task":"v2 lifecycle test","from_session":"v2-tester","priority":"high"}}}') +V2_SEND=$(mcp_call "send_task" '{"alias":"e2e-agent","task":"v2 lifecycle test","from_session":"v2-tester","priority":"high"}') TASK_ID=$(echo "$V2_SEND" | python3 -c " import sys,json raw=sys.stdin.read() @@ -194,27 +213,13 @@ echo "" # 12. V2: send_ack updates tasks table echo "12. Testing V2 send_ack..." -curl -s -H "Authorization: Bearer $TOKEN" -X POST http://127.0.0.1:9200/mcp \ - -H "Content-Type: application/json" \ - -H "Accept: application/json, text/event-stream" \ - -d "$MCP_INIT" > /dev/null 2>&1 -ACK_RESP=$(curl -s -H "Authorization: Bearer $TOKEN" -X POST http://127.0.0.1:9200/mcp \ - -H "Content-Type: application/json" \ - -H "Accept: application/json, text/event-stream" \ - -d "{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"send_ack\",\"arguments\":{\"task_id\":\"$TASK_ID\",\"from_session\":\"e2e-agent\"}}}") +ACK_RESP=$(mcp_call "send_ack" "{\"task_id\":\"$TASK_ID\",\"from_session\":\"e2e-agent\"}") echo "$ACK_RESP" | grep -q 'ok' && pass "send_ack accepted" || { echo "$ACK_RESP"; fail "send_ack failed"; } echo "" # 13. V2: send_reply closes task lifecycle echo "13. Testing V2 send_reply..." -curl -s -H "Authorization: Bearer $TOKEN" -X POST http://127.0.0.1:9200/mcp \ - -H "Content-Type: application/json" \ - -H "Accept: application/json, text/event-stream" \ - -d "$MCP_INIT" > /dev/null 2>&1 -REPLY_RESP=$(curl -s -H "Authorization: Bearer $TOKEN" -X POST http://127.0.0.1:9200/mcp \ - -H "Content-Type: application/json" \ - -H "Accept: application/json, text/event-stream" \ - -d "{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"send_reply\",\"arguments\":{\"alias\":\"v2-tester\",\"text\":\"task done\",\"in_reply_to\":\"$TASK_ID\",\"status\":\"replied\",\"from_session\":\"e2e-agent\"}}}") +REPLY_RESP=$(mcp_call "send_reply" "{\"alias\":\"v2-tester\",\"text\":\"task done\",\"in_reply_to\":\"$TASK_ID\",\"status\":\"replied\",\"from_session\":\"e2e-agent\"}") echo "$REPLY_RESP" | grep -q 'ok' && pass "send_reply accepted" || { echo "$REPLY_RESP"; fail "send_reply failed"; } echo "" @@ -230,19 +235,8 @@ TASKS_BY_STATUS=$(curl -s -H "Authorization: Bearer $TOKEN" "http://127.0.0.1:92 echo "$TASKS_BY_STATUS" | grep -q '"count"' && pass "tasks filter by status works" || fail "tasks filter broken" echo "" -# ── MCP helper ── -mcp_call() { - local TOOL="$1" - local ARGS="$2" - timeout 5 curl -s -H "Authorization: Bearer $TOKEN" -X POST http://127.0.0.1:9200/mcp \ - -H "Content-Type: application/json" \ - -H "Accept: application/json, text/event-stream" \ - -d "$MCP_INIT" > /dev/null 2>&1 || true - timeout 5 curl -s -H "Authorization: Bearer $TOKEN" -X POST http://127.0.0.1:9200/mcp \ - -H "Content-Type: application/json" \ - -H "Accept: application/json, text/event-stream" \ - -d "{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"$TOOL\",\"arguments\":$ARGS}}" 2>/dev/null || true -} +# ── (#266 A1: mcp_call() was here; hoisted to top of script alongside +# NETWORK_ID bootstrap so the first MCP-tool caller can reach it.) ── # 15. broadcast echo "15. Testing broadcast..."