mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-02 20:30:10 +00:00
Add backend unit test framework and modularize settings UI
Introduced a Vitest-based backend unit testing framework with supporting scripts, helpers, and GitHub Actions integration. Refactored the admin settings page to a modular architecture, splitting monolithic logic into feature-specific tabs and hooks for improved maintainability and testability. Updated documentation to reflect the new testing setup and settings architecture, and added new dependencies for testing utilities.
This commit is contained in:
@@ -13,7 +13,14 @@ env:
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Run Tests
|
||||
uses: ./.github/workflows/run-tests.yml
|
||||
secrets:
|
||||
WEBHOOK_URL: ${{ secrets.WEBHOOK_URL }}
|
||||
|
||||
build-and-push:
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -94,3 +101,43 @@ jobs:
|
||||
echo " -v readmeabook-data:/var/lib/postgresql/data \\" >> $GITHUB_STEP_SUMMARY
|
||||
echo " ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Send Discord notification
|
||||
if: github.event_name != 'pull_request' && success()
|
||||
run: |
|
||||
curl -H "Content-Type: application/json" \
|
||||
-X POST \
|
||||
-d '{
|
||||
"embeds": [{
|
||||
"title": "📦 Docker Image Published",
|
||||
"description": "A new version of **ReadMeABook** has been built and published to GitHub Container Registry.",
|
||||
"color": 5763719,
|
||||
"fields": [
|
||||
{
|
||||
"name": "🏷️ Image Tag",
|
||||
"value": "`sha-${{ steps.version.outputs.git_commit }}`",
|
||||
"inline": true
|
||||
},
|
||||
{
|
||||
"name": "🌿 Branch",
|
||||
"value": "`${{ github.ref_name }}`",
|
||||
"inline": true
|
||||
},
|
||||
{
|
||||
"name": "📋 Commit",
|
||||
"value": "[`${{ steps.version.outputs.git_commit }}`](${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }})",
|
||||
"inline": true
|
||||
},
|
||||
{
|
||||
"name": "📥 Pull Command",
|
||||
"value": "```docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ steps.version.outputs.git_commit }}```",
|
||||
"inline": false
|
||||
}
|
||||
],
|
||||
"footer": {
|
||||
"text": "ReadMeABook CI/CD • Built with GitHub Actions"
|
||||
},
|
||||
"timestamp": "${{ steps.version.outputs.build_date }}"
|
||||
}]
|
||||
}' \
|
||||
${{ secrets.WEBHOOK_URL }}
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
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 -- --reporter=json --outputFile=test-results.json 2>&1 | tee test-output.txt
|
||||
END_TIME=$(date +%s)
|
||||
echo "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: |
|
||||
if [ -f test-results.json ]; then
|
||||
TOTAL=$(jq '.numTotalTests // 0' test-results.json)
|
||||
PASSED=$(jq '.numPassedTests // 0' test-results.json)
|
||||
FAILED=$(jq '.numFailedTests // 0' test-results.json)
|
||||
TEST_FILES=$(jq '.numTotalTestSuites // 0' test-results.json)
|
||||
TEST_FILES_PASSED=$(jq '.numPassedTestSuites // 0' test-results.json)
|
||||
TEST_FILES_FAILED=$(jq '.numFailedTestSuites // 0' test-results.json)
|
||||
DURATION=$(jq '((.testResults | map(.endTime) | max) - (.testResults | map(.startTime) | min)) / 1000 | . * 100 | floor / 100' test-results.json 2>/dev/null || echo "0")
|
||||
SUCCESS=$([ "$FAILED" -eq 0 ] && echo "true" || echo "false")
|
||||
else
|
||||
TOTAL=0
|
||||
PASSED=0
|
||||
FAILED=1
|
||||
TEST_FILES=0
|
||||
TEST_FILES_PASSED=0
|
||||
TEST_FILES_FAILED=1
|
||||
DURATION=0
|
||||
SUCCESS="false"
|
||||
fi
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user