#Requires -Version 5.0 # Offline tests: collector and upload calls are mocked; only temporary files are deleted. $ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' $DsaScriptUnderTest = Join-Path (Split-Path $PSScriptRoot -Parent) 'Get-DSAManifest.ps1' $DsaTokens = $null $DsaErrors = $null $null = [Management.Automation.Language.Parser]::ParseFile($DsaScriptUnderTest, [ref]$DsaTokens, [ref]$DsaErrors) If ($DsaErrors.Count) { Throw ($DsaErrors | Out-String) } # Load only the real conversion helper, without running Tools.ps1 setup. $DsaToolsPath = Join-Path (Split-Path $PSScriptRoot -Parent) 'Tools.ps1' $DsaToolsAst = [Management.Automation.Language.Parser]::ParseFile($DsaToolsPath, [ref]$DsaTokens, [ref]$DsaErrors) If ($DsaErrors.Count) { Throw ($DsaErrors | Out-String) } $DsaBooleanDefinition = $DsaToolsAst.Find({ param($Node) $Node -is [Management.Automation.Language.FunctionDefinitionAst] -and $Node.Name -eq 'ConvertTo-RmmBoolean' }, $true) If (!$DsaBooleanDefinition) { Throw 'Missing helper: ConvertTo-RmmBoolean' } . ([scriptblock]::Create($DsaBooleanDefinition.Extent.Text)) Function Assert-Dsa { param([bool]$Condition, [string]$Description) If (!$Condition) { Throw $Description } } Function Download-File { Throw 'DSA must use Download-FileDirectly.' } Function Download-FileDirectly { [CmdletBinding()] param($URL, $File) $script:DsaDownloadCalls++ If ($script:DsaTestMode -eq 'empty-download') { [IO.File]::WriteAllBytes($File, [byte[]]@()) Return $File } If ($script:DsaTestMode -eq 'download-failure') { Throw 'Simulated BITS failure.' } [IO.File]::WriteAllText($File, 'Mock executable; never executed.') Return $File } Function Start-Process { [CmdletBinding()] param($FilePath, $ArgumentList, [switch]$Wait, [switch]$PassThru, $WindowStyle) $script:DsaProcessCalls++ $DsaMatch = [regex]::Match($ArgumentList, '"--output"\s+"([^"]+)"') If (!$DsaMatch.Success) { Throw 'Collector output argument missing.' } $script:DsaCsvPath = $DsaMatch.Groups[1].Value $script:DsaGeneratedPath = $DsaCsvPath If ($ArgumentList -match '"--zip"') { $script:DsaGeneratedPath = [IO.Path]::ChangeExtension($DsaCsvPath, '.zip') } If (!$DsaGeneratedPath.StartsWith($script:DsaTestRoot + '\', [StringComparison]::OrdinalIgnoreCase)) { Throw 'Mock collector output is outside the test directory.' } If ($script:DsaTestMode -eq 'collector-failure') { Return [pscustomobject]@{ExitCode=7} } If ($script:DsaTestMode -ne 'missing-output') { [IO.File]::WriteAllText($DsaGeneratedPath, 'Synthetic manifest.') If ($DsaGeneratedPath -ne $DsaCsvPath) { # A same-basename CSV must not be deleted when the uploaded result is ZIP. [IO.File]::WriteAllText($DsaCsvPath, 'Unrelated CSV sentinel.') } } Return [pscustomobject]@{ExitCode=0} } Function Upload-File { [CmdletBinding()] param($Path, $Subject, $Message) Assert-Dsa (Test-Path -LiteralPath $Path -PathType Leaf) 'File was deleted before upload.' $script:DsaRecordedUploads += [pscustomobject]@{Path=$Path; Subject=$Subject; Message=$Message} If ($script:DsaTestMode -eq 'upload-failure') { Throw 'Simulated upload failure.' } If ($script:DsaTestMode -eq 'no-receipt') { Return } If ($script:DsaTestMode -eq 'incomplete-receipt') { Return [pscustomobject]@{AttachmentId='test-attachment'} } Return [pscustomobject]@{Path=$Path; AttachmentId='test-attachment'; Status='Filedrop message sent successfully'} } Function Invoke-DsaCase { param([bool]$ZipExpected, [bool]$UploadExpected, [bool]$DeleteExpected, [string]$Mode='success', [hashtable]$Inputs) $Zip = If ($ZipExpected) { ' YeS ' } Else { ' n ' } $Upload = If ($UploadExpected) { ' $TrUe ' } Else { ' 0 ' } $DeleteAfterUpload = If ($DeleteExpected) { ' 1 ' } Else { ' $FaLsE ' } If ($null -ne $Inputs) { $Zip = $Inputs.Zip $Upload = $Inputs.Upload $DeleteAfterUpload = $Inputs.DeleteAfterUpload } $DSAManifestSource = 'C:\Synthetic Source' $Global:ToolsDirectory = Join-Path $script:DsaTestRoot 'Tools' $Global:OutputDirectory = Join-Path $script:DsaTestRoot ([guid]::NewGuid().ToString('N')) $script:DsaTestMode = $Mode $script:DsaRecordedUploads = @() $script:DsaDownloadCalls = 0 $script:DsaProcessCalls = 0 $script:DsaGeneratedPath = $null $DsaCaught = $null Try { . $DsaScriptUnderTest | Out-Null } Catch { $DsaCaught = $_ } If ($Mode -eq 'invalid-input') { Assert-Dsa ($null -ne $DsaCaught -and $DsaCaught.Exception.Message -like 'Invalid $* value*') 'Invalid flag was not rejected.' Assert-Dsa ($script:DsaDownloadCalls -eq 0) 'Invalid flag must fail before downloading.' Return } Assert-Dsa ($script:DsaDownloadCalls -eq 1) 'Expected one direct download.' If ($Mode -in @('empty-download','download-failure')) { Assert-Dsa ($null -ne $DsaCaught) 'Download failure must stop collection.' Assert-Dsa ($script:DsaProcessCalls -eq 0 -and $script:DsaRecordedUploads.Count -eq 0) 'Download failure must prevent execution and upload.' If ($Mode -eq 'empty-download') { Assert-Dsa ($DsaCaught.Exception.Message -like '*0 bytes*') 'Empty download error must explain the failure.' } Return } If ($Mode -in @('collector-failure','missing-output')) { Assert-Dsa ($null -ne $DsaCaught -and $script:DsaRecordedUploads.Count -eq 0) 'Collection failure must prevent upload.' Return } If ($Mode -eq 'success') { Assert-Dsa ($null -eq $DsaCaught) "Unexpected failure: ${DsaCaught}" } Else { Assert-Dsa ($null -ne $DsaCaught) 'Expected upload/receipt failure.' } $DsaExpectedExtension = If ($ZipExpected) { '.zip' } Else { '.csv' } Assert-Dsa ([IO.Path]::GetExtension($script:DsaGeneratedPath) -eq $DsaExpectedExtension) 'Incorrect collected file type.' $DsaExpectedCalls = If ($UploadExpected) { 1 } Else { 0 } Assert-Dsa ($script:DsaRecordedUploads.Count -eq $DsaExpectedCalls) 'Unexpected upload call count.' If ($UploadExpected) { Assert-Dsa ($DsaRecordedUploads[0].Path -eq $script:DsaGeneratedPath) 'Upload targeted the wrong file.' Assert-Dsa ($DsaRecordedUploads[0].Subject -ceq "DSA manifest file uploaded from ${Env:COMPUTERNAME}") 'Subject changed.' $DsaExpectedMessage = "DSA collection performed on ${DSAManifestSource} at $($DsaCollectionTime.ToString('yyyy-MM-dd HH:mm:ss zzz'))" Assert-Dsa ($DsaRecordedUploads[0].Message -ceq $DsaExpectedMessage) 'Message changed.' } $DsaShouldRetain = !($UploadExpected -and $DeleteExpected -and $Mode -eq 'success') Assert-Dsa ((Test-Path -LiteralPath $script:DsaGeneratedPath) -eq $DsaShouldRetain) 'Incorrect deletion or retention behavior.' If ($ZipExpected) { Assert-Dsa (Test-Path -LiteralPath $script:DsaCsvPath) 'Cleanup incorrectly deleted the CSV neighbor.' } } $script:DsaTestRoot = Join-Path ([IO.Path]::GetTempPath()) ('dsa-options-tests-' + [guid]::NewGuid().ToString('N')) $null = New-Item -Path $script:DsaTestRoot -ItemType Directory $DsaOriginalToolsDirectory = $Global:ToolsDirectory $DsaOriginalOutputDirectory = $Global:OutputDirectory Try { Foreach ($ZipExpected in @($false,$true)) { Foreach ($UploadExpected in @($false,$true)) { Foreach ($DeleteExpected in @($false,$true)) { Invoke-DsaCase $ZipExpected $UploadExpected $DeleteExpected Write-Host "PASS: ZIP=$ZipExpected Upload=$UploadExpected DeleteAfterUpload=$DeleteExpected" } } Foreach ($Mode in @('upload-failure','no-receipt','incomplete-receipt','collector-failure','missing-output','empty-download','download-failure')) { Invoke-DsaCase $ZipExpected $true $true $Mode Write-Host "PASS: ZIP=$ZipExpected $Mode prevents deletion." } } Invoke-DsaCase $false $false $false -Inputs @{} Foreach ($Name in @('Zip','Upload','DeleteAfterUpload')) { $Inputs = @{Zip='no'; Upload='no'; DeleteAfterUpload='no'} $Inputs[$Name] = 'maybe' Invoke-DsaCase $false $false $false 'invalid-input' $Inputs } Write-Host "PASS: empty options default to false; invalid options fail early." Write-Host "All Get-DSAManifest tests passed on PowerShell $($PSVersionTable.PSVersion), $([IntPtr]::Size * 8)-bit." } Finally { $Global:ToolsDirectory = $DsaOriginalToolsDirectory $Global:OutputDirectory = $DsaOriginalOutputDirectory $DsaResolvedRoot = (Resolve-Path -LiteralPath $script:DsaTestRoot).Path $DsaTempRoot = [IO.Path]::GetFullPath([IO.Path]::GetTempPath()).TrimEnd('\') + '\' If (!$DsaResolvedRoot.StartsWith($DsaTempRoot, [StringComparison]::OrdinalIgnoreCase) -or (Split-Path $DsaResolvedRoot -Leaf) -notlike 'dsa-options-tests-*') { Throw 'Unexpected cleanup directory.' } Remove-Item -LiteralPath $DsaResolvedRoot -Recurse -Force }