Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions .github/workflows/test-coverage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
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

# Create PR comment body
COMMENT_BODY="## Test Coverage Report

| 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} |

<details>
<summary>Coverage Thresholds</summary>

The project has the following coverage thresholds configured:
- Lines: 38%
- Statements: 38%
- Functions: 35%
- Branches: 30%

</details>

---
*Coverage report generated by \\\`npm run test:coverage\\\`*"

# Save for next step (escape newlines for GitHub Actions)
echo "COMMENT_BODY<<EOF" >> $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
4 changes: 3 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
18 changes: 18 additions & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`):
Expand Down