From d6429549c0c48d511013252ed38268a5690cdb39 Mon Sep 17 00:00:00 2001 From: Andrii Sydorenko Date: Wed, 29 Nov 2023 14:29:34 +0100 Subject: [PATCH 1/9] Add DtcSupportEnabled to check --- source/DSCResources/DSC_SqlAG/DSC_SqlAG.psm1 | 1 + 1 file changed, 1 insertion(+) diff --git a/source/DSCResources/DSC_SqlAG/DSC_SqlAG.psm1 b/source/DSCResources/DSC_SqlAG/DSC_SqlAG.psm1 index 921b0b9f5..d61cb60a4 100644 --- a/source/DSCResources/DSC_SqlAG/DSC_SqlAG.psm1 +++ b/source/DSCResources/DSC_SqlAG/DSC_SqlAG.psm1 @@ -753,6 +753,7 @@ function Test-TargetResource { $parametersToCheck += 'BasicAvailabilityGroup' $parametersToCheck += 'DatabaseHealthTrigger' + $parametersToCheck += 'DtcSupportEnabled' if ( $getTargetResourceResult.SeedingMode ) { $parametersToCheck += 'SeedingMode' From d659c528f346efcfb926308793a517e879f4f274 Mon Sep 17 00:00:00 2001 From: Andrii Sydorenko Date: Thu, 14 Dec 2023 13:39:25 +0100 Subject: [PATCH 2/9] fix test --- tests/Unit/DSC_SqlAGReplica.Tests.ps1 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/Unit/DSC_SqlAGReplica.Tests.ps1 b/tests/Unit/DSC_SqlAGReplica.Tests.ps1 index b421097c5..dc0788790 100644 --- a/tests/Unit/DSC_SqlAGReplica.Tests.ps1 +++ b/tests/Unit/DSC_SqlAGReplica.Tests.ps1 @@ -574,8 +574,9 @@ Describe 'SqlAGReplica\Set-TargetResource' { Ensure = 'Absent' } - $mockErrorMessage = $script:localizedData.RemoveAvailabilityGroupReplicaFailed -f $setTargetResourceParameters.Name, $setTargetResourceParameters.AvailabilityGroupName, $setTargetResourceParameters.InstanceName - + $mockErrorMessage = Get-InvalidOperationRecord -Message ( + ($script:localizedData.FailedRemoveAvailabilityGroupReplica -f $setTargetResourceParameters.Name, $setTargetResourceParameters.AvailabilityGroupName, $setTargetResourceParameters.InstanceName) + "*" + ) { Set-TargetResource @setTargetResourceParameters } | Should -Throw -ExpectedMessage $mockErrorMessage } From 6c7a6f9d2808263e4ac986d7ab3553c3533f728f Mon Sep 17 00:00:00 2001 From: Andrii Sydorenko Date: Thu, 14 Dec 2023 15:28:24 +0100 Subject: [PATCH 3/9] Add DTCSupportEnabled support if AG is Present --- source/DSCResources/DSC_SqlAG/DSC_SqlAG.psm1 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/DSCResources/DSC_SqlAG/DSC_SqlAG.psm1 b/source/DSCResources/DSC_SqlAG/DSC_SqlAG.psm1 index d61cb60a4..40feae1a7 100644 --- a/source/DSCResources/DSC_SqlAG/DSC_SqlAG.psm1 +++ b/source/DSCResources/DSC_SqlAG/DSC_SqlAG.psm1 @@ -464,6 +464,12 @@ function Set-TargetResource Update-AvailabilityGroup -AvailabilityGroup $availabilityGroup } + if ( ( $submittedParameters -contains 'DtcSupportEnabled' ) -and ( $sqlMajorVersion -ge 13 ) -and ( $DtcSupportEnabled -ne $availabilityGroup.DtcSupportEnabled ) ) + { + $availabilityGroup.DtcSupportEnabled = $DtcSupportEnabled + Update-AvailabilityGroup -AvailabilityGroup $availabilityGroup + } + # Make sure ConnectionModeInPrimaryRole has a value in order to avoid false positive matches when the parameter is not defined if ( ( $submittedParameters -contains 'ConnectionModeInPrimaryRole' ) -and ( -not [System.String]::IsNullOrEmpty($ConnectionModeInPrimaryRole) ) -and ( $ConnectionModeInPrimaryRole -ne $availabilityGroup.AvailabilityReplicas[$serverObject.DomainInstanceName].ConnectionModeInPrimaryRole ) ) { From 9e34a521c4061cdc636218818a21cdcd45e65dbd Mon Sep 17 00:00:00 2001 From: Andrii Sydorenko Date: Thu, 14 Dec 2023 16:29:30 +0100 Subject: [PATCH 4/9] SQLAG pester 5 tests --- tests/Unit/DSC_SqlAG.Tests.ps1 | 2533 +++++++++++++++++--------------- 1 file changed, 1344 insertions(+), 1189 deletions(-) diff --git a/tests/Unit/DSC_SqlAG.Tests.ps1 b/tests/Unit/DSC_SqlAG.Tests.ps1 index 1fda73717..004dae5f4 100644 --- a/tests/Unit/DSC_SqlAG.Tests.ps1 +++ b/tests/Unit/DSC_SqlAG.Tests.ps1 @@ -1,25 +1,41 @@ <# .SYNOPSIS - Automated unit test for DSC_SqlAG DSC resource. + Unit test for DSC_SqlAG DSC resource. #> -return +# Suppressing this rule because Script Analyzer does not understand Pester's syntax. +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +# Suppressing this rule because tests are mocking passwords in clear text. +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] +param () -Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') - -$script:dscModuleName = 'SqlServerDsc' -$script:dscResourceName = 'DSC_SqlAG' - -function Invoke-TestSetup -{ +BeforeDiscovery { try { - Import-Module -Name DscResource.Test -Force -ErrorAction 'Stop' + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } } catch [System.IO.FileNotFoundException] { - throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -Tasks build" first.' + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + $script:dscResourceName = 'DSC_SqlAG' + + $env:SqlServerDscCI = $true $script:testEnvironment = Initialize-TestEnvironment ` -DSCModuleName $script:dscModuleName ` @@ -27,1399 +43,1538 @@ function Invoke-TestSetup -ResourceType 'Mof' ` -TestType 'Unit' + Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') + # Loading mocked classes Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SMO.cs') - # Load the default SQL Module stub - Import-SQLModuleStub -} + # Load the correct SQL Module stub + $script:stubModuleName = Import-SQLModuleStub -PassThru -function Invoke-TestCleanup -{ - Restore-TestEnvironment -TestEnvironment $script:testEnvironment + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscResourceName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscResourceName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscResourceName } -Invoke-TestSetup +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') -try -{ - InModuleScope $script:dscResourceName { - # This is relative to the path of the resource module script, not test test script. - $script:moduleRoot = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent - Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'Tests' -ChildPath (Join-Path -Path 'TestHelpers' -ChildPath 'CommonTestHelper.psm1'))) -Force + Restore-TestEnvironment -TestEnvironment $script:testEnvironment - #region parameter mocks + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscResourceName -All | Remove-Module -Force - # Define the values that could be passed into the Name parameter - $mockNameParameters = @{ - AbsentAvailabilityGroup = 'AvailabilityGroup2' - CreateAvailabilityGroupFailed = 'AvailabilityGroup5' - CreateAvailabilityGroupReplicaFailed = 'AvailabilityGroup4' - PresentAvailabilityGroup = 'AvailabilityGroup1' - RemoveAvailabilityGroupFailed = 'AvailabilityGroup3' - } + # Unload the stub module. + Remove-SqlModuleStub -Name $script:stubModuleName - # Define the values that could be passed into the ServerName parameter - $mockSqlServerParameters = @{ - Server1 = @{ - FQDN = 'Server1.contoso.com' - #IP = '192.168.1.1' - #NetBIOS = 'Server1' - } + # Remove module common test helper. + Get-Module -Name 'CommonTestHelper' -All | Remove-Module -Force - Server2 = @{ - FQDN = 'Server2.contoso.com' - #IP = '192.168.1.2' - #NetBIOS = 'Server2' + Remove-Item -Path 'env:SqlServerDscCI' +} +Describe 'SqlAG\Get-TargetResource' { + BeforeAll { + $mockConnectSqlServer1 = { + # Mock the server object + $mockServerObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server + $mockServerObject.Name = 'Server1' + $mockServerObject.NetName = 'Server1' + $mockServerObject.DomainInstanceName = 'Server1' + $mockServerObject.IsHadrEnabled = $true + $mockServerObject.ServiceName = 'MSSQLSERVER' + $mockServerObject.Version = @{ + Major = 13 } - } - - # Define the values that could be passed into the InstanceName parameter - $mockInstanceNameParameters = @( - 'MSSQLSERVER', - 'NamedInstance' - ) - $mockProcessOnlyOnActiveNode = $true - - #endregion parameter mocks + # Mock the availability group replicas + $mockAvailabilityGroupReplica1 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica1.AvailabilityMode = 'AsynchronousCommit' + $mockAvailabilityGroupReplica1.BackupPriority = 50 + $mockAvailabilityGroupReplica1.ConnectionModeInPrimaryRole = 'AllowAllConnections' + $mockAvailabilityGroupReplica1.ConnectionModeInSecondaryRole = 'AllowNoConnections' + $mockAvailabilityGroupReplica1.EndpointUrl = 'TCP://Server1:5022' + $mockAvailabilityGroupReplica1.FailoverMode = 'Manual' + $mockAvailabilityGroupReplica1.Name = 'Server1' + $mockAvailabilityGroupReplica1.ReadOnlyRoutingConnectionUrl = 'TCP://Server1.domain.com:1433' + $mockAvailabilityGroupReplica1.ReadOnlyRoutingList = @('Server1', 'Server2') + $mockAvailabilityGroupReplica1.SeedingMode = 'Manual' + + $mockAvailabilityGroupReplica2 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica2.AvailabilityMode = 'AsynchronousCommit' + $mockAvailabilityGroupReplica2.BackupPriority = 50 + $mockAvailabilityGroupReplica2.ConnectionModeInPrimaryRole = 'AllowAllConnections' + $mockAvailabilityGroupReplica2.ConnectionModeInSecondaryRole = 'AllowNoConnections' + $mockAvailabilityGroupReplica2.EndpointUrl = 'TCP://Server2:5022' + $mockAvailabilityGroupReplica2.FailoverMode = 'Manual' + $mockAvailabilityGroupReplica2.Name = 'Server2' + $mockAvailabilityGroupReplica2.ReadOnlyRoutingConnectionUrl = 'TCP://Server2.domain.com:1433' + $mockAvailabilityGroupReplica2.ReadOnlyRoutingList = @('Server1', 'Server2') + $mockAvailabilityGroupReplica2.SeedingMode = 'Manual' + + $mockAvailabilityGroupReplica3 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica3.AvailabilityMode = 'AsynchronousCommit' + $mockAvailabilityGroupReplica3.BackupPriority = 50 + $mockAvailabilityGroupReplica3.ConnectionModeInPrimaryRole = 'AllowAllConnections' + $mockAvailabilityGroupReplica3.ConnectionModeInSecondaryRole = 'AllowNoConnections' + $mockAvailabilityGroupReplica3.EndpointUrl = 'TCP://Server3:5022' + $mockAvailabilityGroupReplica3.FailoverMode = 'Manual' + $mockAvailabilityGroupReplica3.Name = 'Server3' + $mockAvailabilityGroupReplica3.ReadOnlyRoutingConnectionUrl = 'TCP://Server3.domain.com:1433' + $mockAvailabilityGroupReplica3.ReadOnlyRoutingList = @('Server1', 'Server2') + $mockAvailabilityGroupReplica3.SeedingMode = 'Manual' + + # Mock the availability groups + $mockAvailabilityGroup1 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroup + $mockAvailabilityGroup1.Name = 'AG_AllServers' + $mockAvailabilityGroup1.PrimaryReplicaServerName = 'Server2' + $mockAvailabilityGroup1.LocalReplicaRole = 'Secondary' + $mockAvailabilityGroup1.AutomatedBackupPreference = 'Secondary' + $mockAvailabilityGroup1.FailureConditionLevel = 'OnCriticalServerError' + $mockAvailabilityGroup1.BasicAvailabilityGroup = $true + $mockAvailabilityGroup1.DatabaseHealthTrigger = $true + $mockAvailabilityGroup1.DtcSupportEnabled = $true + $mockAvailabilityGroup1.AvailabilityReplicas.Add($mockAvailabilityGroupReplica1) + $mockAvailabilityGroup1.AvailabilityReplicas.Add($mockAvailabilityGroupReplica2) + $mockAvailabilityGroup1.AvailabilityReplicas.Add($mockAvailabilityGroupReplica3) + + $mockServerObject.AvailabilityGroups.Add($mockAvailabilityGroup1) + + $mockEndpoint = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Endpoint + $mockEndpoint.EndpointType = 'DatabaseMirroring' + $mockEndpoint.Protocol = @{ + TCP = @{ + ListenerPort = 5022 + } + } - #region property mocks + $mockServerObject.Endpoints.Add($mockEndpoint) - $mockIsHadrEnabled = $true - $mockIsDatabaseMirroringEndpointPresent = $true + return $mockServerObject + } - $mockServerObjectProperties = @{ - Server1 = @{ - NetName = 'Server1' + $mockConnectSqlServer2 = { + # Mock the server object + $mockServerObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server + $mockServerObject.Name = 'Server2' + $mockServerObject.NetName = 'Server2' + $mockServerObject.DomainInstanceName = 'Server2' + $mockServerObject.IsHadrEnabled = $true + $mockServerObject.ServiceName = 'MSSQLSERVER' + $mockServerObject.Version = @{ + Major = 12 } - Server2 = @{ - NetName = 'Server2' + # Mock the availability group replicas + $mockAvailabilityGroupReplica1 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica1.AvailabilityMode = 'AsynchronousCommit' + $mockAvailabilityGroupReplica1.BackupPriority = 50 + $mockAvailabilityGroupReplica1.ConnectionModeInPrimaryRole = 'AllowAllConnections' + $mockAvailabilityGroupReplica1.ConnectionModeInSecondaryRole = 'AllowNoConnections' + $mockAvailabilityGroupReplica1.EndpointUrl = 'TCP://Server1:5022' + $mockAvailabilityGroupReplica1.FailoverMode = 'Manual' + $mockAvailabilityGroupReplica1.Name = 'Server1' + $mockAvailabilityGroupReplica1.ReadOnlyRoutingConnectionUrl = 'TCP://Server1.domain.com:1433' + $mockAvailabilityGroupReplica1.ReadOnlyRoutingList = @('Server1', 'Server2') + + $mockAvailabilityGroupReplica2 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica2.AvailabilityMode = 'AsynchronousCommit' + $mockAvailabilityGroupReplica2.BackupPriority = 50 + $mockAvailabilityGroupReplica2.ConnectionModeInPrimaryRole = 'AllowAllConnections' + $mockAvailabilityGroupReplica2.ConnectionModeInSecondaryRole = 'AllowNoConnections' + $mockAvailabilityGroupReplica2.EndpointUrl = 'TCP://Server2:5022' + $mockAvailabilityGroupReplica2.FailoverMode = 'Manual' + $mockAvailabilityGroupReplica2.Name = 'Server2' + $mockAvailabilityGroupReplica2.ReadOnlyRoutingConnectionUrl = 'TCP://Server2.domain.com:1433' + $mockAvailabilityGroupReplica2.ReadOnlyRoutingList = @('Server1', 'Server2') + $mockAvailabilityGroupReplica2.SeedingMode = 'Manual' + + $mockAvailabilityGroupReplica3 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica3.AvailabilityMode = 'AsynchronousCommit' + $mockAvailabilityGroupReplica3.BackupPriority = 50 + $mockAvailabilityGroupReplica3.ConnectionModeInPrimaryRole = 'AllowAllConnections' + $mockAvailabilityGroupReplica3.ConnectionModeInSecondaryRole = 'AllowNoConnections' + $mockAvailabilityGroupReplica3.EndpointUrl = 'TCP://Server3:5022' + $mockAvailabilityGroupReplica3.FailoverMode = 'Manual' + $mockAvailabilityGroupReplica3.Name = 'Server3' + $mockAvailabilityGroupReplica3.ReadOnlyRoutingConnectionUrl = 'TCP://Server3.domain.com:1433' + $mockAvailabilityGroupReplica3.ReadOnlyRoutingList = @('Server1', 'Server2') + $mockAvailabilityGroupReplica3.SeedingMode = 'Manual' + + # Mock the availability groups + $mockAvailabilityGroup1 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroup + $mockAvailabilityGroup1.Name = 'AG_AllServers' + $mockAvailabilityGroup1.PrimaryReplicaServerName = 'Server2' + $mockAvailabilityGroup1.LocalReplicaRole = 'Secondary' + $mockAvailabilityGroup1.AutomatedBackupPreference = 'Secondary' + $mockAvailabilityGroup1.FailureConditionLevel = 'OnCriticalServerError' + $mockAvailabilityGroup1.AvailabilityReplicas.Add($mockAvailabilityGroupReplica1) + $mockAvailabilityGroup1.AvailabilityReplicas.Add($mockAvailabilityGroupReplica2) + $mockAvailabilityGroup1.AvailabilityReplicas.Add($mockAvailabilityGroupReplica3) + + $mockServerObject.AvailabilityGroups.Add($mockAvailabilityGroup1) + + $mockEndpoint = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Endpoint + $mockEndpoint.EndpointType = 'DatabaseMirroring' + $mockEndpoint.Protocol = @{ + TCP = @{ + ListenerPort = 5022 + } } - } - - $mockAvailabilityGroupProperties = @{ - AutomatedBackupPreference = 'Secondary' # Not the default parameter value - BasicAvailabilityGroup = $true # Not the default parameter value - DatabaseHealthTrigger = $true # Not the default parameter value - DtcSupportEnabled = $true # Not the default parameter value - FailureConditionLevel = 'OnCriticalServerErrors' # Not the default parameter value - HealthCheckTimeout = 20000 - Name = $mockNameParameters.PresentAvailabilityGroup - PrimaryReplicaServerName = $mockServerObjectProperties.Server1.NetName - } - $mockAvailabilityGroupReplicaProperties = @{ - Server1 = @{ - AvailabilityMode = 'SynchronousCommit' # Not the default parameter value - BackupPriority = 49 # Not the default parameter value - ConnectionModeInPrimaryRole = 'AllowAllConnections' # Not the default parameter value - ConnectionModeInSecondaryRole = 'AllowNoConnections' # Not the default parameter value - EndpointHostName = $mockServerObjectProperties.Server1.NetName - EndpointProtocol = 'TCP' - EndpointPort = 5022 - FailoverMode = 'Automatic' # Not the default parameter value - Name = $mockServerObjectProperties.Server1.NetName - Role = 'Primary' - } - - Server2 = @{ - AvailabilityMode = 'SynchronousCommit' # Not the default parameter value - BackupPriority = 49 # Not the default parameter value - ConnectionModeInPrimaryRole = 'AllowAllConnections' # Not the default parameter value - ConnectionModeInSecondaryRole = 'AllowNoConnections' # Not the default parameter value - EndpointHostName = $mockServerObjectProperties.Server2.NetName - EndpointProtocol = 'TCP' - EndpointPort = 5022 - FailoverMode = 'Automatic' # Not the default parameter value - Name = $mockServerObjectProperties.Server2.NetName - Role = 'Primary' - } - } + $mockServerObject.Endpoints.Add($mockEndpoint) - $mockDatabaseMirroringEndpointProperties = @{ - Protocol = 'TCP' - ListenerPort = 5022 + return $mockServerObject } - #endregion property mocks - - #region test cases - - $getTargetResourceAbsentTestCases = @() - $getTargetResourcePresentTestCases = @() - $setTargetResourceCreateAvailabilityGroupFailedTestCases = @() - $setTargetResourceCreateAvailabilityGroupWithParameterTestCases = @() - $setTargetResourcesEndpointUrlTestCases = @() - $setTargetResourceEndpointMissingTestCases = @() - $setTargetResourceHadrDisabledTestCases = @() - $setTargetResourcePropertyIncorrectTestCases = @() - $setTargetResourceRemoveAvailabilityGroupTestCases = @() - $setTargetResourceRemoveAvailabilityGroupErrorTestCases = @() - $testTargetResourceAbsentTestCases = @() - $testTargetResourceEndpointIncorrectTestCases = @() - $testTargetResourcePresentTestCases = @() - $testTargetResourcePropertyIncorrectTestCases = @() - $testTargetResourceProcessOnlyOnActiveNodeTestCases = @() - - $majorVersionsToTest = @(12,13) - $ensureCasesToTest = @('Absent','Present') - $endpointUrlPropertiesToTest = @('EndpointPort','EndpointProtocol') - - $createAvailabilityGroupFailuresToTest = @{ - $mockNameParameters.CreateAvailabilityGroupFailed = 'CreateAvailabilityGroupFailed' - $mockNameParameters.CreateAvailabilityGroupReplicaFailed = 'CreateAvailabilityGroupReplicaFailed' + Mock -CommandName Connect-SQL -MockWith $mockConnectSqlServer1 -ParameterFilter { + $ServerName -eq 'Server1' } - - # Get all of the parameters tied with the resource except the required parameters, Ensure, and DtcSupportEnabled - $resourceParameters = @{} - ( Get-Command -Name Test-TargetResource ).Parameters.Values | Where-Object -FilterScript { - ( - # Ignore these specific parameters. These get tested enough. - @('Ensure', 'Name', 'ServerName', 'InstanceName', 'DtcSupportEnabled', 'ProcessOnlyOnActiveNode') -notcontains $_.Name - ) -and ( - # Ignore the CmdletBinding parameters - $_.Attributes.TypeId.Name -notcontains 'AliasAttribute' - ) - } | ForEach-Object -Process { - $currentParameter = $_.Name - - $resourceParameters.Add( - $currentParameter, - ( @($mockAvailabilityGroupProperties.$currentParameter,$mockAvailabilityGroupReplicaProperties.Server1.$currentParameter) -join '' ) - ) + Mock -CommandName Connect-SQL -MockWith $mockConnectSqlServer2 -ParameterFilter { + $ServerName -eq 'Server2' } + } - # Define the properties that are SQL 2016 and newer - $sql13AvailabilityGroupProperties = @( - 'BasicAvailabilityGroup' - 'DatabaseHealthTrigger' - 'DtcSupportEnabled' - ) - - foreach ( $majorVersionToTest in $majorVersionsToTest ) - { - foreach ( $mockSqlServer in $mockSqlServerParameters.GetEnumerator() ) - { - # Determine with SQL Server mock will be used - $mockSqlServerToBeUsed = $mockSqlServer.Key - - foreach ( $mockSqlServerParameter in $mockSqlServer.Value.Values ) - { - foreach ( $mockInstanceNameParameter in $mockInstanceNameParameters ) - { - # Build the domain instance name - if ( $mockInstanceNameParameter -eq 'MSSQLSERVER' ) - { - $domainInstanceNameProperty = $mockSqlServerParameter - } - else - { - $domainInstanceNameProperty = '{0}\{1}' -f $mockSqlServerParameter,$mockInstanceNameParameter - } - - if ( $mockSqlServerToBeUsed -eq 'Server1' ) - { - $getTargetResourceAbsentTestCases += @{ - Name = $mockNameParameters.AbsentAvailabilityGroup - ServerName = $mockSqlServerParameter - InstanceName = $mockInstanceNameParameter - Version = $majorVersionToTest - } - - $getTargetResourcePresentTestCases += @{ - Name = $mockNameParameters.PresentAvailabilityGroup - ServerName = $mockSqlServerParameter - InstanceName = $mockInstanceNameParameter - Version = $majorVersionToTest - } - - foreach ( $createAvailabilityGroupFailureToTest in $createAvailabilityGroupFailuresToTest.GetEnumerator() ) - { - $setTargetResourceCreateAvailabilityGroupFailedTestCases += @{ - ErrorResult = $createAvailabilityGroupFailureToTest.Value - Ensure = 'Present' - Name = $createAvailabilityGroupFailureToTest.Key - ServerName = $mockSqlServerParameter - InstanceName = $mockInstanceNameParameter - Version = $majorVersionToTest - } - } - - $setTargetResourceEndpointMissingTestCases += @{ - Ensure = 'Present' - Name = $mockNameParameters.AbsentAvailabilityGroup - Result = $script:localizedData.DatabaseMirroringEndpointNotFound -f ('{0}\{1}' -f $mockSqlServerParameter, $mockInstanceNameParameter) - ServerName = $mockSqlServerParameter - InstanceName = $mockInstanceNameParameter - Version = $majorVersionToTest - } - - $setTargetResourceHadrDisabledTestCases += @{ - Ensure = 'Present' - Name = $mockNameParameters.AbsentAvailabilityGroup - Result = $script:localizedData.HadrNotEnabled - ServerName = $mockSqlServerParameter - InstanceName = $mockInstanceNameParameter - Version = $majorVersionToTest - } - - $setTargetResourceRemoveAvailabilityGroupTestCases += @{ - Ensure = 'Absent' - Name = $mockNameParameters.PresentAvailabilityGroup - ServerName = $mockSqlServerParameter - InstanceName = $mockInstanceNameParameter - Version = $majorVersionToTest - } - } - - switch ( $mockSqlServerToBeUsed ) - { - 'Server1' - { - $setTargetResourceRemoveAvailabilityGroupErrorTestCaseErrorResult = 'RemoveAvailabilityGroupFailed' - $setTargetResourceRemoveAvailabilityGroupErrorTestCaseName = $mockNameParameters.RemoveAvailabilityGroupFailed - } - - 'Server2' - { - $setTargetResourceRemoveAvailabilityGroupErrorTestCaseErrorResult = 'InstanceNotPrimaryReplica' - $setTargetResourceRemoveAvailabilityGroupErrorTestCaseName = $mockNameParameters.PresentAvailabilityGroup - } - } - - $setTargetResourceRemoveAvailabilityGroupErrorTestCases += @{ - ErrorResult = $setTargetResourceRemoveAvailabilityGroupErrorTestCaseErrorResult - Ensure = 'Absent' - Name = $setTargetResourceRemoveAvailabilityGroupErrorTestCaseName - ServerName = $mockSqlServerParameter - InstanceName = $mockInstanceNameParameter - Version = $majorVersionToTest - } - - foreach ( $processOnlyOnActiveNode in @($true,$false) ) - { - $testTargetResourceProcessOnlyOnActiveNodeTestCases += @{ - Ensure = 'Present' - Name = $mockNameParameters.PresentAvailabilityGroup - ProcessOnlyOnActiveNode = $processOnlyOnActiveNode - ServerName = $mockSqlServerParameter - InstanceName = $mockInstanceNameParameter - Version = $majorVersionToTest - } - } + Context 'When the Availability Group is absent' { + It 'Should not return an Availability Group' { + InModuleScope -ScriptBlock { + $getTargetResourceParameters = @{ + Name = 'AbsentAG' + ServerName = 'Server1' + InstanceName = 'MSSQLSERVER' + } - # Create test cases for Absent/Present - foreach ( $ensureCaseToTest in $ensureCasesToTest ) - { - $testTargetResourceAbsentTestCases += @{ - Ensure = $ensureCaseToTest - Name = $mockNameParameters.AbsentAvailabilityGroup - Result = ( $ensureCaseToTest -eq 'Absent' ) - ServerName = $mockSqlServerParameter - InstanceName = $mockInstanceNameParameter - Version = $majorVersionToTest - } - - $testTargetResourcePresentTestCases += @{ - Ensure = $ensureCaseToTest - Name = $mockNameParameters.PresentAvailabilityGroup - Result = ( $ensureCaseToTest -eq 'Present' ) - ServerName = $mockSqlServerParameter - InstanceName = $mockInstanceNameParameter - Version = $majorVersionToTest - } - } + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + + $getTargetResourceResult.Name | Should -Be 'AbsentAG' + $getTargetResourceResult.ServerName | Should -Be 'Server1' + $getTargetResourceResult.InstanceName | Should -Be 'MSSQLSERVER' + $getTargetResourceResult.Ensure | Should -Be 'Absent' + $getTargetResourceResult.IsActiveNode | Should -BeTrue + $getTargetResourceResult.AutomatedBackupPreference | Should -BeNullOrEmpty + $getTargetResourceResultAvailabilityMode | Should -BeNullOrEmpty + $getTargetResourceResultBackupPriority | Should -BeNullOrEmpty + $getTargetResourceResultConnectionModeInPrimaryRole | Should -BeNullOrEmpty + $getTargetResourceResultConnectionModeInSecondaryRole | Should -BeNullOrEmpty + $getTargetResourceResultFailureConditionLevel | Should -BeNullOrEmpty + $getTargetResourceResultFailoverMode | Should -BeNullOrEmpty + $getTargetResourceResultHealthCheckTimeout | Should -BeNullOrEmpty + $getTargetResourceResultEndpointURL | Should -BeNullOrEmpty + $getTargetResourceResultEndpointPort | Should -BeNullOrEmpty + $getTargetResourceResultEndpointHostName | Should -BeNullOrEmpty + $getTargetResourceResultVersion | Should -BeNullOrEmpty + } - # Create Present test cases for each parameter - foreach ( $resourceParameter in $resourceParameters.GetEnumerator() ) - { - # Move on if we're testing a version less than 13 and it's a property that was only introduced in 13 - if ( ( $sql13AvailabilityGroupProperties -contains $resourceParameter.Key ) -and ( $majorVersionToTest -lt 13 ) ) - { - # Move to the next parameter - continue - } - - # Get the current parameter object - $currentParameterObject = ( Get-Command Test-TargetResource ).Parameters.($resourceParameter.Key) - - switch ( $currentParameterObject.ParameterType.ToString() ) - { - 'System.Boolean' - { - # Get the opposite value of what is supplied - $testCaseParameterValue = -not $resourceParameter.Value - } - - 'System.UInt32' - { - # Change the supplied number to something else. Absolute value is to protect against zero minus 1 - $testCaseParameterValue = [System.Math]::Abs( ( $resourceParameter.Value - 1 ) ) - } - - 'System.String' - { - # Get the valid values for the current parameter - $currentParameterValidValues = $currentParameterObject.Attributes.ValidValues - - # Select a value other than what is defined in the mocks - $testCaseParameterValue = $currentParameterValidValues | Where-Object -FilterScript { - $_ -ne $resourceParameter.Value - } | Select-Object -First 1 - - # If the value is null or empty, set it to something - if ( [System.String]::IsNullOrEmpty($testCaseParameterValue) ) - { - $testCaseParameterValue = 'AnotherHostName' - } - } - - default - { - $testCaseParameterValue = $null - } - } - - if ( $mockSqlServerToBeUsed -eq 'Server1' ) - { - $setTargetResourceCreateAvailabilityGroupWithParameterTestCases += @{ - DomainInstanceName = $domainInstanceNameProperty - Ensure = 'Present' - Name = $mockNameParameters.AbsentAvailabilityGroup - ParameterName = $resourceParameter.Key - ParameterValue = $testCaseParameterValue - ServerName = $mockSqlServerParameter - InstanceName = $mockInstanceNameParameter - Version = $majorVersionToTest - } - } - - $setTargetResourcePropertyIncorrectTestCases += @{ - Ensure = 'Present' - Name = $mockNameParameters.PresentAvailabilityGroup - ParameterName = $resourceParameter.Key - ParameterValue = $testCaseParameterValue - Result = $false - ServerName = $mockSqlServerParameter - InstanceName = $mockInstanceNameParameter - Version = $majorVersionToTest - } - - $testTargetResourcePropertyIncorrectTestCases += @{ - Ensure = 'Present' - Name = $mockNameParameters.PresentAvailabilityGroup - ParameterName = $resourceParameter.Key - ParameterValue = $testCaseParameterValue - Result = $false - ServerName = $mockSqlServerParameter - InstanceName = $mockInstanceNameParameter - Version = $majorVersionToTest - } - } + Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It + } + } - # Create Present test cases for the endpoint components - foreach ( $endpointProperty in $endpointUrlPropertiesToTest ) - { - switch ( $mockAvailabilityGroupReplicaProperties.Server1.$endpointProperty.GetType().ToString() ) - { - 'System.Int32' - { - # Change the supplied number to something else. Absolute value is to protect against zero minus 1 - $endpointPropertyValue = [System.Math]::Abs( ( $mockAvailabilityGroupReplicaProperties.Server1.$endpointProperty - 1 ) ) - } - - 'System.String' - { - $endpointPropertyValue = 'UDP' - } - } - - $setTargetResourcesEndpointUrlTestCases += @{ - EndpointPropertyName = $endpointProperty - EndpointPropertyValue = $endpointPropertyValue - Ensure = 'Present' - Name = $mockNameParameters.PresentAvailabilityGroup - ServerName = $mockSqlServerParameter - InstanceName = $mockInstanceNameParameter - Version = $majorVersionToTest - } - - $testTargetResourceEndpointIncorrectTestCases += @{ - EndpointPropertyName = $endpointProperty - EndpointPropertyValue = $endpointPropertyValue - Ensure = 'Present' - Name = $mockNameParameters.PresentAvailabilityGroup - Result = $false - ServerName = $mockSqlServerParameter - InstanceName = $mockInstanceNameParameter - Version = $majorVersionToTest - } - } + Context 'When the Availability Group is present' { + Context 'When SQL server version is 13 and higher' { + It 'Should return an Availability Group' { + InModuleScope -ScriptBlock { + $getTargetResourceParameters = @{ + Name = 'AG_AllServers' + ServerName = 'Server1' + InstanceName = 'MSSQLSERVER' } + + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + + $getTargetResourceResult.Name | Should -Be 'AG_AllServers' + $getTargetResourceResult.ServerName | Should -Be 'Server1' + $getTargetResourceResult.InstanceName | Should -Be 'MSSQLSERVER' + $getTargetResourceResult.Ensure | Should -Be 'Present' + $getTargetResourceResult.IsActiveNode | Should -Be 'True' + $getTargetResourceResult.AutomatedBackupPreference | Should -Be 'Secondary' + $getTargetResourceResult.AvailabilityMode | Should -Be 'AsynchronousCommit' + $getTargetResourceResult.BackupPriority | Should -Be 50 + $getTargetResourceResult.ConnectionModeInPrimaryRole | Should -Be 'AllowAllConnections' + $getTargetResourceResult.ConnectionModeInSecondaryRole | Should -Be 'AllowNoConnections' + $getTargetResourceResult.FailureConditionLevel | Should -Be 'OnCriticalServerError' + $getTargetResourceResult.FailoverMode | Should -Be 'Manual' + $getTargetResourceResult.HealthCheckTimeout | Should -BeNullOrEmpty + $getTargetResourceResult.EndpointURL | Should -Be 'TCP://Server1:5022' + $getTargetResourceResult.EndpointPort | Should -Be 5022 + $getTargetResourceResult.EndpointHostName | Should -Be 'Server1' + $getTargetResourceResult.BasicAvailabilityGroup | Should -Be 'True' + $getTargetResourceResult.DatabaseHealthTrigger | Should -Be 'True' + $getTargetResourceResult.DtcSupportEnabled | Should -Be 'True' + $getTargetResourceResult.Version | Should -Be 13 } + Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It } } + Context 'When SQL server version is 12' { + It 'Should return an Availability Group' { + InModuleScope -ScriptBlock { + $getTargetResourceParameters = @{ + Name = 'AG_AllServers' + ServerName = 'Server2' + InstanceName = 'MSSQLSERVER' + } - #endregion test cases - - #region cmdlet mocks - - $mockConnectSql = { - param - ( - [Parameter()] - [System.String] - $ServerName, - - [Parameter()] - [System.String] - $InstanceName, - - # The following two parameters are used to mock Get-PrimaryReplicaServerObject - [Parameter()] - [Microsoft.SqlServer.Management.Smo.AvailabilityGroup] - $AvailabilityGroup, - - [Parameter()] - [Microsoft.SqlServer.Management.Smo.Server] - $ServerObject - ) - - # If this mock function is called from the Get-PrimaryReplicaServerObject command mock - if ( [System.String]::IsNullOrEmpty($ServerName) -and [System.String]::IsNullOrEmpty($InstanceName) -and $AvailabilityGroup -and $ServerObject ) - { - $ServerName,$InstanceName = $AvailabilityGroup.PrimaryReplicaServerName.Split('\') + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + + $getTargetResourceResult.Name | Should -Be 'AG_AllServers' + $getTargetResourceResult.ServerName | Should -Be 'Server2' + $getTargetResourceResult.InstanceName | Should -Be 'MSSQLSERVER' + $getTargetResourceResult.Ensure | Should -Be 'Present' + $getTargetResourceResult.IsActiveNode | Should -Be 'True' + $getTargetResourceResult.AutomatedBackupPreference | Should -Be 'Secondary' + $getTargetResourceResult.AvailabilityMode | Should -Be 'AsynchronousCommit' + $getTargetResourceResult.BackupPriority | Should -Be 50 + $getTargetResourceResult.ConnectionModeInPrimaryRole | Should -Be 'AllowAllConnections' + $getTargetResourceResult.ConnectionModeInSecondaryRole | Should -Be 'AllowNoConnections' + $getTargetResourceResult.FailureConditionLevel | Should -Be 'OnCriticalServerError' + $getTargetResourceResult.FailoverMode | Should -Be 'Manual' + $getTargetResourceResult.HealthCheckTimeout | Should -BeNullOrEmpty + $getTargetResourceResult.EndpointURL | Should -Be 'TCP://Server2:5022' + $getTargetResourceResult.EndpointPort | Should -Be 5022 + $getTargetResourceResult.EndpointHostName | Should -Be 'Server2' + $getTargetResourceResult.BasicAvailabilityGroup | Should -BeNullOrEmpty + $getTargetResourceResult.DatabaseHealthTrigger | Should -BeNullOrEmpty + $getTargetResourceResult.DtcSupportEnabled | Should -BeNullOrEmpty + $getTargetResourceResult.Version | Should -Be 12 + } + Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It } + } + } +} - # Determine which SQL Server mock data we will use - $mockSqlServer = ( $mockSqlServerParameters.GetEnumerator() | Where-Object -FilterScript { $_.Value.Values -contains $ServerName } ).Name - if ( [System.String]::IsNullOrEmpty($mockSqlServer) ) - { - $mockSqlServer = $ServerName +Describe 'SqlAG\Set-TargetResource' { + BeforeAll { + $mockConnectSqlServer1 = { + # Mock the server object + $mockServerObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server + $mockServerObject.Name = 'Server1' + $mockServerObject.NetName = 'Server1' + $mockServerObject.DomainInstanceName = 'Server1' + $mockServerObject.IsHadrEnabled = $true + $mockServerObject.ServiceName = 'MSSQLSERVER' + $mockServerObject.Version = @{ + Major = 13 } - $mockCurrentServerObjectProperties = $mockServerObjectProperties.$mockSqlServer - # Build the domain instance name - if ( ( $InstanceName -eq 'MSSQLSERVER' ) -or [System.String]::IsNullOrEmpty($InstanceName) ) - { - $mockDomainInstanceName = $mockCurrentServerObjectProperties.NetName - $mockPrimaryReplicaServerName = $mockAvailabilityGroupProperties.PrimaryReplicaServerName - $mockAvailabilityGroupReplica1Name = $mockAvailabilityGroupReplicaProperties.Server1.Name - $mockAvailabilityGroupReplica2Name = $mockAvailabilityGroupReplicaProperties.Server2.Name + # Mock the availability group replicas + $mockAvailabilityGroupReplica1 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica1.AvailabilityMode = 'AsynchronousCommit' + $mockAvailabilityGroupReplica1.BackupPriority = 50 + $mockAvailabilityGroupReplica1.ConnectionModeInPrimaryRole = 'AllowAllConnections' + $mockAvailabilityGroupReplica1.ConnectionModeInSecondaryRole = 'AllowNoConnections' + $mockAvailabilityGroupReplica1.EndpointUrl = 'TCP://Server1:5022' + $mockAvailabilityGroupReplica1.FailoverMode = 'Manual' + $mockAvailabilityGroupReplica1.Name = 'Server1' + $mockAvailabilityGroupReplica1.ReadOnlyRoutingConnectionUrl = 'TCP://Server1.domain.com:1433' + $mockAvailabilityGroupReplica1.ReadOnlyRoutingList = @('Server1', 'Server2') + $mockAvailabilityGroupReplica1.SeedingMode = 'Manual' + + $mockAvailabilityGroupReplica2 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica2.AvailabilityMode = 'AsynchronousCommit' + $mockAvailabilityGroupReplica2.BackupPriority = 50 + $mockAvailabilityGroupReplica2.ConnectionModeInPrimaryRole = 'AllowAllConnections' + $mockAvailabilityGroupReplica2.ConnectionModeInSecondaryRole = 'AllowNoConnections' + $mockAvailabilityGroupReplica2.EndpointUrl = 'TCP://Server2:5022' + $mockAvailabilityGroupReplica2.FailoverMode = 'Manual' + $mockAvailabilityGroupReplica2.Name = 'Server2' + $mockAvailabilityGroupReplica2.ReadOnlyRoutingConnectionUrl = 'TCP://Server2.domain.com:1433' + $mockAvailabilityGroupReplica2.ReadOnlyRoutingList = @('Server1', 'Server2') + $mockAvailabilityGroupReplica2.SeedingMode = 'Manual' + + $mockAvailabilityGroupReplica3 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica3.AvailabilityMode = 'AsynchronousCommit' + $mockAvailabilityGroupReplica3.BackupPriority = 50 + $mockAvailabilityGroupReplica3.ConnectionModeInPrimaryRole = 'AllowAllConnections' + $mockAvailabilityGroupReplica3.ConnectionModeInSecondaryRole = 'AllowNoConnections' + $mockAvailabilityGroupReplica3.EndpointUrl = 'TCP://Server3:5022' + $mockAvailabilityGroupReplica3.FailoverMode = 'Manual' + $mockAvailabilityGroupReplica3.Name = 'Server3' + $mockAvailabilityGroupReplica3.ReadOnlyRoutingConnectionUrl = 'TCP://Server3.domain.com:1433' + $mockAvailabilityGroupReplica3.ReadOnlyRoutingList = @('Server1', 'Server2') + $mockAvailabilityGroupReplica3.SeedingMode = 'Manual' + + # Mock the availability groups + $mockAvailabilityGroup1 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroup + $mockAvailabilityGroup1.Name = 'AG_AllServers' + $mockAvailabilityGroup1.PrimaryReplicaServerName = 'Server2' + $mockAvailabilityGroup1.LocalReplicaRole = 'Secondary' + $mockAvailabilityGroup1.AutomatedBackupPreference = 'Secondary' + $mockAvailabilityGroup1.BasicAvailabilityGroup = $true + $mockAvailabilityGroup1.DatabaseHealthTrigger = $true + $mockAvailabilityGroup1.DtcSupportEnabled = $true + $mockAvailabilityGroup1.AvailabilityReplicas.Add($mockAvailabilityGroupReplica1) + $mockAvailabilityGroup1.AvailabilityReplicas.Add($mockAvailabilityGroupReplica2) + $mockAvailabilityGroup1.AvailabilityReplicas.Add($mockAvailabilityGroupReplica3) + + $mockServerObject.AvailabilityGroups.Add($mockAvailabilityGroup1) + + $mockEndpoint = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Endpoint + $mockEndpoint.EndpointType = 'DatabaseMirroring' + $mockEndpoint.Protocol = @{ + TCP = @{ + ListenerPort = 5022 + } } - else - { - $mockDomainInstanceName = '{0}\{1}' -f $mockCurrentServerObjectProperties.NetName,$InstanceName - $mockPrimaryReplicaServerName = '{0}\{1}' -f $mockAvailabilityGroupProperties.PrimaryReplicaServerName,$InstanceName - $mockAvailabilityGroupReplica1Name = '{0}\{1}' -f $mockAvailabilityGroupReplicaProperties.Server1.Name,$InstanceName - $mockAvailabilityGroupReplica2Name = '{0}\{1}' -f $mockAvailabilityGroupReplicaProperties.Server2.Name,$InstanceName - } + $mockServerObject.Endpoints.Add($mockEndpoint) + + return $mockServerObject + } + $mockConnectSqlServer2 = { + # Mock the server object $mockServerObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server - $mockServerObject.DomainInstanceName = $mockDomainInstanceName - $mockServerObject.IsHadrEnabled = $mockIsHadrEnabled - $mockServerObject.Name = $ServerName - $mockServerObject.NetName = $mockCurrentServerObjectProperties.NetName - $mockServerObject.ServiceName = $InstanceName + $mockServerObject.Name = 'Server2' + $mockServerObject.NetName = 'Server2' + $mockServerObject.DomainInstanceName = 'Server2' + $mockServerObject.IsHadrEnabled = $true + $mockServerObject.ServiceName = 'MSSQLSERVER' $mockServerObject.Version = @{ - Major = $Version + Major = 12 } - # Define the availability group object - $mockAvailabilityGroupObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroup - $mockAvailabilityGroupObject.AutomatedBackupPreference = $mockAvailabilityGroupProperties.AutomatedBackupPreference - $mockAvailabilityGroupObject.FailureConditionLevel = $mockAvailabilityGroupProperties.FailureConditionLevel - $mockAvailabilityGroupObject.HealthCheckTimeout = $mockAvailabilityGroupProperties.HealthCheckTimeout - $mockAvailabilityGroupObject.Name = $mockAvailabilityGroupProperties.Name - $mockAvailabilityGroupObject.PrimaryReplicaServerName = $mockPrimaryReplicaServerName - if ( $Version -ge 13 ) - { - $mockAvailabilityGroupObject.BasicAvailabilityGroup = $mockAvailabilityGroupProperties.BasicAvailabilityGroup - $mockAvailabilityGroupObject.DatabaseHealthTrigger = $mockAvailabilityGroupProperties.DatabaseHealthTrigger - $mockAvailabilityGroupObject.DtcSupportEnabled = $mockAvailabilityGroupProperties.DtcSupportEnabled - } - - # Define an availability group object to use when mocking a remove failure - $mockAvailabilityGroupRemoveFailedObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroup - $mockAvailabilityGroupRemoveFailedObject.Name = $mockNameParameters.RemoveAvailabilityGroupFailed - $mockAvailabilityGroupRemoveFailedObject.PrimaryReplicaServerName = $mockPrimaryReplicaServerName - - # Define the availability replica 1 object - $mockAvailabilityReplica1Object = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica - $mockAvailabilityReplica1Object.Name = $mockAvailabilityGroupReplica1Name - $mockAvailabilityReplica1Object.AvailabilityMode = $mockAvailabilityGroupReplicaProperties.Server1.AvailabilityMode - $mockAvailabilityReplica1Object.BackupPriority = $mockAvailabilityGroupReplicaProperties.Server1.BackupPriority - $mockAvailabilityReplica1Object.ConnectionModeInPrimaryRole = $mockAvailabilityGroupReplicaProperties.Server1.ConnectionModeInPrimaryRole - $mockAvailabilityReplica1Object.ConnectionModeInSecondaryRole = $mockAvailabilityGroupReplicaProperties.Server1.ConnectionModeInSecondaryRole - $mockAvailabilityReplica1Object.EndpointUrl = "$($mockAvailabilityGroupReplicaProperties.Server1.EndpointProtocol)://$($mockAvailabilityGroupReplicaProperties.Server1.EndpointHostName):$($mockAvailabilityGroupReplicaProperties.Server1.EndpointPort)" - $mockAvailabilityReplica1Object.FailoverMode = $mockAvailabilityGroupReplicaProperties.Server1.FailoverMode - $mockAvailabilityReplica1Object.Role = $mockAvailabilityGroupReplicaProperties.Server1.Role - - # Define the availability replica 2 object - $mockAvailabilityReplica2Object = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica - $mockAvailabilityReplica2Object.Name = $mockAvailabilityGroupReplica2Name - $mockAvailabilityReplica2Object.AvailabilityMode = $mockAvailabilityGroupReplicaProperties.Server2.AvailabilityMode - $mockAvailabilityReplica2Object.BackupPriority = $mockAvailabilityGroupReplicaProperties.Server2.BackupPriority - $mockAvailabilityReplica2Object.ConnectionModeInPrimaryRole = $mockAvailabilityGroupReplicaProperties.Server2.ConnectionModeInPrimaryRole - $mockAvailabilityReplica2Object.ConnectionModeInSecondaryRole = $mockAvailabilityGroupReplicaProperties.Server2.ConnectionModeInSecondaryRole - $mockAvailabilityReplica2Object.EndpointUrl = "$($mockAvailabilityGroupReplicaProperties.Server2.EndpointProtocol)://$($mockAvailabilityGroupReplicaProperties.Server2.EndpointHostName):$($mockAvailabilityGroupReplicaProperties.Server2.EndpointPort)" - $mockAvailabilityReplica2Object.FailoverMode = $mockAvailabilityGroupReplicaProperties.Server2.FailoverMode - $mockAvailabilityReplica2Object.Role = $mockAvailabilityGroupReplicaProperties.Server2.Role - - # Add the availability group to the server object - $mockAvailabilityGroupObject.AvailabilityReplicas.Add($mockAvailabilityReplica1Object) - $mockAvailabilityGroupObject.AvailabilityReplicas.Add($mockAvailabilityReplica2Object) - $mockServerObject.AvailabilityGroups.Add($mockAvailabilityGroupObject) - $mockServerObject.AvailabilityGroups.Add($mockAvailabilityGroupRemoveFailedObject) - - # Define the database mirroring endpoint object - if ( $mockIsDatabaseMirroringEndpointPresent ) - { - $mockDatabaseMirroringEndpoint = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Endpoint - $mockDatabaseMirroringEndpoint.EndpointType = 'DatabaseMirroring' - $mockDatabaseMirroringEndpoint.Protocol = @{ - $mockDatabaseMirroringEndpointProperties.Protocol = @{ - ListenerPort = $mockDatabaseMirroringEndpointProperties.ListenerPort - } + # Mock the availability group replicas + $mockAvailabilityGroupReplica1 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica1.AvailabilityMode = 'AsynchronousCommit' + $mockAvailabilityGroupReplica1.BackupPriority = 50 + $mockAvailabilityGroupReplica1.ConnectionModeInPrimaryRole = 'AllowAllConnections' + $mockAvailabilityGroupReplica1.ConnectionModeInSecondaryRole = 'AllowNoConnections' + $mockAvailabilityGroupReplica1.EndpointUrl = 'TCP://Server1:5022' + $mockAvailabilityGroupReplica1.FailoverMode = 'Manual' + $mockAvailabilityGroupReplica1.Name = 'Server1' + $mockAvailabilityGroupReplica1.ReadOnlyRoutingConnectionUrl = 'TCP://Server1.domain.com:1433' + $mockAvailabilityGroupReplica1.ReadOnlyRoutingList = @('Server1', 'Server2') + $mockAvailabilityGroupReplica1.SeedingMode = 'Manual' + + $mockAvailabilityGroupReplica2 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica2.AvailabilityMode = 'AsynchronousCommit' + $mockAvailabilityGroupReplica2.BackupPriority = 50 + $mockAvailabilityGroupReplica2.ConnectionModeInPrimaryRole = 'AllowAllConnections' + $mockAvailabilityGroupReplica2.ConnectionModeInSecondaryRole = 'AllowNoConnections' + $mockAvailabilityGroupReplica2.EndpointUrl = 'TCP://Server2:5022' + $mockAvailabilityGroupReplica2.FailoverMode = 'Manual' + $mockAvailabilityGroupReplica2.Name = 'Server2' + $mockAvailabilityGroupReplica2.ReadOnlyRoutingConnectionUrl = 'TCP://Server2.domain.com:1433' + $mockAvailabilityGroupReplica2.ReadOnlyRoutingList = @('Server1', 'Server2') + $mockAvailabilityGroupReplica2.SeedingMode = 'Manual' + + $mockAvailabilityGroupReplica3 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica3.AvailabilityMode = 'AsynchronousCommit' + $mockAvailabilityGroupReplica3.BackupPriority = 50 + $mockAvailabilityGroupReplica3.ConnectionModeInPrimaryRole = 'AllowAllConnections' + $mockAvailabilityGroupReplica3.ConnectionModeInSecondaryRole = 'AllowNoConnections' + $mockAvailabilityGroupReplica3.EndpointUrl = 'TCP://Server3:5022' + $mockAvailabilityGroupReplica3.FailoverMode = 'Manual' + $mockAvailabilityGroupReplica3.Name = 'Server3' + $mockAvailabilityGroupReplica3.ReadOnlyRoutingConnectionUrl = 'TCP://Server3.domain.com:1433' + $mockAvailabilityGroupReplica3.ReadOnlyRoutingList = @('Server1', 'Server2') + $mockAvailabilityGroupReplica3.SeedingMode = 'Manual' + + # Mock the availability groups + $mockAvailabilityGroup1 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroup + $mockAvailabilityGroup1.Name = 'AG_AllServers' + $mockAvailabilityGroup1.PrimaryReplicaServerName = 'Server2' + $mockAvailabilityGroup1.LocalReplicaRole = 'Secondary' + $mockAvailabilityGroup1.AutomatedBackupPreference = 'Secondary' + $mockAvailabilityGroup1.BasicAvailabilityGroup = $true + $mockAvailabilityGroup1.DatabaseHealthTrigger = $true + $mockAvailabilityGroup1.DtcSupportEnabled = $true + $mockAvailabilityGroup1.AvailabilityReplicas.Add($mockAvailabilityGroupReplica1) + $mockAvailabilityGroup1.AvailabilityReplicas.Add($mockAvailabilityGroupReplica2) + $mockAvailabilityGroup1.AvailabilityReplicas.Add($mockAvailabilityGroupReplica3) + + $mockServerObject.AvailabilityGroups.Add($mockAvailabilityGroup1) + + $mockEndpoint = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Endpoint + $mockEndpoint.EndpointType = 'DatabaseMirroring' + $mockEndpoint.Protocol = @{ + TCP = @{ + ListenerPort = 5022 } - $mockServerObject.Endpoints.Add($mockDatabaseMirroringEndpoint) } + $mockServerObject.Endpoints.Add($mockEndpoint) + return $mockServerObject } - $mockNewSqlAvailabilityGroup = { - if ( $ErrorResult -eq 'CreateAvailabilityGroupFailed' ) - { - throw 'CreateAvailabilityGroupFailed' - } + Mock -CommandName Connect-SQL -MockWith $mockConnectSqlServer1 -ParameterFilter { + $ServerName -eq 'Server1' } - - $mockNewSqlAvailabilityGroupReplica = { - if ( $ErrorResult -eq 'CreateAvailabilityGroupReplicaFailed' ) - { - throw 'CreateAvailabilityGroupReplicaFailed' - } - - return New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica + Mock -CommandName Connect-SQL -MockWith $mockConnectSqlServer2 -ParameterFilter { + $ServerName -eq 'Server2' + } + Mock -CommandName Get-PrimaryReplicaServerObject -MockWith $mockConnectSqlServer1 -ParameterFilter { + $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } - # Mock the Update-AvailabilityGroup function to ensure the specified property was set correctly - $mockUpdateAvailabilityGroup = { - param - ( - [Parameter()] - [Microsoft.SqlServer.Management.Smo.AvailabilityGroup] - $AvailabilityGroup - ) - - # If the current value of the property that was set is not equal to the desired value - if ( $ParameterValue -ne $AvailabilityGroup.$ParameterName ) - { - foreach ( $currentProperty in $resourceParameters.Keys ) - { - # Determine if the property was submitted as part of the configuration - # $submittedParameters comes from the Set-TargetResource code - if ( $submittedParameters -contains $currentProperty ) - { - Write-Verbose -Message "The property '$($currentProperty)' value is '$($AvailabilityGroup.$currentProperty)' and should be '$( ( Get-Variable -Name $currentProperty ).Value )'." -Verbose + Mock -CommandName Get-PrimaryReplicaServerObject -MockWith $mockConnectSqlServer2 -ParameterFilter { + $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' + } + Mock -CommandName Test-ClusterPermissions + } + Context 'When the desired state is Absent' { + BeforeAll { + Mock -CommandName Remove-SqlAvailabilityGroup + } + Context 'When the availability group exists' { + It 'Should silently remove the availability group' { + InModuleScope -ScriptBlock { + $setTargetResourceParameters = @{ + Name = 'AG_AllServers' + ServerName = 'Server2' + InstanceName = 'MSSQLSERVER' + Ensure = 'Absent' } + + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw } - throw "Update-AvailabilityGroup should set the property '$($ParameterName)' to '$($ParameterValue)'." + Should -Invoke -CommandName Connect-SQL -Scope It -ParameterFilter { + $ServerName -eq 'Server1' + } -Times 0 -Exactly + + Should -Invoke -CommandName Connect-SQL -Scope It -ParameterFilter { + $ServerName -eq 'Server2' + } -Times 1 -Exactly + + Should -Invoke -CommandName Remove-SqlAvailabilityGroup -Exactly -Times 1 -Scope It } } - # Mock the Update-AvailabilityGroupReplica function to ensure the specified property was set correctly - $mockUpdateAvailabilityGroupReplica = { - param - ( - [Parameter()] - [Microsoft.SqlServer.Management.Smo.AvailabilityReplica] - $AvailabilityGroupReplica - ) - - # Some parameters don't align directly with a property - switch ( $ParameterName ) - { - EndpointHostName - { - $validatedParameter = 'EndpointUrl' - $validatedParameterValue = "$($mockDatabaseMirroringEndpointProperties.Protocol)://$($ParameterValue):$($mockDatabaseMirroringEndpointProperties.ListenerPort)" - } + Context 'When the availability group exists but the current server is not primary' { + It 'Should throw the correct error (NotPrimaryReplica)' { + InModuleScope -ScriptBlock { + $setTargetResourceParameters = @{ + Name = 'AG_AllServers' + ServerName = 'Server1' + InstanceName = 'MSSQLSERVER' + Ensure = 'Absent' + } - default - { - $validatedParameter = $ParameterName - $validatedParameterValue = $ParameterValue + $mockErrorMessage = Get-InvalidOperationRecord -Message ($script:localizedData.NotPrimaryReplica -f $setTargetResourceParameters.ServerName, $setTargetResourceParameters.Name, 'Server2') + + { Set-TargetResource @setTargetResourceParameters } | Should -Throw -ExpectedMessage $mockErrorMessage } - } - # If the current value of the property that was set is not equal to the desired value - if ( $validatedParameterValue -ne $AvailabilityGroupReplica.$validatedParameter ) - { - foreach ( $currentProperty in $resourceParameters.Keys ) - { - # Determine if the property was submitted as part of the configuration - # $submittedParameters comes from the Set-TargetResource code - if ( $submittedParameters -contains $currentProperty ) - { - switch ( $currentProperty ) - { - EndpointHostName - { - $validatedCurrentProperty = 'EndpointUrl' - $validatedCurrentPropertyValue = "$($mockDatabaseMirroringEndpointProperties.Protocol)://$($ParameterValue):$($mockDatabaseMirroringEndpointProperties.ListenerPort)" - } - - default - { - $validatedCurrentProperty = $currentProperty - $validatedCurrentPropertyValue =$AvailabilityGroupReplica.$currentProperty - } - } + Should -Invoke -CommandName Connect-SQL -Scope It -ParameterFilter { + $ServerName -eq 'Server1' + } -Times 1 -Exactly - Write-Verbose -Message "The property '$($validatedCurrentProperty)' value is '$($AvailabilityGroupReplica.$validatedCurrentProperty)' and should be '$( ( Get-Variable -Name $currentProperty ).Value )'." -Verbose - } - } + Should -Invoke -CommandName Connect-SQL -Scope It -ParameterFilter { + $ServerName -eq 'Server2' + } -Times 0 -Exactly - throw "Update-AvailabilityGroupReplica should set the property '$($validatedParameter)' to '$($validatedParameterValue)'." + Should -Invoke -CommandName Remove-SqlAvailabilityGroup -Exactly -Times 0 -Scope It } - } - #endregion cmdlet mocks + } - Describe 'SqlAG\Get-TargetResource' -Tag 'Get' { + Context 'When the removal of the availability group replica fails' { BeforeAll { - Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable + Mock -CommandName Remove-SqlAvailabilityGroup -MockWith { + throw 'FailedRemoveAvailabilityGroup' + } } - Context 'When the Availability Group is Absent' { + It 'Should throw the correct error (FailedRemoveAvailabilityGroup)' { + InModuleScope -ScriptBlock { + $setTargetResourceParameters = @{ + Name = 'AG_AllServers' + ServerName = 'Server2' + InstanceName = 'MSSQLSERVER' + Ensure = 'Absent' + } - It 'Should not return an Availability Group when Name is "", ServerName is "", InstanceName is "", and the SQL version is ""' -TestCases $getTargetResourceAbsentTestCases { - param - ( - $Name, - $ServerName, - $InstanceName, - $Version + $mockErrorMessage = Get-InvalidOperationRecord -Message ( + # Adding wildcard at the end of string so Pester ignores additional messages in the error message (e.g. the string 'Mocked error') + ($script:localizedData.FailedRemoveAvailabilityGroup -f $setTargetResourceParameters.Name, $setTargetResourceParameters.InstanceName) + '*' ) - # Ensure the correct stubs are loaded for the SQL version - Import-SQLModuleStub -SQLVersion $Version + { Set-TargetResource @setTargetResourceParameters } | Should -Throw -ExpectedMessage $mockErrorMessage + } - $getTargetResourceParameters = @{ - Name = $Name - ServerName = $ServerName - InstanceName = $InstanceName - } + Should -Invoke -CommandName Connect-SQL -Scope It -ParameterFilter { + $ServerName -eq 'Server1' + } -Times 0 -Exactly - $result = Get-TargetResource @getTargetResourceParameters + Should -Invoke -CommandName Connect-SQL -Scope It -ParameterFilter { + $ServerName -eq 'Server2' + } -Times 1 -Exactly - $result.Ensure | Should -Be 'Absent' + Should -Invoke -CommandName Remove-SqlAvailabilityGroup -Exactly -Times 1 -Scope It + } + } + } - Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - } + Context 'When HADR is not enabled' { + BeforeAll { + $mockServerObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server + $mockServerObject.Name = 'ServerNotEnabled' + $mockServerObject.NetName = 'ServerNotEnabled' + $mockServerObject.IsHadrEnabled = $false + $mockServerObject.ServiceName = 'MSSQLSERVER' + + Mock -CommandName Connect-SQL -MockWith { + return $mockServerObject + } -ParameterFilter { + $ServerName -eq 'ServerNotEnabled' } + } - Context 'When the Availability Group is Present' { - It 'Should return the correct Availability Group properties when Name is "", ServerName is "", InstanceName is "", and the SQL version is ""' -TestCases $getTargetResourcePresentTestCases { - param - ( - $Name, - $ServerName, - $InstanceName, - $Version - ) + It 'Should throw the correct error (HadrNotEnabled)' { # cSpell: disable-line + InModuleScope -ScriptBlock { + $setTargetResourceParameters = @{ + Name = 'AG_PrimaryOnServer2' + ServerName = 'ServerNotEnabled' + InstanceName = 'MSSQLSERVER' + } - # Ensure the correct stubs are loaded for the SQL version - Import-SQLModuleStub -SQLVersion $Version + $mockErrorRecord = Get-InvalidOperationRecord -Message ( + $script:localizedData.HadrNotEnabled # cSpell: disable-line + ) - $getTargetResourceParameters = @{ - Name = $Name - ServerName = $ServerName - InstanceName = $InstanceName - } + { Set-TargetResource @setTargetResourceParameters } | Should -Throw -ExpectedMessage $mockErrorRecord + } - # Determine which SQL Server mock data will be used - $mockSqlServer = ( $mockSqlServerParameters.GetEnumerator() | Where-Object -FilterScript { $_.Value.Values -contains $ServerName } ).Name - - $result = Get-TargetResource @getTargetResourceParameters - - $result.Name | Should -Be $Name - $result.ServerName | Should -Be $ServerName - $result.InstanceName | Should -Be $InstanceName - $result.Ensure | Should -Be 'Present' - $result.AutomatedBackupPreference | Should -Be $mockAvailabilityGroupProperties.AutomatedBackupPreference - $result.AvailabilityMode | Should -Be $mockAvailabilityGroupReplicaProperties.$mockSqlServer.AvailabilityMode - $result.BackupPriority | Should -Be $mockAvailabilityGroupReplicaProperties.$mockSqlServer.BackupPriority - $result.ConnectionModeInPrimaryRole | Should -Be $mockAvailabilityGroupReplicaProperties.$mockSqlServer.ConnectionModeInPrimaryRole - $result.ConnectionModeInSecondaryRole | Should -Be $mockAvailabilityGroupReplicaProperties.$mockSqlServer.ConnectionModeInSecondaryRole - $result.EndpointURL | Should -Be "$($mockAvailabilityGroupReplicaProperties.$mockSqlServer.EndpointProtocol)://$($mockAvailabilityGroupReplicaProperties.$mockSqlServer.EndpointHostName):$($mockAvailabilityGroupReplicaProperties.$mockSqlServer.EndpointPort)" - $result.FailureConditionLevel | Should -Be $mockAvailabilityGroupProperties.FailureConditionLevel - $result.FailoverMode | Should -Be $mockAvailabilityGroupReplicaProperties.$mockSqlServer.FailoverMode - $result.HealthCheckTimeout | Should -Be $mockAvailabilityGroupProperties.HealthCheckTimeout - - if ( $Version -ge 13 ) - { - $result.BasicAvailabilityGroup | Should -Be $mockAvailabilityGroupProperties.BasicAvailabilityGroup - $result.DatabaseHealthTrigger | Should -Be $mockAvailabilityGroupProperties.DatabaseHealthTrigger - $result.DtcSupportEnabled | Should -Be $mockAvailabilityGroupProperties.DtcSupportEnabled - } - else - { - $result.BasicAvailabilityGroup | Should -BeNullOrEmpty - $result.DatabaseHealthTrigger | Should -BeNullOrEmpty - $result.DtcSupportEnabled | Should -BeNullOrEmpty - } + Should -Invoke -CommandName Connect-SQL -Scope It -ParameterFilter { + $ServerName -eq 'ServerNotEnabled' + } -Times 1 -Exactly - Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - } + } + } + + Context 'When the database mirroring endpoint is absent' { + BeforeAll { + $mockServerObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server + $mockServerObject.Name = 'ServerWithoutEndpoint' + $mockServerObject.NetName = 'ServerWithoutEndpoint' + $mockServerObject.IsHadrEnabled = $true + $mockServerObject.ServiceName = 'MSSQLSERVER' + + Mock -CommandName Connect-SQL -MockWith { + return $mockServerObject + } -ParameterFilter { + $ServerName -eq 'ServerWithoutEndpoint' } } - Describe 'SqlAG\Set-TargetResource' -Tag 'Set' { - BeforeAll { - Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable - Mock -CommandName Get-PrimaryReplicaServerObject -MockWith $mockConnectSql -Verifiable - Mock -CommandName New-SqlAvailabilityGroup $mockNewSqlAvailabilityGroup -Verifiable - Mock -CommandName New-SqlAvailabilityReplica -MockWith $mockNewSqlAvailabilityGroupReplica -Verifiable - Mock -CommandName Remove-SqlAvailabilityGroup -Verifiable -ParameterFilter { - $InputObject.Name -eq $mockNameParameters.PresentAvailabilityGroup - } - Mock -CommandName Remove-SqlAvailabilityGroup -MockWith { - throw 'RemoveAvailabilityGroupFailed' - } -Verifiable -ParameterFilter { - $InputObject.Name -eq $mockNameParameters.RemoveAvailabilityGroupFailed + It 'Should throw the correct error (DatabaseMirroringEndpointNotFound)' { + InModuleScope -ScriptBlock { + $setTargetResourceParameters = @{ + Name = 'AG_PrimaryOnServer2' + ServerName = 'ServerWithoutEndpoint' + InstanceName = 'MSSQLSERVER' } - Mock -CommandName Test-ClusterPermissions -Verifiable - Mock -CommandName Update-AvailabilityGroup -MockWith $mockUpdateAvailabilityGroup -Verifiable - Mock -CommandName Update-AvailabilityGroupReplica -MockWith $mockUpdateAvailabilityGroupReplica -Verifiable - } - - Context 'When the Availability Group is Absent and the desired state is Present and a parameter is supplied' { - It 'Should create the availability group "" with the parameter "" set to "" when Ensure is "", ServerName is "", InstanceName is "", and the SQL version is ""' -TestCases $setTargetResourceCreateAvailabilityGroupWithParameterTestCases { - param - ( - $DomainInstanceName, - $Ensure, - $Name, - $ParameterName, - $ParameterValue, - $ServerName, - $InstanceName, - $Version - ) - # Ensure the correct stubs are loaded for the SQL version - Import-SQLModuleStub -SQLVersion $Version + $mockErrorRecord = Get-ObjectNotFoundRecord -Message ( + $script:localizedData.DatabaseMirroringEndpointNotFound -f 'ServerWithoutEndpoint\MSSQLSERVER' + ) - $setTargetResourceParameters = @{ - Ensure = $Ensure - Name = $Name - ServerName = $ServerName - InstanceName = $InstanceName - $ParameterName = $ParameterValue - } + { Set-TargetResource @setTargetResourceParameters } | Should -Throw -ExpectedMessage $mockErrorRecord + } - { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw + Should -Invoke -CommandName Connect-SQL -Scope It -ParameterFilter { + $ServerName -eq 'ServerWithoutEndpoint' + } -Times 1 -Exactly - Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly - Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times @{Absent=0;Present=1}.$Ensure -Exactly - Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times @{Absent=0;Present=1}.$Ensure -Exactly - Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly -ParameterFilter { - $InputObject.Name -eq $mockNameParameters.PresentAvailabilityGroup - } - Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly -ParameterFilter { - $InputObject.Name -eq $mockNameParameters.RemoveAvailabilityGroupFailed - } - Assert-MockCalled -CommandName Test-ClusterPermissions -Scope It -Times @{Absent=0;Present=1}.$Ensure -Exactly - Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly - Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly + } + } + + Context 'When the desired state is present and the availability group is absent' { + BeforeAll { + $mockReplicaObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockReplicaObject.AvailabilityMode = 'AsynchronousCommit' + $mockReplicaObject.BackupPriority = 50 + $mockReplicaObject.ConnectionModeInPrimaryRole = 'AllowAllConnections' + $mockReplicaObject.ConnectionModeInSecondaryRole = 'AllowNoConnections' + $mockReplicaObject.EndpointUrl = 'TCP://Server1:5022' + $mockReplicaObject.FailoverMode = 'Manual' + $mockReplicaObject.Name = 'Server1' + $mockReplicaObject.SeedingMode = 'Manual' + + Mock -CommandName New-SqlAvailabilityReplica -MockWith { + return $mockReplicaObject + } -ParameterFilter { + $Name -eq 'Server1' + } + Mock -CommandName Remove-SqlAvailabilityGroup + Mock -CommandName New-SqlAvailabilityGroup + Mock -CommandName Update-AvailabilityGroup + } + + It 'Should create the availability group' { + InModuleScope -ScriptBlock { + $setTargetResourceParameters = @{ + Name = 'AG_PrimaryOnServer2' + ServerName = 'Server1' + InstanceName = 'MSSQLSERVER' + Ensure = 'Present' + AvailabilityMode = 'AsynchronousCommit' + BackupPriority = 50 + ConnectionModeInPrimaryRole = 'AllowAllConnections' + ConnectionModeInSecondaryRole = 'AllowNoConnections' + EndpointHostName = 'Server1' + FailoverMode = 'Manual' + SeedingMode = 'Manual' } + + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw } - Context 'When the Availability Group is Absent, the desired state is Present, and creating the Availability Group fails' { - It 'Should throw "" when creating the availability group "" fails, Ensure is "", ServerName is "", InstanceName is "", and the SQL version is ""' -TestCases $setTargetResourceCreateAvailabilityGroupFailedTestCases { - param - ( - $ErrorResult, - $Ensure, - $Name, - $ServerName, - $InstanceName, - $Version - ) + Should -Invoke -CommandName Connect-SQL -Scope It -ParameterFilter { + $ServerName -eq 'Server1' + } -Times 1 -Exactly - # Ensure the correct stubs are loaded for the SQL version - Import-SQLModuleStub -SQLVersion $Version + Should -Invoke -CommandName Connect-SQL -Scope It -ParameterFilter { + $ServerName -eq 'Server2' + } -Times 0 -Exactly - switch ( $ErrorResult ) - { - 'CreateAvailabilityGroupFailed' - { - $assertCreateAvailabilityGroup = 1 - $errorMessage = $script:localizedData.FailedCreateAvailabilityGroup -f $Name, $InstanceName - } + Should -Invoke -CommandName Get-PrimaryReplicaServerObject -Scope It -Time 0 -Exactly -ParameterFilter { + $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' + } - 'CreateAvailabilityGroupReplicaFailed' - { - $assertCreateAvailabilityGroup = 0 - if ($InstanceName -eq 'MSSQLSERVER') - { - $errorMessage = $script:localizedData.FailedCreateAvailabilityGroupReplica -f 'Server1', $InstanceName - } - else - { - $errorMessage = $script:localizedData.FailedCreateAvailabilityGroupReplica -f 'Server1\NamedInstance', $InstanceName - } - } - } + Should -Invoke -CommandName Get-PrimaryReplicaServerObject -Scope It -Time 0 -Exactly -ParameterFilter { + $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' + } + + Should -Invoke -CommandName New-SqlAvailabilityReplica -Exactly -Times 1 -Scope It + Should -Invoke -CommandName New-SqlAvailabilityGroup -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Should -Invoke -CommandName Test-ClusterPermissions -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly + } + Context 'When the endpoint hostname is not defined' { + It 'Should create the availability group replica' { + InModuleScope -ScriptBlock { $setTargetResourceParameters = @{ - Ensure = $Ensure - Name = $Name - ServerName = $ServerName - InstanceName = $InstanceName + Name = 'AG_PrimaryOnServer2' + ServerName = 'Server1' + InstanceName = 'MSSQLSERVER' + Ensure = 'Present' + AvailabilityMode = 'AsynchronousCommit' + BackupPriority = 50 + ConnectionModeInPrimaryRole = 'AllowAllConnections' + ConnectionModeInSecondaryRole = 'AllowNoConnections' + EndpointHostName = '' + FailoverMode = 'Manual' + SeedingMode = 'Manual' } - { Set-TargetResource @setTargetResourceParameters } | Should -Throw $errorMessage - - Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly - Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times $assertCreateAvailabilityGroup -Exactly - Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly -ParameterFilter { - $InputObject.Name -eq $mockNameParameters.PresentAvailabilityGroup - } - Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly -ParameterFilter { - $InputObject.Name -eq $mockNameParameters.RemoveAvailabilityGroupFailed - } - Assert-MockCalled -CommandName Test-ClusterPermissions -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly - Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw } - } - Context 'When the Availability Group is Present and a value is passed to a parameter' { - It 'Should set "" to "" when Name is "", ServerName is "", InstanceName is "", and the SQL version is ""' -TestCases $setTargetResourcePropertyIncorrectTestCases { - param - ( - $Ensure, - $Name, - $ParameterName, - $ParameterValue, - $Result, - $ServerName, - $InstanceName, - $Version - ) + Should -Invoke -CommandName Connect-SQL -Scope It -ParameterFilter { + $ServerName -eq 'Server1' + } -Times 1 -Exactly - # Ensure the correct stubs are loaded for the SQL version - Import-SQLModuleStub -SQLVersion $Version + Should -Invoke -CommandName Connect-SQL -Scope It -ParameterFilter { + $ServerName -eq 'Server2' + } -Times 0 -Exactly - $setTargetResourceParameters = @{ - Ensure = $Ensure - Name = $Name - ServerName = $ServerName - InstanceName = $InstanceName - $ParameterName = $ParameterValue - } + Should -Invoke -CommandName Get-PrimaryReplicaServerObject -Scope It -Time 0 -Exactly -ParameterFilter { + $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' + } - if ( $mockAvailabilityGroupProperties.Keys -contains $ParameterName ) - { - $assertUpdateAvailabilityGroupMockCalled = 1 - $assertUpdateAvailabilityGroupReplicaMockCalled = 0 - } - elseif ( $mockAvailabilityGroupReplicaProperties.Server1.Keys -contains $ParameterName ) - { - $assertUpdateAvailabilityGroupMockCalled = 0 - $assertUpdateAvailabilityGroupReplicaMockCalled = 1 - } + Should -Invoke -CommandName Get-PrimaryReplicaServerObject -Scope It -Time 1 -Exactly -ParameterFilter { + $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' + } - Set-TargetResource @setTargetResourceParameters - #{ Set-TargetResource @setTargetResourceParameters } | Should Not Throw + Should -Invoke -CommandName New-SqlAvailabilityReplica -Exactly -Times 1 -Scope It + Should -Invoke -CommandName New-SqlAvailabilityGroup -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Remove-SqlAvailabilityGRoup -Scope It -Times 0 -Exactly + Should -Invoke -CommandName Test-ClusterPermissions -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly + } + } - Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly - Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly - Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly -ParameterFilter { - $InputObject.Name -eq $mockNameParameters.PresentAvailabilityGroup - } - Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly -ParameterFilter { - $InputObject.Name -eq $mockNameParameters.RemoveAvailabilityGroupFailed - } - Assert-MockCalled -CommandName Test-ClusterPermissions -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times $assertUpdateAvailabilityGroupMockCalled -Exactly - Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times $assertUpdateAvailabilityGroupReplicaMockCalled -Exactly + Context 'When the availability group replica fails to create' { + BeforeAll { + Mock -CommandName New-SqlAvailabilityReplica { + throw 'Mocked error' } } - Context 'When the Availability Group is Present and the desired state is Absent' { - It 'Should remove the Availability Group "" when Ensure is "", ServerName is "", InstanceName is "", and the SQL version is ""' -TestCases $setTargetResourceRemoveAvailabilityGroupTestCases { - param - ( - $Ensure, - $Name, - $ServerName, - $InstanceName, - $Version - ) - - # Ensure the correct stubs are loaded for the SQL version - Import-SQLModuleStub -SQLVersion $Version - + It 'Should throw the correct error' { + InModuleScope -ScriptBlock { $setTargetResourceParameters = @{ - Ensure = $Ensure - Name = $Name - ServerName = $ServerName - InstanceName = $InstanceName + Name = 'AG_PrimaryOnServer2' + ServerName = 'Server2' + InstanceName = 'MSSQLSERVER' + Ensure = 'Present' + AvailabilityMode = 'AsynchronousCommit' + BackupPriority = 50 + ConnectionModeInPrimaryRole = 'AllowAllConnections' + ConnectionModeInSecondaryRole = 'AllowNoConnections' + EndpointHostName = '' + FailoverMode = 'Manual' + SeedingMode = 'Manual' } - { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw + $mockErrorRecord = Get-InvalidOperationRecord -Message ( + # Adding wildcard at the end of string so Pester ignores additional messages in the error message (e.g. the string 'Mocked error') + ($script:localizedData.FailedCreateAvailabilityGroupReplica -f $setTargetResourceParameters.ServerName, $setTargetResourceParameters.InstanceName) + '*' + ) - Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly - Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly - Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly - Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 1 -Exactly -ParameterFilter { - $InputObject.Name -eq $mockNameParameters.PresentAvailabilityGroup - } - Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly -ParameterFilter { - $InputObject.Name -eq $mockNameParameters.RemoveAvailabilityGroupFailed - } - Assert-MockCalled -CommandName Test-ClusterPermissions -Scope It -Times 0 -Exactly - Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly - Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly + { Set-TargetResource @setTargetResourceParameters } | Should -Throw -ExpectedMessage $mockErrorRecord } - } - Context 'When the Availability Group is Present and throws an error when removal is attempted' { - It 'Should throw "" when Ensure is "", Name is "", ServerName is "", InstanceName is "", and the SQL version is ""' -TestCases $setTargetResourceRemoveAvailabilityGroupErrorTestCases { - param - ( - $ErrorResult, - $Ensure, - $Name, - $ServerName, - $InstanceName, - $Version - ) + Should -Invoke -CommandName Connect-SQL -Scope It -ParameterFilter { + $ServerName -eq 'Server1' + } -Times 0 -Exactly - # Ensure the correct stubs are loaded for the SQL version - Import-SQLModuleStub -SQLVersion $Version + Should -Invoke -CommandName Connect-SQL -Scope It -ParameterFilter { + $ServerName -eq 'Server2' + } -Times 1 -Exactly - switch ( $ErrorResult ) - { - 'RemoveAvailabilityGroupFailed' - { - $assertRemoveAvailabilityGroupFailed = 1 - $errorMessage = $script:localizedData.FailedRemoveAvailabilityGroup -f $Name, $InstanceName - } + Should -Invoke -CommandName Get-PrimaryReplicaServerObject -Scope It -Time 0 -Exactly -ParameterFilter { + $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' + } - 'InstanceNotPrimaryReplica' - { - $assertRemoveAvailabilityGroupFailed = 0 - if ($InstanceName -eq 'MSSQLSERVER') - { - $errorMessage = $script:localizedData.NotPrimaryReplica -f 'Server2', $Name, 'Server1' - } - else - { - $errorMessage = $script:localizedData.NotPrimaryReplica -f 'Server2\NamedInstance', $Name, 'Server1\NamedInstance' - } - } - } + Should -Invoke -CommandName Get-PrimaryReplicaServerObject -Scope It -Time 0 -Exactly -ParameterFilter { + $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' + } + + Should -Invoke -CommandName New-SqlAvailabilityReplica -Exactly -Times 1 -Scope It + Should -Invoke -CommandName New-SqlAvailabilityGroup -Exactly -Times 0 -Scope It + Should -Invoke -CommandName Remove-SqlAvailabilityGRoup -Scope It -Times 0 -Exactly + Should -Invoke -CommandName Test-ClusterPermissions -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly + } + } + + Context 'When the availability group fails to create' { + BeforeAll { + Mock -CommandName New-SqlAvailabilityGroup { + throw 'Mocked error' + } + } + It 'Should throw the correct error' { + InModuleScope -ScriptBlock { $setTargetResourceParameters = @{ - Ensure = $Ensure - Name = $Name - ServerName = $ServerName - InstanceName = $InstanceName + Name = 'AG_PrimaryOnServer2' + ServerName = 'Server1' + InstanceName = 'MSSQLSERVER' + Ensure = 'Present' + AvailabilityMode = 'AsynchronousCommit' + BackupPriority = 50 + ConnectionModeInPrimaryRole = 'AllowAllConnections' + ConnectionModeInSecondaryRole = 'AllowNoConnections' + EndpointHostName = '' + FailoverMode = 'Manual' + SeedingMode = 'Manual' } - { Set-TargetResource @setTargetResourceParameters } | Should -Throw $errorMessage + $mockErrorRecord = Get-InvalidOperationRecord -Message ( + # Adding wildcard at the end of string so Pester ignores additional messages in the error message (e.g. the string 'Mocked error') + ($script:localizedData.FailedCreateAvailabilityGroup -f $setTargetResourceParameters.Name, $setTargetResourceParameters.InstanceName) + '*' + ) - Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly - Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly - Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly - Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly -ParameterFilter { - $InputObject.Name -eq $mockNameParameters.PresentAvailabilityGroup - } - Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times $assertRemoveAvailabilityGroupFailed -Exactly -ParameterFilter { - $InputObject.Name -eq $mockNameParameters.RemoveAvailabilityGroupFailed - } - Assert-MockCalled -CommandName Test-ClusterPermissions -Scope It -Times 0 -Exactly - Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly - Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly + { Set-TargetResource @setTargetResourceParameters } | Should -Throw -ExpectedMessage $mockErrorRecord } - } - Context 'When HADR is not enabled' { - AfterAll { - $mockIsHadrEnabled = $true + Should -Invoke -CommandName Connect-SQL -Scope It -ParameterFilter { + $ServerName -eq 'Server1' + } -Times 1 -Exactly + + Should -Invoke -CommandName Connect-SQL -Scope It -ParameterFilter { + $ServerName -eq 'Server2' + } -Times 0 -Exactly + + Should -Invoke -CommandName Get-PrimaryReplicaServerObject -Scope It -Time 0 -Exactly -ParameterFilter { + $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } - BeforeAll { - $mockIsHadrEnabled = $false + Should -Invoke -CommandName Get-PrimaryReplicaServerObject -Scope It -Time 0 -Exactly -ParameterFilter { + $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' } - It 'Should throw "" when Ensure is "", Name is "", ServerName is "", InstanceName is "", and the SQL version is ""' -TestCases $setTargetResourceHadrDisabledTestCases { - param - ( - $Ensure, - $Name, - $Result, - $ServerName, - $InstanceName, - $Version - ) + Should -Invoke -CommandName New-SqlAvailabilityReplica -Exactly -Times 1 -Scope It + Should -Invoke -CommandName New-SqlAvailabilityGroup -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Remove-SqlAvailabilityGRoup -Scope It -Times 0 -Exactly + Should -Invoke -CommandName Test-ClusterPermissions -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly + } + } + } - # Ensure the correct stubs are loaded for the SQL version - Import-SQLModuleStub -SQLVersion $Version + Context 'When the desired state is present and the availability group is present' { + Context 'When Availability Group property is not in desired state' -ForEach @( + @{ + MockPropertyName = 'AutomatedBackupPreference' + MockPropertyValue = 'None' + } + @{ + MockPropertyName = 'FailureConditionLevel' + MockPropertyValue = 'OnAnyQualifiedFailureCondition' + } + @{ + MockPropertyName = 'HealthCheckTimeout' + MockPropertyValue = 10 + } + @{ + MockPropertyName = 'BasicAvailabilityGroup' + MockPropertyValue = $false + } + @{ + MockPropertyName = 'DatabaseHealthTrigger' + MockPropertyValue = $false + } + @{ + MockPropertyName = 'DtcSupportEnabled' + MockPropertyValue = $false + } + ) { + BeforeAll { + Mock -CommandName Remove-SqlAvailabilityGroup + Mock -CommandName Update-AvailabilityGroup + Mock -CommandName New-SqlAvailabilityReplica + Mock -CommandName New-SqlAvailabilityGroup + Mock -CommandName Update-AvailabilityGroupReplica + } + It 'Should set the property to the desired state' { + InModuleScope -Parameters $_ -ScriptBlock { $setTargetResourceParameters = @{ - Ensure = $Ensure - Name = $Name - ServerName = $ServerName - InstanceName = $InstanceName + Name = 'AG_AllServers' + ServerName = 'Server1' + InstanceName = 'MSSQLSERVER' + Ensure = 'Present' + AvailabilityMode = 'AsynchronousCommit' + BackupPriority = 50 + ConnectionModeInPrimaryRole = 'AllowAllConnections' + ConnectionModeInSecondaryRole = 'AllowNoConnections' + EndpointHostName = 'Server1' + FailoverMode = 'Manual' + SeedingMode = 'Manual' } - { Set-TargetResource @setTargetResourceParameters } | Should -Throw $Result + $setTargetResourceParameters.$MockPropertyName = $MockPropertyValue - Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly - Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly - Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly - Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly -ParameterFilter { - $InputObject.Name -eq $mockNameParameters.PresentAvailabilityGroup - } - Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly -ParameterFilter { - $InputObject.Name -eq $mockNameParameters.RemoveAvailabilityGroupFailed - } - Assert-MockCalled -CommandName Test-ClusterPermissions -Scope It -Times 0 -Exactly - Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly - Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw } - } - Context 'When the Database Mirroring Endpoint is missing' { - AfterAll { - $mockIsDatabaseMirroringEndpointPresent = $true + Should -Invoke -CommandName Connect-SQL -Scope It -ParameterFilter { + $ServerName -eq 'Server1' + } -Times 1 -Exactly + + Should -Invoke -CommandName Connect-SQL -Scope It -ParameterFilter { + $ServerName -eq 'Server2' + } -Times 0 -Exactly + + Should -Invoke -CommandName Get-PrimaryReplicaServerObject -Scope It -Time 0 -Exactly -ParameterFilter { + $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } - BeforeAll { - $mockIsDatabaseMirroringEndpointPresent = $false + Should -Invoke -CommandName Get-PrimaryReplicaServerObject -Scope It -Time 1 -Exactly -ParameterFilter { + $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' } - It 'Should throw "" when Ensure is "", Name is "", ServerName is "", InstanceName is "", and the SQL version is ""' -TestCases $setTargetResourceEndpointMissingTestCases { - param - ( - $Ensure, - $Name, - $Result, - $ServerName, - $InstanceName, - $Version - ) - # Ensure the correct stubs are loaded for the SQL version - Import-SQLModuleStub -SQLVersion $Version + Should -Invoke -CommandName New-SqlAvailabilityReplica -Exactly -Times 0 -Scope It + Should -Invoke -CommandName New-SqlAvailabilityGroup -Exactly -Times 0 -Scope It + Should -Invoke -CommandName Remove-SqlAvailabilityGRoup -Scope It -Times 0 -Exactly + Should -Invoke -CommandName Test-ClusterPermissions -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Update-AvailabilityGroup -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Update-AvailabilityGroupReplica -Exactly -Times 0 -Scope It + } + } + + Context 'When Availability Group replica property is not in desired state' -ForEach @( + @{ + MockPropertyName = 'AvailabilityMode' + MockPropertyValue = 'SynchronousCommit' + } + @{ + MockPropertyName = 'BackupPriority' + MockPropertyValue = 60 + } + @{ + MockPropertyName = 'ConnectionModeInPrimaryRole' + MockPropertyValue = 'AllowReadWriteConnections' + } + @{ + MockPropertyName = 'ConnectionModeInSecondaryRole' + MockPropertyValue = 'AllowReadIntentConnectionsOnly' + } + @{ + MockPropertyName = 'FailoverMode' + MockPropertyValue = 'Automatic' + } + @{ + MockPropertyName = 'EndpointHostName' + MockPropertyValue = 'Server2' + } + @{ + MockPropertyName = 'SeedingMode' + MockPropertyValue = 'Automatic' + } + ) { + BeforeAll { + Mock -CommandName Remove-SqlAvailabilityGroup + Mock -CommandName Update-AvailabilityGroup + Mock -CommandName New-SqlAvailabilityReplica + Mock -CommandName New-SqlAvailabilityGroup + Mock -CommandName Update-AvailabilityGroupReplica + } + It 'Should set the property to the desired state' { + InModuleScope -Parameters $_ -ScriptBlock { $setTargetResourceParameters = @{ - Ensure = $Ensure - Name = $Name - ServerName = $ServerName - InstanceName = $InstanceName + Name = 'AG_AllServers' + ServerName = 'Server1' + InstanceName = 'MSSQLSERVER' + Ensure = 'Present' + AvailabilityMode = 'AsynchronousCommit' + BackupPriority = 50 + ConnectionModeInPrimaryRole = 'AllowAllConnections' + ConnectionModeInSecondaryRole = 'AllowNoConnections' + EndpointHostName = 'Server1' + FailoverMode = 'Manual' + SeedingMode = 'Manual' } - { Set-TargetResource @setTargetResourceParameters } | Should -Throw $Result + $setTargetResourceParameters.$MockPropertyName = $MockPropertyValue - Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly - Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly - Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly - Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly -ParameterFilter { - $InputObject.Name -eq $mockNameParameters.PresentAvailabilityGroup - } - Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly -ParameterFilter { - $InputObject.Name -eq $mockNameParameters.RemoveAvailabilityGroupFailed - } - Assert-MockCalled -CommandName Test-ClusterPermissions -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly - Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw } - } - Context 'When the Endpoint URL is incorrect' { - AfterEach { - # Restore up the original endpoint url settings - $mockAvailabilityGroupReplicaProperties.Server1 = $mockAvailabilityGroupReplicaPropertiesServer1Original.Clone() - $mockAvailabilityGroupReplicaProperties.Server2 = $mockAvailabilityGroupReplicaPropertiesServer2Original.Clone() + Should -Invoke -CommandName Connect-SQL -Scope It -ParameterFilter { + $ServerName -eq 'Server1' + } -Times 1 -Exactly + + Should -Invoke -CommandName Connect-SQL -Scope It -ParameterFilter { + $ServerName -eq 'Server2' + } -Times 0 -Exactly + + Should -Invoke -CommandName Get-PrimaryReplicaServerObject -Scope It -Time 0 -Exactly -ParameterFilter { + $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } - BeforeEach { - # Back up the original endpoint url settings - $mockAvailabilityGroupReplicaPropertiesServer1Original = $mockAvailabilityGroupReplicaProperties.Server1.Clone() - $mockAvailabilityGroupReplicaPropertiesServer2Original = $mockAvailabilityGroupReplicaProperties.Server2.Clone() + Should -Invoke -CommandName Get-PrimaryReplicaServerObject -Scope It -Time 1 -Exactly -ParameterFilter { + $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' } - It 'Should set "" to "" when Name is "", ServerName is "", InstanceName is "", and the SQL version is ""' -TestCases $setTargetResourcesEndpointUrlTestCases { - param - ( - $EndpointPropertyName, - $EndpointPropertyValue, - $Ensure, - $Name, - $ServerName, - $InstanceName, - $Version - ) - # Ensure the correct stubs are loaded for the SQL version - Import-SQLModuleStub -SQLVersion $Version + Should -Invoke -CommandName New-SqlAvailabilityReplica -Exactly -Times 0 -Scope It + Should -Invoke -CommandName New-SqlAvailabilityGroup -Exactly -Times 0 -Scope It + Should -Invoke -CommandName Remove-SqlAvailabilityGRoup -Scope It -Times 0 -Exactly + Should -Invoke -CommandName Test-ClusterPermissions -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Update-AvailabilityGroup -Exactly -Times 0 -Scope It + Should -Invoke -CommandName Update-AvailabilityGroupReplica -Exactly -Times 1 -Scope It + } + } - $mockAvailabilityGroupReplicaProperties.Server1.$EndpointPropertyName = $EndpointPropertyValue - $mockAvailabilityGroupReplicaProperties.Server2.$EndpointPropertyName = $EndpointPropertyValue + Context 'When the endpoint port differ from the port in the replica''s endpoint URL' { + BeforeAll { + Mock -CommandName Update-AvailabilityGroupReplica + + Mock -CommandName Connect-Sql -ParameterFilter { + $ServerName -eq 'Server10' + } -MockWith { + # Mock the server object + $mockServerObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server + $mockServerObject.Name = 'Server10' + $mockServerObject.NetName = 'Server10' + $mockServerObject.IsHadrEnabled = $true + $mockServerObject.ServiceName = 'MSSQLSERVER' + $mockServerObject.DomainInstanceName = 'Server10' + + # Mock the availability group replicas + $mockAvailabilityGroupReplica1 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica1.EndpointUrl = 'TCP://Server10:1234' + $mockAvailabilityGroupReplica1.Name = 'Server10' + + # Mock the availability groups + $mockAvailabilityGroup1 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroup + $mockAvailabilityGroup1.Name = 'AG_AllServers' + $mockAvailabilityGroup1.PrimaryReplicaServerName = 'Server10' + $mockAvailabilityGroup1.LocalReplicaRole = 'Primary' + $mockAvailabilityGroup1.AvailabilityReplicas.Add($mockAvailabilityGroupReplica1) + $mockServerObject.AvailabilityGroups.Add($mockAvailabilityGroup1) + + $mockEndpoint = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Endpoint + $mockEndpoint.EndpointType = 'DatabaseMirroring' + $mockEndpoint.Protocol = @{ + TCP = @{ + ListenerPort = 5022 + } + } + + $mockServerObject.Endpoints.Add($mockEndpoint) + + return $mockServerObject + } + } + It 'Should set the replica''s endpoint URL to use the same port as the endpoint' { + InModuleScope -Parameters $_ -ScriptBlock { $setTargetResourceParameters = @{ - Ensure = $Ensure - Name = $Name - ServerName = $ServerName - InstanceName = $InstanceName + Name = 'AG_AllServers' + ServerName = 'Server10' + InstanceName = 'MSSQLSERVER' + Ensure = 'Present' + EndpointHostName = 'Server10' } { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw - - Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly - Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly - Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly -ParameterFilter { - $InputObject.Name -eq $mockNameParameters.PresentAvailabilityGroup - } - Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly -ParameterFilter { - $InputObject.Name -eq $mockNameParameters.RemoveAvailabilityGroupFailed - } - Assert-MockCalled -CommandName Test-ClusterPermissions -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly - Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 1 -Exactly } + + Should -Invoke -CommandName Update-AvailabilityGroupReplica -Exactly -Times 1 -Scope It } } - Describe 'SqlAG\Test-TargetResource' -Tag 'Test' { + Context 'When the endpoint protocol differ from the protocol in the replica''s endpoint URL' { BeforeAll { - Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable - Mock -CommandName Test-ActiveNode -MockWith { - $mockProcessOnlyOnActiveNode - } -Verifiable - } - - Context 'When the Availability Group is Absent' { - It 'Should be "" when Ensure is "", Name is "", ServerName is "", InstanceName is "", and the SQL version is ""' -TestCases $testTargetResourceAbsentTestCases { - param - ( - $Ensure, - $Name, - $Result, - $ServerName, - $InstanceName, - $Version - ) + Mock -CommandName Update-AvailabilityGroupReplica + + Mock -CommandName Connect-Sql -ParameterFilter { + $ServerName -eq 'Server10' + } -MockWith { + # Mock the server object + $mockServerObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server + $mockServerObject.Name = 'Server10' + $mockServerObject.NetName = 'Server10' + $mockServerObject.IsHadrEnabled = $true + $mockServerObject.ServiceName = 'MSSQLSERVER' + $mockServerObject.DomainInstanceName = 'Server10' + + # Mock the availability group replicas + $mockAvailabilityGroupReplica1 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica1.EndpointUrl = 'UDP://Server10:5022' + $mockAvailabilityGroupReplica1.Name = 'Server10' + + # Mock the availability groups + $mockAvailabilityGroup1 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroup + $mockAvailabilityGroup1.Name = 'AG_AllServers' + $mockAvailabilityGroup1.PrimaryReplicaServerName = 'Server10' + $mockAvailabilityGroup1.LocalReplicaRole = 'Primary' + $mockAvailabilityGroup1.AvailabilityReplicas.Add($mockAvailabilityGroupReplica1) + $mockServerObject.AvailabilityGroups.Add($mockAvailabilityGroup1) + + $mockEndpoint = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Endpoint + $mockEndpoint.EndpointType = 'DatabaseMirroring' + $mockEndpoint.Protocol = @{ + TCP = @{ + ListenerPort = 5022 + } + } - # Ensure the correct stubs are loaded for the SQL version - Import-SQLModuleStub -SQLVersion $Version + $mockServerObject.Endpoints.Add($mockEndpoint) - $testTargetResourceParameters = @{ - Ensure = $Ensure - Name = $Name - ServerName = $ServerName - InstanceName = $InstanceName - } + return $mockServerObject + } + } - Test-TargetResource @testTargetResourceParameters | Should -Be $Result + It 'Should set the replica''s endpoint URL to use the same port as the endpoint' { + InModuleScope -Parameters $_ -ScriptBlock { + $setTargetResourceParameters = @{ + Name = 'AG_AllServers' + ServerName = 'Server10' + InstanceName = 'MSSQLSERVER' + Ensure = 'Present' + EndpointHostName = 'Server10' + } - Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Test-ActiveNode -Scope It -Times 1 -Exactly + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw } + + Should -Invoke -CommandName Update-AvailabilityGroupReplica -Exactly -Times 1 -Scope It } + } + } +} - Context 'When the Availability Group is Present and the default parameters are passed' { - It 'Should be "" when Ensure is "", Name is "", ServerName is "", InstanceName is "", and the SQL version is ""' -TestCases $testTargetResourcePresentTestCases { - param - ( - $Ensure, - $Name, - $Result, - $ServerName, - $InstanceName, - $Version - ) +Describe 'SqlAG\Test-TargetResource' { + BeforeAll { + $mockConnectSqlServer1 = { + # Mock the server object + $mockServerObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server + $mockServerObject.Name = 'Server1' + $mockServerObject.NetName = 'Server1' + $mockServerObject.DomainInstanceName = 'Server1' + $mockServerObject.IsHadrEnabled = $true + $mockServerObject.ServiceName = 'MSSQLSERVER' + $mockServerObject.Version = @{ + Major = 13 + } - # Ensure the correct stubs are loaded for the SQL version - Import-SQLModuleStub -SQLVersion $Version + # Mock the availability group replicas + $mockAvailabilityGroupReplica1 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica1.AvailabilityMode = 'AsynchronousCommit' + $mockAvailabilityGroupReplica1.BackupPriority = 50 + $mockAvailabilityGroupReplica1.ConnectionModeInPrimaryRole = 'AllowAllConnections' + $mockAvailabilityGroupReplica1.ConnectionModeInSecondaryRole = 'AllowNoConnections' + $mockAvailabilityGroupReplica1.EndpointUrl = 'TCP://Server1:5022' + $mockAvailabilityGroupReplica1.FailoverMode = 'Manual' + $mockAvailabilityGroupReplica1.Name = 'Server1' + $mockAvailabilityGroupReplica1.ReadOnlyRoutingConnectionUrl = 'TCP://Server1.domain.com:1433' + $mockAvailabilityGroupReplica1.ReadOnlyRoutingList = @('Server1', 'Server2') + $mockAvailabilityGroupReplica1.SeedingMode = 'Manual' + + $mockAvailabilityGroupReplica2 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica2.AvailabilityMode = 'AsynchronousCommit' + $mockAvailabilityGroupReplica2.BackupPriority = 50 + $mockAvailabilityGroupReplica2.ConnectionModeInPrimaryRole = 'AllowAllConnections' + $mockAvailabilityGroupReplica2.ConnectionModeInSecondaryRole = 'AllowNoConnections' + $mockAvailabilityGroupReplica2.EndpointUrl = 'TCP://Server2:5022' + $mockAvailabilityGroupReplica2.FailoverMode = 'Manual' + $mockAvailabilityGroupReplica2.Name = 'Server2' + $mockAvailabilityGroupReplica2.ReadOnlyRoutingConnectionUrl = 'TCP://Server2.domain.com:1433' + $mockAvailabilityGroupReplica2.ReadOnlyRoutingList = @('Server1', 'Server2') + $mockAvailabilityGroupReplica2.SeedingMode = 'Manual' + + $mockAvailabilityGroupReplica3 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica3.AvailabilityMode = 'AsynchronousCommit' + $mockAvailabilityGroupReplica3.BackupPriority = 50 + $mockAvailabilityGroupReplica3.ConnectionModeInPrimaryRole = 'AllowAllConnections' + $mockAvailabilityGroupReplica3.ConnectionModeInSecondaryRole = 'AllowNoConnections' + $mockAvailabilityGroupReplica3.EndpointUrl = 'TCP://Server3:5022' + $mockAvailabilityGroupReplica3.FailoverMode = 'Manual' + $mockAvailabilityGroupReplica3.Name = 'Server3' + $mockAvailabilityGroupReplica3.ReadOnlyRoutingConnectionUrl = 'TCP://Server3.domain.com:1433' + $mockAvailabilityGroupReplica3.ReadOnlyRoutingList = @('Server1', 'Server2') + $mockAvailabilityGroupReplica3.SeedingMode = 'Manual' + + # Mock the availability groups + $mockAvailabilityGroup1 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroup + $mockAvailabilityGroup1.Name = 'AG_AllServers' + $mockAvailabilityGroup1.PrimaryReplicaServerName = 'Server2' + $mockAvailabilityGroup1.LocalReplicaRole = 'Secondary' + $mockAvailabilityGroup1.AutomatedBackupPreference = 'Secondary' + $mockAvailabilityGroup1.BasicAvailabilityGroup = $true + $mockAvailabilityGroup1.DatabaseHealthTrigger = $true + $mockAvailabilityGroup1.DtcSupportEnabled = $true + $mockAvailabilityGroup1.AvailabilityReplicas.Add($mockAvailabilityGroupReplica1) + $mockAvailabilityGroup1.AvailabilityReplicas.Add($mockAvailabilityGroupReplica2) + $mockAvailabilityGroup1.AvailabilityReplicas.Add($mockAvailabilityGroupReplica3) + + $mockServerObject.AvailabilityGroups.Add($mockAvailabilityGroup1) + + $mockEndpoint = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Endpoint + $mockEndpoint.EndpointType = 'DatabaseMirroring' + $mockEndpoint.Protocol = @{ + TCP = @{ + ListenerPort = 5022 + } + } - $testTargetResourceParameters = @{ - Ensure = $Ensure - Name = $Name - ServerName = $ServerName - InstanceName = $InstanceName - } + $mockServerObject.Endpoints.Add($mockEndpoint) - Test-TargetResource @testTargetResourceParameters | Should -Be $Result + return $mockServerObject + } + + $mockConnectSqlServer2 = { + # Mock the server object + $mockServerObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server + $mockServerObject.Name = 'Server2' + $mockServerObject.NetName = 'Server2' + $mockServerObject.DomainInstanceName = 'Server2' + $mockServerObject.IsHadrEnabled = $true + $mockServerObject.ServiceName = 'MSSQLSERVER' + $mockServerObject.Version = @{ + Major = 12 + } - Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Test-ActiveNode -Scope It -Times 1 -Exactly + # Mock the availability group replicas + $mockAvailabilityGroupReplica1 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica1.AvailabilityMode = 'AsynchronousCommit' + $mockAvailabilityGroupReplica1.BackupPriority = 50 + $mockAvailabilityGroupReplica1.ConnectionModeInPrimaryRole = 'AllowAllConnections' + $mockAvailabilityGroupReplica1.ConnectionModeInSecondaryRole = 'AllowNoConnections' + $mockAvailabilityGroupReplica1.EndpointUrl = 'TCP://Server1:5022' + $mockAvailabilityGroupReplica1.FailoverMode = 'Manual' + $mockAvailabilityGroupReplica1.Name = 'Server1' + $mockAvailabilityGroupReplica1.ReadOnlyRoutingConnectionUrl = 'TCP://Server1.domain.com:1433' + $mockAvailabilityGroupReplica1.ReadOnlyRoutingList = @('Server1', 'Server2') + + $mockAvailabilityGroupReplica2 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica2.AvailabilityMode = 'AsynchronousCommit' + $mockAvailabilityGroupReplica2.BackupPriority = 50 + $mockAvailabilityGroupReplica2.ConnectionModeInPrimaryRole = 'AllowAllConnections' + $mockAvailabilityGroupReplica2.ConnectionModeInSecondaryRole = 'AllowNoConnections' + $mockAvailabilityGroupReplica2.EndpointUrl = 'TCP://Server2:5022' + $mockAvailabilityGroupReplica2.FailoverMode = 'Manual' + $mockAvailabilityGroupReplica2.Name = 'Server2' + $mockAvailabilityGroupReplica2.ReadOnlyRoutingConnectionUrl = 'TCP://Server2.domain.com:1433' + $mockAvailabilityGroupReplica2.ReadOnlyRoutingList = @('Server1', 'Server2') + $mockAvailabilityGroupReplica2.SeedingMode = 'Manual' + + $mockAvailabilityGroupReplica3 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica3.AvailabilityMode = 'AsynchronousCommit' + $mockAvailabilityGroupReplica3.BackupPriority = 50 + $mockAvailabilityGroupReplica3.ConnectionModeInPrimaryRole = 'AllowAllConnections' + $mockAvailabilityGroupReplica3.ConnectionModeInSecondaryRole = 'AllowNoConnections' + $mockAvailabilityGroupReplica3.EndpointUrl = 'TCP://Server3:5022' + $mockAvailabilityGroupReplica3.FailoverMode = 'Manual' + $mockAvailabilityGroupReplica3.Name = 'Server3' + $mockAvailabilityGroupReplica3.ReadOnlyRoutingConnectionUrl = 'TCP://Server3.domain.com:1433' + $mockAvailabilityGroupReplica3.ReadOnlyRoutingList = @('Server1', 'Server2') + $mockAvailabilityGroupReplica3.SeedingMode = 'Manual' + + # Mock the availability groups + $mockAvailabilityGroup1 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroup + $mockAvailabilityGroup1.Name = 'AG_AllServers' + $mockAvailabilityGroup1.PrimaryReplicaServerName = 'Server2' + $mockAvailabilityGroup1.LocalReplicaRole = 'Secondary' + $mockAvailabilityGroup1.AutomatedBackupPreference = 'Secondary' + $mockAvailabilityGroup1.AvailabilityReplicas.Add($mockAvailabilityGroupReplica1) + $mockAvailabilityGroup1.AvailabilityReplicas.Add($mockAvailabilityGroupReplica2) + $mockAvailabilityGroup1.AvailabilityReplicas.Add($mockAvailabilityGroupReplica3) + + $mockServerObject.AvailabilityGroups.Add($mockAvailabilityGroup1) + + $mockEndpoint = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Endpoint + $mockEndpoint.EndpointType = 'DatabaseMirroring' + $mockEndpoint.Protocol = @{ + TCP = @{ + ListenerPort = 5022 } } - Context 'When the Availability Group is Present and a value is passed to a parameter' { - It 'Should be "" when "" is "", Name is "", ServerName is "", InstanceName is "", and the SQL version is ""' -TestCases $testTargetResourcePropertyIncorrectTestCases { - param - ( - $Ensure, - $Name, - $ParameterName, - $ParameterValue, - $Result, - $ServerName, - $InstanceName, - $Version - ) + $mockServerObject.Endpoints.Add($mockEndpoint) - # Ensure the correct stubs are loaded for the SQL version - Import-SQLModuleStub -SQLVersion $Version + return $mockServerObject + } + Mock -CommandName Connect-SQL -MockWith $mockConnectSqlServer1 -ParameterFilter { + $ServerName -eq 'Server1' + } + Mock -CommandName Connect-SQL -MockWith $mockConnectSqlServer2 -ParameterFilter { + $ServerName -eq 'Server2' + } + } + Context 'When the system is in the desired state' { + Context 'When the Availability Group should be absent' { + It 'Should return $true' { + InModuleScope -ScriptBlock { $testTargetResourceParameters = @{ - Ensure = $Ensure - Name = $Name - ServerName = $ServerName - InstanceName = $InstanceName - $ParameterName = $ParameterValue + Name = 'AbsentAG' + ServerName = 'Server1' + InstanceName = 'MSSQLSERVER' + Ensure = 'Absent' } - - Test-TargetResource @testTargetResourceParameters | Should -Be $Result - - Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Test-ActiveNode -Scope It -Times 1 -Exactly + Test-TargetResource @testTargetResourceParameters | Should -BeTrue } + Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 } - - Context 'When the Availability Group is Present an Endpoint property is incorrect' { - AfterEach { - # Restore up the original endpoint url settings - $mockAvailabilityGroupReplicaProperties.Server1 = $mockAvailabilityGroupReplicaPropertiesServer1Original.Clone() - $mockAvailabilityGroupReplicaProperties.Server2 = $mockAvailabilityGroupReplicaPropertiesServer2Original.Clone() + } + Context 'When the Availability Group should be present' { + It 'Should return $true' { + InModuleScope -ScriptBlock { + $testTargetResourceParameters = @{ + Name = 'AG_AllServers' + ServerName = 'Server1' + InstanceName = 'MSSQLSERVER' + Ensure = 'Present' + } + Test-TargetResource @testTargetResourceParameters | Should -BeTrue } + Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 + } + } + } - BeforeEach { - # Back up the original endpoint url settings - $mockAvailabilityGroupReplicaPropertiesServer1Original = $mockAvailabilityGroupReplicaProperties.Server1.Clone() - $mockAvailabilityGroupReplicaPropertiesServer2Original = $mockAvailabilityGroupReplicaProperties.Server2.Clone() + Context 'When the system is not in the desired state' { + Context 'When the Availability Group should be absent' { + It 'Should return $false' { + InModuleScope -ScriptBlock { + $testTargetResourceParameters = @{ + Name = 'AG_AllServers' + ServerName = 'Server1' + InstanceName = 'MSSQLSERVER' + Ensure = 'Absent' + } + Test-TargetResource @testTargetResourceParameters | Should -BeFalse } - - It 'Should be "" when "" is "", Name is "", ServerName is "", InstanceName is "", and the SQL version is ""' -TestCases $testTargetResourceEndpointIncorrectTestCases { - param - ( - $EndpointPropertyName, - $EndpointPropertyValue, - $Ensure, - $Name, - $Result, - $ServerName, - $InstanceName, - $Version - ) - - # Ensure the correct stubs are loaded for the SQL version - Import-SQLModuleStub -SQLVersion $Version - - $mockAvailabilityGroupReplicaProperties.Server1.$EndpointPropertyName = $EndpointPropertyValue - $mockAvailabilityGroupReplicaProperties.Server2.$EndpointPropertyName = $EndpointPropertyValue - + Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 + } + } + Context 'When the Availability Group should be present' { + It 'Should return $false' { + InModuleScope -ScriptBlock { $testTargetResourceParameters = @{ - Ensure = $Ensure - Name = $Name - ServerName = $ServerName - InstanceName = $InstanceName + Name = 'AbsentAG' + ServerName = 'Server1' + InstanceName = 'MSSQLSERVER' } - - Test-TargetResource @testTargetResourceParameters | Should -Be $Result - - Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Test-ActiveNode -Scope It -Times 1 -Exactly + Test-TargetResource @testTargetResourceParameters | Should -BeFalse } + Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 } + } + } - Context 'When the ProcessOnlyOnActiveNode parameter is passed' { - AfterAll { - $mockProcessOnlyOnActiveNode = $mockProcessOnlyOnActiveNodeOriginal + Context 'When enforcing the state shall happen only when the node is the active node' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith { + @{ + Name = 'AG_AllServers' + ServerName = 'Server1' + InstanceName = 'MSSQLSERVER' + Ensure = 'Present' + EndpointPort = '5022' + EndpointUrl = 'TCP://Server1:5022' + IsActiveNode = $false } + } + } - BeforeAll { - $mockProcessOnlyOnActiveNodeOriginal = $mockProcessOnlyOnActiveNode - $mockProcessOnlyOnActiveNode = $false + It 'Should return $true' { + InModuleScope -ScriptBlock { + $testTargetResourceParameters = @{ + Ensure = 'Absent' + Name = 'AG_AllServers' + ServerName = 'Server1' + InstanceName = 'MSSQLSERVER' + ProcessOnlyOnActiveNode = $true } - It 'Should be "true" when ProcessOnlyOnActiveNode is "", Ensure is "", Name is "", ServerName is "", InstanceName is "", and the SQL version is ""' -TestCases $testTargetResourceProcessOnlyOnActiveNodeTestCases { - param - ( - $Ensure, - $Name, - $ProcessOnlyOnActiveNode, - $ServerName, - $InstanceName, - $Version - ) + Test-TargetResource @testTargetResourceParameters | Should -BeTrue + } - # Ensure the correct stubs are loaded for the SQL version - Import-SQLModuleStub -SQLVersion $Version + Should -Invoke -CommandName Get-TargetResource -Exactly -Times 1 -Scope It + } + } - $testTargetResourceParameters = @{ - Ensure = $Ensure - Name = $Name - ProcessOnlyOnActiveNode = $ProcessOnlyOnActiveNode - ServerName = $ServerName - InstanceName = $InstanceName - } + Context 'When property is not in desired state' -ForEach @( + @{ + MockPropertyName = 'AutomatedBackupPreference' + MockPropertyValue = 'None' + } + @{ + MockPropertyName = 'AvailabilityMode' + MockPropertyValue = 'SynchronousCommit' + } + @{ + MockPropertyName = 'BackupPriority' + MockPropertyValue = 60 + } + @{ + MockPropertyName = 'ConnectionModeInPrimaryRole' + MockPropertyValue = 'AllowReadWriteConnections' + } + @{ + MockPropertyName = 'ConnectionModeInSecondaryRole' + MockPropertyValue = 'AllowReadIntentConnectionsOnly' + } + @{ + MockPropertyName = 'FailureConditionLevel' + MockPropertyValue = 'OnAnyQualifiedFailureCondition' + } + @{ + MockPropertyName = 'FailoverMode' + MockPropertyValue = 'Automatic' + } + @{ + MockPropertyName = 'HealthCheckTimeout' + MockPropertyValue = 10 + } + @{ + MockPropertyName = 'EndpointHostName' + MockPropertyValue = 'Server2' + } + @{ + MockPropertyName = 'BasicAvailabilityGroup' + MockPropertyValue = $false + } + @{ + MockPropertyName = 'DatabaseHealthTrigger' + MockPropertyValue = $false + } + @{ + MockPropertyName = 'DtcSupportEnabled' + MockPropertyValue = $false + } + @{ + MockPropertyName = 'SeedingMode' + MockPropertyValue = 'Automatic' + } + ) { + It 'Should return $false' { + InModuleScope -Parameters $_ -ScriptBlock { + $testTargetResourceParameters = @{ + Name = 'AG_AllServers' + ServerName = 'Server1' + InstanceName = 'MSSQLSERVER' + ProcessOnlyOnActiveNode = $true + } - Test-TargetResource @testTargetResourceParameters | Should Be $true + $testTargetResourceParameters.$MockPropertyName = $MockPropertyValue - Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Test-ActiveNode -Scope It -Times 1 -Exactly + Test-TargetResource @testTargetResourceParameters | Should -BeFalse + } + + Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 + } + } + Context 'When endpoint port differ from the endpoint URL port' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith { + @{ + Name = 'AG_AllServers' + ServerName = 'Server1' + InstanceName = 'MSSQLSERVER' + Ensure = 'Present' + + # Read properties + EndpointPort = '5022' + EndpointUrl = 'TCP://Server1:1433' + IsActiveNode = $true } } } - Describe 'SqlAG\Update-AvailabilityGroup' -Tag 'Helper' { - BeforeAll { - $mockAvailabilityGroup = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroup + It 'Should return $false' { + InModuleScope -ScriptBlock { + $testTargetResourceParameters = @{ + Name = 'AG_AllServers' + ServerName = 'Server1' + InstanceName = 'MSSQLSERVER' + ProcessOnlyOnActiveNode = $true + } + + Test-TargetResource @testTargetResourceParameters | Should -BeFalse } - Context 'When the Availability Group is altered' { - It 'Should silently alter the Availability Group' { + Should -Invoke -CommandName Get-TargetResource -Exactly -Times 1 -Scope It + } + } - { Update-AvailabilityGroup -AvailabilityGroup $mockAvailabilityGroup } | Should -Not -Throw + Context 'When endpoint protocol differ from the endpoint URL protocol' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith { + @{ + Name = 'AG_AllServers' + ServerName = 'Server1' + InstanceName = 'MSSQLSERVER' + Ensure = 'Present' + + # Read properties + EndpointPort = '5022' + EndpointUrl = 'UDP://Server1:5022' + IsActiveNode = $true } + } + } - It 'Should throw the correct error, AlterAvailabilityGroupFailed, when altering the Availability Group fails' { - - $mockAvailabilityGroup.Name = 'AlterFailed' - - { Update-AvailabilityGroup -AvailabilityGroup $mockAvailabilityGroup } | Should -Throw ($script:localizedData.FailedAlterAvailabilityGroup -f $mockAvailabilityGroup.Name) + It 'Should return $false' { + InModuleScope -ScriptBlock { + $testTargetResourceParameters = @{ + Name = 'AG_AllServers' + ServerName = 'Server1' + InstanceName = 'MSSQLSERVER' + ProcessOnlyOnActiveNode = $true } + + Test-TargetResource @testTargetResourceParameters | Should -BeFalse } + + Should -Invoke -CommandName Get-TargetResource -Exactly -Times 1 -Scope It } } } -finally -{ - Invoke-TestCleanup -} From b0abcc257db74e3b7587d2e072dce610b4041591 Mon Sep 17 00:00:00 2001 From: Andrii Sydorenko Date: Thu, 14 Dec 2023 16:52:41 +0100 Subject: [PATCH 5/9] fix unit test --- tests/Unit/DSC_SqlAG.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/DSC_SqlAG.Tests.ps1 b/tests/Unit/DSC_SqlAG.Tests.ps1 index 004dae5f4..4cf0825ec 100644 --- a/tests/Unit/DSC_SqlAG.Tests.ps1 +++ b/tests/Unit/DSC_SqlAG.Tests.ps1 @@ -779,7 +779,7 @@ Describe 'SqlAG\Set-TargetResource' { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } - Should -Invoke -CommandName Get-PrimaryReplicaServerObject -Scope It -Time 1 -Exactly -ParameterFilter { + Should -Invoke -CommandName Get-PrimaryReplicaServerObject -Scope It -Time 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' } From e98cfd72e5e80d149563fd77bbaf20d07cb66074 Mon Sep 17 00:00:00 2001 From: Andrii Sydorenko Date: Thu, 14 Dec 2023 17:17:16 +0100 Subject: [PATCH 6/9] add test parameter --- tests/Unit/DSC_SqlAG.Tests.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Unit/DSC_SqlAG.Tests.ps1 b/tests/Unit/DSC_SqlAG.Tests.ps1 index 4cf0825ec..bcacf9011 100644 --- a/tests/Unit/DSC_SqlAG.Tests.ps1 +++ b/tests/Unit/DSC_SqlAG.Tests.ps1 @@ -717,6 +717,7 @@ Describe 'SqlAG\Set-TargetResource' { ConnectionModeInPrimaryRole = 'AllowAllConnections' ConnectionModeInSecondaryRole = 'AllowNoConnections' EndpointHostName = 'Server1' + FailureConditionLevel = 'OnServerUnresponsive ' FailoverMode = 'Manual' SeedingMode = 'Manual' } From 57d3fadcf93ffef7700072b512bb4f3cd144a2b9 Mon Sep 17 00:00:00 2001 From: Andrii Sydorenko Date: Thu, 14 Dec 2023 17:17:49 +0100 Subject: [PATCH 7/9] Update Changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c15cd480e..7dc985415 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,9 +14,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix sort to get the latest version - `Assert-Feature` - Fixed unit tests. +- SqlAGReplica + - Fix unit test FailedRemoveAvailabilityGroupReplica ### Changed +- SqlAG + - Converted unit test to Pester 5 + - DtcSupportEnabled option in Set-TargetResource and TestTargetResource - SqlServerDsc - Bump PSResourceGet to v1.0.0 (used when resolving dependencies). - Update markdown highlights with newly supported keywords. From 02bcd594a0ba005badc7bb88123052a1df423ab0 Mon Sep 17 00:00:00 2001 From: Andrii Sydorenko Date: Thu, 14 Dec 2023 17:29:18 +0100 Subject: [PATCH 8/9] remove space from parameter value --- tests/Unit/DSC_SqlAG.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/DSC_SqlAG.Tests.ps1 b/tests/Unit/DSC_SqlAG.Tests.ps1 index bcacf9011..30f0b4510 100644 --- a/tests/Unit/DSC_SqlAG.Tests.ps1 +++ b/tests/Unit/DSC_SqlAG.Tests.ps1 @@ -717,7 +717,7 @@ Describe 'SqlAG\Set-TargetResource' { ConnectionModeInPrimaryRole = 'AllowAllConnections' ConnectionModeInSecondaryRole = 'AllowNoConnections' EndpointHostName = 'Server1' - FailureConditionLevel = 'OnServerUnresponsive ' + FailureConditionLevel = 'OnServerUnresponsive' FailoverMode = 'Manual' SeedingMode = 'Manual' } From 2331b9d0eb51abe44988c57b1413485dee161e26 Mon Sep 17 00:00:00 2001 From: Andrii Sydorenko Date: Thu, 14 Dec 2023 19:54:34 +0100 Subject: [PATCH 9/9] remove comment about the DtcSupportEnabled --- source/DSCResources/DSC_SqlAG/DSC_SqlAG.psm1 | 1 - 1 file changed, 1 deletion(-) diff --git a/source/DSCResources/DSC_SqlAG/DSC_SqlAG.psm1 b/source/DSCResources/DSC_SqlAG/DSC_SqlAG.psm1 index 40feae1a7..1faff8fae 100644 --- a/source/DSCResources/DSC_SqlAG/DSC_SqlAG.psm1 +++ b/source/DSCResources/DSC_SqlAG/DSC_SqlAG.psm1 @@ -752,7 +752,6 @@ function Test-TargetResource <# Add properties compatible with SQL Server 2016 or later versions - DtcSupportEnabled is enabled at the creation of the Availability Group only, hence it will not be checked in this block SeedingMode should be checked only in case if New-SqlAvailabilityReplica support the SeedingMode parameter #> if ( $sqlMajorVersion -ge 13 )