Tests/Unit/xWebAdministration.Common.Tests.ps1

<#
    This module is loaded as a nested module when the xWebAdministration module is imported,
    remove the module from the session to avoid the error message:
 
        Multiple Script modules named 'xWebAdministration.Common'
        are currently loaded. Make sure to remove any extra copies
        of the module from your session before testing.
#>

Get-Module -Name 'xWebAdministration.Common' -All | Remove-Module -Force

# Import the xWebAdministration.Common module to test
$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent
$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules\xWebAdministration.Common'

Import-Module -Name (Join-Path -Path $script:modulesFolderPath -ChildPath 'xWebAdministration.Common.psm1') -Force

InModuleScope 'xWebAdministration.Common' {
    Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelper\CommonTestHelper.psm1') -Force

    Describe 'xWebAdministration.Common\Get-LocalizedData' {
        $mockTestPath = {
            return $mockTestPathReturnValue
        }

        $mockImportLocalizedData = {
            $BaseDirectory | Should -Be $mockExpectedLanguagePath
        }

        BeforeEach {
            Mock -CommandName Test-Path -MockWith $mockTestPath -Verifiable
            Mock -CommandName Import-LocalizedData -MockWith $mockImportLocalizedData -Verifiable
        }

        Context 'When loading localized data for Swedish' {
            $mockExpectedLanguagePath = 'sv-SE'
            $mockTestPathReturnValue = $true

            It 'Should call Import-LocalizedData with sv-SE language' {
                Mock -CommandName Join-Path -MockWith {
                    return 'sv-SE'
                } -Verifiable

                { Get-LocalizedData -ResourceName 'DummyResource' } | Should -Not -Throw

                Assert-MockCalled -CommandName Join-Path -Exactly -Times 3 -Scope It
                Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 -Scope It
                Assert-MockCalled -CommandName Import-LocalizedData -Exactly -Times 1 -Scope It
            }

            $mockExpectedLanguagePath = 'en-US'
            $mockTestPathReturnValue = $false

            It 'Should call Import-LocalizedData and fallback to en-US if sv-SE language does not exist' {
                Mock -CommandName Join-Path -MockWith {
                    return $ChildPath
                } -Verifiable

                { Get-LocalizedData -ResourceName 'DummyResource' } | Should -Not -Throw

                Assert-MockCalled -CommandName Join-Path -Exactly -Times 4 -Scope It
                Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 -Scope It
                Assert-MockCalled -CommandName Import-LocalizedData -Exactly -Times 1 -Scope It
            }

            Context 'When $ScriptRoot is set to a path' {
                $mockExpectedLanguagePath = 'sv-SE'
                $mockTestPathReturnValue = $true

                It 'Should call Import-LocalizedData with sv-SE language' {
                    Mock -CommandName Join-Path -MockWith {
                        return 'sv-SE'
                    } -Verifiable

                    { Get-LocalizedData -ResourceName 'DummyResource' -ScriptRoot '.' } | Should -Not -Throw

                    Assert-MockCalled -CommandName Join-Path -Exactly -Times 1 -Scope It
                    Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 -Scope It
                    Assert-MockCalled -CommandName Import-LocalizedData -Exactly -Times 1 -Scope It
                }

                $mockExpectedLanguagePath = 'en-US'
                $mockTestPathReturnValue = $false

                It 'Should call Import-LocalizedData and fallback to en-US if sv-SE language does not exist' {
                    Mock -CommandName Join-Path -MockWith {
                        return $ChildPath
                    } -Verifiable

                    { Get-LocalizedData -ResourceName 'DummyResource' -ScriptRoot '.' } | Should -Not -Throw

                    Assert-MockCalled -CommandName Join-Path -Exactly -Times 2 -Scope It
                    Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 -Scope It
                    Assert-MockCalled -CommandName Import-LocalizedData -Exactly -Times 1 -Scope It
                }
            }
        }

        Context 'When loading localized data for English' {
            Mock -CommandName Join-Path -MockWith {
                return 'en-US'
            } -Verifiable

            $mockExpectedLanguagePath = 'en-US'
            $mockTestPathReturnValue = $true

            It 'Should call Import-LocalizedData with en-US language' {
                { Get-LocalizedData -ResourceName 'DummyResource' } | Should -Not -Throw
            }
        }

        Assert-VerifiableMock
    }

    Describe 'xWebAdministration.Common\New-InvalidResultException' {
        Context 'When calling with Message parameter only' {
            It 'Should throw the correct error' {
                $mockErrorMessage = 'Mocked error'

                { New-InvalidResultException -Message $mockErrorMessage } | Should -Throw $mockErrorMessage
            }
        }

        Context 'When calling with both the Message and ErrorRecord parameter' {
            It 'Should throw the correct error' {
                $mockErrorMessage = 'Mocked error'
                $mockExceptionErrorMessage = 'Mocked exception error message'

                $mockException = New-Object -TypeName 'System.Exception' -ArgumentList $mockExceptionErrorMessage
                $mockErrorRecord = New-Object -TypeName 'System.Management.Automation.ErrorRecord' -ArgumentList @($mockException, $null, 'InvalidResult', $null)

                { New-InvalidResultException -Message $mockErrorMessage -ErrorRecord $mockErrorRecord } | Should -Throw ('System.Exception: {0} ---> System.Exception: {1}' -f $mockErrorMessage, $mockExceptionErrorMessage)
            }
        }

        Assert-VerifiableMock
    }

    Describe 'xWebAdministration.Common\New-ObjectNotFoundException' {
        Context 'When calling with Message parameter only' {
            It 'Should throw the correct error' {
                $mockErrorMessage = 'Mocked error'

                { New-ObjectNotFoundException -Message $mockErrorMessage } | Should -Throw $mockErrorMessage
            }
        }

        Context 'When calling with both the Message and ErrorRecord parameter' {
            It 'Should throw the correct error' {
                $mockErrorMessage = 'Mocked error'
                $mockExceptionErrorMessage = 'Mocked exception error message'

                $mockException = New-Object -TypeName 'System.Exception' -ArgumentList $mockExceptionErrorMessage
                $mockErrorRecord = New-Object -TypeName 'System.Management.Automation.ErrorRecord' -ArgumentList @($mockException, $null, 'InvalidResult', $null)

                { New-ObjectNotFoundException -Message $mockErrorMessage -ErrorRecord $mockErrorRecord } | Should -Throw ('System.Exception: {0} ---> System.Exception: {1}' -f $mockErrorMessage, $mockExceptionErrorMessage)
            }
        }

        Assert-VerifiableMock
    }

    Describe 'xWebAdministration.Common\New-InvalidOperationException' {
        Context 'When calling with Message parameter only' {
            It 'Should throw the correct error' {
                $mockErrorMessage = 'Mocked error'

                { New-InvalidOperationException -Message $mockErrorMessage } | Should -Throw $mockErrorMessage
            }
        }

        Context 'When calling with both the Message and ErrorRecord parameter' {
            It 'Should throw the correct error' {
                $mockErrorMessage = 'Mocked error'
                $mockExceptionErrorMessage = 'Mocked exception error message'

                $mockException = New-Object -TypeName 'System.Exception' -ArgumentList $mockExceptionErrorMessage
                $mockErrorRecord = New-Object -TypeName 'System.Management.Automation.ErrorRecord' -ArgumentList @($mockException, $null, 'InvalidResult', $null)

                { New-InvalidOperationException -Message $mockErrorMessage -ErrorRecord $mockErrorRecord } | Should -Throw ('System.InvalidOperationException: {0} ---> System.Exception: {1}' -f $mockErrorMessage, $mockExceptionErrorMessage)
            }
        }

        Assert-VerifiableMock
    }

    Describe 'xWebAdministration.Common\New-InvalidArgumentException' {
        Context 'When calling with both the Message and ArgumentName parameter' {
            It 'Should throw the correct error' {
                $mockErrorMessage = 'Mocked error'
                $mockArgumentName = 'MockArgument'

                { New-InvalidArgumentException -Message $mockErrorMessage -ArgumentName $mockArgumentName } | Should -Throw ('Parameter name: {0}' -f $mockArgumentName)
            }
        }

        Assert-VerifiableMock
    }

    Describe 'DscResource.Common\Start-ProcessWithTimeout' {
        Context 'When starting a process successfully' {
            It 'Should return exit code 0' {
                $startProcessWithTimeoutParameters = @{
                    FilePath = 'powershell.exe'
                    ArgumentList = '-Command &{Start-Sleep -Seconds 2}'
                    Timeout = 300
                }

                $processExitCode = Start-ProcessWithTimeout @startProcessWithTimeoutParameters
                $processExitCode | Should -BeExactly 0
            }
        }

        Context 'When starting a process and the process does not finish before the timeout period' {
            It 'Should throw an error message' {
                $startProcessWithTimeoutParameters = @{
                    FilePath = 'powershell.exe'
                    ArgumentList = '-Command &{Start-Sleep -Seconds 4}'
                    Timeout = 2
                }

                { Start-ProcessWithTimeout @startProcessWithTimeoutParameters } | Should -Throw -ErrorId 'ProcessNotTerminated,Microsoft.PowerShell.Commands.WaitProcessCommand'
            }
        }
    }

    Describe 'xWebAdministration.Common\Assert-Module' {
        BeforeAll {
            $testModuleName = 'TestModule'
        }

        Context 'When module is not installed' {
            BeforeAll {
                Mock -CommandName Get-Module
            }

            It 'Should throw the correct error' {
                { Assert-Module -ModuleName $testModuleName } | Should -Throw ($script:localizedData.RoleNotFoundError -f $testModuleName)
            }
        }

        Context 'When module is available' {
            BeforeAll {
                Mock -CommandName Import-Module
                Mock -CommandName Get-Module -MockWith {
                    return @{
                        Name = $testModuleName
                    }
                }
            }

            Context 'When module should not be imported' {
                It 'Should not throw an error' {
                    { Assert-Module -ModuleName $testModuleName } | Should -Not -Throw

                    Assert-MockCalled -CommandName Import-Module -Exactly -Times 0 -Scope It
                }
            }

            Context 'When module should be imported' {
                It 'Should not throw an error' {
                    { Assert-Module -ModuleName $testModuleName -ImportModule } | Should -Not -Throw

                    Assert-MockCalled -CommandName Import-Module -Exactly -Times 1 -Scope It
                }
            }
        }
    }

    Describe 'DscResource.Common\Test-DscPropertyState' -Tag 'TestDscPropertyState' {
        Context 'When comparing tables' {
            It 'Should return true for two identical tables' {
                $mockValues = @{
                    CurrentValue = 'Test'
                    DesiredValue = 'Test'
                }

                Test-DscPropertyState -Values $mockValues | Should -BeTrue
            }
        }

        Context 'When comparing strings' {
            It 'Should return false when a value is different for [System.String]' {
                $mockValues = @{
                    CurrentValue = [System.String] 'something'
                    DesiredValue = [System.String] 'test'
                }

                Test-DscPropertyState -Values $mockValues | Should -BeFalse
            }

            It 'Should return false when a String value is missing' {
                $mockValues = @{
                    CurrentValue = $null
                    DesiredValue = [System.String] 'Something'
                }

                Test-DscPropertyState -Values $mockValues | Should -BeFalse
            }

            It 'Should return true when two strings are equal' {
                $mockValues = @{
                    CurrentValue = [System.String] 'Something'
                    DesiredValue = [System.String] 'Something'
                }

                Test-DscPropertyState -Values $mockValues | Should -Be $true
            }
        }

        Context 'When comparing integers' {
            It 'Should return false when a value is different for [System.Int32]' {
                $mockValues = @{
                    CurrentValue = [System.Int32] 1
                    DesiredValue = [System.Int32] 2
                }

                Test-DscPropertyState -Values $mockValues | Should -BeFalse
            }

            It 'Should return true when the values are the same for [System.Int32]' {
                $mockValues = @{
                    CurrentValue = [System.Int32] 2
                    DesiredValue = [System.Int32] 2
                }

                Test-DscPropertyState -Values $mockValues | Should -Be $true
            }

            It 'Should return false when a value is different for [System.UInt32]' {
                $mockValues = @{
                    CurrentValue = [System.UInt32] 1
                    DesiredValue = [System.UInt32] 2
                }

                Test-DscPropertyState -Values $mockValues | Should -Be $false
            }

            It 'Should return true when the values are the same for [System.UInt32]' {
                $mockValues = @{
                    CurrentValue = [System.UInt32] 2
                    DesiredValue = [System.UInt32] 2
                }

                Test-DscPropertyState -Values $mockValues | Should -Be $true
            }

            It 'Should return false when a value is different for [System.Int16]' {
                $mockValues = @{
                    CurrentValue = [System.Int16] 1
                    DesiredValue = [System.Int16] 2
                }

                Test-DscPropertyState -Values $mockValues | Should -BeFalse
            }

            It 'Should return true when the values are the same for [System.Int16]' {
                $mockValues = @{
                    CurrentValue = [System.Int16] 2
                    DesiredValue = [System.Int16] 2
                }

                Test-DscPropertyState -Values $mockValues | Should -Be $true
            }

            It 'Should return false when a value is different for [System.UInt16]' {
                $mockValues = @{
                    CurrentValue = [System.UInt16] 1
                    DesiredValue = [System.UInt16] 2
                }

                Test-DscPropertyState -Values $mockValues | Should -BeFalse
            }

            It 'Should return true when the values are the same for [System.UInt16]' {
                $mockValues = @{
                    CurrentValue = [System.UInt16] 2
                    DesiredValue = [System.UInt16] 2
                }

                Test-DscPropertyState -Values $mockValues | Should -Be $true
            }

            It 'Should return false when a Integer value is missing' {
                $mockValues = @{
                    CurrentValue = $null
                    DesiredValue = [System.Int32] 1
                }

                Test-DscPropertyState -Values $mockValues | Should -BeFalse
            }
        }

        Context 'When comparing booleans' {
            It 'Should return false when a value is different for [System.Boolean]' {
                $mockValues = @{
                    CurrentValue = [System.Boolean] $true
                    DesiredValue = [System.Boolean] $false
                }

                Test-DscPropertyState -Values $mockValues | Should -BeFalse
            }

            It 'Should return false when a Boolean value is missing' {
                $mockValues = @{
                    CurrentValue = $null
                    DesiredValue = [System.Boolean] $true
                }

                Test-DscPropertyState -Values $mockValues | Should -BeFalse
            }
        }

        Context 'When comparing arrays' {
            It 'Should return true when evaluating an array' {
                $mockValues = @{
                    CurrentValue = @('1','2')
                    DesiredValue = @('1','2')
                }

                Test-DscPropertyState -Values $mockValues | Should -BeTrue
            }

            It 'Should return false when evaluating an array with wrong values' {
                $mockValues = @{
                    CurrentValue = @('CurrentValueA','CurrentValueB')
                    DesiredValue = @('DesiredValue1','DesiredValue2')
                }

                Test-DscPropertyState -Values $mockValues | Should -BeFalse
            }

            It 'Should return false when evaluating an array, but the current value is $null' {
                $mockValues = @{
                    CurrentValue = $null
                    DesiredValue = @('1','2')
                }

                Test-DscPropertyState -Values $mockValues | Should -BeFalse
            }
        }

        Context -Name 'When passing invalid types for DesiredValue' {
            It 'Should write a warning when DesiredValue contain an unsupported type' {
                Mock -CommandName Write-Warning -Verifiable

                # This is a dummy type to test with a type that could never be a correct one.
                class MockUnknownType
                {
                    [ValidateNotNullOrEmpty()]
                    [System.String]
                    $Property1

                    [ValidateNotNullOrEmpty()]
                    [System.String]
                    $Property2

                    MockUnknownType()
                    {
                    }
                }

                $mockValues = @{
                    CurrentValue = New-Object -TypeName 'MockUnknownType'
                    DesiredValue = New-Object -TypeName 'MockUnknownType'
                }

                Test-DscPropertyState -Values $mockValues | Should -BeFalse

                Assert-MockCalled -CommandName Write-Warning -Exactly -Times 1 -Scope It
            }
        }

        Assert-VerifiableMock
    }

    Describe 'xWebAdministration.Common\Compare-ResourcePropertyState' {
        Context 'When one property is in desired state' {
            BeforeAll {
                $mockCurrentValues = @{
                    ComputerName = 'DC01'
                }

                $mockDesiredValues = @{
                    ComputerName = 'DC01'
                }
            }

            It 'Should return the correct values' {
                $compareTargetResourceStateParameters = @{
                    CurrentValues = $mockCurrentValues
                    DesiredValues = $mockDesiredValues
                }

                $compareTargetResourceStateResult = Compare-ResourcePropertyState @compareTargetResourceStateParameters
                $compareTargetResourceStateResult | Should -HaveCount 1
                $compareTargetResourceStateResult.ParameterName | Should -Be 'ComputerName'
                $compareTargetResourceStateResult.Expected | Should -Be 'DC01'
                $compareTargetResourceStateResult.Actual | Should -Be 'DC01'
                $compareTargetResourceStateResult.InDesiredState | Should -BeTrue
            }
        }

        Context 'When two properties are in desired state' {
            BeforeAll {
                $mockCurrentValues = @{
                    ComputerName = 'DC01'
                    Location = 'Sweden'
                }

                $mockDesiredValues = @{
                    ComputerName = 'DC01'
                    Location = 'Sweden'
                }
            }

            It 'Should return the correct values' {
                $compareTargetResourceStateParameters = @{
                    CurrentValues = $mockCurrentValues
                    DesiredValues = $mockDesiredValues
                }

                $compareTargetResourceStateResult = Compare-ResourcePropertyState @compareTargetResourceStateParameters
                $compareTargetResourceStateResult | Should -HaveCount 2
                $compareTargetResourceStateResult[0].ParameterName | Should -Be 'ComputerName'
                $compareTargetResourceStateResult[0].Expected | Should -Be 'DC01'
                $compareTargetResourceStateResult[0].Actual | Should -Be 'DC01'
                $compareTargetResourceStateResult[0].InDesiredState | Should -BeTrue
                $compareTargetResourceStateResult[1].ParameterName | Should -Be 'Location'
                $compareTargetResourceStateResult[1].Expected | Should -Be 'Sweden'
                $compareTargetResourceStateResult[1].Actual | Should -Be 'Sweden'
                $compareTargetResourceStateResult[1].InDesiredState | Should -BeTrue
            }
        }

        Context 'When passing just one property and that property is not in desired state' {
            BeforeAll {
                $mockCurrentValues = @{
                    ComputerName = 'DC01'
                }

                $mockDesiredValues = @{
                    ComputerName = 'APP01'
                }
            }

            It 'Should return the correct values' {
                $compareTargetResourceStateParameters = @{
                    CurrentValues = $mockCurrentValues
                    DesiredValues = $mockDesiredValues
                }

                $compareTargetResourceStateResult = Compare-ResourcePropertyState @compareTargetResourceStateParameters
                $compareTargetResourceStateResult | Should -HaveCount 1
                $compareTargetResourceStateResult.ParameterName | Should -Be 'ComputerName'
                $compareTargetResourceStateResult.Expected | Should -Be 'APP01'
                $compareTargetResourceStateResult.Actual | Should -Be 'DC01'
                $compareTargetResourceStateResult.InDesiredState | Should -BeFalse
            }
        }

        Context 'When passing two properties and one property is not in desired state' {
            BeforeAll {
                $mockCurrentValues = @{
                    ComputerName = 'DC01'
                    Location = 'Sweden'
                }

                $mockDesiredValues = @{
                    ComputerName = 'DC01'
                    Location = 'Europe'
                }
            }

            It 'Should return the correct values' {
                $compareTargetResourceStateParameters = @{
                    CurrentValues = $mockCurrentValues
                    DesiredValues = $mockDesiredValues
                }

                $compareTargetResourceStateResult = Compare-ResourcePropertyState @compareTargetResourceStateParameters
                $compareTargetResourceStateResult | Should -HaveCount 2
                $compareTargetResourceStateResult[0].ParameterName | Should -Be 'ComputerName'
                $compareTargetResourceStateResult[0].Expected | Should -Be 'DC01'
                $compareTargetResourceStateResult[0].Actual | Should -Be 'DC01'
                $compareTargetResourceStateResult[0].InDesiredState | Should -BeTrue
                $compareTargetResourceStateResult[1].ParameterName | Should -Be 'Location'
                $compareTargetResourceStateResult[1].Expected | Should -Be 'Europe'
                $compareTargetResourceStateResult[1].Actual | Should -Be 'Sweden'
                $compareTargetResourceStateResult[1].InDesiredState | Should -BeFalse
            }
        }

        Context 'When passing a common parameter set to desired value' {
            BeforeAll {
                $mockCurrentValues = @{
                    ComputerName = 'DC01'
                }

                $mockDesiredValues = @{
                    ComputerName = 'DC01'
                    Verbose = $true
                }
            }

            It 'Should return the correct values' {
                $compareTargetResourceStateParameters = @{
                    CurrentValues = $mockCurrentValues
                    DesiredValues = $mockDesiredValues
                }

                $compareTargetResourceStateResult = Compare-ResourcePropertyState @compareTargetResourceStateParameters
                $compareTargetResourceStateResult | Should -HaveCount 1
                $compareTargetResourceStateResult.ParameterName | Should -Be 'ComputerName'
                $compareTargetResourceStateResult.Expected | Should -Be 'DC01'
                $compareTargetResourceStateResult.Actual | Should -Be 'DC01'
                $compareTargetResourceStateResult.InDesiredState | Should -BeTrue
            }
        }

        Context 'When using parameter Properties to compare desired values' {
            BeforeAll {
                $mockCurrentValues = @{
                    ComputerName = 'DC01'
                    Location = 'Sweden'
                }

                $mockDesiredValues = @{
                    ComputerName = 'DC01'
                    Location = 'Europe'
                }
            }

            It 'Should return the correct values' {
                $compareTargetResourceStateParameters = @{
                    CurrentValues = $mockCurrentValues
                    DesiredValues = $mockDesiredValues
                    Properties = @(
                        'ComputerName'
                    )
                }

                $compareTargetResourceStateResult = Compare-ResourcePropertyState @compareTargetResourceStateParameters
                $compareTargetResourceStateResult | Should -HaveCount 1
                $compareTargetResourceStateResult.ParameterName | Should -Be 'ComputerName'
                $compareTargetResourceStateResult.Expected | Should -Be 'DC01'
                $compareTargetResourceStateResult.Actual | Should -Be 'DC01'
                $compareTargetResourceStateResult.InDesiredState | Should -BeTrue
            }
        }

        Context 'When using parameter Properties and IgnoreProperties to compare desired values' {
            BeforeAll {
                $mockCurrentValues = @{
                    ComputerName = 'DC01'
                    Location = 'Sweden'
                    Ensure = 'Present'
                }

                $mockDesiredValues = @{
                    ComputerName = 'DC01'
                    Location = 'Europe'
                    Ensure = 'Absent'
                }
            }

            It 'Should return the correct values' {
                $compareTargetResourceStateParameters = @{
                    CurrentValues = $mockCurrentValues
                    DesiredValues = $mockDesiredValues
                    IgnoreProperties = @(
                        'Ensure'
                    )
                }

                $compareTargetResourceStateResult = Compare-ResourcePropertyState @compareTargetResourceStateParameters
                $compareTargetResourceStateResult | Should -HaveCount 2
                $compareTargetResourceStateResult[0].ParameterName | Should -Be 'ComputerName'
                $compareTargetResourceStateResult[0].Expected | Should -Be 'DC01'
                $compareTargetResourceStateResult[0].Actual | Should -Be 'DC01'
                $compareTargetResourceStateResult[0].InDesiredState | Should -BeTrue
                $compareTargetResourceStateResult[1].ParameterName | Should -Be 'Location'
                $compareTargetResourceStateResult[1].Expected | Should -Be 'Europe'
                $compareTargetResourceStateResult[1].Actual | Should -Be 'Sweden'
                $compareTargetResourceStateResult[1].InDesiredState | Should -BeFalse
            }
        }

        Context 'When using parameter Properties and IgnoreProperties to compare desired values' {
            BeforeAll {
                $mockCurrentValues = @{
                    ComputerName = 'DC01'
                    Location = 'Sweden'
                    Ensure = 'Present'
                }

                $mockDesiredValues = @{
                    ComputerName = 'DC01'
                    Location = 'Europe'
                    Ensure = 'Absent'
                }
            }

            It 'Should return and empty array' {
                $compareTargetResourceStateParameters = @{
                    CurrentValues = $mockCurrentValues
                    DesiredValues = $mockDesiredValues
                    Properties = @(
                        'ComputerName'
                    )
                    IgnoreProperties = @(
                        'ComputerName'
                    )
                }

                $compareTargetResourceStateResult = Compare-ResourcePropertyState @compareTargetResourceStateParameters
                $compareTargetResourceStateResult | Should -BeNullOrEmpty
            }
        }
    }

    Describe 'xWebAdministration.Common\New-CimCredentialInstance' {
        Context 'When creating a new MSFT_Credential CIM instance credential object' {
            BeforeAll {
                $mockAdministratorUser = 'admin@contoso.com'
                $mockAdministratorPassword = 'P@ssw0rd-12P@ssw0rd-12'
                $mockAdministratorCredential = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList @(
                    $mockAdministratorUser,
                    ($mockAdministratorPassword | ConvertTo-SecureString -AsPlainText -Force)
                )
            }

            It 'Should return the correct values' {
                $newCimCredentialInstanceResult = New-CimCredentialInstance -Credential $mockAdministratorCredential
                $newCimCredentialInstanceResult | Should -BeOfType 'Microsoft.Management.Infrastructure.CimInstance'
                $newCimCredentialInstanceResult.CimClass.CimClassName | Should -Be 'MSFT_Credential'
                $newCimCredentialInstanceResult.UserName | Should -Be $mockAdministratorUser
                $newCimCredentialInstanceResult.Password | Should -BeNullOrEmpty
            }
        }
    }

    Describe 'xWebAdministration.Common\Find-Certificate' {

        # Download and dot source the New-SelfSignedCertificateEx script
        . (Install-NewSelfSignedCertificateExScript)

        # Generate the Valid certificate for testing but remove it from the store straight away
        $certDNSNames = @('www.fabrikam.com', 'www.contoso.com')
        $certDNSNamesReverse = @('www.contoso.com', 'www.fabrikam.com')
        $certDNSNamesNoMatch = $certDNSNames + @('www.nothere.com')
        $certKeyUsage = @('DigitalSignature','DataEncipherment')
        $certKeyUsageReverse = @('DataEncipherment','DigitalSignature')
        $certKeyUsageNoMatch = $certKeyUsage + @('KeyEncipherment')
        $certEKU = @('Server Authentication','Client authentication')
        $certEKUReverse = @('Client authentication','Server Authentication')
        $certEKUNoMatch = $certEKU + @('Encrypting File System')
        $certSubject = 'CN=contoso, DC=com'
        $certSubjectLong = 'CN=contoso, E=myemail@contoso.com, O=Fabrikam., OU=IT, L=Location, S=State, C=Country'
        $certSubjectNoSpace = 'CN=contoso,E=myemail@contoso.com,O=Fabrikam.,OU=IT,L=Location,S=State,C=Country'
        $certSubjectLongReverse = 'E=myemail@contoso.com,O=Fabrikam.,L=Location,CN=contoso,OU=IT,S=State,C=Country'
        $certFriendlyName = 'Contoso Test Cert'
        $validCert = New-SelfSignedCertificateEx `
            -Subject $certSubject `
            -KeyUsage $certKeyUsage `
            -KeySpec 'Exchange' `
            -EKU $certEKU `
            -SubjectAlternativeName $certDNSNames `
            -FriendlyName $certFriendlyName `
            -StoreLocation 'CurrentUser' `
            -Exportable
        # Pull the generated certificate from the store so we have the friendlyname
        $validThumbprint = $validCert.Thumbprint
        $validCert = Get-Item -Path "cert:\CurrentUser\My\$validThumbprint"
        Remove-Item -Path $validCert.PSPath -Force

        # Generate the long subject certificate for testing but remove it from the store straight away
        $validCertSubjectLong = New-SelfSignedCertificateEx `
            -Subject $certSubjectLong `
            -KeyUsage $certKeyUsage `
            -KeySpec 'Exchange' `
            -EKU $certEKU `
            -SubjectAlternativeName $certDNSNames `
            -FriendlyName $certFriendlyName `
            -StoreLocation 'CurrentUser' `
            -Exportable
        # Pull the generated certificate from the store so we have the friendlyname
        $longThumbprint = $validCertSubjectLong.Thumbprint
        $validCertSubjectLong = Get-Item -Path "cert:\CurrentUser\My\$longThumbprint"
        Remove-Item -Path $validCertSubjectLong.PSPath -Force

        # Generate the Expired certificate for testing but remove it from the store straight away
        $expiredCert = New-SelfSignedCertificateEx `
            -Subject $certSubject `
            -KeyUsage $certKeyUsage `
            -KeySpec 'Exchange' `
            -EKU $certEKU `
            -SubjectAlternativeName $certDNSNames `
            -FriendlyName $certFriendlyName `
            -NotBefore ((Get-Date) - (New-TimeSpan -Days 2)) `
            -NotAfter ((Get-Date) - (New-TimeSpan -Days 1)) `
            -StoreLocation 'CurrentUser' `
            -Exportable
        # Pull the generated certificate from the store so we have the friendlyname
        $expiredThumbprint = $expiredCert.Thumbprint
        $expiredCert = Get-Item -Path "cert:\CurrentUser\My\$expiredThumbprint"
        Remove-Item -Path $expiredCert.PSPath -Force

        $nocertThumbprint = '1111111111111111111111111111111111111111'

        # Dynamic mock content for Get-ChildItem
        $mockGetChildItem = {
            switch ( $Path )
            {
                'cert:\LocalMachine\My'
                {
                    return @( $validCert )
                }

                'cert:\LocalMachine\NoCert'
                {
                    return @()
                }

                'cert:\LocalMachine\TwoCerts'
                {
                    return @( $expiredCert, $validCert )
                }

                'cert:\LocalMachine\Expired'
                {
                    return @( $expiredCert )
                }

                'cert:\LocalMachine\LongSubject'
                {
                    return @( $validCertSubjectLong )
                }

                default
                {
                    throw 'mock called with unexpected value {0}' -f $Path
                }
            }
        }

        BeforeEach {
            Mock `
                -CommandName Test-Path `
                -MockWith { $true }

            Mock `
                -CommandName Get-ChildItem `
                -MockWith $mockGetChildItem
        }

        Context 'Thumbprint only is passed and matching certificate exists' {
            It 'should not throw exception' {
                { $script:result = Find-Certificate -Thumbprint $validThumbprint } | Should Not Throw
            }

            It 'should return expected certificate' {
                $script:result.Thumbprint | Should Be $validThumbprint
            }

            It 'should call expected mocks' {
                Assert-MockCalled -CommandName Test-Path -Exactly -Times 1
                Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1
            }
        }

        Context 'Thumbprint only is passed and matching certificate does not exist' {
            It 'should not throw exception' {
                { $script:result = Find-Certificate -Thumbprint $nocertThumbprint } | Should Not Throw
            }

            It 'should return null' {
                $script:result | Should BeNullOrEmpty
            }

            It 'should call expected mocks' {
                Assert-MockCalled -CommandName Test-Path -Exactly -Times 1
                Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1
            }
        }

        Context 'FriendlyName only is passed and matching certificate exists' {
            It 'should not throw exception' {
                { $script:result = Find-Certificate -FriendlyName $certFriendlyName } | Should Not Throw
            }

            It 'should return expected certificate' {
                $script:result.Thumbprint | Should Be $validThumbprint
            }

            It 'should call expected mocks' {
                Assert-MockCalled -CommandName Test-Path -Exactly -Times 1
                Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1
            }
        }

        Context 'FriendlyName only is passed and matching certificate does not exist' {
            It 'should not throw exception' {
                { $script:result = Find-Certificate -FriendlyName 'Does Not Exist' } | Should Not Throw
            }

            It 'should return null' {
                $script:result | Should BeNullOrEmpty
            }

            It 'should call expected mocks' {
                Assert-MockCalled -CommandName Test-Path -Exactly -Times 1
                Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1
            }
        }

        Context 'Subject only is passed and matching certificate exists' {
            It 'should not throw exception' {
                { $script:result = Find-Certificate -Subject $certSubject } | Should Not Throw
            }

            It 'should return expected certificate' {
                $script:result.Thumbprint | Should Be $validThumbprint
            }

            It 'should call expected mocks' {
                Assert-MockCalled -CommandName Test-Path -Exactly -Times 1
                Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1
            }
        }

        Context 'Subject only is passed and matching certificate does not exist' {
            It 'should not throw exception' {
                { $script:result = Find-Certificate -Subject 'CN=Does Not Exist' } | Should Not Throw
            }

            It 'should return null' {
                $script:result | Should BeNullOrEmpty
            }

            It 'should call expected mocks' {
                Assert-MockCalled -CommandName Test-Path -Exactly -Times 1
                Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1
            }
        }

        Context 'Subject only is passed and certificate with a different subject order exists' {
            It 'should not throw exception' {
                { $script:result = Find-Certificate -Subject $certSubjectLongReverse -Store 'LongSubject' } | Should Not Throw
            }

            It 'should return expected certificate' {
                $script:result.Thumbprint | Should Be $longThumbprint
            }

            It 'should call expected mocks' {
                Assert-MockCalled -CommandName Test-Path -Exactly -Times 1
                Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1
            }
        }

        Context 'Subject only is passed and certificate subject without spaces exists' {
            It 'should not throw exception' {
                { $script:result = Find-Certificate -Subject $certSubjectNoSpace -Store 'LongSubject' } | Should Not Throw
            }

            It 'should return expected certificate' {
                $script:result.Thumbprint | Should Be $longThumbprint
            }

            It 'should call expected mocks' {
                Assert-MockCalled -CommandName Test-Path -Exactly -Times 1
                Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1
            }
        }

        Context 'Issuer only is passed and matching certificate exists' {
            It 'should not throw exception' {
                { $script:result = Find-Certificate -Issuer $certSubject } | Should Not Throw
            }

            It 'should return expected certificate' {
                $script:result.Thumbprint | Should Be $validThumbprint
            }

            It 'should call expected mocks' {
                Assert-MockCalled -CommandName Test-Path -Exactly -Times 1
                Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1
            }
        }

        Context 'Issuer only is passed and matching certificate does not exist' {
            It 'should not throw exception' {
                { $script:result = Find-Certificate -Issuer 'CN=Does Not Exist' } | Should Not Throw
            }

            It 'should return null' {
                $script:result | Should BeNullOrEmpty
            }

            It 'should call expected mocks' {
                Assert-MockCalled -CommandName Test-Path -Exactly -Times 1
                Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1
            }
        }

        Context 'DNSName only is passed and matching certificate exists' {
            It 'should not throw exception' {
                { $script:result = Find-Certificate -DnsName $certDNSNames } | Should Not Throw
            }

            It 'should return expected certificate' {
                $script:result.Thumbprint | Should Be $validThumbprint
            }

            It 'should call expected mocks' {
                Assert-MockCalled -CommandName Test-Path -Exactly -Times 1
                Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1
            }
        }

        Context 'DNSName only is passed in reversed order and matching certificate exists' {
            It 'should not throw exception' {
                { $script:result = Find-Certificate -DnsName $certDNSNamesReverse } | Should Not Throw
            }

            It 'should return expected certificate' {
                $script:result.Thumbprint | Should Be $validThumbprint
            }

            It 'should call expected mocks' {
                Assert-MockCalled -CommandName Test-Path -Exactly -Times 1
                Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1
            }
        }

        Context 'DNSName only is passed with only one matching DNS name and matching certificate exists' {
            It 'should not throw exception' {
                { $script:result = Find-Certificate -DnsName $certDNSNames[0] } | Should Not Throw
            }

            It 'should return expected certificate' {
                $script:result.Thumbprint | Should Be $validThumbprint
            }

            It 'should call expected mocks' {
                Assert-MockCalled -CommandName Test-Path -Exactly -Times 1
                Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1
            }
        }

        Context 'DNSName only is passed but an entry is missing and matching certificate does not exist' {
            It 'should not throw exception' {
                { $script:result = Find-Certificate -DnsName $certDNSNamesNoMatch } | Should Not Throw
            }

            It 'should return null' {
                $script:result | Should BeNullOrEmpty
            }

            It 'should call expected mocks' {
                Assert-MockCalled -CommandName Test-Path -Exactly -Times 1
                Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1
            }
        }

        Context 'KeyUsage only is passed and matching certificate exists' {
            It 'should not throw exception' {
                { $script:result = Find-Certificate -KeyUsage $certKeyUsage } | Should Not Throw
            }

            It 'should return expected certificate' {
                $script:result.Thumbprint | Should Be $validThumbprint
            }

            It 'should call expected mocks' {
                Assert-MockCalled -CommandName Test-Path -Exactly -Times 1
                Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1
            }
        }

        Context 'KeyUsage only is passed in reversed order and matching certificate exists' {
            It 'should not throw exception' {
                { $script:result = Find-Certificate -KeyUsage $certKeyUsageReverse } | Should Not Throw
            }

            It 'should return expected certificate' {
                $script:result.Thumbprint | Should Be $validThumbprint
            }

            It 'should call expected mocks' {
                Assert-MockCalled -CommandName Test-Path -Exactly -Times 1
                Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1
            }
        }

        Context 'KeyUsage only is passed with only one matching DNS name and matching certificate exists' {
            It 'should not throw exception' {
                { $script:result = Find-Certificate -KeyUsage $certKeyUsage[0] } | Should Not Throw
            }

            It 'should return expected certificate' {
                $script:result.Thumbprint | Should Be $validThumbprint
            }

            It 'should call expected mocks' {
                Assert-MockCalled -CommandName Test-Path -Exactly -Times 1
                Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1
            }
        }

        Context 'KeyUsage only is passed but an entry is missing and matching certificate does not exist' {
            It 'should not throw exception' {
                { $script:result = Find-Certificate -KeyUsage $certKeyUsageNoMatch } | Should Not Throw
            }

            It 'should return null' {
                $script:result | Should BeNullOrEmpty
            }

            It 'should call expected mocks' {
                Assert-MockCalled -CommandName Test-Path -Exactly -Times 1
                Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1
            }
        }

        Context 'EnhancedKeyUsage only is passed and matching certificate exists' {
            It 'should not throw exception' {
                { $script:result = Find-Certificate -EnhancedKeyUsage $certEKU } | Should Not Throw
            }

            It 'should return expected certificate' {
                $script:result.Thumbprint | Should Be $validThumbprint
            }

            It 'should call expected mocks' {
                Assert-MockCalled -CommandName Test-Path -Exactly -Times 1
                Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1
            }
        }

        Context 'EnhancedKeyUsage only is passed in reversed order and matching certificate exists' {
            It 'should not throw exception' {
                { $script:result = Find-Certificate -EnhancedKeyUsage $certEKUReverse } | Should Not Throw
            }

            It 'should return expected certificate' {
                $script:result.Thumbprint | Should Be $validThumbprint
            }

            It 'should call expected mocks' {
                Assert-MockCalled -CommandName Test-Path -Exactly -Times 1
                Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1
            }
        }

        Context 'EnhancedKeyUsage only is passed with only one matching DNS name and matching certificate exists' {
            It 'should not throw exception' {
                { $script:result = Find-Certificate -EnhancedKeyUsage $certEKU[0] } | Should Not Throw
            }

            It 'should return expected certificate' {
                $script:result.Thumbprint | Should Be $validThumbprint
            }

            It 'should call expected mocks' {
                Assert-MockCalled -CommandName Test-Path -Exactly -Times 1
                Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1
            }
        }

        Context 'EnhancedKeyUsage only is passed but an entry is missing and matching certificate does not exist' {
            It 'should not throw exception' {
                { $script:result = Find-Certificate -EnhancedKeyUsage $certEKUNoMatch } | Should Not Throw
            }

            It 'should return null' {
                $script:result | Should BeNullOrEmpty
            }

            It 'should call expected mocks' {
                Assert-MockCalled -CommandName Test-Path -Exactly -Times 1
                Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1
            }
        }

        Context 'Thumbprint only is passed and matching certificate does not exist in the store' {
            It 'should not throw exception' {
                { $script:result = Find-Certificate -Thumbprint $validThumbprint -Store 'NoCert'} | Should Not Throw
            }

            It 'should return null' {
                $script:result | Should BeNullOrEmpty
            }

            It 'should call expected mocks' {
                Assert-MockCalled -CommandName Test-Path -Exactly -Times 1
                Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1
            }
        }

        Context 'FriendlyName only is passed and both valid and expired certificates exist' {
            It 'should not throw exception' {
                { $script:result = Find-Certificate -FriendlyName $certFriendlyName -Store 'TwoCerts' } | Should Not Throw
            }

            It 'should return expected certificate' {
                $script:result.Thumbprint | Should Be $validThumbprint
            }

            It 'should call expected mocks' {
                Assert-MockCalled -CommandName Test-Path -Exactly -Times 1
                Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1
            }
        }

        Context 'FriendlyName only is passed and only expired certificates exist' {
            It 'should not throw exception' {
                { $script:result = Find-Certificate -FriendlyName $certFriendlyName -Store 'Expired' } | Should Not Throw
            }

            It 'should return expected certificate' {
                $script:result | Should BeNullOrEmpty
            }

            It 'should call expected mocks' {
                Assert-MockCalled -CommandName Test-Path -Exactly -Times 1
                Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1
            }
        }

        Context 'FriendlyName only is passed and only expired certificates exist but allowexpired passed' {
            It 'should not throw exception' {
                { $script:result = Find-Certificate -FriendlyName $certFriendlyName -Store 'Expired' -AllowExpired:$true } | Should Not Throw
            }

            It 'should return expected certificate' {
                $script:result.Thumbprint | Should Be $expiredThumbprint
            }

            It 'should call expected mocks' {
                Assert-MockCalled -CommandName Test-Path -Exactly -Times 1
                Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1
            }
        }
    }

}