name: Backend Tests on: workflow_dispatch: workflow_call: inputs: send_notification: description: "Whether to send Discord notification" type: boolean default: true secrets: WEBHOOK_URL: description: "Discord webhook URL" required: false outputs: success: description: "Whether tests passed" value: ${{ jobs.test.outputs.success }} total: description: "Total number of tests" value: ${{ jobs.test.outputs.total }} passed: description: "Number of passed tests" value: ${{ jobs.test.outputs.passed }} failed: description: "Number of failed tests" value: ${{ jobs.test.outputs.failed }} duration: description: "Test duration in seconds" value: ${{ jobs.test.outputs.duration }} permissions: contents: read jobs: test: runs-on: ubuntu-latest outputs: success: ${{ steps.test-results.outputs.success }} total: ${{ steps.test-results.outputs.total }} passed: ${{ steps.test-results.outputs.passed }} failed: ${{ steps.test-results.outputs.failed }} duration: ${{ steps.test-results.outputs.duration }} test_files: ${{ steps.test-results.outputs.test_files }} test_files_passed: ${{ steps.test-results.outputs.test_files_passed }} test_files_failed: ${{ steps.test-results.outputs.test_files_failed }} steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci - name: Run tests id: run-tests continue-on-error: true run: | START_TIME=$(date +%s) npm test 2>&1 | tee test-output.txt TEST_EXIT_CODE=${PIPESTATUS[0]} END_TIME=$(date +%s) echo "exit_code=$TEST_EXIT_CODE" >> $GITHUB_OUTPUT echo "start_time=$(date -u +"%H:%M:%S")" >> $GITHUB_OUTPUT echo "elapsed=$((END_TIME - START_TIME))" >> $GITHUB_OUTPUT - name: Parse test results id: test-results if: always() run: | # Parse the test output from vitest's default reporter if [ -f test-output.txt ]; then # Strip ANSI color codes for reliable parsing sed -i 's/\x1b\[[0-9;]*m//g' test-output.txt # Debug: show relevant lines echo "=== Relevant test output lines ===" grep -E "(Test Files|Tests|Duration)" test-output.txt | tail -5 || true echo "===================================" # Extract test file count (e.g., "Test Files 81 passed (81)") TEST_FILES_LINE=$(grep "Test Files" test-output.txt | tail -1 || echo "") if [ -n "$TEST_FILES_LINE" ]; then TEST_FILES_PASSED=$(echo "$TEST_FILES_LINE" | grep -oE "[0-9]+ passed" | grep -oE "[0-9]+" | head -1 || echo "0") TEST_FILES_FAILED=$(echo "$TEST_FILES_LINE" | grep -oE "[0-9]+ failed" | grep -oE "[0-9]+" | head -1 || echo "0") TEST_FILES=$(echo "$TEST_FILES_LINE" | grep -oE "\([0-9]+\)" | grep -oE "[0-9]+" | head -1 || echo "0") else TEST_FILES_PASSED=0 TEST_FILES_FAILED=0 TEST_FILES=0 fi # Extract test count - look for line containing "Tests" followed by number, exclude "Test Files" TESTS_LINE=$(grep -E "Tests\s+[0-9]+" test-output.txt | grep -v "Test Files" | tail -1 || echo "") if [ -n "$TESTS_LINE" ]; then PASSED=$(echo "$TESTS_LINE" | grep -oE "[0-9]+ passed" | grep -oE "[0-9]+" | head -1 || echo "0") FAILED=$(echo "$TESTS_LINE" | grep -oE "[0-9]+ failed" | grep -oE "[0-9]+" | head -1 || echo "0") TOTAL=$(echo "$TESTS_LINE" | grep -oE "\([0-9]+\)" | grep -oE "[0-9]+" | head -1 || echo "0") else PASSED=0 FAILED=0 TOTAL=0 fi # Extract duration (e.g., "Duration 26.97s") DURATION_LINE=$(grep -E "Duration\s+[0-9]+" test-output.txt | tail -1 || echo "") if [ -n "$DURATION_LINE" ]; then DURATION=$(echo "$DURATION_LINE" | grep -oE "[0-9]+\.[0-9]+s" | head -1 | sed 's/s//' || echo "0") else DURATION="${{ steps.run-tests.outputs.elapsed }}" fi # Determine success based on exit code and failed count if [ "${{ steps.run-tests.outputs.exit_code }}" = "0" ] && [ "${FAILED:-0}" = "0" ]; then SUCCESS="true" else SUCCESS="false" fi else TOTAL=0 PASSED=0 FAILED=1 TEST_FILES=0 TEST_FILES_PASSED=0 TEST_FILES_FAILED=1 DURATION=0 SUCCESS="false" fi # Set defaults for empty values TOTAL=${TOTAL:-0} PASSED=${PASSED:-0} FAILED=${FAILED:-0} TEST_FILES=${TEST_FILES:-0} TEST_FILES_PASSED=${TEST_FILES_PASSED:-0} TEST_FILES_FAILED=${TEST_FILES_FAILED:-0} DURATION=${DURATION:-0} echo "=== Parsed values ===" echo "TEST_FILES_PASSED=$TEST_FILES_PASSED" echo "TEST_FILES=$TEST_FILES" echo "PASSED=$PASSED" echo "TOTAL=$TOTAL" echo "DURATION=$DURATION" echo "SUCCESS=$SUCCESS" echo "====================" echo "total=$TOTAL" >> $GITHUB_OUTPUT echo "passed=$PASSED" >> $GITHUB_OUTPUT echo "failed=$FAILED" >> $GITHUB_OUTPUT echo "duration=$DURATION" >> $GITHUB_OUTPUT echo "success=$SUCCESS" >> $GITHUB_OUTPUT echo "test_files=$TEST_FILES" >> $GITHUB_OUTPUT echo "test_files_passed=$TEST_FILES_PASSED" >> $GITHUB_OUTPUT echo "test_files_failed=$TEST_FILES_FAILED" >> $GITHUB_OUTPUT echo "## ๐Ÿงช Test Results" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY if [ "$SUCCESS" = "true" ]; then echo "### โœ… All tests passed!" >> $GITHUB_STEP_SUMMARY else echo "### โŒ Some tests failed" >> $GITHUB_STEP_SUMMARY fi echo "" >> $GITHUB_STEP_SUMMARY echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY echo "| Test Files | $TEST_FILES_PASSED passed ($TEST_FILES) |" >> $GITHUB_STEP_SUMMARY echo "| Tests | $PASSED passed ($TOTAL) |" >> $GITHUB_STEP_SUMMARY echo "| Duration | ${DURATION}s |" >> $GITHUB_STEP_SUMMARY - name: Send Discord notification if: always() && (inputs.send_notification != false) env: WEBHOOK_URL: ${{ secrets.WEBHOOK_URL }} run: | if [ -z "$WEBHOOK_URL" ]; then echo "No webhook URL provided, skipping notification" exit 0 fi SUCCESS="${{ steps.test-results.outputs.success }}" TOTAL="${{ steps.test-results.outputs.total }}" PASSED="${{ steps.test-results.outputs.passed }}" FAILED="${{ steps.test-results.outputs.failed }}" DURATION="${{ steps.test-results.outputs.duration }}" TEST_FILES="${{ steps.test-results.outputs.test_files }}" TEST_FILES_PASSED="${{ steps.test-results.outputs.test_files_passed }}" TEST_FILES_FAILED="${{ steps.test-results.outputs.test_files_failed }}" if [ "$SUCCESS" = "true" ]; then COLOR=5763719 TITLE="โœ… Tests Passed" DESCRIPTION="All tests completed successfully for **ReadMeABook**" TEST_FILES_VALUE="$TEST_FILES_PASSED passed ($TEST_FILES)" TESTS_VALUE="$PASSED passed ($TOTAL)" else COLOR=15548997 TITLE="โŒ Tests Failed" DESCRIPTION="Some tests failed for **ReadMeABook**" TEST_FILES_VALUE="$TEST_FILES_PASSED passed, $TEST_FILES_FAILED failed ($TEST_FILES)" TESTS_VALUE="$PASSED passed, $FAILED failed ($TOTAL)" fi curl -H "Content-Type: application/json" \ -X POST \ -d "{ \"embeds\": [{ \"title\": \"$TITLE\", \"description\": \"$DESCRIPTION\", \"color\": $COLOR, \"fields\": [ { \"name\": \"๐Ÿ“ Test Files\", \"value\": \"\`$TEST_FILES_VALUE\`\", \"inline\": true }, { \"name\": \"๐Ÿงช Tests\", \"value\": \"\`$TESTS_VALUE\`\", \"inline\": true }, { \"name\": \"โฑ๏ธ Duration\", \"value\": \"\`${DURATION}s\`\", \"inline\": true }, { \"name\": \"๐ŸŒฟ Branch\", \"value\": \"\`${{ github.ref_name }}\`\", \"inline\": true }, { \"name\": \"๐Ÿ“‹ Commit\", \"value\": \"[\`$(echo ${{ github.sha }} | cut -c1-7)\`](${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }})\", \"inline\": true }, { \"name\": \"๐Ÿ”— Workflow\", \"value\": \"[View Run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})\", \"inline\": true } ], \"footer\": { \"text\": \"ReadMeABook CI/CD โ€ข Test Suite\" }, \"timestamp\": \"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\" }] }" \ "$WEBHOOK_URL" - name: Fail if tests failed if: steps.test-results.outputs.success != 'true' run: exit 1