From a60965a35c1bda6a8c163c7b55927fe2646a2b7f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Oct 2025 19:09:40 +0000 Subject: [PATCH 1/4] Initial plan From b4d7976db0c6ae5451c1c1d839ebaf34bd1f32c9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Oct 2025 19:16:39 +0000 Subject: [PATCH 2/4] Add test coverage CI workflow with PR comments and summary Co-authored-by: Mossaka <5447827+Mossaka@users.noreply.github.com> --- .github/workflows/test-coverage.yml | 165 ++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 .github/workflows/test-coverage.yml diff --git a/.github/workflows/test-coverage.yml b/.github/workflows/test-coverage.yml new file mode 100644 index 00000000..f524618d --- /dev/null +++ b/.github/workflows/test-coverage.yml @@ -0,0 +1,165 @@ +name: Test Coverage + +on: + pull_request: + branches: [main] + push: + branches: [main] + +permissions: + contents: read + pull-requests: write + checks: write + +jobs: + coverage: + name: Test Coverage Report + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Checkout repository + uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4 + + - name: Setup Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build project + run: npm run build + + - name: Run tests with coverage + run: npm run test:coverage + + - name: Generate coverage summary + id: coverage + run: | + # Read the coverage summary + COVERAGE_JSON=$(cat coverage/coverage-summary.json) + + # Extract metrics using jq + LINES_PCT=$(echo "$COVERAGE_JSON" | jq -r '.total.lines.pct') + STATEMENTS_PCT=$(echo "$COVERAGE_JSON" | jq -r '.total.statements.pct') + FUNCTIONS_PCT=$(echo "$COVERAGE_JSON" | jq -r '.total.functions.pct') + BRANCHES_PCT=$(echo "$COVERAGE_JSON" | jq -r '.total.branches.pct') + + LINES_COVERED=$(echo "$COVERAGE_JSON" | jq -r '.total.lines.covered') + LINES_TOTAL=$(echo "$COVERAGE_JSON" | jq -r '.total.lines.total') + STATEMENTS_COVERED=$(echo "$COVERAGE_JSON" | jq -r '.total.statements.covered') + STATEMENTS_TOTAL=$(echo "$COVERAGE_JSON" | jq -r '.total.statements.total') + FUNCTIONS_COVERED=$(echo "$COVERAGE_JSON" | jq -r '.total.functions.covered') + FUNCTIONS_TOTAL=$(echo "$COVERAGE_JSON" | jq -r '.total.functions.total') + BRANCHES_COVERED=$(echo "$COVERAGE_JSON" | jq -r '.total.branches.covered') + BRANCHES_TOTAL=$(echo "$COVERAGE_JSON" | jq -r '.total.branches.total') + + # Create summary for GitHub Actions Summary + echo "## Test Coverage Report 📊" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Metric | Coverage | Covered/Total |" >> $GITHUB_STEP_SUMMARY + echo "|--------|----------|---------------|" >> $GITHUB_STEP_SUMMARY + echo "| **Lines** | ${LINES_PCT}% | ${LINES_COVERED}/${LINES_TOTAL} |" >> $GITHUB_STEP_SUMMARY + echo "| **Statements** | ${STATEMENTS_PCT}% | ${STATEMENTS_COVERED}/${STATEMENTS_TOTAL} |" >> $GITHUB_STEP_SUMMARY + echo "| **Functions** | ${FUNCTIONS_PCT}% | ${FUNCTIONS_COVERED}/${FUNCTIONS_TOTAL} |" >> $GITHUB_STEP_SUMMARY + echo "| **Branches** | ${BRANCHES_PCT}% | ${BRANCHES_COVERED}/${BRANCHES_TOTAL} |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Determine coverage status emoji + if (( $(echo "$LINES_PCT >= 80" | bc -l) )); then + EMOJI="✅" + elif (( $(echo "$LINES_PCT >= 60" | bc -l) )); then + EMOJI="⚠️" + else + EMOJI="❌" + fi + + # Create PR comment body + COMMENT_BODY="## Test Coverage Report ${EMOJI} + + | Metric | Coverage | Covered/Total | + |--------|----------|---------------| + | **Lines** | ${LINES_PCT}% | ${LINES_COVERED}/${LINES_TOTAL} | + | **Statements** | ${STATEMENTS_PCT}% | ${STATEMENTS_COVERED}/${STATEMENTS_TOTAL} | + | **Functions** | ${FUNCTIONS_PCT}% | ${FUNCTIONS_COVERED}/${FUNCTIONS_TOTAL} | + | **Branches** | ${BRANCHES_PCT}% | ${BRANCHES_COVERED}/${BRANCHES_TOTAL} | + +
+ Coverage Thresholds + + The project has the following coverage thresholds configured: + - Lines: 38% + - Statements: 38% + - Functions: 35% + - Branches: 30% + +
+ + --- + *Coverage report generated by \\\`npm run test:coverage\\\`*" + + # Save for next step (escape newlines for GitHub Actions) + echo "COMMENT_BODY<> $GITHUB_ENV + echo "$COMMENT_BODY" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + # Also save individual metrics as outputs + echo "lines_pct=${LINES_PCT}" >> $GITHUB_OUTPUT + echo "statements_pct=${STATEMENTS_PCT}" >> $GITHUB_OUTPUT + echo "functions_pct=${FUNCTIONS_PCT}" >> $GITHUB_OUTPUT + echo "branches_pct=${BRANCHES_PCT}" >> $GITHUB_OUTPUT + + - name: Comment PR with coverage report + if: github.event_name == 'pull_request' + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const commentBody = process.env.COMMENT_BODY; + + // Find existing coverage comment + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + + const botComment = comments.find(comment => + comment.user.type === 'Bot' && + comment.body.includes('Test Coverage Report') + ); + + if (botComment) { + // Update existing comment + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: botComment.id, + body: commentBody + }); + } else { + // Create new comment + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: commentBody + }); + } + + - name: Upload coverage reports + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: coverage-report + path: | + coverage/ + retention-days: 30 + + - name: Check coverage thresholds + run: | + echo "Checking if coverage meets minimum thresholds..." + # Jest will fail if coverage is below thresholds defined in jest.config.js + # This step is informational since the test:coverage command already checks From 2c7d0d2595e37b517c518d07f822c493234af925 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Oct 2025 19:20:20 +0000 Subject: [PATCH 3/4] Update documentation for test coverage CI workflow Co-authored-by: Mossaka <5447827+Mossaka@users.noreply.github.com> --- CONTRIBUTING.md | 4 +++- TESTING.md | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d5eb3f0f..f70e5d0a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -109,10 +109,12 @@ logger.success('Operation completed successfully'); - Clear description of what the PR does - Reference any related issues - Include tests for new functionality - - Ensure CI passes + - Ensure CI passes (including test coverage checks) + - Review the automated coverage report posted as a PR comment 3. **Review process:** - Maintainers will review your PR + - The coverage report bot will automatically comment with test coverage metrics - Address any feedback - Once approved, your PR will be merged diff --git a/TESTING.md b/TESTING.md index 4844c27e..dcbfbfa4 100644 --- a/TESTING.md +++ b/TESTING.md @@ -53,6 +53,24 @@ After running `npm run test:coverage`, coverage reports are available in the `co - **LCOV**: `coverage/lcov.info` for integration with CI/CD tools - **JSON**: `coverage/coverage-summary.json` for programmatic access +### CI/CD Coverage Reporting + +The project includes automated test coverage reporting via GitHub Actions (`.github/workflows/test-coverage.yml`): + +- **Automatic PR Comments**: Coverage reports are automatically posted as comments on pull requests +- **GitHub Actions Summary**: Each workflow run includes a coverage summary in the job output +- **Coverage Artifacts**: Full coverage reports are uploaded as artifacts for 30 days +- **Update Strategy**: Existing coverage comments are updated on subsequent pushes to avoid comment spam + +The coverage workflow runs on: +- All pull requests to `main` +- All pushes to `main` + +Required permissions: +- `contents: read` - To checkout the repository +- `pull-requests: write` - To post/update PR comments +- `checks: write` - To update check status + ### Coverage Thresholds The project maintains the following minimum coverage thresholds (configured in `jest.config.js`): From fe9ba8ddddfb9662a6a747c07aaea2b0da67ae73 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Oct 2025 19:38:52 +0000 Subject: [PATCH 4/4] Remove visual indicators from coverage report Co-authored-by: Mossaka <5447827+Mossaka@users.noreply.github.com> --- .github/workflows/test-coverage.yml | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test-coverage.yml b/.github/workflows/test-coverage.yml index f524618d..32754371 100644 --- a/.github/workflows/test-coverage.yml +++ b/.github/workflows/test-coverage.yml @@ -58,7 +58,7 @@ jobs: BRANCHES_TOTAL=$(echo "$COVERAGE_JSON" | jq -r '.total.branches.total') # Create summary for GitHub Actions Summary - echo "## Test Coverage Report 📊" >> $GITHUB_STEP_SUMMARY + echo "## Test Coverage Report" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| Metric | Coverage | Covered/Total |" >> $GITHUB_STEP_SUMMARY echo "|--------|----------|---------------|" >> $GITHUB_STEP_SUMMARY @@ -68,17 +68,8 @@ jobs: echo "| **Branches** | ${BRANCHES_PCT}% | ${BRANCHES_COVERED}/${BRANCHES_TOTAL} |" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - # Determine coverage status emoji - if (( $(echo "$LINES_PCT >= 80" | bc -l) )); then - EMOJI="✅" - elif (( $(echo "$LINES_PCT >= 60" | bc -l) )); then - EMOJI="⚠️" - else - EMOJI="❌" - fi - # Create PR comment body - COMMENT_BODY="## Test Coverage Report ${EMOJI} + COMMENT_BODY="## Test Coverage Report | Metric | Coverage | Covered/Total | |--------|----------|---------------|