161 lines
8.8 KiB
PowerShell
161 lines
8.8 KiB
PowerShell
#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
|
|
}
|