Tests/Unit/MSFT_xCluster.Tests.ps1

$script:DSCModuleName = 'xFailOverCluster'
$script:DSCResourceName = 'MSFT_xCluster'

#region HEADER

# Unit Test Template Version: 1.2.0
$script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or `
    (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) )
{
    & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git', (Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\'))
}

Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force

$TestEnvironment = Initialize-TestEnvironment `
    -DSCModuleName $script:DSCModuleName `
    -DSCResourceName $script:DSCResourceName `
    -TestType Unit

#endregion HEADER

function Invoke-TestSetup
{
    Import-Module -Name (Join-Path -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests') -ChildPath 'Unit') -ChildPath 'Stubs') -ChildPath 'FailoverClusters.stubs.psm1') -Global -Force
}

function Invoke-TestCleanup
{
    Restore-TestEnvironment -TestEnvironment $TestEnvironment
}

# Begin Testing
try
{
    Invoke-TestSetup

    InModuleScope $script:DSCResourceName {
        $mockAdministratorUserName = 'COMPANY\ClusterAdmin'
        $mockAdministratorPassword = ConvertTo-SecureString -String 'dummyPassW0rd' -AsPlainText -Force
        $mockAdministratorCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList @($mockAdministratorUserName, $mockAdministratorPassword)

        $mockDomainName = 'domain.local'
        $mockServerName = $env:COMPUTERNAME
        $mockClusterName = 'CLUSTER001'
        $mockStaticIpAddress = '192.168.10.10'

        $mockGetWmiObject = {
            return [PSCustomObject] @{
                Domain = $mockDynamicDomainName
                Name   = $mockDynamicServerName
            }
        }

        $mockGetWmiObject_ParameterFilter = {
            $Class -eq 'Win32_ComputerSystem'
        }

        $mockGetCluster = {
            return [PSCustomObject] @{
                Domain = $mockDomainName
                Name   = $mockClusterName
            }
        }

        $mockGetCluster_ParameterFilter = {
            $Name -eq $mockDefaultParameters.Name -and $Domain -eq $mockDomainName
        }

        $mockGetClusterGroup = {
            return @{
                Name      = 'Cluster Group'
                OwnerNode = 'Node1'
                State     = 'Online'
            }
        }

        $mockGetClusterGroup_ParameterFilter = {
            $Cluster -eq $mockClusterName
        }

        $mockGetClusterParameter = {
            return @{
                Object = 'Cluster IP Address'
                Name   = 'Address'
                Value  = $mockStaticIpAddress
            }
        }

       $mockGetClusterNode = {
            return @(
                @{
                    Name  = $mockServerName
                    State = $mockDynamicClusterNodeState
                }
            )
        }

        $mockDefaultParameters = @{
            Name                          = $mockClusterName
            StaticIPAddress               = $mockStaticIpAddress
            DomainAdministratorCredential = $mockAdministratorCredential
        }

        Describe 'xCluster\Get-TargetResource' {
            Context 'When the computers domain name cannot be evaluated' {
                It 'Should throw the correct error message' {
                    $mockDynamicDomainName = $null
                    $mockDynamicServerName = $mockServerName

                    Mock -CommandName Get-WmiObject -MockWith $mockGetWmiObject -ParameterFilter $mockGetWmiObject_ParameterFilter -Verifiable

                    { Get-TargetResource @mockDefaultParameters } | Should -Throw "Can't find machine's domain name"
                }
            }

            Context 'When the cluster cannot be found' {
                It 'Should throw the correct error message' {
                    $mockDynamicDomainName = $mockDomainName
                    $mockDynamicServerName = $mockServerName

                    Mock -CommandName Get-Cluster -Verifiable
                    Mock -CommandName Get-WmiObject -MockWith $mockGetWmiObject -ParameterFilter $mockGetWmiObject_ParameterFilter -Verifiable

                    { Get-TargetResource @mockDefaultParameters } | Should -Throw ("Can't find the cluster {0}" -f $mockClusterName)
                }
            }

            Context 'When the system is not in the desired state' {
                BeforeEach {
                    Mock -CommandName Get-WmiObject -MockWith $mockGetWmiObject -ParameterFilter $mockGetWmiObject_ParameterFilter -Verifiable
                    Mock -CommandName Get-Cluster -MockWith $mockGetCluster -ParameterFilter $mockGetCluster_ParameterFilter -Verifiable
                    Mock -CommandName Get-ClusterGroup -MockWith $mockGetClusterGroup -ParameterFilter $mockGetClusterGroup_ParameterFilter -Verifiable
                    Mock -CommandName Get-ClusterParameter -MockWith $mockGetClusterParameter -Verifiable
                }

                $mockDynamicDomainName = $mockDomainName
                $mockDynamicServerName = $mockServerName

                It 'Returns a [System.Collection.Hashtable] type' {
                    $getTargetResourceResult = Get-TargetResource @mockDefaultParameters
                    $getTargetResourceResult | Should BeOfType [System.Collections.Hashtable]
                }

                It 'Returns current configuration' {
                    $getTargetResourceResult = Get-TargetResource @mockDefaultParameters
                    $getTargetResourceResult.Name             | Should Be $mockDefaultParameters.Name
                    $getTargetResourceResult.StaticIPAddress  | Should Be $mockDefaultParameters.StaticIPAddress
                }

                Assert-VerifiableMocks
            }
        }

        Describe 'xCluster\Set-TargetResource' {
            Context 'When computers domain name cannot be evaluated' {
                It 'Should throw the correct error message' {
                    $mockDynamicDomainName = $null
                    $mockDynamicServerName = $mockServerName

                    Mock -CommandName Get-WmiObject -MockWith $mockGetWmiObject -ParameterFilter $mockGetWmiObject_ParameterFilter -Verifiable

                    { Set-TargetResource @mockDefaultParameters } | Should -Throw "Can't find machine's domain name"
                }
            }

            Context 'When the system is not in the desired state' {
                BeforeEach {
                    Mock -CommandName New-Cluster -Verifiable
                    Mock -CommandName Remove-ClusterNode -Verifiable
                    Mock -CommandName Add-ClusterNode -Verifiable
                    Mock -CommandName Get-WmiObject -MockWith $mockGetWmiObject -ParameterFilter $mockGetWmiObject_ParameterFilter -Verifiable
                }

                $mockDynamicDomainName = $mockDomainName
                $mockDynamicServerName = $mockServerName

                Context 'When the cluster does not exist' {
                    Context 'When Get-Cluster returns nothing' {
                        It 'Should call New-Cluster cmdlet' {
                            # This is used for the evaluation of that cluster do not exist.
                            Mock -CommandName Get-Cluster -ParameterFilter $mockGetCluster_ParameterFilter

                            # This is used to evaluate that cluster do exists after New-Cluster cmdlet has been run.
                            Mock -CommandName Get-Cluster -MockWith $mockGetCluster

                            { Set-TargetResource @mockDefaultParameters } | Should -Not -Throw

                            Assert-MockCalled -CommandName New-Cluster -Exactly -Times 1 -Scope It
                            Assert-MockCalled -CommandName Remove-ClusterNode -Exactly -Times 0 -Scope It
                            Assert-MockCalled -CommandName Add-ClusterNode -Exactly -Times 0 -Scope It
                        }
                    }

                    Context 'When Get-Cluster throws an error' {
                        It 'Should call New-Cluster cmdlet' {
                            # This is used for the evaluation of that cluster do not exist.
                            Mock -CommandName Get-Cluster -MockWith {
                                throw 'Mock Get-Cluster throw error'
                            } -ParameterFilter $mockGetCluster_ParameterFilter

                            # This is used to evaluate that cluster do exists after New-Cluster cmdlet has been run.
                            Mock -CommandName Get-Cluster -MockWith $mockGetCluster

                            { Set-TargetResource @mockDefaultParameters } | Should -Not -Throw

                            Assert-MockCalled -CommandName New-Cluster -Exactly -Times 1 -Scope It
                            Assert-MockCalled -CommandName Remove-ClusterNode -Exactly -Times 0 -Scope It
                            Assert-MockCalled -CommandName Add-ClusterNode -Exactly -Times 0 -Scope It
                        }
                    }
                }

                Context 'When the cluster does not exist, and New-Cluster is run, but no cluster can be found after' {
                    It 'Should throw the correct error message' {
                        Mock -CommandName Get-Cluster

                        { Set-TargetResource @mockDefaultParameters } | Should -Throw 'Cluster creation failed. Please verify output of ''Get-Cluster'' command'

                        Assert-MockCalled -CommandName New-Cluster -Exactly -Times 1 -Scope It
                        Assert-MockCalled -CommandName Remove-ClusterNode -Exactly -Times 0 -Scope It
                        Assert-MockCalled -CommandName Add-ClusterNode -Exactly -Times 0 -Scope It
                    }
                }

                Context 'When the cluster exist but the node is not part of the cluster' {
                    It 'Should call Add-ClusterNode cmdlet' {
                        Mock -CommandName Get-ClusterNode
                        Mock -CommandName Get-Cluster -MockWith $mockGetCluster -ParameterFilter $mockGetCluster_ParameterFilter

                        { Set-TargetResource @mockDefaultParameters } | Should -Not -Throw

                        Assert-MockCalled -CommandName New-Cluster -Exactly -Times 0 -Scope It
                        Assert-MockCalled -CommandName Remove-ClusterNode -Exactly -Times 0 -Scope It
                        Assert-MockCalled -CommandName Add-ClusterNode -Exactly -Times 1 -Scope It
                    }
                }

                Context 'When the cluster exist and the node is down' {
                    BeforeEach {
                        Mock -CommandName Get-ClusterNode -MockWith $mockGetClusterNode
                    }

                    $mockDynamicClusterNodeState = 'Down'

                    It 'Should call both Remove-ClusterNode and Add-ClusterNode cmdlet' {
                        Mock -CommandName Get-Cluster -MockWith $mockGetCluster -ParameterFilter $mockGetCluster_ParameterFilter

                        { Set-TargetResource @mockDefaultParameters } | Should -Not -Throw

                        Assert-MockCalled -CommandName New-Cluster -Exactly -Times 0 -Scope It
                        Assert-MockCalled -CommandName Remove-ClusterNode -Exactly -Times 1 -Scope It
                        Assert-MockCalled -CommandName Add-ClusterNode -Exactly -Times 1 -Scope It
                    }
                }
            }

            Context 'When the system is in the desired state' {
                BeforeEach {
                    Mock -CommandName Get-ClusterNode -Verifiable
                    Mock -CommandName New-Cluster -Verifiable
                    Mock -CommandName Remove-ClusterNode -Verifiable
                    Mock -CommandName Add-ClusterNode -Verifiable
                    Mock -CommandName Get-WmiObject -MockWith $mockGetWmiObject -ParameterFilter $mockGetWmiObject_ParameterFilter -Verifiable
                    Mock -CommandName Get-Cluster -MockWith $mockGetCluster -ParameterFilter $mockGetCluster_ParameterFilter -Verifiable
                    Mock -CommandName Get-ClusterParameter -MockWith $mockGetClusterParameter -Verifiable

                    Mock -CommandName Get-ClusterResource -MockWith {
                        @{
                            Name         = 'Resource1'
                            State        = 'Online'
                            OwnerGroup   = 'ClusterGroup1'
                            ResourceType = 'type1'
                        }
                    } -Verifiable
                }

                $mockDynamicDomainName = $mockDomainName
                $mockDynamicServerName = $mockServerName

                Context 'When the node already exist' {
                    # This test is skipped because due to a logic error it's not possible to test this (issue #79)
                    It 'Should not call any of the cluster cmdlets' -Skip {
                        Mock -CommandName Get-Cluster -MockWith $mockGetCluster -ParameterFilter $mockGetCluster_ParameterFilter -Verifiable

                        { Set-TargetResource @mockDefaultParameters } | Should -Not -Throw

                        Assert-MockCalled -CommandName New-Cluster -Exactly -Times 0 -Scope It
                        Assert-MockCalled -CommandName Remove-ClusterNode -Exactly -Times 0 -Scope It
                        Assert-MockCalled -CommandName Add-ClusterNode -Exactly -Times 0 -Scope It
                    }

                    Assert-VerifiableMocks
                }
            }
        }

        Describe 'xCluster\Test-TargetResource' {
            Context 'When computers domain name cannot be evaluated' {
                It 'Should throw the correct error message' {
                    $mockDynamicDomainName = $null
                    $mockDynamicServerName = $mockServerName

                    Mock -CommandName Get-WmiObject -MockWith $mockGetWmiObject -ParameterFilter $mockGetWmiObject_ParameterFilter -Verifiable

                    { Test-TargetResource @mockDefaultParameters } | Should -Throw "Can't find machine's domain name"
                }
            }

            Context 'When the system is not in the desired state' {
                BeforeEach {
                    Mock -CommandName Get-WmiObject -MockWith $mockGetWmiObject -ParameterFilter $mockGetWmiObject_ParameterFilter -Verifiable
                }

                $mockDynamicDomainName = $mockDomainName
                $mockDynamicServerName = $mockServerName

                Context 'When the cluster does not exist' {
                    It 'Should return $false' {
                        Mock -CommandName Get-Cluster -Verifiable

                        $testTargetResourceResult = Test-TargetResource @mockDefaultParameters
                        $testTargetResourceResult | Should -Be $false
                    }

                    Assert-VerifiableMocks
                }

                Context 'When the Get-Cluster throws an error' {
                    It 'Should return $false' {
                        Mock -CommandName Get-Cluster -MockWith {
                            throw 'Mock Get-Cluster throw error'
                        } -Verifiable

                        $testTargetResourceResult = Test-TargetResource @mockDefaultParameters
                        $testTargetResourceResult | Should -Be $false
                    }

                    Assert-VerifiableMocks
                }

                Context 'When the node does not exist' {
                    It 'Should return $false' {
                        Mock -CommandName Get-Cluster -MockWith $mockGetCluster -ParameterFilter $mockGetCluster_ParameterFilter -Verifiable
                        Mock -CommandName Get-ClusterNode -Verifiable

                        $testTargetResourceResult = Test-TargetResource @mockDefaultParameters

                        $testTargetResourceResult | Should -Be $false
                    }

                    Assert-VerifiableMocks
                }

                Context 'When the node do exist, but is down' {
                    BeforeEach {
                        Mock -CommandName Get-Cluster -MockWith $mockGetCluster -ParameterFilter $mockGetCluster_ParameterFilter -Verifiable
                        Mock -CommandName Get-ClusterNode -MockWith $mockGetClusterNode
                    }

                    $mockDynamicClusterNodeState = 'Down'

                    It 'Should return $false' {
                        $testTargetResourceResult = Test-TargetResource @mockDefaultParameters

                        $testTargetResourceResult | Should -Be $false
                    }

                    Assert-VerifiableMocks
                }

            }

            Context 'When the system is in the desired state' {
                BeforeEach {
                    Mock -CommandName Get-ClusterNode -MockWith $mockGetClusterNode -Verifiable
                    Mock -CommandName Get-WmiObject -MockWith $mockGetWmiObject -ParameterFilter $mockGetWmiObject_ParameterFilter -Verifiable
                    Mock -CommandName Get-Cluster -MockWith $mockGetCluster -ParameterFilter $mockGetCluster_ParameterFilter -Verifiable
                }

                $mockDynamicDomainName = $mockDomainName
                $mockDynamicServerName = $mockServerName
                $mockDynamicClusterNodeState = 'Up'

                Context 'When the node already exist' {
                    It 'Should return $true' {
                        $testTargetResourceResult = Test-TargetResource @mockDefaultParameters
                        $testTargetResourceResult | Should -Be $true
                    }

                    Assert-VerifiableMocks
                }
            }
        }
    }
}
finally
{
    Invoke-TestCleanup
}