tests/P5C4_F2.Tests.ps1

BeforeAll {
    $script:RepoRoot = Split-Path -Parent $PSScriptRoot
    $script:ModulePath = Join-Path $script:RepoRoot 'Skyline.DataMiner.QAOps.PipelineLibrary.psd1'
    Import-Module $script:ModulePath -Force
}

Describe 'P5C3F2 redacted native dotnet execution' {
    BeforeEach {
        $script:OldPath = $env:PATH
        $script:OldContext = $env:QAOPS_PUBLICATION_CONTEXT
        $env:QAOPS_PUBLICATION_CONTEXT = 'p5c3f2-secret'
        $stubDir = Join-Path $TestDrive 'stubbin'
        New-Item -Path $stubDir -ItemType Directory -Force | Out-Null
        if ($env:OS -eq 'Windows_NT') {
            @'
@echo off
echo X-QAOps-Publication-Context: %QAOPS_PUBLICATION_CONTEXT% 1>&2
if "%1"=="fail" exit /b 1
exit /b 0
'@
 | Set-Content -LiteralPath (Join-Path $stubDir 'dotnet.cmd') -Encoding ASCII
        }
        else {
            $stubPath = Join-Path $stubDir 'dotnet'
            @'
#!/usr/bin/env sh
echo "X-QAOps-Publication-Context: $QAOPS_PUBLICATION_CONTEXT" >&2
if [ "$1" = "fail" ]; then exit 1; fi
exit 0
'@
 | Set-Content -LiteralPath $stubPath -Encoding ASCII
            & chmod '+x' $stubPath
        }
        $env:PATH = $stubDir + [System.IO.Path]::PathSeparator + $env:PATH
    }

    AfterEach {
        $env:PATH = $script:OldPath
        $env:QAOPS_PUBLICATION_CONTEXT = $script:OldContext
    }

    It 'P5C3F2_RedactedDotNet_stderr_exit0_does_not_throw_under_EAP_Stop' {
        InModuleScope Skyline.DataMiner.QAOps.PipelineLibrary {
            $old = $ErrorActionPreference
            $ErrorActionPreference = 'Stop'
            try {
                $output = (& { $script:ExitCode = Invoke-QAOpsRedactedDotNet -Arguments @('ok') } *>&1 | Out-String)
            }
            finally { $ErrorActionPreference = $old }
            $script:ExitCode | Should -Be 0
            $output | Should -Not -Match 'p5c3f2-secret'
            $output | Should -Match 'REDACTED-QAOPS-PUBLICATION-CONTEXT'
        }
    }

    It 'P5C3F2_RedactedDotNet_stderr_exit1_returns_nonzero_without_throw_under_EAP_Stop' {
        InModuleScope Skyline.DataMiner.QAOps.PipelineLibrary {
            $old = $ErrorActionPreference
            $ErrorActionPreference = 'Stop'
            try {
                { $script:ExitCode = Invoke-QAOpsRedactedDotNet -Arguments @('fail') } | Should -Not -Throw
            }
            finally { $ErrorActionPreference = $old }
            $script:ExitCode | Should -Be 1
        }
    }
}

Describe 'P5C3F2 harvest assembly path resolution' {
    It 'P5C3F2_Harvest_resolves_relative_assemblies_against_content_and_leaves_absolute_paths' {
        $content = Join-Path $TestDrive 'content'
        New-Item -Path (Join-Path $content 'TestHarvesting') -ItemType Directory -Force | Out-Null
        New-Item -Path (Join-Path $content 'TestPackagePipeline') -ItemType Directory -Force | Out-Null
        $absolute = Join-Path $TestDrive 'External.Tests.dll'
        Set-Content -LiteralPath $absolute -Value 'x'
        InModuleScope Skyline.DataMiner.QAOps.PipelineLibrary -Parameters @{ Content = $content; Absolute = $absolute } {
            Mock Install-QAFrameworkTool { [pscustomobject]@{} }
            Mock Invoke-QAFrameworkDotNet {
                $script:HarvestArguments = $Arguments
                [pscustomobject]@{ ExitCode = 0; TimedOut = $false; StdOut = '{"status":"completed"}'; StdErr = '' }
            }
            Invoke-DotNetTestHarvesting -TestPackageContentPath $Content -TestAssemblyPath @('TestHarvesting\tests.generated\Rel.Tests.dll', $Absolute) | Out-Null
            $assemblyIndexes = for ($i = 0; $i -lt $script:HarvestArguments.Count; $i++) { if ($script:HarvestArguments[$i] -eq '--assembly') { $i + 1 } }
            $script:HarvestArguments[$assemblyIndexes[0]] | Should -Be ([System.IO.Path]::GetFullPath((Join-Path $Content 'TestHarvesting\tests.generated\Rel.Tests.dll')))
            $script:HarvestArguments[$assemblyIndexes[1]] | Should -Be ([System.IO.Path]::GetFullPath($Absolute))
        }
    }
}

Describe 'P5C4 canonical identity handling' {
    It 'P5C4_Runtime_lookup_vectors_match_exact_5c4_outputs' {
        $fixture = Get-Content -LiteralPath (Join-Path $script:RepoRoot 'tests\fixtures\conformance\runtime-lookup-vectors.json') -Raw -Encoding UTF8 | ConvertFrom-Json
        foreach ($vector in @($fixture.vectors | Where-Object { $_.change -eq '5c-4' })) {
            InModuleScope Skyline.DataMiner.QAOps.PipelineLibrary -Parameters @{ Vector = $vector } {
                $identity = New-QAOpsTestInvocationIdentity -Assembly $Vector.runtimeResult.assembly -FullyQualifiedName $Vector.runtimeResult.fullyQualifiedName -DataCaseId $Vector.runtimeResult.dataCaseId -Target $Vector.runtimeResult.target
                if ($null -eq $Vector.expected.testInvocationId) { $identity.TestInvocationId | Should -BeNullOrEmpty } else { $identity.TestInvocationId | Should -Be $Vector.expected.testInvocationId }
                @($identity.Diagnostics | ForEach-Object { $_.code }) | Should -Be @($Vector.expected.diagnostics | ForEach-Object { $_.code })
                if ($Vector.expected.scalarCount) { (Get-QAOpsScalarCount -Value $identity.TestInvocationId) | Should -Be $Vector.expected.scalarCount }
            }
        }
    }

    It 'P5C4_Lone_surrogate_identity_does_not_throw_and_returns_identity_invalid' {
        InModuleScope Skyline.DataMiner.QAOps.PipelineLibrary {
            $bad = [string][char]0xD800
            { $script:Identity = New-QAOpsTestInvocationIdentity -Assembly 'Tests.dll' -FullyQualifiedName ('Acme.Tests.' + $bad) -DataCaseId $bad -Target 'default' } | Should -Not -Throw
            $script:Identity.TestInvocationId | Should -BeNullOrEmpty
            @($script:Identity.Diagnostics | ForEach-Object { $_.code }) | Should -Be @('identity-invalid')
            (New-QAOpsTestInvocationId -Assembly 'Tests.dll' -FullyQualifiedName ('Acme.Tests.' + $bad) -DataCaseId $bad -Target 'default') | Should -BeNullOrEmpty
        }
    }

    It 'P5C4_Runtime_publishes_lone_surrogate_result_without_id_or_manifest_entry' {
        $content = Join-Path $TestDrive 'content-invalid'
        New-Item -Path (Join-Path (Join-Path $content 'TestHarvesting') 'dependencies.generated') -ItemType Directory -Force | Out-Null
        $dll = Join-Path $content 'Tests.dll'
        Set-Content -LiteralPath $dll -Value 'x'
        InModuleScope Skyline.DataMiner.QAOps.PipelineLibrary -Parameters @{ Content = $content; Dll = $dll } {
            $bad = [string][char]0xD800
            function dotnet { param([Parameter(ValueFromRemainingArguments=$true)][object[]]$Arguments) $logger = @($Arguments | Where-Object { [string]$_ -like 'trx;LogFileName=*' } | Select-Object -First 1); '<TestRun xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010" />' | Set-Content -LiteralPath ([string]$logger).Substring('trx;LogFileName='.Length) -Encoding UTF8; $global:LASTEXITCODE = 0 }
            Mock Get-QAOpsTrxResult { @([pscustomobject]@{ Assembly='Tests.dll'; FullyQualifiedName=('Acme.Tests.' + $bad); DisplayName='bad identity'; DataCaseId=$bad; Outcome='Passed'; Duration=[TimeSpan]::Zero; Message=''; Diagnostics=@(); Comparable=$true }) }
            function Push-TestRunManifest { param($TestInvocations) $script:ManifestInvocations = @($TestInvocations); [pscustomobject]@{ Supported=$true; Accepted=$true } }
            function Push-TestRunFinalization { param($PublishedResultCount,$PublisherErrors) $script:FinalCount=$PublishedResultCount; $script:FinalErrors=@($PublisherErrors); [pscustomobject]@{ Supported=$true; Accepted=$true } }
            function Push-TestCaseResult { param($Name,$TestInvocationId,$Maintainers) $script:Published = [pscustomobject]@{ Name=$Name; TestInvocationId=$TestInvocationId; Maintainers=$Maintainers }; [pscustomobject]@{ Supported=$true; Accepted=$true } }
            Invoke-DotNetTestAndPublishResults -PathToTestPackageContent $Content -TestDllPath $Dll -ResultsFileName 'invalid.trx'
            $script:Published.Name | Should -Be 'bad identity'
            $script:Published.TestInvocationId | Should -BeNullOrEmpty
            $script:Published.Maintainers | Should -BeNullOrEmpty
            @($script:ManifestInvocations).Count | Should -Be 0
            @($script:FinalErrors | Where-Object code -eq 'identity-invalid').Count | Should -Be 1
            $script:FinalCount | Should -Be 1
        }
    }
}