- Add path exemption in TenantValidationMiddleware for /api/clubs/me - Change authorization policy from RequireMember to RequireViewer - Fix KEYCLOAK_CLIENT_ID in docker-compose.yml (workclub-app not workclub-api) - Endpoint now works without X-Tenant-Id header as intended - Other endpoints still protected by tenant validation This fixes the chicken-and-egg problem where frontend needs to call /api/clubs/me to discover available clubs before selecting a tenant.
159 lines
5.8 KiB
Bash
Executable File
159 lines
5.8 KiB
Bash
Executable File
#!/bin/bash
|
|
# Phase 5: Cross-Task Integration Journey (Scenarios 42-51)
|
|
# 10-step end-to-end workflow testing via API
|
|
|
|
source /tmp/qa-test-env.sh
|
|
|
|
echo "=========================================="
|
|
echo "Phase 5: Integration Journey (S42-S51)"
|
|
echo "=========================================="
|
|
echo ""
|
|
|
|
# Step 1-2: Login as admin, select Tennis Club (already authenticated via tokens)
|
|
echo "=== STEP 1-2: Admin Authentication + Tennis Club Context ==="
|
|
echo "Token: ${TOKEN_ADMIN:0:20}..."
|
|
echo "Tenant: $TENANT_TENNIS (Tennis Club)"
|
|
echo "✅ Using pre-acquired admin token with Tennis Club context"
|
|
echo ""
|
|
|
|
# Step 3: Create task "Replace court net"
|
|
echo "=== STEP 3: Create Task 'Replace court net' ==="
|
|
CREATE_RESULT=$(curl -s -X POST "$API_BASE/api/tasks" \
|
|
-H "Authorization: Bearer $TOKEN_ADMIN" \
|
|
-H "X-Tenant-Id: $TENANT_TENNIS" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"title": "Replace court net",
|
|
"description": "Replace worn center court net with new professional-grade net",
|
|
"dueDate": "2026-03-20T23:59:59Z"
|
|
}')
|
|
JOURNEY_TASK_ID=$(echo $CREATE_RESULT | jq -r '.id')
|
|
echo "Created task ID: $JOURNEY_TASK_ID"
|
|
echo $CREATE_RESULT | jq '.'
|
|
echo ""
|
|
|
|
# Step 4: Assign to member1
|
|
echo "=== STEP 4: Assign Task to member1 ==="
|
|
# Get member1's user ID from token
|
|
MEMBER1_SUB=$(curl -s -X POST "$AUTH_URL" \
|
|
-d "client_id=workclub-app" \
|
|
-d "grant_type=password" \
|
|
-d "username=$USER_MEMBER1" \
|
|
-d "password=$PASSWORD" | jq -r '.access_token' | cut -d'.' -f2 | base64 -d 2>/dev/null | jq -r '.sub')
|
|
echo "Member1 sub: $MEMBER1_SUB"
|
|
|
|
ASSIGN_RESULT=$(curl -s -X PATCH "$API_BASE/api/tasks/$JOURNEY_TASK_ID" \
|
|
-H "Authorization: Bearer $TOKEN_ADMIN" \
|
|
-H "X-Tenant-Id: $TENANT_TENNIS" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{\"status\":\"Assigned\",\"assigneeId\":\"$MEMBER1_SUB\"}")
|
|
echo "Task assigned:"
|
|
echo $ASSIGN_RESULT | jq '.'
|
|
echo ""
|
|
|
|
# Step 5: Login as member1, transition Open → InProgress
|
|
echo "=== STEP 5: Member1 Transitions Assigned → InProgress ==="
|
|
PROGRESS_RESULT=$(curl -s -X PATCH "$API_BASE/api/tasks/$JOURNEY_TASK_ID" \
|
|
-H "Authorization: Bearer $TOKEN_MEMBER1" \
|
|
-H "X-Tenant-Id: $TENANT_TENNIS" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"status":"InProgress"}')
|
|
echo "Transitioned to InProgress:"
|
|
echo $PROGRESS_RESULT | jq '.'
|
|
echo ""
|
|
|
|
# Step 6: Transition InProgress → Review
|
|
echo "=== STEP 6: Member1 Transitions InProgress → Review ==="
|
|
REVIEW_RESULT=$(curl -s -X PATCH "$API_BASE/api/tasks/$JOURNEY_TASK_ID" \
|
|
-H "Authorization: Bearer $TOKEN_MEMBER1" \
|
|
-H "X-Tenant-Id: $TENANT_TENNIS" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"status":"Review"}')
|
|
echo "Transitioned to Review:"
|
|
echo $REVIEW_RESULT | jq '.'
|
|
echo ""
|
|
|
|
# Step 7: Login as admin, transition Review → Done
|
|
echo "=== STEP 7: Admin Approves - Review → Done ==="
|
|
DONE_RESULT=$(curl -s -X PATCH "$API_BASE/api/tasks/$JOURNEY_TASK_ID" \
|
|
-H "Authorization: Bearer $TOKEN_ADMIN" \
|
|
-H "X-Tenant-Id: $TENANT_TENNIS" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"status":"Done"}')
|
|
echo "Task completed:"
|
|
echo $DONE_RESULT | jq '.'
|
|
echo ""
|
|
|
|
# Step 8: Switch to Cycling Club
|
|
echo "=== STEP 8: Switch Context to Cycling Club ==="
|
|
echo "New Tenant: $TENANT_CYCLING (Cycling Club)"
|
|
echo ""
|
|
|
|
# Step 9: Verify Tennis tasks NOT visible in Cycling Club
|
|
echo "=== STEP 9: Verify Tenant Isolation - Tennis Task Invisible ==="
|
|
ISOLATION_CHECK=$(curl -s "$API_BASE/api/tasks/$JOURNEY_TASK_ID" \
|
|
-H "Authorization: Bearer $TOKEN_ADMIN" \
|
|
-H "X-Tenant-Id: $TENANT_CYCLING")
|
|
ISOLATION_STATUS=$(curl -s -w "%{http_code}" -o /dev/null "$API_BASE/api/tasks/$JOURNEY_TASK_ID" \
|
|
-H "Authorization: Bearer $TOKEN_ADMIN" \
|
|
-H "X-Tenant-Id: $TENANT_CYCLING")
|
|
echo "Attempting to access Tennis task from Cycling Club context..."
|
|
echo "HTTP Status: $ISOLATION_STATUS"
|
|
if [ "$ISOLATION_STATUS" = "404" ]; then
|
|
echo "✅ PASS: Task correctly isolated (404 Not Found)"
|
|
else
|
|
echo "❌ FAIL: Task visible across tenants (security issue!)"
|
|
echo "Response: $ISOLATION_CHECK"
|
|
fi
|
|
echo ""
|
|
|
|
# Step 10: Create shift in Cycling Club, sign up, verify capacity
|
|
echo "=== STEP 10: Cycling Club - Create Shift + Signup ==="
|
|
SHIFT_RESULT=$(curl -s -X POST "$API_BASE/api/shifts" \
|
|
-H "Authorization: Bearer $TOKEN_ADMIN" \
|
|
-H "X-Tenant-Id: $TENANT_CYCLING" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"title": "Bike Maintenance Workshop",
|
|
"description": "Monthly bike maintenance and repair workshop",
|
|
"startTime": "2026-03-22T10:00:00Z",
|
|
"endTime": "2026-03-22T14:00:00Z",
|
|
"capacity": 2,
|
|
"requiredRole": "Member"
|
|
}')
|
|
JOURNEY_SHIFT_ID=$(echo $SHIFT_RESULT | jq -r '.id')
|
|
echo "Created shift ID: $JOURNEY_SHIFT_ID"
|
|
echo $SHIFT_RESULT | jq '.'
|
|
echo ""
|
|
|
|
echo "Signing up member1 for shift..."
|
|
SIGNUP_RESULT=$(curl -s -w "\nHTTP:%{http_code}" -X POST "$API_BASE/api/shifts/$JOURNEY_SHIFT_ID/signup" \
|
|
-H "Authorization: Bearer $TOKEN_MEMBER1" \
|
|
-H "X-Tenant-Id: $TENANT_CYCLING")
|
|
echo "$SIGNUP_RESULT"
|
|
echo ""
|
|
|
|
echo "Verifying shift capacity (1/2 filled)..."
|
|
SHIFT_CHECK=$(curl -s "$API_BASE/api/shifts/$JOURNEY_SHIFT_ID" \
|
|
-H "Authorization: Bearer $TOKEN_ADMIN" \
|
|
-H "X-Tenant-Id: $TENANT_CYCLING")
|
|
SIGNUP_COUNT=$(echo $SHIFT_CHECK | jq '.signups | length')
|
|
echo "Current signups: $SIGNUP_COUNT / 2"
|
|
if [ "$SIGNUP_COUNT" = "1" ]; then
|
|
echo "✅ PASS: Signup recorded correctly"
|
|
else
|
|
echo "❌ FAIL: Signup count mismatch"
|
|
fi
|
|
echo ""
|
|
|
|
echo "=========================================="
|
|
echo "Integration Journey Complete!"
|
|
echo "=========================================="
|
|
echo "Summary:"
|
|
echo " - Created task in Tennis Club: $JOURNEY_TASK_ID"
|
|
echo " - Assigned to member1, progressed through all states"
|
|
echo " - Verified tenant isolation (Tennis task invisible from Cycling)"
|
|
echo " - Created shift in Cycling Club: $JOURNEY_SHIFT_ID"
|
|
echo " - Verified shift signup and capacity tracking"
|
|
echo ""
|