molecule-core/tests/e2e/test_claude_code_e2e.sh
Hongming Wang c84b9998b6 chore: apply code-review round-6 suggestions
All 5 suggestions from the latest review pass.

## tests/e2e/_extract_token.py (new)
Extracted the 14-line python-in-bash heredoc from _lib.sh into a real
Python file. Easier to edit, fewer escaping traps, same behavior.
Shell helper now just shells out to it.

## tests/e2e/_lib.sh
- Replaced inline python with: python3 "$(dirname "${BASH_SOURCE[0]}")/_extract_token.py"
- Removed redundant sys.exit(0) as part of the extraction

## Shellcheck-clean scripts (new CI job enforces)
- Removed dead captures: BEFORE_COUNT (test_activity_e2e.sh), ORIG_SKILLS,
  REIMPORT_SKILLS (test_api.sh), QA_TOKEN (test_comprehensive_e2e.sh)
- Renamed unused loop vars `i`, `j` -> `_` in 4 sites
- Added `# shellcheck disable=SC2046` on the two intentional word-splits
  in test_claude_code_e2e.sh (docker stop/rm of multiple container IDs)
- Removed a useless re-register of QA mid-script (was done in Section 2)

## CI (.github/workflows/ci.yml)
- Replaced `sudo apt-get install postgresql-client` + psql with a direct
  `docker exec` into the existing postgres:16 service container. Saves
  ~10-20s per CI run.
- Added new `shellcheck` job that lints tests/e2e/*.sh on every PR.
  Local: shellcheck --severity=warning returns 0 across all 5 scripts.

## Verification
- go test -race ./internal/handlers/... : pass
- mcp-server: 96/96 jest
- canvas: 357/357 vitest + clean build
- tests/e2e/test_api.sh: 62/62
- tests/e2e/test_comprehensive_e2e.sh: 67/67
- shellcheck tests/e2e/*.sh : clean
- CI YAML: valid

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 17:08:45 -07:00

146 lines
6.4 KiB
Bash
Executable File

#!/bin/bash
# Full E2E test for Claude Code workspace runtime
# Run from repo root after: docker compose up -d && docker build -t workspace-template:latest workspace-template/
#
# Prerequisites:
# - Platform running on localhost:8080
# - workspace-template:latest image built
# - .auth-token in workspace-configs-templates/claude-code-default/
set -euo pipefail
PLATFORM="http://localhost:8080"
PASS=0
FAIL=0
ERRORS=""
pass() { echo "PASS: $1"; PASS=$((PASS+1)); }
fail() { echo "FAIL: $1"; echo " expected: $2"; echo " got: $3"; FAIL=$((FAIL+1)); ERRORS="$ERRORS\n - $1"; }
check_contains() {
if echo "$3" | grep -qi "$2"; then pass "$1"; else fail "$1" "contains '$2'" "$3"; fi
}
# --- Health Check ---
echo "=== Claude Code E2E Tests ==="
echo ""
HEALTH=$(curl -s $PLATFORM/health)
check_contains "Platform healthy" '"status":"ok"' "$HEALTH"
# --- Verify auth token exists ---
if [ -f workspace-configs-templates/claude-code-default/.auth-token ]; then
pass "Auth token file exists"
else
fail "Auth token file exists" "file present" "missing"
echo "FATAL: No .auth-token. Write your Claude Code OAuth token to workspace-configs-templates/claude-code-default/.auth-token"
exit 1
fi
# --- Clean existing workspaces ---
for id in $(curl -s $PLATFORM/workspaces | python3 -c "import sys,json; [print(w['id']) for w in json.load(sys.stdin)]" 2>/dev/null); do
curl -s -X DELETE "$PLATFORM/workspaces/$id" > /dev/null
done
# shellcheck disable=SC2046 # Intentional word-split over container IDs
docker stop $(docker ps -q --filter "name=ws-") 2>/dev/null || true
# shellcheck disable=SC2046
docker rm $(docker ps -aq --filter "name=ws-") 2>/dev/null || true
# --- Create Org Chart ---
echo ""
echo "--- Create Workspaces ---"
ROOT=$(curl -s -X POST $PLATFORM/workspaces -H "Content-Type: application/json" \
-d '{"name":"Root Agent","role":"Company coordinator","runtime":"claude-code","tier":3}' \
| python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
check_contains "Create root workspace" "-" "$ROOT"
CHILD=$(curl -s -X POST $PLATFORM/workspaces -H "Content-Type: application/json" \
-d "{\"name\":\"Child Agent\",\"role\":\"Sub-team member\",\"runtime\":\"claude-code\",\"tier\":2,\"parent_id\":\"$ROOT\"}" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
check_contains "Create child workspace" "-" "$CHILD"
# --- Wait for online ---
echo ""
echo "--- Wait for provisioning (40s) ---"
sleep 40
ROOT_STATUS=$(curl -s "$PLATFORM/workspaces/$ROOT" | python3 -c "import sys,json; print(json.load(sys.stdin)['status'])")
check_contains "Root is online" "online" "$ROOT_STATUS"
CHILD_STATUS=$(curl -s "$PLATFORM/workspaces/$CHILD" | python3 -c "import sys,json; print(json.load(sys.stdin)['status'])")
check_contains "Child is online" "online" "$CHILD_STATUS"
# --- Containers running ---
CONTAINER_COUNT=$(docker ps --filter "name=ws-" -q | wc -l | tr -d ' ')
if [ "$CONTAINER_COUNT" -eq 2 ]; then pass "2 containers running"; else fail "2 containers running" "2" "$CONTAINER_COUNT"; fi
# --- Upload system prompts ---
echo ""
echo "--- Upload System Prompts ---"
ROOT_UPLOAD=$(curl -s -X PUT "$PLATFORM/workspaces/$ROOT/files" \
-H "Content-Type: application/json" \
-d '{"files":{"system-prompt.md":"You are the Root Agent. You coordinate sub-teams. The company is called TestCorp."}}')
check_contains "Upload root prompt" "replaced" "$ROOT_UPLOAD"
CHILD_UPLOAD=$(curl -s -X PUT "$PLATFORM/workspaces/$CHILD/files" \
-H "Content-Type: application/json" \
-d '{"files":{"system-prompt.md":"You are a Child Agent under Root. You specialize in data analysis for TestCorp."}}')
check_contains "Upload child prompt" "replaced" "$CHILD_UPLOAD"
# Verify prompts in containers
sleep 2
ROOT_CONTAINER=$(docker ps --filter "name=ws-${ROOT:0:12}" -q | head -1)
CHILD_CONTAINER=$(docker ps --filter "name=ws-${CHILD:0:12}" -q | head -1)
ROOT_HAS_PROMPT=$(docker exec $ROOT_CONTAINER cat /configs/system-prompt.md 2>/dev/null | head -1)
check_contains "Root container has prompt" "Root Agent" "$ROOT_HAS_PROMPT"
CHILD_HAS_PROMPT=$(docker exec $CHILD_CONTAINER cat /configs/system-prompt.md 2>/dev/null | head -1)
check_contains "Child container has prompt" "Child Agent" "$CHILD_HAS_PROMPT"
# --- A2A Tests ---
echo ""
echo "--- A2A Communication ---"
ROOT_REPLY=$(curl -s -X POST "$PLATFORM/workspaces/$ROOT/a2a" \
-H "Content-Type: application/json" --max-time 90 \
-d '{"jsonrpc":"2.0","id":"t1","method":"message/send","params":{"message":{"role":"user","parts":[{"type":"text","text":"What company do you work for? One word."}]}}}')
ROOT_TEXT=$(echo "$ROOT_REPLY" | python3 -c "import sys,json; d=json.load(sys.stdin); p=d.get('result',{}).get('parts',[]); print(p[0]['text'] if p else d.get('error',{}).get('message','EMPTY'))" 2>/dev/null)
check_contains "Root knows company name" "TestCorp" "$ROOT_TEXT"
CHILD_REPLY=$(curl -s -X POST "$PLATFORM/workspaces/$CHILD/a2a" \
-H "Content-Type: application/json" --max-time 90 \
-d '{"jsonrpc":"2.0","id":"t2","method":"message/send","params":{"message":{"role":"user","parts":[{"type":"text","text":"What do you specialize in? One phrase."}]}}}')
CHILD_TEXT=$(echo "$CHILD_REPLY" | python3 -c "import sys,json; d=json.load(sys.stdin); p=d.get('result',{}).get('parts',[]); print(p[0]['text'] if p else d.get('error',{}).get('message','EMPTY'))" 2>/dev/null)
check_contains "Child knows its specialty" "data" "$CHILD_TEXT"
# --- Access Control ---
echo ""
echo "--- Access Control ---"
PARENT_CHILD=$(curl -s -X POST $PLATFORM/registry/check-access -H "Content-Type: application/json" \
-d "{\"caller_id\":\"$ROOT\",\"target_id\":\"$CHILD\"}" | python3 -c "import sys,json; print(json.load(sys.stdin).get('allowed','?'))")
check_contains "Parent→Child allowed" "True" "$PARENT_CHILD"
CHILD_PARENT=$(curl -s -X POST $PLATFORM/registry/check-access -H "Content-Type: application/json" \
-d "{\"caller_id\":\"$CHILD\",\"target_id\":\"$ROOT\"}" | python3 -c "import sys,json; print(json.load(sys.stdin).get('allowed','?'))")
check_contains "Child→Parent allowed" "True" "$CHILD_PARENT"
# --- Canvas ---
echo ""
echo "--- Canvas ---"
CANVAS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000)
check_contains "Canvas returns 200" "200" "$CANVAS"
# --- Summary ---
echo ""
echo "==============================="
echo " PASS: $PASS FAIL: $FAIL"
echo "==============================="
if [ $FAIL -gt 0 ]; then
echo -e "\nFailed tests:$ERRORS"
exit 1
fi