first commit
This commit is contained in:
1 parent
831814283e
commit
7aacc0b63f
29 files changed
+5114
No files matched your search
@@ -0,0 +1,125 @@
|
||||
#Requires -Version 5.1
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference='Stop'
|
||||
Import-Module (Join-Path $PSScriptRoot '../src/Wind.Apps.psm1') -Force
|
||||
Import-Module (Join-Path $PSScriptRoot '../src/Wind.Tui.psm1') -Force
|
||||
$script:passed=0
|
||||
function Assert-True { param([bool]$Condition,[string]$Message) if (-not $Condition) { throw $Message } }
|
||||
function Assert-Throws { param([scriptblock]$Action) $threw=$false; try { & $Action | Out-Null } catch { $threw=$true }; Assert-True $threw 'Expected an exception.' }
|
||||
function Run-Test { param([string]$Name,[scriptblock]$Action) & $Action; $script:passed++; Write-Output "PASS $Name" }
|
||||
function New-App {
|
||||
param([string]$Name='SpotifyAB.SpotifyMusic',[string]$Kind='Appx',[string]$Id='app-1')
|
||||
[pscustomobject]@{Id=$Id;Name=$Name;Version='1.0';Publisher='Example';Kind=$Kind;Scope='user';Target=$Id;Protected=$false}
|
||||
}
|
||||
function New-Harness {
|
||||
param([object[]]$Apps=@((New-App)))
|
||||
$state=[pscustomobject]@{Apps=$Apps;Saves=0;Saved=$null;FailSaveAt=0;Browser=$false;InstallCode=0;RemoveCode=0;RetainApp=$false;Events=@()}
|
||||
$backend=@{
|
||||
Inventory={@($state.Apps)}.GetNewClosure()
|
||||
BrowserPresent={param($b) $state.Events+='check-browser'; $state.Browser}.GetNewClosure()
|
||||
InstallBrowser={param($b) $state.Events+='install-browser'; if ($state.InstallCode -eq 0) {$state.Browser=$true}; $state.InstallCode}.GetNewClosure()
|
||||
Remove={param($a) $state.Events+='remove-'+$a.Id; if ($state.RemoveCode -eq 0 -and -not $state.RetainApp) {$state.Apps=@($state.Apps | Where-Object Id -ne $a.Id)}; $state.RemoveCode}.GetNewClosure()
|
||||
}
|
||||
$save={param($r) $state.Saves++; if ($state.Saves -eq $state.FailSaveAt) {throw 'Storage failure'}; $state.Saved=$r | ConvertTo-Json -Depth 8 | ConvertFrom-Json}.GetNewClosure()
|
||||
[pscustomobject]@{State=$state;Backend=$backend;Save=$save;Plan=@(Get-WindAppPlan -Inventory $Apps)}
|
||||
}
|
||||
function Run-Changes {param($h) Invoke-WindAppChanges -Plan $h.Plan -Backend $h.Backend -Save $h.Save -Confirm:$false}
|
||||
|
||||
Run-Test 'Default selects curated apps, OneDrive and Edge; keeps personal apps' {
|
||||
$apps=@((New-App),(New-App 'PersonalEditor' 'Desktop' 'editor'),(New-App 'Microsoft OneDrive' 'Desktop' 'onedrive'),(New-App 'Microsoft Edge' 'Desktop' 'edge'))
|
||||
$plan=@(Get-WindAppPlan -Inventory $apps)
|
||||
Assert-True (@($plan | Where-Object Status -eq 'Remove').Count -eq 3) 'Wrong defaults.'
|
||||
Assert-True (($plan | Where-Object Id -eq 'editor').Status -eq 'Keep') 'Personal app removed by default.'
|
||||
}
|
||||
Run-Test 'Personal app opt-in and bundled exclusions' {
|
||||
$apps=@((New-App),(New-App 'PersonalEditor' 'Desktop' 'editor'))
|
||||
$plan=@(Get-WindAppPlan -Inventory $apps -ExtraId editor -ExcludeId app-1)
|
||||
Assert-True (($plan | Where-Object Id -eq 'editor').Status -eq 'Remove') 'Personal selection ignored.'
|
||||
Assert-True (($plan | Where-Object Id -eq 'app-1').Status -eq 'Keep') 'Exclusion ignored.'
|
||||
}
|
||||
Run-Test 'Protected dependencies cannot be opted into removal' {
|
||||
foreach ($name in @('Microsoft Edge WebView2 Runtime','Microsoft.DesktopAppInstaller','Microsoft.WindowsStore','Microsoft.VCLibs.140.00','Bitdefender','Microsoft.GamingServices','NVIDIA Display Driver','Mullvad VPN','VeraCrypt')) {
|
||||
$app=New-App $name
|
||||
Assert-True ((Get-WindAppPlan -Inventory @($app) -ExtraId $app.Id).Status -eq 'Keep') "Protected app selectable: $name"
|
||||
}
|
||||
}
|
||||
Run-Test 'OS nonremovable metadata overrides curated selection' {
|
||||
$app=New-App; $app.Protected=$true
|
||||
Assert-True ((Get-WindAppPlan -Inventory @($app)).Status -eq 'Keep') 'OS protection ignored.'
|
||||
}
|
||||
Run-Test 'Stale selection fails instead of broadening removal' {
|
||||
Assert-Throws {Get-WindAppPlan -Inventory @((New-App)) -ExtraId missing}
|
||||
}
|
||||
Run-Test 'Ambiguous desktop registrations block execution' {
|
||||
$h=New-Harness @((New-App 'Microsoft OneDrive' 'Desktop' 'a'),(New-App 'Microsoft OneDrive' 'Desktop' 'b'))
|
||||
Assert-Throws {Run-Changes $h}
|
||||
Assert-True ($h.State.Events.Count -eq 0) 'Blocked plan executed.'
|
||||
}
|
||||
Run-Test 'WhatIf does not call backend or write reports' {
|
||||
$h=New-Harness
|
||||
$result=Invoke-WindAppChanges -Plan $h.Plan -Backend $h.Backend -Save $h.Save -WhatIf
|
||||
Assert-True ($result.Status -eq 'Preview' -and $h.State.Saves -eq 0 -and $h.State.Events.Count -eq 0) 'WhatIf touched backend.'
|
||||
}
|
||||
Run-Test 'Browser installed and verified before app removal' {
|
||||
$h=New-Harness; $r=Run-Changes $h
|
||||
Assert-True (($h.State.Events -join ',') -eq 'check-browser,install-browser,check-browser,remove-app-1') 'Unsafe operation order.'
|
||||
Assert-True ($r.Status -eq 'Completed' -and $r.Entries[-1].Status -eq 'Removed') 'Removal not verified.'
|
||||
}
|
||||
Run-Test 'Failed browser install prevents every removal' {
|
||||
$h=New-Harness; $h.State.InstallCode=1603
|
||||
Assert-Throws {Run-Changes $h}
|
||||
Assert-True (@($h.State.Events | Where-Object {$_ -like 'remove-*'}).Count -eq 0) 'Browser failure removed app.'
|
||||
Assert-True ($h.State.Saved.Status -eq 'Incomplete') 'False completion.'
|
||||
}
|
||||
Run-Test 'Browser verification failure prevents removal' {
|
||||
$h=New-Harness; $h.Backend.BrowserPresent={param($b) $false}
|
||||
Assert-Throws {Run-Changes $h}
|
||||
Assert-True ($h.State.Apps.Count -eq 1) 'Unverified browser allowed removal.'
|
||||
}
|
||||
Run-Test 'Re-running after removal is idempotent' {
|
||||
$h=New-Harness; Run-Changes $h | Out-Null; $r=Run-Changes $h
|
||||
Assert-True ($r.Entries[-1].Status -eq 'AlreadyAbsent') 'Repeated removal attempted.'
|
||||
Assert-True (@($h.State.Events | Where-Object {$_ -like 'remove-*'}).Count -eq 1) 'Duplicate uninstall.'
|
||||
}
|
||||
Run-Test 'App update between preview and apply stops removal' {
|
||||
$h=New-Harness; $h.State.Apps[0].Version='2.0'
|
||||
Assert-Throws {Run-Changes $h}
|
||||
Assert-True ($h.State.Apps.Count -eq 1 -and $h.State.Saved.Entries[-1].Status -eq 'Conflict') 'Stale app removed.'
|
||||
}
|
||||
Run-Test 'Uninstaller nonzero and reboot code stop later operations' {
|
||||
foreach ($code in @(1603,3010,-1)) {
|
||||
$h=New-Harness @((New-App),(New-App 'Microsoft OneDrive' 'Desktop' 'b')); $h.State.RemoveCode=$code
|
||||
Assert-Throws {Run-Changes $h}
|
||||
Assert-True (@($h.State.Events | Where-Object {$_ -like 'remove-*'}).Count -eq 1) 'Continued after uncertain uninstall.'
|
||||
Assert-True ($h.State.Saved.Entries[-1].Status -eq 'Failed') 'False success.'
|
||||
}
|
||||
}
|
||||
Run-Test 'Uninstaller success without disappearance is unverified' {
|
||||
$h=New-Harness; $h.State.RetainApp=$true
|
||||
Assert-Throws {Run-Changes $h}
|
||||
Assert-True ($h.State.Saved.Entries[-1].Status -eq 'Unverified') 'Trusted exit code without readback.'
|
||||
}
|
||||
Run-Test 'Edge runs last and requires selected browser' {
|
||||
$h=New-Harness @((New-App 'Microsoft Edge' 'Desktop' 'edge'),(New-App 'Microsoft OneDrive' 'Desktop' 'onedrive'))
|
||||
Assert-Throws {Invoke-WindAppChanges -Plan $h.Plan -Browser @() -Backend $h.Backend -Save $h.Save}
|
||||
$r=Run-Changes $h
|
||||
Assert-True ($r.Entries[-1].Name -eq 'Microsoft Edge') 'Edge did not run last.'
|
||||
}
|
||||
Run-Test 'Write-ahead report failure prevents app removal' {
|
||||
$h=New-Harness; $h.State.Browser=$true; $h.State.FailSaveAt=5
|
||||
Assert-Throws {Run-Changes $h}
|
||||
Assert-True ($h.State.Apps.Count -eq 1) 'Removal ran without saved intent.'
|
||||
}
|
||||
Run-Test 'Forged protected plan is rejected before any action' {
|
||||
$h=New-Harness @((New-App 'Microsoft Edge WebView2 Runtime' 'Desktop'))
|
||||
$h.Plan[0].Status='Remove'; $h.Plan[0].Protected=$false
|
||||
Assert-Throws {Run-Changes $h}
|
||||
Assert-True ($h.State.Events.Count -eq 0) 'Forged plan executed.'
|
||||
}
|
||||
Run-Test 'Menu selection rejects invalid input and supports combinations' {
|
||||
Assert-True ((@(ConvertFrom-WindChoice '1,3,1' 3) -join ',') -eq '0,2') 'Multi-selection incorrect.'
|
||||
Assert-True (@(ConvertFrom-WindChoice 'none' 3).Count -eq 0) 'None incorrect.'
|
||||
Assert-True (@(ConvertFrom-WindChoice 'all' 3).Count -eq 3) 'All incorrect.'
|
||||
foreach ($bad in @('0','4','-1','1;whoami','1.5')) {Assert-Throws {ConvertFrom-WindChoice $bad 3}}
|
||||
}
|
||||
Write-Output "$script:passed app/menu tests passed. Only synthetic inventories and backends were used."
|
||||
@@ -0,0 +1,52 @@
|
||||
#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:[email protected]/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)
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
#Requires -Version 5.1
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Import-Module (Join-Path $PSScriptRoot '../src/Wind.Core.psm1') -Force
|
||||
$script:passed = 0
|
||||
|
||||
function Assert-True { param([bool]$Condition,[string]$Message) if (-not $Condition) { throw $Message } }
|
||||
function Assert-Throws { param([scriptblock]$Action) $threw=$false; try { & $Action | Out-Null } catch { $threw=$true }; Assert-True $threw 'Expected an exception.' }
|
||||
function Run-Test { param([string]$Name,[scriptblock]$Action) & $Action; $script:passed++; Write-Output "PASS $Name" }
|
||||
function New-Harness {
|
||||
$state = [pscustomobject]@{ Values=@{}; Writes=0; FailName=''; FailAfterWrite=$false; ReadFail=$false; Saved=$null; Saves=0; FailSaveAt=0 }
|
||||
$store = @{
|
||||
Read = {
|
||||
param($policy)
|
||||
if ($state.ReadFail) { throw 'Injected read error' }
|
||||
$key=$policy.Path+'|'+$policy.Name
|
||||
if ($state.Values.ContainsKey($key)) {
|
||||
$v=$state.Values[$key]
|
||||
return [pscustomobject]@{ Exists=$true; Kind=$v.Kind; Value=$v.Value }
|
||||
}
|
||||
[pscustomobject]@{ Exists=$false; Kind=$null; Value=$null }
|
||||
}.GetNewClosure()
|
||||
Write = {
|
||||
param($policy,$value)
|
||||
$state.Writes++
|
||||
$fail=$state.FailName -eq $policy.Name
|
||||
if ($fail -and -not $state.FailAfterWrite) { $state.FailName=''; throw 'Injected write error' }
|
||||
$key=$policy.Path+'|'+$policy.Name
|
||||
if ($value.Exists) { $state.Values[$key]=[pscustomobject]@{ Kind=$value.Kind; Value=$value.Value } }
|
||||
else { $state.Values.Remove($key) }
|
||||
if ($fail) { $state.FailName=''; throw 'Injected error after write' }
|
||||
}.GetNewClosure()
|
||||
}
|
||||
$save = {
|
||||
param($journal)
|
||||
$state.Saves++
|
||||
if ($state.Saves -eq $state.FailSaveAt) { throw 'Injected journal error' }
|
||||
$state.Saved=$journal | ConvertTo-Json -Depth 12 | ConvertFrom-Json
|
||||
}.GetNewClosure()
|
||||
[pscustomobject]@{ State=$state; Store=$store; Save=$save; Context=[pscustomobject]@{ Edition='Professional'; Build=26200; Revision=1 } }
|
||||
}
|
||||
function New-Plan { param($Harness,[string[]]$Ids=@('advertising-id','clipboard-sync')) @(Get-WindPlan -Context $Harness.Context -Store $Harness.Store -Id $Ids) }
|
||||
function Apply-Test { param($Harness,$Plan) Invoke-WindTransaction -Plan $Plan -Context $Harness.Context -Store $Harness.Store -Save $Harness.Save -InstallationId 'test-installation' }
|
||||
function Restore-Test { param($Harness,$Journal) Restore-WindTransaction -Journal $Journal -Store $Harness.Store -Save $Harness.Save -InstallationId 'test-installation' }
|
||||
|
||||
Run-Test 'Home is unsupported even if a matching policy value exists' {
|
||||
$h=New-Harness; $h.Context.Edition='Core'
|
||||
$plan=New-Plan $h
|
||||
Assert-True (@($plan | Where-Object Status -NE 'Unsupported').Count -eq 0) 'Home policies must not be called enforced.'
|
||||
Assert-Throws { Apply-Test $h $plan }
|
||||
Assert-True ($h.State.Writes -eq 0 -and $h.State.Saves -eq 0) 'Unsupported apply touched state.'
|
||||
}
|
||||
Run-Test 'Unknown build is audit-only' {
|
||||
$h=New-Harness; $h.Context.Build=99999
|
||||
Assert-True ((New-Plan $h)[0].Status -eq 'Unsupported') 'Unknown build accepted.'
|
||||
}
|
||||
Run-Test 'Edition-aware diagnostic level' {
|
||||
$h=New-Harness
|
||||
Assert-True ((New-Plan $h @('diagnostics'))[0].Desired -eq 1) 'Pro should retain required diagnostics.'
|
||||
$h.Context.Edition='Enterprise'
|
||||
Assert-True ((New-Plan $h @('diagnostics'))[0].Desired -eq 0) 'Enterprise should use supported minimum.'
|
||||
}
|
||||
Run-Test 'Update choices are explicit and preserve update services' {
|
||||
$h=New-Harness
|
||||
$keep=@(Get-WindPlan -Context $h.Context -Store $h.Store)
|
||||
Assert-True (@($keep | Where-Object Id -Like 'updates-*').Count -eq 0) 'Default modified update policy.'
|
||||
$notify=@(Get-WindPlan -Context $h.Context -Store $h.Store -Updates Notify)
|
||||
Assert-True (($notify | Where-Object Id -EQ 'updates-options').Desired -eq 2) 'Notify value wrong.'
|
||||
$manual=@(Get-WindPlan -Context $h.Context -Store $h.Store -Updates Manual)
|
||||
Assert-True (($manual | Where-Object Id -EQ 'updates-auto').Desired -eq 1) 'Manual value wrong.'
|
||||
}
|
||||
Run-Test 'Unknown ID fails instead of silently ignoring a typo' {
|
||||
$h=New-Harness; Assert-Throws { New-Plan $h @('not-a-policy') }
|
||||
}
|
||||
Run-Test 'Non-DWORD pre-existing value is never overwritten' {
|
||||
$h=New-Harness; $p=Get-WindCatalog | Where-Object Id -EQ 'advertising-id'
|
||||
$h.State.Values[$p.Path+'|'+$p.Name]=[pscustomobject]@{ Kind='String'; Value='custom' }
|
||||
Assert-Throws { Apply-Test $h (New-Plan $h) }
|
||||
Assert-True ($h.State.Writes -eq 0) 'Conflicting type was overwritten.'
|
||||
}
|
||||
Run-Test 'Microphone per-app overrides block misleading default-deny claim' {
|
||||
$h=New-Harness; $p=Get-WindCatalog | Where-Object Id -EQ 'microphone-apps'
|
||||
$h.State.Values[$p.Path+'|LetAppsAccessMicrophone_ForceAllowTheseApps']=[pscustomobject]@{ Kind='MultiString'; Value=@('example.app') }
|
||||
Assert-True ((New-Plan $h @('microphone-apps'))[0].Status -eq 'Conflict') 'Exception was missed.'
|
||||
}
|
||||
Run-Test 'Unreadable registry is a read error, not missing or configured' {
|
||||
$h=New-Harness; $h.State.ReadFail=$true
|
||||
Assert-True ((New-Plan $h)[0].Status -eq 'ReadError') 'Read error hidden.'
|
||||
}
|
||||
Run-Test 'Apply and restore preserve absence and arbitrary original DWORD' {
|
||||
$h=New-Harness; $p=Get-WindCatalog | Where-Object Id -EQ 'advertising-id'
|
||||
$h.State.Values[$p.Path+'|'+$p.Name]=[pscustomobject]@{ Kind='DWord'; Value=7 }
|
||||
$j=Apply-Test $h (New-Plan $h)
|
||||
Assert-True ($j.Status -eq 'Applied' -and $h.State.Values.Count -eq 2) 'Apply incomplete.'
|
||||
$r=Restore-Test $h ($j | ConvertTo-Json -Depth 12 | ConvertFrom-Json)
|
||||
Assert-True ($r.Status -eq 'Restored' -and $h.State.Values.Count -eq 1) 'Restore did not preserve absence.'
|
||||
Assert-True ($h.State.Values[$p.Path+'|'+$p.Name].Value -eq 7) 'Original value lost.'
|
||||
}
|
||||
Run-Test 'Reapplying configured policies makes zero registry writes' {
|
||||
$h=New-Harness; Apply-Test $h (New-Plan $h) | Out-Null; $count=$h.State.Writes
|
||||
$j=Apply-Test $h (New-Plan $h)
|
||||
Assert-True ($h.State.Writes -eq $count -and @($j.Entries).Count -eq 0) 'Reapply mutated configured values.'
|
||||
}
|
||||
Run-Test 'Write-ahead journal failure prevents all policy writes' {
|
||||
$h=New-Harness; $h.State.FailSaveAt=1
|
||||
Assert-Throws { Apply-Test $h (New-Plan $h) }
|
||||
Assert-True ($h.State.Writes -eq 0) 'Write occurred without durable journal.'
|
||||
}
|
||||
Run-Test 'Mid-apply failure rolls back successful earlier writes' {
|
||||
$h=New-Harness; $h.State.FailName='AllowCrossDeviceClipboard'
|
||||
Assert-Throws { Apply-Test $h (New-Plan $h) }
|
||||
Assert-True ($h.State.Values.Count -eq 0 -and $h.State.Saved.Status -eq 'Restored') 'Rollback incomplete.'
|
||||
}
|
||||
Run-Test 'Failure after mutation also rolls back the current setting' {
|
||||
$h=New-Harness; $h.State.FailName='AllowCrossDeviceClipboard'; $h.State.FailAfterWrite=$true
|
||||
Assert-Throws { Apply-Test $h (New-Plan $h) }
|
||||
Assert-True ($h.State.Values.Count -eq 0) 'Current setting was not recovered.'
|
||||
}
|
||||
Run-Test 'External edits cause restore conflict and are preserved' {
|
||||
$h=New-Harness; $j=Apply-Test $h (New-Plan $h)
|
||||
$p=Get-WindCatalog | Where-Object Id -EQ 'advertising-id'
|
||||
$h.State.Values[$p.Path+'|'+$p.Name].Value=9
|
||||
$r=Restore-Test $h $j
|
||||
Assert-True ($r.Status -eq 'RestoreIncomplete') 'Conflict not reported.'
|
||||
Assert-True ($h.State.Values[$p.Path+'|'+$p.Name].Value -eq 9) 'External edit overwritten.'
|
||||
}
|
||||
Run-Test 'Crash with Writing journal status remains recoverable' {
|
||||
$h=New-Harness; $j=Apply-Test $h (New-Plan $h); $j.Entries[0].Status='Writing'
|
||||
Restore-Test $h $j | Out-Null
|
||||
Assert-True ($h.State.Values.Count -eq 0) 'Writing status not recovered.'
|
||||
}
|
||||
Run-Test 'Pending entries never restore changes made by another program' {
|
||||
$h=New-Harness; $j=Apply-Test $h (New-Plan $h); $j.Entries[0].Status='Pending'
|
||||
Restore-Test $h $j | Out-Null
|
||||
Assert-True ($h.State.Values.Count -eq 1) 'Never-started entry was modified.'
|
||||
}
|
||||
Run-Test 'Repeated restore does not undo subsequent configuration' {
|
||||
$h=New-Harness; $j=Apply-Test $h (New-Plan $h); Restore-Test $h $j | Out-Null
|
||||
Apply-Test $h (New-Plan $h) | Out-Null; $writes=$h.State.Writes
|
||||
Restore-Test $h $j | Out-Null
|
||||
Assert-True ($h.State.Writes -eq $writes -and $h.State.Values.Count -eq 2) 'Repeated restore overwrote later apply.'
|
||||
}
|
||||
Run-Test 'Journal rejects unknown entries before any write' {
|
||||
$h=New-Harness; $j=Apply-Test $h (New-Plan $h); $j.Entries[1].Id='untrusted-path'; $writes=$h.State.Writes
|
||||
Assert-Throws { Restore-Test $h $j }
|
||||
Assert-True ($writes -eq $h.State.Writes) 'Partial restore before validation.'
|
||||
}
|
||||
Run-Test 'Journal rejects wrong installation and invalid target' {
|
||||
$h=New-Harness; $j=Apply-Test $h (New-Plan $h)
|
||||
$j.InstallationId='other'; Assert-Throws { Restore-Test $h $j }
|
||||
$j.InstallationId='test-installation'; $j.Entries[0].After.Value=999
|
||||
Assert-Throws { Restore-Test $h $j }
|
||||
}
|
||||
Run-Test 'State change between planning and apply is not overwritten' {
|
||||
$h=New-Harness; $plan=New-Plan $h; $p=Get-WindCatalog | Where-Object Id -EQ 'advertising-id'
|
||||
$h.State.Values[$p.Path+'|'+$p.Name]=[pscustomobject]@{ Kind='DWord'; Value=88 }
|
||||
Assert-Throws { Apply-Test $h $plan }
|
||||
Assert-True ($h.State.Writes -eq 0 -and $h.State.Values[$p.Path+'|'+$p.Name].Value -eq 88) 'Stale plan wrote registry.'
|
||||
}
|
||||
Run-Test 'Journal failure after a successful write triggers recovery' {
|
||||
$h=New-Harness; $h.State.FailSaveAt=3
|
||||
Assert-Throws { Apply-Test $h (New-Plan $h) }
|
||||
Assert-True ($h.State.Values.Count -eq 0 -and $h.State.Saved.Status -eq 'Restored') 'Post-write journal failure was not recovered.'
|
||||
}
|
||||
Run-Test 'Duplicate journal entry is rejected before restore' {
|
||||
$h=New-Harness; $j=Apply-Test $h (New-Plan $h); $j.Entries[1].Id=$j.Entries[0].Id; $writes=$h.State.Writes
|
||||
Assert-Throws { Restore-Test $h $j }
|
||||
Assert-True ($writes -eq $h.State.Writes) 'Duplicate journal triggered writes.'
|
||||
}
|
||||
Run-Test 'Durable journal creation and replacement round-trip' {
|
||||
$testRoot=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../reports'))
|
||||
[IO.Directory]::CreateDirectory($testRoot) | Out-Null
|
||||
$path=Join-Path $testRoot ([guid]::NewGuid().ToString('N')+'.journal.json')
|
||||
try {
|
||||
$h=New-Harness; $j=Apply-Test $h (New-Plan $h)
|
||||
Save-WindJournal -Journal $j -Path $path
|
||||
$loaded=Get-Content -LiteralPath $path -Raw | ConvertFrom-Json
|
||||
Assert-WindJournal $loaded 'test-installation'
|
||||
$j.Status='AppliedAgain'; Save-WindJournal -Journal $j -Path $path
|
||||
Assert-True ((Get-Content -LiteralPath $path -Raw | ConvertFrom-Json).Status -eq 'AppliedAgain') 'Atomic replacement failed.'
|
||||
} finally {
|
||||
# Exact single file generated in the workspace; never a recursive cleanup.
|
||||
if ([IO.File]::Exists($path)) { [IO.File]::Delete($path) }
|
||||
}
|
||||
}
|
||||
|
||||
Write-Output "$script:passed tests passed. No Windows policy settings were modified."
|
||||
Reference in new issue
Block a user