Files
molecule-core/scripts/test-team-e2e.sh
hongming f820780036
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
CI / Canvas Deploy Reminder (pull_request) Blocked by required conditions
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 9s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 5s
CI / Detect changes (pull_request) Successful in 6s
CI / Python Lint & Test (pull_request) Successful in 3s
E2E API Smoke Test / detect-changes (pull_request) Successful in 6s
E2E Chat / detect-changes (pull_request) Successful in 10s
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (pull_request) Has been skipped
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 12s
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (local) (pull_request) Successful in 48s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 4s
Harness Replays / detect-changes (pull_request) Successful in 5s
E2E Staging SaaS (full lifecycle) / pr-validate (pull_request) Successful in 39s
Lint curl status-code capture / Scan workflows for curl status-capture pollution (pull_request) Successful in 6s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 4s
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 5s
lint-continue-on-error-tracking / lint-continue-on-error-tracking (pull_request) Successful in 1m8s
Lint pre-flip continue-on-error / Verify continue-on-error flips have run-log proof (pull_request) Successful in 1m10s
lint-required-workflows-docker-host-pinned / Lint docker-host pin on docker-touching workflows (pull_request) Successful in 5s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 53s
lint-required-context-exists-in-bp / lint-required-context-exists-in-bp (pull_request) Successful in 1m23s
Lint workflow YAML (Gitea-1.22.6-hostile shapes) / Lint workflow YAML for Gitea-1.22.6-hostile shapes (pull_request) Successful in 1m23s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 5s
gate-check-v3 / gate-check (pull_request) Successful in 5s
sop-checklist / review-refire (pull_request) Has been skipped
sop-checklist / na-declarations (pull_request) N/A: (none)
sop-checklist / all-items-acked (pull_request) Successful in 4s
sop-tier-check / tier-check (pull_request) Successful in 3s
Ops Scripts Tests / Ops scripts (unittest) (pull_request) Successful in 1m8s
E2E Staging External Runtime / E2E Staging External Runtime (pull_request) Successful in 5m11s
E2E Staging SaaS (full lifecycle) / E2E Staging SaaS (pull_request) Successful in 5m40s
qa-review / approved (pull_request) Refired via /qa-recheck by codex-local
security-review / approved (pull_request) Refired via /security-recheck by codex-local
CI / Shellcheck (E2E scripts) (pull_request) Successful in 33s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 2m19s
E2E Chat / E2E Chat (pull_request) Successful in 6s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 3s
Harness Replays / Harness Replays (pull_request) Successful in 3s
CI / Platform (Go) (pull_request) Successful in 10m5s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 2m3s
CI / Canvas (Next.js) (pull_request) Successful in 9m45s
CI / all-required (pull_request) Successful in 31m8s
audit-force-merge / audit (pull_request) Successful in 14s
chore: restrict maintained workspace runtimes
2026-05-24 19:48:00 -07:00

245 lines
10 KiB
Bash
Executable File

#!/usr/bin/env bash
# E2E test: Create a team with different templates/models, test A2A communication.
# Everything via platform API — no manual file edits.
set -euo pipefail
PLATFORM="${1:-http://localhost:8080}"
OR_KEY="${OPENAI_API_KEY:-${OPENROUTER_API_KEY:?Set OPENAI_API_KEY or OPENROUTER_API_KEY env var}}"
PASS=0
FAIL=0
check() {
local label="$1" expected="$2" actual="$3"
if echo "$actual" | grep -q "$expected"; then
echo "PASS: $label"
PASS=$((PASS + 1))
else
echo "FAIL: $label"
echo " expected: $expected"
echo " got: $actual"
FAIL=$((FAIL + 1))
fi
}
wait_online() {
local id="$1" name="$2"
for i in $(seq 1 20); do
local s
s=$(curl -s "$PLATFORM/workspaces/$id" | python3 -c "import sys,json; print(json.load(sys.stdin).get('status',''))" 2>/dev/null)
[ "$s" = "online" ] && return 0
sleep 3
done
echo " WARNING: $name did not come online within 60s"
return 1
}
echo "============================================"
echo " E2E Test: Multi-Template Team + A2A"
echo "============================================"
echo ""
# -------------------------------------------------------
# Step 1: Create workspaces from different templates
# -------------------------------------------------------
echo "--- Step 1: Create workspaces ---"
# PM — Claude Code (uses OAuth token from template)
R=$(curl -s -X POST "$PLATFORM/workspaces" -H 'Content-Type: application/json' \
-d '{"name":"PM","role":"Project Manager — coordinates the team","tier":2,"template":"claude-code-default"}')
PM_ID=$(echo "$R" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
check "Create PM (claude-code)" "provisioning" "$R"
# Research Agent — Claude Code + Gemini Flash
R=$(curl -s -X POST "$PLATFORM/workspaces" -H 'Content-Type: application/json' \
-d '{"name":"Researcher","role":"Deep research and analysis","tier":2,"template":"claude-code-default"}')
RES_ID=$(echo "$R" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
check "Create Researcher (claude-code)" "provisioning" "$R"
# Dev Agent — OpenClaw + Gemini Flash
R=$(curl -s -X POST "$PLATFORM/workspaces" -H 'Content-Type: application/json' \
-d '{"name":"Developer","role":"Code implementation and review","tier":2,"template":"openclaw"}')
DEV_ID=$(echo "$R" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
check "Create Developer (openclaw)" "provisioning" "$R"
# Analyst — Hermes + Gemini Flash
R=$(curl -s -X POST "$PLATFORM/workspaces" -H 'Content-Type: application/json' \
-d '{"name":"Analyst","role":"Data analysis and reporting","tier":2,"template":"hermes"}')
ANA_ID=$(echo "$R" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
check "Create Analyst (hermes)" "provisioning" "$R"
echo ""
echo " PM: $PM_ID"
echo " Researcher: $RES_ID"
echo " Developer: $DEV_ID"
echo " Analyst: $ANA_ID"
# -------------------------------------------------------
# Step 2: Set parent hierarchy via API
# -------------------------------------------------------
echo ""
echo "--- Step 2: Set parent hierarchy ---"
for CHILD_ID in $RES_ID $DEV_ID $ANA_ID; do
R=$(curl -s -X PATCH "$PLATFORM/workspaces/$CHILD_ID" -H 'Content-Type: application/json' \
-d "{\"parent_id\":\"$PM_ID\"}")
check "Set parent for $(echo $CHILD_ID | cut -c1-8)..." "updated" "$R"
done
# -------------------------------------------------------
# Step 3: Set secrets via API (OpenRouter key for non-Claude agents)
# -------------------------------------------------------
echo ""
echo "--- Step 3: Set API keys via secrets API ---"
for CHILD_ID in $RES_ID $DEV_ID $ANA_ID; do
R=$(curl -s -X POST "$PLATFORM/workspaces/$CHILD_ID/secrets" -H 'Content-Type: application/json' \
-d "{\"key\":\"OPENROUTER_API_KEY\",\"value\":\"$OR_KEY\"}")
check "Set OPENROUTER_API_KEY for $(echo $CHILD_ID | cut -c1-8)..." "saved" "$R"
done
# -------------------------------------------------------
# Step 4: Customize system prompts via Files API
# -------------------------------------------------------
echo ""
echo "--- Step 4: Wait for all to come online ---"
for name_id in "PM:$PM_ID" "Researcher:$RES_ID" "Developer:$DEV_ID" "Analyst:$ANA_ID"; do
IFS=: read name id <<< "$name_id"
if wait_online "$id" "$name"; then
check "$name online" "online" "online"
else
check "$name online" "online" "timeout"
fi
done
# -------------------------------------------------------
# Step 5: Customize prompts via Files API (containers are online now)
# -------------------------------------------------------
echo ""
echo "--- Step 5: Customize prompts via Files API ---"
R=$(curl -s -X PUT "$PLATFORM/workspaces/$RES_ID/files/system-prompt.md" -H 'Content-Type: application/json' \
-d '{"content":"You are a research agent. When asked to introduce yourself, say: I am the Researcher agent."}')
check "Set Researcher prompt" "saved" "$R"
R=$(curl -s -X PUT "$PLATFORM/workspaces/$DEV_ID/files/SOUL.md" -H 'Content-Type: application/json' \
-d '{"content":"You are a developer agent. When asked to introduce yourself, say: I am the Developer agent."}')
check "Set Developer prompt (SOUL.md)" "saved" "$R"
R=$(curl -s -X PUT "$PLATFORM/workspaces/$ANA_ID/files/system-prompt.md" -H 'Content-Type: application/json' \
-d '{"content":"You are an analyst agent. When asked to introduce yourself, say: I am the Analyst agent."}')
check "Set Analyst prompt" "saved" "$R"
# -------------------------------------------------------
# Step 6: Restart to pick up new prompts + secrets
# -------------------------------------------------------
echo ""
echo "--- Step 6: Restart agents ---"
for ID in $RES_ID $DEV_ID $ANA_ID; do
curl -s -X POST "$PLATFORM/workspaces/$ID/restart" > /dev/null
done
echo "Restarting 3 agents... waiting 30s"
sleep 30
for name_id in "Researcher:$RES_ID" "Developer:$DEV_ID" "Analyst:$ANA_ID"; do
IFS=: read name id <<< "$name_id"
if wait_online "$id" "$name"; then
check "$name back online" "online" "online"
else
check "$name back online" "online" "timeout"
fi
done
# -------------------------------------------------------
# Step 7: Verify files in containers (no host ws-* dirs)
# -------------------------------------------------------
echo ""
echo "--- Step 7: Verify config files in containers ---"
R=$(curl -s "$PLATFORM/workspaces/$RES_ID/files" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))")
check "Researcher has files" "[12]" "$R"
R=$(curl -s "$PLATFORM/workspaces/$DEV_ID/files" | python3 -c "import sys,json; files=json.load(sys.stdin); print(' '.join(f['path'] for f in files if not f['dir']))")
check "Developer has OpenClaw files" "SOUL.md" "$R"
# Verify NO ws-* dirs on host
HOST_WS=$(find /Users/hongming/Documents/GitHub/molecule-monorepo/workspace-configs-templates -maxdepth 1 -name 'ws-*' -type d 2>/dev/null | wc -l | tr -d ' ')
check "No ws-* dirs on host" "0" "$HOST_WS"
# -------------------------------------------------------
# Step 8: Test A2A — direct messages
# -------------------------------------------------------
echo ""
echo "--- Step 8: A2A direct messages ---"
# Talk to Researcher
R=$(curl -s -X POST "$PLATFORM/workspaces/$RES_ID/a2a" -H 'Content-Type: application/json' \
-d '{"method":"message/send","params":{"message":{"role":"user","parts":[{"kind":"text","text":"introduce yourself in one sentence"}]}}}')
RESP=$(echo "$R" | python3 -c "import sys,json; print(json.load(sys.stdin).get('result',{}).get('parts',[{}])[0].get('text','ERROR')[:200])" 2>/dev/null)
echo " Researcher says: $RESP"
check "Researcher responds" "Researcher" "$RESP"
# Talk to Developer
R=$(curl -s -X POST "$PLATFORM/workspaces/$DEV_ID/a2a" -H 'Content-Type: application/json' \
-d '{"method":"message/send","params":{"message":{"role":"user","parts":[{"kind":"text","text":"introduce yourself in one sentence"}]}}}')
RESP=$(echo "$R" | python3 -c "import sys,json; print(json.load(sys.stdin).get('result',{}).get('parts',[{}])[0].get('text','ERROR')[:200])" 2>/dev/null)
echo " Developer says: $RESP"
check "Developer responds" "Developer" "$RESP"
# Talk to Analyst
R=$(curl -s -X POST "$PLATFORM/workspaces/$ANA_ID/a2a" -H 'Content-Type: application/json' \
-d '{"method":"message/send","params":{"message":{"role":"user","parts":[{"kind":"text","text":"introduce yourself in one sentence"}]}}}')
RESP=$(echo "$R" | python3 -c "import sys,json; print(json.load(sys.stdin).get('result',{}).get('parts',[{}])[0].get('text','ERROR')[:200])" 2>/dev/null)
echo " Analyst says: $RESP"
check "Analyst responds" "Analyst" "$RESP"
# -------------------------------------------------------
# Step 9: Test peer discovery
# -------------------------------------------------------
echo ""
echo "--- Step 9: Peer discovery ---"
R=$(curl -s "$PLATFORM/registry/${PM_ID}/peers" | python3 -c "import sys,json; peers=json.load(sys.stdin); print(len(peers))")
check "PM has 3 peers (children)" "3" "$R"
R=$(curl -s "$PLATFORM/registry/${RES_ID}/peers" | python3 -c "import sys,json; peers=json.load(sys.stdin); names=[p.get('name','') for p in peers]; print(' '.join(names))")
check "Researcher sees siblings" "Developer" "$R"
# -------------------------------------------------------
# Step 10: Test cross-workspace access control
# -------------------------------------------------------
echo ""
echo "--- Step 10: Access control ---"
# Parent <-> child (PM <-> Researcher) should be allowed
R=$(curl -s -X POST "$PLATFORM/registry/check-access" -H 'Content-Type: application/json' \
-d "{\"caller_id\":\"$PM_ID\",\"target_id\":\"$RES_ID\"}")
check "PM -> Researcher allowed" "true" "$R"
# Siblings (Researcher <-> Developer) should be allowed
R=$(curl -s -X POST "$PLATFORM/registry/check-access" -H 'Content-Type: application/json' \
-d "{\"caller_id\":\"$RES_ID\",\"target_id\":\"$DEV_ID\"}")
check "Researcher -> Developer allowed" "true" "$R"
# -------------------------------------------------------
# Step 11: Cleanup
# -------------------------------------------------------
echo ""
echo "--- Step 11: Cleanup ---"
curl -s -X DELETE "$PLATFORM/workspaces/$PM_ID" > /dev/null
check "Delete PM (cascades)" "ok" "ok"
REMAINING=$(curl -s "$PLATFORM/workspaces" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))")
check "All workspaces deleted" "0" "$REMAINING"
# -------------------------------------------------------
# Results
# -------------------------------------------------------
echo ""
echo "============================================"
echo " Results: $PASS passed, $FAIL failed"
echo "============================================"
exit $FAIL