diff --git a/Scripts/Features/Get-CurrentTweakState.ps1 b/Scripts/Features/Get-CurrentTweakState.ps1 index 770b5c6..525c113 100644 --- a/Scripts/Features/Get-CurrentTweakState.ps1 +++ b/Scripts/Features/Get-CurrentTweakState.ps1 @@ -144,8 +144,17 @@ function Test-FeatureApplied { $expected = $op.ValueData - $match = if (($actual -is [array]) -and ($expected -is [array])) { - (Compare-Object $actual $expected).Count -eq 0 + # Binary and MultiString data is an ordered sequence. The .reg parser hands back a single-element + # sequence as a scalar and an empty one as $null, so normalise both sides to arrays by kind and + # compare them position by position (Compare-Object would treat [1,2,3] and [3,2,1] as equal). + $match = if ($actualKind -in @([Microsoft.Win32.RegistryValueKind]::Binary, [Microsoft.Win32.RegistryValueKind]::MultiString)) { + $actualItems = @(if ($null -ne $actual) { $actual }) + $expectedItems = @(if ($null -ne $expected) { $expected }) + $sameSequence = $actualItems.Count -eq $expectedItems.Count + for ($i = 0; $sameSequence -and $i -lt $actualItems.Count; $i++) { + if ($actualItems[$i] -ne $expectedItems[$i]) { $sameSequence = $false } + } + $sameSequence } else { $actual -eq $expected } diff --git a/Tests/Get-CurrentTweakState.Tests.ps1 b/Tests/Get-CurrentTweakState.Tests.ps1 index 413ad4f..00200fd 100644 --- a/Tests/Get-CurrentTweakState.Tests.ps1 +++ b/Tests/Get-CurrentTweakState.Tests.ps1 @@ -152,6 +152,42 @@ Describe 'Test-FeatureApplied - registry state comparison' { $key.Closed | Should -BeTrue } + It 'compares binary data in order: ' -ForEach @( + @{ Case = 'identical bytes'; Actual = @(1, 2, 3); Expected = @(1, 2, 3); Result = $true } + @{ Case = 'the same bytes in a different order'; Actual = @(1, 2, 3); Expected = @(3, 2, 1); Result = $false } + @{ Case = 'a different length'; Actual = @(1, 2, 3); Expected = @(1, 2); Result = $false } + @{ Case = 'a single byte that matches'; Actual = @(1); Expected = 1; Result = $true } + @{ Case = 'a single zero byte that matches'; Actual = @(0); Expected = 0; Result = $true } + @{ Case = 'a longer value against a single expected byte'; Actual = @(1, 2); Expected = 1; Result = $false } + @{ Case = 'an empty value on both sides'; Actual = @(); Expected = $null; Result = $true } + ) { + $key = New-CurrentStateRegistryKey -Values @{ Mask = [byte[]]$Actual } -Kinds @{ Mask = [Microsoft.Win32.RegistryValueKind]::Binary } + $root = [PSCustomObject]@{ Key = $key } + $root | Add-Member ScriptMethod OpenSubKey { param($path, $writable) $this.Key } + Mock Get-RegistryRootKey { $root } + Mock Get-RegFileOperations { @([PSCustomObject]@{ OperationType = 'SetValue'; KeyPath = 'HKEY_CURRENT_USER\Software\Example'; ValueName = 'Mask'; ValueType = 'Binary'; ValueData = $Expected }) } + + Test-FeatureApplied -FeatureId 'RegistryFeature' | Should -Be $Result + } + + It 'compares multi-string data in order: ' -ForEach @( + @{ Case = 'identical strings'; Actual = @('one', 'two'); Expected = @('one', 'two'); Result = $true } + @{ Case = 'the same strings in a different order'; Actual = @('one', 'two'); Expected = @('two', 'one'); Result = $false } + @{ Case = 'a different length'; Actual = @('one', 'two'); Expected = 'one'; Result = $false } + @{ Case = 'a single string that matches'; Actual = @('one'); Expected = 'one'; Result = $true } + @{ Case = 'single strings sharing their first character'; Actual = @('other'); Expected = 'one'; Result = $false } + @{ Case = 'an empty value on both sides'; Actual = @(); Expected = $null; Result = $true } + ) { + $key = New-CurrentStateRegistryKey -Values @{ Items = [string[]]$Actual } -Kinds @{ Items = [Microsoft.Win32.RegistryValueKind]::MultiString } + $root = [PSCustomObject]@{ Key = $key } + $root | Add-Member ScriptMethod OpenSubKey { param($path, $writable) $this.Key } + Mock Get-RegistryRootKey { $root } + Mock Get-RegFileOperations { @([PSCustomObject]@{ OperationType = 'SetValue'; KeyPath = 'HKEY_CURRENT_USER\Software\Example'; ValueName = 'Items'; ValueType = 'Hex7'; ValueData = $Expected }) } + + Test-FeatureApplied -FeatureId 'RegistryFeature' | Should -Be $Result + $key.Closed | Should -BeTrue + } + It 'treats missing keys and values as successful delete operations' -ForEach @( @{ OperationType = 'DeleteKey'; ReturnKey = $false } @{ OperationType = 'DeleteValue'; ReturnKey = $true }