Browse Source

test: make the stream rows independent of the error action, show the Init branch, and pin the dot rules

The independent review of the delta found the tests below weak. The verbose and debug rows of PipelineControl ran their command without an error action; the CI runner sets Stop, under which the handler that reports a later command's exception as an item error ended the pipeline with it, so the mutations that stop recording the verbose or the debug exception escaped (M19 in two configurations, M20 in all four). The rows now say -ErrorAction SilentlyContinue. The Enable-Privileges tests for a script named NTFSSecurity.Init.ps1 could not fail for the branch they name when the module setting was true, because the module enables the privileges itself before the cmdlet runs; they now also assert the verbose message that only the cmdlet writes, and they disable the privileges before each test so that the child process inherits none.

The first-nested-folder test asserts the first folder, not either. Set-TestNullDacl refuses an item that is a link, which the native call would follow out of the sandbox. A table pins the three outcomes that the filter documentation lists for a dot (Report.*, a trailing dot, an empty value), so a change of those rules is a decision.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Co-authored-by: AI Assistant <ai@example.com>
pull/118/head
Raimund Andree 2 days ago
parent
commit
f4a16e1bdf
  1. 24
      Tests/ItemCmdlets.Tests.ps1
  2. 2
      Tests/Links.Tests.ps1
  3. 12
      Tests/PipelineControl.Tests.ps1
  4. 45
      Tests/Privileges.Tests.ps1
  5. 19
      Tests/TestHelpers.Tests.ps1
  6. 11
      Tests/TestHelpers.psm1

24
Tests/ItemCmdlets.Tests.ps1

@ -207,6 +207,30 @@ Describe 'Get-ChildItem2' {
($result.Name -join ',') | Should -BeExactly 'Two.dots.txt'
}
# The names are matched twice, by the enumeration and by the cmdlet, and the rules for a dot differ from those of
# Get-ChildItem, where Report.* also returns Report. The documentation lists this as a limitation; these cases pin
# it, so that a change of the rules is a decision. Report* is the control: without a dot in the pattern, the names
# without a dot are returned.
It 'Should return <Outcome> for -Filter "<Filter>"' -ForEach @(
@{ Filter = 'Report.*'; Expected = 'Report.txt'; Outcome = 'only the names with a dot' }
@{ Filter = 'Report*'; Expected = 'Report,Report.txt'; Outcome = 'the names with and without a dot' }
@{ Filter = 'Report.'; Expected = ''; Outcome = 'nothing' }
@{ Filter = 'Rep*.'; Expected = ''; Outcome = 'nothing' }
@{ Filter = '*.'; Expected = ''; Outcome = 'nothing' }
@{ Filter = ''; Expected = ''; Outcome = 'nothing' }
) {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'FilterDotRules' -Directory
foreach ($name in 'Report', 'Report.txt', 'Other') {
$file = Join-Path -Path $folder -ChildPath $name
Assert-TestSandboxPath -Sandbox $sandbox -Path $file
Set-Content -LiteralPath $file -Value $name
}
$result = @(Get-ChildItem2 -Path $folder -Filter $Filter -ErrorAction Stop)
($result.Name | Sort-Object) -join ',' | Should -BeExactly $Expected
}
It 'Should reject a null -Filter' {
{ Get-ChildItem2 -Path $tree -Filter $null -ErrorAction Stop } |
Should -Throw -ErrorId 'ParameterArgumentValidationError,NTFSSecurity.GetChildItem2' -ExpectedMessage "*'Filter'*"

2
Tests/Links.Tests.ps1

@ -267,6 +267,8 @@ Describe 'Get-NTFSHardLink' {
$readRights = 'ReadAttributes, ReadData, ReadPermissions'
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = $readRights; $currentUser = $readRights }
# Reading the data is refused. ReadAttributes and ReadPermissions are denied as well, but nothing shows that in
# every session: an elevated one was seen to read the permissions anyway.
{ Get-Content -LiteralPath $file -ErrorAction Stop } | Should -Throw
$result = @(Get-NTFSHardLink -Path $file -ErrorVariable linkErrors -ErrorAction SilentlyContinue)

12
Tests/PipelineControl.Tests.ps1

@ -288,7 +288,9 @@ BeforeAll {
# The commands that write a verbose or a debug message inside the try of their loop, which the later command takes.
# The first record that reaches Select-Object ends the pipeline there. The preference of the debug stream is set by
# Assert-StreamStop: the Debug switch would ask before every message.
# Assert-StreamStop: the Debug switch would ask before every message. The error action is named because the CI runner
# sets $ErrorActionPreference to Stop: a catch that reports the exception of the later command as an error of the
# item would then end the pipeline with it, and the test could not tell that catch from passing the exception on.
$streamRuns = @{
'Get-FileHash2/verbose' = @{
# The first path is a folder, which the cmdlet skips with a verbose message.
@ -297,16 +299,16 @@ BeforeAll {
$context.File = New-TestSandboxItem -Sandbox $sandbox -Name 'Hashed'
$context
}
Run = { param ($Context) Get-FileHash2 -Path $Context.First, $Context.File -Verbose 4>&1 }
Run = { param ($Context) Get-FileHash2 -Path $Context.First, $Context.File -Verbose -ErrorAction SilentlyContinue 4>&1 }
}
'Set-NTFSSecurityDescriptor/verbose' = @{
Prepare = $cases['Set-NTFSSecurityDescriptor'].Prepare
Run = { param ($Context) Set-NTFSSecurityDescriptor -SecurityDescriptor $Context.Descriptors -Verbose 4>&1 }
Run = { param ($Context) Set-NTFSSecurityDescriptor -SecurityDescriptor $Context.Descriptors -Verbose -ErrorAction SilentlyContinue 4>&1 }
Untouched = $cases['Set-NTFSSecurityDescriptor'].Untouched
}
'Set-NTFSOwner/debug' = @{
Prepare = { New-Pair }
Run = { param ($Context) Set-NTFSOwner -Path $Context.First, $Context.Second -Account $currentUser 5>&1 }
Run = { param ($Context) Set-NTFSOwner -Path $Context.First, $Context.Second -Account $currentUser -ErrorAction SilentlyContinue 5>&1 }
}
}
@ -545,7 +547,7 @@ Describe 'A later command and the error of a folder that Get-ChildItem2 cannot r
$caught | Should -Not -BeNullOrEmpty
$caught.FullyQualifiedErrorId | Should -BeLike 'DirUnauthorizedAccessError,*'
$caught.TargetObject | Should -BeIn $unreadable
$caught.TargetObject | Should -Be $unreadable[0]
$listed | Should -BeNullOrEmpty
}
}

45
Tests/Privileges.Tests.ps1

@ -241,8 +241,10 @@ Describe 'Privileges when a later command takes the debug messages of the cmdlet
# Enable-Privileges recognizes the script NTFSSecurity.Init.ps1, which a user adds to start the module, by its name: from
# that script, it enables the privileges only for the module setting EnablePrivileges, from any other script always. Each
# test runs the script in a child process, which starts without enabled privileges, so that the privileges of this
# process stay as they are.
# test runs the script in a child process, which inherits the privilege states of this one (disabled here, see BeforeEach),
# so that the privileges of this process stay as they are. With the setting $true, the module enables the privileges
# itself before the cmdlet runs, so the state alone does not show that the cmdlet did: it also announces that in a verbose
# message.
Describe 'Enable-Privileges in the script NTFSSecurity.Init.ps1' {
BeforeAll {
function Invoke-StartScript {
@ -256,24 +258,51 @@ Describe 'Enable-Privileges in the script NTFSSecurity.Init.ps1' {
param ($ModulePath, $Setting)
Import-Module -Name $ModulePath -ErrorAction Stop
(Get-Module -Name NTFSSecurity).PrivateData['EnablePrivileges'] = ($Setting -eq 'True')
Enable-Privileges
$messages = @(Enable-Privileges -Verbose 4>&1 | ForEach-Object -Process { "$($_.Message)" })
'ANNOUNCED:{0}' -f [bool] @($messages -like '*are now enabled giving you access*').Count
'BACKUP:{0}' -f (Get-Privileges | Where-Object -Property Privilege -EQ -Value 'Backup').PrivilegeState
'@
$output = & (Get-Process -Id $PID).Path -NoProfile -NonInteractive -ExecutionPolicy Bypass -File $script -ModulePath ([IO.Path]::GetFullPath($modulePath)) -Setting $Setting
@($output | Where-Object -FilterScript { $_ -like 'BACKUP:*' }) -replace '^BACKUP:'
$output = @(& (Get-Process -Id $PID).Path -NoProfile -NonInteractive -ExecutionPolicy Bypass -File $script -ModulePath ([IO.Path]::GetFullPath($modulePath)) -Setting $Setting)
[pscustomobject]@{
Announced = @($output | Where-Object -FilterScript { $_ -like 'ANNOUNCED:*' }) -replace '^ANNOUNCED:'
Backup = @($output | Where-Object -FilterScript { $_ -like 'BACKUP:*' }) -replace '^BACKUP:'
}
}
}
BeforeEach {
Disable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
}
AfterEach {
Disable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
}
It 'Should enable the privileges when the module setting EnablePrivileges is $true' -Skip:(-not $holdsPrivileges) {
Invoke-StartScript -ScriptName 'NTFSSecurity.Init.ps1' -Setting $true | Should -Be 'Enabled'
Get-BackupPrivilegeState | Should -Be 'Disabled'
$result = Invoke-StartScript -ScriptName 'NTFSSecurity.Init.ps1' -Setting $true
$result.Backup | Should -Be 'Enabled'
$result.Announced | Should -Be 'True'
}
It 'Should leave the privileges disabled when the module setting EnablePrivileges is $false' -Skip:(-not $holdsPrivileges) {
Invoke-StartScript -ScriptName 'NTFSSecurity.Init.ps1' -Setting $false | Should -Be 'Disabled'
Get-BackupPrivilegeState | Should -Be 'Disabled'
$result = Invoke-StartScript -ScriptName 'NTFSSecurity.Init.ps1' -Setting $false
$result.Backup | Should -Be 'Disabled'
$result.Announced | Should -Be 'False'
}
It 'Should enable the privileges in a script of another name also when the module setting EnablePrivileges is $false' -Skip:(-not $holdsPrivileges) {
Invoke-StartScript -ScriptName 'Other.ps1' -Setting $false | Should -Be 'Enabled'
Get-BackupPrivilegeState | Should -Be 'Disabled'
$result = Invoke-StartScript -ScriptName 'Other.ps1' -Setting $false
$result.Backup | Should -Be 'Enabled'
$result.Announced | Should -Be 'True'
}
}

19
Tests/TestHelpers.Tests.ps1

@ -283,6 +283,25 @@ Describe 'Test helpers' {
Should -Throw -ExpectedMessage 'Refusing to change*'
}
# The native call follows a link, so a junction in the sandbox that points to another folder is refused as the
# item itself, not only as a folder of its path.
It 'Should refuse an item that is a link, which can point outside the sandbox' {
$otherSandbox = New-TestSandbox -Name 'Helpers'
try {
$link = Join-Path -Path $sandbox -ChildPath 'NullDaclLink'
Assert-TestSandboxPath -Sandbox $sandbox -Path $link
New-Item -ItemType Junction -Path $link -Value $otherSandbox | Out-Null
$before = (Get-Acl -LiteralPath $otherSandbox).Sddl
{ Set-TestNullDacl -Sandbox $sandbox -Path $link } | Should -Throw -ExpectedMessage '*because it is a link*'
(Get-Acl -LiteralPath $otherSandbox).Sddl | Should -BeExactly $before
}
finally {
Remove-TestSandbox -Sandbox $otherSandbox
}
}
It 'Should throw its own error when Windows refuses' {
$missing = Join-Path -Path $sandbox -ChildPath 'Missing.txt'

11
Tests/TestHelpers.psm1

@ -364,6 +364,15 @@ function Set-TestNullDacl {
)
Assert-TestSandboxPath -Sandbox $Sandbox -Path $Path
$location = (Get-Location -PSProvider FileSystem).ProviderPath
$fullName = [IO.Path]::GetFullPath([IO.Path]::Combine($location, $Path))
# Assert-TestSandboxPath checks the folders of the path for links, not the item itself, and the native call follows a
# link: a junction to a folder outside the sandbox would give everyone every access to that folder.
$attributes = try { [IO.File]::GetAttributes($fullName) } catch { $null }
if ($null -ne $attributes -and ($attributes -band [IO.FileAttributes]::ReparsePoint)) {
throw "Refusing to change '$fullName', because it is a link."
}
if (-not ('NtfsSecurityTests.NativeAcl' -as [type])) {
Add-Type -TypeDefinition @'
namespace NtfsSecurityTests
@ -385,8 +394,6 @@ namespace NtfsSecurityTests
'@
}
$location = (Get-Location -PSProvider FileSystem).ProviderPath
$fullName = [IO.Path]::GetFullPath([IO.Path]::Combine($location, $Path))
$result = [NtfsSecurityTests.NativeAcl]::SetNullDacl($fullName)
if ($result -ne 0) {
throw "SetNamedSecurityInfo could not set a NULL DACL on '$fullName' (error $result)."

Loading…
Cancel
Save