auto-claude: subtask-5-1 - E2E verification documentation and middleware fix
Created comprehensive E2E verification framework: - E2E_VERIFICATION.md: Complete manual testing guide with 9 scenarios - scripts/verify-e2e-rate-limiting.sh: Automated API testing script - scripts/test-concurrent-rate-limit.sh: Concurrent request testing - scripts/reset-rate-limit.sh: Database reset utility for testing - SUBTASK_5-1_VERIFICATION_REPORT.md: Status and blocker documentation Fixed middleware configuration: - Updated src/middleware.ts matcher to exclude /api/ routes - Changed from complex negative lookahead to explicit locale matching - Pattern now: ['/', '/(de|en|sr)/:path*'] Known issue: - Middleware fix requires dev server restart to take effect - API routes currently return 404 until server is restarted - All implementation code is complete and ready for testing Test coverage: - Basic rate limiting flow (5 requests succeed, 6th fails) - Response header verification (X-RateLimit-Remaining, Retry-After) - Persistence testing (across page refreshes, browser sessions) - Multi-locale support (en, de, sr) - Error handling and validation - Concurrent request handling - Database record verification Next steps: 1. Restart dev server: npm run dev 2. Run automated tests: bash ./scripts/verify-e2e-rate-limiting.sh 3. Perform manual browser testing per E2E_VERIFICATION.md 4. Verify database records in Supabase 5. Mark subtask as completed Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Reset Rate Limit - Utility Script
|
||||
# Helps reset rate limits during testing
|
||||
|
||||
echo "================================================"
|
||||
echo "Rate Limit Reset Utility"
|
||||
echo "================================================"
|
||||
echo ""
|
||||
|
||||
echo "This script helps you reset rate limits for testing."
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo "1. Reset for 'unknown-ip' (default dev identifier)"
|
||||
echo "2. Reset for specific IP address"
|
||||
echo "3. Reset ALL rate limits (use with caution)"
|
||||
echo "4. Show current rate limit records"
|
||||
echo ""
|
||||
|
||||
read -p "Select option (1-4): " option
|
||||
|
||||
case $option in
|
||||
1)
|
||||
echo ""
|
||||
echo "SQL to run in Supabase SQL Editor:"
|
||||
echo "https://app.supabase.com/project/mxadgucxhmstlzsbgmoz/sql"
|
||||
echo ""
|
||||
echo "DELETE FROM rate_limits WHERE identifier = 'unknown-ip';"
|
||||
echo ""
|
||||
echo "After running this query, rate limits will reset for the dev environment."
|
||||
;;
|
||||
2)
|
||||
echo ""
|
||||
read -p "Enter IP address to reset: " ip_address
|
||||
echo ""
|
||||
echo "SQL to run in Supabase SQL Editor:"
|
||||
echo "https://app.supabase.com/project/mxadgucxhmstlzsbgmoz/sql"
|
||||
echo ""
|
||||
echo "DELETE FROM rate_limits WHERE identifier = '$ip_address';"
|
||||
echo ""
|
||||
;;
|
||||
3)
|
||||
echo ""
|
||||
echo "⚠️ WARNING: This will reset ALL rate limits!"
|
||||
read -p "Are you sure? (yes/no): " confirm
|
||||
if [ "$confirm" = "yes" ]; then
|
||||
echo ""
|
||||
echo "SQL to run in Supabase SQL Editor:"
|
||||
echo "https://app.supabase.com/project/mxadgucxhmstlzsbgmoz/sql"
|
||||
echo ""
|
||||
echo "TRUNCATE TABLE rate_limits;"
|
||||
echo ""
|
||||
else
|
||||
echo "Cancelled."
|
||||
fi
|
||||
;;
|
||||
4)
|
||||
echo ""
|
||||
echo "SQL to run in Supabase SQL Editor:"
|
||||
echo "https://app.supabase.com/project/mxadgucxhmstlzsbgmoz/sql"
|
||||
echo ""
|
||||
echo "SELECT * FROM rate_limits ORDER BY window_start DESC;"
|
||||
echo ""
|
||||
echo "This will show all current rate limit records."
|
||||
;;
|
||||
*)
|
||||
echo "Invalid option."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
echo "Note: Rate limits can also naturally expire after 1 hour."
|
||||
@@ -0,0 +1,79 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Concurrent Rate Limiting Test
|
||||
# Tests that rate limiting correctly handles simultaneous requests
|
||||
|
||||
echo "================================================"
|
||||
echo "Concurrent Rate Limiting Test"
|
||||
echo "================================================"
|
||||
echo ""
|
||||
|
||||
API_URL="http://localhost:3000/api/contact"
|
||||
CONCURRENT_REQUESTS=7
|
||||
|
||||
echo "Sending $CONCURRENT_REQUESTS concurrent requests..."
|
||||
echo "Expected: First 5 should succeed (200), remaining should fail (429)"
|
||||
echo ""
|
||||
|
||||
# Create a temporary directory for results
|
||||
TMP_DIR=$(mktemp -d)
|
||||
trap "rm -rf $TMP_DIR" EXIT
|
||||
|
||||
# Send concurrent requests
|
||||
for i in $(seq 1 $CONCURRENT_REQUESTS); do
|
||||
{
|
||||
response=$(curl -s -w "\n%{http_code}" -X POST "$API_URL" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"name\":\"Concurrent Test $i\",\"email\":\"test$i@example.com\",\"message\":\"Concurrent message $i\"}")
|
||||
|
||||
status_code=$(echo "$response" | tail -n 1)
|
||||
echo "$i:$status_code" > "$TMP_DIR/result_$i.txt"
|
||||
echo "Request #$i: $status_code"
|
||||
} &
|
||||
done
|
||||
|
||||
# Wait for all background jobs to complete
|
||||
wait
|
||||
|
||||
echo ""
|
||||
echo "Results:"
|
||||
echo "--------"
|
||||
|
||||
# Analyze results
|
||||
success_count=0
|
||||
rate_limited_count=0
|
||||
|
||||
for i in $(seq 1 $CONCURRENT_REQUESTS); do
|
||||
if [ -f "$TMP_DIR/result_$i.txt" ]; then
|
||||
result=$(cat "$TMP_DIR/result_$i.txt")
|
||||
status_code=$(echo "$result" | cut -d':' -f2)
|
||||
|
||||
if [ "$status_code" = "200" ]; then
|
||||
((success_count++))
|
||||
echo "✅ Request #$i: 200 OK"
|
||||
elif [ "$status_code" = "429" ]; then
|
||||
((rate_limited_count++))
|
||||
echo "🚫 Request #$i: 429 Rate Limited"
|
||||
else
|
||||
echo "⚠️ Request #$i: $status_code (Unexpected)"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "Summary:"
|
||||
echo "--------"
|
||||
echo "Successful: $success_count"
|
||||
echo "Rate Limited: $rate_limited_count"
|
||||
echo ""
|
||||
|
||||
# Verify expectations
|
||||
if [ $success_count -le 5 ] && [ $rate_limited_count -ge 2 ]; then
|
||||
echo "✅ Concurrent rate limiting works correctly!"
|
||||
echo "Note: Due to timing, some requests within the limit might also be blocked."
|
||||
echo "This is acceptable behavior for concurrent requests."
|
||||
else
|
||||
echo "⚠️ Results might indicate an issue:"
|
||||
echo "Expected: ~5 successful, ~2 rate limited"
|
||||
echo "Got: $success_count successful, $rate_limited_count rate limited"
|
||||
fi
|
||||
@@ -0,0 +1,219 @@
|
||||
#!/bin/bash
|
||||
|
||||
# End-to-End Rate Limiting Verification Script
|
||||
# This script tests the Supabase-based rate limiting implementation
|
||||
|
||||
set -e
|
||||
|
||||
echo "================================================"
|
||||
echo "E2E Rate Limiting Verification"
|
||||
echo "================================================"
|
||||
echo ""
|
||||
|
||||
# Configuration
|
||||
API_URL="http://localhost:3000/api/contact"
|
||||
MAX_REQUESTS=5
|
||||
EXPECTED_WINDOW_MS=3600000
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Test counters
|
||||
TESTS_PASSED=0
|
||||
TESTS_FAILED=0
|
||||
|
||||
# Helper function to print test result
|
||||
print_result() {
|
||||
local test_name=$1
|
||||
local result=$2
|
||||
|
||||
if [ "$result" = "PASS" ]; then
|
||||
echo -e "${GREEN}✅ PASS${NC}: $test_name"
|
||||
((TESTS_PASSED++))
|
||||
else
|
||||
echo -e "${RED}❌ FAIL${NC}: $test_name"
|
||||
((TESTS_FAILED++))
|
||||
fi
|
||||
}
|
||||
|
||||
# Helper function to make API request
|
||||
make_request() {
|
||||
local name=$1
|
||||
local email=$2
|
||||
local message=$3
|
||||
|
||||
curl -s -w "\n%{http_code}\n%{header_json}" -X POST "$API_URL" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"name\":\"$name\",\"email\":\"$email\",\"message\":\"$message\"}"
|
||||
}
|
||||
|
||||
echo "Step 1: Check if dev server is running..."
|
||||
if ! curl -s -f -o /dev/null "$API_URL" -X POST -H "Content-Type: application/json" -d '{}'; then
|
||||
if [ $? -eq 7 ]; then
|
||||
echo -e "${RED}❌ Dev server is not running!${NC}"
|
||||
echo "Please start it with: npm run dev"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
echo -e "${GREEN}✅ Dev server is running${NC}"
|
||||
echo ""
|
||||
|
||||
echo "Step 2: Testing basic rate limiting flow..."
|
||||
echo "Sending $MAX_REQUESTS requests (should all succeed)..."
|
||||
|
||||
# Track response headers
|
||||
declare -a status_codes
|
||||
declare -a remaining_counts
|
||||
|
||||
for i in $(seq 1 $MAX_REQUESTS); do
|
||||
echo -n "Request #$i... "
|
||||
|
||||
response=$(make_request "Test User $i" "test$i@example.com" "Test message $i")
|
||||
|
||||
# Extract HTTP status code
|
||||
status_code=$(echo "$response" | tail -n 2 | head -n 1)
|
||||
status_codes[$i]=$status_code
|
||||
|
||||
# Extract X-RateLimit-Remaining header
|
||||
remaining=$(echo "$response" | grep -i "x-ratelimit-remaining" | grep -oP '\d+' | head -n 1 || echo "N/A")
|
||||
remaining_counts[$i]=$remaining
|
||||
|
||||
if [ "$status_code" = "200" ]; then
|
||||
echo -e "${GREEN}200 OK${NC} (Remaining: $remaining)"
|
||||
else
|
||||
echo -e "${RED}$status_code${NC} (Unexpected!)"
|
||||
fi
|
||||
|
||||
sleep 0.5
|
||||
done
|
||||
echo ""
|
||||
|
||||
# Verify all requests succeeded
|
||||
echo "Verifying first $MAX_REQUESTS requests..."
|
||||
all_success=true
|
||||
for i in $(seq 1 $MAX_REQUESTS); do
|
||||
if [ "${status_codes[$i]}" != "200" ]; then
|
||||
all_success=false
|
||||
print_result "Request #$i should return 200" "FAIL"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$all_success" = true ]; then
|
||||
print_result "First $MAX_REQUESTS requests succeeded" "PASS"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Verify remaining counts decrement
|
||||
echo "Verifying rate limit counters..."
|
||||
for i in $(seq 1 $((MAX_REQUESTS - 1))); do
|
||||
current=${remaining_counts[$i]}
|
||||
next=${remaining_counts[$((i + 1))]}
|
||||
|
||||
if [ "$current" != "N/A" ] && [ "$next" != "N/A" ]; then
|
||||
expected=$((current - 1))
|
||||
if [ "$next" -eq "$expected" ]; then
|
||||
print_result "Counter decremented from $current to $next" "PASS"
|
||||
else
|
||||
print_result "Counter should decrement (expected $expected, got $next)" "FAIL"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
|
||||
echo "Step 3: Testing rate limit enforcement..."
|
||||
echo "Sending 6th request (should be rate limited)..."
|
||||
|
||||
response=$(make_request "Test User 6" "test6@example.com" "Test message 6")
|
||||
status_code=$(echo "$response" | tail -n 2 | head -n 1)
|
||||
body=$(echo "$response" | head -n -2)
|
||||
|
||||
echo "Status Code: $status_code"
|
||||
echo "Response: $body"
|
||||
echo ""
|
||||
|
||||
if [ "$status_code" = "429" ]; then
|
||||
print_result "6th request returns 429 Too Many Requests" "PASS"
|
||||
|
||||
# Check for Retry-After header
|
||||
retry_after=$(echo "$response" | grep -i "retry-after" | grep -oP '\d+' || echo "")
|
||||
if [ -n "$retry_after" ]; then
|
||||
print_result "Retry-After header present ($retry_after seconds)" "PASS"
|
||||
else
|
||||
print_result "Retry-After header should be present" "FAIL"
|
||||
fi
|
||||
|
||||
# Check for X-RateLimit-Remaining: 0
|
||||
remaining=$(echo "$response" | grep -i "x-ratelimit-remaining" | grep -oP '\d+' || echo "")
|
||||
if [ "$remaining" = "0" ]; then
|
||||
print_result "X-RateLimit-Remaining is 0" "PASS"
|
||||
else
|
||||
print_result "X-RateLimit-Remaining should be 0" "FAIL"
|
||||
fi
|
||||
|
||||
# Check for error message in body
|
||||
if echo "$body" | grep -q "too many\|rate limit"; then
|
||||
print_result "Response body contains rate limit error message" "PASS"
|
||||
else
|
||||
print_result "Response body should contain rate limit error" "FAIL"
|
||||
fi
|
||||
else
|
||||
print_result "6th request should return 429" "FAIL"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
echo "Step 4: Testing rate limit persistence..."
|
||||
echo "Sending another request (should still be rate limited)..."
|
||||
|
||||
response=$(make_request "Test User 7" "test7@example.com" "Test message 7")
|
||||
status_code=$(echo "$response" | tail -n 2 | head -n 1)
|
||||
|
||||
if [ "$status_code" = "429" ]; then
|
||||
print_result "Subsequent request still rate limited" "PASS"
|
||||
else
|
||||
print_result "Rate limiting should persist" "FAIL"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
echo "Step 5: Testing validation (should not count against rate limit)..."
|
||||
echo "Sending request with invalid data..."
|
||||
|
||||
response=$(curl -s -w "\n%{http_code}" -X POST "$API_URL" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name":"","email":"invalid","message":""}')
|
||||
|
||||
status_code=$(echo "$response" | tail -n 1)
|
||||
|
||||
if [ "$status_code" = "400" ] || [ "$status_code" = "429" ]; then
|
||||
print_result "Invalid data returns 400 or still rate limited (429)" "PASS"
|
||||
else
|
||||
print_result "Invalid data handling (got $status_code)" "FAIL"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
echo "================================================"
|
||||
echo "Test Summary"
|
||||
echo "================================================"
|
||||
echo -e "${GREEN}Passed: $TESTS_PASSED${NC}"
|
||||
echo -e "${RED}Failed: $TESTS_FAILED${NC}"
|
||||
echo ""
|
||||
|
||||
if [ $TESTS_FAILED -eq 0 ]; then
|
||||
echo -e "${GREEN}✅ All tests passed!${NC}"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo "1. Verify database records in Supabase dashboard"
|
||||
echo "2. Test browser UI at http://localhost:3000/en/contact"
|
||||
echo "3. Test persistence across page refreshes"
|
||||
echo "4. Complete manual verification scenarios in E2E_VERIFICATION.md"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}❌ Some tests failed${NC}"
|
||||
echo "Please review the failures above and check:"
|
||||
echo "- Is the migration applied in Supabase?"
|
||||
echo "- Are environment variables set correctly?"
|
||||
echo "- Check server logs for errors"
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user