diff --git a/run-tests.sh b/run-tests.sh index 2aff59a..bd2205a 100644 --- a/run-tests.sh +++ b/run-tests.sh @@ -8,7 +8,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" BASE_URL="${BASE_URL:-http://127.0.0.1:4099}" TEST_EMAIL="${TEST_EMAIL:-admin@example.com}" TEST_PASSWORD="${TEST_PASSWORD:-password}" -START_SERVER=false +SPECIFIC_SUITE="" SERVER_PID="" # Colors @@ -20,14 +20,15 @@ YELLOW='\033[0;33m' NC='\033[0m' # Parse command line arguments -while getopts "s" opt; do +while getopts "s:" opt; do case $opt in s) - START_SERVER=true + SPECIFIC_SUITE="$OPTARG" ;; \?) - echo "Usage: $0 [-s]" - echo " -s: Start the API server before running tests" + echo "Usage: $0 [-s suite_name]" + echo " -s suite_name: Run only a specific test suite" + echo " Available suites: auth, users, roles, teams, mentors, cms, gacha, hackathon" exit 1 ;; esac @@ -48,76 +49,79 @@ echo -e "${NC}" echo -e "${BLUE}Configuration:${NC}" echo -e " Base URL: ${GREEN}$BASE_URL${NC}" echo -e " Test User: ${GREEN}$TEST_EMAIL${NC}" +if [ -n "$SPECIFIC_SUITE" ]; then + echo -e " Mode: ${YELLOW}Single Suite ($SPECIFIC_SUITE)${NC}" +else + echo -e " Mode: ${YELLOW}All Suites${NC}" +fi echo "" # ============================================================================== -# Start Server if requested +# Start Server (ALWAYS) # ============================================================================== -if [ "$START_SERVER" = true ]; then - echo -e "${YELLOW}Starting API server...${NC}" +echo -e "${YELLOW}Starting API server...${NC}" + +# Force kill any existing api processes first +echo -e "${CYAN}Cleaning up any existing API processes...${NC}" +ps aux | grep "target/release/api" | grep -v grep | awk '{print $1}' | xargs kill -9 2>/dev/null || true +ps aux | grep "cargo run --bin api" | grep -v grep | awk '{print $1}' | xargs kill -9 2>/dev/null || true +sleep 2 + +echo -e "${CYAN}Building server in release mode...${NC}" +cargo build --bin api --release + +if [ $? -ne 0 ]; then + echo -e "${RED}Failed to compile server${NC}" + exit 1 +fi - # Force kill any existing api processes first - echo -e "${CYAN}Cleaning up any existing API processes...${NC}" - ps aux | grep "target/release/api" | grep -v grep | awk '{print $1}' | xargs kill -9 2>/dev/null || true - ps aux | grep "cargo run --bin api" | grep -v grep | awk '{print $1}' | xargs kill -9 2>/dev/null || true - sleep 2 +echo -e "${CYAN}Starting server in background...${NC}" + +# Start server directly from binary in background +nohup ./target/release/api > server.log 2>&1 & +SERVER_PID=$! + +echo -e "${CYAN}Server started with PID: $SERVER_PID${NC}" - echo -e "${CYAN}Building server in release mode...${NC}" - cargo build --bin api --release +# Wait for server to be ready +echo -e "${CYAN}Waiting for server to be ready...${NC}" +MAX_WAIT=30 +WAIT_COUNT=0 +while true; do + # Check if any HTTP status code is returned (even 404/405 means server is up) + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/v1/auth/login" 2>/dev/null || echo "000") + if [ "$HTTP_CODE" != "000" ] && [ "$HTTP_CODE" != "" ]; then + break + fi - if [ $? -ne 0 ]; then - echo -e "${RED}Failed to compile server${NC}" + sleep 1 + ((WAIT_COUNT++)) + if [ $WAIT_COUNT -ge $MAX_WAIT ]; then + echo -e "${RED}Server failed to start within $MAX_WAIT seconds${NC}" + echo -e "${RED}Server log:${NC}" + tail -20 server.log + if [ -n "$SERVER_PID" ]; then + kill $SERVER_PID 2>/dev/null + fi exit 1 fi - - echo -e "${CYAN}Starting server in background...${NC}" - - # Start server directly from binary in background - nohup ./target/release/api > server.log 2>&1 & - SERVER_PID=$! - - echo -e "${CYAN}Server started with PID: $SERVER_PID${NC}" - - # Wait for server to be ready - echo -e "${CYAN}Waiting for server to be ready...${NC}" - MAX_WAIT=30 - WAIT_COUNT=0 - while true; do - # Check if any HTTP status code is returned (even 404/405 means server is up) - HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/v1/auth/login" 2>/dev/null || echo "000") - if [ "$HTTP_CODE" != "000" ] && [ "$HTTP_CODE" != "" ]; then - break - fi - - sleep 1 - ((WAIT_COUNT++)) - if [ $WAIT_COUNT -ge $MAX_WAIT ]; then - echo -e "${RED}Server failed to start within $MAX_WAIT seconds${NC}" - echo -e "${RED}Server log:${NC}" - tail -20 server.log - if [ -n "$SERVER_PID" ]; then - kill $SERVER_PID 2>/dev/null - fi - exit 1 - fi - printf "." - done - echo "" - echo -e "${GREEN}✓ Server is ready!${NC}" - - # Run seeder to populate test data - echo -e "${CYAN}Running database seeder...${NC}" - cargo run --bin seeder --release > /dev/null 2>&1 || { - echo -e "${YELLOW}⚠ Seeder failed or already populated${NC}" - } - echo -e "${GREEN}✓ Database seeded${NC}" - echo "" -fi + printf "." +done +echo "" +echo -e "${GREEN}✓ Server is ready!${NC}" + +# Run seeder to populate test data +echo -e "${CYAN}Running database seeder...${NC}" +cargo run --bin seeder --release > /dev/null 2>&1 || { + echo -e "${YELLOW}⚠ Seeder failed or already populated${NC}" +} +echo -e "${GREEN}✓ Database seeded${NC}" +echo "" # Cleanup function cleanup() { - if [ -n "$SERVER_PID" ] && [ "$START_SERVER" = true ]; then + if [ -n "$SERVER_PID" ]; then echo -e "\n${YELLOW}Stopping server (PID: $SERVER_PID)...${NC}" kill $SERVER_PID 2>/dev/null sleep 1 @@ -134,9 +138,13 @@ trap cleanup EXIT INT TERM # Test suite tracking declare -A SUITE_RESULTS +declare -A SUITE_TEST_COUNTS TOTAL_SUITES=0 PASSED_SUITES=0 FAILED_SUITES=0 +TOTAL_TESTS=0 +PASSED_TESTS=0 +FAILED_TESTS=0 run_test_suite() { local suite_name=$1 @@ -151,6 +159,7 @@ run_test_suite() { if [ ! -f "$test_script" ]; then printf "${RED}✗ Test script not found: %s${NC}\n" "$test_script" SUITE_RESULTS["$suite_name"]="NOT_FOUND" + SUITE_TEST_COUNTS["$suite_name"]="0:0:0" ((FAILED_SUITES++)) return 1 fi @@ -158,16 +167,46 @@ run_test_suite() { # Make script executable chmod +x "$test_script" + # Capture test output to extract test counts + local output_file=$(mktemp) + # Run test suite - if bash "$test_script"; then + if bash "$test_script" 2>&1 | tee "$output_file"; then SUITE_RESULTS["$suite_name"]="PASSED" ((PASSED_SUITES++)) printf "${GREEN}✓ Suite '%s' completed successfully${NC}\n" "$suite_name" + local suite_exit=0 else SUITE_RESULTS["$suite_name"]="FAILED" ((FAILED_SUITES++)) printf "${RED}✗ Suite '%s' failed${NC}\n" "$suite_name" + local suite_exit=1 fi + + # Extract test counts from output (use API Requests line for accurate count) + local api_line=$(grep -oP "API Requests: \K\d+ \(Passed: \d+, Failed: \d+\)" "$output_file" | tail -1 || echo "0 (Passed: 0, Failed: 0)") + local suite_total=$(echo "$api_line" | grep -oP "^\d+" || echo "0") + local suite_passed=$(echo "$api_line" | grep -oP "Passed: \K\d+" || echo "0") + local suite_failed=$(echo "$api_line" | grep -oP "Failed: \K\d+" || echo "0") + + # If no API line found, try Total Tests line as fallback + if [ "$suite_total" = "0" ]; then + suite_total=$(grep -oP "Total Tests: \K\d+" "$output_file" | tail -1 || echo "0") + suite_passed=$(grep -oP "^Passed: \K\d+" "$output_file" | tail -1 || echo "0") + suite_failed=$(grep -oP "^Failed: \K\d+" "$output_file" | tail -1 || echo "0") + fi + + # Store suite test counts + SUITE_TEST_COUNTS["$suite_name"]="$suite_total:$suite_passed:$suite_failed" + + # Accumulate totals + ((TOTAL_TESTS += suite_total)) + ((PASSED_TESTS += suite_passed)) + ((FAILED_TESTS += suite_failed)) + + rm -f "$output_file" + + return $suite_exit } # ============================================================================== @@ -176,23 +215,52 @@ run_test_suite() { START_TIME=$(date +%s) -# IAM Tests -run_test_suite "IAM - Authentication" "$SCRIPT_DIR/tests/iam/test-auth.sh" -run_test_suite "IAM - Users" "$SCRIPT_DIR/tests/iam/test-users.sh" -run_test_suite "IAM - Roles & Permissions" "$SCRIPT_DIR/tests/iam/test-roles-permissions.sh" -run_test_suite "IAM - Teams" "$SCRIPT_DIR/tests/iam/test-teams.sh" - -# Dimentorin Tests -run_test_suite "Dimentorin - Mentors" "$SCRIPT_DIR/tests/dimentorin/test-mentors.sh" - -# CMS Tests -run_test_suite "CMS - Events & Testimonials" "$SCRIPT_DIR/tests/cms/test-cms.sh" - -# Gacha Tests -run_test_suite "Gacha - Items & Rolls" "$SCRIPT_DIR/tests/gacha/test-gacha.sh" - -# Hackathon Tests -run_test_suite "Hackathon - Full Suite" "$SCRIPT_DIR/tests/hackathon/test-hackathon.sh" +# Determine which suites to run +if [ -n "$SPECIFIC_SUITE" ]; then + # Run only the specified suite + case "$SPECIFIC_SUITE" in + auth) + run_test_suite "IAM - Authentication" "$SCRIPT_DIR/tests/iam/test-auth.sh" + ;; + users) + run_test_suite "IAM - Users" "$SCRIPT_DIR/tests/iam/test-users.sh" + ;; + roles) + run_test_suite "IAM - Roles & Permissions" "$SCRIPT_DIR/tests/iam/test-roles-permissions.sh" + ;; + teams) + run_test_suite "IAM - Teams" "$SCRIPT_DIR/tests/iam/test-teams.sh" + ;; + mentors) + run_test_suite "Dimentorin - Mentors" "$SCRIPT_DIR/tests/dimentorin/test-mentors.sh" + ;; + cms) + run_test_suite "CMS - Events & Testimonials" "$SCRIPT_DIR/tests/cms/test-cms.sh" + ;; + gacha) + run_test_suite "Gacha - Items & Rolls" "$SCRIPT_DIR/tests/gacha/test-gacha.sh" + ;; + hackathon) + run_test_suite "Hackathon - Full Suite" "$SCRIPT_DIR/tests/hackathon/test-hackathon.sh" + ;; + *) + echo -e "${RED}Unknown suite: $SPECIFIC_SUITE${NC}" + echo -e "${YELLOW}Available suites: auth, users, roles, teams, mentors, cms, gacha, hackathon${NC}" + cleanup + exit 1 + ;; + esac +else + # Run all suites + run_test_suite "IAM - Authentication" "$SCRIPT_DIR/tests/iam/test-auth.sh" + run_test_suite "IAM - Users" "$SCRIPT_DIR/tests/iam/test-users.sh" + run_test_suite "IAM - Roles & Permissions" "$SCRIPT_DIR/tests/iam/test-roles-permissions.sh" + run_test_suite "IAM - Teams" "$SCRIPT_DIR/tests/iam/test-teams.sh" + run_test_suite "Dimentorin - Mentors" "$SCRIPT_DIR/tests/dimentorin/test-mentors.sh" + run_test_suite "CMS - Events & Testimonials" "$SCRIPT_DIR/tests/cms/test-cms.sh" + run_test_suite "Gacha - Items & Rolls" "$SCRIPT_DIR/tests/gacha/test-gacha.sh" + run_test_suite "Hackathon - Full Suite" "$SCRIPT_DIR/tests/hackathon/test-hackathon.sh" +fi END_TIME=$(date +%s) DURATION=$((END_TIME - START_TIME)) @@ -205,16 +273,32 @@ printf "\n${CYAN}═════════════════════ printf "${BLUE} FINAL TEST SUMMARY ${NC}\n" printf "${CYAN}════════════════════════════════════════════════════════════════${NC}\n\n" -printf "Total Test Suites: ${BLUE}%d${NC}\n" "$TOTAL_SUITES" -printf "${GREEN}Passed Suites: %d${NC}\n" "$PASSED_SUITES" -printf "${RED}Failed Suites: %d${NC}\n" "$FAILED_SUITES" -printf "\n" +# Test Suites Summary +printf "${BLUE}Test Suites:${NC}\n" +printf " Total Suites: ${BLUE}%d${NC}\n" "$TOTAL_SUITES" +printf " ${GREEN}Passed Suites: %d${NC}\n" "$PASSED_SUITES" +printf " ${RED}Failed Suites: %d${NC}\n" "$FAILED_SUITES" if [ "$TOTAL_SUITES" -gt 0 ]; then SUCCESS_RATE=$(( (PASSED_SUITES * 100) / TOTAL_SUITES )) - printf "Success Rate: ${BLUE}%d%%${NC}\n" "$SUCCESS_RATE" + printf " Suite Success Rate: ${BLUE}%d%%${NC}\n" "$SUCCESS_RATE" fi +printf "\n" + +# Individual Tests Summary +printf "${BLUE}Individual Tests:${NC}\n" +printf " Total Tests: ${BLUE}%d${NC}\n" "$TOTAL_TESTS" +printf " ${GREEN}Passed Tests: %d${NC}\n" "$PASSED_TESTS" +printf " ${RED}Failed Tests: %d${NC}\n" "$FAILED_TESTS" + +if [ "$TOTAL_TESTS" -gt 0 ]; then + TEST_SUCCESS_RATE=$(( (PASSED_TESTS * 100) / TOTAL_TESTS )) + printf " Test Success Rate: ${BLUE}%d%%${NC}\n" "$TEST_SUCCESS_RATE" +fi + +printf "\n" + printf "Total Duration: ${BLUE}%d seconds${NC}\n\n" "$DURATION" # Print individual suite results diff --git a/tests/common/test-common.sh b/tests/common/test-common.sh index c56dd87..86f1a54 100644 --- a/tests/common/test-common.sh +++ b/tests/common/test-common.sh @@ -26,6 +26,10 @@ TEST_RESULTS=() FAILED_TESTS_SUMMARY=() PASS_COUNT=0 FAIL_COUNT=0 +# A cross-subshell results accumulator so command substitutions $(...) still record results +# Each line is a compact JSON object describing one API call result +RESULTS_FILE=${RESULTS_FILE:-"$(mktemp)"} +export RESULTS_FILE # Colors CYAN='\033[0;36m' @@ -105,12 +109,20 @@ test_api_endpoint() { FAILED_TESTS_SUMMARY+=("✗ $test_name - $error_msg") fi - result_json=$(jq -n --arg name "$test_name" --arg ep "$endpoint" --arg meth "$method" \ + result_json=$(jq -c -n --arg name "$test_name" --arg ep "$endpoint" --arg meth "$method" \ --arg stat "$status" --arg code "$http_status" --arg dur "$duration" \ --arg err "$error_msg" \ '{TestName: $name, Endpoint: $ep, Method: $meth, Status: $stat, StatusCode: $code, ResponseTimeMs: $dur, Error: $err}') + # Append to in-memory array for same-shell calls TEST_RESULTS+=("$result_json") - printf "%s" "$response_body" + # Also append to file so subshell calls (via command substitution) are not lost + printf "%s\n" "$result_json" >> "$RESULTS_FILE" + # Ensure we always print valid JSON to avoid jq parse errors downstream + if echo "$response_body" | jq . >/dev/null 2>&1; then + printf "%s" "$response_body" + else + jq -n --arg raw "$response_body" '{raw: $raw}' + fi } get_auth_token() { @@ -165,6 +177,104 @@ print_test_summary() { printf "${GREEN}Passed: %d${NC}\n" "$PASS_COUNT" printf "${RED}Failed: %d${NC}\n" "$FAIL_COUNT" printf "Success Rate: %d%%\n\n" "$success_rate" + + # Optional debug: show results file path and a preview when DEBUG_RESULTS=1 + if [[ "$DEBUG_RESULTS" = "1" || "$DEBUG_RESULTS" = "true" ]]; then + printf "${YELLOW}Debug: RESULTS_FILE=${NC} %s\n" "$RESULTS_FILE" + if [[ -f "$RESULTS_FILE" ]]; then + printf "${YELLOW}Debug: RESULTS_FILE size=${NC} %s bytes\n" "$(wc -c < "$RESULTS_FILE" 2>/dev/null || echo 0)" + printf "${YELLOW}Debug: RESULTS_FILE head (up to 5 lines):${NC}\n" + head -n 5 "$RESULTS_FILE" | sed 's/^/ /' + else + printf "${YELLOW}Debug: RESULTS_FILE does not exist${NC}\n" + fi + printf "\n" + fi + + # Detailed API results (from test_api_endpoint calls only) + # Prefer the persisted file so subshell calls are included + local api_total=0 + local api_pass=0 + local api_fail=0 + if [[ -s "$RESULTS_FILE" ]]; then + # shellcheck disable=SC2162 + while IFS= read -r r; do + [[ -z "$r" ]] && continue + # Skip non-JSON or malformed lines to avoid jq errors + if ! echo "$r" | jq -e 'type=="object" and has("Status")' >/dev/null 2>&1; then + continue + fi + ((api_total++)) + local st + st=$(echo "$r" | jq -r '.Status') + if [[ "$st" == "PASS" ]]; then + ((api_pass++)) + else + ((api_fail++)) + fi + done < "$RESULTS_FILE" + else + # Fallback to in-memory array (should be rare) + api_total=${#TEST_RESULTS[@]} + for r in "${TEST_RESULTS[@]}"; do + local st + st=$(echo "$r" | jq -r '.Status') + if [[ "$st" == "PASS" ]]; then + ((api_pass++)) + else + ((api_fail++)) + fi + done + fi + + if [ "$api_total" -gt 0 ]; then + printf "API Requests: %d (Passed: %d, Failed: %d)\n" "$api_total" "$api_pass" "$api_fail" + printf "\n${BLUE}API Results:${NC}\n" + if [[ -s "$RESULTS_FILE" ]]; then + # shellcheck disable=SC2162 + while IFS= read -r r; do + [[ -z "$r" ]] && continue + if ! echo "$r" | jq -e 'type=="object" and has("Status")' >/dev/null 2>&1; then + continue + fi + local name method ep status code dur + name=$(echo "$r" | jq -r '.TestName') + method=$(echo "$r" | jq -r '.Method') + ep=$(echo "$r" | jq -r '.Endpoint') + status=$(echo "$r" | jq -r '.Status') + code=$(echo "$r" | jq -r '.StatusCode') + dur=$(echo "$r" | jq -r '.ResponseTimeMs') + if [[ "$status" == "PASS" ]]; then + printf " ${GREEN}[%s]${NC} %s %s (status: %s, time: %sms) — %s\n" "$status" "$method" "$ep" "$code" "$dur" "$name" + else + printf " ${RED}[%s]${NC} %s %s (status: %s, time: %sms) — %s\n" "$status" "$method" "$ep" "$code" "$dur" "$name" + fi + done < "$RESULTS_FILE" + else + for r in "${TEST_RESULTS[@]}"; do + local name method ep status code dur + name=$(echo "$r" | jq -r '.TestName') + method=$(echo "$r" | jq -r '.Method') + ep=$(echo "$r" | jq -r '.Endpoint') + status=$(echo "$r" | jq -r '.Status') + code=$(echo "$r" | jq -r '.StatusCode') + dur=$(echo "$r" | jq -r '.ResponseTimeMs') + if [[ "$status" == "PASS" ]]; then + printf " ${GREEN}[%s]${NC} %s %s (status: %s, time: %sms) — %s\n" "$status" "$method" "$ep" "$code" "$dur" "$name" + else + printf " ${RED}[%s]${NC} %s %s (status: %s, time: %sms) — %s\n" "$status" "$method" "$ep" "$code" "$dur" "$name" + fi + done + fi + printf "\n" + fi + + # Align global PASS/FAIL counters with computed API results so exit codes reflect failures + # This ensures failures inside subshells are not ignored + if [ "$api_total" -gt 0 ]; then + PASS_COUNT=$api_pass + FAIL_COUNT=$api_fail + fi if [ "$FAIL_COUNT" -gt 0 ]; then printf "${RED}Failed Tests:${NC}\n" diff --git a/tests/hackathon/test-hackathon.sh b/tests/hackathon/test-hackathon.sh index e5aea77..30ef6cf 100644 --- a/tests/hackathon/test-hackathon.sh +++ b/tests/hackathon/test-hackathon.sh @@ -50,7 +50,10 @@ test_hackathon_endpoints() { title: "Kickoff Meeting", description: "Opening ceremony and team formation", event_date: "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'", + start_time: "'$(date -u +%Y-%m-%dT09:00:00Z)'", + end_time: "'$(date -u +%Y-%m-%dT11:00:00Z)'", location: "Online - Zoom", + event_type: "workshop", is_mandatory: true }') local create_event_response=$(test_api_endpoint "POST Create Hackathon Event" "POST" "/v1/hackathons/$created_hackathon_id/events" 201 "$create_event_data" true) @@ -72,6 +75,7 @@ test_hackathon_endpoints() { # === Hackathon Timeline === local create_timeline_data=$(jq -n --arg hackathon_id "$created_hackathon_id" '{ hackathon_id: $hackathon_id, + phase: "registration", phase_name: "Registration Phase", description: "Team registration and formation", start_date: "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'", @@ -84,6 +88,7 @@ test_hackathon_endpoints() { if [ -n "$created_timeline_id" ]; then # Update timeline local update_timeline_data=$(jq -n '{ + phase: "registration", phase_name: "Updated Registration Phase", description: "Updated description" }')