Test/03_CIMRegistry_Get_Functions.ps1

# This is a Pester test file

##### Get Functions #### (the most complex and crucial functions)

Describe 'Get functions' {

#region Mocks (used only by Get Functions test)
    # Pseudo Registry decription:
    <#
     HKLM
     ├───SOFTWARE\TestInc
     │ ├───Reechani
     │ │ └───Aqua
     │ ├───Sentrosi
     │ │ └───Ignis
     │ └───Vasi
     │ └───Aer
     │
     ├───SOFTWARE\Microsoft\EmptyKey
     │
     ├───SOFTWARE\Microsoft\HiddenDefaultValueKey
     |
     └───SOFTWARE\Microsoft\InvalidValueKey
 
 
    1) TestInc key has (default) value and six registry values = 7 ($MockKeyName)
    2) Subkeys: Reechani, Sentrosi and Vasi have one value name: 'MagicType', string. Data of the values: Chime1, Chime2, Chime3 accordingly.
    3) Subkeys: Reechani, Sentrosi and Vasi contain one subkey each: Aqua, Ignis, Aer accordingly.
    4) Sub-subkeys: Aqua, Ignis, Aer have one Default value each. Data of the valus: 'Water', 'Fire', 'Air' accordingly.
 
    5) EmptyKey has no subkeys and values ($MockEmptyKeyName)
    6) HiddenDefaultValueKey ($HiddenValueKeyName) contains only the Default value that is not enumerated by WMI, string: 'Temple of the Winds'
       (it was the only value that was ever created in the key)
 
    #>


    ## 1) Mock registry key with data.
        # Subkey list

            $MockKeyName = 'SOFTWARE\TestInc'
            $MockKeyPath = 'HKEY_LOCAL_MACHINE\' + $MockKeyName

            $MockSubKeyNameL1 = @('Sentrosi','Vasi','Reechani')
            $MockSubKeyNameL2 = @('Aqua','Ignis','Aer')

            Mock Get-cdxmlSubkeyName {[PSCustomObject]@{ ReturnValue = 0; sNames = @('Sentrosi','Vasi','Reechani')} } { $PSBoundParameters.Key -eq $MockKeyName }
            Mock Get-cdxmlSubkeyName {[PSCustomObject]@{ ReturnValue = 0; sNames = @('Aqua')} }                       { $PSBoundParameters.Key -eq 'SOFTWARE\TestInc\Reechani' }
            Mock Get-cdxmlSubkeyName {[PSCustomObject]@{ ReturnValue = 0; sNames = @('Ignis')} }                      { $PSBoundParameters.Key -eq 'SOFTWARE\TestInc\Sentrosi' }
            Mock Get-cdxmlSubkeyName {[PSCustomObject]@{ ReturnValue = 0; sNames = @('Aer')} }                        { $PSBoundParameters.Key -eq 'SOFTWARE\TestInc\Vasi' }
            Mock Get-cdxmlSubkeyName {[PSCustomObject]@{ ReturnValue = 0; sNames = @()} } {$PSBoundParameters.Key -in @('SOFTWARE\TestInc\Reechani\Aqua',
                'SOFTWARE\TestInc\Sentrosi\Ignis',
                'SOFTWARE\TestInc\Vasi\Aer')
            }

            Mock Test-cdxmlRegistryKeyAccess  { [PSCustomObject]@{ReturnValue = 0; bGranted = $true} } # { $PSBoundParameters.Key -eq $NormalNewKeyName }

       # Value-Type list
            $MockValueList = [PSCustomObject]@{
                ReturnValue = 0
                sNames = @('','String', 'ExpString', 'Bin', 'Dword', 'MultiString','Qword')
                Types =  @(1,1,2,3,4,7,11)
            }
            Mock Get-cdxmlValueName { $MockValueList } { $PSBoundParameters.Key -eq $MockKeyName }
            Mock Get-cdxmlValueName { [PSCustomObject]@{ReturnValue = 0; sNames = @('MagicType'); Types =  @(1)} } {
                $PSBoundParameters.Key -in @('SOFTWARE\TestInc\Reechani',
                                             'SOFTWARE\TestInc\Sentrosi',
                                             'SOFTWARE\TestInc\Vasi')
            }

            Mock Get-cdxmlValueName { [PSCustomObject]@{ReturnValue = 0; sNames = @(''); Types =  @(1)} } {
                $PSBoundParameters.Key -in @('SOFTWARE\TestInc\Reechani\Aqua',
                                             'SOFTWARE\TestInc\Sentrosi\Ignis',
                                             'SOFTWARE\TestInc\Vasi\Aer')
            }


        # Values
            # Level 1
            $MockBinData = @([Byte]'65',[Byte]'66',[Byte]'67')
            $MockMulStrDt = @('Eins', 'Zwei', 'Drei')
            Mock Get-cdxmlStringValue {[PSCustomObject]@{ReturnValue = 0; sValue = 'DefaultData'}} {($PSBoundParameters.Key -eq $MockKeyName) -and ($PSBoundParameters.ValueName -eq '')}
            Mock Get-cdxmlStringValue         {[PSCustomObject]@{ReturnValue = 0; sValue = 'StringData'}}    {($PSBoundParameters.ValueName -eq 'String')}
            Mock Get-cdxmlExpandedStringValue {[PSCustomObject]@{ReturnValue = 0; sValue = 'ExpStringData'}} {($PSBoundParameters.ValueName -eq 'ExpString')}
            Mock Get-cdxmlBinaryValue         {[PSCustomObject]@{ReturnValue = 0; uValue = $MockBinData}}    {($PSBoundParameters.ValueName -eq 'Bin')}
            Mock Get-cdxmlDWORDValue          {[PSCustomObject]@{ReturnValue = 0; uValue = [UInt32]123}}     {($PSBoundParameters.ValueName -eq 'Dword')}
            Mock Get-cdxmlMultiStringValue    {[PSCustomObject]@{ReturnValue = 0; sValue = $MockMulStrDt}}   {($PSBoundParameters.ValueName -eq 'MultiString')}
            Mock Get-cdxmlQWORDValue          {[PSCustomObject]@{ReturnValue = 0; uValue = [UInt64]456}}     {($PSBoundParameters.ValueName -eq 'Qword')}

            # Level 2
            Mock Get-cdxmlStringValue {[PSCustomObject]@{ReturnValue = 0; sValue = 'Chime1'}}  {($PSBoundParameters.Key -eq "$MockKeyName\Reechani") -and ($PSBoundParameters.ValueName -eq 'MagicType')}
            Mock Get-cdxmlStringValue {[PSCustomObject]@{ReturnValue = 0; sValue = 'Chime2'}}  {($PSBoundParameters.Key -eq "$MockKeyName\Sentrosi") -and ($PSBoundParameters.ValueName -eq 'MagicType')}
            Mock Get-cdxmlStringValue {[PSCustomObject]@{ReturnValue = 0; sValue = 'Chime3'}}  {($PSBoundParameters.Key -eq "$MockKeyName\Vasi")     -and ($PSBoundParameters.ValueName -eq 'MagicType')}


            # Level 3
            Mock Get-cdxmlStringValue {[PSCustomObject]@{ReturnValue = 0; sValue = 'Water'}}       {($PSBoundParameters.Key -eq "$MockKeyName\Reechani\Aqua")  -and ($PSBoundParameters.ValueName -eq '')}
            Mock Get-cdxmlStringValue {[PSCustomObject]@{ReturnValue = 0; sValue = 'Fire'}}        {($PSBoundParameters.Key -eq "$MockKeyName\Sentrosi\Ignis") -and ($PSBoundParameters.ValueName -eq '')}
            Mock Get-cdxmlStringValue {[PSCustomObject]@{ReturnValue = 0; sValue = 'Air'}}         {($PSBoundParameters.Key -eq "$MockKeyName\Vasi\Aer")       -and ($PSBoundParameters.ValueName -eq '')}


    ## 2) Mock empty key
         # Subkey list
         $MockEmptyKeyName = 'SOFTWARE\Microsoft\EmptyKey'
         $MockEmptyKeyPath = 'HKEY_LOCAL_MACHINE\' + $MockEmptyKeyName
         Mock Get-cdxmlSubkeyName  {[PSCustomObject]@{ReturnValue = 0; sNames = $null}}                {$PSBoundParameters.Key -eq $MockEmptyKeyName}
         Mock Get-cdxmlValueName   {[PSCustomObject]@{ReturnValue = 0; sNames = $null; Types = $null}} {$PSBoundParameters.Key -eq $MockEmptyKeyName}
         # Value not found
         Mock Get-cdxmlStringValue {[PSCustomObject]@{ReturnValue = 1; sValue = $null}}                {$PSBoundParameters.Key -eq $MockEmptyKeyName}

    ## 3) Mock key with WMI hidden default value.
         $HiddenValueKeyName = 'SOFTWARE\Microsoft\HiddenDefaultValueKey'
         $HiddenValueKeyPath = 'HKEY_LOCAL_MACHINE\' + $HiddenValueKeyName
         Mock Get-cdxmlSubkeyName {[PSCustomObject]@{ReturnValue = 0; sNames = $null}}                 {$PSBoundParameters.Key -eq $HiddenValueKeyName}
         # Default value not enumerated
         Mock Get-cdxmlValueName  {[PSCustomObject]@{ReturnValue = 0; sNames = $null; Types = $null}}  {$PSBoundParameters.Key -eq $HiddenValueKeyName}
         # But it can be read directly
         Mock Get-cdxmlStringValue         {[PSCustomObject]@{ReturnValue = 0; sValue = 'Temple of the Winds'}}  {$PSBoundParameters.Key -eq $HiddenValueKeyName}
         # Mock Get-cdxmlExpandedStringValue {[PSCustomObject]@{ReturnValue = 2147749893; sValue = $null}} {$PSBoundParameters.Key -eq $HiddenValueKeyName}
         # Mock Get-cdxmlBinaryValue {[PSCustomObject]@{ReturnValue = 2147749893; uValue = $null}} {$PSBoundParameters.Key -eq $HiddenValueKeyName}
         # Mock Get-cdxmlDWORDValue {[PSCustomObject]@{ReturnValue = 2147749893; uValue = $null}} {$PSBoundParameters.Key -eq $HiddenValueKeyName}
         # Mock Get-cdxmlMultiStringValue {[PSCustomObject]@{ReturnValue = 2147749893; sValue = $null}} {$PSBoundParameters.Key -eq $HiddenValueKeyName}
         # Mock Get-cdxmlQWORDValue {[PSCustomObject]@{ReturnValue = 2147749893; uValue = $null}} {$PSBoundParameters.Key -eq $HiddenValueKeyName}

    ## 4) Mock key with an invalid value
         $InvalidValueKeyName = 'SOFTWARE\Microsoft\InvalidValueKey'
         $InvalidValueKeyPath = 'HKEY_LOCAL_MACHINE\' + $InvalidValueKeyName
         Mock Get-cdxmlValueName  {[PSCustomObject]@{ReturnValue = 0; sNames = @('BinData'); Types = @(4)}}  {$PSBoundParameters.Key -eq $InvalidValueKeyName}
         Mock Get-cdxmlDWORDValue {[PSCustomObject]@{ReturnValue = 1; uValue = $null}}  {$PSBoundParameters.Key -eq $InvalidValueKeyName}

#endregion
    #
    Context 'Get-RegistryKey' {

        $NormalKeyResult = Get-RegistryKey -Path $MockKeyPath

        It 'Calls Test-cdxmlRegistryKeyAccess function once' {
            Assert-MockCalled Test-cdxmlRegistryKeyAccess -Times 1 -Exactly
        }

        It 'Calls Get-cdxmlSubkeyName cdxml function once' {
            Assert-MockCalled Get-cdxmlSubkeyName -Times 1 -Exactly
        }

        It 'Calls Get-cdxmlValueName cdxml function once' {
            Assert-MockCalled Get-cdxmlValueName -Times 1 -Exactly
        }

        It 'Returns objects of custom CIMRegistryKey type' {
            $NormalKeyResult.GetType().Fullname | Should -Be 'CIMRegistryKey'
        }

        It 'Path is correct' {
           $NormalKeyResult.Path | Should -Be $MockKeyPath
        }

        It 'Returns proper subkeys counter' {
            $NormalKeyResult.SubKeyCount | Should -Be 3
        }

        It 'Returns proper values counter' {
            $NormalKeyResult.ValueCount | Should -Be 7
        }

        It 'Returns no Default value by default' {
            $NormalKeyResult.DefaultValue | Should -BeNullOrEmpty
        }

        It 'Returns Default value on demand' {
            $DefaultValueResult = Get-RegistryKey -Path $MockKeyPath -GetDefaultValue
            $DefaultValueResult.DefaultValue | Should -Be 'DefaultData'
        }

        It 'Returns no Default value if it is hidden' {
            $HiddenValueResult = Get-RegistryKey -Path $HiddenValueKeyPath -GetDefaultValue
            $HiddenValueResult.DefaultValue | Should -BeNullOrEmpty
        }

        It 'Returns hidden Default value if enforced' {
            $HiddenValueResult = Get-RegistryKey -Path $HiddenValueKeyPath -GetDefaultValue -Force
            $HiddenValueResult.DefaultValue | Should -Be 'Temple of the Winds'
        }

        Context "Call common tests" {
            & $PSScriptRoot\99_CIMRegistry_CommonTests.ps1 -FunctionName Get-RegistryKey -WrongPathReturn Null -CheckOutput
        }

    } # Context Get-RegistryKey

    Context 'Get-RegistrySubkey' {

        Context 'Normal registry key' {
            $NormalKeyResult = Get-RegistrySubkey -Path $MockKeyPath

            It 'Calls Get-cdxmlSubkeyName cdxml function once' {
                Assert-MockCalled Get-cdxmlSubkeyName -Times 1 -Exactly
            }

            It 'Returns objects of custom CIMRegistryKey type' {
                $NormalKeyResult[0].GetType().Fullname | Should -Be 'CIMRegistryKey'
            }

            It 'Returns proper number of subkeys' {
                $NormalKeyResult.Count | Should -Be 3
            }

            It 'SubKey names are correct' {
                $_tmpSubKeysNames = $MockSubKeyNameL1 | Sort-Object
                foreach ($i in (0..($NormalKeyResult.Count - 1))) {
                    ($NormalKeyResult.Key)[$i] | Should -Be $_tmpSubKeysNames[$i]
                }
            }

            It 'ParentKey is correct' {
                $NormalKeyResult  | ForEach-Object {
                    $_.ParentKey | Should -Be $MockKeyPath
                }
            }

            It 'Path is correct' {
               $NormalKeyResult  | foreach {
                    $_.Path | Should -Be ($_.ParentKey + '\' +  $_.Key)
                }
            }
        }

        Context 'Empty registry key' {
            $EmptyKeyResult = Get-RegistrySubkey -Path $MockEmptyKeyPath

            It 'Calls Get-cdxmlSubkeyName cdxml function once' {
                Assert-MockCalled Get-cdxmlSubkeyName -Times 1 -Exactly
            }

            It 'Returns nothing if a key has no subkeys' {
                $EmptyKeyResult | Should -Be $null
            }
        }

        Context 'Fitering' {
            It 'Filtering with wildcards' {
                (Get-RegistrySubkey -Path $MockKeyPath -SubkeyName '*si').Count | Should -Be 2
            }

            It 'Filtering with specific single name' {
                (Get-RegistrySubkey -Path $MockKeyPath -SubkeyName Vasi).Count | Should -Be 1
            }

        }

        Context "Call common tests" {
            & $PSScriptRoot\99_CIMRegistry_CommonTests.ps1 -FunctionName Get-RegistrySubkey -WrongPathReturn Error -CheckOutput
        }

    } # Context 'Get-RegistrySubkey'

    Context 'Get-RegistryValue' {

        Context 'Normal registry key' {
            $NormalKeyResult = Get-RegistryValue -Path $MockKeyPath

            It 'Calls Get-Get-cdxmlValueName cdxml function once' {
                Assert-MockCalled Get-cdxmlValueName -Times 1 -Exactly
            }

             It 'Calls Get-cdxmlStringValue cdxml function twice' {
                Assert-MockCalled Get-cdxmlStringValue -Times 2 -Exactly
            }

              It 'Calls Get-Get-cdxmlExpandedStringValue cdxml function once' {
                Assert-MockCalled Get-cdxmlExpandedStringValue -Times 1 -Exactly
            }

             It 'Calls Get-cdxmlBinaryValue cdxml function once' {
                Assert-MockCalled Get-cdxmlBinaryValue -Times 1 -Exactly
            }

             It 'Calls Get-cdxmlDWORDValue cdxml function once' {
                Assert-MockCalled Get-cdxmlDWORDValue -Times 1 -Exactly
            }

             It 'Calls Get-cdxmlMultiStringValue cdxml function once' {
                Assert-MockCalled Get-cdxmlMultiStringValue -Times 1 -Exactly
            }

             It 'Calls Get-cdxmlQWORDValue cdxml function once' {
                Assert-MockCalled Get-cdxmlQWORDValue -Times 1 -Exactly
            }

            It 'Returns objects of custom CIMRegistryKey type' {
                $NormalKeyResult[0].GetType().Fullname | Should Be 'CIMRegistryValue'
                # $NormalKeyResult[0] | Should BeOfType CIMRegistryValue
            }

            It 'Returns proper number of objects with Parameter Input' {
                $NormalKeyResult.Count | Should Be $MockValueList.sNames.Count
            }

            It 'Path is correct' {
                $NormalKeyResult  | % {
                    $_.Path | Should Be $MockKeyPath
                }
            }

            It 'Default value Name is correct' {
               $NormalKeyResult[0].ValueName | Should Be '(default)'
               $NormalKeyResult[0].Data      | Should Be 'DefaultData'
            }

            It 'Value names are correct' {
                $_tmpSubKeysNames = $MockValueList.sNames | Sort
                foreach ($i in (1..($NormalKeyResult.Count - 1))) {
                    ($NormalKeyResult.ValueName)[$i] | Should Be $_tmpSubKeysNames[$i]
                }
            } # (1..($NormalKeyResult.Count - 1) - excludes (default) value (1.. instead 0..)

            It "Values' Data is correct" {
                ($NormalKeyResult.Where{$_.ValueName -eq '(default)'}).Data   | Should Be 'DefaultData'
                ($NormalKeyResult.Where{$_.ValueName -eq 'String'}).Data      | Should Be 'StringData'
                ($NormalKeyResult.Where{$_.ValueName -eq 'ExpString'}).Data   | Should Be 'ExpStringData'
                ($NormalKeyResult.Where{$_.ValueName -eq 'Bin'}).Data         | Should Be $MockBinData
                ($NormalKeyResult.Where{$_.ValueName -eq 'Dword'}).Data       | Should Be ([UInt32]123)
                ($NormalKeyResult.Where{$_.ValueName -eq 'MultiString'}).Data | Should Be $MockMulStrDt
                ($NormalKeyResult.Where{$_.ValueName -eq 'Qword'}).Data       | Should Be ([UInt64]456)
            } # Really it doesn't check types like [UInt64], just data.
        }

        Context 'Empty registry key' {
            It 'Returns nothing if a key has no values' {
                Get-RegistryValue -Path $MockEmptyKeyPath |  Should Be $null
            }
        }

        Context 'Fitering' {
            It 'Filtering with wildcards' {
                (Get-RegistryValue -Path $MockKeyPath -ValueName *string).Count | Should Be 3
            }

            It 'Filtering with specific single name' {
                (Get-RegistryValue -Path $MockKeyPath -ValueName Dword).Count | Should Be 1
            }

        }

        Context 'ValueName pipeline binding' {

            $CIMclassObject = Get-RegistryValue -Path $MockKeyPath -ComputerName 'SRV12' -ValueName String
            $PSCustomObject = $CIMclassObject | ConvertTo-Csv -NoTypeInformation | ConvertFrom-Csv

            It 'Accepts ValueName from pipeline InputObject' {
                ($CIMclassObject | Get-RegistryValue).Count | Should -Be 1
                ($CIMclassObject | Get-RegistryValue).ValueName | Should -Be 'String'
            } # -Skip

            It 'Accepts ValueName from pipeline by Parameters Binding' {
                ($PSCustomObject | Get-RegistryValue).Count | Should -Be 1
                ($CIMclassObject | Get-RegistryValue).ValueName | Should -Be 'String'
            } # -Skip

        }

        Context 'Hidden Default value' {

            It 'Returns nothing by default' {
                $DefaultValueResult = Get-RegistryValue -Path $HiddenValueKeyPath
                $NormalKeyResult.Count | Should -Be 0
            }

            It 'Returns hidden Default value if enforced' {
                $HiddenValueResult = Get-RegistryValue -Path $HiddenValueKeyPath -Force
                $HiddenValueResult.Data | Should -Be 'Temple of the Winds'
            }
        }

        Context 'Invalid values' {
            $InvalidValue = Get-RegistryValue -Path $InvalidValueKeyPath -ValueName BinData
            It 'Marks invalid values as invalid' {
                $InvalidValue.ErrorCode | Should -Not -Be 0
            } # -Skip

            $GoodValue = Get-RegistryValue -Path $MockKeyPath -ValueName Dword
            It 'Does not mark good values as invalid' {
                $GoodValue.ErrorCode | Should -Be 0
            }

        }

        Context "Call common tests" {
            & $PSScriptRoot\99_CIMRegistry_CommonTests.ps1 -FunctionName Get-RegistryValue -WrongPathReturn Error -CheckOutput
        }
    }

} # Describe 'Main functions'