#Requires -Version 5.1 Set-StrictMode -Version Latest $ErrorActionPreference='Stop' Import-Module (Join-Path $PSScriptRoot '../src/Wind.OfflineUpdates.psm1') -Force $testRoot=Join-Path ([IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../reports'))) ('offline-tests-'+[guid]::NewGuid().ToString('N')) [IO.Directory]::CreateDirectory($testRoot) | Out-Null $manifestPath=Join-Path $testRoot 'bundle.json' $packagePath=Join-Path $testRoot 'synthetic.msu' [IO.File]::WriteAllText($packagePath,'Synthetic test bytes. Not an update or executable.') $hash=(Get-FileHash -LiteralPath $packagePath -Algorithm SHA256).Hash $target=[pscustomobject]@{Build=26200;Architecture='x64'} $script:passed=0 function New-Case { [pscustomobject]@{Schema=1;Target=[pscustomobject]@{Build=26200;Architecture='x64'};CollectedOn='2026-09-19';Packages=@([pscustomobject]@{Id='test';File='synthetic.msu';Sha256=$hash;Source='https://www.catalog.update.microsoft.com/';Kind='Windows';Requires=@()})} } function Verify-Case {param($Case) $Case | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $manifestPath; Test-WindOfflineBundle -Manifest $manifestPath -Target $target} function Assert-True {param($Condition,$Message) if (-not $Condition) {throw $Message}} function Run-Test {param($Name,[scriptblock]$Action) & $Action; $script:passed++; Write-Output "PASS $Name"} function Must-Fail {param([scriptblock]$Action) $failed=$false;try{& $Action | Out-Null}catch{$failed=$true};Assert-True $failed 'Expected refusal.'} try { Run-Test 'Matching synthetic file remains unauthenticated and uninstalled' { $r=Verify-Case (New-Case);Assert-True ($r.Status -eq 'IntegrityVerified' -and $r.Authenticity -eq 'NotVerified' -and $r.Installation -eq 'NotPerformed') 'Overclaimed verification.' } Run-Test 'Tampered file is refused' { $c=New-Case;$c.Packages[0].Sha256=('0'*64);Must-Fail {Verify-Case $c} } Run-Test 'Wrong architecture is refused' { $c=New-Case;$c.Target.Architecture='arm64';Must-Fail {Verify-Case $c} } Run-Test 'Wrong build is refused' { $c=New-Case;$c.Target.Build=19045;Must-Fail {Verify-Case $c} } Run-Test 'Path traversal is refused' { $c=New-Case;$c.Packages[0].File='../synthetic.msu';Must-Fail {Verify-Case $c} } Run-Test 'Alternate stream is refused' { $c=New-Case;$c.Packages[0].File='synthetic.msu:payload.exe';Must-Fail {Verify-Case $c} } Run-Test 'UNC manifest is refused before filesystem access' { Must-Fail {Test-WindOfflineBundle -Manifest '\\invalid.test\share\bundle.json' -Target $target} } Run-Test 'Missing file is refused' { $c=New-Case;$c.Packages[0].File='missing.msu';Must-Fail {Verify-Case $c} } Run-Test 'Unknown execution field is refused' { $c=New-Case;$c.Packages[0] | Add-Member NoteProperty InstallCommand 'do-not-run';Must-Fail {Verify-Case $c} } Run-Test 'Empty package list is refused' { $c=New-Case;$c.Packages=@();Must-Fail {Verify-Case $c} } Run-Test 'Duplicate package ID is refused' { $c=New-Case;$c.Packages+= $c.Packages[0];Must-Fail {Verify-Case $c} } Run-Test 'Forward dependency is refused' { $c=New-Case;$c.Packages[0].Requires=@('later');Must-Fail {Verify-Case $c} } Run-Test 'Self dependency is refused' { $c=New-Case;$c.Packages[0].Requires=@('test');Must-Fail {Verify-Case $c} } Run-Test 'Non-HTTPS source is refused' { $c=New-Case;$c.Packages[0].Source='http://example.test/file';Must-Fail {Verify-Case $c} } Run-Test 'Credentials in provenance URL are refused' { $c=New-Case;$c.Packages[0].Source='https://user:password@example.test/file';Must-Fail {Verify-Case $c} } Run-Test 'Invalid date is refused' { $c=New-Case;$c.CollectedOn='2026-02-30';Must-Fail {Verify-Case $c} } Run-Test 'Future date is refused' { $c=New-Case;$c.CollectedOn='2099-01-01';Must-Fail {Verify-Case $c} } Run-Test 'Valid ordered dependency passes integrity only' { [IO.File]::WriteAllText((Join-Path $testRoot 'second.msu'),'Synthetic second package.') $c=New-Case;$c.Packages+= [pscustomobject]@{Id='second';File='second.msu';Sha256=(Get-FileHash -LiteralPath (Join-Path $testRoot 'second.msu') -Algorithm SHA256).Hash;Source='https://www.catalog.update.microsoft.com/';Kind='Windows';Requires=@('test')} Assert-True ((Verify-Case $c).PackageCount -eq 2) 'Ordered dependencies rejected.' } Run-Test 'Junction bundle root is refused' { $junction=Join-Path $testRoot 'redirect';New-Item -ItemType Junction -Path $junction -Target $testRoot | Out-Null try {Must-Fail {Test-WindOfflineBundle -Manifest (Join-Path $junction 'bundle.json') -Target $target}} finally {[IO.Directory]::Delete($junction)} } Write-Output "$script:passed offline-bundle tests passed. Synthetic files only; no update installation or network calls." } finally { # Delete only the explicitly created files; never recursively delete a computed path. foreach($leaf in @('bundle.json','synthetic.msu','second.msu')) { $file=Join-Path $testRoot $leaf;if(Test-Path -LiteralPath $file){Remove-Item -LiteralPath $file -Force} } [IO.Directory]::Delete($testRoot) }