Modules/businessdev.ALbuild.Apps/Public/Publish-BcTestinyResult.ps1

function Publish-BcTestinyResult {
    <#
    .SYNOPSIS
        Publishes a JUnit test result file to Testiny via the Testiny importer CLI.
 
    .DESCRIPTION
        Uploads AL test results (a JUnit XML file, e.g. the one produced by Invoke-BcContainerTest) to
        Testiny using the official Testiny importer command-line tool. The importer is taken from
        -ImporterPath if supplied, otherwise downloaded and extracted from -ImporterUrl. Native
        replacement for the V1 PublishTestResultsTestiny task. For standard pipeline reporting use the
        built-in Azure DevOps PublishTestResults task instead; this is for teams that mirror results
        into Testiny.
 
    .PARAMETER ResultsFile
        Path to the JUnit results file to upload.
 
    .PARAMETER ApiKey
        Testiny API key.
 
    .PARAMETER ProjectId
        Testiny project id.
 
    .PARAMETER RunId
        Optional Testiny test-run id to attach the results to.
 
    .PARAMETER Format
        Result format passed to the importer. Default 'junit'.
 
    .PARAMETER ImporterUrl
        URL of the importer download (a .zip). Used when -ImporterPath is not supplied.
 
    .PARAMETER ImporterPath
        Path to an already-available Testiny importer executable (skips the download).
 
    .EXAMPLE
        Publish-BcTestinyResult -ResultsFile .\TestResults.xml -ApiKey $key -ProjectId '42'
 
    .OUTPUTS
        PSCustomObject: ResultsFile, ProjectId, RunId.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $ResultsFile,
        [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $ApiKey,
        [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $ProjectId,
        [string] $RunId,
        [ValidateSet('junit', 'xunit')] [string] $Format = 'junit',
        [string] $ImporterUrl = 'https://app.testiny.io/download/latest/importer/testiny-importer-win.exe.zip',
        [string] $ImporterPath
    )

    if (-not (Test-Path -LiteralPath $ResultsFile)) { throw "Test results file not found: '$ResultsFile'." }

    if (-not $PSCmdlet.ShouldProcess($ResultsFile, "Publish to Testiny project $ProjectId")) {
        return [PSCustomObject]@{ ResultsFile = $ResultsFile; ProjectId = $ProjectId; RunId = $RunId }
    }

    $cli = $null
    if ($ImporterPath) {
        if (-not (Test-Path -LiteralPath $ImporterPath)) { throw "Testiny importer not found at '$ImporterPath'." }
        $cli = (Get-Item -LiteralPath $ImporterPath).FullName
    }
    else {
        $tempRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("testiny-importer-" + [guid]::NewGuid())
        $zip = "$tempRoot.zip"
        Write-ALbuildLog -Level Information "Downloading Testiny importer from '$ImporterUrl'..."
        Invoke-WebRequest -Uri $ImporterUrl -OutFile $zip -UseBasicParsing -TimeoutSec 120 -ErrorAction Stop
        Expand-Archive -LiteralPath $zip -DestinationPath $tempRoot -Force
        $found = Get-ChildItem -LiteralPath $tempRoot -Filter 'testiny-importer-win.exe' -Recurse -File | Select-Object -First 1
        if (-not $found) { throw 'Could not locate the Testiny importer executable after extraction.' }
        $cli = $found.FullName
    }

    $arguments = @('--api-key', $ApiKey, '--project', $ProjectId, '--format', $Format, $ResultsFile)
    if ($RunId) { $arguments += @('--run-id', $RunId) }

    Invoke-ALbuildProcess -FilePath $cli -Arguments $arguments | Out-Null
    Write-ALbuildLog -Level Success "Published test results to Testiny project $ProjectId."

    return [PSCustomObject]@{ ResultsFile = $ResultsFile; ProjectId = $ProjectId; RunId = $RunId }
}