Tests/Unit/MSFT_xSQLAOGroupEnsure.Tests.ps1

# Suppressing this rule because PlainText is required for one of the functions used in this test
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')]
param ()

<#
    AppVeyor build worker loads the SMO assembly which unable the tests to load the SMO stub classes.
    Running the tests in a Start-Job script block give us a clean environment. This is a workaround.
#>

$testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock {
    param
    (
        [System.String] $PSScriptRoot
    )
    $script:DSCModuleName      = 'xSQLServer' 
    $script:DSCResourceName    = 'MSFT_xSQLAOGroupEnsure' 

    #region HEADER

    # Unit Test Template Version: 1.1.0
    [String] $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 (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force

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

    #endregion HEADER

    try
    {

        #region Pester Test Initialization

        # Loading mocked classes
        Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs')

        $mockpassword = "dummyPassw0rd" | ConvertTo-SecureString -asPlainText -Force
        $mockusername = "dba" 
        $mockcredential = New-Object System.Management.Automation.PSCredential($mockusername,$mockpassword)

        #endregion Pester Test Initialization
        
        #region Get-TargetResource
        Describe 'Get-TargetResource' {
            Mock -CommandName Connect-SQL -MockWith {
                # build a custom object to return which is close to the real SMO object
                $smoObj = [PSCustomObject] @{
                    SQLServer = 'Node01'
                    SQLInstanceName = 'Prd01'
                    ClusterName = 'Clust01'
                }

                # add the AvailabilityGroups entry as this is an ArrayList and allows us the functionality later
                $smoObj | Add-Member -MemberType NoteProperty -Name 'AvailabilityGroups' -Value @{
                    'AG01' = @{
                        AvailabilityGroupListeners = @{ 
                            name = 'AgList01'
                            availabilitygrouplisteneripaddresses = [System.Collections.ArrayList] @(@{IpAddress = '192.168.0.1'; SubnetMask = '255.255.255.0'})
                            portnumber = 5022
                        }
                        
                        AvailabilityDatabases = @(
                            @{
                                name='AdventureWorks'
                            } 
                        )
                    }
                }

                $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType NoteProperty -Name Name -Value 'AG01' -Force
                $smoObj.AvailabilityGroups | Add-Member -MemberType ScriptMethod -Name 'Add' -Value {
                    return $true 
                } -Force

                $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name ToString -Value {
                    return 'AG01'
                } -Force

                $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name Drop -Value {
                    return $true
                } -Force

                return $smoObj
            }  -ModuleName $script:DSCResourceName
            
            Context "When the system is in the desired state" {
                $SqlAOGroup = Get-TargetResource -Ensure 'Present' -AvailabilityGroupName 'AG01' -SQLServer 'localhost' -SQLInstanceName 'MSSQLSERVER' -SetupCredential $mockcredential
        
                It 'Should return hashtable with Ensure = $true'{
                    $SqlAOGroup.Ensure | Should Be $true
                }
            }
        
            Context "When the system is not in the desired state" {
                $SqlAOGroup = Get-TargetResource -Ensure 'Absent' -AvailabilityGroupName 'AG01' -SQLServer 'localhost' -SQLInstanceName 'MSSQLSERVER' -SetupCredential $mockcredential
        
                It 'Should return hashtable with Ensure = $false' {
                    $SqlAOGroup.Ensure | Should Be $false
                }
            }
        }
        #endregion Get-TargetResource

        #region Test-TargetResource
        Describe 'Test-TargetResource' {
            Mock -CommandName Connect-SQL -MockWith {
                # build a custom object to return which is close to the real SMO object
                $smoObj = [PSCustomObject] @{
                    SQLServer = 'Node01'
                    SQLInstanceName = 'Prd01'
                    ClusterName = 'Clust01'
                }

                # add the AvailabilityGroups entry as this is an ArrayList and allows us the functionality later
                $smoObj | Add-Member -MemberType NoteProperty -Name 'AvailabilityGroups' -Value @{
                    'AG01' = @{
                        AvailabilityGroupListeners = @{ 
                            name = 'AgList01'
                            availabilitygrouplisteneripaddresses = [System.Collections.ArrayList] @(@{IpAddress = '192.168.0.1'; SubnetMask = '255.255.255.0'})
                            portnumber = 5022
                        }
                        
                        AvailabilityDatabases = @(
                            @{
                                name='AdventureWorks'
                            }
                        )
                    }
                }

                $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType NoteProperty -Name Name -Value 'AG01' -Force
                $smoObj.AvailabilityGroups | Add-Member -MemberType ScriptMethod -Name 'Add' -Value {
                    return $true
                } -Force

                $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name ToString -Value {
                    return 'AG01'
                } -Force

                $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name Drop -Value {
                    return $true
                } -Force

                return $smoObj
            } -ModuleName $script:DSCResourceName
        
            Context "When the system is in the desired state" {
                $SqlAOGroupTest = Test-TargetResource -Ensure 'Present' -AvailabilityGroupName 'AG01' -SQLServer 'localhost' -SQLInstanceName 'MSSQLSERVER' -SetupCredential $mockcredential
        
                It 'Should return $true'{
                    $SqlAOGroupTest | Should Be $true
                }
            }
        
            Context "When the system is not in the desired state" {
                $SqlAOGroupTest = Test-TargetResource -Ensure 'Absent' -AvailabilityGroupName 'AG01' -SQLServer 'localhost' -SQLInstanceName 'MSSQLSERVER' -SetupCredential $mockcredential
        
                It 'Should return $false' {
                    $SqlAOGroupTest | Should Be $false
                }
            }
        }
        #endregion Test-TargetResource

        Describe 'Set-TargetResource' {
            # Mocking the module FailoverCluster, to be able to mock the function Get-ClusterNode
            Get-Module -Name FailoverClusters | Remove-Module
            New-Module -Name FailoverClusters  -ScriptBlock {
                # This was generated by Write-ModuleStubFile function from the folder Tests\Unit\Stubs
                function Get-ClusterNode {
                    [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216215')]
                    param(
                        [Parameter(Position=0)]
                        [ValidateNotNullOrEmpty()]
                        [System.Collections.Specialized.StringCollection]
                        ${Name},

                        [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)]
                        [ValidateNotNull()]
                        [psobject]
                        ${InputObject},

                        [ValidateNotNullOrEmpty()]
                        [string]
                        ${Cluster}
                    )

                    throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand
                }
            
                Export-ModuleMember -Function *-Cluster*
            } | Import-Module -Force

            Mock Grant-ServerPerms -MockWith {} -ModuleName $script:DSCResourceName -Verifiable
            Mock New-ListenerADObject -MockWith {} -ModuleName $script:DSCResourceName -Verifiable
            Mock Get-ClusterNode -MockWith {
                $clusterNode = @(
                    [PSCustomObject] @{
                        Name = 'Node01'
                    }, 
                    [PSCustomObject] @{
                        Name = 'Node02'
                    },
                    [PSCustomObject] @{
                        Name = 'Node03'
                    },
                    [PSCustomObject] @{
                        Name = 'Node04'
                    }
                )
                return $clusterNode
            } -ModuleName $script:DSCResourceName -Verifiable

            Mock Connect-SQL -MockWith {
                # build a custom object to return which is close to the real SMO object
                $smoObj = [PSCustomObject] @{
                    SQLServer = 'Node01'
                    SQLInstanceName = 'Prd01'
                    ClusterName = 'Clust01'
                }

                # add the AvailabilityGroups entry as this is an ArrayList and allows us the functionality later
                $smoObj | Add-Member -MemberType NoteProperty -Name 'AvailabilityGroups' -Value @{
                    'AG01' = @{
                        AvailabilityGroupListeners = @{ 
                            name = 'AgList01'
                            availabilitygrouplisteneripaddresses = [System.Collections.ArrayList] @(@{IpAddress = '192.168.0.1'; SubnetMask = '255.255.255.0'})
                            portnumber = 5022
                        }

                        AvailabilityDatabases = @(
                            @{
                                name='AdventureWorks'
                            }
                        )
                    }
                }

                $smoObj.AvailabilityGroups | Add-Member -MemberType ScriptMethod -Name 'Add' -Value {return $true} -Force
                $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType NoteProperty -Name Name -Value 'AG01' -Force
                $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name ToString -Value {return 'AG01'} -Force
                $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name Drop -Value {return $true} -Force

                return $smoObj
            } -ModuleName $script:DSCResourceName -Verifiable

            Mock New-Object -MockWith {
                Param($TypeName)
                Switch ($TypeName)
                {
                    'Microsoft.SqlServer.Management.Smo.AvailabilityGroup' {
                        $object = [PSCustomObject] @{
                                    Name = "MockedObject"
                                    AutomatedBackupPreference = ''
                                    FailureConditionLevel = ''
                                    HealthCheckTimeout = ''
                                    AvailabilityReplicas = [System.Collections.ArrayList] @()
                                    AvailabilityGroupListeners = [System.Collections.ArrayList] @()
                                }
                        $object | Add-Member -MemberType ScriptMethod -Name Create -Value {return $true}
                    }

                    'Microsoft.SqlServer.Management.Smo.AvailabilityReplica' {
                        $object = [PSCustomObject] @{
                                    Name = "MockedObject"
                                    EndpointUrl = ''
                                    FailoverMode = ''
                                    AvailabilityMode = ''
                                    BackupPriority = 0
                                    ConnectionModeInPrimaryRole = ''
                                    ConnectionModeInSecondaryRole = ''
                                }
                    }

                    'Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener' {
                        $object = [PSCustomObject] @{
                                    Name = "MockedObject"
                                    PortNumber = ''
                                    AvailabilityGroupListenerIPAddresses = [System.Collections.ArrayList] @()
                                }
                    }

                    'Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress' {
                        $object = [PSCustomObject] @{
                                    Name = "MockedObject"
                                    IsDHCP = ''
                                    IPAddress = ''
                                    SubnetMask = ''
                                }
                    }

                    Default {
                        $object = [PSCustomObject] @{
                                    Name = "MockedObject"
                                }
                    }
                }

                return $object
            } -ModuleName $script:DSCResourceName -Verifiable

            Context "When the system is not in the desired state" {
                $params = @{
                    Ensure = 'Present'
                    AvailabilityGroupName = 'AG01'
                    AvailabilityGroupNameListener = 'AgList01'
                    AvailabilityGroupNameIP = '192.168.0.1'
                    AvailabilityGroupSubMask = '255.255.255.0'
                    AvailabilityGroupPort = 1433
                    ReadableSecondary = 'ReadOnly'
                    AutoBackupPreference = 'Primary'
                    SQLServer = 'localhost'
                    SQLInstanceName = 'MSSQLSERVER'
                    SetupCredential = $mockcredential
                }

                It 'Should not throw when calling Set-method' {
                    { Set-TargetResource @Params } | Should Not Throw
                }
            }
        }
    }
    finally
    {
        #region FOOTER

        Restore-TestEnvironment -TestEnvironment $TestEnvironment 

        #endregion
    }
}

$testJob | Receive-Job -Wait