Test/99_CIMRegistry_CommonTests.ps1
# This is a Pester test file # Test general behaviour, such as check of Path and network parameters, and network CIM calls. # The test uses general Mocks from the Main test. All other Mocks defined locally. Param ( # Name of a CIMRegistry module function to test [Parameter(Mandatory=$true)] $FunctionName, [hashtable] $ExtraParams = @{}, # What function retuns if Path doesn't exists [ValidateSet("Null", "Error", "NA")] $WrongPathReturn, # Check network info in output object or not (Remove-* function have no output) [switch] $CheckOutput ) # Check common properties of output object ? # Pre-cr session goes throug pipeline (InstanceID) Describe "Common tests for $FunctionName" { #region Mocks # VALUES: We simulate only one type of values - REG_SZ. It's enough to test common logic of the functions. # Parent key $MockKeyParentName = 'SOFTWARE\Discworld\Ramtop' Mock Test-cdxmlRegistryKeyAccess { [PSCustomObject]@{ReturnValue = 0; bGranted = $true} } { $PSBoundParameters.Key -eq $MockKeyParentName } # Child key $MockKeyName = 'SOFTWARE\Discworld\Ramtops\Lancre' $MockKeyPath = 'HKEY_LOCAL_MACHINE\' + $MockKeyName $MockSubkeyList = [PSCustomObject]@{ReturnValue = 0; sNames = @('Castle')} $MockValueList = [PSCustomObject]@{ReturnValue = 0; sNames = @('King'); Types = @(1)} $MockValue = [PSCustomObject]@{ReturnValue = 0; sValue = 'Verence'} Mock Test-cdxmlRegistryKeyAccess { [PSCustomObject]@{ReturnValue = 0; bGranted = $true} } { $PSBoundParameters.Key -eq $MockKeyName } Mock Get-cdxmlSubkeyName { $MockSubkeyList } { $PSBoundParameters.Key -eq $MockKeyName } Mock Get-cdxmlValueName { $MockValueList } { $PSBoundParameters.Key -eq $MockKeyName } Mock Get-cdxmlStringValue { $MockValue } { $PSBoundParameters.Key -eq $MockKeyName } Mock Set-cdxmlStringValue { [PSCustomObject]@{ReturnValue = 0} } { $PSBoundParameters.Key -eq $MockKeyName } Mock New-cdxmlRegistryKey { [PSCustomObject]@{ReturnValue = 0} } { $PSBoundParameters.Key -eq $MockKeyName } Mock Remove-cdxmlRegistryKey { [PSCustomObject]@{ReturnValue = 0} } { $PSBoundParameters.Key -eq $MockKeyName } Mock Remove-cdxmlRegistryValue { [PSCustomObject]@{ReturnValue = 0} } { $PSBoundParameters.Key -eq $MockKeyName } # No access key $MockNoAccessKeyName = 'SOFTWARE\Discworld\Ramtops\Cori Celesti' $MockNoAccessKeyPath = 'HKEY_LOCAL_MACHINE\' + $MockNoAccessKeyName Mock Test-cdxmlRegistryKeyAccess { [PSCustomObject]@{ReturnValue = 5; bGranted = $false} } { $PSBoundParameters.Key -eq $NoSuchKeyName } # Non-existing key mock $NoSuchKeyName = 'SOFTWARE\NoSuchKey' $NoSuchKeyPath = 'HKEY_LOCAL_MACHINE\' + $NoSuchKeyName Mock Test-cdxmlRegistryKeyAccess { [PSCustomObject]@{ReturnValue = 2; bGranted = $false} } { $PSBoundParameters.Key -eq $NoSuchKeyName } # #endregion $TestedCommand = Get-Command $FunctionName Context 'Input' { It 'Parameter Path must be Mandatory ' { foreach ($prm in ($TestedCommand.Parameters['Path'].Attributes.Mandatory)) { $prm | Should Be $true } } It 'Throws if Path syntax is not correct' { { & $FunctionName -Path '*HKEY_LOCAL_MACHINE\SOFTWARE\Discworld\Ramtop' -ErrorAction Stop @ExtraParams } | Should Throw 'is not a valid registry path' } It 'Throws if RootKey is not correct' { { & $FunctionName -Path 'NO_SUCH_ROOTKEY\SOFTWARE' -ErrorAction Stop @ExtraParams } | Should Throw 'is not a valid registry root key' } It 'Accepts short RootKey name' { { & $FunctionName -Path ($MockKeyPath -replace 'HKEY_LOCAL_MACHINE', 'HKLM') -ErrorAction Stop @ExtraParams } | Should -Not -Throw } It "Returns $WrongPathReturn if Path does not exist" { If ($WrongPathReturn -eq 'Error') { {& $FunctionName -Path $NoSuchKeyPath -ErrorAction Stop @ExtraParams } | Should Throw 'Registry key does not exist' } If ($WrongPathReturn -eq 'Null') { & $FunctionName -Path $NoSuchKeyPath @ExtraParams | Should -BeNullOrEmpty } } It 'Throws if Protocol argument is not Dcom or Wsman' { {& $FunctionName -Path $MockKeyPath -ComputerName $RemoteCompName -Protocol HTTPS @ExtraParams} | Should Throw "Cannot validate argument on parameter 'Protocol'" } It 'Throws if there are both Cimsession and ComputerName' { {& $FunctionName -Path $MockKeyPath -ComputerName $RemoteCompName -CimSession $ExistingCimSession @ExtraParams} | Should Throw 'Parameter set cannot be resolved' } } Context 'Local Execution' { $LocalResult = & $FunctionName -Path $MockKeyPath @ExtraParams It 'Does not call New-CimSession to access local registry ' { Assert-MockCalled New-CimSession -Times 0 -Exactly } If ($CheckOutput) { It 'ComputerName is localhost' { $LocalResult.PSComputerName | Should -Be 'localhost' } It 'CimSessionId is empty' { $LocalResult.CimSessionId | Should -BeNullOrEmpty } } } # 'Local Execution' Context 'Network Execution' { Context 'Remote Execution with Different protocols WSMAN/DCOM' { It 'Calls New-CimSession with WSMAN options' { & $FunctionName -Path $MockKeyPath -ComputerName $RemoteCompName -Protocol WSMAN @ExtraParams Assert-MockCalled New-CimSession -ParameterFilter {($PSBoundParameters.SessionOption).GetType().Name -eq 'WSManSessionOptions'} } It 'Calls New-CimSession with DCOM options' { & $FunctionName -Path $MockKeyPath -ComputerName $RemoteCompName -Protocol Dcom @ExtraParams Assert-MockCalled New-CimSession -ParameterFilter {($PSBoundParameters.SessionOption).GetType().Name -eq 'DComSessionOptions'} } } Context 'Remote Execution with temporary CIMsession' { $RemoteResult = & $FunctionName -Path $MockKeyPath -ComputerName $RemoteCompName @ExtraParams It 'Calls New-CimSession with the proper ComputerName parameter' { Assert-MockCalled New-CimSession -Times 1 -Exactly -ParameterFilter {$PSBoundParameters.ComputerName -eq $RemoteCompName} } It 'Removes a temporary CIMsession' { Assert-MockCalled Remove-CimSession -Times 1 -Exactly } If ($CheckOutput) { It 'ComputerName is correct' { $RemoteResult.PSComputerName | Should -Be $RemoteCompName } It 'CimSessionId is empty' { $RemoteResult.CimSessionId | Should -BeNullOrEmpty } } } Context 'Remote Execution with pre-existing CIMsession' { $RemoteSesResult = & $FunctionName -Path $MockKeyPath -CimSession $ExistingCimSession @ExtraParams It 'Does not call New-CimSession' { Assert-MockCalled New-CimSession -Times 0 -Exactly } It 'Leaves alone external CIMsession' { Assert-MockCalled Remove-CimSession -Times 0 -Exactly } If ($CheckOutput) { It 'ComputerName is correct' { $RemoteSesResult.PSComputerName | Should -Be $ExistingCimSession.ComputerName } It 'CimSessionId is correct' { $RemoteSesResult.CimSessionId | Should -Be $ExistingCimSession.InstanceId } } } Context 'Remote Execution with automatically created CIMsession' { # The name of a computer was passed to CimSession parameter. $RemoteSesResult = & $FunctionName -Path $MockKeyPath -CimSession $RemoteCompName @ExtraParams It 'Does not call New-CimSession' { Assert-MockCalled New-CimSession -Times 0 -Exactly } It 'Does not call Remove-CimSession' { Assert-MockCalled Remove-CimSession -Times 0 -Exactly } If ($CheckOutput) { It 'ComputerName is correct' { $RemoteSesResult.PSComputerName | Should -Be $RemoteCompName } It 'CimSessionId is empty' { $RemoteSesResult.CimSessionId | Should -BeNullOrEmpty } } } } # 'Network Execution' Context 'Pipeline parameter bindings' { Context 'Local only query' { Context 'Native object type' { $NativeResult = Get-RegistryKey -Path $MockKeyPath | & $FunctionName @ExtraParams It 'Does not call New-CimSession' { Assert-MockCalled New-CimSession -Times 0 -Exactly } It 'Does not call Remove-CIMsession' { Assert-MockCalled Remove-CimSession -Times 0 -Exactly } If ($CheckOutput) { It 'ComputerName is localhost' { $NativeResult.PSComputerName | Should -Be 'localhost' } It 'CimSessionId is empty' { $NativeResult.CimSessionId | Should -BeNullOrEmpty } } } # 'Native object type' Context 'PSCustomObject type' { $PSCustomTempObject = Get-RegistryKey -Path $MockKeyPath | ConvertTo-Csv -NoTypeInformation | ConvertFrom-Csv $PSCustomResult = $PSCustomTempObject | & $FunctionName @ExtraParams It 'Does not call New-CimSession' { Assert-MockCalled New-CimSession -Times 0 -Exactly } It 'Does not call Remove-CIMsession' { Assert-MockCalled Remove-CimSession -Times 0 -Exactly } If ($CheckOutput) { It 'ComputerName is localhost' { $PSCustomResult.PSComputerName | Should -Be 'localhost' } It 'CimSessionId is empty' { $PSCustomResult.CimSessionId | Should -BeNullOrEmpty } } } # 'Native object type' } #'Local query' Context 'Temporary CIMSession' { Context 'Native object type' { $NativeResult = Get-RegistryKey -Path $MockKeyPath -ComputerName $RemoteCompName -Protocol Wsman | & $FunctionName @ExtraParams It 'Does not call New-CimSession' { Assert-MockCalled New-CimSession -Times 2 -Exactly } It 'Does not call Remove-CIMsession' { Assert-MockCalled Remove-CimSession -Times 2 -Exactly } If ($CheckOutput) { It 'ComputerName is correct' { $NativeResult.PSComputerName | Should -Be $RemoteCompName } It 'CimSessionId is empty' { $NativeResult.CimSessionId | Should -BeNullOrEmpty } It 'Protocol is correct' { $NativeResult.Protocol | Should -Be 'Wsman' } } } # 'Native object type' Context 'PSCustomObject type' { $PSCustomTempObject = Get-RegistryKey -Path $MockKeyPath -ComputerName $RemoteCompName -Protocol Wsman | ConvertTo-Csv -NoTypeInformation | ConvertFrom-Csv $PSCustomResult = $PSCustomTempObject | & $FunctionName -Protocol Dcom @ExtraParams It 'Does not call New-CimSession' { Assert-MockCalled New-CimSession -Times 2 -Exactly } It 'Does not call Remove-CIMsession' { Assert-MockCalled Remove-CimSession -Times 2 -Exactly } If ($CheckOutput) { It 'ComputerName is correct' { $PSCustomResult.PSComputerName | Should -Be $RemoteCompName } It 'CimSessionId is empty' { $PSCustomResult.CimSessionId | Should -BeNullOrEmpty } It 'Protocol was changed correctly' { $PSCustomResult.Protocol | Should -Be 'Dcom' } } } # 'Native object type' } # 'Temporary CIMSession' Context 'Pre-existing CIMsession' { Context 'Native object type' { $NativeResult = Get-RegistryKey -Path $MockKeyPath -CimSession $ExistingCimSession | & $FunctionName @ExtraParams It 'Does not call New-CimSession' { Assert-MockCalled New-CimSession -Times 0 -Exactly } It 'Does not call Remove-CIMsession' { Assert-MockCalled Remove-CimSession -Times 0 -Exactly } If ($CheckOutput) { It 'ComputerName is correct' { $NativeResult.PSComputerName | Should -Be $ExistingCimSession.ComputerName } It 'CimSessionId is correct' { $NativeResult.CimSessionId | Should -Be $ExistingCimSession.InstanceId } } } # 'Native object type' Context 'PSCustomObject type' { # CimSessionId gets exported BUT not binded from PSCustomObject (there is no such parameter in the functions) as the result - temporary session down on the pipe. $PSCustomTempObject = Get-RegistryKey -Path $MockKeyPath -CimSession $ExistingCimSession | ConvertTo-Csv -NoTypeInformation | ConvertFrom-Csv $PSCustomResult = $PSCustomTempObject | & $FunctionName @ExtraParams It 'Does not call New-CimSession' { Assert-MockCalled New-CimSession -Times 1 -Exactly } It 'Does not call Remove-CIMsession' { Assert-MockCalled Remove-CimSession -Times 1 -Exactly } If ($CheckOutput) { It 'ComputerName is correct' { $PSCustomResult.PSComputerName | Should -Be $ExistingCimSession.ComputerName } It 'CimSessionId is empty' { $PSCustomResult.CimSessionId | Should -BeNullOrEmpty } } } # 'Native object type' } # 'Pre-existing CIMsession' } # 'Pipeline parameter bindings' <# Context 'Pipeline errors handling' { $ThreeKeys = 1..3 | ForEach-Object {Get-RegistryKey -Path $MockKeyPath} $ThreeKeys[1].Path = $MockNoAccessKeyPath $PartialResult = $ThreeKeys | & $FunctionName -ErrorAction Stop @ExtraParams It 'Continues pipeline operations after a WMI error' { $PartialResult = $ThreeKeys | & $FunctionName @ExtraParams Assert-MockCalled Test-cdxmlRegistryKeyAccess -Times 3 -Exactly -Scope It } -Skip If ($CheckOutput) { It 'Skips an error object returns with the rest' { $PartialResult.Count | Should -Be 2 } } } #> } |