diff --git a/.github/workflows/test-coverage.yml b/.github/workflows/test-coverage.yml
new file mode 100644
index 00000000..32754371
--- /dev/null
+++ b/.github/workflows/test-coverage.yml
@@ -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} |
+
+
+ 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
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`):