deps-test: Bump Microsoft.EntityFrameworkCore from 9.0.9 to 9.0.12 #99
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI/CD Pipeline - Parking Management System | |
| on: | |
| push: | |
| branches: [ "main", "master", "Csharp_development_CICDnew" ] | |
| pull_request: | |
| branches: [ "main", "master", "Csharp_development_CICDnew" ] | |
| permissions: | |
| contents: read | |
| security-events: write | |
| env: | |
| DOTNET_VERSION: '8.0.x' | |
| API_PROJECT_PATH: "CSharp Parking API/CSharp Parking API.csproj" | |
| TEST_PROJECT_PATH: "CSharp Parking Tests/CSharp Parking Tests.csproj" | |
| COVERAGE_SETTINGS_PATH: "CSharp Parking Tests/coverlet.runsettings" | |
| MIN_LINE_COVERAGE: 80.0 | |
| MIN_BRANCH_COVERAGE: 80.0 | |
| DOTNET_ROOT: /usr/share/dotnet | |
| # ============================================================ | |
| # JOB 1 — BUILD & TEST | |
| # ============================================================ | |
| jobs: | |
| build-and-test: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_VERSION }} | |
| - run: dotnet restore "${{ env.API_PROJECT_PATH }}" | |
| - run: dotnet restore "${{ env.TEST_PROJECT_PATH }}" | |
| - run: dotnet build "${{ env.API_PROJECT_PATH }}" --configuration Release --no-restore | |
| - run: dotnet build "${{ env.TEST_PROJECT_PATH }}" --configuration Release --no-restore | |
| - run: dotnet tool install -g dotnet-reportgenerator-globaltool | |
| - name: Run tests with detailed coverage | |
| run: | | |
| dotnet test "${{ env.TEST_PROJECT_PATH }}" \ | |
| --configuration Release \ | |
| --no-restore \ | |
| --verbosity normal \ | |
| --settings "${{ env.COVERAGE_SETTINGS_PATH }}" \ | |
| --collect:"XPlat Code Coverage" \ | |
| --results-directory ./test-results \ | |
| --logger "trx;LogFileName=test-results.trx" || true | |
| - name: Generate coverage report | |
| run: | | |
| reportgenerator \ | |
| "-reports:./test-results/**/coverage.cobertura.xml" \ | |
| "-targetdir:coveragereport" \ | |
| "-reporttypes:TextSummary" | |
| - name: Display coverage summary | |
| run: | | |
| echo "## Test Coverage Report" >> $GITHUB_STEP_SUMMARY | |
| echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY | |
| echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY | |
| if [ -f "coveragereport/Summary.txt" ]; then | |
| cat coveragereport/Summary.txt | grep -E "(Line coverage|Branch coverage|Method coverage)" | sed 's/:/|/' | sed 's/$/|/' >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "| Error | Coverage report not found |" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| - name: Display slowest API tests | |
| run: | | |
| python - <<'PY' | |
| import xml.etree.ElementTree as ET | |
| from datetime import timedelta | |
| trx_path = "test-results/test-results.trx" | |
| try: | |
| tree = ET.parse(trx_path) | |
| except FileNotFoundError: | |
| print("## API Test Response Times", file=open("GITHUB_STEP_SUMMARY", "a")) | |
| print("TRX file not found.", file=open("GITHUB_STEP_SUMMARY", "a")) | |
| raise SystemExit(0) | |
| root = tree.getroot() | |
| ns = {"t": root.tag.split("}")[0].strip("{")} if "}" in root.tag else {} | |
| results = root.findall(".//t:UnitTestResult", ns) if ns else root.findall(".//UnitTestResult") | |
| def parse_duration(d): | |
| # format: HH:MM:SS.mmmmmm | |
| parts = d.split(":") | |
| if len(parts) != 3: | |
| return 0.0 | |
| h, m = int(parts[0]), int(parts[1]) | |
| s = float(parts[2]) | |
| return h * 3600 + m * 60 + s | |
| items = [] | |
| for r in results: | |
| name = r.attrib.get("testName", "Unknown") | |
| duration = r.attrib.get("duration", "00:00:00") | |
| seconds = parse_duration(duration) | |
| items.append((seconds, name)) | |
| items.sort(reverse=True) | |
| top = items[:10] | |
| with open("GITHUB_STEP_SUMMARY", "a") as f: | |
| f.write("## API Test Response Times (slowest 10)\n") | |
| f.write("| Test | Duration (ms) |\n") | |
| f.write("|------|---------------|\n") | |
| for seconds, name in top: | |
| f.write(f"| {name} | {int(seconds*1000)} |\n") | |
| PY | |
| - name: Check branch coverage requirement (80%) | |
| run: | | |
| if [ -f "coveragereport/Summary.txt" ]; then | |
| BRANCH_COVERAGE=$(grep -oP '(?<=Branch coverage: )\d+(\.\d+)?' coveragereport/Summary.txt || echo "0") | |
| else | |
| echo "Coverage report not found - checking for any coverage files..." | |
| find . -name "*.cobertura.xml" -type f | |
| BRANCH_COVERAGE="0" | |
| fi | |
| echo "Current branch coverage: ${BRANCH_COVERAGE}%" | |
| echo "Required branch coverage: ${{ env.MIN_BRANCH_COVERAGE }}%" | |
| if (( $(echo "${BRANCH_COVERAGE} < ${{ env.MIN_BRANCH_COVERAGE }}" | bc -l) )); then | |
| echo "BRANCH COVERAGE REQUIREMENT FAILED" | |
| echo "Branch coverage (${BRANCH_COVERAGE}%) is below minimum threshold of ${{ env.MIN_BRANCH_COVERAGE }}%" | |
| echo "Fix: Add more tests to cover conditional branches and edge cases" | |
| exit 1 | |
| else | |
| echo "BRANCH COVERAGE REQUIREMENT MET" | |
| echo "Branch coverage (${BRANCH_COVERAGE}%) exceeds ${{ env.MIN_BRANCH_COVERAGE }}% threshold" | |
| fi | |
| - name: Enforce branch coverage | |
| run: | | |
| BRANCH=$(grep -oP '(?<=Branch coverage: )\d+(\.\d+)?' coveragereport/Summary.txt || echo 0) | |
| echo "Branch coverage: $BRANCH%" | |
| if (( $(echo "$BRANCH < ${{ env.MIN_BRANCH_COVERAGE }}" | bc -l) )); then | |
| echo "❌ Branch coverage too low" | |
| exit 1 | |
| fi | |
| - name: Upload coverage report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-report-${{ github.run_id }} | |
| path: coveragereport/ | |
| - uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: coverage-report | |
| path: coveragereport/ | |
| # ============================================================ | |
| # JOB 2 — SECURITY SCAN | |
| # ============================================================ | |
| security-scan: | |
| runs-on: ubuntu-latest | |
| needs: build-and-test | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: aquasecurity/trivy-action@master | |
| with: | |
| scan-type: 'fs' | |
| scan-ref: '.' | |
| format: 'sarif' | |
| output: 'trivy-results.sarif' | |
| - uses: github/codeql-action/upload-sarif@v4 | |
| with: | |
| sarif_file: trivy-results.sarif | |
| # ============================================================ | |
| # JOB 3 — STAGING DEPLOY (SELF-HOSTED, NO SSH/SCP) | |
| # ============================================================ | |
| deploy-staging: | |
| runs-on: self-hosted | |
| needs: [build-and-test, security-scan] | |
| if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' || github.ref == 'refs/heads/Csharp_development_CICDnew' | |
| environment: staging | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Debug runner & docker access | |
| run: | | |
| echo "== Runner info ==" | |
| whoami | |
| hostname | |
| pwd | |
| echo | |
| echo "== Docker info ==" | |
| docker --version | |
| docker ps | |
| - name: Build Docker image (staging) | |
| run: | | |
| docker build -t parking-api-staging:${{ github.run_id }} -f "CSharp Parking API/Dockerfile" . | |
| - name: Tag image as latest (staging) | |
| run: | | |
| docker tag parking-api-staging:${{ github.run_id }} parking-api-staging:latest | |
| - name: Stop old staging container (if any) | |
| run: | | |
| docker rm -f parking-api-staging || true | |
| - name: Run staging container (port 8402) | |
| run: | | |
| docker run -d --restart unless-stopped --name parking-api-staging -p 8402:80 -e ASPNETCORE_ENVIRONMENT=Docker parking-api-staging:${{ github.run_id }} | |
| - name: Verify staging deployment | |
| run: | | |
| echo "== docker ps ==" | |
| docker ps --filter "name=parking-api-staging" | |
| echo | |
| echo "== docker port ==" | |
| docker port parking-api-staging || true | |
| echo | |
| echo "== last logs ==" | |
| docker logs --tail 50 parking-api-staging || true | |
| # Job 5: Notifications | |
| notifications: | |
| runs-on: ubuntu-latest | |
| needs: [build-and-test, security-scan, deploy-staging] | |
| if: always() | |
| steps: | |
| - name: Pipeline success notification | |
| if: needs.build-and-test.result == 'success' && needs.security-scan.result == 'success' | |
| run: | | |
| echo "CI/CD Pipeline Successful!" | |
| echo "Tests: PASSED" | |
| echo "Security: PASSED" | |
| echo "Deployment: Ready" | |
| echo "Deployment: http://your-host:8402/swagger" | |
| - name: Pipeline failure notification | |
| if: failure() | |
| run: | | |
| echo "CI/CD Pipeline Failed!" | |
| echo "Check the logs above for failure details" | |
| echo "Common fixes:" | |
| echo " - Check test failures in test-results" | |
| echo " - Verify coverage meets 80% requirement" | |
| echo " - Review security scan results" |