Modules/businessdev.ALbuild.Apps/Public/Test-BcCodeCoverageThreshold.ps1

function Test-BcCodeCoverageThreshold {
    <#
    .SYNOPSIS
        Gates a build on code-coverage thresholds -- overall and (optionally) per object.
 
    .DESCRIPTION
        Evaluates ALbuild coverage data (raw *.dat + workspace, or an existing coverage-summary.json) against a
        minimum overall line-coverage percentage and an optional per-object floor. Returns a verdict object
        ({ passed, lineCoverage, minLineCoverage, belowMinimum[], offenders[] }) and, with -ThrowOnFailure,
        throws so a pipeline step fails. Honest by construction: it uses the source-based denominator, so a suite
        that merely executes every covered line does not pass a real threshold.
 
    .PARAMETER MinLineCoverage
        Minimum overall line-coverage percent (0-100) required to pass.
 
    .PARAMETER MinObjectLineCoverage
        Optional per-object minimum; any workspace object below it is reported as an offender (and fails the gate).
 
    .PARAMETER ThrowOnFailure
        Throw a terminating error when the gate fails (for use as a pipeline gate).
 
    .OUTPUTS
        PSCustomObject verdict.
    #>

    [CmdletBinding(DefaultParameterSetName = 'Raw')]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory, ParameterSetName = 'Raw')] [ValidateNotNullOrEmpty()] [string] $CoveragePath,
        [Parameter(ParameterSetName = 'Raw')] [string] $WorkspaceRoot,
        [Parameter(ParameterSetName = 'Raw')] [ValidateSet('Auto', 'Source', 'CoveredOnly')] [string] $DenominatorMode = 'Auto',
        [Parameter(Mandatory, ParameterSetName = 'Summary')] [ValidateNotNullOrEmpty()] [string] $SummaryPath,
        [Parameter(Mandatory)] [ValidateRange(0, 100)] [double] $MinLineCoverage,
        [ValidateRange(0, 100)] [double] $MinObjectLineCoverage = 0,
        [switch] $ThrowOnFailure
    )

    $data = if ($PSCmdlet.ParameterSetName -eq 'Summary') {
        Resolve-BcCoverageData -SummaryPath $SummaryPath
    }
    else {
        $params = @{ CoveragePath = $CoveragePath; DenominatorMode = $DenominatorMode }
        if ($WorkspaceRoot) { $params['WorkspaceRoot'] = $WorkspaceRoot }
        Resolve-BcCoverageData @params
    }

    $overall = [double]$data.Summary.lineCoverage
    $overallPass = $overall -ge $MinLineCoverage

    $offenders = @()
    if ($MinObjectLineCoverage -gt 0) {
        $offenders = @($data.Objects |
                Where-Object { [double]$_.lineCoverage -lt $MinObjectLineCoverage } |
                ForEach-Object {
                    [PSCustomObject]@{
                        objectType   = $_.objectType
                        objectId     = $_.objectId
                        objectName   = $_.objectName
                        filePath     = $_.filePath
                        lineCoverage = $_.lineCoverage
                    }
                })
    }

    $passed = $overallPass -and ($offenders.Count -eq 0)
    $verdict = [PSCustomObject]@{
        passed                = $passed
        lineCoverage          = $overall
        minLineCoverage       = $MinLineCoverage
        minObjectLineCoverage = $MinObjectLineCoverage
        overallPassed         = $overallPass
        offenders             = @($offenders)
        coveredLines          = $data.Summary.coveredLines
        totalExecutableLines  = $data.Summary.totalExecutableLines
        denominatorMode       = $data.Summary.denominatorMode
    }

    if ($passed) {
        Write-ALbuildLog -Level Success ("Coverage gate PASSED: {0}% >= {1}% ({2}/{3} lines)." -f $overall, $MinLineCoverage, $data.Summary.coveredLines, $data.Summary.totalExecutableLines)
    }
    else {
        $msg = if (-not $overallPass) { "Coverage {0}% is below the required {1}%." -f $overall, $MinLineCoverage }
        else { "{0} object(s) are below the per-object minimum of {1}%." -f $offenders.Count, $MinObjectLineCoverage }
        Write-ALbuildLog -Level Warning "Coverage gate FAILED: $msg"
        if ($ThrowOnFailure) { throw "Coverage gate failed: $msg" }
    }

    return $verdict
}