tests/R23_Fixes.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 'R23 launcher and optional-harvest hardening' { It 'R23_H1_unmocked_launcher_smoke_returns_exact_dotnet_version' { InModuleScope Skyline.DataMiner.QAOps.PipelineLibrary { $expected = (& dotnet --version | Out-String).Trim() $actual = Invoke-QAFrameworkDotNet -Arguments @('--version') -TimeoutSeconds 20 $actual.ExitCode | Should -Be 0 $actual.StdOut.Trim() | Should -Be $expected $actual.StdErr.Trim() | Should -Be '' } } It 'R23_H1_quotes_arguments_with_spaces_on_real_launcher' { InModuleScope Skyline.DataMiner.QAOps.PipelineLibrary { $actual = Invoke-QAFrameworkDotNet -Arguments @('nuget','locals','global-packages','--list') -TimeoutSeconds 20 $actual.ExitCode | Should -Be 0 $actual.StdOut | Should -Match 'global-packages' } } It 'R23_H2_timeout_returns_bounded_result' { InModuleScope Skyline.DataMiner.QAOps.PipelineLibrary { $actual = Invoke-QAFrameworkDotNet -Arguments @('help') -TimeoutSeconds 1 -MaxStreamCharacters 64 $actual.StdOut.Length | Should -BeLessOrEqual 64 } } It 'R23_H3_process_start_failure_returns_skipped_report' { $content = Join-Path $TestDrive 'content-start' New-Item -Path (Join-Path $content 'TestHarvesting') -ItemType Directory -Force | Out-Null New-Item -Path (Join-Path $content 'TestPackagePipeline') -ItemType Directory -Force | Out-Null InModuleScope Skyline.DataMiner.QAOps.PipelineLibrary -Parameters @{ Content = $content } { Mock Install-QAFrameworkTool { [pscustomobject]@{} } Mock Invoke-QAFrameworkDotNet { throw 'runtime missing' } $report = Invoke-DotNetTestHarvesting -TestPackageContentPath $Content -WarningVariable warnings 3>$null $report.status | Should -Be 'skipped' $report.diagnostics[0].code | Should -Be 'toolLaunchFailed' @($warnings).Count | Should -Be 1 } } It 'R23_M3_abandoned_tool_cache_mutex_counts_as_acquired' { $content = Join-Path $TestDrive 'content-abandoned' New-Item -Path (Join-Path $content 'TestHarvesting') -ItemType Directory -Force | Out-Null New-Item -Path (Join-Path $content 'TestPackagePipeline') -ItemType Directory -Force | Out-Null $abandon = '$m = New-Object System.Threading.Mutex($false, ''Global\Skyline.QAOps.QAFrameworkOrchestrator.ToolCache''); $null = $m.WaitOne(); [Environment]::Exit(0)' $start = @{ FilePath = (Get-Process -Id $PID).Path ArgumentList = @('-NoProfile', '-EncodedCommand', [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($abandon))) PassThru = $true } if ($env:OS -eq 'Windows_NT') { $start['WindowStyle'] = 'Hidden' } $p = Start-Process @start $p.WaitForExit(10000) | Should -BeTrue InModuleScope Skyline.DataMiner.QAOps.PipelineLibrary -Parameters @{ Content = $content } { $script:InstallCalls = 0 Mock Install-QAFrameworkTool { $script:InstallCalls++; [pscustomobject]@{} } Mock Invoke-QAFrameworkDotNet { [pscustomobject]@{ ExitCode = 0; TimedOut = $false; StdOut = '{"status":"ok"}'; StdErr = '' } } $report = Invoke-DotNetTestHarvesting -TestPackageContentPath $Content $report.status | Should -Be 'ok' $script:InstallCalls | Should -Be 1 } } } Describe 'R23 sidecar and identity hardening' { BeforeEach { $script:Content = Join-Path $TestDrive ([guid]::NewGuid().ToString('N')) New-Item -Path (Join-Path (Join-Path $script:Content 'TestHarvesting') 'dependencies.generated') -ItemType Directory -Force | Out-Null } It 'R23_H4_unsupported_sidecar_and_bad_envelope_do_not_throw' { '{"schema":"unsupported","version":99,"testInvocations":[null]}' | Set-Content -LiteralPath (Join-Path (Join-Path (Join-Path $script:Content 'TestHarvesting') 'dependencies.generated') 'qaops.maintainers.json') -Encoding UTF8 InModuleScope Skyline.DataMiner.QAOps.PipelineLibrary -Parameters @{ Content = $script:Content } { $sidecar = Import-QAOpsMaintainerSidecar -ContentPath $Content -WarningVariable warnings 3>$null $sidecar.Entries.Count | Should -Be 0 @($sidecar.Diagnostics).Count | Should -Be 1 (Test-QAOpsMaintainerEnvelope -Maintainers ([pscustomobject]@{ version='not-an-int'; references=@() })).Valid | Should -BeFalse } } It 'R23_M2_uses_ordinal_sidecar_keys' { InModuleScope Skyline.DataMiner.QAOps.PipelineLibrary { $entries = New-QAOpsOrdinalDictionary $list = New-Object System.Collections.ArrayList [void]$list.Add([pscustomobject]@{ maintainers = [pscustomobject]@{ version=1; references=@([pscustomobject]@{ kind='user'; alias='case-owner' }) } }) $entries.Add('Tests.dll|Acme.Tests.Run|data:caseA|target:default', $list) $result = Resolve-QAOpsRuntimeMaintainers -Entries $entries -Assembly 'Tests.dll' -FullyQualifiedName 'Acme.Tests.run' -DataCaseId 'casea' -Target 'default' $result.Maintainers | Should -BeNullOrEmpty } } It 'R23_M1_extracts_nunit_parameterized_method_suffix_and_apphost_dll_namespace' { $trxPath = Join-Path $TestDrive 'nunit-real-shape.trx' '<TestRun xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010"><TestDefinitions><UnitTest id="1"><TestMethod codeBase="C:\scrubbed\Managed.Tests.dll" className="Fixtures.NUnitTests" name="Adds(1,2,3)" /></UnitTest></TestDefinitions><Results><UnitTestResult testId="1" testName="Adds(1,2,3)" outcome="Passed" /></Results></TestRun>' | Set-Content -LiteralPath $trxPath -Encoding UTF8 InModuleScope Skyline.DataMiner.QAOps.PipelineLibrary -Parameters @{ Trx = $trxPath } { $row = @(Get-QAOpsTrxResult -ResultsPath $Trx -AssemblyName 'Managed.Tests.exe')[0] $row.Assembly | Should -Be 'Managed.Tests.dll' $row.FullyQualifiedName | Should -Be 'Fixtures.NUnitTests.Adds' $row.DataCaseId | Should -Be '(1,2,3)' } } It 'R23_GAP_rejects_dtd_and_bounds_large_trx' { $dtd = Join-Path $TestDrive 'dtd.trx' '<!DOCTYPE x [ <!ENTITY e "boom"> ]><TestRun xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010">&e;</TestRun>' | Set-Content -LiteralPath $dtd -Encoding UTF8 InModuleScope Skyline.DataMiner.QAOps.PipelineLibrary -Parameters @{ Dtd = $dtd } { { Get-QAOpsTrxResult -ResultsPath $Dtd -AssemblyName 'Tests.dll' } | Should -Throw '*DTD*' { Get-QAOpsTrxResult -ResultsPath $Dtd -AssemblyName 'Tests.dll' -MaxTrxBytes 10 } | Should -Throw '*size limit*' } } } Describe 'R23 discovery, MTP, and retry accounting' { It 'R23_H5_public_discovery_preserves_root_legacy_filters' { $content = Join-Path $TestDrive 'content-discovery' New-Item -Path (Join-Path $content 'TestHarvesting') -ItemType Directory -Force | Out-Null New-Item -Path (Join-Path $content 'TestPackagePipeline') -ItemType Directory -Force | Out-Null @{ keywords=@('include'); excludeKeywords=@('exclude'); squads=@('alpha'); excludeSquads=@('beta') } | ConvertTo-Json | Set-Content -LiteralPath (Join-Path (Join-Path $content 'TestHarvesting') 'qaframework.discovery.json') -Encoding UTF8 InModuleScope Skyline.DataMiner.QAOps.PipelineLibrary -Parameters @{ Content = $content } { Mock Install-QAFrameworkTool { [pscustomobject]@{} } Mock Invoke-QAFrameworkToolJson { $requestPath = $Arguments[([array]::IndexOf($Arguments,'--request-file') + 1)] $request = Get-Content -LiteralPath $requestPath -Raw | ConvertFrom-Json $script:Request = $request [pscustomobject]@{ status='ok' } } Invoke-QAFrameworkTestDiscovery -TestPackageContentPath $Content | Out-Null $script:Request.filter.keywords | Should -Be @('include') $script:Request.filter.excludeKeywords | Should -Be @('exclude') $script:Request.filter.squads | Should -Be @('alpha') $script:Request.filter.excludeSquads | Should -Be @('beta') } } It 'R23_H6_mtp_does_not_publish_old_trx_from_assembly_directory' { $content = Join-Path $TestDrive 'content-mtp-old' New-Item -Path $content -ItemType Directory -Force | Out-Null New-Item -Path (Join-Path (Join-Path $content 'TestHarvesting') 'dependencies.generated') -ItemType Directory -Force | Out-Null $dll = Join-Path $content 'Old.Tests.dll'; Set-Content -LiteralPath $dll -Value 'x' New-Item -Path (Join-Path $content 'TestResults') -ItemType Directory -Force | Out-Null Copy-Item -LiteralPath (Join-Path $script:RepoRoot 'tests\fixtures\trx\mstest3-mtp.trx') -Destination (Join-Path (Join-Path $content 'TestResults') 'old.trx') InModuleScope Skyline.DataMiner.QAOps.PipelineLibrary -Parameters @{ Content=$content; Dll=$dll } { function dotnet { param([Parameter(ValueFromRemainingArguments=$true)][object[]]$Arguments) $global:LASTEXITCODE = 1 } function Push-TestCaseResult { throw 'old result must not publish' } { Invoke-DotNetTestAndPublishResults -PathToTestPackageContent $Content -TestDllPath $Dll -ResultsFileName 'old.trx' -UsesMTP 'true' } | Should -Not -Throw } } It 'R23_H7_retry_rejection_is_not_counted_as_accepted' { $content = Join-Path $TestDrive 'content-retry' New-Item -Path $content -ItemType Directory -Force | Out-Null 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' $sidecar = [pscustomobject]@{ schema='https://schema.skyline.be/qaops/maintainers/v1'; version=1; testInvocations=@([pscustomobject]@{ testInvocationId='mstest3.Tests.dll|Fixtures.mstest3.ParameterizedTests.Adds|data:(1, 2, expected: 3)|target:default'; maintainers=[pscustomobject]@{ version=1; references=@([pscustomobject]@{ kind='user'; alias='owner' }) } }) } $sidecar | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath (Join-Path (Join-Path (Join-Path $content 'TestHarvesting') 'dependencies.generated') 'qaops.maintainers.json') -Encoding UTF8 InModuleScope Skyline.DataMiner.QAOps.PipelineLibrary -Parameters @{ Content=$content; Dll=$dll; RepoRoot=$script:RepoRoot } { function dotnet { param([Parameter(ValueFromRemainingArguments=$true)][object[]]$Arguments) $logger = @($Arguments | Where-Object { [string]$_ -like 'trx;LogFileName=*' } | Select-Object -First 1); Copy-Item -LiteralPath (Join-Path $RepoRoot 'tests\fixtures\trx\mstest3-vstest.trx') -Destination ([string]$logger).Substring('trx;LogFileName='.Length); $global:LASTEXITCODE = 0 } function Push-TestRunManifest { [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($Maintainers, $Name) if ($Maintainers) { throw 'maintainer validation failed' } if ($Name -like 'Adds*') { [pscustomobject]@{ Supported=$true; Accepted=$false; Message='rejected' } } else { [pscustomobject]@{ Supported=$true; Accepted=$true; Message='ok' } } } Invoke-DotNetTestAndPublishResults -PathToTestPackageContent $Content -TestDllPath $Dll -ResultsFileName 'r.trx' | Out-Null $script:FinalCount | Should -Be 7 @($script:FinalErrors | Where-Object code -eq 'PushTestCaseResultNotAccepted').Count | Should -Be 1 } } } |