fix: exempt /api/clubs/me from tenant validation
- 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.
This commit is contained in:
158
.sisyphus/evidence/final-qa/phase5-integration-journey.sh
Executable file
158
.sisyphus/evidence/final-qa/phase5-integration-journey.sh
Executable file
@@ -0,0 +1,158 @@
|
||||
#!/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 ""
|
||||
Reference in New Issue
Block a user