Private/Add-Win32ToolkitTestResult.ps1

function Add-Win32ToolkitTestResult {
    <#
    .SYNOPSIS
        Appends one test-outcome entry to a project's Documentation\TestResults.json.
    .DESCRIPTION
        TestResults.json is the shared persistence file that records every install/update test run
        against a project (Sandbox or Hyper-V). The customer documentation reads it back to list
        "all the tests we did". Entries are appended newest-last.
 
        Each entry has the shape:
            { Scenario, Backend, Mode, TimestampUtc, Verdict, Assertions[], Notes }
        where Assertions is an array of @{ Name; Result } (Result in PASS|FAIL|SKIP).
 
        The file is written as a JSON ARRAY (even for a single entry) in BOM-less UTF-8, matching
        Set-Win32ToolkitAppConfig. A corrupt/unparseable existing file is never fatal: it is
        replaced with a fresh single-entry array and a warning is emitted.
    .PARAMETER ProjectPath
        Full path to the PSADT project folder (the folder that contains Invoke-AppDeployToolkit.ps1).
        The Documentation subfolder is created if it does not exist.
    .PARAMETER Scenario
        The test scenario: InstallUninstall or Update.
    .PARAMETER Backend
        Where the test ran: Sandbox or HyperV.
    .PARAMETER Verdict
        The overall outcome: Passed, Failed or Inconclusive.
    .PARAMETER Mode
        Interactive or Unattended. Defaults to Unattended.
    .PARAMETER Assertions
        Array of individual assertion results, each @{ Name = <string>; Result = 'PASS'|'FAIL'|'SKIP' }.
    .PARAMETER Notes
        Free-text notes for this run.
    .EXAMPLE
        Add-Win32ToolkitTestResult -ProjectPath $p -Scenario InstallUninstall -Backend Sandbox `
            -Verdict Passed -Assertions @(@{ Name = 'Installed'; Result = 'PASS' }) -Notes 'clean run'
    #>

    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$ProjectPath,

        [Parameter(Mandatory)]
        [ValidateSet('InstallUninstall', 'Update')]
        [string]$Scenario,

        [Parameter(Mandatory)]
        [ValidateSet('Sandbox', 'HyperV')]
        [string]$Backend,

        [Parameter(Mandatory)]
        [ValidateSet('Passed', 'Failed', 'Inconclusive')]
        [string]$Verdict,

        [ValidateSet('Interactive', 'Unattended')]
        [string]$Mode = 'Unattended',

        [ValidateNotNull()]
        [object[]]$Assertions = @(),

        [string]$Notes = ''
    )

    $docDir = Join-Path $ProjectPath 'Documentation'
    if (-not (Test-Path -LiteralPath $docDir)) {
        New-Item -ItemType Directory -Path $docDir -Force | Out-Null
    }
    $resultsPath = Join-Path $docDir 'TestResults.json'

    # Read existing entries; never throw on a corrupt/partial file — start fresh and warn.
    $entries = @()
    if (Test-Path -LiteralPath $resultsPath) {
        try {
            $raw = Get-Content -LiteralPath $resultsPath -Raw -Encoding UTF8
            if (-not [string]::IsNullOrWhiteSpace($raw)) {
                $parsed = $raw | ConvertFrom-Json
                $entries = @($parsed)
            }
        }
        catch {
            Write-Warning "TestResults.json is unparseable; starting a fresh results file. ($($_.Exception.Message))"
            $entries = @()
        }
    }

    # Normalise assertions to plain { Name; Result } objects so serialisation is predictable.
    $normAssertions = @()
    foreach ($a in $Assertions) {
        if ($null -eq $a) { continue }
        $normAssertions += [pscustomobject]@{
            Name   = [string]$a.Name
            Result = [string]$a.Result
        }
    }

    $entry = [pscustomobject]@{
        Scenario     = $Scenario
        Backend      = $Backend
        Mode         = $Mode
        TimestampUtc = (Get-Date).ToUniversalTime().ToString('o')
        Verdict      = $Verdict
        Assertions   = $normAssertions
        Notes        = $Notes
    }

    $entries += $entry

    # Force a JSON ARRAY even for a single entry — ConvertTo-Json collapses a one-element array.
    $json = ConvertTo-Json -InputObject @($entries) -Depth 6
    # Write BOM-less UTF-8 (matches Set-Win32ToolkitAppConfig).
    [System.IO.File]::WriteAllText($resultsPath, $json, (New-Object System.Text.UTF8Encoding($false)))

    return $entry
}