diff --git a/CHANGELOG.md b/CHANGELOG.md index 29a3bd82b0..0d7659ae83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,9 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Removed parameter `OwnerName` [issue #2177](https://github.com/dsccommunity/SqlServerDsc/issues/2177). Use the new command `Set-SqlDscDatabaseOwner` to change database ownership instead. - BREAKING CHANGE: `Set-SqlDscDatabaseProperty` - - Removed parameters `AzureEdition` and `AzureServiceObjective`. Azure SQL Database - service tier and SLO changes should be managed using `Set-AzSqlDatabase` from the - Azure PowerShell module instead. See [issue #2177](https://github.com/dsccommunity/SqlServerDsc/issues/2177). + - Removed parameters `AzureEdition` and `AzureServiceObjective`. Azure SQL + Database service tier and SLO changes should be managed using + `Set-AzSqlDatabase` from the Azure PowerShell module instead. See + [issue #2177](https://github.com/dsccommunity/SqlServerDsc/issues/2177). - Removed parameter `DatabaseSnapshotBaseName`. Database snapshots should be created using the `New-SqlDscDatabaseSnapshot`, or the `New-SqlDscDatabase` command with the `-DatabaseSnapshotBaseName` parameter. @@ -49,6 +50,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 added to a Database later using `Add-SqlDscFileGroup`. - Added public command `New-SqlDscDataFile` to create DataFile objects for SQL Server FileGroups. This command simplifies creating DataFile objects with +- Added public command `Set-SqlDscDatabaseDefaultFileGroup` to set the default + filegroup or default FILESTREAM filegroup for a database in a SQL Server Database + Engine instance. This command uses the SMO `SetDefaultFileGroup()` or + `SetDefaultFileStreamFileGroup()` methods to change the default filegroup settings + ([issue #2328](https://github.com/dsccommunity/SqlServerDsc/issues/2328)). specified physical file paths, supporting both regular database files (.mdf, .ndf) and sparse files for database snapshots (.ss). The `FileGroup` parameter is mandatory, requiring DataFile objects to be created with an associated FileGroup. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index c73555db99..162811dfda 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -344,6 +344,7 @@ stages: 'tests/Integration/Commands/Get-SqlDscCompatibilityLevel.Integration.Tests.ps1' 'tests/Integration/Commands/Set-SqlDscDatabaseProperty.Integration.Tests.ps1' 'tests/Integration/Commands/Set-SqlDscDatabaseOwner.Integration.Tests.ps1' + 'tests/Integration/Commands/Set-SqlDscDatabaseDefaultFileGroup.Integration.Tests.ps1' 'tests/Integration/Commands/Test-SqlDscIsDatabase.Integration.Tests.ps1' 'tests/Integration/Commands/Test-SqlDscDatabaseProperty.Integration.Tests.ps1' 'tests/Integration/Commands/Get-SqlDscDatabasePermission.Integration.Tests.ps1' diff --git a/source/Public/Set-SqlDscDatabaseDefaultFileGroup.ps1 b/source/Public/Set-SqlDscDatabaseDefaultFileGroup.ps1 new file mode 100644 index 0000000000..aa56fa1215 --- /dev/null +++ b/source/Public/Set-SqlDscDatabaseDefaultFileGroup.ps1 @@ -0,0 +1,267 @@ +<# + .SYNOPSIS + Sets the default filegroup for a database in a SQL Server Database Engine instance. + + .DESCRIPTION + This command sets the default filegroup or default FILESTREAM filegroup for a + database in a SQL Server Database Engine instance. + + The filegroup must exist in the database. The command uses the SetDefaultFileGroup() + or SetDefaultFileStreamFileGroup() methods on the SMO Database object to change + the default filegroup. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER Name + Specifies the name of the database to modify. + + .PARAMETER DatabaseObject + Specifies the database object to modify (from Get-SqlDscDatabase). + + .PARAMETER Refresh + Specifies that the **ServerObject**'s databases should be refreshed before + trying to get the database object. This is helpful when databases could have been + modified outside of the **ServerObject**, for example through T-SQL. But + on instances with a large amount of databases it might be better to make + sure the **ServerObject** is recent enough. + + This parameter is only used when setting the default filegroup using **ServerObject** + and **Name** parameters. + + .PARAMETER DefaultFileGroup + Specifies the name of the filegroup that should be set as the default filegroup + for the database. This is mutually exclusive with **DefaultFileStreamFileGroup**. + + .PARAMETER DefaultFileStreamFileGroup + Specifies the name of the filegroup that should be set as the default FILESTREAM + filegroup for the database. This is mutually exclusive with **DefaultFileGroup**. + + .PARAMETER Force + Specifies that the default filegroup should be modified without any confirmation. + + .PARAMETER PassThru + Specifies that the database object should be returned after modification. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + Set-SqlDscDatabaseDefaultFileGroup -ServerObject $serverObject -Name 'MyDatabase' -DefaultFileGroup 'UserData' + + Sets the default filegroup of the database named **MyDatabase** to **UserData**. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + Set-SqlDscDatabaseDefaultFileGroup -ServerObject $serverObject -Name 'MyDatabase' -DefaultFileStreamFileGroup 'FileStreamData' + + Sets the default FILESTREAM filegroup of the database named **MyDatabase** to **FileStreamData**. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $databaseObject = $serverObject | Get-SqlDscDatabase -Name 'MyDatabase' + Set-SqlDscDatabaseDefaultFileGroup -DatabaseObject $databaseObject -DefaultFileGroup 'UserData' -Force + + Sets the default filegroup of the database using a database object without prompting for confirmation. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $databaseObject = $serverObject | Get-SqlDscDatabase -Name 'MyDatabase' + Set-SqlDscDatabaseDefaultFileGroup -DatabaseObject $databaseObject -DefaultFileStreamFileGroup 'FileStreamData' + + Sets the default FILESTREAM filegroup of the database using a database object. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + Set-SqlDscDatabaseDefaultFileGroup -ServerObject $serverObject -Name 'MyDatabase' -DefaultFileGroup 'UserData' -PassThru + + Sets the default filegroup and returns the updated database object. + + .INPUTS + Microsoft.SqlServer.Management.Smo.Database + + The database object to modify (from Get-SqlDscDatabase). + + .OUTPUTS + None + + No output is returned by default. + + .OUTPUTS + Microsoft.SqlServer.Management.Smo.Database + + Returns the database object when PassThru is specified. +#> +function Set-SqlDscDatabaseDefaultFileGroup +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues.')] + [OutputType()] + [OutputType([Microsoft.SqlServer.Management.Smo.Database])] + [CmdletBinding(DefaultParameterSetName = 'ServerObjectSet', SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] + param + ( + [Parameter(ParameterSetName = 'ServerObjectSet', Mandatory = $true)] + [Parameter(ParameterSetName = 'ServerObjectSetFileStream', Mandatory = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(ParameterSetName = 'ServerObjectSet', Mandatory = $true)] + [Parameter(ParameterSetName = 'ServerObjectSetFileStream', Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Name, + + [Parameter(ParameterSetName = 'ServerObjectSet')] + [Parameter(ParameterSetName = 'ServerObjectSetFileStream')] + [System.Management.Automation.SwitchParameter] + $Refresh, + + [Parameter(ParameterSetName = 'DatabaseObjectSet', Mandatory = $true, ValueFromPipeline = $true)] + [Parameter(ParameterSetName = 'DatabaseObjectSetFileStream', Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Database] + $DatabaseObject, + + [Parameter(ParameterSetName = 'ServerObjectSet', Mandatory = $true)] + [Parameter(ParameterSetName = 'DatabaseObjectSet', Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $DefaultFileGroup, + + [Parameter(ParameterSetName = 'ServerObjectSetFileStream', Mandatory = $true)] + [Parameter(ParameterSetName = 'DatabaseObjectSetFileStream', Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $DefaultFileStreamFileGroup, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $PassThru + ) + + begin + { + if ($Force.IsPresent -and -not $Confirm) + { + $ConfirmPreference = 'None' + } + } + + process + { + # Get the database object based on the parameter set + switch -Wildcard ($PSCmdlet.ParameterSetName) + { + 'ServerObjectSet*' + { + $previousErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = 'Stop' + + $sqlDatabaseObject = $ServerObject | + Get-SqlDscDatabase -Name $Name -Refresh:$Refresh -ErrorAction 'Stop' + + $ErrorActionPreference = $previousErrorActionPreference + } + + 'DatabaseObjectSet*' + { + $sqlDatabaseObject = $DatabaseObject + } + } + + # Determine which filegroup type to set + if ($PSBoundParameters.ContainsKey('DefaultFileGroup')) + { + $fileGroupName = $DefaultFileGroup + $currentFileGroup = $sqlDatabaseObject.DefaultFileGroup + $verboseDescriptionMessage = $script:localizedData.DatabaseDefaultFileGroup_Set_ShouldProcessVerboseDescription_DefaultFileGroup -f $sqlDatabaseObject.Name, $fileGroupName, $sqlDatabaseObject.Parent.InstanceName + $verboseWarningMessage = $script:localizedData.DatabaseDefaultFileGroup_Set_ShouldProcessVerboseWarning_DefaultFileGroup -f $sqlDatabaseObject.Name, $fileGroupName + } + else + { + $fileGroupName = $DefaultFileStreamFileGroup + $currentFileGroup = $sqlDatabaseObject.DefaultFileStreamFileGroup + $verboseDescriptionMessage = $script:localizedData.DatabaseDefaultFileGroup_Set_ShouldProcessVerboseDescription_DefaultFileStreamFileGroup -f $sqlDatabaseObject.Name, $fileGroupName, $sqlDatabaseObject.Parent.InstanceName + $verboseWarningMessage = $script:localizedData.DatabaseDefaultFileGroup_Set_ShouldProcessVerboseWarning_DefaultFileStreamFileGroup -f $sqlDatabaseObject.Name, $fileGroupName + } + + $captionMessage = $script:localizedData.DatabaseDefaultFileGroup_Set_ShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage)) + { + # Check if the default filegroup is already correct (idempotence) + if ($currentFileGroup -eq $fileGroupName) + { + if ($PSBoundParameters.ContainsKey('DefaultFileGroup')) + { + Write-Debug -Message ($script:localizedData.DatabaseDefaultFileGroup_AlreadyCorrect_DefaultFileGroup -f $sqlDatabaseObject.Name, $fileGroupName) + } + else + { + Write-Debug -Message ($script:localizedData.DatabaseDefaultFileGroup_AlreadyCorrect_DefaultFileStreamFileGroup -f $sqlDatabaseObject.Name, $fileGroupName) + } + } + else + { + if ($PSBoundParameters.ContainsKey('DefaultFileGroup')) + { + Write-Debug -Message ($script:localizedData.DatabaseDefaultFileGroup_Updating_DefaultFileGroup -f $sqlDatabaseObject.Name, $fileGroupName) + } + else + { + Write-Debug -Message ($script:localizedData.DatabaseDefaultFileGroup_Updating_DefaultFileStreamFileGroup -f $sqlDatabaseObject.Name, $fileGroupName) + } + + try + { + if ($PSBoundParameters.ContainsKey('DefaultFileGroup')) + { + $sqlDatabaseObject.SetDefaultFileGroup($fileGroupName) + } + else + { + $sqlDatabaseObject.SetDefaultFileStreamFileGroup($fileGroupName) + } + } + catch + { + if ($PSBoundParameters.ContainsKey('DefaultFileGroup')) + { + $errorMessage = $script:localizedData.DatabaseDefaultFileGroup_SetFailed_DefaultFileGroup -f $sqlDatabaseObject.Name, $fileGroupName + } + else + { + $errorMessage = $script:localizedData.DatabaseDefaultFileGroup_SetFailed_DefaultFileStreamFileGroup -f $sqlDatabaseObject.Name, $fileGroupName + } + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.InvalidOperationException]::new($errorMessage, $_.Exception), + 'SSDDFG0004', # cspell: disable-line + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $sqlDatabaseObject + ) + ) + } + + if ($PSBoundParameters.ContainsKey('DefaultFileGroup')) + { + Write-Debug -Message ($script:localizedData.DatabaseDefaultFileGroup_Updated_DefaultFileGroup -f $sqlDatabaseObject.Name, $fileGroupName) + } + else + { + Write-Debug -Message ($script:localizedData.DatabaseDefaultFileGroup_Updated_DefaultFileStreamFileGroup -f $sqlDatabaseObject.Name, $fileGroupName) + } + } + + if ($PassThru.IsPresent) + { + # Refresh the database object to get the updated default filegroup property + $sqlDatabaseObject.Refresh() + + $sqlDatabaseObject + } + } + } +} diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index 702c027cc7..db5d70508a 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -422,6 +422,22 @@ ConvertFrom-StringData @' # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. DatabaseOwner_Set_ShouldProcessCaption = Set database owner on instance + ## Set-SqlDscDatabaseDefaultFileGroup + DatabaseDefaultFileGroup_Updating_DefaultFileGroup = Setting default filegroup of database '{0}' to '{1}'. (SSDDFG0001) + DatabaseDefaultFileGroup_Updated_DefaultFileGroup = Default filegroup of database '{0}' was set to '{1}'. (SSDDFG0002) + DatabaseDefaultFileGroup_AlreadyCorrect_DefaultFileGroup = Default filegroup of database '{0}' is already set to '{1}'. (SSDDFG0003) + DatabaseDefaultFileGroup_SetFailed_DefaultFileGroup = Failed to set default filegroup of database '{0}' to '{1}'. (SSDDFG0004) + DatabaseDefaultFileGroup_Set_ShouldProcessVerboseDescription_DefaultFileGroup = Setting the default filegroup of the database '{0}' to '{1}' on the instance '{2}'. + DatabaseDefaultFileGroup_Set_ShouldProcessVerboseWarning_DefaultFileGroup = Are you sure you want to change the default filegroup of the database '{0}' to '{1}'? + DatabaseDefaultFileGroup_Updating_DefaultFileStreamFileGroup = Setting default FILESTREAM filegroup of database '{0}' to '{1}'. (SSDDFG0005) + DatabaseDefaultFileGroup_Updated_DefaultFileStreamFileGroup = Default FILESTREAM filegroup of database '{0}' was set to '{1}'. (SSDDFG0006) + DatabaseDefaultFileGroup_AlreadyCorrect_DefaultFileStreamFileGroup = Default FILESTREAM filegroup of database '{0}' is already set to '{1}'. (SSDDFG0007) + DatabaseDefaultFileGroup_SetFailed_DefaultFileStreamFileGroup = Failed to set default FILESTREAM filegroup of database '{0}' to '{1}'. (SSDDFG0008) + DatabaseDefaultFileGroup_Set_ShouldProcessVerboseDescription_DefaultFileStreamFileGroup = Setting the default FILESTREAM filegroup of the database '{0}' to '{1}' on the instance '{2}'. + DatabaseDefaultFileGroup_Set_ShouldProcessVerboseWarning_DefaultFileStreamFileGroup = Are you sure you want to change the default FILESTREAM filegroup of the database '{0}' to '{1}'? + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + DatabaseDefaultFileGroup_Set_ShouldProcessCaption = Set database default filegroup on instance + ## Enable-SqlDscDatabaseSnapshotIsolation DatabaseSnapshotIsolation_Enabling = Enabling snapshot isolation for database '{0}'. (ESDSI0002) DatabaseSnapshotIsolation_Enabled = Snapshot isolation for database '{0}' was enabled. (ESDSI0003) diff --git a/tests/Integration/Commands/README.md b/tests/Integration/Commands/README.md index 224a6bfbde..2ce42dbc28 100644 --- a/tests/Integration/Commands/README.md +++ b/tests/Integration/Commands/README.md @@ -95,6 +95,7 @@ New-SqlDscDatabaseSnapshot | 5 | 4 (New-SqlDscDatabase), 1 (Install-SqlDscServer Get-SqlDscCompatibilityLevel | 4 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - Set-SqlDscDatabaseProperty | 4 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - Set-SqlDscDatabaseOwner | 4 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - +Set-SqlDscDatabaseDefaultFileGroup | 4 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - Test-SqlDscIsDatabase | 4 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - Test-SqlDscDatabaseProperty | 4 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - Get-SqlDscDatabasePermission | 4 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | Test database, Test user diff --git a/tests/Integration/Commands/Set-SqlDscDatabaseDefaultFileGroup.Integration.Tests.ps1 b/tests/Integration/Commands/Set-SqlDscDatabaseDefaultFileGroup.Integration.Tests.ps1 new file mode 100644 index 0000000000..288f548b4c --- /dev/null +++ b/tests/Integration/Commands/Set-SqlDscDatabaseDefaultFileGroup.Integration.Tests.ps1 @@ -0,0 +1,305 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have 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' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have 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 -ResolveDependency -Tasks noop" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' +} + +Describe 'Set-SqlDscDatabaseDefaultFileGroup' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { + BeforeAll { + $script:mockInstanceName = 'DSCSQLTEST' + + $mockSqlAdministratorUserName = 'SqlAdmin' + $mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force + + $script:mockSqlAdminCredential = [System.Management.Automation.PSCredential]::new($mockSqlAdministratorUserName, $mockSqlAdministratorPassword) + + $script:serverObject = Connect-SqlDscDatabaseEngine -InstanceName $script:mockInstanceName -Credential $script:mockSqlAdminCredential -ErrorAction 'Stop' + + # Test database names + $script:testDatabaseName = 'SqlDscTestSetDefaultFG_' + (Get-Random) + $script:testDatabaseNameForObject = 'SqlDscTestSetDefaultFGObj_' + (Get-Random) + $script:testDatabaseNameForFileStream = 'SqlDscTestSetDefaultFSFG_' + (Get-Random) + + # Get the default data path dynamically + $script:dataPath = $script:serverObject.Settings.DefaultFile + + if (-not $script:dataPath) + { + $script:dataPath = $script:serverObject.Information.MasterDBPath + } + + $script:dataPath = $script:dataPath.TrimEnd('\') + + # Create test databases with filegroups + $null = Invoke-SqlDscQuery -ServerObject $script:serverObject -DatabaseName 'master' -Query @" +CREATE DATABASE [$($script:testDatabaseName)] +GO +USE [$($script:testDatabaseName)] +GO +-- Create a secondary filegroup +ALTER DATABASE [$($script:testDatabaseName)] +ADD FILEGROUP [UserDataFileGroup] +GO +-- Add a file to the filegroup +ALTER DATABASE [$($script:testDatabaseName)] +ADD FILE ( + NAME = N'UserData1', + FILENAME = N'$($script:dataPath)\UserData1.ndf', + SIZE = 5MB, + MAXSIZE = UNLIMITED, + FILEGROWTH = 5MB +) TO FILEGROUP [UserDataFileGroup] +GO +-- Create another filegroup +ALTER DATABASE [$($script:testDatabaseName)] +ADD FILEGROUP [SecondaryFileGroup] +GO +-- Add a file to the second filegroup +ALTER DATABASE [$($script:testDatabaseName)] +ADD FILE ( + NAME = N'SecondaryData1', + FILENAME = N'$($script:dataPath)\SecondaryData1.ndf', + SIZE = 5MB, + MAXSIZE = UNLIMITED, + FILEGROWTH = 5MB +) TO FILEGROUP [SecondaryFileGroup] +GO +"@ -Force -ErrorAction 'Stop' + + # Create second test database + $null = Invoke-SqlDscQuery -ServerObject $script:serverObject -DatabaseName 'master' -Query @" +CREATE DATABASE [$($script:testDatabaseNameForObject)] +GO +USE [$($script:testDatabaseNameForObject)] +GO +-- Create a secondary filegroup +ALTER DATABASE [$($script:testDatabaseNameForObject)] +ADD FILEGROUP [ObjectTestFileGroup] +GO +-- Add a file to the filegroup +ALTER DATABASE [$($script:testDatabaseNameForObject)] +ADD FILE ( + NAME = N'ObjectTest1', + FILENAME = N'$($script:dataPath)\ObjectTest1.ndf', + SIZE = 5MB, + MAXSIZE = UNLIMITED, + FILEGROWTH = 5MB +) TO FILEGROUP [ObjectTestFileGroup] +GO +"@ -Force -ErrorAction 'Stop' + + # Create third test database for FILESTREAM testing (if FILESTREAM is enabled) + try + { + $null = Invoke-SqlDscQuery -ServerObject $script:serverObject -DatabaseName 'master' -Query @" +CREATE DATABASE [$($script:testDatabaseNameForFileStream)] +GO +USE [$($script:testDatabaseNameForFileStream)] +GO +-- Create a FILESTREAM filegroup +ALTER DATABASE [$($script:testDatabaseNameForFileStream)] +ADD FILEGROUP [FileStreamFileGroup] CONTAINS FILESTREAM +GO +-- Add a file to the FILESTREAM filegroup +ALTER DATABASE [$($script:testDatabaseNameForFileStream)] +ADD FILE ( + NAME = N'FileStreamData1', + FILENAME = N'$($script:dataPath)\FileStreamData1' +) TO FILEGROUP [FileStreamFileGroup] +GO +"@ -Force -ErrorAction 'Stop' + + $script:fileStreamSupported = $true + } + catch + { + Write-Verbose -Message "FILESTREAM is not enabled on this instance. Skipping FILESTREAM tests." -Verbose + $script:fileStreamSupported = $false + } + + # Get the current default filegroup to restore later + $testDb = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:testDatabaseName -ErrorAction 'Stop' + $script:originalDefaultFileGroup = $testDb.DefaultFileGroup + Write-Verbose -Message "Original default filegroup of database '$($script:testDatabaseName)' is '$($script:originalDefaultFileGroup)'." -Verbose + } + + AfterAll { + # Clean up test databases + $testDatabasesToRemove = @($script:testDatabaseName, $script:testDatabaseNameForObject) + + if ($script:fileStreamSupported) + { + $testDatabasesToRemove += $script:testDatabaseNameForFileStream + } + + foreach ($dbName in $testDatabasesToRemove) + { + $existingDb = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $dbName -ErrorAction 'SilentlyContinue' + + if ($existingDb) + { + $null = Remove-SqlDscDatabase -DatabaseObject $existingDb -Force -ErrorAction 'Stop' + } + } + + Disconnect-SqlDscDatabaseEngine -ServerObject $script:serverObject -ErrorAction 'Stop' + } + + Context 'When setting default filegroup using ServerObject parameter set' { + It 'Should set default filegroup to UserDataFileGroup successfully' { + $resultDb = Set-SqlDscDatabaseDefaultFileGroup -ServerObject $script:serverObject -Name $script:testDatabaseName -DefaultFileGroup 'UserDataFileGroup' -Force -PassThru -ErrorAction 'Stop' + $resultDb.DefaultFileGroup | Should -Be 'UserDataFileGroup' + + # Verify the change + $updatedDb = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:testDatabaseName -Refresh -ErrorAction 'Stop' + $updatedDb.DefaultFileGroup | Should -Be 'UserDataFileGroup' + } + + It 'Should set default filegroup to SecondaryFileGroup successfully' { + $null = Set-SqlDscDatabaseDefaultFileGroup -ServerObject $script:serverObject -Name $script:testDatabaseName -DefaultFileGroup 'SecondaryFileGroup' -Force -ErrorAction 'Stop' + + # Verify the change + $updatedDb = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:testDatabaseName -Refresh -ErrorAction 'Stop' + $updatedDb.DefaultFileGroup | Should -Be 'SecondaryFileGroup' + } + + It 'Should be idempotent when default filegroup is already set' { + $fileGroupName = 'UserDataFileGroup' + + # Set default filegroup + $null = Set-SqlDscDatabaseDefaultFileGroup -ServerObject $script:serverObject -Name $script:testDatabaseName -DefaultFileGroup $fileGroupName -Force -ErrorAction 'Stop' + + # Set same default filegroup again - should not throw + $null = Set-SqlDscDatabaseDefaultFileGroup -ServerObject $script:serverObject -Name $script:testDatabaseName -DefaultFileGroup $fileGroupName -Force -ErrorAction 'Stop' + + # Verify the value is still correct + $updatedDb = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:testDatabaseName -Refresh -ErrorAction 'Stop' + $updatedDb.DefaultFileGroup | Should -Be $fileGroupName + } + + It 'Should throw error when trying to set default filegroup of non-existent database' { + { Set-SqlDscDatabaseDefaultFileGroup -ServerObject $script:serverObject -Name 'NonExistentDatabase' -DefaultFileGroup 'UserDataFileGroup' -Force -ErrorAction 'Stop' } | + Should -Throw + } + + It 'Should throw error when trying to set non-existent filegroup as default' { + { Set-SqlDscDatabaseDefaultFileGroup -ServerObject $script:serverObject -Name $script:testDatabaseName -DefaultFileGroup 'NonExistentFileGroup' -Force -ErrorAction 'Stop' } | + Should -Throw + } + } + + Context 'When setting default filegroup using DatabaseObject parameter set' { + It 'Should set default filegroup using database object' { + $databaseObject = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:testDatabaseNameForObject -ErrorAction 'Stop' + + $null = Set-SqlDscDatabaseDefaultFileGroup -DatabaseObject $databaseObject -DefaultFileGroup 'ObjectTestFileGroup' -Force -ErrorAction 'Stop' + + # Verify the change + $updatedDb = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:testDatabaseNameForObject -Refresh -ErrorAction 'Stop' + $updatedDb.DefaultFileGroup | Should -Be 'ObjectTestFileGroup' + } + + It 'Should support pipeline input with database object' { + $databaseObject = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:testDatabaseNameForObject -ErrorAction 'Stop' + + $null = $databaseObject | Set-SqlDscDatabaseDefaultFileGroup -DefaultFileGroup 'PRIMARY' -Force -ErrorAction 'Stop' + + # Verify the change + $updatedDb = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:testDatabaseNameForObject -Refresh -ErrorAction 'Stop' + $updatedDb.DefaultFileGroup | Should -Be 'PRIMARY' + } + + It 'Should revert default filegroup back to PRIMARY' { + $databaseObject = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:testDatabaseName -ErrorAction 'Stop' + + $null = Set-SqlDscDatabaseDefaultFileGroup -DatabaseObject $databaseObject -DefaultFileGroup 'PRIMARY' -Force -ErrorAction 'Stop' + + # Verify the change + $updatedDb = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:testDatabaseName -Refresh -ErrorAction 'Stop' + $updatedDb.DefaultFileGroup | Should -Be 'PRIMARY' + } + } + + Context 'When using the Refresh parameter' { + It 'Should refresh the database collection before setting default filegroup' { + $fileGroupName = 'UserDataFileGroup' + $null = Set-SqlDscDatabaseDefaultFileGroup -ServerObject $script:serverObject -Name $script:testDatabaseName -DefaultFileGroup $fileGroupName -Refresh -Force -ErrorAction 'Stop' + + # Verify the change + $updatedDb = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:testDatabaseName -Refresh -ErrorAction 'Stop' + $updatedDb.DefaultFileGroup | Should -Be $fileGroupName + } + } + + Context 'When using the PassThru parameter' { + It 'Should return the database object when PassThru is specified' { + $fileGroupName = 'SecondaryFileGroup' + $result = Set-SqlDscDatabaseDefaultFileGroup -ServerObject $script:serverObject -Name $script:testDatabaseName -DefaultFileGroup $fileGroupName -PassThru -Force -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.Database' + $result.Name | Should -Be $script:testDatabaseName + $result.DefaultFileGroup | Should -Be $fileGroupName + } + } + + Context 'When setting default FILESTREAM filegroup' -Skip:(-not $script:fileStreamSupported) { + It 'Should set default FILESTREAM filegroup successfully' { + $resultDb = Set-SqlDscDatabaseDefaultFileGroup -ServerObject $script:serverObject -Name $script:testDatabaseNameForFileStream -DefaultFileStreamFileGroup 'FileStreamFileGroup' -Force -PassThru -ErrorAction 'Stop' + $resultDb.DefaultFileStreamFileGroup | Should -Be 'FileStreamFileGroup' + + # Verify the change + $updatedDb = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:testDatabaseNameForFileStream -Refresh -ErrorAction 'Stop' + $updatedDb.DefaultFileStreamFileGroup | Should -Be 'FileStreamFileGroup' + } + + It 'Should be idempotent when default FILESTREAM filegroup is already set' { + $fileGroupName = 'FileStreamFileGroup' + + # Set default FILESTREAM filegroup + $null = Set-SqlDscDatabaseDefaultFileGroup -ServerObject $script:serverObject -Name $script:testDatabaseNameForFileStream -DefaultFileStreamFileGroup $fileGroupName -Force -ErrorAction 'Stop' + + # Set same default FILESTREAM filegroup again - should not throw + $null = Set-SqlDscDatabaseDefaultFileGroup -ServerObject $script:serverObject -Name $script:testDatabaseNameForFileStream -DefaultFileStreamFileGroup $fileGroupName -Force -ErrorAction 'Stop' + + # Verify the value is still correct + $updatedDb = Get-SqlDscDatabase -ServerObject $script:serverObject -Name $script:testDatabaseNameForFileStream -Refresh -ErrorAction 'Stop' + $updatedDb.DefaultFileStreamFileGroup | Should -Be $fileGroupName + } + + It 'Should return the database object when PassThru is specified for FILESTREAM filegroup' { + $fileGroupName = 'FileStreamFileGroup' + $result = Set-SqlDscDatabaseDefaultFileGroup -ServerObject $script:serverObject -Name $script:testDatabaseNameForFileStream -DefaultFileStreamFileGroup $fileGroupName -PassThru -Force -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.Database' + $result.Name | Should -Be $script:testDatabaseNameForFileStream + $result.DefaultFileStreamFileGroup | Should -Be $fileGroupName + } + } +} diff --git a/tests/Unit/Public/Set-SqlDscDatabaseDefaultFileGroup.Tests.ps1 b/tests/Unit/Public/Set-SqlDscDatabaseDefaultFileGroup.Tests.ps1 new file mode 100644 index 0000000000..a507298a3b --- /dev/null +++ b/tests/Unit/Public/Set-SqlDscDatabaseDefaultFileGroup.Tests.ps1 @@ -0,0 +1,445 @@ +<# + .SYNOPSIS + Unit tests for Set-SqlDscDatabaseDefaultFileGroup. + + .DESCRIPTION + Unit tests for Set-SqlDscDatabaseDefaultFileGroup. +#> + +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have 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' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have 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 -ResolveDependency -Tasks noop" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:dscModuleName -Force -ErrorAction 'Stop' + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force + + Remove-Item -Path 'env:SqlServerDscCI' +} + +Describe 'Set-SqlDscDatabaseDefaultFileGroup' -Tag 'Public' { + Context 'When validating parameter sets' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = 'ServerObjectSet' + ExpectedParameters = '-ServerObject -Name -DefaultFileGroup [-Refresh] [-Force] [-PassThru] [-WhatIf] [-Confirm] []' + } + @{ + ExpectedParameterSetName = 'ServerObjectSetFileStream' + ExpectedParameters = '-ServerObject -Name -DefaultFileStreamFileGroup [-Refresh] [-Force] [-PassThru] [-WhatIf] [-Confirm] []' + } + @{ + ExpectedParameterSetName = 'DatabaseObjectSet' + ExpectedParameters = '-DatabaseObject -DefaultFileGroup [-Force] [-PassThru] [-WhatIf] [-Confirm] []' + } + @{ + ExpectedParameterSetName = 'DatabaseObjectSetFileStream' + ExpectedParameters = '-DatabaseObject -DefaultFileStreamFileGroup [-Force] [-PassThru] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'Set-SqlDscDatabaseDefaultFileGroup').ParameterSets | + Where-Object -FilterScript { $_.Name -eq $ExpectedParameterSetName } | + Select-Object -Property @( + @{ Name = 'ParameterSetName'; Expression = { $_.Name } }, + @{ Name = 'ParameterListAsString'; Expression = { $_.ToString() } } + ) + + $result.ParameterSetName | Should -Be $ExpectedParameterSetName + $result.ParameterListAsString | Should -Be $ExpectedParameters + } + } + + Context 'When setting default filegroup using ServerObject and Name' { + BeforeAll { + $mockDatabaseObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'TestDatabase' -Force + $mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFileGroup' -Value 'PRIMARY' -Force + $mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFileStreamFileGroup' -Value '' -Force + $mockDatabaseObject | Add-Member -MemberType 'ScriptProperty' -Name 'Parent' -Value { + $mockParent = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockParent | Add-Member -MemberType 'NoteProperty' -Name 'InstanceName' -Value 'TestInstance' -Force + return $mockParent + } -Force + $script:setDefaultFileGroupCalled = $false + $mockDatabaseObject | Add-Member -MemberType 'ScriptMethod' -Name 'SetDefaultFileGroup' -Value { + param($FileGroupName) + $script:setDefaultFileGroupCalled = $true + $this.DefaultFileGroup = $FileGroupName + } -Force + $script:setDefaultFileStreamFileGroupCalled = $false + $mockDatabaseObject | Add-Member -MemberType 'ScriptMethod' -Name 'SetDefaultFileStreamFileGroup' -Value { + param($FileGroupName) + $script:setDefaultFileStreamFileGroupCalled = $true + $this.DefaultFileStreamFileGroup = $FileGroupName + } -Force + $mockDatabaseObject | Add-Member -MemberType 'ScriptMethod' -Name 'Refresh' -Value { + # Mock implementation - in real SMO this updates properties from server + } -Force + + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'InstanceName' -Value 'TestInstance' -Force + $mockServerObject | Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'TestDatabase' = $mockDatabaseObject + } | Add-Member -MemberType 'ScriptMethod' -Name 'Refresh' -Value { + # Mock implementation + } -PassThru -Force + } -Force + } + + It 'Should set default filegroup successfully' { + $script:setDefaultFileGroupCalled = $false + $null = Set-SqlDscDatabaseDefaultFileGroup -ServerObject $mockServerObject -Name 'TestDatabase' -DefaultFileGroup 'UserData' -Force + $mockDatabaseObject.DefaultFileGroup | Should -Be 'UserData' + $script:setDefaultFileGroupCalled | Should -BeTrue -Because 'SetDefaultFileGroup should be called to change the default filegroup' + } + + It 'Should return a database object when PassThru is specified' { + # Reset default filegroup to ensure the test starts with a different filegroup + $mockDatabaseObject.DefaultFileGroup = 'PRIMARY' + $script:setDefaultFileGroupCalled = $false + $result = Set-SqlDscDatabaseDefaultFileGroup -ServerObject $mockServerObject -Name 'TestDatabase' -DefaultFileGroup 'UserData' -Force -PassThru + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'TestDatabase' + } + + It 'Should refresh database properties when Refresh is specified' { + # Reset default filegroup for this test + $mockDatabaseObject.DefaultFileGroup = 'PRIMARY' + $script:setDefaultFileGroupCalled = $false + $null = Set-SqlDscDatabaseDefaultFileGroup -ServerObject $mockServerObject -Name 'TestDatabase' -DefaultFileGroup 'UserData' -Force -Refresh + $mockDatabaseObject.DefaultFileGroup | Should -Be 'UserData' + } + + It 'Should call SetDefaultFileGroup with correct filegroup name' { + # Reset default filegroup for this test + $mockDatabaseObject.DefaultFileGroup = 'PRIMARY' + $script:setDefaultFileGroupCalled = $false + $null = Set-SqlDscDatabaseDefaultFileGroup -ServerObject $mockServerObject -Name 'TestDatabase' -DefaultFileGroup 'NewFileGroup' -Force + $mockDatabaseObject.DefaultFileGroup | Should -Be 'NewFileGroup' + $script:setDefaultFileGroupCalled | Should -BeTrue -Because 'SetDefaultFileGroup should be called to change the default filegroup' + } + } + + Context 'When setting default FILESTREAM filegroup using ServerObject and Name' { + BeforeAll { + $mockDatabaseObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'TestDatabase' -Force + $mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFileGroup' -Value 'PRIMARY' -Force + $mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFileStreamFileGroup' -Value '' -Force + $mockDatabaseObject | Add-Member -MemberType 'ScriptProperty' -Name 'Parent' -Value { + $mockParent = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockParent | Add-Member -MemberType 'NoteProperty' -Name 'InstanceName' -Value 'TestInstance' -Force + return $mockParent + } -Force + $script:setDefaultFileGroupCalled = $false + $mockDatabaseObject | Add-Member -MemberType 'ScriptMethod' -Name 'SetDefaultFileGroup' -Value { + param($FileGroupName) + $script:setDefaultFileGroupCalled = $true + $this.DefaultFileGroup = $FileGroupName + } -Force + $script:setDefaultFileStreamFileGroupCalled = $false + $mockDatabaseObject | Add-Member -MemberType 'ScriptMethod' -Name 'SetDefaultFileStreamFileGroup' -Value { + param($FileGroupName) + $script:setDefaultFileStreamFileGroupCalled = $true + $this.DefaultFileStreamFileGroup = $FileGroupName + } -Force + $mockDatabaseObject | Add-Member -MemberType 'ScriptMethod' -Name 'Refresh' -Value { + # Mock implementation - in real SMO this updates properties from server + } -Force + + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'InstanceName' -Value 'TestInstance' -Force + $mockServerObject | Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'TestDatabase' = $mockDatabaseObject + } | Add-Member -MemberType 'ScriptMethod' -Name 'Refresh' -Value { + # Mock implementation + } -PassThru -Force + } -Force + } + + It 'Should set default FILESTREAM filegroup successfully' { + $script:setDefaultFileStreamFileGroupCalled = $false + $null = Set-SqlDscDatabaseDefaultFileGroup -ServerObject $mockServerObject -Name 'TestDatabase' -DefaultFileStreamFileGroup 'FileStreamData' -Force + $mockDatabaseObject.DefaultFileStreamFileGroup | Should -Be 'FileStreamData' + $script:setDefaultFileStreamFileGroupCalled | Should -BeTrue -Because 'SetDefaultFileStreamFileGroup should be called to change the default FILESTREAM filegroup' + } + + It 'Should return a database object when PassThru is specified' { + # Reset default FILESTREAM filegroup to ensure the test starts with a different filegroup + $mockDatabaseObject.DefaultFileStreamFileGroup = '' + $script:setDefaultFileStreamFileGroupCalled = $false + $result = Set-SqlDscDatabaseDefaultFileGroup -ServerObject $mockServerObject -Name 'TestDatabase' -DefaultFileStreamFileGroup 'FileStreamData' -Force -PassThru + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'TestDatabase' + } + } + + Context 'When setting default filegroup using DatabaseObject' { + BeforeAll { + $mockDatabaseObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'TestDatabase' -Force + $mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFileGroup' -Value 'PRIMARY' -Force + $mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFileStreamFileGroup' -Value '' -Force + $mockDatabaseObject | Add-Member -MemberType 'ScriptProperty' -Name 'Parent' -Value { + $mockParent = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockParent | Add-Member -MemberType 'NoteProperty' -Name 'InstanceName' -Value 'TestInstance' -Force + return $mockParent + } -Force + $script:setDefaultFileGroupCalled = $false + $mockDatabaseObject | Add-Member -MemberType 'ScriptMethod' -Name 'SetDefaultFileGroup' -Value { + param($FileGroupName) + $script:setDefaultFileGroupCalled = $true + $this.DefaultFileGroup = $FileGroupName + } -Force + $script:setDefaultFileStreamFileGroupCalled = $false + $mockDatabaseObject | Add-Member -MemberType 'ScriptMethod' -Name 'SetDefaultFileStreamFileGroup' -Value { + param($FileGroupName) + $script:setDefaultFileStreamFileGroupCalled = $true + $this.DefaultFileStreamFileGroup = $FileGroupName + } -Force + $mockDatabaseObject | Add-Member -MemberType 'ScriptMethod' -Name 'Refresh' -Value { + # Mock implementation - in real SMO this updates properties from server + } -Force + } + + It 'Should set default filegroup successfully' { + $script:setDefaultFileGroupCalled = $false + $null = Set-SqlDscDatabaseDefaultFileGroup -DatabaseObject $mockDatabaseObject -DefaultFileGroup 'UserData' -Force + $mockDatabaseObject.DefaultFileGroup | Should -Be 'UserData' + $script:setDefaultFileGroupCalled | Should -BeTrue -Because 'SetDefaultFileGroup should be called to change the default filegroup' + } + + It 'Should return a database object when PassThru is specified' { + # Reset default filegroup to ensure the test starts with a different filegroup + $mockDatabaseObject.DefaultFileGroup = 'PRIMARY' + $script:setDefaultFileGroupCalled = $false + $result = Set-SqlDscDatabaseDefaultFileGroup -DatabaseObject $mockDatabaseObject -DefaultFileGroup 'UserData' -Force -PassThru + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'TestDatabase' + } + + It 'Should call SetDefaultFileGroup with correct filegroup name' { + $mockDatabaseObject.DefaultFileGroup = 'PRIMARY' + $script:setDefaultFileGroupCalled = $false + $null = Set-SqlDscDatabaseDefaultFileGroup -DatabaseObject $mockDatabaseObject -DefaultFileGroup 'CustomFileGroup' -Force + $mockDatabaseObject.DefaultFileGroup | Should -Be 'CustomFileGroup' + $script:setDefaultFileGroupCalled | Should -BeTrue -Because 'SetDefaultFileGroup should be called to change the default filegroup' + } + } + + Context 'When setting default FILESTREAM filegroup using DatabaseObject' { + BeforeAll { + $mockDatabaseObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'TestDatabase' -Force + $mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFileGroup' -Value 'PRIMARY' -Force + $mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFileStreamFileGroup' -Value '' -Force + $mockDatabaseObject | Add-Member -MemberType 'ScriptProperty' -Name 'Parent' -Value { + $mockParent = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockParent | Add-Member -MemberType 'NoteProperty' -Name 'InstanceName' -Value 'TestInstance' -Force + return $mockParent + } -Force + $script:setDefaultFileGroupCalled = $false + $mockDatabaseObject | Add-Member -MemberType 'ScriptMethod' -Name 'SetDefaultFileGroup' -Value { + param($FileGroupName) + $script:setDefaultFileGroupCalled = $true + $this.DefaultFileGroup = $FileGroupName + } -Force + $script:setDefaultFileStreamFileGroupCalled = $false + $mockDatabaseObject | Add-Member -MemberType 'ScriptMethod' -Name 'SetDefaultFileStreamFileGroup' -Value { + param($FileGroupName) + $script:setDefaultFileStreamFileGroupCalled = $true + $this.DefaultFileStreamFileGroup = $FileGroupName + } -Force + $mockDatabaseObject | Add-Member -MemberType 'ScriptMethod' -Name 'Refresh' -Value { + # Mock implementation - in real SMO this updates properties from server + } -Force + } + + It 'Should set default FILESTREAM filegroup successfully' { + $script:setDefaultFileStreamFileGroupCalled = $false + $null = Set-SqlDscDatabaseDefaultFileGroup -DatabaseObject $mockDatabaseObject -DefaultFileStreamFileGroup 'FileStreamData' -Force + $mockDatabaseObject.DefaultFileStreamFileGroup | Should -Be 'FileStreamData' + $script:setDefaultFileStreamFileGroupCalled | Should -BeTrue -Because 'SetDefaultFileStreamFileGroup should be called to change the default FILESTREAM filegroup' + } + + It 'Should return a database object when PassThru is specified' { + # Reset default FILESTREAM filegroup to ensure the test starts with a different filegroup + $mockDatabaseObject.DefaultFileStreamFileGroup = '' + $script:setDefaultFileStreamFileGroupCalled = $false + $result = Set-SqlDscDatabaseDefaultFileGroup -DatabaseObject $mockDatabaseObject -DefaultFileStreamFileGroup 'FileStreamData' -Force -PassThru + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'TestDatabase' + } + } + + Context 'When default filegroup is already set to desired value' { + BeforeAll { + $mockDatabaseObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'TestDatabase' -Force + $mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFileGroup' -Value 'UserData' -Force + $mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFileStreamFileGroup' -Value '' -Force + $mockDatabaseObject | Add-Member -MemberType 'ScriptProperty' -Name 'Parent' -Value { + $mockParent = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockParent | Add-Member -MemberType 'NoteProperty' -Name 'InstanceName' -Value 'TestInstance' -Force + return $mockParent + } -Force + # Track whether SetDefaultFileGroup was called using script-scoped variables + $script:setDefaultFileGroupCalled = $false + $mockDatabaseObject | Add-Member -MemberType 'ScriptMethod' -Name 'SetDefaultFileGroup' -Value { + param($FileGroupName) + $script:setDefaultFileGroupCalled = $true + $this.DefaultFileGroup = $FileGroupName + } -Force + $mockDatabaseObject | Add-Member -MemberType 'ScriptMethod' -Name 'Refresh' -Value { + # Mock implementation - in real SMO this updates properties from server + } -Force + } + + It 'Should not call SetDefaultFileGroup when default filegroup already matches the desired value' { + # Reset the flags before the test + $script:setDefaultFileGroupCalled = $false + + # The command should skip calling SetDefaultFileGroup when the default filegroup already matches + $null = Set-SqlDscDatabaseDefaultFileGroup -DatabaseObject $mockDatabaseObject -DefaultFileGroup 'UserData' -Force + + # Verify SetDefaultFileGroup was not called (idempotent behavior) + $script:setDefaultFileGroupCalled | Should -BeFalse -Because 'SetDefaultFileGroup should not be called when the default filegroup already matches' + $mockDatabaseObject.DefaultFileGroup | Should -Be 'UserData' + } + } + + Context 'When default FILESTREAM filegroup is already set to desired value' { + BeforeAll { + $mockDatabaseObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'TestDatabase' -Force + $mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFileGroup' -Value 'PRIMARY' -Force + $mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFileStreamFileGroup' -Value 'FileStreamData' -Force + $mockDatabaseObject | Add-Member -MemberType 'ScriptProperty' -Name 'Parent' -Value { + $mockParent = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockParent | Add-Member -MemberType 'NoteProperty' -Name 'InstanceName' -Value 'TestInstance' -Force + return $mockParent + } -Force + # Track whether SetDefaultFileStreamFileGroup was called using script-scoped variables + $script:setDefaultFileStreamFileGroupCalled = $false + $mockDatabaseObject | Add-Member -MemberType 'ScriptMethod' -Name 'SetDefaultFileStreamFileGroup' -Value { + param($FileGroupName) + $script:setDefaultFileStreamFileGroupCalled = $true + $this.DefaultFileStreamFileGroup = $FileGroupName + } -Force + $mockDatabaseObject | Add-Member -MemberType 'ScriptMethod' -Name 'Refresh' -Value { + # Mock implementation - in real SMO this updates properties from server + } -Force + } + + It 'Should not call SetDefaultFileStreamFileGroup when default FILESTREAM filegroup already matches the desired value' { + # Reset the flags before the test + $script:setDefaultFileStreamFileGroupCalled = $false + + # The command should skip calling SetDefaultFileStreamFileGroup when the default FILESTREAM filegroup already matches + $null = Set-SqlDscDatabaseDefaultFileGroup -DatabaseObject $mockDatabaseObject -DefaultFileStreamFileGroup 'FileStreamData' -Force + + # Verify SetDefaultFileStreamFileGroup was not called (idempotent behavior) + $script:setDefaultFileStreamFileGroupCalled | Should -BeFalse -Because 'SetDefaultFileStreamFileGroup should not be called when the default FILESTREAM filegroup already matches' + $mockDatabaseObject.DefaultFileStreamFileGroup | Should -Be 'FileStreamData' + } + } + + Context 'When database modification fails' { + BeforeAll { + $mockDatabaseObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Database' + $mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'TestDatabase' -Force + $mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFileGroup' -Value 'PRIMARY' -Force + $mockDatabaseObject | Add-Member -MemberType 'NoteProperty' -Name 'DefaultFileStreamFileGroup' -Value '' -Force + $mockDatabaseObject | Add-Member -MemberType 'ScriptProperty' -Name 'Parent' -Value { + $mockParent = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockParent | Add-Member -MemberType 'NoteProperty' -Name 'InstanceName' -Value 'TestInstance' -Force + return $mockParent + } -Force + $mockDatabaseObject | Add-Member -MemberType 'ScriptMethod' -Name 'SetDefaultFileGroup' -Value { + param($FileGroupName) + throw 'Simulated SetDefaultFileGroup() failure' + } -Force + $mockDatabaseObject | Add-Member -MemberType 'ScriptMethod' -Name 'SetDefaultFileStreamFileGroup' -Value { + param($FileGroupName) + throw 'Simulated SetDefaultFileStreamFileGroup() failure' + } -Force + $mockDatabaseObject | Add-Member -MemberType 'ScriptMethod' -Name 'Refresh' -Value { + # Mock implementation - in real SMO this updates properties from server + } -Force + } + + It 'Should throw error when SetDefaultFileGroup() fails' { + { Set-SqlDscDatabaseDefaultFileGroup -DatabaseObject $mockDatabaseObject -DefaultFileGroup 'UserData' -Force } | + Should -Throw -ExpectedMessage '*Failed to set default filegroup of database*' + } + + It 'Should throw error when SetDefaultFileStreamFileGroup() fails' { + { Set-SqlDscDatabaseDefaultFileGroup -DatabaseObject $mockDatabaseObject -DefaultFileStreamFileGroup 'FileStreamData' -Force } | + Should -Throw -ExpectedMessage '*Failed to set default FILESTREAM filegroup of database*' + } + } + + Context 'When database does not exist' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject | Add-Member -MemberType 'NoteProperty' -Name 'InstanceName' -Value 'TestInstance' -Force + $mockServerObject | Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{} | Add-Member -MemberType 'ScriptMethod' -Name 'Refresh' -Value { + # Mock implementation + } -PassThru -Force + } -Force + } + + It 'Should throw error when database does not exist for DefaultFileGroup' { + { Set-SqlDscDatabaseDefaultFileGroup -ServerObject $mockServerObject -Name 'NonExistentDatabase' -DefaultFileGroup 'UserData' -Force } | + Should -Throw -ExpectedMessage '*Database * was not found*' + } + + It 'Should throw error when database does not exist for DefaultFileStreamFileGroup' { + { Set-SqlDscDatabaseDefaultFileGroup -ServerObject $mockServerObject -Name 'NonExistentDatabase' -DefaultFileStreamFileGroup 'FileStreamData' -Force } | + Should -Throw -ExpectedMessage '*Database * was not found*' + } + } +}