Skip to content

Commit a4db933

Browse files
committed
Refactor Set-TargetResource to streamline snapshot isolation handling and enhance test coverage for database operations
1 parent 5e509ce commit a4db933

File tree

3 files changed

+202
-15
lines changed

3 files changed

+202
-15
lines changed

source/DSCResources/DSC_SqlDatabase/DSC_SqlDatabase.psm1

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -293,22 +293,13 @@ function Set-TargetResource
293293
$script:localizedData.UpdatingSnapshotIsolation -f $SnapshotIsolation
294294
)
295295

296-
try
296+
if ($SnapshotIsolation)
297297
{
298-
if ($SnapshotIsolation)
299-
{
300-
Enable-SqlDscDatabaseSnapshotIsolation -DatabaseObject $sqlDatabaseObject -Force -ErrorAction 'Stop'
301-
}
302-
else
303-
{
304-
Disable-SqlDscDatabaseSnapshotIsolation -DatabaseObject $sqlDatabaseObject -Force -ErrorAction 'Stop'
305-
}
298+
Enable-SqlDscDatabaseSnapshotIsolation -DatabaseObject $sqlDatabaseObject -Force -ErrorAction 'Stop'
306299
}
307-
catch
300+
else
308301
{
309-
$errorMessage = $script:localizedData.FailedToUpdateSnapshotIsolation -f $SnapshotIsolation, $Name
310-
311-
New-InvalidOperationException -Message $errorMessage -ErrorRecord $_
302+
Disable-SqlDscDatabaseSnapshotIsolation -DatabaseObject $sqlDatabaseObject -Force -ErrorAction 'Stop'
312303
}
313304
}
314305

@@ -364,6 +355,17 @@ function Set-TargetResource
364355
{
365356
$sqlDatabaseObjectToCreate.SetOwner($OwnerName)
366357
}
358+
359+
<#
360+
This must be run after the object is created because
361+
snapshot isolation can only be set on an existing database.
362+
Snapshot isolation is disabled by default, so we only need
363+
to enable it if explicitly requested.
364+
#>
365+
if ($PSBoundParameters.ContainsKey('SnapshotIsolation') -and $SnapshotIsolation)
366+
{
367+
Enable-SqlDscDatabaseSnapshotIsolation -DatabaseObject $sqlDatabaseObjectToCreate -Force -ErrorAction 'Stop'
368+
}
367369
}
368370
}
369371
catch

source/DSCResources/DSC_SqlDatabase/en-US/DSC_SqlDatabase.strings.psd1

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,5 @@ ConvertFrom-StringData @'
2323
UpdatingOwner = Changing the database owner to '{0}'.
2424
FailedToUpdateOwner = Failed changing to owner to '{0}' for the database '{1}'.
2525
UpdatingSnapshotIsolation = Updating snapshot isolation to '{0}'.
26-
FailedToUpdateSnapshotIsolation = Failed changing snapshot isolation to '{0}' for the database '{1}'.
2726
SnapshotIsolationWrong = The database '{0}' exists and has snapshot isolation set to '{1}', but expected it to have snapshot isolation set to '{2}'.
2827
'@

tests/Unit/DSC_SqlDatabase.Tests.ps1

Lines changed: 187 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ BeforeAll {
4646
# Load the correct SQL Module stub
4747
$script:stubModuleName = Import-SQLModuleStub -PassThru
4848

49+
# Loading mocked SMO types required for mocking Enable/Disable-SqlDscDatabaseSnapshotIsolation
50+
Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SMO.cs')
51+
4952
$PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscResourceName
5053
$PSDefaultParameterValues['Mock:ModuleName'] = $script:dscResourceName
5154
$PSDefaultParameterValues['Should:ModuleName'] = $script:dscResourceName
@@ -83,7 +86,8 @@ Describe 'SqlDatabase\Get-TargetResource' {
8386
Add-Member -MemberType NoteProperty -Name 'Collation' -Value 'SQL_Latin1_General_CP1_CS_AS' -PassThru |
8487
Add-Member -MemberType NoteProperty -Name 'CompatibilityLevel' -Value 'Version130' -PassThru |
8588
Add-Member -MemberType NoteProperty -Name 'RecoveryModel' -Value 'Full' -PassThru |
86-
Add-Member -MemberType NoteProperty -Name 'Owner' -Value 'sa' -PassThru -Force
89+
Add-Member -MemberType NoteProperty -Name 'Owner' -Value 'sa' -PassThru |
90+
Add-Member -MemberType NoteProperty -Name 'SnapshotIsolationState' -Value 'Enabled' -PassThru -Force
8791
}
8892
} -PassThru -Force
8993
)
@@ -194,6 +198,7 @@ Describe 'SqlDatabase\Get-TargetResource' {
194198
$result.CompatibilityLevel | Should -Be 'Version130'
195199
$result.RecoveryModel | Should -Be 'Full'
196200
$result.OwnerName | Should -Be 'sa'
201+
$result.SnapshotIsolation | Should -BeTrue
197202
}
198203
}
199204
}
@@ -231,6 +236,7 @@ Describe 'SqlDatabase\Test-TargetResource' {
231236
CompatibilityLevel = $null
232237
RecoveryModel = $null
233238
OwnerName = $null
239+
SnapshotIsolation = $null
234240
}
235241
}
236242
}
@@ -262,6 +268,7 @@ Describe 'SqlDatabase\Test-TargetResource' {
262268
CompatibilityLevel = 'Version130'
263269
RecoveryModel = 'Full'
264270
OwnerName = 'sa'
271+
SnapshotIsolation = $true
265272
}
266273
}
267274
}
@@ -296,6 +303,10 @@ Describe 'SqlDatabase\Test-TargetResource' {
296303
PropertyName = 'OwnerName'
297304
PropertyValue = 'sa'
298305
}
306+
@{
307+
PropertyName = 'SnapshotIsolation'
308+
PropertyValue = $true
309+
}
299310
) {
300311
BeforeAll {
301312
Mock -CommandName Get-TargetResource -MockWith {
@@ -308,6 +319,7 @@ Describe 'SqlDatabase\Test-TargetResource' {
308319
CompatibilityLevel = 'Version130'
309320
RecoveryModel = 'Full'
310321
OwnerName = 'sa'
322+
SnapshotIsolation = $true
311323
}
312324
}
313325
}
@@ -341,6 +353,7 @@ Describe 'SqlDatabase\Test-TargetResource' {
341353
CompatibilityLevel = $null
342354
RecoveryModel = $null
343355
OwnerName = $null
356+
SnapshotIsolation = $null
344357
}
345358
}
346359
}
@@ -408,6 +421,10 @@ Describe 'SqlDatabase\Test-TargetResource' {
408421
PropertyName = 'OwnerName'
409422
PropertyValue = 'dbOwner1'
410423
}
424+
@{
425+
PropertyName = 'SnapshotIsolation'
426+
PropertyValue = $false
427+
}
411428
) {
412429
BeforeAll {
413430
Mock -CommandName Get-TargetResource -MockWith {
@@ -799,5 +816,174 @@ Describe 'SqlDatabase\Set-TargetResource' {
799816
}
800817
}
801818
}
819+
820+
Context 'When updating an existing database' {
821+
BeforeAll {
822+
# Create a mock Connect-SQL that returns a Server with a real SMO Database object
823+
$mockConnectSQLWithSmoDatabase = {
824+
$server = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server'
825+
$server.Name = 'localhost'
826+
$server.InstanceName = 'MSSQLSERVER'
827+
$server.ComputerNamePhysicalNetBIOS = 'localhost'
828+
829+
# Create a real SMO Database object
830+
$database = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' -ArgumentList $server, 'AdventureWorks'
831+
832+
# Mock the Databases collection as a hashtable
833+
$server | Add-Member -MemberType ScriptProperty -Name 'Databases' -Value {
834+
return @{
835+
'AdventureWorks' = $database
836+
}
837+
}.GetNewClosure() -Force
838+
839+
return $server
840+
}
841+
842+
Mock -CommandName Connect-SQL -MockWith $mockConnectSQLWithSmoDatabase
843+
Mock -CommandName Enable-SqlDscDatabaseSnapshotIsolation
844+
Mock -CommandName Disable-SqlDscDatabaseSnapshotIsolation
845+
}
846+
847+
Context 'When enabling snapshot isolation' {
848+
It 'Should call Enable-SqlDscDatabaseSnapshotIsolation' {
849+
InModuleScope -ScriptBlock {
850+
Set-StrictMode -Version 1.0
851+
852+
$script:mockSetTargetResourceParameters['SnapshotIsolation'] = $true
853+
854+
$null = Set-TargetResource @mockSetTargetResourceParameters
855+
}
856+
857+
Should -Invoke -CommandName Enable-SqlDscDatabaseSnapshotIsolation -Exactly -Times 1 -Scope It
858+
Should -Invoke -CommandName Disable-SqlDscDatabaseSnapshotIsolation -Exactly -Times 0 -Scope It
859+
Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It
860+
}
861+
}
862+
863+
Context 'When disabling snapshot isolation' {
864+
It 'Should call Disable-SqlDscDatabaseSnapshotIsolation' {
865+
InModuleScope -ScriptBlock {
866+
Set-StrictMode -Version 1.0
867+
868+
$script:mockSetTargetResourceParameters['SnapshotIsolation'] = $false
869+
870+
$null = Set-TargetResource @mockSetTargetResourceParameters
871+
}
872+
873+
Should -Invoke -CommandName Enable-SqlDscDatabaseSnapshotIsolation -Exactly -Times 0 -Scope It
874+
Should -Invoke -CommandName Disable-SqlDscDatabaseSnapshotIsolation -Exactly -Times 1 -Scope It
875+
Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It
876+
}
877+
}
878+
}
879+
880+
Context 'When creating a new database' {
881+
BeforeAll {
882+
$mockNewObjectDatabase = {
883+
$database = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database'
884+
$database.Name = 'NewDatabase'
885+
886+
$database | Add-Member -MemberType 'ScriptMethod' -Name 'Create' -Value {
887+
if ($mockInvalidOperationForCreateMethod)
888+
{
889+
throw 'Mock Create Method was called with invalid operation.'
890+
}
891+
892+
if ($this.Name -ne $mockExpectedDatabaseNameToCreate)
893+
{
894+
throw "Called mocked Create() method without adding the right database. Expected '{0}'. But was '{1}'." `
895+
-f $mockExpectedDatabaseNameToCreate, $this.Name
896+
}
897+
898+
InModuleScope -ScriptBlock {
899+
$script:newObjectMethodCreateWasCalled += 1
900+
}
901+
} -Force
902+
903+
$database | Add-Member -MemberType 'ScriptMethod' -Name 'SetOwner' -Value {
904+
InModuleScope -ScriptBlock {
905+
$script:newObjectMethodSetOwnerWasCalled += 1
906+
}
907+
} -Force
908+
909+
return $database
910+
}
911+
912+
Mock -CommandName New-Object -MockWith $mockNewObjectDatabase -ParameterFilter {
913+
$TypeName -eq 'Microsoft.SqlServer.Management.Smo.Database'
914+
}
915+
916+
Mock -CommandName Enable-SqlDscDatabaseSnapshotIsolation
917+
}
918+
919+
BeforeEach {
920+
InModuleScope -ScriptBlock {
921+
$script:newObjectMethodCreateWasCalled = 0
922+
$script:newObjectMethodSetOwnerWasCalled = 0
923+
}
924+
}
925+
926+
Context 'When SnapshotIsolation is false' {
927+
It 'Should not call Enable-SqlDscDatabaseSnapshotIsolation' {
928+
$mockExpectedDatabaseNameToCreate = 'NewDatabase'
929+
930+
InModuleScope -ScriptBlock {
931+
Set-StrictMode -Version 1.0
932+
933+
$script:mockSetTargetResourceParameters['Ensure'] = 'Present'
934+
$script:mockSetTargetResourceParameters['Name'] = 'NewDatabase'
935+
$script:mockSetTargetResourceParameters['SnapshotIsolation'] = $false
936+
937+
$null = Set-TargetResource @mockSetTargetResourceParameters
938+
939+
$script:newObjectMethodCreateWasCalled | Should -Be 1
940+
}
941+
942+
Should -Invoke -CommandName Enable-SqlDscDatabaseSnapshotIsolation -Exactly -Times 0 -Scope It
943+
Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It
944+
}
945+
}
946+
947+
Context 'When SnapshotIsolation is not specified' {
948+
It 'Should not call Enable-SqlDscDatabaseSnapshotIsolation' {
949+
$mockExpectedDatabaseNameToCreate = 'NewDatabase'
950+
951+
InModuleScope -ScriptBlock {
952+
Set-StrictMode -Version 1.0
953+
954+
$script:mockSetTargetResourceParameters['Ensure'] = 'Present'
955+
$script:mockSetTargetResourceParameters['Name'] = 'NewDatabase'
956+
957+
$null = Set-TargetResource @mockSetTargetResourceParameters
958+
959+
$script:newObjectMethodCreateWasCalled | Should -Be 1
960+
}
961+
962+
Should -Invoke -CommandName Enable-SqlDscDatabaseSnapshotIsolation -Exactly -Times 0 -Scope It
963+
Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It
964+
}
965+
}
966+
967+
Context 'When SnapshotIsolation is true' {
968+
It 'Should call Enable-SqlDscDatabaseSnapshotIsolation' {
969+
$mockExpectedDatabaseNameToCreate = 'NewDatabase'
970+
971+
InModuleScope -ScriptBlock {
972+
Set-StrictMode -Version 1.0
973+
974+
$script:mockSetTargetResourceParameters['Ensure'] = 'Present'
975+
$script:mockSetTargetResourceParameters['Name'] = 'NewDatabase'
976+
$script:mockSetTargetResourceParameters['SnapshotIsolation'] = $true
977+
978+
$null = Set-TargetResource @mockSetTargetResourceParameters
979+
980+
$script:newObjectMethodCreateWasCalled | Should -Be 1
981+
}
982+
983+
Should -Invoke -CommandName Enable-SqlDscDatabaseSnapshotIsolation -Exactly -Times 1 -Scope It
984+
Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It
985+
}
986+
}
987+
}
802988
}
803989
}

0 commit comments

Comments
 (0)