Enforce explicit test coverage thresholds in CI
This commit is contained in:
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@@ -40,4 +40,7 @@ jobs:
|
|||||||
run: dotnet build GameList.sln --no-restore -warnaserror
|
run: dotnet build GameList.sln --no-restore -warnaserror
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: dotnet test GameList.Tests/GameList.Tests.csproj --no-build --verbosity normal
|
run: dotnet test GameList.Tests/GameList.Tests.csproj --no-build --verbosity normal --collect:"XPlat Code Coverage"
|
||||||
|
|
||||||
|
- name: Enforce coverage thresholds
|
||||||
|
run: pwsh ./scripts/check-coverage.ps1 -MinLineRate 0.90 -MinBranchRate 0.70
|
||||||
|
|||||||
@@ -60,4 +60,5 @@ GitHub Actions workflow: `.github/workflows/ci.yml`
|
|||||||
- Restores dependencies
|
- Restores dependencies
|
||||||
- Runs frontend lint and format checks
|
- Runs frontend lint and format checks
|
||||||
- Builds with warnings treated as errors
|
- Builds with warnings treated as errors
|
||||||
- Runs `GameList.Tests`
|
- Runs `GameList.Tests` with coverage collection
|
||||||
|
- Enforces minimum coverage thresholds (line 90%, branch 70%)
|
||||||
|
|||||||
7
TESTS.md
7
TESTS.md
@@ -94,7 +94,12 @@ stateDiagram-v2
|
|||||||
- Security middleware tests validate response headers and rate-limiting behavior on auth/admin routes.
|
- Security middleware tests validate response headers and rate-limiting behavior on auth/admin routes.
|
||||||
- Frontend regression guard tests assert modal/admin JS no longer interpolate untrusted values in vulnerable patterns.
|
- Frontend regression guard tests assert modal/admin JS no longer interpolate untrusted values in vulnerable patterns.
|
||||||
|
|
||||||
|
## Coverage Policy
|
||||||
|
- CI and local script enforce Cobertura thresholds from test coverage collection.
|
||||||
|
- Minimum line coverage: 90%.
|
||||||
|
- Minimum branch coverage: 70%.
|
||||||
|
|
||||||
## Execution Notes
|
## Execution Notes
|
||||||
- Use named test data builders for players/suggestions to keep cases small and isolated.
|
- Use named test data builders for players/suggestions to keep cases small and isolated.
|
||||||
- Reset in-memory DB per test to avoid cross-contamination; assert timestamps using time providers or approximate windows.
|
- Reset in-memory DB per test to avoid cross-contamination; assert timestamps using time providers or approximate windows.
|
||||||
- Cover success + failure for every endpoint status path to reach 100% line/branch coverage.
|
- Cover success + failure for endpoint status paths and critical helper branches to stay above enforced thresholds.
|
||||||
|
|||||||
43
scripts/check-coverage.ps1
Normal file
43
scripts/check-coverage.ps1
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
param(
|
||||||
|
[double]$MinLineRate = 0.90,
|
||||||
|
[double]$MinBranchRate = 0.70,
|
||||||
|
[string]$ResultsRoot = "GameList.Tests/TestResults"
|
||||||
|
)
|
||||||
|
|
||||||
|
Set-StrictMode -Version Latest
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
if (-not (Test-Path $ResultsRoot)) {
|
||||||
|
throw "Coverage results folder not found: $ResultsRoot"
|
||||||
|
}
|
||||||
|
|
||||||
|
$coverageFile = Get-ChildItem -Path $ResultsRoot -Recurse -Filter "coverage.cobertura.xml" |
|
||||||
|
Sort-Object LastWriteTimeUtc -Descending |
|
||||||
|
Select-Object -First 1
|
||||||
|
|
||||||
|
if ($null -eq $coverageFile) {
|
||||||
|
throw "No coverage.cobertura.xml found under $ResultsRoot"
|
||||||
|
}
|
||||||
|
|
||||||
|
[xml]$xml = Get-Content -Path $coverageFile.FullName
|
||||||
|
$coverage = $xml.coverage
|
||||||
|
|
||||||
|
if ($null -eq $coverage) {
|
||||||
|
throw "Coverage XML is missing root coverage node: $($coverageFile.FullName)"
|
||||||
|
}
|
||||||
|
|
||||||
|
[double]$lineRate = [double]$coverage.'line-rate'
|
||||||
|
[double]$branchRate = [double]$coverage.'branch-rate'
|
||||||
|
|
||||||
|
$linePercent = [Math]::Round($lineRate * 100, 2)
|
||||||
|
$branchPercent = [Math]::Round($branchRate * 100, 2)
|
||||||
|
$minLinePercent = [Math]::Round($MinLineRate * 100, 2)
|
||||||
|
$minBranchPercent = [Math]::Round($MinBranchRate * 100, 2)
|
||||||
|
|
||||||
|
Write-Host "Coverage source: $($coverageFile.FullName)"
|
||||||
|
Write-Host ("Line coverage: {0}% (required >= {1}%)" -f $linePercent, $minLinePercent)
|
||||||
|
Write-Host ("Branch coverage: {0}% (required >= {1}%)" -f $branchPercent, $minBranchPercent)
|
||||||
|
|
||||||
|
if ($lineRate -lt $MinLineRate -or $branchRate -lt $MinBranchRate) {
|
||||||
|
throw "Coverage thresholds failed."
|
||||||
|
}
|
||||||
@@ -53,13 +53,17 @@ try {
|
|||||||
|
|
||||||
Invoke-Step -Name "Run tests" -Action {
|
Invoke-Step -Name "Run tests" -Action {
|
||||||
if ($SkipBuild) {
|
if ($SkipBuild) {
|
||||||
dotnet test GameList.Tests/GameList.Tests.csproj --verbosity normal
|
dotnet test GameList.Tests/GameList.Tests.csproj --verbosity normal --collect:"XPlat Code Coverage"
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
dotnet test GameList.Tests/GameList.Tests.csproj --no-build --verbosity normal
|
dotnet test GameList.Tests/GameList.Tests.csproj --no-build --verbosity normal --collect:"XPlat Code Coverage"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Invoke-Step -Name "Enforce coverage thresholds" -Action {
|
||||||
|
pwsh ./scripts/check-coverage.ps1 -MinLineRate 0.90 -MinBranchRate 0.70
|
||||||
|
}
|
||||||
|
|
||||||
Write-Host "CI checks passed."
|
Write-Host "CI checks passed."
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
|||||||
Reference in New Issue
Block a user