Some checks failed
CI/CD / test-sqlite (pull_request) Has been cancelled
CI/CD / build-and-deploy (pull_request) Has been cancelled
CI/CD / test-runners (pull_request) Has been skipped
CI/CD / test-frontend (pull_request) Has been cancelled
CI/CD / detect-changes (pull_request) Successful in 4s
CI/CD / test-go-pg (pull_request) Has been cancelled
CI/CD / e2e-smoke (pull_request) Has been cancelled
Public→team stage handoff with audience mismatch detection, enriched assignment API responses (workflow_name, stage_name, sla_breached), team inbox with claim/assign actions, assignment notifications, team middleware system-admin bypass fix, and SDK gap closure. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
233 lines
9.4 KiB
Bash
Executable File
233 lines
9.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# ═══════════════════════════════════════════════
|
|
# E2E Workflow Handoff Test
|
|
# ═══════════════════════════════════════════════
|
|
#
|
|
# Verifies the public→team handoff flow:
|
|
# 1. Public visitor completes a form stage
|
|
# 2. Visitor sees "submitted" screen (not the team stage)
|
|
# 3. Team user sees assignment, claims it, completes review
|
|
# 4. Instance status is "completed"
|
|
#
|
|
# Prerequisites:
|
|
# - Server running at $SERVER_URL (default: http://localhost:3000)
|
|
# - ADMIN_USER / ADMIN_PASS env vars (default: admin/admin)
|
|
#
|
|
# Exit codes: 0 = all assertions passed, 1 = failures detected
|
|
set -euo pipefail
|
|
|
|
SERVER_URL="${SERVER_URL:-http://localhost:3000}"
|
|
ADMIN_USER="${ADMIN_USER:-admin}"
|
|
ADMIN_PASS="${ADMIN_PASS:-admin}"
|
|
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m'
|
|
|
|
PASS=0
|
|
FAIL=0
|
|
|
|
assert() {
|
|
local desc="$1" ok="$2"
|
|
if [ "$ok" = "true" ]; then
|
|
echo -e " ${GREEN}✓${NC} $desc"
|
|
PASS=$((PASS + 1))
|
|
else
|
|
echo -e " ${RED}✗${NC} $desc"
|
|
FAIL=$((FAIL + 1))
|
|
fi
|
|
}
|
|
|
|
# Parse JSON field via node (portable)
|
|
json_field() {
|
|
node -e "process.stdin.resume(); let d=''; process.stdin.on('data',c=>d+=c); process.stdin.on('end',()=>{try{console.log(JSON.parse(d)$1||'')}catch(e){console.log('')}})" 2>/dev/null
|
|
}
|
|
|
|
echo -e "${YELLOW}═══ E2E Workflow Handoff Test ═══${NC}"
|
|
echo " Server: ${SERVER_URL}"
|
|
|
|
# ── Authenticate ─────────────────────────────
|
|
TOKEN=$(curl -sf -X POST "${SERVER_URL}/api/v1/auth/login" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{\"login\":\"${ADMIN_USER}\",\"password\":\"${ADMIN_PASS}\"}" \
|
|
| json_field ".token" || true)
|
|
|
|
if [ -z "$TOKEN" ]; then
|
|
echo -e "${RED}Failed to authenticate${NC}"
|
|
exit 1
|
|
fi
|
|
echo -e "${GREEN}Authenticated${NC}"
|
|
|
|
AUTH="Authorization: Bearer ${TOKEN}"
|
|
|
|
# Get admin user ID
|
|
USER_ID=$(curl -sf -H "$AUTH" "${SERVER_URL}/api/v1/profile" | json_field ".id")
|
|
assert "Got admin user ID" "$([ -n "$USER_ID" ] && echo true || echo false)"
|
|
|
|
# ── Phase 1: Create team ─────────────────────
|
|
echo -e "\n${YELLOW}Phase 1: Create test team${NC}"
|
|
|
|
TEAM_RESP=$(curl -sf -X POST "${SERVER_URL}/api/v1/admin/teams" \
|
|
-H "$AUTH" -H "Content-Type: application/json" \
|
|
-d '{"name": "E2E Handoff Team", "slug": "e2e-handoff-team"}' 2>/dev/null || echo '{}')
|
|
|
|
TEAM_ID=$(echo "$TEAM_RESP" | json_field ".id")
|
|
assert "Team created" "$([ -n "$TEAM_ID" ] && echo true || echo false)"
|
|
|
|
# Add admin as team member
|
|
if [ -n "$TEAM_ID" ] && [ -n "$USER_ID" ]; then
|
|
curl -sf -X POST "${SERVER_URL}/api/v1/teams/${TEAM_ID}/members" \
|
|
-H "$AUTH" -H "Content-Type: application/json" \
|
|
-d "{\"user_id\":\"${USER_ID}\",\"role\":\"admin\"}" >/dev/null 2>&1 || true
|
|
fi
|
|
|
|
# ── Phase 2: Create workflow with public + team stages ──
|
|
echo -e "\n${YELLOW}Phase 2: Create workflow (public form → team review)${NC}"
|
|
|
|
WF_RESP=$(curl -sf -X POST "${SERVER_URL}/api/v1/workflows" \
|
|
-H "$AUTH" -H "Content-Type: application/json" \
|
|
-d '{
|
|
"name": "E2E Handoff Test",
|
|
"slug": "e2e-handoff-test",
|
|
"description": "Tests public to team handoff",
|
|
"entry_mode": "public_link",
|
|
"is_active": true
|
|
}' 2>/dev/null || echo '{"error":"failed"}')
|
|
|
|
WF_ID=$(echo "$WF_RESP" | json_field ".id")
|
|
assert "Workflow created" "$([ -n "$WF_ID" ] && echo true || echo false)"
|
|
|
|
if [ -z "$WF_ID" ]; then
|
|
echo -e "${RED}Cannot continue without workflow ID${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
# Stage 1: public form
|
|
STAGE1_RESP=$(curl -sf -X POST "${SERVER_URL}/api/v1/workflows/${WF_ID}/stages" \
|
|
-H "$AUTH" -H "Content-Type: application/json" \
|
|
-d '{
|
|
"name": "Customer Request",
|
|
"stage_mode": "form",
|
|
"audience": "public",
|
|
"ordinal": 0,
|
|
"form_template": {
|
|
"fields": [
|
|
{"key": "name", "type": "text", "label": "Your Name", "required": true},
|
|
{"key": "request", "type": "textarea", "label": "Request Details"}
|
|
]
|
|
}
|
|
}' 2>/dev/null || echo '{}')
|
|
|
|
STAGE1_ID=$(echo "$STAGE1_RESP" | json_field ".id")
|
|
assert "Public form stage created" "$([ -n "$STAGE1_ID" ] && echo true || echo false)"
|
|
|
|
# Stage 2: team review (with assignment)
|
|
STAGE2_RESP=$(curl -sf -X POST "${SERVER_URL}/api/v1/workflows/${WF_ID}/stages" \
|
|
-H "$AUTH" -H "Content-Type: application/json" \
|
|
-d "{
|
|
\"name\": \"Team Review\",
|
|
\"stage_mode\": \"review\",
|
|
\"audience\": \"team\",
|
|
\"ordinal\": 1,
|
|
\"assignment_team_id\": \"${TEAM_ID}\"
|
|
}" 2>/dev/null || echo '{}')
|
|
|
|
STAGE2_ID=$(echo "$STAGE2_RESP" | json_field ".id")
|
|
assert "Team review stage created" "$([ -n "$STAGE2_ID" ] && echo true || echo false)"
|
|
|
|
# ── Phase 3: Public visitor starts and completes form ──
|
|
echo -e "\n${YELLOW}Phase 3: Public visitor submits form${NC}"
|
|
|
|
START_RESP=$(curl -sf -X POST "${SERVER_URL}/api/v1/workflow-entry/global/e2e-handoff-test" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{}' 2>/dev/null || echo '{"error":"failed"}')
|
|
|
|
INST_ID=$(echo "$START_RESP" | json_field ".id")
|
|
ENTRY_TOKEN=$(echo "$START_RESP" | json_field ".entry_token")
|
|
|
|
assert "Instance created" "$([ -n "$INST_ID" ] && echo true || echo false)"
|
|
assert "Entry token present" "$([ -n "$ENTRY_TOKEN" ] && echo true || echo false)"
|
|
|
|
# Submit form data (advance past stage 1)
|
|
if [ -n "$ENTRY_TOKEN" ]; then
|
|
ADV_RESP=$(curl -sf -X POST "${SERVER_URL}/api/v1/public/workflows/advance/${ENTRY_TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"data": {"name": "Test User", "request": "Please review this"}}' 2>/dev/null || echo '{}')
|
|
|
|
ADV_STATUS=$(echo "$ADV_RESP" | json_field ".status")
|
|
assert "Instance advanced to team stage (status=active)" "$([ "$ADV_STATUS" = "active" ] && echo true || echo false)"
|
|
fi
|
|
|
|
# ── Phase 4: Verify audience mismatch screen ──
|
|
echo -e "\n${YELLOW}Phase 4: Verify visitor sees submitted screen${NC}"
|
|
|
|
if [ -n "$INST_ID" ]; then
|
|
# Fetch the workflow page without auth (public visitor)
|
|
WF_PAGE=$(curl -sf "${SERVER_URL}/w/${INST_ID}" 2>/dev/null || echo "")
|
|
HAS_SUBMITTED=$(echo "$WF_PAGE" | grep -c "Submitted Successfully" || true)
|
|
assert "Page shows 'Submitted Successfully'" "$([ "$HAS_SUBMITTED" -gt 0 ] && echo true || echo false)"
|
|
|
|
HAS_FORM=$(echo "$WF_PAGE" | grep -c 'id="formArea"' || true)
|
|
assert "Page does NOT show form area" "$([ "$HAS_FORM" -eq 0 ] && echo true || echo false)"
|
|
fi
|
|
|
|
# ── Phase 5: Team user sees assignment ─────────
|
|
echo -e "\n${YELLOW}Phase 5: Team assignment appears${NC}"
|
|
|
|
if [ -n "$TEAM_ID" ]; then
|
|
ASSIGN_RESP=$(curl -sf -H "$AUTH" "${SERVER_URL}/api/v1/teams/${TEAM_ID}/assignments?status=unassigned" 2>/dev/null || echo '{}')
|
|
ASSIGN_COUNT=$(echo "$ASSIGN_RESP" | node -e "process.stdin.resume(); let d=''; process.stdin.on('data',c=>d+=c); process.stdin.on('end',()=>{try{const r=JSON.parse(d); console.log((r.data||r).length||0)}catch(e){console.log(0)}})" 2>/dev/null)
|
|
assert "Unassigned assignment exists" "$([ "$ASSIGN_COUNT" -gt 0 ] && echo true || echo false)"
|
|
|
|
# Get assignment ID
|
|
ASSIGN_ID=$(echo "$ASSIGN_RESP" | node -e "process.stdin.resume(); let d=''; process.stdin.on('data',c=>d+=c); process.stdin.on('end',()=>{try{const r=JSON.parse(d); console.log((r.data||r)[0].id||'')}catch(e){console.log('')}})" 2>/dev/null)
|
|
fi
|
|
|
|
# ── Phase 6: Claim and complete assignment ─────
|
|
echo -e "\n${YELLOW}Phase 6: Claim and complete${NC}"
|
|
|
|
if [ -n "$ASSIGN_ID" ]; then
|
|
# Claim
|
|
CLAIM_RESP=$(curl -sf -X POST "${SERVER_URL}/api/v1/assignments/${ASSIGN_ID}/claim" \
|
|
-H "$AUTH" -H "Content-Type: application/json" -d '{}' 2>/dev/null || echo '{}')
|
|
CLAIMED=$(echo "$CLAIM_RESP" | json_field ".claimed")
|
|
assert "Assignment claimed" "$([ "$CLAIMED" = "true" ] && echo true || echo false)"
|
|
|
|
# Complete (advance the review stage)
|
|
COMPLETE_RESP=$(curl -sf -X POST "${SERVER_URL}/api/v1/assignments/${ASSIGN_ID}/complete" \
|
|
-H "$AUTH" -H "Content-Type: application/json" \
|
|
-d '{"review_data": {"decision": "approved", "comment": "Looks good"}}' 2>/dev/null || echo '{}')
|
|
COMPLETED=$(echo "$COMPLETE_RESP" | json_field ".completed")
|
|
assert "Assignment completed" "$([ "$COMPLETED" = "true" ] && echo true || echo false)"
|
|
fi
|
|
|
|
# ── Phase 7: Verify instance is completed ──────
|
|
echo -e "\n${YELLOW}Phase 7: Verify final state${NC}"
|
|
|
|
if [ -n "$INST_ID" ]; then
|
|
FINAL_RESP=$(curl -sf -H "$AUTH" "${SERVER_URL}/api/v1/workflows/${WF_ID}/instances/${INST_ID}" 2>/dev/null || echo '{}')
|
|
FINAL_STATUS=$(echo "$FINAL_RESP" | json_field ".status")
|
|
assert "Instance status is completed" "$([ "$FINAL_STATUS" = "completed" ] && echo true || echo false)"
|
|
fi
|
|
|
|
# ── Cleanup ──────────────────────────────────
|
|
echo -e "\n${YELLOW}Cleanup${NC}"
|
|
|
|
curl -sf -X DELETE "${SERVER_URL}/api/v1/workflows/${WF_ID}" -H "$AUTH" >/dev/null 2>&1 || true
|
|
if [ -n "$TEAM_ID" ]; then
|
|
curl -sf -X DELETE "${SERVER_URL}/api/v1/admin/teams/${TEAM_ID}" -H "$AUTH" >/dev/null 2>&1 || true
|
|
fi
|
|
echo -e " Deleted test resources"
|
|
|
|
# ── Summary ──────────────────────────────────
|
|
echo ""
|
|
TOTAL=$((PASS + FAIL))
|
|
if [ $FAIL -eq 0 ]; then
|
|
echo -e "${GREEN}═══ All ${TOTAL} assertions passed ═══${NC}"
|
|
exit 0
|
|
else
|
|
echo -e "${RED}═══ ${FAIL}/${TOTAL} assertions failed ═══${NC}"
|
|
exit 1
|
|
fi
|