name: Code Coverage on: push: branches: [ main, develop ] pull_request: branches: [ main, develop ] schedule: # Run daily at 2 AM UTC - cron: '0 2 * * *' workflow_dispatch: jobs: coverage: name: Generate Coverage Report runs-on: ubuntu-latest services: postgres: image: postgres:16-alpine env: POSTGRES_DB: colaflow_test POSTGRES_USER: colaflow_test POSTGRES_PASSWORD: colaflow_test_password options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 ports: - 5432:5432 redis: image: redis:7-alpine options: >- --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 3s --health-retries 5 ports: - 6379:6379 steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup .NET 9 uses: actions/setup-dotnet@v4 with: dotnet-version: '9.0.x' - name: Restore dependencies run: dotnet restore working-directory: ./src - name: Build solution run: dotnet build --no-restore --configuration Release working-directory: ./src - name: Run tests with coverage run: | dotnet test \ --no-build \ --configuration Release \ --logger "console;verbosity=minimal" \ /p:CollectCoverage=true \ /p:CoverletOutputFormat=opencover \ /p:CoverletOutput=./coverage/ \ /p:ExcludeByFile="**/*.g.cs,**/*.Designer.cs,**/Migrations/**" \ /p:Exclude="[*.Tests]*" working-directory: ./tests env: ConnectionStrings__DefaultConnection: "Host=localhost;Port=5432;Database=colaflow_test;Username=colaflow_test;Password=colaflow_test_password" ConnectionStrings__Redis: "localhost:6379" - name: Install ReportGenerator run: dotnet tool install -g dotnet-reportgenerator-globaltool - name: Generate detailed coverage report run: | reportgenerator \ -reports:./tests/coverage/coverage.opencover.xml \ -targetdir:./coverage-report \ -reporttypes:"Html;Badges;TextSummary;MarkdownSummaryGithub;Cobertura" - name: Display coverage summary run: | echo "## Coverage Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY cat ./coverage-report/SummaryGithub.md >> $GITHUB_STEP_SUMMARY - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 with: files: ./tests/coverage/coverage.opencover.xml flags: unittests name: colaflow-coverage fail_ci_if_error: false env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} continue-on-error: true - name: Archive coverage report uses: actions/upload-artifact@v4 with: name: coverage-report-${{ github.sha }} path: ./coverage-report retention-days: 90 - name: Generate coverage badge run: | # Extract line coverage percentage COVERAGE=$(grep "Line coverage:" ./coverage-report/Summary.txt | awk '{print $3}' | sed 's/%//') echo "COVERAGE=$COVERAGE" >> $GITHUB_ENV # Determine badge color if (( $(echo "$COVERAGE >= 90" | bc -l) )); then COLOR="brightgreen" elif (( $(echo "$COVERAGE >= 80" | bc -l) )); then COLOR="green" elif (( $(echo "$COVERAGE >= 70" | bc -l) )); then COLOR="yellow" elif (( $(echo "$COVERAGE >= 60" | bc -l) )); then COLOR="orange" else COLOR="red" fi echo "BADGE_COLOR=$COLOR" >> $GITHUB_ENV - name: Create coverage badge uses: schneegans/dynamic-badges-action@v1.7.0 with: auth: ${{ secrets.GIST_SECRET }} gistID: your-gist-id-here filename: colaflow-coverage.json label: Coverage message: ${{ env.COVERAGE }}% color: ${{ env.BADGE_COLOR }} continue-on-error: true - name: Check coverage threshold run: | COVERAGE=$(grep "Line coverage:" ./coverage-report/Summary.txt | awk '{print $3}' | sed 's/%//') echo "📊 Coverage Report" echo "==================" cat ./coverage-report/Summary.txt echo "" if (( $(echo "$COVERAGE < 80" | bc -l) )); then echo "❌ FAILED: Coverage $COVERAGE% is below threshold 80%" exit 1 else echo "✅ PASSED: Coverage $COVERAGE% meets threshold 80%" fi - name: Comment coverage on PR if: github.event_name == 'pull_request' uses: actions/github-script@v7 with: script: | const fs = require('fs'); const summary = fs.readFileSync('./coverage-report/Summary.txt', 'utf8'); const comment = `## 📊 Code Coverage Report ${summary} [View detailed report in artifacts](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) `; github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: comment });