Browse Source

test: describe the new tests by behavior, not by review finding

Remove the labels of review findings and the references to probe scripts
and reports that are not in the repository from the comments of the tests
that rc9 adds, and say what each test pins in words that stand alone. Only
comments and the name of one Context in each of two files change; no
assertion does.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
pull/131/head
Raimund Andree 1 week ago
parent
commit
68eab76867
  1. 6
      Tests/ItemCmdlets.Tests.ps1
  2. 8
      Tests/ObjectApis.Tests.ps1
  3. 53
      Tests/ParameterSets.Access.Tests.ps1
  4. 55
      Tests/ParameterSets.Audit.Tests.ps1
  5. 43
      Tests/PipelineControl.Tests.ps1

6
Tests/ItemCmdlets.Tests.ps1

@ -350,9 +350,9 @@ Describe 'Get-ChildItem2' {
# last dot before the dot token and the question mark account for the final segment. The 8.3-compliant
# names of the truth table above have at most one dot, where the first dot and the last dot are the same
# position, so they cannot tell the two rules apart; this fixture uses names with two or three dots
# instead, including the realistic ones from the WI. Expected values are pinned with the real name of each
# item, confirmed with `fsutil file setshortname` so that a short name the volume might otherwise assign
# cannot also produce a match.
# instead, including realistic ones such as archive.tar.gz and jquery.min.js. Expected values are pinned
# with the real name of each item, confirmed with `fsutil file setshortname` so that a short name the volume
# might otherwise assign cannot also produce a match.
It 'Should match -Filter "<Filter>" on a name with two or more dots the way Windows PowerShell 5.1 does' -ForEach @(
@{ Filter = '*.?'; Expected = @('a.b.c', 'v1.2.3', 'x.y.z.w') }
@{ Filter = '*.??'; Expected = @('a.b.c', 'a.b.cd', 'archive.tar.gz', 'index.d.ts', 'jquery.min.js', 'my.file.tx', 'v1.2.3', 'x.y.z.w') }

8
Tests/ObjectApis.Tests.ps1

@ -1026,9 +1026,10 @@ Describe 'The FileSystemSecurity2 conversions from FileSecurity and DirectorySec
}
}
# Review finding: PowerShell's binder uses the fixed conversion of 3.1 for every -SecurityDescriptor parameter, so a
# descriptor that Get-Acl returns, never meant for these cmdlets, now binds where it used to be a binding error. These
# pin the four cmdlets the review named; the one-line probes of the others are in the final report, not here.
# PowerShell's binder uses the conversion from FileSecurity and DirectorySecurity for every -SecurityDescriptor
# parameter, so a descriptor that Get-Acl returns, never meant for these cmdlets, binds where it used to be a binding
# error. The descriptor has no item and no audit section unless Get-Acl read one. These pin a reader of the access
# entries, a reader of the audit entries, the inheritance reader, and the writer.
Describe 'Cmdlets with a security descriptor that Get-Acl returns' {
BeforeAll {
$plainFile = New-TestSandboxItem -Sandbox $sandbox -Name 'GetAclPlain'
@ -1082,6 +1083,7 @@ Describe 'Cmdlets with a security descriptor that Get-Acl returns' {
(Get-Acl -LiteralPath $plainFile).Sddl | Should -BeExactly $before
}
}
Describe 'RemoveFileSystemAccessRuleAll and RemoveFileSystemAuditRuleAll honor the account list' {
BeforeAll {
$allow = [System.Security.AccessControl.AccessControlType]::Allow

53
Tests/ParameterSets.Access.Tests.ps1

@ -1384,7 +1384,7 @@ Describe 'Invalid and valid values of AccessType, InheritanceFlags, PropagationF
@{ Cmdlet = $cmdlet; Parameter = 'AppliesTo'; Value = 'Typoo' }
}
# R1-2: a named $null (a literal $null, a variable, an alias, splatting, or $PSDefaultParameterValues) must
# A named $null (a literal $null, a variable, an alias, splatting, or $PSDefaultParameterValues) must
# stay a terminating error, the same as a bad named value; only a $null that a piped object's property
# carries keeps the parameter's own default.
$accessNamedNullCases = foreach ($cmdlet in 'Add-NTFSAccess', 'Remove-NTFSAccess') {
@ -1421,10 +1421,10 @@ Describe 'Invalid and valid values of AccessType, InheritanceFlags, PropagationF
@{ Cmdlet = $cmdlet; Parameter = 'AppliesTo'; EnumTypeName = 'Security2.ApplyTo' }
}
# R1-4: a quoted or a wildcard word completed in rc8 (a typed parameter of an enumeration); the first
# EnumArgumentCompleters.cs returned nothing for either shape. Each ExpectedName has no sibling name that
# also starts with it (ThisFolderOnly has no ThisFolderOnlyOneLevel), so the unquoted and quoted forms match
# it alone; the wildcard only needs to contain it among the matches.
# A quoted or a wildcard word completed while these parameters were typed as enumerations, so the completers
# must offer the names for either shape too. Each ExpectedName has no sibling name that also starts with it
# (ThisFolderOnly has no ThisFolderOnlyOneLevel), so the unquoted and quoted forms match it alone; the
# wildcard only needs to contain it among the matches.
$accessCompletionFormCases = @(
@{ Parameter = 'AccessType'; Word = 'D'; ExpectedName = 'Deny' }
@{ Parameter = 'InheritanceFlags'; Word = 'Container'; ExpectedName = 'ContainerInherit' }
@ -1432,15 +1432,13 @@ Describe 'Invalid and valid values of AccessType, InheritanceFlags, PropagationF
@{ Parameter = 'AppliesTo'; Word = 'ThisFolderO'; ExpectedName = 'ThisFolderOnly' }
)
# R1-1: because the five parameters are object, PowerShell counts a $null, an empty string, or DBNull value
# of a property as present when it chooses the parameter set, the same as a real value, so AppliesTo (the
# Simple group) together with a $null/''/DBNull InheritanceFlags or PropagationFlags (the Complex group)
# offers both groups and is AmbiguousParameterSet -- not InvalidParameterValue, which is what the docs
# wrongly claimed for every shape. The reverse (AppliesTo blank, a Complex property valid) is
# AmbiguousParameterSet too, except that AppliesTo is Mandatory, so a literal $null of it (unlike '' or
# DBNull) is rejected earlier by the binder's own Mandatory check, unrelated to and unchanged by this
# finding. Covers the four cmdlets' Path and SD sets; the Audit half of the four cmdlets is pinned in
# ParameterSets.Audit.Tests.ps1.
# Because the five parameters are object, PowerShell counts a $null, an empty string, or DBNull value of a
# property as present when it chooses the parameter set, the same as a real value, so AppliesTo (the Simple
# group) together with a $null/''/DBNull InheritanceFlags or PropagationFlags (the Complex group) offers
# both groups and is AmbiguousParameterSet, not InvalidParameterValue. The reverse (AppliesTo blank, a
# Complex property valid) is AmbiguousParameterSet too, except that AppliesTo is Mandatory, so a literal
# $null of it (unlike '' or DBNull) is rejected earlier by the binder's own Mandatory check. Covers the
# Path and SD sets of the access cmdlets; the audit cmdlets are pinned in ParameterSets.Audit.Tests.ps1.
$accessAmbiguousOtherGroupCases = foreach ($cmdlet in 'Add-NTFSAccess', 'Remove-NTFSAccess') {
foreach ($targetKind in 'Path', 'SD') {
foreach ($shape in 'a $null', 'an empty string', 'DBNull') {
@ -1459,10 +1457,9 @@ Describe 'Invalid and valid values of AccessType, InheritanceFlags, PropagationF
}
}
# R1-3: with the object type, a script block is a plain value (rc8's delay-bind evaluation, once per piped
# object, no longer applies), so it fails the same way any other value of the wrong shape does: a
# terminating InvalidParameterValue from ValidateNamedValue in BeginProcessing, before any piped object is
# processed.
# With the object type, a script block is a plain value (a typed parameter evaluates it once per piped
# object), so it fails the same way any other value of the wrong shape does: a terminating
# InvalidParameterValue from ValidateNamedValue in BeginProcessing, before any piped object is processed.
$accessScriptBlockCases = foreach ($cmdlet in 'Add-NTFSAccess', 'Remove-NTFSAccess') {
@{ Cmdlet = $cmdlet; Parameter = 'AccessType' }
@{ Cmdlet = $cmdlet; Parameter = 'InheritanceFlags' }
@ -1671,9 +1668,9 @@ Describe 'Invalid and valid values of AccessType, InheritanceFlags, PropagationF
}
}
# R1-2: rc8 ended a named $null (for example a variable that misspells a property, such as -AccessType
# $rule.AccessTpye) with a terminating ParameterBindingFailed; the object-typed parameter accepted it and
# silently kept the default instead. A named $null is now reported the same way as a bad named value.
# A typed parameter ends a named $null (for example a variable that misspells a property, such as -AccessType
# $rule.AccessTpye) with a terminating ParameterBindingFailed; the object-typed parameter accepts it, so the
# cmdlet must not keep the default silently. A named $null is reported the same way as a bad named value.
Context 'A named $null is a terminating error' {
It 'Should throw a terminating error for a named $null of <Cmdlet> -<Parameter>, and change nothing' -ForEach $accessNamedNullCases {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'NamedNull' -Directory
@ -1789,10 +1786,10 @@ Describe 'Invalid and valid values of AccessType, InheritanceFlags, PropagationF
}
}
# R1-1: rc8 created the entry for every shape below; the object-typed parameters make a $null, an empty
# string, or DBNull value of a property of the other group count as present too, so PowerShell cannot resolve
# the parameter set and nothing is changed.
Context 'A blank value of the other groups property' {
# A typed parameter skips a $null, an empty string, or DBNull value of a property, and the entry was created for
# every shape below; the object-typed parameters count such a value of a property of the other group as present
# too, so PowerShell cannot resolve the parameter set and nothing is changed.
Context 'A blank value of a property of the other group' {
It 'Should reject <Cmdlet> (<TargetKind>) for <Direction> (<Shape>) with the error the binder gives, change nothing, and still process the next object' -ForEach $accessAmbiguousOtherGroupCases {
$bad = New-TestSandboxItem -Sandbox $sandbox -Name 'AmbiguousOtherGroupBad' -Directory
$next = New-TestSandboxItem -Sandbox $sandbox -Name 'AmbiguousOtherGroupNext' -Directory
@ -1826,9 +1823,9 @@ Describe 'Invalid and valid values of AccessType, InheritanceFlags, PropagationF
}
}
# R1-3: with the object type, a script block is a plain value (rc8's delay-bind evaluation, once per piped
# object, no longer applies), so ValidateNamedValue in BeginProcessing rejects it the same as any other bad
# value, before any piped object is processed.
# With the object type, a script block is a plain value (a typed parameter evaluates it once per piped object),
# so ValidateNamedValue in BeginProcessing rejects it the same as any other bad value, before any piped object
# is processed.
Context 'A named script block is a terminating error' {
It 'Should throw a terminating InvalidParameterValue error for a named script block value of <Cmdlet> -<Parameter>, and change nothing' -ForEach $accessScriptBlockCases {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'NamedScriptBlock' -Directory

55
Tests/ParameterSets.Audit.Tests.ps1

@ -1054,7 +1054,7 @@ Describe 'Invalid and valid values of AuditFlags, InheritanceFlags, PropagationF
@{ Cmdlet = $cmdlet; Parameter = 'AppliesTo'; Value = 'Typoo' }
}
# R1-2: a named $null (a literal $null, a variable, splatting, or $PSDefaultParameterValues) must stay a
# A named $null (a literal $null, a variable, splatting, or $PSDefaultParameterValues) must stay a
# terminating error, the same as a bad named value; only a $null that a piped object's property carries
# keeps the parameter's own default.
$auditNamedNullCases = foreach ($cmdlet in 'Add-NTFSAudit', 'Remove-NTFSAudit') {
@ -1093,22 +1093,20 @@ Describe 'Invalid and valid values of AuditFlags, InheritanceFlags, PropagationF
@{ Cmdlet = $cmdlet; Parameter = 'AppliesTo'; EnumTypeName = 'Security2.ApplyTo' }
}
# R1-4: AuditFlags is the one completer that ParameterSets.Access.Tests.ps1 does not already cover with the
# AuditFlags is the one completer that ParameterSets.Access.Tests.ps1 does not already cover with the
# quoted and wildcard forms (it shares the same completer code for InheritanceFlags, PropagationFlags, and
# AppliesTo, so those are pinned there).
$auditCompletionFormCases = @(
@{ Parameter = 'AuditFlags'; Word = 'F'; ExpectedName = 'Failure' }
)
# R1-1: because the five parameters are object, PowerShell counts a $null, an empty string, or DBNull value
# of a property as present when it chooses the parameter set, the same as a real value, so AppliesTo (the
# Simple group) together with a $null/''/DBNull InheritanceFlags or PropagationFlags (the Complex group)
# offers both groups and is AmbiguousParameterSet -- not InvalidParameterValue, which is what the docs
# wrongly claimed for every shape. The reverse (AppliesTo blank, a Complex property valid) is
# AmbiguousParameterSet too, except that AppliesTo is Mandatory, so a literal $null of it (unlike '' or
# DBNull) is rejected earlier by the binder's own Mandatory check, unrelated to and unchanged by this
# finding. Covers the four cmdlets' Path and SD sets; the Access half is pinned in
# ParameterSets.Access.Tests.ps1.
# Because the five parameters are object, PowerShell counts a $null, an empty string, or DBNull value of a
# property as present when it chooses the parameter set, the same as a real value, so AppliesTo (the Simple
# group) together with a $null/''/DBNull InheritanceFlags or PropagationFlags (the Complex group) offers
# both groups and is AmbiguousParameterSet, not InvalidParameterValue. The reverse (AppliesTo blank, a
# Complex property valid) is AmbiguousParameterSet too, except that AppliesTo is Mandatory, so a literal
# $null of it (unlike '' or DBNull) is rejected earlier by the binder's own Mandatory check. Covers the
# Path and SD sets of the audit cmdlets; the access cmdlets are pinned in ParameterSets.Access.Tests.ps1.
$auditAmbiguousOtherGroupCases = foreach ($cmdlet in 'Add-NTFSAudit', 'Remove-NTFSAudit') {
foreach ($targetKind in 'Path', 'SD') {
foreach ($shape in 'a $null', 'an empty string', 'DBNull') {
@ -1127,10 +1125,9 @@ Describe 'Invalid and valid values of AuditFlags, InheritanceFlags, PropagationF
}
}
# R1-3: with the object type, a script block is a plain value (rc8's delay-bind evaluation, once per piped
# object, no longer applies), so it fails the same way any other value of the wrong shape does: a
# terminating InvalidParameterValue from ValidateNamedValue in BeginProcessing, before any piped object is
# processed.
# With the object type, a script block is a plain value (a typed parameter evaluates it once per piped
# object), so it fails the same way any other value of the wrong shape does: a terminating
# InvalidParameterValue from ValidateNamedValue in BeginProcessing, before any piped object is processed.
$auditScriptBlockCases = foreach ($cmdlet in 'Add-NTFSAudit', 'Remove-NTFSAudit') {
@{ Cmdlet = $cmdlet; Parameter = 'AuditFlags' }
@{ Cmdlet = $cmdlet; Parameter = 'InheritanceFlags' }
@ -1349,8 +1346,9 @@ Describe 'Invalid and valid values of AuditFlags, InheritanceFlags, PropagationF
}
}
# R1-2: rc8 ended a named $null with a terminating ParameterBindingFailed; the object-typed parameter accepted
# it and silently kept the default instead. A named $null is now reported the same way as a bad named value.
# A typed parameter ends a named $null with a terminating ParameterBindingFailed; the object-typed parameter
# accepts it, so the cmdlet must not keep the default silently. A named $null is reported the same way as a bad
# named value.
Context 'A named $null is a terminating error' {
It 'Should throw a terminating error for a named $null of <Cmdlet> -<Parameter>, and change nothing' -ForEach $auditNamedNullCases {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'NamedNull' -Directory
@ -1445,13 +1443,13 @@ Describe 'Invalid and valid values of AuditFlags, InheritanceFlags, PropagationF
}
}
# R1-1: rc8 created the entry for every shape below; the object-typed parameters make a $null, an empty
# string, or DBNull value of a property of the other group count as present too, so PowerShell cannot resolve
# the parameter set and nothing is changed. The conversion/parameter-set failure happens before the cmdlet
# ever touches the SACL, so the core assertion (the error id, and that the bad object's audit entries are
# unchanged) runs in every configuration; only the "good next object succeeded" part needs the Security
# privilege, the same way the rest of this file does.
Context 'A blank value of the other groups property' {
# A typed parameter skips a $null, an empty string, or DBNull value of a property, and the entry was created for
# every shape below; the object-typed parameters count such a value of a property of the other group as present
# too, so PowerShell cannot resolve the parameter set and nothing is changed. The conversion/parameter-set
# failure happens before the cmdlet ever touches the SACL, so the core assertion (the error id, and that the bad
# object's audit entries are unchanged) runs in every configuration; only the "good next object succeeded" part
# needs the Security privilege, the same way the rest of this file does.
Context 'A blank value of a property of the other group' {
It 'Should reject <Cmdlet> (<TargetKind>) for <Direction> (<Shape>) with the error the binder gives, change nothing, and still process the next object' -ForEach $auditAmbiguousOtherGroupCases {
$bad = New-TestSandboxItem -Sandbox $sandbox -Name 'AmbiguousOtherGroupBad' -Directory
$next = New-TestSandboxItem -Sandbox $sandbox -Name 'AmbiguousOtherGroupNext' -Directory
@ -1492,11 +1490,10 @@ Describe 'Invalid and valid values of AuditFlags, InheritanceFlags, PropagationF
}
}
# R1-3: with the object type, a script block is a plain value (rc8's delay-bind evaluation, once per piped
# object, no longer applies), so ValidateNamedValue in BeginProcessing rejects it the same as any other bad
# value, before any piped object is processed and before the cmdlet ever touches the SACL. The terminating
# error itself needs no Security privilege; reading the SACL back to confirm nothing changed does, the same
# way the rest of this file does.
# With the object type, a script block is a plain value (a typed parameter evaluates it once per piped object),
# so ValidateNamedValue in BeginProcessing rejects it the same as any other bad value, before any piped object
# is processed and before the cmdlet ever touches the SACL. The terminating error itself needs no Security
# privilege; reading the SACL back to confirm nothing changed does, the same way the rest of this file does.
Context 'A named script block is a terminating error' {
It 'Should throw a terminating InvalidParameterValue error for a named script block value of <Cmdlet> -<Parameter>, and change nothing' -ForEach $auditScriptBlockCases {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'NamedScriptBlock' -Directory

43
Tests/PipelineControl.Tests.ps1

@ -637,33 +637,28 @@ namespace NtfsSecurityTests { public class StandInForBreak : System.Management.A
}
}
# InvokeAsOwner (and Set-NTFSSecurityDescriptor's WriteChangesAsOwner) take ownership, run the action, and in a
# InvokeAsOwner (and WriteChangesAsOwner of Set-NTFSSecurityDescriptor) take ownership, run the action, and in a
# finally restore the previous owner, writing a RestoreOwnerError through WriteError when that restore fails.
# WriteError runs the later commands of the pipeline and notes what they raise (IsFromLaterCommand, above); the catch
# around the InvokeAsOwner/WriteChangesAsOwner call must pass that on instead of reporting it as the item's own error
# and going on, or the caller never sees it. Each row denies the current user the right its own action needs (a
# change denial for the writers, a read denial on Everyone for Get-FileHash2, which needs file data, not
# permissions) and gives the item an owner that only the Restore privilege can set back, which the tests turn off;
# InvokeAsOwner/WriteChangesAsOwner then takes ownership, runs the action, and fails to restore the owner. Eight of
# the seventeen handlers of item 7 can be pinned this way. The scenario and the "without a later command" assertion
# already exist for six of them: ParameterSets.Inheritance.Tests.ps1 (Disable/Enable-NTFSAccessInheritance,
# Set-NTFSInheritance), PathErrors.Tests.ps1 (Clear-NTFSAccess -DisableInheritance), SecurityDescriptor.Tests.ps1
# (Set-NTFSSecurityDescriptor), FileHash.Tests.ps1 (Get-FileHash2); Add-NTFSAccess and Remove-NTFSAccess use the same
# scenario as the inheritance cmdlets. So this table only adds the later-command assertion, not a duplicate of the
# existing one.
# around the call must pass that on instead of reporting it as the item's own error and going on, or the caller never
# sees it. Each row denies the current user the right its own action needs (a change denial for the writers, a read
# denial on Everyone for Get-FileHash2, which needs file data, not permissions) and gives the item an owner that only
# the Restore privilege can set back, which the tests turn off; the cmdlet then takes ownership, runs the action, and
# fails to restore the owner. Eight of the seventeen handlers can be pinned this way. The scenario and the "without a
# later command" assertion already exist for most of them in the test files of their cmdlets
# (ParameterSets.Inheritance.Tests.ps1, PathErrors.Tests.ps1, SecurityDescriptor.Tests.ps1, FileHash.Tests.ps1), and
# Add-NTFSAccess and Remove-NTFSAccess use the scenario of the inheritance cmdlets, so this table only adds the
# later-command assertion.
#
# NOT DONE for the other nine (the reason is the same Windows rule in both groups: taking ownership only bypasses a
# denial of ReadPermissions/ChangePermissions, never of a different right): Get-NTFSAccess, Get-NTFSOrphanedAccess,
# Get-NTFSEffectiveAccess, and Get-NTFSInheritance read the security descriptor; InvokeAsOwner's own first statement,
# FileSystemOwner.GetOwner, reads it too and needs the same ReadPermissions right the cmdlet's read needs, so a
# denial that fails one fails the other and InvokeAsOwner never reaches the owner-restore step (verified: denying
# ReadPermissions directly to the user reports ReadSecurityError with the plain UnauthorizedAccessException, not
# RestoreOwnerError, and the owner is never taken; probes-wi2\p-restoreowner-read-vs-write.ps1,
# p-read-cmdlets-2.ps1). Clear-NTFSAudit, Disable-NTFSAuditInheritance, Enable-NTFSAuditInheritance, Add-NTFSAudit, and
# Remove-NTFSAudit write the SACL, which Windows grants to this elevated session regardless of a DACL deny and
# regardless of the module's EnablePrivileges setting, so the action does not fail to begin with (verified with both
# a ChangePermissions deny and EnablePrivileges off: no error, the SACL is written, and the owner is left unchanged;
# probes-wi2\p-audit-restoreowner.ps1, p-audit-restoreowner-2.ps1). The handlers of all seventeen carry the check.
# The other nine have no row, for one Windows rule: taking ownership only bypasses a denial of
# ReadPermissions/ChangePermissions, never of a different right. Get-NTFSAccess, Get-NTFSOrphanedAccess,
# Get-NTFSEffectiveAccess, and Get-NTFSInheritance read the security descriptor, and so does the first statement of
# InvokeAsOwner (FileSystemOwner.GetOwner), which needs the same ReadPermissions right: a denial that fails one fails
# the other, so the owner-restore step is never reached (denying ReadPermissions reports a ReadSecurityError with the
# plain UnauthorizedAccessException, and the owner is never taken). Clear-NTFSAudit, Disable-NTFSAuditInheritance,
# Enable-NTFSAuditInheritance, Add-NTFSAudit, and Remove-NTFSAudit write the SACL, which Windows grants to this
# elevated session whatever the DACL says and whatever the EnablePrivileges setting is, so the action does not fail
# to begin with (the SACL is written and the owner is left unchanged). The handlers of all seventeen carry the check.
Describe 'A later command that throws at the RestoreOwnerError record' {
BeforeDiscovery {
$holdsRestorePrivilege = Test-PrivilegeHeld -Name 'SeRestorePrivilege'

Loading…
Cancel
Save