modules/Invoke-AzureLoadTesting.ps1

#requires -Version 7.0
<#
.SYNOPSIS
    Wrapper for Azure Load Testing failed and regressed test runs.
 
.DESCRIPTION
    Enumerates Microsoft.LoadTestService/loadTests resources in a subscription,
    lists tests and recent test runs, and emits v1 findings for:
      - Failed or cancelled runs (High)
      - Regressions beyond threshold (Medium)
      - Missing recent runs (Info)
      - Healthy runs (Info, optional via -IncludeHealthyRuns)
 
    All REST calls are wrapped in Invoke-WithRetry. Optional disk output is
    scrubbed through Remove-Credentials before being written.
#>

[CmdletBinding()]
param (
    [Parameter(Mandatory)] [string] $SubscriptionId,
    [string] $ResourceGroup,
    [string] $LoadTestResourceName,
    [ValidateRange(1, 365)] [int] $DaysBack = 30,
    [ValidateRange(1, 100)] [int] $RegressionThresholdPercent = 10,
    [switch] $IncludeHealthyRuns,
    [string] $OutputPath
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

$retryPath = Join-Path $PSScriptRoot 'shared' 'Retry.ps1'
if (Test-Path $retryPath) { . $retryPath }
if (-not (Get-Command Invoke-WithRetry -ErrorAction SilentlyContinue)) {
    function Invoke-WithRetry { param([scriptblock]$ScriptBlock, [int]$MaxAttempts = 3) & $ScriptBlock }
}

$sanitizePath = Join-Path $PSScriptRoot 'shared' 'Sanitize.ps1'
if (Test-Path $sanitizePath) { . $sanitizePath }
if (-not (Get-Command Remove-Credentials -ErrorAction SilentlyContinue)) {
    function Remove-Credentials { param([string]$Text) return $Text }
}

$errorsPath = Join-Path $PSScriptRoot 'shared' 'Errors.ps1'
if (Test-Path $errorsPath) { . $errorsPath }

$envelopePath = Join-Path $PSScriptRoot 'shared' 'New-WrapperEnvelope.ps1'
if (Test-Path $envelopePath) { . $envelopePath }
if (-not (Get-Command New-WrapperEnvelope -ErrorAction SilentlyContinue)) { function New-WrapperEnvelope { param([string]$Source,[string]$Status='Failed',[string]$Message='',[object[]]$FindingErrors=@()) return [PSCustomObject]@{ Source=$Source; SchemaVersion='1.0'; Status=$Status; Message=$Message; Findings=@(); Errors=@($FindingErrors) } } }
if (-not (Get-Command New-FindingError -ErrorAction SilentlyContinue)) {
    function New-FindingError { param([string]$Source,[string]$Category,[string]$Reason,[string]$Remediation,[string]$Details) return [pscustomobject]@{ Source=$Source; Category=$Category; Reason=$Reason; Remediation=$Remediation; Details=$Details } }
}
if (-not (Get-Command Format-FindingErrorMessage -ErrorAction SilentlyContinue)) {
    function Format-FindingErrorMessage {
        param([Parameter(Mandatory)]$FindingError)
        $line = "[{0}] {1}: {2}" -f $FindingError.Source, $FindingError.Category, $FindingError.Reason
        if ($FindingError.Remediation) { $line += " Action: $($FindingError.Remediation)" }
        return $line
    }
}

$result = [ordered]@{
    SchemaVersion = '1.0'
    Source        = 'loadtesting'
    Status        = 'Success'
    Message       = ''
    ToolVersion   = ''
    Findings      = @()
    Errors        = @()
    Subscription  = $SubscriptionId
    Timestamp     = (Get-Date).ToUniversalTime().ToString('o')
}

if (-not (Get-Module -ListAvailable -Name Az.Accounts)) {
    $result.Status  = 'Skipped'
    $result.Message = 'Az.Accounts module not installed. Run: Install-Module Az.Accounts -Scope CurrentUser'
    return [PSCustomObject]$result
}

try {
    Import-Module Az.Accounts -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
} catch {
    Write-Verbose "Import-Module Az.Accounts failed, continuing with existing session context."
}
try {
    $ctx = Get-AzContext -ErrorAction Stop
    if (-not $ctx) { Write-Error 'No Az context' -ErrorAction Stop }
} catch {
    $result.Status  = 'Skipped'
    $result.Message = 'Not signed in. Run Connect-AzAccount first.'
    return [PSCustomObject]$result
}

function Invoke-LoadTestingGetPaged {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)] [string] $Uri,
        [int] $MaxPages = 50
    )

    $items = [System.Collections.Generic.List[object]]::new()
    $next = $Uri
    $pages = 0
    while ($next -and $pages -lt $MaxPages) {
        $pages++
        $resp = Invoke-WithRetry -MaxAttempts 4 -ScriptBlock {
            Invoke-AzRestMethod -Method GET -Uri $next -ErrorAction Stop
        }
        if (-not $resp -or $resp.StatusCode -ge 400) {
            $statusCode = if ($resp) { $resp.StatusCode } else { 'null' }
            $content    = if ($resp) { [string]$resp.Content } else { 'No response' }
            throw (Format-FindingErrorMessage (New-FindingError `
                -Source 'wrapper:loadtesting' `
                -Category 'TransientFailure' `
                -Reason "Load Testing REST call failed (HTTP ${statusCode})." `
                -Remediation 'Verify Load Testing Reader role on the resource and retry.' `
                -Details (Remove-Credentials -Text $content)))
        }

        $payload = $resp.Content | ConvertFrom-Json -Depth 30
        if ($payload.PSObject.Properties['value'] -and $payload.value) {
            foreach ($v in @($payload.value)) {
                $items.Add($v) | Out-Null
            }
        }
        $next = $null
        if ($payload.PSObject.Properties['nextLink'] -and $payload.nextLink) {
            $next = [string]$payload.nextLink
        }
    }

    return @($items)
}

function Get-PortalRunUrl {
    param(
        [Parameter(Mandatory)] [string] $ResourceId,
        [Parameter(Mandatory)] [string] $RunId
    )
    $encoded = [System.Uri]::EscapeDataString($ResourceId)
    return "https://portal.azure.com/#view/Microsoft_Azure_LoadTesting/LoadTestResourceMenuBlade/~/testRun/resourceId/$encoded/testRunId/$RunId"
}

function Get-PortalMetricUrl {
    param(
        [Parameter(Mandatory)] [string] $ResourceId,
        [Parameter(Mandatory)] [string] $RunId,
        [Parameter(Mandatory)] [string] $MetricName
    )
    $encoded = [System.Uri]::EscapeDataString($ResourceId)
    $metricEncoded = [System.Uri]::EscapeDataString($MetricName)
    return "https://portal.azure.com/#view/Microsoft_Azure_LoadTesting/LoadTestResourceMenuBlade/~/testRun/resourceId/$encoded/testRunId/$RunId/metricName/$metricEncoded"
}

function Get-PortalResourceUrl {
    param([Parameter(Mandatory)] [string] $ResourceId)
    return "https://portal.azure.com/#@/resource$ResourceId/overview"
}

function Get-OptionalString {
    param([object] $Value)
    if ($null -eq $Value) { return '' }
    return [string]$Value
}

function Get-RunTimestampUtc {
    param([object] $Run)
    foreach ($candidate in @(
            (Get-PropertyPathValue -Object $Run -Path 'properties.endDateTime'),
            (Get-PropertyPathValue -Object $Run -Path 'properties.startDateTime'),
            (Get-PropertyPathValue -Object $Run -Path 'properties.lastModifiedDateTime'),
            (Get-PropertyPathValue -Object $Run -Path 'properties.createdDateTime'),
            (Get-PropertyPathValue -Object $Run -Path 'endDateTime'),
            (Get-PropertyPathValue -Object $Run -Path 'startDateTime'),
            (Get-PropertyPathValue -Object $Run -Path 'createdDateTime')
        )) {
        if ($null -eq $candidate) { continue }
        try {
            return ([datetime]$candidate).ToUniversalTime()
        } catch {
            continue
        }
    }
    return $null
}

function Get-RunRawResultsUri {
    param([object] $Run)
    foreach ($candidate in @(
            (Get-PropertyPathValue -Object $Run -Path 'properties.testResultFileInfo.url'),
            (Get-PropertyPathValue -Object $Run -Path 'properties.testResult.fileUri'),
            (Get-PropertyPathValue -Object $Run -Path 'properties.resultFileUrl'),
            (Get-PropertyPathValue -Object $Run -Path 'properties.artifacts.resultsUrl'),
            (Get-PropertyPathValue -Object $Run -Path 'properties.rawResultUrl')
        )) {
        $uri = Get-OptionalString $candidate
        if (-not [string]::IsNullOrWhiteSpace($uri)) {
            return $uri
        }
    }
    return ''
}

function Get-PropertyPathValue {
    param(
        [Parameter(Mandatory)] [object] $Object,
        [Parameter(Mandatory)] [string] $Path
    )
    $current = $Object
    foreach ($segment in ($Path -split '\.')) {
        if ($null -eq $current) { return $null }
        $prop = $current.PSObject.Properties[$segment]
        if ($null -eq $prop) { return $null }
        $current = $prop.Value
    }
    return $current
}

function Get-RunMetric {
    param(
        [Parameter(Mandatory)] [object] $Run,
        [Parameter(Mandatory)] [string[]] $CandidatePaths,
        [string[]] $ArrayMetricNames
    )

    foreach ($path in $CandidatePaths) {
        $value = Get-PropertyPathValue -Object $Run -Path $path
        if ($null -eq $value) { continue }
        try {
            return [double]$value
        } catch {
            continue
        }
    }

    $metricArrays = @(
        (Get-PropertyPathValue -Object $Run -Path 'properties.metrics'),
        (Get-PropertyPathValue -Object $Run -Path 'properties.testRunStatistics.metrics'),
        (Get-PropertyPathValue -Object $Run -Path 'metrics')
    )
    foreach ($arr in $metricArrays) {
        if (-not $arr) { continue }
        foreach ($entry in @($arr)) {
            $name = Get-OptionalString $entry.name
            if (-not $name) { $name = Get-OptionalString $entry.metricName }
            if (-not $name) { continue }
            if ($ArrayMetricNames -and ($ArrayMetricNames -notcontains $name.ToLowerInvariant())) { continue }
            foreach ($field in @('value', 'metricValue', 'average', 'max', 'p95')) {
                if ($entry.PSObject.Properties[$field] -and $null -ne $entry.$field) {
                    try { return [double]$entry.$field } catch { }
                }
            }
        }
    }

    return $null
}

function Test-PassFailCriteriaFailed {
    param([object] $Run)

    $criteria = Get-PropertyPathValue -Object $Run -Path 'properties.passFailCriteria'
    if (-not $criteria) { return $false }

    foreach ($collectionName in @('passFailMetrics', 'passFailAggregation', 'metrics')) {
        if (-not $criteria.PSObject.Properties[$collectionName]) { continue }
        foreach ($metric in @($criteria.$collectionName)) {
            foreach ($field in @('status', 'result', 'state')) {
                if ($metric.PSObject.Properties[$field] -and $metric.$field) {
                    if ([string]$metric.$field -match '^(?i)failed$') { return $true }
                }
            }
        }
    }

    return $false
}

function Get-PassFailCriteriaTags {
    param([object] $Run)

    $tags = [System.Collections.Generic.List[string]]::new()
    $criteriaFailed = Test-PassFailCriteriaFailed -Run $Run
    if ($criteriaFailed) {
        $tags.Add('LoadTesting-PassFailCriteriaFailed') | Out-Null
    } else {
        $tags.Add('LoadTesting-PassFailCriteriaPassed') | Out-Null
    }

    $criteria = Get-PropertyPathValue -Object $Run -Path 'properties.passFailCriteria'
    if ($criteria) {
        foreach ($collectionName in @('passFailMetrics', 'passFailAggregation', 'metrics')) {
            if (-not $criteria.PSObject.Properties[$collectionName]) { continue }
            foreach ($metric in @($criteria.$collectionName)) {
                $status = ''
                foreach ($field in @('status', 'result', 'state')) {
                    if ($metric.PSObject.Properties[$field] -and $metric.$field) {
                        $status = [string]$metric.$field
                        break
                    }
                }
                if ($status -notmatch '^(?i)failed$') { continue }
                $metricName = ''
                if ($metric.PSObject.Properties['metricName']) { $metricName = Get-OptionalString $metric.metricName }
                if (-not $metricName -and $metric.PSObject.Properties['name']) { $metricName = Get-OptionalString $metric.name }
                if (-not [string]::IsNullOrWhiteSpace($metricName)) {
                    $metricTag = ($metricName -replace '[^A-Za-z0-9]+', '')
                    if (-not [string]::IsNullOrWhiteSpace($metricTag)) {
                        $tags.Add("LoadTesting-$metricTag") | Out-Null
                    }
                }
            }
        }
    }
    return @($tags | Select-Object -Unique)
}

function Get-RegressionImpact {
    param(
        [double] $RegressionPercent,
        [double] $ThresholdPercent
    )
    if ($RegressionPercent -ge (2 * $ThresholdPercent)) { return 'High' }
    if ($RegressionPercent -gt $ThresholdPercent) { return 'Medium' }
    return 'Low'
}

function Get-LoadTestingToolVersion {
    try {
        $module = Get-Module -ListAvailable -Name Az.LoadTesting | Sort-Object Version -Descending | Select-Object -First 1
        if ($module -and $module.Version) {
            return "Az.LoadTesting/$($module.Version.ToString())"
        }
    } catch {
    }

    try {
        $cliRaw = az version --output json 2>$null
        if ($LASTEXITCODE -eq 0 -and $cliRaw) {
            $cliVersion = ([string]($cliRaw | ConvertFrom-Json).'azure-cli').Trim()
            if (-not [string]::IsNullOrWhiteSpace($cliVersion)) {
                return "azure-cli/$cliVersion"
            }
        }
    } catch {
    }

    return ''
}

$findings = [System.Collections.Generic.List[object]]::new()
$apiVersion = '2022-12-01'
$windowStartUtc = (Get-Date).ToUniversalTime().AddDays(-$DaysBack)

function Add-LoadTestingFinding {
    param(
        [Parameter(Mandatory)] [string] $Id,
        [Parameter(Mandatory)] [string] $Severity,
        [Parameter(Mandatory)] [bool] $Compliant,
        [Parameter(Mandatory)] [string] $Title,
        [Parameter(Mandatory)] [string] $Detail,
        [Parameter(Mandatory)] [string] $ResourceId,
        [Parameter(Mandatory)] [string] $LearnMoreUrl,
        [string] $Remediation = '',
        [hashtable] $Extras
    )

    $row = [ordered]@{
        Id           = $Id
        Source       = 'loadtesting'
        Category     = 'Performance'
        Severity     = $Severity
        Compliant    = $Compliant
        Title        = $Title
        Detail       = $Detail
        Remediation  = $Remediation
        ResourceId   = $ResourceId
        LearnMoreUrl = $LearnMoreUrl
    }
    if ($Extras) {
        foreach ($k in $Extras.Keys) {
            $row[$k] = $Extras[$k]
        }
    }
    $findings.Add([PSCustomObject]$row) | Out-Null
}

try {
    $toolVersion = Get-LoadTestingToolVersion
    $result.ToolVersion = $toolVersion
    $resourceUris = @()
    if ($ResourceGroup -and $LoadTestResourceName) {
        $resourceUris += "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$([System.Uri]::EscapeDataString($ResourceGroup))/providers/Microsoft.LoadTestService/loadTests/$([System.Uri]::EscapeDataString($LoadTestResourceName))?api-version=$apiVersion"
    } elseif ($ResourceGroup) {
        $resourceUris += "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$([System.Uri]::EscapeDataString($ResourceGroup))/providers/Microsoft.LoadTestService/loadTests?api-version=$apiVersion"
    } else {
        $resourceUris += "https://management.azure.com/subscriptions/$SubscriptionId/providers/Microsoft.LoadTestService/loadTests?api-version=$apiVersion"
    }

    $loadTestResources = [System.Collections.Generic.List[object]]::new()
    foreach ($resourceUri in $resourceUris) {
        $items = Invoke-LoadTestingGetPaged -Uri $resourceUri
        foreach ($item in @($items)) {
            $loadTestResources.Add($item) | Out-Null
        }
    }

    if ($LoadTestResourceName -and -not $ResourceGroup) {
        $filtered = @($loadTestResources | Where-Object { ([string]$_.name) -ieq $LoadTestResourceName })
        $loadTestResources = [System.Collections.Generic.List[object]]::new()
        foreach ($item in $filtered) { $loadTestResources.Add($item) | Out-Null }
    }

    if ($loadTestResources.Count -eq 0) {
        $result.Status = 'Skipped'
        $result.Message = 'No Azure Load Testing resources found in the requested scope.'
        return [PSCustomObject]$result
    }

    foreach ($resource in $loadTestResources) {
        $resourceId = [string]$resource.id
        $resourceName = [string]$resource.name
        if (-not $resourceId) { continue }

        $testsUri = "https://management.azure.com$resourceId/tests?api-version=$apiVersion"
        $tests = @()
        try {
            $tests = @(Invoke-LoadTestingGetPaged -Uri $testsUri)
        } catch {
            Write-Warning ("Load testing tests query failed for {0}: {1}" -f $resourceName, (Remove-Credentials -Text ([string]$_.Exception.Message)))
            continue
        }

        foreach ($test in $tests) {
            $testName = [string]$test.name
            if (-not $testName) { continue }

            $testNameEncoded = [System.Uri]::EscapeDataString($testName)
            $runsUri = "https://management.azure.com$resourceId/tests/$testNameEncoded/test-runs?api-version=$apiVersion"
            $runs = @()
            try {
                $runs = @(Invoke-LoadTestingGetPaged -Uri $runsUri)
            } catch {
                Write-Warning ("Load testing runs query failed for {0}/{1}: {2}" -f $resourceName, $testName, (Remove-Credentials -Text ([string]$_.Exception.Message)))
                continue
            }

            $runsInWindow = @($runs | Where-Object {
                    $ts = Get-RunTimestampUtc -Run $_
                    $null -ne $ts -and $ts -ge $windowStartUtc
                } | Sort-Object -Property @{ Expression = { Get-RunTimestampUtc -Run $_ } } -Descending)

            if ($runsInWindow.Count -eq 0) {
                Add-LoadTestingFinding -Id "loadtesting/$resourceName/$testName/no-runs" `
                    -Severity 'Info' -Compliant $false `
                    -Title "Load test '$testName' has no runs in the last $DaysBack day(s)" `
                    -Detail "No test runs were found for load test '$testName' in resource '$resourceName' during the configured lookback window." `
                    -ResourceId $resourceId `
                    -LearnMoreUrl (Get-PortalResourceUrl -ResourceId $resourceId) `
                    -Remediation 'Run a baseline test and keep recurring runs to detect reliability and performance regressions.' `
                    -Extras @{
                        LoadTestResourceName = $resourceName
                        TestName             = $testName
                        DaysBack             = $DaysBack
                        Pillar               = 'Performance Efficiency'
                        Impact               = 'Low'
                        Effort               = 'Low'
                        DeepLinkUrl          = (Get-PortalResourceUrl -ResourceId $resourceId)
                        EvidenceUris         = @((Get-PortalResourceUrl -ResourceId $resourceId))
                        BaselineTags         = @('LoadTesting-StaleCadence')
                        EntityRefs           = @($resourceId, 'testrun:none')
                        ToolVersion          = $toolVersion
                    }
                continue
            }

            $latest = $runsInWindow[0]
            $previous = if ($runsInWindow.Count -gt 1) { $runsInWindow[1] } else { $null }

            $runId = Get-OptionalString $latest.name
            if (-not $runId) { $runId = Get-OptionalString $latest.id }
            if (-not $runId) { $runId = ([guid]::NewGuid().ToString()) }

            $runStatusRaw = Get-OptionalString (Get-PropertyPathValue -Object $latest -Path 'properties.status')
            if (-not $runStatusRaw) { $runStatusRaw = Get-OptionalString (Get-PropertyPathValue -Object $latest -Path 'status') }
            $runStatus = $runStatusRaw.ToUpperInvariant()

            $failureCause = Get-OptionalString (Get-PropertyPathValue -Object $latest -Path 'properties.errorDetails.message')
            if (-not $failureCause) { $failureCause = Get-OptionalString (Get-PropertyPathValue -Object $latest -Path 'properties.failureReason') }
            if (-not $failureCause) { $failureCause = Get-OptionalString (Get-PropertyPathValue -Object $latest -Path 'properties.message') }
            if (-not $failureCause) { $failureCause = 'No failure cause provided by the API.' }

            $criteriaFailed = Test-PassFailCriteriaFailed -Run $latest
            $passFailTags = @(Get-PassFailCriteriaTags -Run $latest)
            $isFailed = $runStatus -in @('FAILED', 'CANCELLED') -or $criteriaFailed
            $hadRegression = $false
            $runUrl = Get-PortalRunUrl -ResourceId $resourceId -RunId $runId
            $rawResultsUri = Get-RunRawResultsUri -Run $latest

            if ($isFailed) {
                $evidenceUris = [System.Collections.Generic.List[string]]::new()
                $evidenceUris.Add($runUrl) | Out-Null
                if (-not [string]::IsNullOrWhiteSpace($rawResultsUri)) {
                    $evidenceUris.Add($rawResultsUri) | Out-Null
                }
                Add-LoadTestingFinding -Id "loadtesting/$resourceName/$testName/$runId/failed" `
                    -Severity 'High' -Compliant $false `
                    -Title "Load test '$testName' run $runId failed: $failureCause" `
                    -Detail "Latest run status is '$runStatus'. Pass/fail criteria failed: $criteriaFailed. Cause: $failureCause" `
                    -ResourceId $resourceId `
                    -LearnMoreUrl $runUrl `
                    -Remediation 'Review test logs, recent deployment changes, and backend telemetry before promoting the release.' `
                    -Extras @{
                        LoadTestResourceName   = $resourceName
                        TestName               = $testName
                        TestRunId              = $runId
                        RunStatus              = $runStatus
                        FailureCause           = $failureCause
                        PassFailCriteriaFailed = $criteriaFailed
                        Pillar                 = 'Performance Efficiency'
                        Impact                 = 'Medium'
                        Effort                 = 'Medium'
                        DeepLinkUrl            = $runUrl
                        EvidenceUris           = @($evidenceUris.ToArray())
                        BaselineTags           = @($passFailTags)
                        EntityRefs             = @($resourceId, $runId)
                        ToolVersion            = $toolVersion
                    }
            }

            if ($previous) {
                $metricDefinitions = @(
                    @{
                        Name           = 'ResponseTimeP95'
                        Display        = 'response time p95'
                        HigherIsWorse  = $true
                        Paths          = @(
                            'properties.testRunStatistics.responseTimeP95',
                            'properties.statistics.responseTimeP95',
                            'properties.responseTimeMetrics.p95',
                            'properties.responseTimeP95'
                        )
                        MetricNames    = @('responsetimep95', 'p95', 'latencyp95')
                    },
                    @{
                        Name           = 'ErrorRate'
                        Display        = 'error rate'
                        HigherIsWorse  = $true
                        Paths          = @(
                            'properties.testRunStatistics.errorRate',
                            'properties.statistics.errorRate',
                            'properties.errorRate'
                        )
                        MetricNames    = @('errorrate', 'errorpercentage', 'errorspercentage')
                    },
                    @{
                        Name           = 'RequestsPerSecond'
                        Display        = 'requests per second'
                        HigherIsWorse  = $false
                        Paths          = @(
                            'properties.testRunStatistics.requestsPerSecond',
                            'properties.statistics.requestsPerSecond',
                            'properties.rps'
                        )
                        MetricNames    = @('requestspersecond', 'rps', 'throughput')
                    }
                )

                foreach ($metric in $metricDefinitions) {
                    $latestValue = Get-RunMetric -Run $latest -CandidatePaths $metric.Paths -ArrayMetricNames $metric.MetricNames
                    $previousValue = Get-RunMetric -Run $previous -CandidatePaths $metric.Paths -ArrayMetricNames $metric.MetricNames
                    if ($null -eq $latestValue -or $null -eq $previousValue) { continue }
                    if ($previousValue -eq 0) { continue }

                    $deltaPercent = if ($metric.HigherIsWorse) {
                        (($latestValue - $previousValue) / [math]::Abs($previousValue)) * 100
                    } else {
                        (($previousValue - $latestValue) / [math]::Abs($previousValue)) * 100
                    }

                    if ($deltaPercent -ge $RegressionThresholdPercent) {
                        $hadRegression = $true
                        $roundedDelta = [math]::Round($deltaPercent, 2)
                        $metricPortalUrl = Get-PortalMetricUrl -ResourceId $resourceId -RunId $runId -MetricName $metric.Name
                        $impact = Get-RegressionImpact -RegressionPercent $roundedDelta -ThresholdPercent $RegressionThresholdPercent
                        $evidenceUris = [System.Collections.Generic.List[string]]::new()
                        $evidenceUris.Add($runUrl) | Out-Null
                        $evidenceUris.Add($metricPortalUrl) | Out-Null
                        if (-not [string]::IsNullOrWhiteSpace($rawResultsUri)) {
                            $evidenceUris.Add($rawResultsUri) | Out-Null
                        }
                        $baselineTags = [System.Collections.Generic.List[string]]::new()
                        foreach ($tag in $passFailTags) { $baselineTags.Add($tag) | Out-Null }
                        $baselineTags.Add("LoadTesting-$($metric.Name)") | Out-Null
                        Add-LoadTestingFinding -Id "loadtesting/$resourceName/$testName/$runId/regression/$($metric.Name)" `
                            -Severity 'Medium' -Compliant $false `
                            -Title "Load test '$testName' regressed by $roundedDelta% in $($metric.Display)" `
                            -Detail "Latest value: $([math]::Round($latestValue, 4)); baseline value: $([math]::Round($previousValue, 4)); threshold: $RegressionThresholdPercent%." `
                            -ResourceId $resourceId `
                            -LearnMoreUrl $runUrl `
                            -Remediation 'Investigate recent code and infrastructure changes, then re-run load tests before release.' `
                            -Extras @{
                                LoadTestResourceName  = $resourceName
                                TestName              = $testName
                                TestRunId             = $runId
                                MetricName            = $metric.Name
                                MetricDisplayName     = $metric.Display
                                BaselineValue         = [math]::Round($previousValue, 6)
                                CurrentValue          = [math]::Round($latestValue, 6)
                                RegressionPercent     = $roundedDelta
                                ThresholdPercent      = $RegressionThresholdPercent
                                Pillar                = 'Performance Efficiency'
                                Impact                = $impact
                                Effort                = 'Medium'
                                DeepLinkUrl           = $runUrl
                                EvidenceUris          = @($evidenceUris.ToArray())
                                BaselineTags          = @($baselineTags | Select-Object -Unique)
                                ScoreDelta            = [double]$roundedDelta
                                EntityRefs            = @($resourceId, $runId)
                                ToolVersion           = $toolVersion
                            }
                    }
                }
            }

            if ($IncludeHealthyRuns -and (-not $isFailed) -and (-not $hadRegression)) {
                Add-LoadTestingFinding -Id "loadtesting/$resourceName/$testName/$runId/healthy" `
                    -Severity 'Info' -Compliant $true `
                    -Title "Load test '$testName' run $runId is healthy" `
                    -Detail "Latest run status is '$runStatus' and no tracked metric regressed beyond $RegressionThresholdPercent% against the prior run." `
                    -ResourceId $resourceId `
                    -LearnMoreUrl $runUrl `
                    -Remediation '' `
                    -Extras @{
                        LoadTestResourceName = $resourceName
                        TestName             = $testName
                        TestRunId            = $runId
                        RunStatus            = $runStatus
                        ThresholdPercent     = $RegressionThresholdPercent
                        Pillar               = 'Performance Efficiency'
                        Impact               = 'Low'
                        Effort               = 'Low'
                        DeepLinkUrl          = $runUrl
                        EvidenceUris         = @($runUrl)
                        BaselineTags         = @($passFailTags)
                        EntityRefs           = @($resourceId, $runId)
                        ToolVersion          = $toolVersion
                    }
            }
        }
    }
} catch {
    $result.Status  = 'Failed'
    $result.Message = "Azure Load Testing query failed: $(Remove-Credentials -Text ([string]$_.Exception.Message))"
    return [PSCustomObject]$result
}

$result.Findings = @($findings)
$result.Message = "Scanned Azure Load Testing resources for the last $DaysBack day(s); emitted $($findings.Count) finding(s). Regression threshold: $RegressionThresholdPercent%."

if ($OutputPath) {
    try {
        if (-not (Test-Path $OutputPath)) { New-Item -ItemType Directory -Path $OutputPath -Force | Out-Null }
        $raw = Join-Path $OutputPath "loadtesting-$SubscriptionId-$(Get-Date -Format yyyyMMddHHmmss).json"
        Set-Content -Path $raw -Value (Remove-Credentials ($result | ConvertTo-Json -Depth 30)) -Encoding utf8
    } catch {
        Write-Warning "Failed to write raw load testing JSON: $(Remove-Credentials -Text ([string]$_.Exception.Message))"
    }
}

return [PSCustomObject]$result