#!/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