Skip to content

deps-test: Bump Microsoft.EntityFrameworkCore from 9.0.9 to 9.0.12 #99

deps-test: Bump Microsoft.EntityFrameworkCore from 9.0.9 to 9.0.12

deps-test: Bump Microsoft.EntityFrameworkCore from 9.0.9 to 9.0.12 #99

Workflow file for this run

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"