diff --git a/.github/instructions/dsc-community-style-guidelines-pester.instructions.md b/.github/instructions/dsc-community-style-guidelines-pester.instructions.md index cfbc43a63c..7695a1f7b3 100644 --- a/.github/instructions/dsc-community-style-guidelines-pester.instructions.md +++ b/.github/instructions/dsc-community-style-guidelines-pester.instructions.md @@ -38,7 +38,7 @@ applyTo: "**/*.[Tt]ests.ps1" ## Syntax Rules - PascalCase: `Describe`, `Context`, `It`, `Should`, `BeforeAll`, `BeforeEach`, `AfterAll`, `AfterEach` -- Prefer `-BeTrue`/`-BeFalse` over `-Be $true`/`-Be $false` +- Use `-BeTrue`/`-BeFalse` never `-Be $true`/`-Be $false` - Never use `Assert-MockCalled`, use `Should -Invoke` instead - No `Should -Not -Throw` - invoke commands directly - Never add an empty `-MockWith` block diff --git a/.github/instructions/dsc-community-style-guidelines-powershell.instructions.md b/.github/instructions/dsc-community-style-guidelines-powershell.instructions.md index b2f7f13222..aadddb45ba 100644 --- a/.github/instructions/dsc-community-style-guidelines-powershell.instructions.md +++ b/.github/instructions/dsc-community-style-guidelines-powershell.instructions.md @@ -78,10 +78,11 @@ applyTo: "**/*.ps?(m|d)1" - Include a `Force` parameter for functions that uses `$PSCmdlet.ShouldContinue` or `$PSCmdlet.ShouldProcess` - For state-changing functions, use `SupportsShouldProcess` - Place ShouldProcess check immediately before each state-change + - Set `ConfirmImpact` to 'Low', 'Medium', or 'High' depending on risk - `$PSCmdlet.ShouldProcess` must use required pattern - Inside `$PSCmdlet.ShouldProcess`-block, avoid using `Write-Verbose` - Never use backtick as line continuation in production code. -- Set `$ErrorActionPreference = 'Stop'` before commands using `-ErrorAction 'Stop'`; restore after +- Set `$ErrorActionPreference = 'Stop'` before commands using `-ErrorAction 'Stop'`; restore previous value after ## Output streams @@ -90,8 +91,8 @@ applyTo: "**/*.ps?(m|d)1" - Use `Write-Verbose` for: High-level execution flow only; User-actionable information - Use `Write-Information` for: User-facing status updates; Important operational messages; Non-error state changes - Use `Write-Warning` for: Non-fatal issues requiring attention; Deprecated functionality usage; Configuration problems that don't block execution -- Use `$PSCmdlet.ThrowTerminatingError()` for terminating errors (except for classes), use relevant error category, in try-catch include exception -- Use `Write-Error` for non-terminating errors, use relevant error category +- Use `$PSCmdlet.ThrowTerminatingError()` for terminating errors (except for classes), use relevant error category, in try-catch include exception with localized message +- Use `Write-Error` for non-terminating errors, use relevant error category; always use `return` after `Write-Error` to avoid further processing ## ShouldProcess Required Pattern @@ -184,6 +185,7 @@ function Get-Something - Assign function results to variables rather than inline calls - Return a single, consistent object type per function - return `$null` for no objects/non-terminating errors +- Use `::new()` static method instead of `New-Object` for .NET types, e.g `[System.Management.Automation.ErrorRecord]::new()` ### Security & Safety @@ -207,6 +209,7 @@ function Get-Something - End files with only one blank line - Use CR+LF line endings - Maximum two consecutive newlines +- No line shall have trailing whitespace ## File Encoding diff --git a/.github/instructions/dsc-community-style-guidelines-unit-tests.instructions.md b/.github/instructions/dsc-community-style-guidelines-unit-tests.instructions.md index 5cebdb743a..2b893aa1aa 100644 --- a/.github/instructions/dsc-community-style-guidelines-unit-tests.instructions.md +++ b/.github/instructions/dsc-community-style-guidelines-unit-tests.instructions.md @@ -15,7 +15,6 @@ applyTo: "tests/[u]Unit/**/*.[Tt]ests.ps1" Use this exact setup block before `Describe`: ```powershell -[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] param () diff --git a/.gitignore b/.gitignore index db34251d8f..c15dd8c155 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .vs output/ coverage.xml +testResults.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 184f61aa19..037d417973 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Set-SqlDscDatabaseDefault` - Added new command to set default objects of a database in a SQL Server Database Engine instance (issue [#2178](https://github.com/dsccommunity/SqlServerDsc/issues/2178)). +- `Set-SqlDscConfigurationOption` + - Added new command to set SQL Server Database Engine configuration options + using SMO with validation, ShouldProcess support, and dynamic tab completion. +- `Test-SqlDscConfigurationOption` + - Added new command to test if SQL Server Database Engine configuration options + have the specified value using SMO with dynamic tab completion for both + option names and values. +- `Get-SqlDscConfigurationOption` + - Enhanced existing command to return user-friendly metadata objects by default + with properties Name, RunValue, ConfigValue, Minimum, Maximum, and IsDynamic. + - Added `-Raw` switch to return original SMO ConfigProperty objects for + backward compatibility. + - Added dynamic tab completion for the `-Name` parameter. - The command can set the default filegroup, default FILESTREAM filegroup, and default Full-Text catalog using SMO methods SetDefaultFileGroup, SetDefaultFileStreamFileGroup, and SetDefaultFullTextCatalog. @@ -81,6 +94,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `Get-SqlDscAgentAlert`, `New-SqlDscAgentAlert`, `Set-SqlDscAgentAlert`, `Remove-SqlDscAgentAlert`, and `Test-SqlDscAgentAlert` to manage SQL Agent alerts on a Database Engine instance. +- Added new public commands for SQL Agent Operator management: + - `Get-SqlDscAgentOperator` - Get SQL Agent Operators from a SQL Server + Database Engine instance + - `New-SqlDscAgentOperator` - Create a new SQL Agent Operator with specified properties + - `Set-SqlDscAgentOperator` - Update existing SQL Agent Operator properties + - `Remove-SqlDscAgentOperator` - Remove a SQL Agent Operator from the instance + - `Enable-SqlDscAgentOperator` - Enable a SQL Agent Operator + - `Disable-SqlDscAgentOperator` - Disable a SQL Agent Operator + - `Test-SqlDscIsAgentOperator` - Test if a SQL Agent Operator exists + - Supports pipeline input for both ServerObject and OperatorObject where applicable + - Includes comprehensive unit tests and follows ShouldProcess patterns - Added new public commands for database management: - `Get-SqlDscDatabase` - Get databases from a SQL Server Database Engine instance - `New-SqlDscDatabase` - Create a new database with specified properties @@ -91,9 +115,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 patterns - Database objects can also be used as pipeline input for Set and Remove operations - Commands include comprehensive validation, localization, and ShouldProcess support +- Added private function `Get-CommandParameter` to filter command parameters + by excluding specified parameter names and common parameters, providing a + reusable way to determine settable properties on objects. ### Changed +- Improved code quality by ensuring all function invocations in the private + and public functions use named parameters instead of positional parameters. - SqlServerDsc - Updated GitVersion.yml feature branch regex pattern to use anchor `^f(eature(s)?)?[\/-]` for more precise branch name matching. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 784c393968..ee79bd15d0 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -293,6 +293,9 @@ stages: 'tests/Integration/Commands/Assert-SqlDscLogin.Integration.Tests.ps1' 'tests/Integration/Commands/New-SqlDscLogin.Integration.Tests.ps1' 'tests/Integration/Commands/Get-SqlDscLogin.Integration.Tests.ps1' + 'tests/Integration/Commands/Get-SqlDscConfigurationOption.Integration.Tests.ps1' + 'tests/Integration/Commands/Set-SqlDscConfigurationOption.Integration.Tests.ps1' + 'tests/Integration/Commands/Test-SqlDscConfigurationOption.Integration.Tests.ps1' 'tests/Integration/Commands/Disable-SqlDscLogin.Integration.Tests.ps1' 'tests/Integration/Commands/Enable-SqlDscLogin.Integration.Tests.ps1' 'tests/Integration/Commands/Test-SqlDscIsLoginEnabled.Integration.Tests.ps1' @@ -311,8 +314,16 @@ stages: 'tests/Integration/Commands/New-SqlDscAgentAlert.Integration.Tests.ps1' 'tests/Integration/Commands/Set-SqlDscAgentAlert.Integration.Tests.ps1' 'tests/Integration/Commands/Test-SqlDscAgentAlert.Integration.Tests.ps1' + 'tests/Integration/Commands/Get-SqlDscAgentOperator.Integration.Tests.ps1' + 'tests/Integration/Commands/New-SqlDscAgentOperator.Integration.Tests.ps1' + 'tests/Integration/Commands/Set-SqlDscAgentOperator.Integration.Tests.ps1' + 'tests/Integration/Commands/Test-SqlDscIsAgentOperator.Integration.Tests.ps1' + 'tests/Integration/Commands/Assert-SqlDscAgentOperator.Integration.Tests.ps1' + 'tests/Integration/Commands/Enable-SqlDscAgentOperator.Integration.Tests.ps1' + 'tests/Integration/Commands/Disable-SqlDscAgentOperator.Integration.Tests.ps1' # Group 8 'tests/Integration/Commands/Remove-SqlDscAgentAlert.Integration.Tests.ps1' + 'tests/Integration/Commands/Remove-SqlDscAgentOperator.Integration.Tests.ps1' 'tests/Integration/Commands/Remove-SqlDscDatabase.Integration.Tests.ps1' 'tests/Integration/Commands/Remove-SqlDscRole.Integration.Tests.ps1' 'tests/Integration/Commands/Remove-SqlDscLogin.Integration.Tests.ps1' diff --git a/source/Classes/020.SqlRSSetup.ps1 b/source/Classes/020.SqlRSSetup.ps1 index 27a4289734..24190e59ea 100644 --- a/source/Classes/020.SqlRSSetup.ps1 +++ b/source/Classes/020.SqlRSSetup.ps1 @@ -608,7 +608,7 @@ class SqlRSSetup : ResourceBase 'InstallFolder' 'LogPath' ) | - Where-Object { + Where-Object -FilterScript { $properties.ContainsKey($_) } diff --git a/source/Examples/Resources/SqlDatabaseMail/1-EnableDatabaseMail.ps1 b/source/Examples/Resources/SqlDatabaseMail/1-EnableDatabaseMail.ps1 index a0ed0653a9..78cf0f7a75 100644 --- a/source/Examples/Resources/SqlDatabaseMail/1-EnableDatabaseMail.ps1 +++ b/source/Examples/Resources/SqlDatabaseMail/1-EnableDatabaseMail.ps1 @@ -47,4 +47,3 @@ Configuration Example } } } - diff --git a/source/Private/ConvertTo-FormattedParameterDescription.ps1 b/source/Private/ConvertTo-FormattedParameterDescription.ps1 new file mode 100644 index 0000000000..d9c7d50dd2 --- /dev/null +++ b/source/Private/ConvertTo-FormattedParameterDescription.ps1 @@ -0,0 +1,82 @@ +<# + .SYNOPSIS + Converts a hashtable of bound parameters into a formatted string for ShouldProcess descriptions. + + .DESCRIPTION + This function takes a hashtable of bound parameters and formats them into a readable string + for use in ShouldProcess verbose descriptions. It excludes non-settable parameters and + formats each parameter as 'ParameterName: Value'. + + .PARAMETER BoundParameters + Hashtable of bound parameters (typically $PSBoundParameters). + + .PARAMETER Exclude + Array of parameter names to exclude from the formatted output. + + .OUTPUTS + System.String + + Returns a formatted string with parameters and their values. + + .EXAMPLE + $formattedText = ConvertTo-FormattedParameterDescription -BoundParameters $PSBoundParameters -Exclude @('ServerObject', 'Name', 'Force') + + Returns a formatted string like: + " + EmailAddress: 'admin@company.com' + CategoryName: 'Notifications' + " +#> +function ConvertTo-FormattedParameterDescription +{ + [CmdletBinding()] + [OutputType([System.String])] + param + ( + [Parameter(Mandatory = $true)] + [System.Collections.Hashtable] + $BoundParameters, + + [Parameter()] + [System.String[]] + $Exclude = @() + ) + + $parameterDescriptions = @() + + foreach ($parameter in ($BoundParameters.Keys | Sort-Object)) + { + if ($parameter -notin $Exclude) + { + $raw = $BoundParameters[$parameter] + + $value = if ($raw -is [System.Security.SecureString]) + { + '***' + } + elseif ($raw -is [System.Management.Automation.PSCredential]) + { + $raw.UserName + } + elseif ($raw -is [System.Array]) + { + ($raw -join ', ') + } + else + { + $raw + } + + $parameterDescriptions += "$parameter`: '$value'" + } + } + + if ($parameterDescriptions.Count -gt 0) + { + return "`r`n " + ($parameterDescriptions -join "`r`n ") + } + else + { + return " $($script:localizedData.ConvertTo_FormattedParameterDescription_NoParametersToUpdate)" + } +} diff --git a/source/Private/Get-AgentOperatorObject.ps1 b/source/Private/Get-AgentOperatorObject.ps1 new file mode 100644 index 0000000000..2dad3c289a --- /dev/null +++ b/source/Private/Get-AgentOperatorObject.ps1 @@ -0,0 +1,77 @@ +<# + .SYNOPSIS + Retrieves a SQL Server Agent Operator object. + + .DESCRIPTION + This private function retrieves a SQL Server Agent Operator object using the provided + parameters. If the operator is not found, it throws a non-terminating error. Callers + can use -ErrorAction to control error behavior. + + .PARAMETER ServerObject + Specifies the server object on which to retrieve the operator. + + .PARAMETER Name + Specifies the name of the operator to retrieve. + + .PARAMETER Refresh + Specifies whether to refresh the operators collection before retrieving the operator. + When specified, the function calls Refresh() on the JobServer.Operators collection. + + .INPUTS + None. + + .OUTPUTS + Microsoft.SqlServer.Management.Smo.Agent.Operator + + Returns the operator object if found. + + .EXAMPLE + $operatorObject = Get-AgentOperatorObject -ServerObject $serverObject -Name 'TestOperator' + + Returns the SQL Agent Operator object for 'TestOperator', throws error if not found. + + .EXAMPLE + $operatorObject = Get-AgentOperatorObject -ServerObject $serverObject -Name 'TestOperator' -Refresh + + Returns the SQL Agent Operator object for 'TestOperator' after refreshing the operators collection. +#> +function Get-AgentOperatorObject +{ + [CmdletBinding()] + [OutputType([Microsoft.SqlServer.Management.Smo.Agent.Operator])] + param + ( + [Parameter(Mandatory = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Refresh + ) + + Write-Verbose -Message ($script:localizedData.Get_AgentOperatorObject_GettingOperator -f $Name) + + if ($Refresh) + { + Write-Verbose -Message $script:localizedData.Get_AgentOperatorObject_RefreshingOperators + $ServerObject.JobServer.Operators.Refresh() + } + + $operatorObject = $ServerObject.JobServer.Operators[$Name] + + if ($null -eq $operatorObject) + { + $errorMessage = $script:localizedData.AgentOperator_NotFound -f $Name + + Write-Error -Message $errorMessage -Category 'ObjectNotFound' -ErrorId 'GAOO0001' -TargetObject $Name + + return $null + } + + return $operatorObject +} diff --git a/source/Private/Get-CommandParameter.ps1 b/source/Private/Get-CommandParameter.ps1 new file mode 100644 index 0000000000..dd210dca45 --- /dev/null +++ b/source/Private/Get-CommandParameter.ps1 @@ -0,0 +1,55 @@ +<# + .SYNOPSIS + Gets command parameters excluding specified ones. + + .DESCRIPTION + This private function filters command parameters by excluding specified parameter names, + common parameters, and optional common parameters. It returns an array of parameter names + that can be used to set properties on objects. + + .PARAMETER Command + Specifies the command information object containing parameter definitions. + + .PARAMETER Exclude + Specifies an optional array of parameter names to exclude from the result. + Common parameters and optional common parameters are always excluded. + + .INPUTS + None. + + .OUTPUTS + System.String[] + + Returns an array of parameter names that are not excluded. + + .EXAMPLE + $settableProperties = Get-CommandParameter -Command $MyInvocation.MyCommand -Exclude @('ServerObject', 'Name', 'PassThru', 'Force') + + Returns all parameters except the excluded ones and common parameters. + + .EXAMPLE + $settableProperties = Get-CommandParameter -Command $MyInvocation.MyCommand + + Returns all parameters except common parameters and optional common parameters. +#> +function Get-CommandParameter +{ + [CmdletBinding()] + [OutputType([System.String[]])] + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.FunctionInfo] + $Command, + + [Parameter()] + [System.String[]] + $Exclude = @() + ) + + $parametersWithoutCommon = Remove-CommonParameter -Hashtable $Command.Parameters + + $settableProperties = $parametersWithoutCommon.Keys | Where-Object -FilterScript { $_ -notin $Exclude } + + return $settableProperties +} diff --git a/source/Public/Assert-SqlDscAgentOperator.ps1 b/source/Public/Assert-SqlDscAgentOperator.ps1 new file mode 100644 index 0000000000..735f32b8e2 --- /dev/null +++ b/source/Public/Assert-SqlDscAgentOperator.ps1 @@ -0,0 +1,63 @@ +<# + .SYNOPSIS + Asserts that a SQL Server Agent Operator exists and throws a terminating error if not found. + + .DESCRIPTION + This command asserts that a SQL Server Agent Operator exists using the provided + parameters. If the operator is not found, it throws a terminating error with a + generic localized error message. + + .PARAMETER ServerObject + Specifies the server object on which to check for the operator. + + .PARAMETER Name + Specifies the name of the operator to check for. + + .INPUTS + [Microsoft.SqlServer.Management.Smo.Server] + + .OUTPUTS + None. + + This command does not return anything if the operator exists. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | Assert-SqlDscAgentOperator -Name 'TestOperator' + + Asserts that the SQL Agent Operator 'TestOperator' exists, throws error if not found. + + .EXAMPLE + Assert-SqlDscAgentOperator -ServerObject $serverObject -Name 'TestOperator' + + Asserts that the SQL Agent Operator 'TestOperator' exists, throws error if not found. +#> +function Assert-SqlDscAgentOperator +{ + [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/8.')] + [CmdletBinding()] + [OutputType()] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(Mandatory = $true)] + [System.String] + $Name + ) + + process + { + $originalErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = 'Stop' + + # This will throw a terminating error if the operator is not found + $null = Get-AgentOperatorObject -ServerObject $ServerObject -Name $Name -ErrorAction 'Stop' + + $ErrorActionPreference = $originalErrorActionPreference + + # This command does not return anything if the operator exists + } +} diff --git a/source/Public/Disable-SqlDscAgentOperator.ps1 b/source/Public/Disable-SqlDscAgentOperator.ps1 new file mode 100644 index 0000000000..b1f5e6d301 --- /dev/null +++ b/source/Public/Disable-SqlDscAgentOperator.ps1 @@ -0,0 +1,134 @@ +<# + .SYNOPSIS + Disables a SQL Agent Operator. + + .DESCRIPTION + This command disables a SQL Agent Operator on a SQL Server Database Engine instance. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER OperatorObject + Specifies a SQL Agent Operator object to disable. + + .PARAMETER Name + Specifies the name of the SQL Agent Operator to be disabled. + + .PARAMETER Force + Specifies that the operator should be disabled without any confirmation. + + .PARAMETER Refresh + Specifies that the **ServerObject**'s operators should be refreshed before + trying to disable the operator object. This is helpful when operators could have + been modified outside of the **ServerObject**, for example through T-SQL. + But on instances with a large amount of operators it might be better to make + sure the **ServerObject** is recent enough, or pass in **OperatorObject**. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $operatorObject = $serverObject | Get-SqlDscAgentOperator -Name 'MyOperator' + $operatorObject | Disable-SqlDscAgentOperator + + Disables the operator named **MyOperator**. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | Disable-SqlDscAgentOperator -Name 'MyOperator' + + Disables the operator named **MyOperator**. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | Disable-SqlDscAgentOperator -Name 'MyOperator' -Force + + Disables the operator without confirmation using **-Force**. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | Disable-SqlDscAgentOperator -Name 'MyOperator' -Refresh + + Refreshes the server operators collection before disabling **MyOperator**. + + .INPUTS + [Microsoft.SqlServer.Management.Smo.Server] + + Server object accepted from the pipeline (ServerObject parameter set). + + .INPUTS + [Microsoft.SqlServer.Management.Smo.Agent.Operator] + + Operator object accepted from the pipeline (OperatorObject parameter set). + + .OUTPUTS + None. +#> +function Disable-SqlDscAgentOperator +{ + [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/8.')] + [OutputType()] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] + param + ( + [Parameter(ParameterSetName = 'ServerObject', Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(ParameterSetName = 'OperatorObject', Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Agent.Operator] + $OperatorObject, + + [Parameter(ParameterSetName = 'ServerObject', Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force, + + [Parameter(ParameterSetName = 'ServerObject')] + [System.Management.Automation.SwitchParameter] + $Refresh + ) + + process + { + if ($Force.IsPresent -and -not $Confirm) + { + $ConfirmPreference = 'None' + } + + if ($PSCmdlet.ParameterSetName -eq 'ServerObject') + { + $originalErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = 'Stop' + $OperatorObject = Get-AgentOperatorObject -ServerObject $ServerObject -Name $Name -Refresh:$Refresh -ErrorAction 'Stop' + $ErrorActionPreference = $originalErrorActionPreference + } + + $verboseDescriptionMessage = $script:localizedData.Disable_SqlDscAgentOperator_ShouldProcessVerboseDescription -f $OperatorObject.Name, $OperatorObject.Parent.Parent.InstanceName + $verboseWarningMessage = $script:localizedData.Disable_SqlDscAgentOperator_ShouldProcessVerboseWarning -f $OperatorObject.Name + $captionMessage = $script:localizedData.Disable_SqlDscAgentOperator_ShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage)) + { + try + { + $OperatorObject.Enabled = $false + $OperatorObject.Alter() + } + catch + { + $errorMessage = $script:localizedData.Disable_SqlDscAgentOperator_DisableFailed -f $OperatorObject.Name + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.InvalidOperationException]::new($errorMessage, $_.Exception), + 'DSAO0005', # Align with Disable_SqlDscAgentOperator_DisableFailed + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $OperatorObject + ) + ) + } + } + } +} diff --git a/source/Public/Enable-SqlDscAgentOperator.ps1 b/source/Public/Enable-SqlDscAgentOperator.ps1 new file mode 100644 index 0000000000..2a0f266576 --- /dev/null +++ b/source/Public/Enable-SqlDscAgentOperator.ps1 @@ -0,0 +1,122 @@ +<# + .SYNOPSIS + Enables a SQL Agent Operator. + + .DESCRIPTION + This command enables a SQL Agent Operator on a SQL Server Database Engine instance. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER OperatorObject + Specifies a SQL Agent Operator object to enable. + + .PARAMETER Name + Specifies the name of the SQL Agent Operator to be enabled. + + .PARAMETER Force + Specifies that the operator should be enabled without any confirmation. + + .PARAMETER Refresh + Specifies that the **ServerObject**'s operators should be refreshed before + trying to enable the operator object. This is helpful when operators could have + been modified outside of the **ServerObject**, for example through T-SQL. + But on instances with a large amount of operators it might be better to make + sure the **ServerObject** is recent enough, or pass in **OperatorObject**. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $operatorObject = $serverObject | Get-SqlDscAgentOperator -Name 'MyOperator' + $operatorObject | Enable-SqlDscAgentOperator + + Enables the operator named **MyOperator**. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | Enable-SqlDscAgentOperator -Name 'MyOperator' + + Enables the operator named **MyOperator**. + + .INPUTS + Microsoft.SqlServer.Management.Smo.Server + + When using the ServerObject parameter set, a Server object can be piped in. + + .INPUTS + Microsoft.SqlServer.Management.Smo.Agent.Operator + + When using the OperatorObject parameter set, an Operator object can be piped in. + + .OUTPUTS + None. +#> +function Enable-SqlDscAgentOperator +{ + [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/8.')] + [OutputType()] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] + param + ( + [Parameter(ParameterSetName = 'ServerObject', Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(ParameterSetName = 'OperatorObject', Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Agent.Operator] + $OperatorObject, + + [Parameter(ParameterSetName = 'ServerObject', Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force, + + [Parameter(ParameterSetName = 'ServerObject')] + [System.Management.Automation.SwitchParameter] + $Refresh + ) + + process + { + if ($Force.IsPresent -and -not $Confirm) + { + $ConfirmPreference = 'None' + } + + if ($PSCmdlet.ParameterSetName -eq 'ServerObject') + { + $originalErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = 'Stop' + $OperatorObject = Get-AgentOperatorObject -ServerObject $ServerObject -Name $Name -Refresh:$Refresh -ErrorAction 'Stop' + $ErrorActionPreference = $originalErrorActionPreference + } + + $verboseDescriptionMessage = $script:localizedData.Enable_SqlDscAgentOperator_ShouldProcessVerboseDescription -f $OperatorObject.Name, $OperatorObject.Parent.Parent.InstanceName + $verboseWarningMessage = $script:localizedData.Enable_SqlDscAgentOperator_ShouldProcessVerboseWarning -f $OperatorObject.Name + $captionMessage = $script:localizedData.Enable_SqlDscAgentOperator_ShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage)) + { + try + { + $OperatorObject.Enabled = $true + $OperatorObject.Alter() + } + catch + { + $errorMessage = $script:localizedData.Enable_SqlDscAgentOperator_EnableFailed -f $OperatorObject.Name + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.InvalidOperationException]::new($errorMessage, $_.Exception), + 'ESAO0001', # cspell: disable-line + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $OperatorObject + ) + ) + } + } + } +} diff --git a/source/Public/Get-SqlDscAgentOperator.ps1 b/source/Public/Get-SqlDscAgentOperator.ps1 new file mode 100644 index 0000000000..2457328c9a --- /dev/null +++ b/source/Public/Get-SqlDscAgentOperator.ps1 @@ -0,0 +1,96 @@ +<# + .SYNOPSIS + Returns SQL Agent Operator information. + + .DESCRIPTION + Returns SQL Agent Operator information from a SQL Server Database Engine instance. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER Name + Specifies the name of the SQL Agent Operator to retrieve. If not specified, + all operators are returned. + + .INPUTS + Microsoft.SqlServer.Management.Smo.Server + + SQL Server Database Engine instance object. + + .OUTPUTS + Microsoft.SqlServer.Management.Smo.Agent.Operator + + When using the ByName parameter set, returns a single SQL Agent Operator object. + + .OUTPUTS + Microsoft.SqlServer.Management.Smo.Agent.Operator[] + + When using the All parameter set, returns an array of SQL Agent Operator objects. + + .OUTPUTS + None + Returns nothing when no operators are found for the specified criteria. + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine + Get-SqlDscAgentOperator -ServerObject $serverObject + + Returns all SQL Agent Operators from the server. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine + Get-SqlDscAgentOperator -ServerObject $serverObject -Name 'MyOperator' + + Returns the SQL Agent Operator named 'MyOperator'. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine + $serverObject | Get-SqlDscAgentOperator -Name 'MyOperator' + + Returns the SQL Agent Operator named 'MyOperator' using pipeline input. +#> +function Get-SqlDscAgentOperator +{ + [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/8.')] + [CmdletBinding(DefaultParameterSetName = 'All')] + [OutputType([Microsoft.SqlServer.Management.Smo.Agent.Operator], ParameterSetName = 'ByName')] + [OutputType([Microsoft.SqlServer.Management.Smo.Agent.Operator[]], ParameterSetName = 'All')] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'ByName')] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'All')] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(Mandatory = $true, ParameterSetName = 'ByName')] + [ValidateNotNullOrEmpty()] + [System.String] + $Name + ) + + # cSpell: ignore GSAO + process + { + switch ($PSCmdlet.ParameterSetName) + { + 'ByName' + { + Write-Verbose -Message ($script:localizedData.Get_SqlDscAgentOperator_GettingOperator -f $Name) + + $operatorObject = Get-AgentOperatorObject -ServerObject $ServerObject -Name $Name -ErrorAction 'SilentlyContinue' + + return $operatorObject + } + + 'All' + { + Write-Verbose -Message ($script:localizedData.Get_SqlDscAgentOperator_GettingOperators -f $ServerObject.InstanceName) + + $operatorCollection = $ServerObject.JobServer.Operators + + Write-Verbose -Message ($script:localizedData.Get_SqlDscAgentOperator_ReturningAllOperators -f $operatorCollection.Count) + + return [Microsoft.SqlServer.Management.Smo.Agent.Operator[]] $operatorCollection + } + } + } +} diff --git a/source/Public/Get-SqlDscConfigurationOption.ps1 b/source/Public/Get-SqlDscConfigurationOption.ps1 index 42d66f8eb0..da77f13347 100644 --- a/source/Public/Get-SqlDscConfigurationOption.ps1 +++ b/source/Public/Get-SqlDscConfigurationOption.ps1 @@ -1,15 +1,22 @@ <# .SYNOPSIS - Get server configuration option. + Get server configuration option metadata or raw SMO objects. .DESCRIPTION - This command gets the available configuration options from a SQL Server Database Engine instance. + This command gets configuration options from a SQL Server Database Engine instance. + By default, it returns user-friendly metadata objects with current values, ranges, + and dynamic properties. Use the -Raw switch to get the original SMO ConfigProperty objects. .PARAMETER ServerObject Specifies current server connection object. .PARAMETER Name - Specifies the name of the configuration option to get. + Specifies the name of the configuration option to get. Supports wildcards. + If not specified, all configuration options are returned. + + .PARAMETER Raw + Specifies that the original SMO ConfigProperty objects should be returned + instead of the enhanced metadata objects. .PARAMETER Refresh Specifies that the **ServerObject**'s configuration property should be @@ -19,25 +26,46 @@ .EXAMPLE $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' - $sqlServerObject | Get-SqlDscConfigurationOption + $serverObject | Get-SqlDscConfigurationOption + + Get metadata for all available configuration options. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | Get-SqlDscConfigurationOption -Name '*threshold*' + + Get metadata for configuration options that contain the word "threshold". + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | Get-SqlDscConfigurationOption -Name "Agent XPs" - Get all the available configuration options. + Get metadata for the specific "Agent XPs" configuration option. .EXAMPLE $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' - $sqlServerObject | Get-SqlDscConfigurationOption -Name '*threshold*' + $serverObject | Get-SqlDscConfigurationOption -Raw - Get the configuration options that contains the word **threshold**. + Get all configuration options as raw SMO ConfigProperty objects. + + .INPUTS + Microsoft.SqlServer.Management.Smo.Server + SQL Server Management Objects (SMO) Server object representing a SQL Server instance. .OUTPUTS - `[Microsoft.SqlServer.Management.Smo.ConfigProperty[]]` - Array of SMO ConfigProperty objects representing server configuration options. + PSCustomObject[] + Returns user-friendly metadata objects with configuration option details (default behavior). + + Microsoft.SqlServer.Management.Smo.ConfigProperty[] + Returns raw SMO ConfigProperty objects when using the -Raw parameter. #> function Get-SqlDscConfigurationOption { + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Justification = 'Because we pass parameters in the argument completer that are not yet used.')] [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/8.')] - [OutputType([Microsoft.SqlServer.Management.Smo.ConfigProperty[]])] - [CmdletBinding()] + [OutputType([PSCustomObject[]], ParameterSetName = 'Metadata')] + [OutputType([Microsoft.SqlServer.Management.Smo.ConfigProperty[]], ParameterSetName = 'Raw')] + [CmdletBinding(DefaultParameterSetName = 'Metadata')] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] @@ -45,9 +73,71 @@ function Get-SqlDscConfigurationOption $ServerObject, [Parameter()] + [ArgumentCompleter({ + param + ( + [Parameter()] + $commandName, + + [Parameter()] + $parameterName, + + [Parameter()] + $wordToComplete, + + [Parameter()] + $commandAst, + + [Parameter()] + $fakeBoundParameters + ) + + # Get ServerObject from bound parameters only + $serverObject = $null + + if ($FakeBoundParameters.ContainsKey('ServerObject')) + { + $serverObject = $FakeBoundParameters['ServerObject'] + } + + if ($serverObject -and $serverObject -is [Microsoft.SqlServer.Management.Smo.Server]) + { + try + { + $options = $serverObject.Configuration.Properties | Where-Object { + $_.DisplayName -like "*$WordToComplete*" + } | Sort-Object DisplayName + + foreach ($option in $options) + { + $tooltip = "Current: $($option.ConfigValue), Run: $($option.RunValue), Range: $($option.Minimum)-$($option.Maximum)" + [System.Management.Automation.CompletionResult]::new( + "'$($option.DisplayName)'", + $option.DisplayName, + 'ParameterValue', + $tooltip + ) + } + } + catch + { + # Return empty if there's an error accessing the server + @() + } + } + else + { + # Return empty array if no server object available + @() + } + })] [System.String] $Name, + [Parameter(ParameterSetName = 'Raw')] + [System.Management.Automation.SwitchParameter] + $Raw, + [Parameter()] [System.Management.Automation.SwitchParameter] $Refresh @@ -63,12 +153,12 @@ function Get-SqlDscConfigurationOption if ($PSBoundParameters.ContainsKey('Name')) { - $configurationOption = $ServerObject.Configuration.Properties | + $configurationOptions = $ServerObject.Configuration.Properties | Where-Object -FilterScript { $_.DisplayName -like $Name } - if (-not $configurationOption) + if (-not $configurationOptions) { $missingConfigurationOptionMessage = $script:localizedData.ConfigurationOption_Get_Missing -f $Name @@ -80,16 +170,42 @@ function Get-SqlDscConfigurationOption } Write-Error @writeErrorParameters + return } } else { - $configurationOption = $ServerObject.Configuration.Properties.ForEach({ $_ }) + $configurationOptions = $ServerObject.Configuration.Properties.ForEach({ $_ }) } - return [Microsoft.SqlServer.Management.Smo.ConfigProperty[]] ( - $configurationOption | - Sort-Object -Property 'DisplayName' - ) + # Sort the options by DisplayName + $sortedOptions = $configurationOptions | Sort-Object -Property 'DisplayName' + + if ($Raw.IsPresent) + { + # Return raw SMO ConfigProperty objects + return [Microsoft.SqlServer.Management.Smo.ConfigProperty[]] $sortedOptions + } + else + { + # Return enhanced metadata objects + $metadataObjects = foreach ($option in $sortedOptions) + { + $metadata = [PSCustomObject]@{ + Name = $option.DisplayName + RunValue = $option.RunValue + ConfigValue = $option.ConfigValue + Minimum = $option.Minimum + Maximum = $option.Maximum + IsDynamic = $option.IsDynamic + } + + # Add custom type name for formatting + $metadata.PSTypeNames.Insert(0, 'SqlDsc.ConfigurationOption') + $metadata + } + + return $metadataObjects + } } } diff --git a/source/Public/New-SqlDscAgentAlert.ps1 b/source/Public/New-SqlDscAgentAlert.ps1 index 4269232f23..83f0c8f993 100644 --- a/source/Public/New-SqlDscAgentAlert.ps1 +++ b/source/Public/New-SqlDscAgentAlert.ps1 @@ -22,6 +22,9 @@ .PARAMETER PassThru If specified, the created alert object will be returned. + .PARAMETER Force + Forces the action without prompting for confirmation. + .INPUTS Microsoft.SqlServer.Management.Smo.Server @@ -55,7 +58,7 @@ function New-SqlDscAgentAlert { [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/8.')] - [CmdletBinding(SupportsShouldProcess = $true)] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] [OutputType([Microsoft.SqlServer.Management.Smo.Agent.Alert])] param ( @@ -80,9 +83,21 @@ function New-SqlDscAgentAlert [Parameter()] [System.Management.Automation.SwitchParameter] - $PassThru + $PassThru, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force ) + begin + { + if ($Force.IsPresent -and -not $Confirm) + { + $ConfirmPreference = 'None' + } + } + # cSpell: ignore NSAA process { @@ -95,7 +110,15 @@ function New-SqlDscAgentAlert if ($existingAlert) { $errorMessage = $script:localizedData.New_SqlDscAgentAlert_AlertAlreadyExists -f $Name - New-InvalidOperationException -Message $errorMessage + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.InvalidOperationException]::new($errorMessage), + 'NSAA0001', # cspell: disable-line + [System.Management.Automation.ErrorCategory]::ResourceExists, + $Name + ) + ) } $verboseDescriptionMessage = $script:localizedData.New_SqlDscAgentAlert_CreateShouldProcessVerboseDescription -f $Name, $ServerObject.InstanceName @@ -135,7 +158,15 @@ function New-SqlDscAgentAlert catch { $errorMessage = $script:localizedData.New_SqlDscAgentAlert_CreateFailed -f $Name - New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.InvalidOperationException]::new($errorMessage, $_.Exception), + 'NSAA0002', # cspell: disable-line + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $Name + ) + ) } } } diff --git a/source/Public/New-SqlDscAgentOperator.ps1 b/source/Public/New-SqlDscAgentOperator.ps1 new file mode 100644 index 0000000000..ec48e8f8a9 --- /dev/null +++ b/source/Public/New-SqlDscAgentOperator.ps1 @@ -0,0 +1,285 @@ +<# + .SYNOPSIS + Creates a new SQL Agent Operator. + + .DESCRIPTION + This command creates a new SQL Agent Operator on a SQL Server Database Engine instance. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER Name + Specifies the name of the SQL Agent Operator to create. + + .PARAMETER EmailAddress + Specifies the email address for the SQL Agent Operator. + + .PARAMETER CategoryName + Specifies the category name for the SQL Agent Operator. + + .PARAMETER NetSendAddress + Specifies the net send address for the SQL Agent Operator. + + .PARAMETER PagerAddress + Specifies the pager address for the SQL Agent Operator. + + .PARAMETER PagerDays + Specifies the days when pager notifications are active for the SQL Agent Operator. + + .PARAMETER SaturdayPagerEndTime + Specifies the Saturday pager end time for the SQL Agent Operator. + + .PARAMETER SaturdayPagerStartTime + Specifies the Saturday pager start time for the SQL Agent Operator. + + .PARAMETER SundayPagerEndTime + Specifies the Sunday pager end time for the SQL Agent Operator. + + .PARAMETER SundayPagerStartTime + Specifies the Sunday pager start time for the SQL Agent Operator. + + .PARAMETER WeekdayPagerEndTime + Specifies the weekday pager end time for the SQL Agent Operator. + + .PARAMETER WeekdayPagerStartTime + Specifies the weekday pager start time for the SQL Agent Operator. + + .PARAMETER PassThru + If specified, the created operator object will be returned. + + .PARAMETER Force + Specifies that the operator should be created without any confirmation. + + .PARAMETER Refresh + Specifies that the **ServerObject**'s operators should be refreshed before + testing if the operator already exists. This is helpful when operators could have + been modified outside of the **ServerObject**, for example through T-SQL. + But on instances with a large amount of operators it might be better to make + sure the **ServerObject** is recent enough. + + .INPUTS + Microsoft.SqlServer.Management.Smo.Server + + SQL Server Database Engine instance object. + + .OUTPUTS + `[Microsoft.SqlServer.Management.Smo.Agent.Operator]` if passing parameter **PassThru**, + otherwise none. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + New-SqlDscAgentOperator -ServerObject $serverObject -Name 'MyOperator' -EmailAddress 'admin@example.com' + + Creates a new SQL Agent Operator named 'MyOperator' with an email address. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | New-SqlDscAgentOperator -Name 'MyOperator' -EmailAddress 'admin@contoso.com' + + Creates a new SQL Agent Operator named 'MyOperator' with an email address using pipeline input. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $operatorObject = $serverObject | New-SqlDscAgentOperator -Name 'MyOperator' -EmailAddress 'admin@example.com' -PassThru + + Creates a new SQL Agent Operator with an email address and returns the created object. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | New-SqlDscAgentOperator -Name 'MyOperator' -EmailAddress 'admin@contoso.com' -Refresh + + Creates a new SQL Agent Operator, refreshing the operators collection before checking if it already exists. +#> +function New-SqlDscAgentOperator +{ + [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/8.')] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] + [OutputType([Microsoft.SqlServer.Management.Smo.Agent.Operator])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Name, + + [Parameter()] + [System.String] + $EmailAddress, + + [Parameter()] + [System.String] + $CategoryName, + + [Parameter()] + [System.String] + $NetSendAddress, + + [Parameter()] + [System.String] + $PagerAddress, + + [Parameter()] + [Microsoft.SqlServer.Management.Smo.Agent.WeekDays] + $PagerDays, + + [Parameter()] + [System.TimeSpan] + $SaturdayPagerEndTime, + + [Parameter()] + [System.TimeSpan] + $SaturdayPagerStartTime, + + [Parameter()] + [System.TimeSpan] + $SundayPagerEndTime, + + [Parameter()] + [System.TimeSpan] + $SundayPagerStartTime, + + [Parameter()] + [System.TimeSpan] + $WeekdayPagerEndTime, + + [Parameter()] + [System.TimeSpan] + $WeekdayPagerStartTime, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $PassThru, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Refresh + ) + + begin + { + # Dynamically get settable properties by filtering out common parameters and control parameters + $settableProperties = Get-CommandParameter -Command $MyInvocation.MyCommand -Exclude @('ServerObject', 'Name', 'PassThru', 'Force', 'Refresh') + + Assert-BoundParameter -BoundParameterList $PSBoundParameters -AtLeastOneList $settableProperties + } + + # cSpell: ignore NSAO + process + { + if ($Force.IsPresent -and -not $Confirm) + { + $ConfirmPreference = 'None' + } + + # Check if operator already exists + if (Test-SqlDscIsAgentOperator -ServerObject $ServerObject -Name $Name -Refresh:$Refresh) + { + $errorMessage = $script:localizedData.New_SqlDscAgentOperator_OperatorAlreadyExists -f $Name + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.InvalidOperationException]::new($errorMessage), + 'NSAO0001', # cspell: disable-line + [System.Management.Automation.ErrorCategory]::ResourceExists, + $Name + ) + ) + } + + $verboseDescriptionMessage = $script:localizedData.New_SqlDscAgentOperator_CreateShouldProcessVerboseDescription -f $Name, $ServerObject.InstanceName + $verboseWarningMessage = $script:localizedData.New_SqlDscAgentOperator_CreateShouldProcessVerboseWarning -f $Name + $captionMessage = $script:localizedData.New_SqlDscAgentOperator_CreateShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage)) + { + try + { + # Create the new operator SMO object + $newOperatorObject = [Microsoft.SqlServer.Management.Smo.Agent.Operator]::new($ServerObject.JobServer, $Name) + + if ($PSBoundParameters.ContainsKey('EmailAddress')) + { + $newOperatorObject.EmailAddress = $EmailAddress + } + + if ($PSBoundParameters.ContainsKey('CategoryName')) + { + $newOperatorObject.CategoryName = $CategoryName + } + + if ($PSBoundParameters.ContainsKey('NetSendAddress')) + { + $newOperatorObject.NetSendAddress = $NetSendAddress + } + + if ($PSBoundParameters.ContainsKey('PagerAddress')) + { + $newOperatorObject.PagerAddress = $PagerAddress + } + + if ($PSBoundParameters.ContainsKey('PagerDays')) + { + $newOperatorObject.PagerDays = $PagerDays + } + + if ($PSBoundParameters.ContainsKey('SaturdayPagerEndTime')) + { + $newOperatorObject.SaturdayPagerEndTime = $SaturdayPagerEndTime + } + + if ($PSBoundParameters.ContainsKey('SaturdayPagerStartTime')) + { + $newOperatorObject.SaturdayPagerStartTime = $SaturdayPagerStartTime + } + + if ($PSBoundParameters.ContainsKey('SundayPagerEndTime')) + { + $newOperatorObject.SundayPagerEndTime = $SundayPagerEndTime + } + + if ($PSBoundParameters.ContainsKey('SundayPagerStartTime')) + { + $newOperatorObject.SundayPagerStartTime = $SundayPagerStartTime + } + + if ($PSBoundParameters.ContainsKey('WeekdayPagerEndTime')) + { + $newOperatorObject.WeekdayPagerEndTime = $WeekdayPagerEndTime + } + + if ($PSBoundParameters.ContainsKey('WeekdayPagerStartTime')) + { + $newOperatorObject.WeekdayPagerStartTime = $WeekdayPagerStartTime + } + + $newOperatorObject.Create() + + if ($PassThru.IsPresent) + { + return $newOperatorObject + } + } + catch + { + $errorMessage = $script:localizedData.New_SqlDscAgentOperator_CreateFailed -f $Name + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.InvalidOperationException]::new($errorMessage, $_.Exception), + 'NSAO0004', # cspell: disable-line + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $Name + ) + ) + } + } + } +} diff --git a/source/Public/New-SqlDscDatabase.ps1 b/source/Public/New-SqlDscDatabase.ps1 index f463793810..ba805ce511 100644 --- a/source/Public/New-SqlDscDatabase.ps1 +++ b/source/Public/New-SqlDscDatabase.ps1 @@ -118,7 +118,15 @@ function New-SqlDscDatabase if ($ServerObject.Databases[$Name]) { $errorMessage = $script:localizedData.Database_AlreadyExists -f $Name, $ServerObject.InstanceName - New-InvalidOperationException -Message $errorMessage + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.InvalidOperationException]::new($errorMessage), + 'NSD0001', # cspell: disable-line + [System.Management.Automation.ErrorCategory]::ResourceExists, + $Name + ) + ) } # Validate compatibility level if specified @@ -199,7 +207,15 @@ function New-SqlDscDatabase catch { $errorMessage = $script:localizedData.Database_CreateFailed -f $Name, $ServerObject.InstanceName - New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.InvalidOperationException]::new($errorMessage, $_.Exception), + 'NSD0002', # cspell: disable-line + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $Name + ) + ) } } } diff --git a/source/Public/New-SqlDscRole.ps1 b/source/Public/New-SqlDscRole.ps1 index 4dbee7da5e..a15f37afe0 100644 --- a/source/Public/New-SqlDscRole.ps1 +++ b/source/Public/New-SqlDscRole.ps1 @@ -84,7 +84,15 @@ function New-SqlDscRole if ($ServerObject.Roles[$Name]) { $errorMessage = $script:localizedData.Role_AlreadyExists -f $Name, $ServerObject.InstanceName - New-InvalidOperationException -Message $errorMessage + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.InvalidOperationException]::new($errorMessage), + 'NSR0001', # cspell: disable-line + [System.Management.Automation.ErrorCategory]::ResourceExists, + $Name + ) + ) } $verboseDescriptionMessage = $script:localizedData.Role_Create_ShouldProcessVerboseDescription -f $Name, $ServerObject.InstanceName @@ -113,7 +121,15 @@ function New-SqlDscRole catch { $errorMessage = $script:localizedData.Role_CreateFailed -f $Name, $ServerObject.InstanceName - New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.InvalidOperationException]::new($errorMessage, $_.Exception), + 'NSR0002', # cspell: disable-line + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $Name + ) + ) } } } diff --git a/source/Public/Remove-SqlDscAgentOperator.ps1 b/source/Public/Remove-SqlDscAgentOperator.ps1 new file mode 100644 index 0000000000..0cc0530e9f --- /dev/null +++ b/source/Public/Remove-SqlDscAgentOperator.ps1 @@ -0,0 +1,135 @@ +<# + .SYNOPSIS + Removes a SQL Agent Operator. + + .DESCRIPTION + This command removes a SQL Agent Operator from a SQL Server Database Engine instance. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER OperatorObject + Specifies the SQL Agent Operator object to remove. + + .PARAMETER Name + Specifies the name of the SQL Agent Operator to remove. + + .PARAMETER Force + Specifies that the operator should be removed without any confirmation. + + .PARAMETER Refresh + Specifies that the SQL Agent Operator object should be refreshed before removal. + This is only used when specifying the operator by name. + + .INPUTS + Microsoft.SqlServer.Management.Smo.Server + + SQL Server Database Engine instance object. + + .INPUTS + Microsoft.SqlServer.Management.Smo.Agent.Operator + + SQL Agent Operator object. + + .OUTPUTS + None. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + Remove-SqlDscAgentOperator -ServerObject $serverObject -Name 'MyOperator' + + Removes the SQL Agent Operator named 'MyOperator'. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | Remove-SqlDscAgentOperator -Name 'MyOperator' + + Removes the SQL Agent Operator using pipeline input. + + .EXAMPLE + $operatorObject = Get-SqlDscAgentOperator -ServerObject $serverObject -Name 'MyOperator' + $operatorObject | Remove-SqlDscAgentOperator + + Removes the SQL Agent Operator using operator object pipeline input. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + Remove-SqlDscAgentOperator -ServerObject $serverObject -Name 'MyOperator' -Refresh + + Removes the SQL Agent Operator named 'MyOperator' with explicit refresh of the operator object. +#> +function Remove-SqlDscAgentOperator +{ + [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/8.')] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High', DefaultParameterSetName = 'ByName')] + [OutputType()] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'ByName')] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'ByObject')] + [Microsoft.SqlServer.Management.Smo.Agent.Operator] + $OperatorObject, + + [Parameter(Mandatory = $true, ParameterSetName = 'ByName')] + [ValidateNotNullOrEmpty()] + [System.String] + $Name, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force, + + [Parameter(ParameterSetName = 'ByName')] + [System.Management.Automation.SwitchParameter] + $Refresh + ) + + # cSpell: ignore RSAO + process + { + if ($Force.IsPresent -and -not $Confirm) + { + $ConfirmPreference = 'None' + } + if ($PSCmdlet.ParameterSetName -eq 'ByName') + { + # Get the operator by name, throws when ErrorAction preference is Stop. + $OperatorObject = Get-AgentOperatorObject -ServerObject $ServerObject -Name $Name -Refresh:$Refresh -ErrorAction $ErrorActionPreference + + if (-not $OperatorObject) + { + Write-Verbose -Message ($script:localizedData.Remove_SqlDscAgentOperator_OperatorNotFound -f $Name) + + return + } + } + + $verboseDescriptionMessage = $script:localizedData.Remove_SqlDscAgentOperator_RemoveShouldProcessVerboseDescription -f $OperatorObject.Name, $OperatorObject.Parent.Parent.InstanceName + $verboseWarningMessage = $script:localizedData.Remove_SqlDscAgentOperator_RemoveShouldProcessVerboseWarning -f $OperatorObject.Name + $captionMessage = $script:localizedData.Remove_SqlDscAgentOperator_RemoveShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage)) + { + try + { + $OperatorObject.Drop() + } + catch + { + $errorMessage = $script:localizedData.Remove_SqlDscAgentOperator_RemoveFailed -f $OperatorObject.Name + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.InvalidOperationException]::new($errorMessage, $_.Exception), + 'RSAO0001', # cspell: disable-line + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $OperatorObject + ) + ) + } + } + } +} diff --git a/source/Public/Set-SqlDscAgentAlert.ps1 b/source/Public/Set-SqlDscAgentAlert.ps1 index 10046c069e..02c7cf8970 100644 --- a/source/Public/Set-SqlDscAgentAlert.ps1 +++ b/source/Public/Set-SqlDscAgentAlert.ps1 @@ -77,7 +77,7 @@ function Set-SqlDscAgentAlert { [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/8.')] - [CmdletBinding(SupportsShouldProcess = $true)] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] [OutputType([Microsoft.SqlServer.Management.Smo.Agent.Alert])] param ( diff --git a/source/Public/Set-SqlDscAgentOperator.ps1 b/source/Public/Set-SqlDscAgentOperator.ps1 new file mode 100644 index 0000000000..99b0432cb1 --- /dev/null +++ b/source/Public/Set-SqlDscAgentOperator.ps1 @@ -0,0 +1,264 @@ +<# + .SYNOPSIS + Updates properties of a SQL Agent Operator. + + .DESCRIPTION + This command updates properties of an existing SQL Agent Operator on a SQL Server Database Engine instance. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER OperatorObject + Specifies the SQL Agent Operator object to update. + + .PARAMETER Name + Specifies the name of the SQL Agent Operator to update. + + .PARAMETER EmailAddress + Specifies the email address for the SQL Agent Operator. + + .PARAMETER CategoryName + Specifies the category name for the SQL Agent Operator. + + .PARAMETER NetSendAddress + Specifies the net send address for the SQL Agent Operator. + + .PARAMETER PagerAddress + Specifies the pager address for the SQL Agent Operator. + + .PARAMETER PagerDays + Specifies the days when pager notifications are active for the SQL Agent Operator. + + .PARAMETER SaturdayPagerEndTime + Specifies the Saturday pager end time for the SQL Agent Operator. + + .PARAMETER SaturdayPagerStartTime + Specifies the Saturday pager start time for the SQL Agent Operator. + + .PARAMETER SundayPagerEndTime + Specifies the Sunday pager end time for the SQL Agent Operator. + + .PARAMETER SundayPagerStartTime + Specifies the Sunday pager start time for the SQL Agent Operator. + + .PARAMETER WeekdayPagerEndTime + Specifies the weekday pager end time for the SQL Agent Operator. + + .PARAMETER WeekdayPagerStartTime + Specifies the weekday pager start time for the SQL Agent Operator. + + .PARAMETER Force + Specifies that the operator should be updated without any confirmation. + + .INPUTS + Microsoft.SqlServer.Management.Smo.Server + + SQL Server Database Engine instance object. + + .INPUTS + Microsoft.SqlServer.Management.Smo.Agent.Operator + + SQL Agent Operator object. + + .OUTPUTS + None. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + Set-SqlDscAgentOperator -ServerObject $serverObject -Name 'MyOperator' -EmailAddress 'admin@contoso.com' + + Updates the email address of the SQL Agent Operator named 'MyOperator'. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | Set-SqlDscAgentOperator -Name 'MyOperator' -EmailAddress 'admin@contoso.com' + + Updates the email address of the SQL Agent Operator using pipeline input. + + .EXAMPLE + $operatorObject = Get-SqlDscAgentOperator -ServerObject $serverObject -Name 'MyOperator' + $operatorObject | Set-SqlDscAgentOperator -EmailAddress 'admin@contoso.com' + + Updates the email address of the SQL Agent Operator using operator object pipeline input. +#> +function Set-SqlDscAgentOperator +{ + [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/8.')] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High', DefaultParameterSetName = 'ByName')] + [OutputType()] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'ByName')] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'ByObject')] + [Microsoft.SqlServer.Management.Smo.Agent.Operator] + $OperatorObject, + + [Parameter(Mandatory = $true, ParameterSetName = 'ByName')] + [ValidateNotNullOrEmpty()] + [System.String] + $Name, + + [Parameter()] + [System.String] + $EmailAddress, + + [Parameter()] + [System.String] + $CategoryName, + + [Parameter()] + [System.String] + $NetSendAddress, + + [Parameter()] + [System.String] + $PagerAddress, + + [Parameter()] + [Microsoft.SqlServer.Management.Smo.Agent.WeekDays] + $PagerDays, + + [Parameter()] + [System.TimeSpan] + $SaturdayPagerEndTime, + + [Parameter()] + [System.TimeSpan] + $SaturdayPagerStartTime, + + [Parameter()] + [System.TimeSpan] + $SundayPagerEndTime, + + [Parameter()] + [System.TimeSpan] + $SundayPagerStartTime, + + [Parameter()] + [System.TimeSpan] + $WeekdayPagerEndTime, + + [Parameter()] + [System.TimeSpan] + $WeekdayPagerStartTime, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force + ) + + begin + { + # Dynamically get settable properties by filtering out common parameters and control parameters + $settableProperties = Get-CommandParameter -Command $MyInvocation.MyCommand -Exclude @('ServerObject', 'OperatorObject', 'Name', 'Force') + + Assert-BoundParameter -BoundParameterList $PSBoundParameters -AtLeastOneList $settableProperties + } + + # cSpell: ignore SSAO + process + { + if ($Force.IsPresent -and -not $Confirm) + { + $ConfirmPreference = 'None' + } + if ($PSCmdlet.ParameterSetName -eq 'ByName') + { + Write-Verbose -Message ($script:localizedData.Set_SqlDscAgentOperator_RefreshingServerObject) + + $ServerObject.JobServer.Operators.Refresh() + + $originalErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = 'Stop' + $OperatorObject = Get-AgentOperatorObject -ServerObject $ServerObject -Name $Name -ErrorAction 'Stop' + $ErrorActionPreference = $originalErrorActionPreference + } + + # Build description of parameters being set for ShouldProcess + $excludeParameters = @('ServerObject', 'OperatorObject', 'Name', 'Force') + $parametersText = ConvertTo-FormattedParameterDescription -BoundParameters $PSBoundParameters -Exclude $excludeParameters + + $verboseDescriptionMessage = $script:localizedData.Set_SqlDscAgentOperator_UpdateShouldProcessVerboseDescription -f $OperatorObject.Name, $OperatorObject.Parent.Parent.InstanceName, $parametersText + $verboseWarningMessage = $script:localizedData.Set_SqlDscAgentOperator_UpdateShouldProcessVerboseWarning -f $OperatorObject.Name + $captionMessage = $script:localizedData.Set_SqlDscAgentOperator_UpdateShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage)) + { + try + { + if ($PSBoundParameters.ContainsKey('EmailAddress')) + { + $OperatorObject.EmailAddress = $EmailAddress + } + + if ($PSBoundParameters.ContainsKey('CategoryName')) + { + $OperatorObject.CategoryName = $CategoryName + } + + if ($PSBoundParameters.ContainsKey('NetSendAddress')) + { + $OperatorObject.NetSendAddress = $NetSendAddress + } + + if ($PSBoundParameters.ContainsKey('PagerAddress')) + { + $OperatorObject.PagerAddress = $PagerAddress + } + + if ($PSBoundParameters.ContainsKey('PagerDays')) + { + $OperatorObject.PagerDays = $PagerDays + } + + if ($PSBoundParameters.ContainsKey('SaturdayPagerEndTime')) + { + $OperatorObject.SaturdayPagerEndTime = $SaturdayPagerEndTime + } + + if ($PSBoundParameters.ContainsKey('SaturdayPagerStartTime')) + { + $OperatorObject.SaturdayPagerStartTime = $SaturdayPagerStartTime + } + + if ($PSBoundParameters.ContainsKey('SundayPagerEndTime')) + { + $OperatorObject.SundayPagerEndTime = $SundayPagerEndTime + } + + if ($PSBoundParameters.ContainsKey('SundayPagerStartTime')) + { + $OperatorObject.SundayPagerStartTime = $SundayPagerStartTime + } + + if ($PSBoundParameters.ContainsKey('WeekdayPagerEndTime')) + { + $OperatorObject.WeekdayPagerEndTime = $WeekdayPagerEndTime + } + + if ($PSBoundParameters.ContainsKey('WeekdayPagerStartTime')) + { + $OperatorObject.WeekdayPagerStartTime = $WeekdayPagerStartTime + } + + $OperatorObject.Alter() + } + catch + { + $errorMessage = $script:localizedData.Set_SqlDscAgentOperator_UpdateFailed -f $OperatorObject.Name + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.InvalidOperationException]::new($errorMessage, $_.Exception), + 'SSAO0001', # cspell: disable-line + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $OperatorObject + ) + ) + } + } + } +} diff --git a/source/Public/Set-SqlDscConfigurationOption.ps1 b/source/Public/Set-SqlDscConfigurationOption.ps1 new file mode 100644 index 0000000000..20b0b8c2d6 --- /dev/null +++ b/source/Public/Set-SqlDscConfigurationOption.ps1 @@ -0,0 +1,331 @@ +<# + .SYNOPSIS + Set server configuration option value. + + .DESCRIPTION + This command sets the value of a SQL Server Database Engine configuration option + using SQL Server Management Objects (SMO). The function validates that the option + exists and that the provided value is within the option's minimum and maximum range. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER Name + Specifies the name of the configuration option to set. + + .PARAMETER Value + Specifies the value to set for the configuration option. + + .PARAMETER Force + Suppresses prompts and overrides restrictions that would normally prevent the command from completing. + When specified, the command will not prompt for confirmation before making changes. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | Set-SqlDscConfigurationOption -Name "Agent XPs" -Value 1 + + Sets the "Agent XPs" configuration option to enabled (1). + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + Set-SqlDscConfigurationOption -ServerObject $serverObject -Name "cost threshold for parallelism" -Value 50 + + Sets the "cost threshold for parallelism" configuration option to 50. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | Set-SqlDscConfigurationOption -Name "max degree of parallelism" -Value 4 -WhatIf + + Shows what would happen if the "max degree of parallelism" option was set to 4, without actually making the change. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | Set-SqlDscConfigurationOption -Name "cost threshold for parallelism" -Value 25 -Force + + Sets the "cost threshold for parallelism" configuration option to 25 without prompting for confirmation. + + .INPUTS + Microsoft.SqlServer.Management.Smo.Server + SQL Server Management Objects (SMO) Server object representing a SQL Server instance. + + .OUTPUTS + None. + + .NOTES + This function supports ShouldProcess, allowing the use of -WhatIf and -Confirm parameters. +#> +function Set-SqlDscConfigurationOption +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Justification = 'Because we pass parameters in the argument completer that are not yet used.')] + [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/8.')] + [OutputType()] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(Mandatory = $true)] + [ArgumentCompleter({ + param + ( + [Parameter()] + $commandName, + + [Parameter()] + $parameterName, + + [Parameter()] + $wordToComplete, + + [Parameter()] + $commandAst, + + [Parameter()] + $fakeBoundParameters + ) + + # Get ServerObject from bound parameters only + $serverObject = $null + + if ($FakeBoundParameters.ContainsKey('ServerObject')) + { + $serverObject = $FakeBoundParameters['ServerObject'] + } + + if ($serverObject -and $serverObject -is [Microsoft.SqlServer.Management.Smo.Server]) + { + try + { + $options = $serverObject.Configuration.Properties | Where-Object { + $_.DisplayName -like "*$WordToComplete*" + } | Sort-Object DisplayName + + foreach ($option in $options) + { + $tooltip = "Current: $($option.ConfigValue), Run: $($option.RunValue), Range: $($option.Minimum)-$($option.Maximum)" + [System.Management.Automation.CompletionResult]::new( + "'$($option.DisplayName)'", + $option.DisplayName, + 'ParameterValue', + $tooltip + ) + } + } + catch + { + # Return empty if there's an error accessing the server + @() + } + } + else + { + # Return empty array if no server object available + @() + } + })] + [System.String] + $Name, + + [Parameter(Mandatory = $true)] + [ArgumentCompleter({ + param + ( + [Parameter()] + $commandName, + + [Parameter()] + $parameterName, + + [Parameter()] + $wordToComplete, + + [Parameter()] + $commandAst, + + [Parameter()] + $fakeBoundParameters + ) + + # Get ServerObject and Name from bound parameters + $serverObject = $null + $optionName = $null + + if ($FakeBoundParameters.ContainsKey('ServerObject')) + { + $serverObject = $FakeBoundParameters['ServerObject'] + } + + if ($FakeBoundParameters.ContainsKey('Name')) + { + $optionName = $FakeBoundParameters['Name'] + } + + if ($serverObject -and $serverObject -is [Microsoft.SqlServer.Management.Smo.Server] -and $optionName) + { + try + { + $option = $serverObject.Configuration.Properties | Where-Object { + $_.DisplayName -eq $optionName + } + + if ($option) + { + $suggestions = @() + + # Add current values + $suggestions += [PSCustomObject]@{ + Value = $option.ConfigValue + Tooltip = "Current ConfigValue: $($option.ConfigValue)" + } + + $suggestions += [PSCustomObject]@{ + Value = $option.RunValue + Tooltip = "Current RunValue: $($option.RunValue)" + } + + # Add min/max values + $suggestions += [PSCustomObject]@{ + Value = $option.Minimum + Tooltip = "Minimum allowed value: $($option.Minimum)" + } + + $suggestions += [PSCustomObject]@{ + Value = $option.Maximum + Tooltip = "Maximum allowed value: $($option.Maximum)" + } + + # If it's a boolean option (0-1), suggest both values + if ($option.Minimum -eq 0 -and $option.Maximum -eq 1) + { + $suggestions += [PSCustomObject]@{ + Value = 0 + Tooltip = 'Disabled (0)' + } + + $suggestions += [PSCustomObject]@{ + Value = 1 + Tooltip = 'Enabled (1)' + } + } + + # Remove duplicates and filter by word to complete + $uniqueSuggestions = $suggestions | Group-Object Value | ForEach-Object { $_.Group[0] } + $filteredSuggestions = $uniqueSuggestions | Where-Object { + $_.Value -like "*$WordToComplete*" + } | Sort-Object Value + + foreach ($suggestion in $filteredSuggestions) + { + [System.Management.Automation.CompletionResult]::new( + $suggestion.Value.ToString(), + $suggestion.Value.ToString(), + 'ParameterValue', + $suggestion.Tooltip + ) + } + } + } + catch + { + # Return empty if there's an error + @() + } + } + else + { + # Return empty array if prerequisites not met + @() + } + })] + [System.Int32] + $Value, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force + ) + + begin + { + if ($Force.IsPresent -and -not $Confirm) + { + $ConfirmPreference = 'None' + } + } + + process + { + # Find the configuration option by name + $configurationOption = $ServerObject.Configuration.Properties | + Where-Object { + $_.DisplayName -eq $Name + } + + if (-not $configurationOption) + { + $missingConfigurationOptionMessage = $script:localizedData.ConfigurationOption_Set_Missing -f $Name + + $writeErrorParameters = @{ + Message = $missingConfigurationOptionMessage + Category = 'InvalidOperation' + ErrorId = 'SSDCO0001' # cspell: disable-line + TargetObject = $Name + } + + Write-Error @writeErrorParameters + return + } + + # Validate that the option value is within the allowed range + if ($Value -lt $configurationOption.Minimum -or $Value -gt $configurationOption.Maximum) + { + $invalidValueMessage = $script:localizedData.ConfigurationOption_Set_InvalidValue -f $Name, $Value, $configurationOption.Minimum, $configurationOption.Maximum + + $writeErrorParameters = @{ + Message = $invalidValueMessage + Category = 'InvalidArgument' + ErrorId = 'SSDCO0002' # cspell: disable-line + TargetObject = $Value + } + + Write-Error @writeErrorParameters + return + } + + # Prepare ShouldProcess messages + $descriptionMessage = $script:localizedData.ConfigurationOption_Set_ShouldProcessDescription -f $Name, $Value, $ServerObject.Name + $confirmationMessage = $script:localizedData.ConfigurationOption_Set_ShouldProcessConfirmation -f $Name, $Value + $captionMessage = $script:localizedData.ConfigurationOption_Set_ShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($descriptionMessage, $confirmationMessage, $captionMessage)) + { + try + { + # Set the new configuration value + $configurationOption.ConfigValue = $Value + + # Apply the configuration change + $ServerObject.Configuration.Alter() + + Write-Information -MessageData ($script:localizedData.ConfigurationOption_Set_Success -f $Name, $Value, $ServerObject.Name) + } + catch + { + $setConfigurationOptionFailedMessage = $script:localizedData.ConfigurationOption_Set_Failed -f $Name, $Value, $_.Exception.Message + + $writeErrorParameters = @{ + Message = $setConfigurationOptionFailedMessage + Category = 'InvalidOperation' + ErrorId = 'SSDCO0003' # cspell: disable-line + TargetObject = $Name + Exception = $_.Exception + } + + Write-Error @writeErrorParameters + return + } + } + } +} diff --git a/source/Public/Test-SqlDscConfigurationOption.ps1 b/source/Public/Test-SqlDscConfigurationOption.ps1 new file mode 100644 index 0000000000..5d858b05b9 --- /dev/null +++ b/source/Public/Test-SqlDscConfigurationOption.ps1 @@ -0,0 +1,268 @@ +<# + .SYNOPSIS + Test if server configuration option has the specified value. + + .DESCRIPTION + This command tests whether a SQL Server Database Engine configuration option + has the specified value using SQL Server Management Objects (SMO). The function + validates that the option exists and compares the current value with the expected value. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER Name + Specifies the name of the configuration option to test. + + .PARAMETER Value + Specifies the expected value for the configuration option. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | Test-SqlDscConfigurationOption -Name "Agent XPs" -Value 1 + + Tests if the "Agent XPs" configuration option is enabled (1). + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + Test-SqlDscConfigurationOption -ServerObject $serverObject -Name "cost threshold for parallelism" -Value 50 + + Tests if the "cost threshold for parallelism" configuration option is set to 50. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | Test-SqlDscConfigurationOption -Name "max degree of parallelism" -Value 4 + + Tests if the "max degree of parallelism" option is set to 4. + + .INPUTS + Microsoft.SqlServer.Management.Smo.Server + SQL Server Management Objects (SMO) Server object representing a SQL Server instance. + + .OUTPUTS + System.Boolean + Returns $true if the configuration option has the specified value, $false otherwise. + + .NOTES + This function does not support ShouldProcess as it is a read-only operation. +#> +function Test-SqlDscConfigurationOption +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Justification = 'Because we pass parameters in the argument completer that are not yet used.')] + [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/8.')] + [OutputType([System.Boolean])] + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(Mandatory = $true)] + [ArgumentCompleter({ + param + ( + [Parameter()] + $commandName, + + [Parameter()] + $parameterName, + + [Parameter()] + $wordToComplete, + + [Parameter()] + $commandAst, + + [Parameter()] + $fakeBoundParameters + ) + + # Get ServerObject from bound parameters only + $serverObject = $null + + if ($FakeBoundParameters.ContainsKey('ServerObject')) + { + $serverObject = $FakeBoundParameters['ServerObject'] + } + + if ($serverObject -and $serverObject -is [Microsoft.SqlServer.Management.Smo.Server]) + { + try + { + $options = $serverObject.Configuration.Properties | Where-Object { + $_.DisplayName -like "*$WordToComplete*" + } | Sort-Object DisplayName + + foreach ($option in $options) + { + $tooltip = "Current: $($option.ConfigValue), Run: $($option.RunValue), Range: $($option.Minimum)-$($option.Maximum)" + [System.Management.Automation.CompletionResult]::new( + "'$($option.DisplayName)'", + $option.DisplayName, + 'ParameterValue', + $tooltip + ) + } + } + catch + { + # Return empty if there's an error accessing the server + @() + } + } + else + { + # Return empty array if no server object available + @() + } + })] + [System.String] + $Name, + + [Parameter(Mandatory = $true)] + [ArgumentCompleter({ + param + ( + [Parameter()] + $commandName, + + [Parameter()] + $parameterName, + + [Parameter()] + $wordToComplete, + + [Parameter()] + $commandAst, + + [Parameter()] + $fakeBoundParameters + ) + + # Get ServerObject and Name from bound parameters + $serverObject = $null + $optionName = $null + + if ($FakeBoundParameters.ContainsKey('ServerObject')) + { + $serverObject = $FakeBoundParameters['ServerObject'] + } + + if ($FakeBoundParameters.ContainsKey('Name')) + { + $optionName = $FakeBoundParameters['Name'] + } + + if ($serverObject -and $serverObject -is [Microsoft.SqlServer.Management.Smo.Server] -and $optionName) + { + try + { + $option = $serverObject.Configuration.Properties | Where-Object { + $_.DisplayName -eq $optionName + } + + if ($option) + { + $suggestions = @() + + # Add current values + $suggestions += [PSCustomObject]@{ + Value = $option.ConfigValue + Tooltip = "Current ConfigValue: $($option.ConfigValue)" + } + + $suggestions += [PSCustomObject]@{ + Value = $option.RunValue + Tooltip = "Current RunValue: $($option.RunValue)" + } + + # Add min/max values + $suggestions += [PSCustomObject]@{ + Value = $option.Minimum + Tooltip = "Minimum allowed value: $($option.Minimum)" + } + + $suggestions += [PSCustomObject]@{ + Value = $option.Maximum + Tooltip = "Maximum allowed value: $($option.Maximum)" + } + + # If it's a boolean option (0-1), suggest both values + if ($option.Minimum -eq 0 -and $option.Maximum -eq 1) + { + $suggestions += [PSCustomObject]@{ + Value = 0 + Tooltip = 'Disabled (0)' + } + + $suggestions += [PSCustomObject]@{ + Value = 1 + Tooltip = 'Enabled (1)' + } + } + + # Remove duplicates and filter by word to complete + $uniqueSuggestions = $suggestions | Group-Object Value | ForEach-Object { $_.Group[0] } + $filteredSuggestions = $uniqueSuggestions | Where-Object { + $_.Value -like "*$WordToComplete*" + } | Sort-Object Value + + foreach ($suggestion in $filteredSuggestions) + { + [System.Management.Automation.CompletionResult]::new( + $suggestion.Value.ToString(), + $suggestion.Value.ToString(), + 'ParameterValue', + $suggestion.Tooltip + ) + } + } + } + catch + { + # Return empty if there's an error + @() + } + } + else + { + # Return empty array if prerequisites not met + @() + } + })] + [System.Int32] + $Value + ) + + process + { + # Find the configuration option by name + $configurationOption = $ServerObject.Configuration.Properties | + Where-Object { + $_.DisplayName -eq $Name + } + + if (-not $configurationOption) + { + $missingConfigurationOptionMessage = $script:localizedData.ConfigurationOption_Test_Missing -f $Name + + $writeErrorParameters = @{ + Message = $missingConfigurationOptionMessage + Category = 'InvalidOperation' + ErrorId = 'TSDCO0001' # cspell: disable-line + TargetObject = $Name + } + + Write-Error @writeErrorParameters + return $false + } + + # Compare the current configuration value with the expected value + $currentValue = $configurationOption.ConfigValue + $isMatch = $currentValue -eq $Value + + Write-Verbose -Message ($script:localizedData.ConfigurationOption_Test_Result -f $Name, $currentValue, $Value, $isMatch, $ServerObject.Name) + + return $isMatch + } +} diff --git a/source/Public/Test-SqlDscIsAgentOperator.ps1 b/source/Public/Test-SqlDscIsAgentOperator.ps1 new file mode 100644 index 0000000000..f67109a245 --- /dev/null +++ b/source/Public/Test-SqlDscIsAgentOperator.ps1 @@ -0,0 +1,87 @@ +<# + .SYNOPSIS + Tests for the existence of a SQL Agent Operator. + + .DESCRIPTION + This command tests if a SQL Agent Operator exists on a SQL Server Database Engine instance. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER Name + Specifies the name of the SQL Agent Operator to test. + + .PARAMETER Refresh + Specifies that the **ServerObject**'s operators should be refreshed before + testing the operator. This is helpful when operators could have + been modified outside of the **ServerObject**, for example through T-SQL. + But on instances with a large amount of operators it might be better to make + sure the **ServerObject** is recent enough. + + .INPUTS + Microsoft.SqlServer.Management.Smo.Server + + SQL Server Database Engine instance object. + + .OUTPUTS + System.Boolean + + Returns $true if the operator exists, $false otherwise. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + Test-SqlDscIsAgentOperator -ServerObject $serverObject -Name 'MyOperator' + + Tests if the SQL Agent Operator named 'MyOperator' exists. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | Test-SqlDscIsAgentOperator -Name 'MyOperator' + + Tests if the SQL Agent Operator named 'MyOperator' exists using pipeline input. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | Test-SqlDscIsAgentOperator -Name 'MyOperator' -Refresh + + Refreshes the server operators collection before testing if **MyOperator** exists. +#> +function Test-SqlDscIsAgentOperator +{ + [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/8.')] + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Name, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Refresh + ) + + # cSpell: ignore TISAO + process + { + Write-Verbose -Message ($script:localizedData.Test_SqlDscIsAgentOperator_TestingOperator -f $Name) + + $operatorObject = Get-AgentOperatorObject -ServerObject $ServerObject -Name $Name -Refresh:$Refresh -ErrorAction 'SilentlyContinue' + + if (-not $operatorObject) + { + Write-Verbose -Message ($script:localizedData.Test_SqlDscIsAgentOperator_OperatorNotFound -f $Name) + return $false + } + + Write-Verbose -Message ($script:localizedData.Test_SqlDscIsAgentOperator_OperatorFound -f $Name) + + return $true + } +} diff --git a/source/Public/Test-SqlDscServerPermission.ps1 b/source/Public/Test-SqlDscServerPermission.ps1 index 557a46c72c..c160a517b7 100644 --- a/source/Public/Test-SqlDscServerPermission.ps1 +++ b/source/Public/Test-SqlDscServerPermission.ps1 @@ -239,7 +239,7 @@ function Test-SqlDscServerPermission # Check that no unexpected permissions are present (only if ExactMatch is specified) if ($ExactMatch.IsPresent) { - $desiredPermissionNames = $Permission | ForEach-Object { $_.ToString() } + $desiredPermissionNames = $Permission | ForEach-Object -Process { $_.ToString() } foreach ($currentPermissionName in $currentPermissionForState.Permission) { if ($currentPermissionName -notin $desiredPermissionNames) diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index c9af4710d5..629b73d040 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -246,6 +246,19 @@ ConvertFrom-StringData @' ## Get-SqlDscConfigurationOption ConfigurationOption_Get_Missing = There is no configuration option with the name '{0}'. + ## Set-SqlDscConfigurationOption + ConfigurationOption_Set_Missing = There is no configuration option with the name '{0}'. + ConfigurationOption_Set_InvalidValue = The value '{1}' for configuration option '{0}' is outside the valid range of {2} to {3}. + ConfigurationOption_Set_ShouldProcessDescription = Set configuration option '{0}' to '{1}' on server '{2}'. + ConfigurationOption_Set_ShouldProcessConfirmation = Are you sure you want to set configuration option '{0}' to '{1}'? + ConfigurationOption_Set_ShouldProcessCaption = Set configuration option + ConfigurationOption_Set_Success = Successfully set configuration option '{0}' to '{1}' on server '{2}'. + ConfigurationOption_Set_Failed = Failed to set configuration option '{0}' to '{1}'. {2} + + ## Test-SqlDscConfigurationOption + ConfigurationOption_Test_Missing = There is no configuration option with the name '{0}'. + ConfigurationOption_Test_Result = Testing configuration option '{0}': Current value is '{1}', expected value is '{2}', match result is '{3}' on server '{4}'. + ## Save-SqlDscSqlServerMediaFile SqlServerMediaFile_Save_ShouldProcessVerboseDescription = The existing destination file '{0}' already exists and will be replaced. SqlServerMediaFile_Save_ShouldProcessVerboseWarning = Are you sure you want to replace existing file '{0}'? @@ -444,4 +457,62 @@ ConvertFrom-StringData @' Test_SqlDscAgentAlert_MessageIdMismatch = Message ID mismatch: current '{0}', expected '{1}'. (TSAA0007) Test_SqlDscAgentAlert_MessageIdMatch = Message ID matches expected value '{0}'. (TSAA0008) Test_SqlDscAgentAlert_AllTestsPassed = All tests passed for SQL Agent Alert '{0}'. (TSAA0009) + + ## Get-SqlDscAgentOperator + Get_SqlDscAgentOperator_GettingOperator = Getting SQL Agent Operator '{0}'. (GSAO0003) + Get_SqlDscAgentOperator_GettingOperators = Getting SQL Agent Operators from instance '{0}'. (GSAO0001) + Get_SqlDscAgentOperator_ReturningAllOperators = Returning all {0} SQL Agent Operators. (GSAO0002) + + ## New-SqlDscAgentOperator + New_SqlDscAgentOperator_OperatorAlreadyExists = SQL Agent Operator '{0}' already exists. (NSAO0001) + New_SqlDscAgentOperator_CreateFailed = Failed to create SQL Agent Operator '{0}'. (NSAO0004) + New_SqlDscAgentOperator_CreateShouldProcessVerboseDescription = Creating the SQL Agent Operator '{0}' on the instance '{1}'. + New_SqlDscAgentOperator_CreateShouldProcessVerboseWarning = Are you sure you want to create the SQL Agent Operator '{0}'? + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + New_SqlDscAgentOperator_CreateShouldProcessCaption = Create SQL Agent Operator on instance + + ## Set-SqlDscAgentOperator + Set_SqlDscAgentOperator_RefreshingServerObject = Refreshing server object's operators collection. (SSAO0001) + Set_SqlDscAgentOperator_UpdateFailed = Failed to update SQL Agent Operator '{0}'. (SSAO0007) + Set_SqlDscAgentOperator_UpdateShouldProcessVerboseDescription = Updating the SQL Agent Operator '{0}' on the instance '{1}' with parameters:{2} + Set_SqlDscAgentOperator_UpdateShouldProcessVerboseWarning = Are you sure you want to update the SQL Agent Operator '{0}'? + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + Set_SqlDscAgentOperator_UpdateShouldProcessCaption = Update SQL Agent Operator on instance + + ## Remove-SqlDscAgentOperator + Remove_SqlDscAgentOperator_RemoveFailed = Failed to remove SQL Agent Operator '{0}'. (RSAO0005) + Remove_SqlDscAgentOperator_RemoveShouldProcessVerboseDescription = Removing the SQL Agent Operator '{0}' on the instance '{1}'. + Remove_SqlDscAgentOperator_RemoveShouldProcessVerboseWarning = Are you sure you want to remove the SQL Agent Operator '{0}'? + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + Remove_SqlDscAgentOperator_RemoveShouldProcessCaption = Remove SQL Agent Operator on instance + Remove_SqlDscAgentOperator_OperatorNotFound = SQL Agent Operator '{0}' was not found. (RSAO0002) + + ## Test-SqlDscIsAgentOperator + Test_SqlDscIsAgentOperator_TestingOperator = Testing if the SQL Agent Operator '{0}' exists and has the desired properties. (TISAO0001) + Test_SqlDscIsAgentOperator_OperatorNotFound = SQL Agent Operator '{0}' was not found. (TISAO0002) + Test_SqlDscIsAgentOperator_OperatorFound = SQL Agent Operator '{0}' was found. (TISAO0003) + + ## Enable-SqlDscAgentOperator + Enable_SqlDscAgentOperator_EnableFailed = Failed to enable SQL Agent Operator '{0}'. (ESAO0005) + Enable_SqlDscAgentOperator_ShouldProcessVerboseDescription = Enabling the SQL Agent Operator '{0}' on the instance '{1}'. + Enable_SqlDscAgentOperator_ShouldProcessVerboseWarning = Are you sure you want to enable the SQL Agent Operator '{0}'? + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + Enable_SqlDscAgentOperator_ShouldProcessCaption = Enable SQL Agent Operator on instance + + ## Disable-SqlDscAgentOperator + Disable_SqlDscAgentOperator_DisableFailed = Failed to disable SQL Agent Operator '{0}'. (DSAO0005) + Disable_SqlDscAgentOperator_ShouldProcessVerboseDescription = Disabling the SQL Agent Operator '{0}' on the instance '{1}'. + Disable_SqlDscAgentOperator_ShouldProcessVerboseWarning = Are you sure you want to disable the SQL Agent Operator '{0}'? + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + Disable_SqlDscAgentOperator_ShouldProcessCaption = Disable SQL Agent Operator on instance + + ## Agent Operator common + AgentOperator_NotFound = The SQL Agent Operator '{0}' was not found. (AO0001) + + ## Get-AgentOperatorObject + Get_AgentOperatorObject_GettingOperator = Getting SQL Agent Operator '{0}' from server object. (GAOO0002) + Get_AgentOperatorObject_RefreshingOperators = Refreshing SQL Agent Operators collection. (GAOO0003) + + ## ConvertTo-FormattedParameterDescription + ConvertTo_FormattedParameterDescription_NoParametersToUpdate = (no parameters to update) '@ diff --git a/tests/Integration/Commands/Assert-SqlDscAgentOperator.Integration.Tests.ps1 b/tests/Integration/Commands/Assert-SqlDscAgentOperator.Integration.Tests.ps1 new file mode 100644 index 0000000000..87cbe7c895 --- /dev/null +++ b/tests/Integration/Commands/Assert-SqlDscAgentOperator.Integration.Tests.ps1 @@ -0,0 +1,98 @@ +[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 build" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' + + $env:SqlServerDscCI = $true + + # Integration tests are run on the DSCSQLTEST instance + $script:sqlServerInstance = 'DSCSQLTEST' +} + +AfterAll { + Remove-Item -Path 'Env:\SqlServerDscCI' -ErrorAction 'SilentlyContinue' + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:moduleName -All | Remove-Module -Force +} + +Describe 'Assert-SqlDscAgentOperator' -Tag 'Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022' { + BeforeAll { + # Starting the named instance SQL Server service prior to running tests. + Start-Service -Name 'MSSQL$DSCSQLTEST' -Verbose -ErrorAction 'Stop' + + $mockSqlAdministratorUserName = 'SqlAdmin' # Using computer name as NetBIOS name throw exception. + $mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force + + $script:mockSqlAdminCredential = [System.Management.Automation.PSCredential]::new($mockSqlAdministratorUserName, $mockSqlAdministratorPassword) + + # Connect to the SQL Server instance + $script:sqlServerObject = Connect-SqlDscDatabaseEngine -InstanceName $script:sqlServerInstance -Credential $script:mockSqlAdminCredential -ErrorAction 'Stop' + + # Create a test operator for assertion tests + $script:sqlServerObject | New-SqlDscAgentOperator -Name 'IntegrationTest_AssertOperator' -EmailAddress 'assert@contoso.com' -ErrorAction Stop + } + + AfterAll { + $script:sqlServerObject | Remove-SqlDscAgentOperator -Name 'IntegrationTest_AssertOperator' -Force -ErrorAction 'SilentlyContinue' + + # Disconnect from the SQL Server + Disconnect-SqlDscDatabaseEngine -ServerObject $script:sqlServerObject + + # Stop the named instance SQL Server service to save memory on the build worker. + Stop-Service -Name 'MSSQL$DSCSQLTEST' -Verbose -ErrorAction 'Stop' + } + + Context 'When operator exists' { + It 'Should not throw when asserting existing operator' { + $null = Assert-SqlDscAgentOperator -ServerObject $script:sqlServerObject -Name 'IntegrationTest_AssertOperator' -ErrorAction Stop + } + + It 'Should not throw when asserting persistent operator created by New-SqlDscAgentOperator' { + $null = Assert-SqlDscAgentOperator -ServerObject $script:sqlServerObject -Name 'SqlDscIntegrationTestOperator_Persistent' -ErrorAction Stop + } + + It 'Should not return anything when operator exists' { + $result = Assert-SqlDscAgentOperator -ServerObject $script:sqlServerObject -Name 'IntegrationTest_AssertOperator' -ErrorAction Stop + $result | Should -BeNullOrEmpty + } + } + + Context 'When operator does not exist' { + It 'Should throw terminating error when operator does not exist' { + { Assert-SqlDscAgentOperator -ServerObject $script:sqlServerObject -Name 'NonExistentOperator' -ErrorAction Stop } | Should -Throw + } + + } + + Context 'When using pipeline input' { + It 'Should accept ServerObject from pipeline' { + $null = $script:sqlServerObject | Assert-SqlDscAgentOperator -Name 'IntegrationTest_AssertOperator' -ErrorAction 'Stop' + } + } +} diff --git a/tests/Integration/Commands/Disable-SqlDscAgentOperator.Integration.Tests.ps1 b/tests/Integration/Commands/Disable-SqlDscAgentOperator.Integration.Tests.ps1 new file mode 100644 index 0000000000..acfe627718 --- /dev/null +++ b/tests/Integration/Commands/Disable-SqlDscAgentOperator.Integration.Tests.ps1 @@ -0,0 +1,103 @@ +[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 build" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' + + $env:SqlServerDscCI = $true + + # Integration tests are run on the DSCSQLTEST instance + $script:sqlServerInstance = 'DSCSQLTEST' +} + +AfterAll { + Remove-Item -Path 'Env:\SqlServerDscCI' -ErrorAction 'SilentlyContinue' + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:moduleName -All | Remove-Module -Force +} + +Describe 'Disable-SqlDscAgentOperator' -Tag 'Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022' { + BeforeAll { + # Starting the named instance SQL Server service prior to running tests. + Start-Service -Name 'MSSQL$DSCSQLTEST' -Verbose -ErrorAction 'Stop' + + $mockSqlAdministratorUserName = 'SqlAdmin' # Using computer name as NetBIOS name throw exception. + $mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force + $mockSqlAdministratorCredential = [System.Management.Automation.PSCredential]::new($mockSqlAdministratorUserName, $mockSqlAdministratorPassword) + + $script:serverObject = Connect-SqlDscDatabaseEngine -InstanceName $script:sqlServerInstance -Credential $mockSqlAdministratorCredential -ErrorAction 'Stop' + + # Enable Agent XPs component for SQL Server Agent functionality + Set-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' -Value 1 -Force -Verbose -ErrorAction 'Stop' + } + + AfterAll { + # Disable Agent XPs component to clean up test environment + Set-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' -Value 0 -Force -Verbose -ErrorAction 'Stop' + + Disconnect-SqlDscDatabaseEngine -ServerObject $script:serverObject + + # Stopping the named instance SQL Server service after running tests. + Stop-Service -Name 'MSSQL$DSCSQLTEST' -Verbose -ErrorAction 'Stop' + } + + Context 'When disabling an existing agent operator' { + It 'Should disable the persistent operator' { + # Get the persistent operator created by New-SqlDscAgentOperator integration test + $operatorObject = Get-SqlDscAgentOperator -ServerObject $script:serverObject -Name 'SqlDscIntegrationTestOperator_Persistent' -ErrorAction 'Stop' + $operatorObject | Should -Not -BeNullOrEmpty + + # Enable it first if it's not enabled + if (-not $operatorObject.Enabled) + { + Enable-SqlDscAgentOperator -OperatorObject $operatorObject -Force -ErrorAction 'Stop' + } + + # Disable the operator + Disable-SqlDscAgentOperator -OperatorObject $operatorObject -Force -ErrorAction 'Stop' + + # Verify it's disabled + $operatorObject.Refresh() + $operatorObject.Enabled | Should -BeFalse + } + } + + Context 'When using ServerObject parameter set' { + It 'Should disable an operator using ServerObject and Name parameters' { + # First enable the operator to ensure it's enabled + Enable-SqlDscAgentOperator -ServerObject $script:serverObject -Name 'SqlDscIntegrationTestOperator_Persistent' -Force -ErrorAction 'Stop' + + # Disable using ServerObject parameter set + Disable-SqlDscAgentOperator -ServerObject $script:serverObject -Name 'SqlDscIntegrationTestOperator_Persistent' -Force -ErrorAction 'Stop' + + # Verify it's disabled + $operatorObject = Get-SqlDscAgentOperator -ServerObject $script:serverObject -Name 'SqlDscIntegrationTestOperator_Persistent' -ErrorAction 'Stop' + $operatorObject.Enabled | Should -BeFalse + } + } +} diff --git a/tests/Integration/Commands/Enable-SqlDscAgentOperator.Integration.Tests.ps1 b/tests/Integration/Commands/Enable-SqlDscAgentOperator.Integration.Tests.ps1 new file mode 100644 index 0000000000..47fe8f878b --- /dev/null +++ b/tests/Integration/Commands/Enable-SqlDscAgentOperator.Integration.Tests.ps1 @@ -0,0 +1,103 @@ +[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 build" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' + + $env:SqlServerDscCI = $true + + # Integration tests are run on the DSCSQLTEST instance + $script:sqlServerInstance = 'DSCSQLTEST' +} + +AfterAll { + Remove-Item -Path 'Env:\SqlServerDscCI' -ErrorAction 'SilentlyContinue' + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:moduleName -All | Remove-Module -Force +} + +Describe 'Enable-SqlDscAgentOperator' -Tag 'Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022' { + BeforeAll { + # Starting the named instance SQL Server service prior to running tests. + Start-Service -Name 'MSSQL$DSCSQLTEST' -Verbose -ErrorAction 'Stop' + + $mockSqlAdministratorUserName = 'SqlAdmin' # Using computer name as NetBIOS name throw exception. + $mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force + $mockSqlAdministratorCredential = [System.Management.Automation.PSCredential]::new($mockSqlAdministratorUserName, $mockSqlAdministratorPassword) + + $script:serverObject = Connect-SqlDscDatabaseEngine -InstanceName $script:sqlServerInstance -Credential $mockSqlAdministratorCredential -ErrorAction 'Stop' + + # Enable Agent XPs component for SQL Server Agent functionality + Set-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' -Value 1 -Force -Verbose -ErrorAction 'Stop' + } + + AfterAll { + # Disable Agent XPs component to clean up test environment + Set-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' -Value 0 -Force -Verbose -ErrorAction 'Stop' + + Disconnect-SqlDscDatabaseEngine -ServerObject $script:serverObject + + # Stopping the named instance SQL Server service after running tests. + Stop-Service -Name 'MSSQL$DSCSQLTEST' -Verbose -ErrorAction 'Stop' + } + + Context 'When enabling an existing agent operator' { + It 'Should enable the persistent operator' { + # Get the persistent operator created by New-SqlDscAgentOperator integration test + $operatorObject = Get-SqlDscAgentOperator -ServerObject $script:serverObject -Name 'SqlDscIntegrationTestOperator_Persistent' -ErrorAction 'Stop' + $operatorObject | Should -Not -BeNullOrEmpty + + # Disable it first if it's enabled + if ($operatorObject.Enabled) + { + Disable-SqlDscAgentOperator -OperatorObject $operatorObject -Force -ErrorAction 'Stop' + } + + # Enable the operator + Enable-SqlDscAgentOperator -OperatorObject $operatorObject -Force -ErrorAction 'Stop' + + # Verify it's enabled + $operatorObject.Refresh() + $operatorObject.Enabled | Should -BeTrue + } + } + + Context 'When using ServerObject parameter set' { + It 'Should enable an operator using ServerObject and Name parameters' { + # First disable the operator to ensure it's disabled + Disable-SqlDscAgentOperator -ServerObject $script:serverObject -Name 'SqlDscIntegrationTestOperator_Persistent' -Force -ErrorAction 'Stop' + + # Enable using ServerObject parameter set + Enable-SqlDscAgentOperator -ServerObject $script:serverObject -Name 'SqlDscIntegrationTestOperator_Persistent' -Force -ErrorAction 'Stop' + + # Verify it's enabled + $operatorObject = Get-SqlDscAgentOperator -ServerObject $script:serverObject -Name 'SqlDscIntegrationTestOperator_Persistent' -ErrorAction 'Stop' + $operatorObject.Enabled | Should -BeTrue + } + } +} diff --git a/tests/Integration/Commands/Get-SqlDscAgentOperator.Integration.Tests.ps1 b/tests/Integration/Commands/Get-SqlDscAgentOperator.Integration.Tests.ps1 new file mode 100644 index 0000000000..650095d582 --- /dev/null +++ b/tests/Integration/Commands/Get-SqlDscAgentOperator.Integration.Tests.ps1 @@ -0,0 +1,102 @@ +[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 build" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' + + $env:SqlServerDscCI = $true + + # Integration tests are run on the DSCSQLTEST instance + $script:sqlServerInstance = 'DSCSQLTEST' +} + +AfterAll { + Remove-Item -Path 'Env:\SqlServerDscCI' -ErrorAction 'SilentlyContinue' + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:moduleName -All | Remove-Module -Force +} + +Describe 'Get-SqlDscAgentOperator' -Tag 'Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022' { + BeforeAll { + # Starting the named instance SQL Server service prior to running tests. + Start-Service -Name 'MSSQL$DSCSQLTEST' -Verbose -ErrorAction 'Stop' + + $mockSqlAdministratorUserName = 'SqlAdmin' # Using computer name as NetBIOS name throw exception. + $mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force + + $script:mockSqlAdminCredential = [System.Management.Automation.PSCredential]::new($mockSqlAdministratorUserName, $mockSqlAdministratorPassword) + + # Connect to the SQL Server instance + $script:sqlServerObject = Connect-SqlDscDatabaseEngine -InstanceName $script:sqlServerInstance -Credential $script:mockSqlAdminCredential -ErrorAction 'Stop' + + # Create test operators for getting + $script:sqlServerObject | New-SqlDscAgentOperator -Name 'IntegrationTest_GetOperator1' -EmailAddress 'operator1@contoso.com' -Force -ErrorAction 'Stop' + $script:sqlServerObject | New-SqlDscAgentOperator -Name 'IntegrationTest_GetOperator2' -EmailAddress 'operator2@contoso.com' -Force -ErrorAction 'Stop' + } + + AfterAll { + $script:sqlServerObject | Remove-SqlDscAgentOperator -Name 'IntegrationTest_GetOperator1' -Force -ErrorAction 'SilentlyContinue' + $script:sqlServerObject | Remove-SqlDscAgentOperator -Name 'IntegrationTest_GetOperator2' -Force -ErrorAction 'SilentlyContinue' + + # Disconnect from the SQL Server + Disconnect-SqlDscDatabaseEngine -ServerObject $script:sqlServerObject + + # Stop the named instance SQL Server service to save memory on the build worker. + Stop-Service -Name 'MSSQL$DSCSQLTEST' -Verbose -ErrorAction 'Stop' + } + + It 'Should get all operators' { + $operators = $script:sqlServerObject | Get-SqlDscAgentOperator -ErrorAction 'Stop' + + $operators | Should -Not -BeNullOrEmpty + @($operators)[0] | Should -BeOfType [Microsoft.SqlServer.Management.Smo.Agent.Operator] + } + + It 'Should get specific operator by name' { + $operator = $script:sqlServerObject | Get-SqlDscAgentOperator -Name 'IntegrationTest_GetOperator1' -ErrorAction 'Stop' + + $operator | Should -Not -BeNullOrEmpty + $operator | Should -BeOfType [Microsoft.SqlServer.Management.Smo.Agent.Operator] + $operator.Name | Should -Be 'IntegrationTest_GetOperator1' + $operator.EmailAddress | Should -Be 'operator1@contoso.com' + } + + It 'Should return nothing when operator does not exist' { + $operator = $script:sqlServerObject | Get-SqlDscAgentOperator -Name 'NonExistentOperator' -ErrorAction 'Stop' + + $operator | Should -BeNullOrEmpty + } + + It 'Should get operator using ServerObject parameter directly' { + $operator = Get-SqlDscAgentOperator -ServerObject $script:sqlServerObject -Name 'IntegrationTest_GetOperator2' -ErrorAction 'Stop' + + $operator | Should -Not -BeNullOrEmpty + $operator | Should -BeOfType [Microsoft.SqlServer.Management.Smo.Agent.Operator] + $operator.Name | Should -Be 'IntegrationTest_GetOperator2' + } +} diff --git a/tests/Integration/Commands/Get-SqlDscConfigurationOption.Integration.Tests.ps1 b/tests/Integration/Commands/Get-SqlDscConfigurationOption.Integration.Tests.ps1 new file mode 100644 index 0000000000..3fd0101fa0 --- /dev/null +++ b/tests/Integration/Commands/Get-SqlDscConfigurationOption.Integration.Tests.ps1 @@ -0,0 +1,149 @@ +[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 build" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' +} + +Describe 'Get-SqlDscConfigurationOption' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { + BeforeAll { + # Starting the named instance SQL Server service prior to running tests. + Start-Service -Name 'MSSQL$DSCSQLTEST' -Verbose -ErrorAction 'Stop' + + $script:mockInstanceName = 'DSCSQLTEST' + + $mockSqlAdministratorUserName = 'SqlAdmin' # Using computer name as NetBIOS name throw exception. + $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 + } + + AfterAll { + Disconnect-SqlDscDatabaseEngine -ServerObject $script:serverObject + + # Stop the named instance SQL Server service to save memory on the build worker. + Stop-Service -Name 'MSSQL$DSCSQLTEST' -Verbose -ErrorAction 'Stop' + } + + Context 'When getting all configuration options' { + It 'Should return an array of configuration option metadata objects' { + $result = Get-SqlDscConfigurationOption -ServerObject $script:serverObject + + <# + Casting to array to ensure we get the count on Windows PowerShell + when there is only one configuration option. + #> + @($result).Count | Should -BeGreaterOrEqual 1 + @($result)[0] | Should -BeOfType 'PSCustomObject' + @($result)[0].PSTypeNames[0] | Should -Be 'SqlDsc.ConfigurationOption' + } + + It 'Should return configuration options with expected properties' { + $result = Get-SqlDscConfigurationOption -ServerObject $script:serverObject + + $firstOption = @($result)[0] + $firstOption.Name | Should -Not -BeNullOrEmpty + $firstOption.PSObject.Properties.Name | Should -Contain 'RunValue' + $firstOption.PSObject.Properties.Name | Should -Contain 'ConfigValue' + $firstOption.PSObject.Properties.Name | Should -Contain 'Minimum' + $firstOption.PSObject.Properties.Name | Should -Contain 'Maximum' + $firstOption.PSObject.Properties.Name | Should -Contain 'IsDynamic' + } + } + + Context 'When getting a specific configuration option' { + It 'Should return the Agent XPs configuration option with expected values' { + $result = Get-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'PSCustomObject' + $result.PSTypeNames[0] | Should -Be 'SqlDsc.ConfigurationOption' + $result.Name | Should -Be 'Agent XPs' + $result.RunValue | Should -Be 0 + $result.ConfigValue | Should -Be 0 + $result.Minimum | Should -Be 0 + $result.Maximum | Should -Be 1 + $result.IsDynamic | Should -BeTrue + } + + It 'Should throw an error when the configuration option does not exist' { + { Get-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'NonExistentOption' -ErrorAction 'Stop' } | + Should -Throw + } + + It 'Should return null when the configuration option does not exist and error action is SilentlyContinue' { + $result = Get-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'NonExistentOption' -ErrorAction 'SilentlyContinue' + + $result | Should -BeNullOrEmpty + } + } + + Context 'When using wildcard patterns' { + It 'Should return configuration options that match the wildcard pattern' { + $result = Get-SqlDscConfigurationOption -ServerObject $script:serverObject -Name '*XP*' + + @($result).Count | Should -BeGreaterOrEqual 1 + $result | Where-Object { $_.Name -eq 'Agent XPs' } | Should -Not -BeNullOrEmpty + } + } + + Context 'When using the Raw parameter' { + It 'Should return raw SMO ConfigProperty objects' { + $result = Get-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' -Raw + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.ConfigProperty' + $result.DisplayName | Should -Be 'Agent XPs' + $result.RunValue | Should -Be 0 + $result.ConfigValue | Should -Be 0 + } + } + + Context 'When using the Refresh parameter' { + It 'Should return the same results with and without Refresh' { + $resultWithoutRefresh = Get-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' + $resultWithRefresh = Get-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' -Refresh + + $resultWithoutRefresh.Name | Should -Be $resultWithRefresh.Name + $resultWithoutRefresh.RunValue | Should -Be $resultWithRefresh.RunValue + $resultWithoutRefresh.ConfigValue | Should -Be $resultWithRefresh.ConfigValue + } + } + + Context 'When using pipeline input' { + It 'Should accept ServerObject from pipeline' { + $result = $script:serverObject | Get-SqlDscConfigurationOption -Name 'Agent XPs' + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType 'PSCustomObject' + $result.Name | Should -Be 'Agent XPs' + $result.RunValue | Should -Be 0 + } + } +} diff --git a/tests/Integration/Commands/New-SqlDscAgentOperator.Integration.Tests.ps1 b/tests/Integration/Commands/New-SqlDscAgentOperator.Integration.Tests.ps1 new file mode 100644 index 0000000000..ad8fbf3a19 --- /dev/null +++ b/tests/Integration/Commands/New-SqlDscAgentOperator.Integration.Tests.ps1 @@ -0,0 +1,124 @@ +[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 build" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' + + $env:SqlServerDscCI = $true + + # Integration tests are run on the DSCSQLTEST instance + $script:sqlServerInstance = 'DSCSQLTEST' +} + +AfterAll { + Remove-Item -Path 'Env:\SqlServerDscCI' -ErrorAction 'SilentlyContinue' + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:moduleName -All | Remove-Module -Force +} + +Describe 'New-SqlDscAgentOperator' -Tag 'Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022' { + BeforeAll { + # Starting the named instance SQL Server service prior to running tests. + Start-Service -Name 'MSSQL$DSCSQLTEST' -Verbose -ErrorAction 'Stop' + + $mockSqlAdministratorUserName = 'SqlAdmin' # Using computer name as NetBIOS name throw exception. + $mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force + + $script:mockSqlAdminCredential = [System.Management.Automation.PSCredential]::new($mockSqlAdministratorUserName, $mockSqlAdministratorPassword) + + # Connect to the SQL Server instance + $script:sqlServerObject = Connect-SqlDscDatabaseEngine -InstanceName $script:sqlServerInstance -Credential $script:mockSqlAdminCredential -ErrorAction 'Stop' + } + + AfterAll { + # Remove temporary test operators (but keep the persistent one) + $script:sqlServerObject | Remove-SqlDscAgentOperator -Name 'IntegrationTest_NewOperator1' -Force -ErrorAction 'SilentlyContinue' + $script:sqlServerObject | Remove-SqlDscAgentOperator -Name 'IntegrationTest_NewOperator2' -Force -ErrorAction 'SilentlyContinue' + $script:sqlServerObject | Remove-SqlDscAgentOperator -Name 'IntegrationTest_NewOperator3' -Force -ErrorAction 'SilentlyContinue' + $script:sqlServerObject | Remove-SqlDscAgentOperator -Name 'IntegrationTest_NewOperator10' -Force -ErrorAction 'SilentlyContinue' + + # Disconnect from the SQL Server + Disconnect-SqlDscDatabaseEngine -ServerObject $script:sqlServerObject + + # Stop the named instance SQL Server service to save memory on the build worker. + Stop-Service -Name 'MSSQL$DSCSQLTEST' -Verbose -ErrorAction 'Stop' + } + + It 'Should create the persistent operator for other tests to use' { + # Create the persistent operator with comprehensive properties + $script:sqlServerObject | New-SqlDscAgentOperator -Name 'SqlDscIntegrationTestOperator_Persistent' -EmailAddress 'persistent@example.com' -NetSendAddress 'SERVER\User' -PagerAddress '555-0123' -Force -ErrorAction 'Stop' + + # Verify the operator was created + $operator = $script:sqlServerObject | Get-SqlDscAgentOperator -Name 'SqlDscIntegrationTestOperator_Persistent' + $operator | Should -Not -BeNullOrEmpty + $operator.Name | Should -Be 'SqlDscIntegrationTestOperator_Persistent' + $operator.EmailAddress | Should -Be 'persistent@example.com' + $operator.NetSendAddress | Should -Be 'SERVER\User' + $operator.PagerAddress | Should -Be '555-0123' + } + + It 'Should create a new operator with email address' { + $script:sqlServerObject | New-SqlDscAgentOperator -Name 'IntegrationTest_NewOperator2' -EmailAddress 'operator2@contoso.com' -Force -ErrorAction 'Stop' + + # Verify the operator was created + $operator = $script:sqlServerObject | Get-SqlDscAgentOperator -Name 'IntegrationTest_NewOperator2' + $operator | Should -Not -BeNullOrEmpty + $operator.Name | Should -Be 'IntegrationTest_NewOperator2' + $operator.EmailAddress | Should -Be 'operator2@contoso.com' + } + + It 'Should create a new operator and return the object with PassThru' { + $operator = $script:sqlServerObject | New-SqlDscAgentOperator -Name 'IntegrationTest_NewOperator3' -EmailAddress 'operator3@contoso.com' -PassThru -Force -ErrorAction 'Stop' + + $operator | Should -Not -BeNullOrEmpty + $operator | Should -BeOfType [Microsoft.SqlServer.Management.Smo.Agent.Operator] + $operator.Name | Should -Be 'IntegrationTest_NewOperator3' + $operator.EmailAddress | Should -Be 'operator3@contoso.com' + } + + It 'Should create a new operator using ServerObject parameter directly' { + # Remove operator first if it exists + $script:sqlServerObject | Remove-SqlDscAgentOperator -Name 'IntegrationTest_NewOperator1' -Force -ErrorAction 'SilentlyContinue' + + New-SqlDscAgentOperator -ServerObject $script:sqlServerObject -Name 'IntegrationTest_NewOperator1' -EmailAddress 'operator3@contoso.com' -Force -ErrorAction 'Stop' + + # Verify the operator was created + $operator = $script:sqlServerObject | Get-SqlDscAgentOperator -Name 'IntegrationTest_NewOperator1' + $operator | Should -Not -BeNullOrEmpty + $operator.Name | Should -Be 'IntegrationTest_NewOperator1' + } + + It 'Should throw an error when trying to create an operator that already exists' { + # Create the operator first + $script:sqlServerObject | New-SqlDscAgentOperator -Name 'IntegrationTest_NewOperator10' -EmailAddress 'operator10@contoso.com' -Force -ErrorAction 'Stop' + + # Try to create the same operator again + { $script:sqlServerObject | New-SqlDscAgentOperator -Name 'IntegrationTest_NewOperator10' -ErrorAction 'Stop' } | + Should -Throw + } +} diff --git a/tests/Integration/Commands/README.md b/tests/Integration/Commands/README.md index 26710b6fd2..4c56a43123 100644 --- a/tests/Integration/Commands/README.md +++ b/tests/Integration/Commands/README.md @@ -46,6 +46,8 @@ Connect-SqlDscDatabaseEngine | 1 | 0 (Prerequisites) | DSCSQLTEST | - Assert-SqlDscLogin | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - New-SqlDscLogin | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | IntegrationTestSqlLogin, SqlIntegrationTestGroup login Get-SqlDscLogin | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - +Get-SqlDscConfigurationOption | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - +Set-SqlDscConfigurationOption | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - Disable-SqlDscLogin | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - Enable-SqlDscLogin | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - Test-SqlDscIsLoginEnabled | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - @@ -64,7 +66,15 @@ Get-SqlDscAgentAlert | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLT New-SqlDscAgentAlert | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | Test alerts Set-SqlDscAgentAlert | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - Test-SqlDscAgentAlert | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - +Get-SqlDscAgentOperator | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - +New-SqlDscAgentOperator | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | SqlDscIntegrationTestOperator_Persistent operator +Set-SqlDscAgentOperator | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - +Test-SqlDscIsAgentOperator | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - +Assert-SqlDscAgentOperator | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - +Enable-SqlDscAgentOperator | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - +Disable-SqlDscAgentOperator | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - Remove-SqlDscAgentAlert | 8 | 2 (New-SqlDscAgentAlert) | DSCSQLTEST | - +Remove-SqlDscAgentOperator | 8 | 2 (New-SqlDscAgentOperator) | DSCSQLTEST | - Remove-SqlDscDatabase | 8 | 2 (New-SqlDscDatabase) | DSCSQLTEST | - Remove-SqlDscRole | 8 | 2 (New-SqlDscRole) | DSCSQLTEST | - Remove-SqlDscLogin | 8 | 2 (New-SqlDscLogin) | DSCSQLTEST | - @@ -111,6 +121,11 @@ Grants `CreateEndpoint` permission to the role `SqlDscIntegrationTestRole_Persis Creates a persistent `AlterTrace` denial on the persistent principals `IntegrationTestSqlLogin` that remains for other tests to validate against. +### `New-SqlDscAgentOperator` + +Creates a persistent agent operator `SqlDscIntegrationTestOperator_Persistent` +that remains on the instance for other tests to use. + ## Dependencies ### SqlServer module diff --git a/tests/Integration/Commands/Remove-SqlDscAgentOperator.Integration.Tests.ps1 b/tests/Integration/Commands/Remove-SqlDscAgentOperator.Integration.Tests.ps1 new file mode 100644 index 0000000000..960819462e --- /dev/null +++ b/tests/Integration/Commands/Remove-SqlDscAgentOperator.Integration.Tests.ps1 @@ -0,0 +1,98 @@ +[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 build" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' + + $env:SqlServerDscCI = $true + + # Integration tests are run on the DSCSQLTEST instance + $script:sqlServerInstance = 'DSCSQLTEST' +} + +AfterAll { + Remove-Item -Path 'Env:\SqlServerDscCI' -ErrorAction 'SilentlyContinue' + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:moduleName -All | Remove-Module -Force +} + +Describe 'Remove-SqlDscAgentOperator' -Tag 'Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022' { + BeforeAll { + # Starting the named instance SQL Server service prior to running tests. + Start-Service -Name 'MSSQL$DSCSQLTEST' -Verbose -ErrorAction 'Stop' + + $mockSqlAdministratorUserName = 'SqlAdmin' # Using computer name as NetBIOS name throw exception. + $mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force + $mockSqlAdministratorCredential = [System.Management.Automation.PSCredential]::new($mockSqlAdministratorUserName, $mockSqlAdministratorPassword) + + $script:serverObject = Connect-SqlDscDatabaseEngine -InstanceName $script:sqlServerInstance -Credential $mockSqlAdministratorCredential -ErrorAction Stop + } + + AfterAll { + Disconnect-SqlDscDatabaseEngine -ServerObject $script:serverObject + + # Stopping the named instance SQL Server service after running tests. + Stop-Service -Name 'MSSQL$DSCSQLTEST' -Verbose -ErrorAction 'Stop' + } + + Context 'When removing test agent operators' { + It 'Should create and remove a temporary operator' { + # Create a temporary operator for removal testing + $tempOperatorName = 'SqlDscTempOperator_ToRemove' + $null = New-SqlDscAgentOperator -ServerObject $script:serverObject -Name $tempOperatorName -EmailAddress 'temp@example.com' -Force -ErrorAction 'Stop' + + # Verify it was created + $operatorExists = Test-SqlDscIsAgentOperator -ServerObject $script:serverObject -Name $tempOperatorName -ErrorAction 'Stop' + $operatorExists | Should -BeTrue + + # Remove the operator + $null = Remove-SqlDscAgentOperator -ServerObject $script:serverObject -Name $tempOperatorName -Force -ErrorAction 'Stop' + + # Verify it was removed + $operatorExists = Test-SqlDscIsAgentOperator -ServerObject $script:serverObject -Name $tempOperatorName -ErrorAction 'Stop' + $operatorExists | Should -BeFalse + } + + It 'Should remove operator using OperatorObject parameter set' { + # Create a temporary operator + $tempOperatorName = 'SqlDscTempOperator_ByObject' + $null = New-SqlDscAgentOperator -ServerObject $script:serverObject -Name $tempOperatorName -EmailAddress 'temp2@example.com' -Force -ErrorAction 'Stop' + + # Get the operator object + $operatorObject = Get-SqlDscAgentOperator -ServerObject $script:serverObject -Name $tempOperatorName -ErrorAction 'Stop' + $operatorObject | Should -Not -BeNullOrEmpty + + # Remove using OperatorObject parameter set + $null = Remove-SqlDscAgentOperator -OperatorObject $operatorObject -Force -ErrorAction 'Stop' + + # Verify it was removed + $operatorExists = Test-SqlDscIsAgentOperator -ServerObject $script:serverObject -Name $tempOperatorName -ErrorAction 'Stop' + $operatorExists | Should -BeFalse + } + } +} diff --git a/tests/Integration/Commands/Set-SqlDscAgentAlert.Integration.Tests.ps1 b/tests/Integration/Commands/Set-SqlDscAgentAlert.Integration.Tests.ps1 index 2731f95b18..560bf2ce64 100644 --- a/tests/Integration/Commands/Set-SqlDscAgentAlert.Integration.Tests.ps1 +++ b/tests/Integration/Commands/Set-SqlDscAgentAlert.Integration.Tests.ps1 @@ -60,7 +60,7 @@ Describe 'Set-SqlDscAgentAlert' -Tag @('Integration_SQL2017', 'Integration_SQL20 } It 'Should update alert severity using ServerObject parameter set' { - $null = $script:sqlServerObject | Set-SqlDscAgentAlert -Name 'IntegrationTest_UpdateAlert' -Severity 16 -ErrorAction Stop + $null = $script:sqlServerObject | Set-SqlDscAgentAlert -Name 'IntegrationTest_UpdateAlert' -Severity 16 -ErrorAction 'Stop' -Force $alert = $script:sqlServerObject | Get-SqlDscAgentAlert -Name 'IntegrationTest_UpdateAlert' $alert.Severity | Should -Be 16 @@ -81,7 +81,7 @@ END $script:sqlServerObject | Invoke-SqlDscQuery -DatabaseName 'master' -Query $addMessageQuery -Verbose -Force -ErrorAction 'Stop' - $null = $script:sqlServerObject | Set-SqlDscAgentAlert -Name 'IntegrationTest_UpdateAlert' -MessageId 50003 -ErrorAction Stop + $null = $script:sqlServerObject | Set-SqlDscAgentAlert -Name 'IntegrationTest_UpdateAlert' -MessageId 50003 -ErrorAction 'Stop' -Force $alert = $script:sqlServerObject | Get-SqlDscAgentAlert -Name 'IntegrationTest_UpdateAlert' $alert.MessageId | Should -Be 50003 @@ -90,11 +90,11 @@ END It 'Should update alert using AlertObject parameter set' { # First reset to severity for this test - $script:sqlServerObject | Set-SqlDscAgentAlert -Name 'IntegrationTest_UpdateAlert' -Severity 14 -ErrorAction Stop + $script:sqlServerObject | Set-SqlDscAgentAlert -Name 'IntegrationTest_UpdateAlert' -Severity 14 -ErrorAction 'Stop' -Force $alert = $script:sqlServerObject | Get-SqlDscAgentAlert -Name 'IntegrationTest_UpdateAlert' - $null = $alert | Set-SqlDscAgentAlert -Severity 18 -ErrorAction Stop + $null = $alert | Set-SqlDscAgentAlert -Severity 18 -ErrorAction 'Stop' -Force # Refresh the alert to get updated values $updatedAlert = $script:sqlServerObject | Get-SqlDscAgentAlert -Name 'IntegrationTest_UpdateAlert' @@ -103,7 +103,7 @@ END } It 'Should return updated alert when PassThru is specified' { - $result = $script:sqlServerObject | Set-SqlDscAgentAlert -Name 'IntegrationTest_UpdateAlert' -Severity 20 -PassThru -ErrorAction Stop + $result = $script:sqlServerObject | Set-SqlDscAgentAlert -Name 'IntegrationTest_UpdateAlert' -Severity 20 -PassThru -ErrorAction 'Stop' -Force $result | Should -Not -BeNullOrEmpty $result.Name | Should -Be 'IntegrationTest_UpdateAlert' @@ -111,12 +111,12 @@ END } It 'Should throw error when alert does not exist' { - { $script:sqlServerObject | Set-SqlDscAgentAlert -Name 'NonExistentAlert' -Severity 16 } | + { $script:sqlServerObject | Set-SqlDscAgentAlert -Name 'NonExistentAlert' -Severity 16 -Force -ErrorAction 'Stop' } | Should -Throw } It 'Should throw error when both Severity and MessageId are specified' { - { $script:sqlServerObject | Set-SqlDscAgentAlert -Name 'IntegrationTest_UpdateAlert' -Severity 16 -MessageId 50001 } | + { $script:sqlServerObject | Set-SqlDscAgentAlert -Name 'IntegrationTest_UpdateAlert' -Severity 16 -MessageId 50001 -Force -ErrorAction 'Stop' } | Should -Throw } } diff --git a/tests/Integration/Commands/Set-SqlDscAgentOperator.Integration.Tests.ps1 b/tests/Integration/Commands/Set-SqlDscAgentOperator.Integration.Tests.ps1 new file mode 100644 index 0000000000..30565bea19 --- /dev/null +++ b/tests/Integration/Commands/Set-SqlDscAgentOperator.Integration.Tests.ps1 @@ -0,0 +1,115 @@ +[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 build" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' + + $env:SqlServerDscCI = $true + + # Integration tests are run on the DSCSQLTEST instance + $script:sqlServerInstance = 'DSCSQLTEST' +} + +AfterAll { + Remove-Item -Path 'Env:\SqlServerDscCI' -ErrorAction 'SilentlyContinue' + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:moduleName -All | Remove-Module -Force +} + +Describe 'Set-SqlDscAgentOperator' -Tag 'Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022' { + BeforeAll { + # Starting the named instance SQL Server service prior to running tests. + Start-Service -Name 'MSSQL$DSCSQLTEST' -Verbose -ErrorAction 'Stop' + + $mockSqlAdministratorUserName = 'SqlAdmin' # Using computer name as NetBIOS name throw exception. + $mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force + $mockSqlAdministratorCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $mockSqlAdministratorUserName, $mockSqlAdministratorPassword + + $script:serverObject = Connect-SqlDscDatabaseEngine -InstanceName $script:sqlServerInstance -Credential $mockSqlAdministratorCredential -ErrorAction 'Stop' + + # Enable Agent XPs component for SQL Server Agent functionality + Set-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' -Value 1 -Force -Verbose -ErrorAction 'Stop' + } + + AfterAll { + # Disable Agent XPs component to clean up test environment + Set-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' -Value 0 -Force -Verbose -ErrorAction 'Stop' + + Disconnect-SqlDscDatabaseEngine -ServerObject $script:serverObject + + # Stopping the named instance SQL Server service after running tests. + Stop-Service -Name 'MSSQL$DSCSQLTEST' -Verbose -ErrorAction 'Stop' + } + + Context 'When updating an existing agent operator' { + It 'Should update the email address' { + # Get the persistent operator created by New-SqlDscAgentOperator integration test + $operatorObject = Get-SqlDscAgentOperator -ServerObject $script:serverObject -Name 'SqlDscIntegrationTestOperator_Persistent' -ErrorAction 'Stop' + $operatorObject | Should -Not -BeNullOrEmpty + + # Update the email address + $newEmailAddress = 'updated@example.com' + $null = Set-SqlDscAgentOperator -OperatorObject $operatorObject -EmailAddress $newEmailAddress -Force -ErrorAction 'Stop' + + # Verify the email address was updated + $operatorObject.Refresh() + $operatorObject.EmailAddress | Should -Be $newEmailAddress + } + + It 'Should update multiple properties' { + # Get the persistent operator + $operatorObject = Get-SqlDscAgentOperator -ServerObject $script:serverObject -Name 'SqlDscIntegrationTestOperator_Persistent' -ErrorAction 'Stop' + + # Update multiple properties + $newEmailAddress = 'multi@example.com' + $newNetSendAddress = 'COMPUTER\User' + $newPagerAddress = '555-1234' + + $null = Set-SqlDscAgentOperator -OperatorObject $operatorObject -EmailAddress $newEmailAddress -NetSendAddress $newNetSendAddress -PagerAddress $newPagerAddress -Force -ErrorAction 'Stop' + + # Verify all properties were updated + $operatorObject.Refresh() + $operatorObject.EmailAddress | Should -Be $newEmailAddress + $operatorObject.NetSendAddress | Should -Be $newNetSendAddress + $operatorObject.PagerAddress | Should -Be $newPagerAddress + } + } + + Context 'When using ServerObject parameter set' { + It 'Should update an operator using ServerObject and Name parameters' { + $newEmailAddress = 'serverset@example.com' + + # Update using ServerObject parameter set + $null = Set-SqlDscAgentOperator -ServerObject $script:serverObject -Name 'SqlDscIntegrationTestOperator_Persistent' -EmailAddress $newEmailAddress -Force -ErrorAction 'Stop' + + # Verify the email address was updated + $operatorObject = Get-SqlDscAgentOperator -ServerObject $script:serverObject -Name 'SqlDscIntegrationTestOperator_Persistent' -ErrorAction 'Stop' + $operatorObject.EmailAddress | Should -Be $newEmailAddress + } + } +} diff --git a/tests/Integration/Commands/Set-SqlDscConfigurationOption.Integration.Tests.ps1 b/tests/Integration/Commands/Set-SqlDscConfigurationOption.Integration.Tests.ps1 new file mode 100644 index 0000000000..c9e4041b45 --- /dev/null +++ b/tests/Integration/Commands/Set-SqlDscConfigurationOption.Integration.Tests.ps1 @@ -0,0 +1,150 @@ +[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 build" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' +} + +Describe 'Set-SqlDscConfigurationOption' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { + BeforeAll { + # Starting the named instance SQL Server service prior to running tests. + Start-Service -Name 'MSSQL$DSCSQLTEST' -Verbose -ErrorAction 'Stop' + + $script:mockInstanceName = 'DSCSQLTEST' + + $mockSqlAdministratorUserName = 'SqlAdmin' # Using computer name as NetBIOS name throw exception. + $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 + + # Store the original value of Agent XPs to restore it later + $script:originalAgentXPsValue = Get-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' + } + + AfterAll { + # Restore the original Agent XPs value + if ($script:originalAgentXPsValue) + { + Set-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' -Value $script:originalAgentXPsValue.RunValue -Force -ErrorAction 'SilentlyContinue' + } + + Disconnect-SqlDscDatabaseEngine -ServerObject $script:serverObject + + # Stop the named instance SQL Server service to save memory on the build worker. + Stop-Service -Name 'MSSQL$DSCSQLTEST' -Verbose -ErrorAction 'Stop' + } + + Context 'When setting Agent XPs configuration option' { + It 'Should set Agent XPs from 0 to 1 and verify the change' { + # First, ensure Agent XPs is set to 0 + Set-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' -Value 0 -Force -ErrorAction 'Stop' + + # Verify it's set to 0 + $result = Get-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' + $result.RunValue | Should -Be 0 + $result.ConfigValue | Should -Be 0 + + # Now set it to 1 + Set-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' -Value 1 -Force -ErrorAction 'Stop' + + # Verify it's set to 1 + $result = Get-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' + $result.RunValue | Should -Be 1 + $result.ConfigValue | Should -Be 1 + } + + It 'Should set Agent XPs from 1 back to 0 and verify the change' { + # Ensure Agent XPs is set to 1 first + Set-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' -Value 1 -Force -ErrorAction 'Stop' + + # Verify it's set to 1 + $result = Get-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' + $result.RunValue | Should -Be 1 + $result.ConfigValue | Should -Be 1 + + # Now set it back to 0 + Set-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' -Value 0 -Force -ErrorAction 'Stop' + + # Verify it's set to 0 + $result = Get-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' + $result.RunValue | Should -Be 0 + $result.ConfigValue | Should -Be 0 + } + + It 'Should throw an error when setting an invalid value for Agent XPs' { + { Set-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' -Value 2 -Force -ErrorAction 'Stop' } | + Should -Throw + } + + It 'Should throw an error when setting a negative value for Agent XPs' { + { Set-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' -Value -1 -Force -ErrorAction 'Stop' } | + Should -Throw + } + + It 'Should throw an error when the configuration option does not exist' { + { Set-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'NonExistentOption' -Value 1 -Force -ErrorAction 'Stop' } | + Should -Throw + } + } + + Context 'When using ShouldProcess with WhatIf' { + It 'Should not actually change the value when using WhatIf' { + # Get current value + $originalValue = Get-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' + + # Use WhatIf to simulate setting a different value + $newValue = if ($originalValue.RunValue -eq 0) { 1 } else { 0 } + Set-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' -Value $newValue -WhatIf + + # Verify the value hasn't changed + $currentValue = Get-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' + $currentValue.RunValue | Should -Be $originalValue.RunValue + $currentValue.ConfigValue | Should -Be $originalValue.ConfigValue + } + } + + Context 'When using pipeline input' { + It 'Should accept ServerObject from pipeline' { + # Get current value + $originalValue = Get-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' + + # Set to opposite value using pipeline + $newValue = if ($originalValue.RunValue -eq 0) { 1 } else { 0 } + $script:serverObject | Set-SqlDscConfigurationOption -Name 'Agent XPs' -Value $newValue -Force -ErrorAction 'Stop' + + # Verify the change + $result = Get-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' + $result.RunValue | Should -Be $newValue + $result.ConfigValue | Should -Be $newValue + + # Set back to original value + $script:serverObject | Set-SqlDscConfigurationOption -Name 'Agent XPs' -Value $originalValue.RunValue -Force -ErrorAction 'Stop' + } + } +} diff --git a/tests/Integration/Commands/Test-SqlDscConfigurationOption.Integration.Tests.ps1 b/tests/Integration/Commands/Test-SqlDscConfigurationOption.Integration.Tests.ps1 new file mode 100644 index 0000000000..c1a9e75ebf --- /dev/null +++ b/tests/Integration/Commands/Test-SqlDscConfigurationOption.Integration.Tests.ps1 @@ -0,0 +1,180 @@ +[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 build" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' +} + +Describe 'Test-SqlDscConfigurationOption' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { + BeforeAll { + # Starting the named instance SQL Server service prior to running tests. + Start-Service -Name 'MSSQL$DSCSQLTEST' -Verbose -ErrorAction 'Stop' + + $script:mockInstanceName = 'DSCSQLTEST' + + $mockSqlAdministratorUserName = 'SqlAdmin' # Using computer name as NetBIOS name throw exception. + $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 + + # Store the original value of Agent XPs to restore it later + $script:originalAgentXPsValue = Get-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' + } + + AfterAll { + # Restore the original Agent XPs value + if ($script:originalAgentXPsValue) + { + Set-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' -Value $script:originalAgentXPsValue.RunValue -Force -ErrorAction 'SilentlyContinue' + } + + Disconnect-SqlDscDatabaseEngine -ServerObject $script:serverObject + + # Stop the named instance SQL Server service to save memory on the build worker. + Stop-Service -Name 'MSSQL$DSCSQLTEST' -Verbose -ErrorAction 'Stop' + } + + Context 'When testing Agent XPs configuration option' { + It 'Should return true when testing current value' { + # Get current value + $currentValue = Get-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' + + # Test should return true for current value + $result = Test-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' -Value $currentValue.RunValue + $result | Should -BeTrue + } + + It 'Should return false when testing incorrect value' { + # Get current value + $currentValue = Get-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' + + # Test opposite value - should return false + $oppositeValue = if ($currentValue.RunValue -eq 0) { 1 } else { 0 } + $result = Test-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' -Value $oppositeValue + $result | Should -BeFalse + } + + It 'Should return true after setting value and testing' { + # Set Agent XPs to 1 + Set-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' -Value 1 -Force + + # Test should return true for the value we just set + $result = Test-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' -Value 1 + $result | Should -BeTrue + + # Test should return false for the opposite value + $result = Test-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' -Value 0 + $result | Should -BeFalse + } + + It 'Should return true after setting different value and testing' { + # Set Agent XPs to 0 + Set-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' -Value 0 -Force + + # Test should return true for the value we just set + $result = Test-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' -Value 0 + $result | Should -BeTrue + + # Test should return false for the opposite value + $result = Test-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' -Value 1 + $result | Should -BeFalse + } + + It 'Should throw an error when the configuration option does not exist' { + { Test-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'NonExistentOption' -Value 1 -ErrorAction 'Stop' } | + Should -Throw + } + } + + Context 'When testing different configuration options' { + It 'Should correctly test cost threshold for parallelism option' { + # Get current value + $currentValue = Get-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'cost threshold for parallelism' + + # Test should return true for current value + $result = Test-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'cost threshold for parallelism' -Value $currentValue.RunValue + $result | Should -BeTrue + + # Test should return false for a different value (if current is not 99, test 99, otherwise test 50) + $differentValue = if ($currentValue.RunValue -ne 99) { 99 } else { 50 } + $result = Test-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'cost threshold for parallelism' -Value $differentValue + $result | Should -BeFalse + } + + It 'Should correctly test max degree of parallelism option' { + # Get current value + $currentValue = Get-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'max degree of parallelism' + + # Test should return true for current value + $result = Test-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'max degree of parallelism' -Value $currentValue.RunValue + $result | Should -BeTrue + + # Test should return false for a different value (if current is not 8, test 8, otherwise test 4) + $differentValue = if ($currentValue.RunValue -ne 8) { 8 } else { 4 } + $result = Test-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'max degree of parallelism' -Value $differentValue + $result | Should -BeFalse + } + } + + Context 'When using pipeline input' { + It 'Should accept ServerObject from pipeline' { + # Get current value + $currentValue = Get-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' + + # Test using pipeline should return true for current value + $result = $script:serverObject | Test-SqlDscConfigurationOption -Name 'Agent XPs' -Value $currentValue.RunValue + $result | Should -BeTrue + + # Test using pipeline should return false for opposite value + $oppositeValue = if ($currentValue.RunValue -eq 0) { 1 } else { 0 } + $result = $script:serverObject | Test-SqlDscConfigurationOption -Name 'Agent XPs' -Value $oppositeValue + $result | Should -BeFalse + } + } + + Context 'When testing boundary values' { + It 'Should correctly test minimum boundary value for Agent XPs' { + # Set to minimum value (0) + Set-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' -Value 0 -Force + + # Test minimum value + $result = Test-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' -Value 0 + $result | Should -BeTrue + } + + It 'Should correctly test maximum boundary value for Agent XPs' { + # Set to maximum value (1) + Set-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' -Value 1 -Force + + # Test maximum value + $result = Test-SqlDscConfigurationOption -ServerObject $script:serverObject -Name 'Agent XPs' -Value 1 + $result | Should -BeTrue + } + } +} diff --git a/tests/Integration/Commands/Test-SqlDscIsAgentOperator.Integration.Tests.ps1 b/tests/Integration/Commands/Test-SqlDscIsAgentOperator.Integration.Tests.ps1 new file mode 100644 index 0000000000..a781fb2089 --- /dev/null +++ b/tests/Integration/Commands/Test-SqlDscIsAgentOperator.Integration.Tests.ps1 @@ -0,0 +1,83 @@ +[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 build" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' + + $env:SqlServerDscCI = $true + + # Integration tests are run on the DSCSQLTEST instance + $script:sqlServerInstance = 'DSCSQLTEST' +} + +AfterAll { + $env:SqlServerDscCI = $null + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:moduleName -All | Remove-Module -Force +} + +Describe 'Test-SqlDscIsAgentOperator' -Tag 'Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022' { + BeforeAll { + # Starting the named instance SQL Server service prior to running tests. + Start-Service -Name 'MSSQL$DSCSQLTEST' -Verbose -ErrorAction 'Stop' + + $mockSqlAdministratorUserName = 'SqlAdmin' # Using computer name as NetBIOS name throw exception. + $mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force + $mockSqlAdministratorCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $mockSqlAdministratorUserName, $mockSqlAdministratorPassword + + $script:serverObject = Connect-SqlDscDatabaseEngine -InstanceName $script:sqlServerInstance -Credential $mockSqlAdministratorCredential -ErrorAction 'Stop' + } + + AfterAll { + Disconnect-SqlDscDatabaseEngine -ServerObject $script:serverObject + + # Stopping the named instance SQL Server service after running tests. + Stop-Service -Name 'MSSQL$DSCSQLTEST' -Verbose -ErrorAction 'Stop' + } + + Context 'When testing for an existing agent operator' { + It 'Should return true for existing operator' { + # Test for the persistent operator created by New-SqlDscAgentOperator integration test + $result = Test-SqlDscIsAgentOperator -ServerObject $script:serverObject -Name 'SqlDscIntegrationTestOperator_Persistent' -ErrorAction 'Stop' + $result | Should -BeTrue + } + + It 'Should return false for non-existing operator' { + # Test for a non-existing operator + $result = Test-SqlDscIsAgentOperator -ServerObject $script:serverObject -Name 'NonExistentOperator' -ErrorAction 'Stop' + $result | Should -BeFalse + } + } + + Context 'When using pipeline input' { + It 'Should accept ServerObject from pipeline' { + $result = $script:serverObject | Test-SqlDscIsAgentOperator -Name 'SqlDscIntegrationTestOperator_Persistent' -ErrorAction 'Stop' + $result | Should -BeTrue + } + } +} diff --git a/tests/Integration/Resources/DSC_SqlLogin.Integration.Tests.ps1 b/tests/Integration/Resources/DSC_SqlLogin.Integration.Tests.ps1 index 8ea25912f9..6990a30d88 100644 --- a/tests/Integration/Resources/DSC_SqlLogin.Integration.Tests.ps1 +++ b/tests/Integration/Resources/DSC_SqlLogin.Integration.Tests.ps1 @@ -144,7 +144,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Ensure | Should -Be 'Present' $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.DscUser1Name $resourceCurrentState.LoginType | Should -Be $ConfigurationData.AllNodes.DscUser1Type - $resourceCurrentState.Disabled | Should -Be $false + $resourceCurrentState.Disabled | Should -BeFalse } It 'Should return $true when Test-DscConfiguration is run' { @@ -202,7 +202,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.DscUser2Name $resourceCurrentState.LoginType | Should -Be $ConfigurationData.AllNodes.DscUser2Type $resourceCurrentState.DefaultDatabase | Should -Be $ConfigurationData.AllNodes.DefaultDbName - $resourceCurrentState.Disabled | Should -Be $false + $resourceCurrentState.Disabled | Should -BeFalse } It 'Should return $true when Test-DscConfiguration is run' { @@ -259,7 +259,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Ensure | Should -Be 'Present' $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.DscUser3Name $resourceCurrentState.LoginType | Should -Be $ConfigurationData.AllNodes.DscUser3Type - $resourceCurrentState.Disabled | Should -Be $true + $resourceCurrentState.Disabled | Should -BeTrue } It 'Should return $true when Test-DscConfiguration is run' { @@ -316,10 +316,10 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Ensure | Should -Be 'Present' $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.DscUser4Name $resourceCurrentState.LoginType | Should -Be $ConfigurationData.AllNodes.DscUser4Type - $resourceCurrentState.Disabled | Should -Be $false - $resourceCurrentState.LoginMustChangePassword | Should -Be $false - $resourceCurrentState.LoginPasswordExpirationEnabled | Should -Be $true - $resourceCurrentState.LoginPasswordPolicyEnforced | Should -Be $true + $resourceCurrentState.Disabled | Should -BeFalse + $resourceCurrentState.LoginMustChangePassword | Should -BeFalse + $resourceCurrentState.LoginPasswordExpirationEnabled | Should -BeTrue + $resourceCurrentState.LoginPasswordPolicyEnforced | Should -BeTrue } It 'Should return $true when Test-DscConfiguration is run' { @@ -422,10 +422,10 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Ensure | Should -Be 'Present' $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.DscUser4Name $resourceCurrentState.LoginType | Should -Be $ConfigurationData.AllNodes.DscUser4Type - $resourceCurrentState.Disabled | Should -Be $false - $resourceCurrentState.LoginMustChangePassword | Should -Be $false # Left the same as this cannot be updated - $resourceCurrentState.LoginPasswordExpirationEnabled | Should -Be $false - $resourceCurrentState.LoginPasswordPolicyEnforced | Should -Be $false + $resourceCurrentState.Disabled | Should -BeFalse + $resourceCurrentState.LoginMustChangePassword | Should -BeFalse # Left the same as this cannot be updated + $resourceCurrentState.LoginPasswordExpirationEnabled | Should -BeFalse + $resourceCurrentState.LoginPasswordPolicyEnforced | Should -BeFalse } It 'Should return $true when Test-DscConfiguration is run' { @@ -593,7 +593,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Ensure | Should -Be 'Present' $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.DscUser5Name $resourceCurrentState.LoginType | Should -Be $ConfigurationData.AllNodes.DscUser5Type - $resourceCurrentState.Disabled | Should -Be $false + $resourceCurrentState.Disabled | Should -BeFalse $resourceCurrentState.LoginMustChangePassword | Should -BeFalse $resourceCurrentState.LoginPasswordExpirationEnabled | Should -BeFalse $resourceCurrentState.LoginPasswordPolicyEnforced | Should -BeFalse @@ -697,7 +697,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Ensure | Should -Be 'Present' $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.DscUser5Name $resourceCurrentState.LoginType | Should -Be $ConfigurationData.AllNodes.DscUser5Type - $resourceCurrentState.Disabled | Should -Be $false + $resourceCurrentState.Disabled | Should -BeFalse $resourceCurrentState.LoginMustChangePassword | Should -BeFalse $resourceCurrentState.LoginPasswordExpirationEnabled | Should -BeFalse $resourceCurrentState.LoginPasswordPolicyEnforced | Should -BeTrue @@ -841,7 +841,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Ensure | Should -Be 'Present' $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.DscUser5Name $resourceCurrentState.LoginType | Should -Be $ConfigurationData.AllNodes.DscUser5Type - $resourceCurrentState.Disabled | Should -Be $false + $resourceCurrentState.Disabled | Should -BeFalse $resourceCurrentState.LoginMustChangePassword | Should -BeFalse $resourceCurrentState.LoginPasswordExpirationEnabled | Should -BeTrue $resourceCurrentState.LoginPasswordPolicyEnforced | Should -BeTrue @@ -901,7 +901,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Ensure | Should -Be 'Present' $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.DscUser5Name $resourceCurrentState.LoginType | Should -Be $ConfigurationData.AllNodes.DscUser5Type - $resourceCurrentState.Disabled | Should -Be $false + $resourceCurrentState.Disabled | Should -BeFalse $resourceCurrentState.LoginMustChangePassword | Should -BeFalse $resourceCurrentState.LoginPasswordExpirationEnabled | Should -BeFalse # This was set to true by the previous test. @@ -962,7 +962,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Ensure | Should -Be 'Present' $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.DscUser5Name $resourceCurrentState.LoginType | Should -Be $ConfigurationData.AllNodes.DscUser5Type - $resourceCurrentState.Disabled | Should -Be $false + $resourceCurrentState.Disabled | Should -BeFalse $resourceCurrentState.LoginMustChangePassword | Should -BeFalse $resourceCurrentState.LoginPasswordExpirationEnabled | Should -BeFalse $resourceCurrentState.LoginPasswordPolicyEnforced | Should -BeFalse @@ -1022,7 +1022,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Ensure | Should -Be 'Present' $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.DscSqlUsers1Name $resourceCurrentState.LoginType | Should -Be $ConfigurationData.AllNodes.DscSqlUsers1Type - $resourceCurrentState.Disabled | Should -Be $false + $resourceCurrentState.Disabled | Should -BeFalse } It 'Should return $true when Test-DscConfiguration is run' { @@ -1202,3 +1202,4 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', } } } + diff --git a/tests/Integration/Resources/DSC_SqlRS.Integration.Tests.ps1 b/tests/Integration/Resources/DSC_SqlRS.Integration.Tests.ps1 index 9989168c1f..02abeb7451 100644 --- a/tests/Integration/Resources/DSC_SqlRS.Integration.Tests.ps1 +++ b/tests/Integration/Resources/DSC_SqlRS.Integration.Tests.ps1 @@ -168,8 +168,8 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName $resourceCurrentState.DatabaseServerName | Should -Be $ConfigurationData.AllNodes.DatabaseServerName $resourceCurrentState.DatabaseInstanceName | Should -Be $ConfigurationData.AllNodes.DatabaseInstanceName - $resourceCurrentState.IsInitialized | Should -Be $true - $resourceCurrentState.UseSsl | Should -Be $false + $resourceCurrentState.IsInitialized | Should -BeTrue + $resourceCurrentState.UseSsl | Should -BeFalse $resourceCurrentState.ReportServerReservedUrl | Should -Contain 'http://+:80' } @@ -276,3 +276,4 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', } } } + diff --git a/tests/Integration/Resources/DSC_SqlRSSetup.Integration.Tests.ps1 b/tests/Integration/Resources/DSC_SqlRSSetup.Integration.Tests.ps1 index 216655800d..d566bfa34a 100644 --- a/tests/Integration/Resources/DSC_SqlRSSetup.Integration.Tests.ps1 +++ b/tests/Integration/Resources/DSC_SqlRSSetup.Integration.Tests.ps1 @@ -10,7 +10,7 @@ BeforeDiscovery { 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 + & "$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. diff --git a/tests/Integration/Resources/DSC_SqlRS_Default.Integration.Tests.ps1 b/tests/Integration/Resources/DSC_SqlRS_Default.Integration.Tests.ps1 index 3772c1203f..877d8b0982 100644 --- a/tests/Integration/Resources/DSC_SqlRS_Default.Integration.Tests.ps1 +++ b/tests/Integration/Resources/DSC_SqlRS_Default.Integration.Tests.ps1 @@ -168,8 +168,8 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName $resourceCurrentState.DatabaseServerName | Should -Be $ConfigurationData.AllNodes.DatabaseServerName $resourceCurrentState.DatabaseInstanceName | Should -Be $ConfigurationData.AllNodes.DatabaseInstanceName - $resourceCurrentState.IsInitialized | Should -Be $true - $resourceCurrentState.UseSsl | Should -Be $false + $resourceCurrentState.IsInitialized | Should -BeTrue + $resourceCurrentState.UseSsl | Should -BeFalse } It 'Should return $true when Test-DscConfiguration is run' { @@ -286,7 +286,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', -and $_.ResourceId -eq $resourceId } - $resourceCurrentState.UseSsl | Should -Be $true + $resourceCurrentState.UseSsl | Should -BeTrue } It 'Should return $true when Test-DscConfiguration is run' { @@ -360,7 +360,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', -and $_.ResourceId -eq $resourceId } - $resourceCurrentState.UseSsl | Should -Be $false + $resourceCurrentState.UseSsl | Should -BeFalse } It 'Should return $true when Test-DscConfiguration is run' { @@ -463,3 +463,4 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', } } } + diff --git a/tests/Unit/DSC_SqlAGDatabase.Tests.ps1 b/tests/Unit/DSC_SqlAGDatabase.Tests.ps1 index a3b6f8d734..4ac1a8ba74 100644 --- a/tests/Unit/DSC_SqlAGDatabase.Tests.ps1 +++ b/tests/Unit/DSC_SqlAGDatabase.Tests.ps1 @@ -426,12 +426,12 @@ REVERT' foreach ( $resultDatabaseName in $result.DatabaseName ) { - $mockAvailabilityDatabaseNames -contains $resultDatabaseName | Should -Be $true + $mockAvailabilityDatabaseNames -contains $resultDatabaseName | Should -BeTrue } foreach ( $mockAvailabilityDatabaseName in $mockAvailabilityDatabaseNames ) { - $result.DatabaseName -contains $mockAvailabilityDatabaseName | Should -Be $true + $result.DatabaseName -contains $mockAvailabilityDatabaseName | Should -BeTrue } Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly @@ -2753,7 +2753,7 @@ REVERT' It 'Should return $true when the configuration is in the desired state' { $mockTestTargetResourceParameters.DatabaseName = $mockAvailabilityDatabaseNames.Clone() - Test-TargetResource @mockTestTargetResourceParameters | Should -Be $true + Test-TargetResource @mockTestTargetResourceParameters | Should -BeTrue Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 2 -Exactly Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } @@ -2764,7 +2764,7 @@ REVERT' It 'Should return $false when the specified availability group is not found' { $mockTestTargetResourceParameters.AvailabilityGroupName = 'NonExistentAvailabilityGroup' - Test-TargetResource @mockTestTargetResourceParameters | Should -Be $false + Test-TargetResource @mockTestTargetResourceParameters | Should -BeFalse Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 2 -Exactly Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } @@ -2775,7 +2775,7 @@ REVERT' It 'Should return $false when no matching databases are found' { $mockTestTargetResourceParameters.DatabaseName = $mockDatabaseNameParameterWithNonExistingDatabases.Clone() - Test-TargetResource @mockTestTargetResourceParameters | Should -Be $false + Test-TargetResource @mockTestTargetResourceParameters | Should -BeFalse Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 2 -Exactly Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } @@ -2784,7 +2784,7 @@ REVERT' } It 'Should return $false when databases are found to add to the availability group' { - Test-TargetResource @mockTestTargetResourceParameters | Should -Be $false + Test-TargetResource @mockTestTargetResourceParameters | Should -BeFalse Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 2 -Exactly Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } @@ -2796,7 +2796,7 @@ REVERT' $mockTestTargetResourceParameters.DatabaseName = $mockAvailabilityDatabaseNames.Clone() $mockTestTargetResourceParameters.AvailabilityGroupName = $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.Name - Test-TargetResource @mockTestTargetResourceParameters | Should -Be $true + Test-TargetResource @mockTestTargetResourceParameters | Should -BeTrue Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 2 -Exactly Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } @@ -2810,7 +2810,7 @@ REVERT' $mockTestTargetResourceParameters.DatabaseName = $mockAvailabilityDatabaseNames.Clone() $mockTestTargetResourceParameters.ProcessOnlyOnActiveNode = $mockProcessOnlyOnActiveNode - Test-TargetResource @mockTestTargetResourceParameters | Should -Be $true + Test-TargetResource @mockTestTargetResourceParameters | Should -BeTrue Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } @@ -2827,7 +2827,7 @@ REVERT' It 'Should return $true when the configuration is in the desired state' { $mockTestTargetResourceParameters.DatabaseName = $mockDatabaseNameParameterWithNonExistingDatabases.Clone() - Test-TargetResource @mockTestTargetResourceParameters | Should -Be $true + Test-TargetResource @mockTestTargetResourceParameters | Should -BeTrue Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 2 -Exactly Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } @@ -2838,7 +2838,7 @@ REVERT' It 'Should return $true when no matching databases are found' { $mockTestTargetResourceParameters.DatabaseName = $mockDatabaseNameParameterWithNonExistingDatabases.Clone() - Test-TargetResource @mockTestTargetResourceParameters | Should -Be $true + Test-TargetResource @mockTestTargetResourceParameters | Should -BeTrue Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 2 -Exactly Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } @@ -2846,7 +2846,7 @@ REVERT' } It 'Should return $false when databases are found to remove from the availability group' { - Test-TargetResource @mockTestTargetResourceParameters | Should -Be $false + Test-TargetResource @mockTestTargetResourceParameters | Should -BeFalse Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 2 -Exactly Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } @@ -2857,7 +2857,7 @@ REVERT' $mockTestTargetResourceParameters.DatabaseName = $mockDatabaseNameParameterWithNonExistingDatabases.Clone() $mockTestTargetResourceParameters.AvailabilityGroupName = $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.Name - Test-TargetResource @mockTestTargetResourceParameters | Should -Be $true + Test-TargetResource @mockTestTargetResourceParameters | Should -BeTrue Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 2 -Exactly Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } @@ -2873,7 +2873,7 @@ REVERT' It 'Should return $true when the configuration is in the desired state' { $mockTestTargetResourceParameters.DatabaseName = $mockAvailabilityDatabaseNames.Clone() - Test-TargetResource @mockTestTargetResourceParameters | Should -Be $true + Test-TargetResource @mockTestTargetResourceParameters | Should -BeTrue Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 2 -Exactly Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } @@ -2883,7 +2883,7 @@ REVERT' It 'Should return $false when no matching databases are found' { $mockTestTargetResourceParameters.DatabaseName = $mockDatabaseNameParameterWithNonExistingDatabases.Clone() - Test-TargetResource @mockTestTargetResourceParameters | Should -Be $false + Test-TargetResource @mockTestTargetResourceParameters | Should -BeFalse Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 2 -Exactly Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } @@ -2891,7 +2891,7 @@ REVERT' } It 'Should return $false when databases are found to add to the availability group' { - Test-TargetResource @mockTestTargetResourceParameters | Should -Be $false + Test-TargetResource @mockTestTargetResourceParameters | Should -BeFalse Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 2 -Exactly Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } @@ -2899,7 +2899,7 @@ REVERT' } It 'Should return $false when databases are found to remove from the availability group' { - Test-TargetResource @mockTestTargetResourceParameters | Should -Be $false + Test-TargetResource @mockTestTargetResourceParameters | Should -BeFalse Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 2 -Exactly Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } @@ -2910,7 +2910,7 @@ REVERT' $mockTestTargetResourceParameters.DatabaseName = $mockAvailabilityDatabaseNames.Clone() $mockTestTargetResourceParameters.AvailabilityGroupName = $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.Name - Test-TargetResource @mockTestTargetResourceParameters | Should -Be $true + Test-TargetResource @mockTestTargetResourceParameters | Should -BeTrue Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 2 -Exactly Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } @@ -2935,7 +2935,7 @@ REVERT' foreach ( $result in $results ) { - $mockAvailabilityDatabasePresentResults -contains $result | Should -Be $true + $mockAvailabilityDatabasePresentResults -contains $result | Should -BeTrue } } @@ -2946,7 +2946,7 @@ REVERT' foreach ( $result in $results ) { - $mockPresentDatabaseNames -contains $result | Should -Be $true + $mockPresentDatabaseNames -contains $result | Should -BeTrue } } @@ -2979,7 +2979,7 @@ REVERT' foreach ( $result in $results ) { - $mockAvailabilityDatabaseAbsentResults -contains $result | Should -Be $true + $mockAvailabilityDatabaseAbsentResults -contains $result | Should -BeTrue } } @@ -2990,7 +2990,7 @@ REVERT' foreach ( $result in $results ) { - $mockAvailabilityDatabaseAbsentResults -contains $result | Should -Be $true + $mockAvailabilityDatabaseAbsentResults -contains $result | Should -BeTrue } } @@ -3011,7 +3011,7 @@ REVERT' foreach ( $result in $results ) { - $mockAvailabilityDatabaseExactlyRemoveResults -contains $result | Should -Be $true + $mockAvailabilityDatabaseExactlyRemoveResults -contains $result | Should -BeTrue } } @@ -3023,13 +3023,13 @@ REVERT' # Ensure all of the results are in the Availability Databases foreach ( $result in $results ) { - $mockAvailabilityDatabaseNames -contains $result | Should -Be $true + $mockAvailabilityDatabaseNames -contains $result | Should -BeTrue } # Ensure all of the Availability Databases are in the results foreach ( $mockAvailabilityDatabaseName in $mockAvailabilityDatabaseNames ) { - $results -contains $mockAvailabilityDatabaseName | Should -Be $true + $results -contains $mockAvailabilityDatabaseName | Should -BeTrue } } } @@ -3061,7 +3061,7 @@ REVERT' foreach ( $result in $results ) { - $mockPresentDatabaseNames -contains $result | Should -Be $true + $mockPresentDatabaseNames -contains $result | Should -BeTrue } } @@ -3072,7 +3072,7 @@ REVERT' foreach ( $result in $results ) { - $mockPresentDatabaseNames -contains $result | Should -Be $true + $mockPresentDatabaseNames -contains $result | Should -BeTrue } } } @@ -3108,7 +3108,7 @@ REVERT' foreach ( $result in $results ) { - $mockMissingDatabases -contains $result | Should -Be $true + $mockMissingDatabases -contains $result | Should -BeTrue } } diff --git a/tests/Unit/DSC_SqlAlias.Tests.ps1 b/tests/Unit/DSC_SqlAlias.Tests.ps1 index d6663b8e83..3a8a6cc4cf 100644 --- a/tests/Unit/DSC_SqlAlias.Tests.ps1 +++ b/tests/Unit/DSC_SqlAlias.Tests.ps1 @@ -138,7 +138,7 @@ Describe 'SqlAlias\Get-TargetResource' { $result.ServerName | Should -Be 'SqlNode.company.local' $result.Protocol | Should -Be 'TCP' $result.TcpPort | Should -BeExactly 1433 - $result.UseDynamicTcpPort | Should -Be $false + $result.UseDynamicTcpPort | Should -BeFalse $result.PipeName | Should -Be '' } } @@ -192,7 +192,7 @@ Describe 'SqlAlias\Get-TargetResource' { $result.ServerName | Should -BeNullOrEmpty $result.Protocol | Should -Be '' $result.TcpPort | Should -BeExactly 0 - $result.UseDynamicTcpPort | Should -Be $false + $result.UseDynamicTcpPort | Should -BeFalse $result.PipeName | Should -Be '' } } @@ -248,7 +248,7 @@ Describe 'SqlAlias\Get-TargetResource' { $result.ServerName | Should -Be 'SqlNode.company.local' $result.Protocol | Should -Be 'TCP' $result.TcpPort | Should -BeExactly 0 - $result.UseDynamicTcpPort | Should -Be $true + $result.UseDynamicTcpPort | Should -BeTrue $result.PipeName | Should -Be '' } } @@ -302,7 +302,7 @@ Describe 'SqlAlias\Get-TargetResource' { $result.ServerName | Should -BeNullOrEmpty $result.Protocol | Should -Be '' $result.TcpPort | Should -BeExactly 0 - $result.UseDynamicTcpPort | Should -Be $false + $result.UseDynamicTcpPort | Should -BeFalse $result.PipeName | Should -Be '' } } @@ -375,7 +375,7 @@ Describe 'SqlAlias\Get-TargetResource' { $result.ServerName | Should -BeNullOrEmpty $result.Protocol | Should -Be 'NP' $result.TcpPort | Should -BeExactly 0 - $result.UseDynamicTcpPort | Should -Be $false + $result.UseDynamicTcpPort | Should -BeFalse $result.PipeName | Should -Be '\\SqlNode\PIPE\sql\query' } } @@ -429,7 +429,7 @@ Describe 'SqlAlias\Get-TargetResource' { $result.ServerName | Should -BeNullOrEmpty $result.Protocol | Should -Be '' $result.TcpPort | Should -BeExactly 0 - $result.UseDynamicTcpPort | Should -Be $false + $result.UseDynamicTcpPort | Should -BeFalse $result.PipeName | Should -Be '' } } @@ -490,7 +490,7 @@ Describe 'SqlAlias\Get-TargetResource' { $result.ServerName | Should -BeNullOrEmpty $result.Protocol | Should -Be '' $result.TcpPort | Should -BeExactly 0 - $result.UseDynamicTcpPort | Should -Be $false + $result.UseDynamicTcpPort | Should -BeFalse $result.PipeName | Should -Be '' } } @@ -543,7 +543,7 @@ Describe 'SqlAlias\Get-TargetResource' { $result.ServerName | Should -BeNullOrEmpty $result.Protocol | Should -Be '' $result.TcpPort | Should -BeExactly 0 - $result.UseDynamicTcpPort | Should -Be $false + $result.UseDynamicTcpPort | Should -BeFalse $result.PipeName | Should -Be '' } } diff --git a/tests/Unit/DSC_SqlLogin.Tests.ps1 b/tests/Unit/DSC_SqlLogin.Tests.ps1 index 19b727d6b5..5d45d7399e 100644 --- a/tests/Unit/DSC_SqlLogin.Tests.ps1 +++ b/tests/Unit/DSC_SqlLogin.Tests.ps1 @@ -1842,9 +1842,3 @@ Describe 'SqlLogin\Set-SQLServerLoginPassword' { } } } - -# } -# finally -# { -# Invoke-TestCleanup -# } diff --git a/tests/Unit/DSC_SqlMaxDop.Tests.ps1 b/tests/Unit/DSC_SqlMaxDop.Tests.ps1 index c9b5b1be78..e2648161ef 100644 --- a/tests/Unit/DSC_SqlMaxDop.Tests.ps1 +++ b/tests/Unit/DSC_SqlMaxDop.Tests.ps1 @@ -442,7 +442,7 @@ Describe 'SqlMaxDop\Set-TargetResource' -Tag 'Set' { ) return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | - Add-Member -MemberType 'NoteProperty' -Name 'Configuration' -Value $mockConfigurationObject -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'Configuration' -Value $mockConfigurationObject -PassThru -Force | Add-Member -MemberType 'ScriptMethod' -Name 'Alter' -Value { InModuleScope -ScriptBlock { $script:mockMethodAlterWasRun += 1 @@ -493,7 +493,7 @@ Describe 'SqlMaxDop\Set-TargetResource' -Tag 'Set' { ) return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | - Add-Member -MemberType 'NoteProperty' -Name 'Configuration' -Value $mockConfigurationObject -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'Configuration' -Value $mockConfigurationObject -PassThru -Force | Add-Member -MemberType 'ScriptMethod' -Name 'Alter' -Value { InModuleScope -ScriptBlock { $script:mockMethodAlterWasRun += 1 @@ -548,7 +548,7 @@ Describe 'SqlMaxDop\Set-TargetResource' -Tag 'Set' { ) return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | - Add-Member -MemberType 'NoteProperty' -Name 'Configuration' -Value $mockConfigurationObject -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'Configuration' -Value $mockConfigurationObject -PassThru -Force | Add-Member -MemberType 'ScriptMethod' -Name 'Alter' -Value { InModuleScope -ScriptBlock { $script:mockMethodAlterWasRun += 1 @@ -603,7 +603,7 @@ Describe 'SqlMaxDop\Set-TargetResource' -Tag 'Set' { ) return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | - Add-Member -MemberType 'NoteProperty' -Name 'Configuration' -Value $mockConfigurationObject -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'Configuration' -Value $mockConfigurationObject -PassThru -Force | Add-Member -MemberType 'ScriptMethod' -Name 'Alter' -Value { InModuleScope -ScriptBlock { $script:mockMethodAlterWasRun += 1 diff --git a/tests/Unit/Private/ConvertTo-FormattedParameterDescription.Tests.ps1 b/tests/Unit/Private/ConvertTo-FormattedParameterDescription.Tests.ps1 new file mode 100644 index 0000000000..c9b2fcfbd0 --- /dev/null +++ b/tests/Unit/Private/ConvertTo-FormattedParameterDescription.Tests.ps1 @@ -0,0 +1,196 @@ +[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 build" first.' + } +} + +BeforeAll { + $env:SqlServerDscCI = $true + + $script:moduleName = 'SqlServerDsc' + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + Remove-Item -Path 'Env:\SqlServerDscCI' -ErrorAction 'SilentlyContinue' + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:moduleName -All | Remove-Module -Force +} + +Describe 'ConvertTo-FormattedParameterDescription' -Tag 'Private' { + Context 'When converting bound parameters to formatted description' { + It 'Should format parameters correctly when multiple parameters are provided' { + InModuleScope -ScriptBlock { + $boundParameters = @{ + EmailAddress = 'test@contoso.com' + CategoryName = 'TestCategory' + ServerObject = 'MockServerObject' + Name = 'TestOperator' + Force = $true + } + + $excludeParameters = @('ServerObject', 'Name', 'Force') + + $result = ConvertTo-FormattedParameterDescription -BoundParameters $boundParameters -Exclude $excludeParameters + + # Check that parameters are sorted alphabetically + $result | Should -Be "`r`n CategoryName: 'TestCategory'`r`n EmailAddress: 'test@contoso.com'" + } + } + + It 'Should return no parameters message when no settable parameters are provided' { + InModuleScope -ScriptBlock { + $boundParameters = @{ + ServerObject = 'MockServerObject' + Name = 'TestOperator' + Force = $true + } + + $excludeParameters = @('ServerObject', 'Name', 'Force') + + $result = ConvertTo-FormattedParameterDescription -BoundParameters $boundParameters -Exclude $excludeParameters + + $result | Should -Be " $($script:localizedData.ConvertTo_FormattedParameterDescription_NoParametersToUpdate)" + } + } + + It 'Should format single parameter correctly' { + InModuleScope -ScriptBlock { + $boundParameters = @{ + EmailAddress = 'admin@company.com' + ServerObject = 'MockServerObject' + } + + $excludeParameters = @('ServerObject') + + $result = ConvertTo-FormattedParameterDescription -BoundParameters $boundParameters -Exclude $excludeParameters + + $result | Should -Be "`r`n EmailAddress: 'admin@company.com'" + } + } + + It 'Should handle empty exclude parameters array' { + InModuleScope -ScriptBlock { + $boundParameters = @{ + EmailAddress = 'test@contoso.com' + CategoryName = 'TestCategory' + } + + $result = ConvertTo-FormattedParameterDescription -BoundParameters $boundParameters -Exclude @() + + # Check that parameters are sorted alphabetically + $result | Should -Be "`r`n CategoryName: 'TestCategory'`r`n EmailAddress: 'test@contoso.com'" + } + } + + It 'Should handle various data types correctly' { + InModuleScope -ScriptBlock { + $boundParameters = @{ + EmailAddress = 'test@contoso.com' + PagerDays = 'Monday' + SaturdayPagerEndTime = [TimeSpan]::new(17, 0, 0) + Force = $true + } + + $excludeParameters = @('Force') + + $result = ConvertTo-FormattedParameterDescription -BoundParameters $boundParameters -Exclude $excludeParameters + + # Check that parameters are sorted alphabetically + $result | Should -Be "`r`n EmailAddress: 'test@contoso.com'`r`n PagerDays: 'Monday'`r`n SaturdayPagerEndTime: '17:00:00'" + } + } + + It 'Should mask SecureString values' { + InModuleScope -ScriptBlock { + $securePassword = ConvertTo-SecureString 'P@ssw0rd' -AsPlainText -Force + $boundParameters = @{ + EmailAddress = 'test@contoso.com' + Password = $securePassword + } + + $result = ConvertTo-FormattedParameterDescription -BoundParameters $boundParameters -Exclude @() + + $result | Should -Be "`r`n EmailAddress: 'test@contoso.com'`r`n Password: '***'" + } + } + + It 'Should show only username for PSCredential objects' { + InModuleScope -ScriptBlock { + $securePassword = ConvertTo-SecureString 'P@ssw0rd' -AsPlainText -Force + $credential = [System.Management.Automation.PSCredential]::new('TestUser', $securePassword) + $boundParameters = @{ + EmailAddress = 'test@contoso.com' + Credential = $credential + } + + $result = ConvertTo-FormattedParameterDescription -BoundParameters $boundParameters -Exclude @() + + $result | Should -Be "`r`n Credential: 'TestUser'`r`n EmailAddress: 'test@contoso.com'" + } + } + + It 'Should format arrays as comma-separated values' { + InModuleScope -ScriptBlock { + $boundParameters = @{ + EmailAddress = 'test@contoso.com' + Categories = @('Category1', 'Category2', 'Category3') + Numbers = @(1, 2, 3) + } + + $result = ConvertTo-FormattedParameterDescription -BoundParameters $boundParameters -Exclude @() + + $result | Should -Be "`r`n Categories: 'Category1, Category2, Category3'`r`n EmailAddress: 'test@contoso.com'`r`n Numbers: '1, 2, 3'" + } + } + + It 'Should handle mixed sensitive and non-sensitive parameters' { + InModuleScope -ScriptBlock { + $securePassword = ConvertTo-SecureString 'P@ssw0rd' -AsPlainText -Force + $credential = [System.Management.Automation.PSCredential]::new('TestUser', $securePassword) + $boundParameters = @{ + EmailAddress = 'test@contoso.com' + Password = $securePassword + Credential = $credential + Categories = @('Cat1', 'Cat2') + Force = $true + } + + $excludeParameters = @('Force') + + $result = ConvertTo-FormattedParameterDescription -BoundParameters $boundParameters -Exclude $excludeParameters + + $result | Should -Be "`r`n Categories: 'Cat1, Cat2'`r`n Credential: 'TestUser'`r`n EmailAddress: 'test@contoso.com'`r`n Password: '***'" + } + } + } +} diff --git a/tests/Unit/Private/Get-AgentOperatorObject.Tests.ps1 b/tests/Unit/Private/Get-AgentOperatorObject.Tests.ps1 new file mode 100644 index 0000000000..6e8d736f45 --- /dev/null +++ b/tests/Unit/Private/Get-AgentOperatorObject.Tests.ps1 @@ -0,0 +1,137 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +[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 build" first.' + } +} + +BeforeAll { + $env:SqlServerDscCI = $true + + $script:moduleName = 'SqlServerDsc' + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../../Unit') -ChildPath 'Stubs/SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + Remove-Item -Path 'Env:\SqlServerDscCI' -ErrorAction 'SilentlyContinue' + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:moduleName -All | Remove-Module -Force +} + +Describe 'Get-AgentOperatorObject' -Tag 'Private' { + Context 'When operator exists' { + BeforeAll { + # Create a mock operator object + $mockOperatorObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Agent.Operator' + $mockOperatorObject.Name = 'TestOperator' + + # Create a mock server object with a JobServer that contains operators + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockJobServer = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Agent.JobServer' + $mockOperators = [Microsoft.SqlServer.Management.Smo.Agent.OperatorCollection]::CreateTypeInstance() + $mockOperators.Add($mockOperatorObject) + + $mockJobServer | Add-Member -MemberType NoteProperty -Name 'Operators' -Value $mockOperators -Force + $mockServerObject | Add-Member -MemberType NoteProperty -Name 'JobServer' -Value $mockJobServer -Force + } + + It 'Should return the operator object when found' { + InModuleScope -Parameters @{ mockServerObject = $mockServerObject } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $result = Get-AgentOperatorObject -ServerObject $mockServerObject -Name 'TestOperator' + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'TestOperator' + } + } + } + + Context 'When operator does not exist' { + BeforeAll { + # Create a mock server object with an empty JobServer operators collection + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockJobServer = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Agent.JobServer' + $mockOperators = [Microsoft.SqlServer.Management.Smo.Agent.OperatorCollection]::CreateTypeInstance() + + $mockJobServer | Add-Member -MemberType NoteProperty -Name 'Operators' -Value $mockOperators -Force + $mockServerObject | Add-Member -MemberType NoteProperty -Name 'JobServer' -Value $mockJobServer -Force + } + + It 'Should return null when operator not found and ErrorAction is SilentlyContinue' { + InModuleScope -Parameters @{ mockServerObject = $mockServerObject } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $result = Get-AgentOperatorObject -ServerObject $mockServerObject -Name 'NonExistentOperator' -ErrorAction 'SilentlyContinue' + $result | Should -BeNull + } + } + + It 'Should throw a terminating error when operator not found and ErrorAction is Stop' { + InModuleScope -Parameters @{ mockServerObject = $mockServerObject } -ScriptBlock { + Set-StrictMode -Version 1.0 + + { Get-AgentOperatorObject -ServerObject $mockServerObject -Name 'NonExistentOperator' -ErrorAction 'Stop' } | + Should -Throw -ExpectedMessage "*NonExistentOperator*not found*" + } + } + } + + Context 'When using Refresh parameter' { + BeforeAll { + # Create a mock operator object + $mockOperatorObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Agent.Operator' + $mockOperatorObject.Name = 'TestOperator' + + # Create a mock server object with a JobServer that contains operators + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockJobServer = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Agent.JobServer' + $mockOperators = [Microsoft.SqlServer.Management.Smo.Agent.OperatorCollection]::CreateTypeInstance() + $mockOperators.Add($mockOperatorObject) + + $mockJobServer | Add-Member -MemberType NoteProperty -Name 'Operators' -Value $mockOperators -Force + $mockServerObject | Add-Member -MemberType NoteProperty -Name 'JobServer' -Value $mockJobServer -Force + } + + It 'Should return the operator object when found' { + InModuleScope -Parameters @{ mockServerObject = $mockServerObject } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $result = Get-AgentOperatorObject -ServerObject $mockServerObject -Name 'TestOperator' -Refresh + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'TestOperator' + } + } + } +} diff --git a/tests/Unit/Private/Get-CommandParameter.Tests.ps1 b/tests/Unit/Private/Get-CommandParameter.Tests.ps1 new file mode 100644 index 0000000000..cca9e42833 --- /dev/null +++ b/tests/Unit/Private/Get-CommandParameter.Tests.ps1 @@ -0,0 +1,180 @@ +BeforeDiscovery { + try + { + 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' 3>&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 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:dscModuleName -Force + + $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 'Get-CommandParameter' -Tag 'Private' { + Context 'When getting command parameters' { + It 'Should return all parameters excluding common parameters when no exclude list is provided' { + InModuleScope -ScriptBlock { + function Test-Function + { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $RequiredParam, + + [Parameter()] + [System.String] + $OptionalParam1, + + [Parameter()] + [System.String] + $OptionalParam2, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $PassThru, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force + ) + } + + $commandInfo = Get-Command Test-Function + $result = Get-CommandParameter -Command $commandInfo + + $result | Should -Contain 'RequiredParam' + $result | Should -Contain 'OptionalParam1' + $result | Should -Contain 'OptionalParam2' + $result | Should -Contain 'PassThru' + $result | Should -Contain 'Force' + $result | Should -Not -Contain 'Verbose' + $result | Should -Not -Contain 'Debug' + $result | Should -Not -Contain 'ErrorAction' + } + } + + It 'Should exclude specified parameters and common parameters' { + InModuleScope -ScriptBlock { + function Test-Function + { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $RequiredParam, + + [Parameter()] + [System.String] + $OptionalParam1, + + [Parameter()] + [System.String] + $OptionalParam2, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $PassThru, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force + ) + } + + $commandInfo = Get-Command Test-Function + $result = Get-CommandParameter -Command $commandInfo -Exclude @('RequiredParam', 'PassThru', 'Force') + + $result | Should -Not -Contain 'RequiredParam' + $result | Should -Contain 'OptionalParam1' + $result | Should -Contain 'OptionalParam2' + $result | Should -Not -Contain 'PassThru' + $result | Should -Not -Contain 'Force' + $result | Should -Not -Contain 'Verbose' + $result | Should -Not -Contain 'Debug' + } + } + + It 'Should return empty array when all parameters are excluded' { + InModuleScope -ScriptBlock { + function Test-Function + { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $RequiredParam, + + [Parameter()] + [System.String] + $OptionalParam1, + + [Parameter()] + [System.String] + $OptionalParam2, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $PassThru, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force + ) + } + + $commandInfo = Get-Command Test-Function + $result = Get-CommandParameter -Command $commandInfo -Exclude @('RequiredParam', 'OptionalParam1', 'OptionalParam2', 'PassThru', 'Force') + + $result | Should -BeNullOrEmpty + } + } + + It 'Should handle empty parameter hashtable' { + InModuleScope -ScriptBlock { + # Create a mock function with no parameters + function Test-EmptyFunction { } + $commandInfo = Get-Command Test-EmptyFunction + $result = Get-CommandParameter -Command $commandInfo + + $result | Should -BeNullOrEmpty + } + } + } +} diff --git a/tests/Unit/Public/Assert-SqlDscAgentOperator.Tests.ps1 b/tests/Unit/Public/Assert-SqlDscAgentOperator.Tests.ps1 new file mode 100644 index 0000000000..3f7bc0774f --- /dev/null +++ b/tests/Unit/Public/Assert-SqlDscAgentOperator.Tests.ps1 @@ -0,0 +1,134 @@ +[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 build" first.' + } +} + +BeforeAll { + $env:SqlServerDscCI = $true + + $script:moduleName = 'SqlServerDsc' + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../../Unit') -ChildPath 'Stubs/SMO.cs') + + $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName +} + +AfterAll { + $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:moduleName -All | Remove-Module -Force + + Remove-Item -Path 'Env:\SqlServerDscCI' -ErrorAction 'SilentlyContinue' +} + +Describe 'Assert-SqlDscAgentOperator' -Tag 'Public' { + BeforeAll { + $mockOperatorName = 'TestOperator' + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Context 'When operator exists' { + BeforeAll { + Mock -CommandName Get-AgentOperatorObject -MockWith { + $mockOperatorObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Agent.Operator' + return $mockOperatorObject + } + } + + It 'Should not throw and not return anything when operator found' { + $null = Assert-SqlDscAgentOperator -ServerObject $mockServerObject -Name $mockOperatorName + } + + It 'Should call Get-AgentOperatorObject with correct parameters' { + Assert-SqlDscAgentOperator -ServerObject $mockServerObject -Name $mockOperatorName + + Should -Invoke -CommandName Get-AgentOperatorObject -ParameterFilter { + $ServerObject -eq $mockServerObject -and + $Name -eq $mockOperatorName -and + $ErrorAction -eq 'Stop' + } -Exactly -Times 1 + } + } + + Context 'When operator does not exist' { + BeforeAll { + Mock -CommandName Get-AgentOperatorObject -MockWith { + $errorMessage = "The operator 'NonExistentOperator' does not exist on the instance 'TestInstance'." + $exception = [System.InvalidOperationException]::new($errorMessage) + $errorRecord = [System.Management.Automation.ErrorRecord]::new( + $exception, + 'GAOO0001', + [System.Management.Automation.ErrorCategory]::ObjectNotFound, + 'NonExistentOperator' + ) + throw $errorRecord + } + } + + It 'Should throw a terminating error when operator not found' { + { Assert-SqlDscAgentOperator -ServerObject $mockServerObject -Name 'NonExistentOperator' } | Should -Throw -ExpectedMessage "*NonExistentOperator*does not exist*" + } + + It 'Should call Get-AgentOperatorObject once before throwing error' { + try + { + Assert-SqlDscAgentOperator -ServerObject $mockServerObject -Name 'NonExistentOperator' + } + catch + { + # Expected to throw + } + + Should -Invoke -CommandName Get-AgentOperatorObject -Exactly -Times 1 + } + } + + Context 'When using pipeline input' { + BeforeAll { + Mock -CommandName Get-AgentOperatorObject -MockWith { + $mockOperatorObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Agent.Operator' + return $mockOperatorObject + } + } + + It 'Should accept ServerObject from pipeline' { + $null = $mockServerObject | Assert-SqlDscAgentOperator -Name $mockOperatorName + } + + It 'Should call Get-AgentOperatorObject with correct parameters when using pipeline' { + $mockServerObject | Assert-SqlDscAgentOperator -Name $mockOperatorName + + Should -Invoke -CommandName Get-AgentOperatorObject -ParameterFilter { + $ServerObject -eq $mockServerObject -and + $Name -eq $mockOperatorName -and + $ErrorAction -eq 'Stop' + } -Exactly -Times 1 + } + } +} diff --git a/tests/Unit/Public/Disable-SqlDscAgentOperator.Tests.ps1 b/tests/Unit/Public/Disable-SqlDscAgentOperator.Tests.ps1 new file mode 100644 index 0000000000..4cb83186c4 --- /dev/null +++ b/tests/Unit/Public/Disable-SqlDscAgentOperator.Tests.ps1 @@ -0,0 +1,220 @@ +[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 build" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' + + # Load SMO stub types + Add-Type -Path "$PSScriptRoot/../Stubs/SMO.cs" + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + Remove-Item -Path 'Env:\SqlServerDscCI' -ErrorAction 'SilentlyContinue' + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:moduleName -All | Remove-Module -Force +} + +Describe 'Disable-SqlDscAgentOperator' -Tag 'Public' { + Context 'When command has correct parameter sets' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = 'ServerObject' + ExpectedParameters = '-ServerObject -Name [-Force] [-Refresh] [-WhatIf] [-Confirm] []' + } + @{ + ExpectedParameterSetName = 'OperatorObject' + ExpectedParameters = '-OperatorObject [-Force] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'Disable-SqlDscAgentOperator').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 command has correct parameter properties' { + It 'Should have ServerObject as a mandatory parameter in ServerObject parameter set' { + $parameterInfo = (Get-Command -Name 'Disable-SqlDscAgentOperator').Parameters['ServerObject'] + $parameterInfo.ParameterSets['ServerObject'].IsMandatory | Should -BeTrue + } + + It 'Should have OperatorObject as a mandatory parameter in OperatorObject parameter set' { + $parameterInfo = (Get-Command -Name 'Disable-SqlDscAgentOperator').Parameters['OperatorObject'] + $parameterInfo.ParameterSets['OperatorObject'].IsMandatory | Should -BeTrue + } + + It 'Should have Name as a mandatory parameter in ServerObject parameter set' { + $parameterInfo = (Get-Command -Name 'Disable-SqlDscAgentOperator').Parameters['Name'] + $parameterInfo.ParameterSets['ServerObject'].IsMandatory | Should -BeTrue + } + } + + Context 'When disabling an operator using ServerObject parameter set' { + BeforeAll { + # Mock operator object + $script:mockOperator = [Microsoft.SqlServer.Management.Smo.Agent.Operator]::CreateTypeInstance() + $script:mockOperator.Name = 'TestOperator' + $script:mockOperator.EmailAddress = 'test@contoso.com' + $script:mockOperator.Enabled = $true + + # Mock parent objects for verbose messages + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.InstanceName = 'TestInstance' + $script:mockJobServer.Parent = $script:mockServerObject + $script:mockOperator.Parent = $script:mockJobServer + + # Mock the Get-AgentOperatorObject function + Mock -CommandName Get-AgentOperatorObject -MockWith { + return $script:mockOperator + } + } + + It 'Should disable the operator successfully' { + Disable-SqlDscAgentOperator -ServerObject $script:mockServerObject -Name 'TestOperator' -Force + + $script:mockOperator.Enabled | Should -BeFalse + Should -Invoke -CommandName Get-AgentOperatorObject -Exactly -Times 1 -Scope It + } + + It 'Should disable the operator with Refresh parameter' { + Disable-SqlDscAgentOperator -ServerObject $script:mockServerObject -Name 'TestOperator' -Refresh -Force + + $script:mockOperator.Enabled | Should -BeFalse + Should -Invoke -CommandName Get-AgentOperatorObject -ParameterFilter { + $Refresh -eq $true + } -Exactly -Times 1 -Scope It + } + + It 'Should use pipeline input for ServerObject' { + $script:mockServerObject | Disable-SqlDscAgentOperator -Name 'TestOperator' -Force + + $script:mockOperator.Enabled | Should -BeFalse + Should -Invoke -CommandName Get-AgentOperatorObject -Exactly -Times 1 -Scope It + } + + It 'Should throw when operator cannot be found' { + Mock -CommandName Get-AgentOperatorObject -MockWith { + throw 'Operator not found' + } + + { Disable-SqlDscAgentOperator -ServerObject $script:mockServerObject -Name 'NonExistentOperator' -Force } | + Should -Throw -ExpectedMessage 'Operator not found' + } + + It 'Should support WhatIf' { + $script:mockOperator.Enabled = $true + + Disable-SqlDscAgentOperator -ServerObject $script:mockServerObject -Name 'TestOperator' -WhatIf + + $script:mockOperator.Enabled | Should -BeTrue + Should -Invoke -CommandName Get-AgentOperatorObject -Exactly -Times 1 -Scope It + } + } + + Context 'When disabling an operator using OperatorObject parameter set' { + BeforeAll { + # Mock operator object + $script:mockOperator = [Microsoft.SqlServer.Management.Smo.Agent.Operator]::CreateTypeInstance() + $script:mockOperator.Name = 'TestOperator' + $script:mockOperator.EmailAddress = 'test@contoso.com' + $script:mockOperator.Enabled = $true + + # Mock parent objects for verbose messages + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.InstanceName = 'TestInstance' + $script:mockJobServer.Parent = $script:mockServerObject + $script:mockOperator.Parent = $script:mockJobServer + } + + It 'Should disable the operator successfully using OperatorObject' { + Disable-SqlDscAgentOperator -OperatorObject $script:mockOperator -Force + + $script:mockOperator.Enabled | Should -BeFalse + } + + It 'Should use pipeline input for OperatorObject' { + $script:mockOperator.Enabled = $true + + $script:mockOperator | Disable-SqlDscAgentOperator -Force + + $script:mockOperator.Enabled | Should -BeFalse + } + + It 'Should support WhatIf with OperatorObject' { + $script:mockOperator.Enabled = $true + + Disable-SqlDscAgentOperator -OperatorObject $script:mockOperator -WhatIf + + $script:mockOperator.Enabled | Should -BeTrue + } + } + + Context 'When disabling an operator fails' { + BeforeAll { + # Mock operator object that will fail to alter + $script:mockOperator = [Microsoft.SqlServer.Management.Smo.Agent.Operator]::CreateTypeInstance() + $script:mockOperator.Name = 'TestOperator' + $script:mockOperator.EmailAddress = 'test@contoso.com' + $script:mockOperator.Enabled = $true + + # Mock parent objects for verbose messages + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.InstanceName = 'TestInstance' + $script:mockJobServer.Parent = $script:mockServerObject + $script:mockOperator.Parent = $script:mockJobServer + + # Add a method to throw exception when Alter() is called + $script:mockOperator | Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { + throw 'Failed to alter operator' + } -Force + } + + It 'Should throw terminating error when disabling fails' { + { Disable-SqlDscAgentOperator -OperatorObject $script:mockOperator -Force } | + Should -Throw -ExpectedMessage "*Failed to disable SQL Agent Operator 'TestOperator'*" + } + } +} diff --git a/tests/Unit/Public/Enable-SqlDscAgentOperator.Tests.ps1 b/tests/Unit/Public/Enable-SqlDscAgentOperator.Tests.ps1 new file mode 100644 index 0000000000..41e4404685 --- /dev/null +++ b/tests/Unit/Public/Enable-SqlDscAgentOperator.Tests.ps1 @@ -0,0 +1,220 @@ +[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 build" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' + + # Load SMO stub types + Add-Type -Path "$PSScriptRoot/../Stubs/SMO.cs" + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + Remove-Item -Path 'Env:\SqlServerDscCI' -ErrorAction 'SilentlyContinue' + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:moduleName -All | Remove-Module -Force +} + +Describe 'Enable-SqlDscAgentOperator' -Tag 'Public' { + Context 'When command has correct parameter sets' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = 'ServerObject' + ExpectedParameters = '-ServerObject -Name [-Force] [-Refresh] [-WhatIf] [-Confirm] []' + } + @{ + ExpectedParameterSetName = 'OperatorObject' + ExpectedParameters = '-OperatorObject [-Force] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'Enable-SqlDscAgentOperator').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 command has correct parameter properties' { + It 'Should have ServerObject as a mandatory parameter in ServerObject parameter set' { + $parameterInfo = (Get-Command -Name 'Enable-SqlDscAgentOperator').Parameters['ServerObject'] + $parameterInfo.ParameterSets['ServerObject'].IsMandatory | Should -BeTrue + } + + It 'Should have OperatorObject as a mandatory parameter in OperatorObject parameter set' { + $parameterInfo = (Get-Command -Name 'Enable-SqlDscAgentOperator').Parameters['OperatorObject'] + $parameterInfo.ParameterSets['OperatorObject'].IsMandatory | Should -BeTrue + } + + It 'Should have Name as a mandatory parameter in ServerObject parameter set' { + $parameterInfo = (Get-Command -Name 'Enable-SqlDscAgentOperator').Parameters['Name'] + $parameterInfo.ParameterSets['ServerObject'].IsMandatory | Should -BeTrue + } + } + + Context 'When enabling an operator using ServerObject parameter set' { + BeforeAll { + # Mock operator object + $script:mockOperator = [Microsoft.SqlServer.Management.Smo.Agent.Operator]::CreateTypeInstance() + $script:mockOperator.Name = 'TestOperator' + $script:mockOperator.EmailAddress = 'test@contoso.com' + $script:mockOperator.Enabled = $false + + # Mock parent objects for verbose messages + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.InstanceName = 'TestInstance' + $script:mockJobServer.Parent = $script:mockServerObject + $script:mockOperator.Parent = $script:mockJobServer + + # Mock the Get-AgentOperatorObject function + Mock -CommandName Get-AgentOperatorObject -MockWith { + return $script:mockOperator + } + } + + It 'Should enable the operator successfully' { + Enable-SqlDscAgentOperator -ServerObject $script:mockServerObject -Name 'TestOperator' -Force + + $script:mockOperator.Enabled | Should -BeTrue + Should -Invoke -CommandName Get-AgentOperatorObject -Exactly -Times 1 -Scope It + } + + It 'Should enable the operator with Refresh parameter' { + Enable-SqlDscAgentOperator -ServerObject $script:mockServerObject -Name 'TestOperator' -Refresh -Force + + $script:mockOperator.Enabled | Should -BeTrue + Should -Invoke -CommandName Get-AgentOperatorObject -ParameterFilter { + $Refresh -eq $true + } -Exactly -Times 1 -Scope It + } + + It 'Should use pipeline input for ServerObject' { + $script:mockServerObject | Enable-SqlDscAgentOperator -Name 'TestOperator' -Force + + $script:mockOperator.Enabled | Should -BeTrue + Should -Invoke -CommandName Get-AgentOperatorObject -Exactly -Times 1 -Scope It + } + + It 'Should throw when operator cannot be found' { + Mock -CommandName Get-AgentOperatorObject -MockWith { + throw 'Operator not found' + } + + { Enable-SqlDscAgentOperator -ServerObject $script:mockServerObject -Name 'NonExistentOperator' -Force } | + Should -Throw -ExpectedMessage 'Operator not found' + } + + It 'Should support WhatIf' { + $script:mockOperator.Enabled = $false + + Enable-SqlDscAgentOperator -ServerObject $script:mockServerObject -Name 'TestOperator' -WhatIf + + $script:mockOperator.Enabled | Should -BeFalse + Should -Invoke -CommandName Get-AgentOperatorObject -Exactly -Times 1 -Scope It + } + } + + Context 'When enabling an operator using OperatorObject parameter set' { + BeforeAll { + # Mock operator object + $script:mockOperator = [Microsoft.SqlServer.Management.Smo.Agent.Operator]::CreateTypeInstance() + $script:mockOperator.Name = 'TestOperator' + $script:mockOperator.EmailAddress = 'test@contoso.com' + $script:mockOperator.Enabled = $false + + # Mock parent objects for verbose messages + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.InstanceName = 'TestInstance' + $script:mockJobServer.Parent = $script:mockServerObject + $script:mockOperator.Parent = $script:mockJobServer + } + + It 'Should enable the operator successfully using OperatorObject' { + Enable-SqlDscAgentOperator -OperatorObject $script:mockOperator -Force + + $script:mockOperator.Enabled | Should -BeTrue + } + + It 'Should use pipeline input for OperatorObject' { + $script:mockOperator.Enabled = $false + + $script:mockOperator | Enable-SqlDscAgentOperator -Force + + $script:mockOperator.Enabled | Should -BeTrue + } + + It 'Should support WhatIf with OperatorObject' { + $script:mockOperator.Enabled = $false + + Enable-SqlDscAgentOperator -OperatorObject $script:mockOperator -WhatIf + + $script:mockOperator.Enabled | Should -BeFalse + } + } + + Context 'When enabling an operator fails' { + BeforeAll { + # Mock operator object that will fail to alter + $script:mockOperator = [Microsoft.SqlServer.Management.Smo.Agent.Operator]::CreateTypeInstance() + $script:mockOperator.Name = 'TestOperator' + $script:mockOperator.EmailAddress = 'test@contoso.com' + $script:mockOperator.Enabled = $false + + # Mock parent objects for verbose messages + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.InstanceName = 'TestInstance' + $script:mockJobServer.Parent = $script:mockServerObject + $script:mockOperator.Parent = $script:mockJobServer + + # Add a method to throw exception when Alter() is called + $script:mockOperator | Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { + throw 'Failed to alter operator' + } -Force + } + + It 'Should throw terminating error when enabling fails' { + { Enable-SqlDscAgentOperator -OperatorObject $script:mockOperator -Force } | + Should -Throw -ExpectedMessage "*Failed to enable SQL Agent Operator 'TestOperator'*" + } + } +} diff --git a/tests/Unit/Public/Get-SqlDscAgentOperator.Tests.ps1 b/tests/Unit/Public/Get-SqlDscAgentOperator.Tests.ps1 new file mode 100644 index 0000000000..f202a0fca0 --- /dev/null +++ b/tests/Unit/Public/Get-SqlDscAgentOperator.Tests.ps1 @@ -0,0 +1,181 @@ +[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 build" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' + + # Load SMO stub types + Add-Type -Path "$PSScriptRoot/../Stubs/SMO.cs" + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + Remove-Item -Path 'Env:\SqlServerDscCI' -ErrorAction 'SilentlyContinue' + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:moduleName -All | Remove-Module -Force +} + +Describe 'Get-SqlDscAgentOperator' -Tag 'Public' { + Context 'When command has correct parameter sets' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = 'All' + ExpectedParameters = '-ServerObject []' + } + @{ + ExpectedParameterSetName = 'ByName' + ExpectedParameters = '-ServerObject -Name []' + } + ) { + $result = (Get-Command -Name 'Get-SqlDscAgentOperator').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 command has correct parameter properties' { + It 'Should have ServerObject as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'Get-SqlDscAgentOperator').Parameters['ServerObject'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have ServerObject accept pipeline input' { + $parameterInfo = (Get-Command -Name 'Get-SqlDscAgentOperator').Parameters['ServerObject'] + $parameterInfo.Attributes.ValueFromPipeline | Should -BeTrue + } + + It 'Should have Name as a mandatory parameter in ByName parameter set' { + $parameterInfo = (Get-Command -Name 'Get-SqlDscAgentOperator').Parameters['Name'] + $byNameParameterSet = $parameterInfo.ParameterSets['ByName'] + $byNameParameterSet.IsMandatory | Should -BeTrue + } + } + + Context 'When getting all operators' { + BeforeAll { + # Mock operator objects using SMO stub types + $script:mockOperator1 = [Microsoft.SqlServer.Management.Smo.Agent.Operator]::CreateTypeInstance() + $script:mockOperator1.Name = 'Operator1' + $script:mockOperator1.EmailAddress = 'operator1@contoso.com' + + $script:mockOperator2 = [Microsoft.SqlServer.Management.Smo.Agent.Operator]::CreateTypeInstance() + $script:mockOperator2.Name = 'Operator2' + $script:mockOperator2.EmailAddress = 'operator2@contoso.com' + + # Mock operator collection + $script:mockOperatorCollection = [Microsoft.SqlServer.Management.Smo.Agent.OperatorCollection]::CreateTypeInstance() + $script:mockOperatorCollection.Add($script:mockOperator1) + $script:mockOperatorCollection.Add($script:mockOperator2) + + # Mock JobServer object + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + $script:mockJobServer.Operators = $script:mockOperatorCollection + + # Mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.JobServer = $script:mockJobServer + } + + It 'Should return all operators when no name is specified' { + $result = Get-SqlDscAgentOperator -ServerObject $script:mockServerObject + + $result | Should -HaveCount 2 + $result[0].Name | Should -Be 'Operator1' + $result[1].Name | Should -Be 'Operator2' + } + } + + Context 'When getting a specific operator' { + BeforeAll { + # Mock operator objects using SMO stub types + $script:mockOperator1 = [Microsoft.SqlServer.Management.Smo.Agent.Operator]::CreateTypeInstance() + $script:mockOperator1.Name = 'TestOperator' + $script:mockOperator1.EmailAddress = 'test@contoso.com' + + # Mock operator collection + $script:mockOperatorCollection = [Microsoft.SqlServer.Management.Smo.Agent.OperatorCollection]::CreateTypeInstance() + $script:mockOperatorCollection.Add($script:mockOperator1) + + # Mock JobServer object + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + $script:mockJobServer.Operators = $script:mockOperatorCollection + + # Mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.JobServer = $script:mockJobServer + } + + It 'Should return specific operator when name matches' { + $result = Get-SqlDscAgentOperator -ServerObject $script:mockServerObject -Name 'TestOperator' + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'TestOperator' + $result.EmailAddress | Should -Be 'test@contoso.com' + } + + It 'Should return null when operator does not exist' { + $result = Get-SqlDscAgentOperator -ServerObject $script:mockServerObject -Name 'NonExistentOperator' + + $result | Should -BeNull + } + } + + Context 'When using pipeline input' { + BeforeAll { + # Mock operator collection + $script:mockOperatorCollection = [Microsoft.SqlServer.Management.Smo.Agent.OperatorCollection]::CreateTypeInstance() + + # Mock JobServer object + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + $script:mockJobServer.Operators = $script:mockOperatorCollection + + # Mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.JobServer = $script:mockJobServer + } + + It 'Should accept server object from pipeline' { + $result = $script:mockServerObject | Get-SqlDscAgentOperator + $result | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Public/Get-SqlDscConfigurationOption.Tests.ps1 b/tests/Unit/Public/Get-SqlDscConfigurationOption.Tests.ps1 index d8979f5330..d3b52be61f 100644 --- a/tests/Unit/Public/Get-SqlDscConfigurationOption.Tests.ps1 +++ b/tests/Unit/Public/Get-SqlDscConfigurationOption.Tests.ps1 @@ -52,13 +52,17 @@ AfterAll { Describe 'Get-SqlDscConfigurationOption' -Tag 'Public' { It 'Should have the correct parameters in parameter set ' -ForEach @( @{ - MockParameterSetName = '__AllParameterSets' - MockExpectedParameters = '[-ServerObject] [[-Name] ] [-Refresh] []' + MockParameterSetName = 'Metadata' + MockExpectedParameters = '-ServerObject [-Name ] [-Refresh] []' + } + @{ + MockParameterSetName = 'Raw' + MockExpectedParameters = '-ServerObject [-Name ] [-Raw] [-Refresh] []' } ) { $result = (Get-Command -Name 'Get-SqlDscConfigurationOption').ParameterSets | Where-Object -FilterScript { - $_.Name -eq $mockParameterSetName + $_.Name -eq $MockParameterSetName } | Select-Object -Property @( @{ @@ -75,7 +79,7 @@ Describe 'Get-SqlDscConfigurationOption' -Tag 'Public' { $result.ParameterListAsString | Should -Be $MockExpectedParameters } - Context 'When the specified configuration option exist' { + Context 'When the specified configuration option does not exist' { BeforeAll { $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | Add-Member -MemberType 'ScriptProperty' -Name 'Configuration' -Value { @@ -113,18 +117,24 @@ Describe 'Get-SqlDscConfigurationOption' -Tag 'Public' { Context 'When getting a specific configuration option' { BeforeAll { + $configOption1 = [Microsoft.SqlServer.Management.Smo.ConfigProperty]::CreateTypeInstance() + $configOption1.DisplayName = 'blocked process threshold (s)' + $configOption1.RunValue = 10 + $configOption1.ConfigValue = 10 + $configOption1.Minimum = 0 + $configOption1.Maximum = 86400 + $configOption1.IsDynamic = $true + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | Add-Member -MemberType 'ScriptProperty' -Name 'Configuration' -Value { - $configOption1 = [Microsoft.SqlServer.Management.Smo.ConfigProperty]::CreateTypeInstance() - $configOption1.DisplayName = 'blocked process threshold (s)' - - return @{ + return [PSCustomObject]@{ Properties = @($configOption1) + Refresh = { } } } -PassThru -Force } - It 'Should return the correct values' { + It 'Should return the correct metadata values by default' { $mockDefaultParameters = @{ ServerObject = $mockServerObject Name = 'blocked process threshold (s)' @@ -132,17 +142,43 @@ Describe 'Get-SqlDscConfigurationOption' -Tag 'Public' { $result = Get-SqlDscConfigurationOption @mockDefaultParameters - $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.ConfigProperty' + $result | Should -BeOfType 'PSCustomObject' + $result.PSTypeNames[0] | Should -Be 'SqlDsc.ConfigurationOption' + $result.Name | Should -Be 'blocked process threshold (s)' + $result.RunValue | Should -Be 10 + $result.ConfigValue | Should -Be 10 + $result.Minimum | Should -Be 0 + $result.Maximum | Should -Be 86400 + $result.IsDynamic | Should -BeTrue + } + + It 'Should return raw ConfigProperty when using -Raw switch' { + $mockDefaultParameters = @{ + ServerObject = $mockServerObject + Name = 'blocked process threshold (s)' + Raw = $true + } + + $result = Get-SqlDscConfigurationOption @mockDefaultParameters + + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.ConfigProperty' $result.DisplayName | Should -Be 'blocked process threshold (s)' } Context 'When passing parameter ServerObject over the pipeline' { - It 'Should return the correct values' { + It 'Should return the correct metadata values' { $result = $mockServerObject | Get-SqlDscConfigurationOption -Name 'blocked process threshold (s)' - $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.ConfigProperty' + $result | Should -BeOfType 'PSCustomObject' + $result.PSTypeNames[0] | Should -Be 'SqlDsc.ConfigurationOption' + $result.Name | Should -Be 'blocked process threshold (s)' + } + + It 'Should return raw ConfigProperty when using -Raw switch' { + $result = $mockServerObject | Get-SqlDscConfigurationOption -Name 'blocked process threshold (s)' -Raw + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.ConfigProperty' $result.DisplayName | Should -Be 'blocked process threshold (s)' } } @@ -150,38 +186,284 @@ Describe 'Get-SqlDscConfigurationOption' -Tag 'Public' { Context 'When getting all available configuration options' { BeforeAll { + $configOption1 = [Microsoft.SqlServer.Management.Smo.ConfigProperty]::CreateTypeInstance() + $configOption1.DisplayName = 'blocked process threshold (s)' + $configOption1.RunValue = 10 + $configOption1.ConfigValue = 10 + $configOption1.Minimum = 0 + $configOption1.Maximum = 86400 + $configOption1.IsDynamic = $true + + $configOption2 = [Microsoft.SqlServer.Management.Smo.ConfigProperty]::CreateTypeInstance() + $configOption2.DisplayName = 'show advanced options' + $configOption2.RunValue = 1 + $configOption2.ConfigValue = 1 + $configOption2.Minimum = 0 + $configOption2.Maximum = 1 + $configOption2.IsDynamic = $true + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | Add-Member -MemberType 'ScriptProperty' -Name 'Configuration' -Value { - $configOption1 = [Microsoft.SqlServer.Management.Smo.ConfigProperty]::CreateTypeInstance() - $configOption1.DisplayName = 'blocked process threshold (s)' - - $configOption2 = [Microsoft.SqlServer.Management.Smo.ConfigProperty]::CreateTypeInstance() - $configOption2.DisplayName = 'show advanced options' - - return @{ - Properties = @($configOption1, $configOption2) - } + $properties = @($configOption1, $configOption2) + return [PSCustomObject]@{ + Properties = $properties + Refresh = { } + } | Add-Member -MemberType ScriptMethod -Name 'ForEach' -Value { param($script) & $script } -PassThru } -PassThru -Force } - It 'Should return the correct values' { + It 'Should return the correct metadata values by default' { $result = Get-SqlDscConfigurationOption -ServerObject $mockServerObject - $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.ConfigProperty' + $result | Should -BeOfType 'PSCustomObject' + $result | Should -HaveCount 2 + $result[0].PSTypeNames[0] | Should -Be 'SqlDsc.ConfigurationOption' + $result.Name | Should -Contain 'show advanced options' + $result.Name | Should -Contain 'blocked process threshold (s)' + } + + It 'Should return raw ConfigProperty objects when using -Raw switch' { + $result = Get-SqlDscConfigurationOption -ServerObject $mockServerObject -Raw + + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.ConfigProperty' + $result | Should -HaveCount 2 $result.DisplayName | Should -Contain 'show advanced options' $result.DisplayName | Should -Contain 'blocked process threshold (s)' } Context 'When passing parameter ServerObject over the pipeline' { - It 'Should return the correct values' { + It 'Should return the correct metadata values' { $result = $mockServerObject | Get-SqlDscConfigurationOption - $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.ConfigProperty' + $result | Should -BeOfType 'PSCustomObject' + $result | Should -HaveCount 2 + $result[0].PSTypeNames[0] | Should -Be 'SqlDsc.ConfigurationOption' + $result.Name | Should -Contain 'show advanced options' + $result.Name | Should -Contain 'blocked process threshold (s)' + } + + It 'Should return raw ConfigProperty objects when using -Raw switch' { + $result = $mockServerObject | Get-SqlDscConfigurationOption -Raw + + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.ConfigProperty' + $result | Should -HaveCount 2 $result.DisplayName | Should -Contain 'show advanced options' $result.DisplayName | Should -Contain 'blocked process threshold (s)' } } } + + Context 'When testing argument completers through PowerShell tab completion system' { + BeforeAll { + # Import module to ensure tab completion is registered + Import-Module -Name $script:dscModuleName -Force + + # Create mock server object for testing + $script:mockServerForTabCompletion = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerForTabCompletion.Name = 'TestServer' + + # Create test configuration properties + $mockConfigProperty1 = [Microsoft.SqlServer.Management.Smo.ConfigProperty]::CreateTypeInstance() + $mockConfigProperty1.DisplayName = 'max server memory (MB)' + $mockConfigProperty1.ConfigValue = 2048 + $mockConfigProperty1.RunValue = 2048 + $mockConfigProperty1.Minimum = 0 + $mockConfigProperty1.Maximum = 2147483647 + + $mockConfigProperty2 = [Microsoft.SqlServer.Management.Smo.ConfigProperty]::CreateTypeInstance() + $mockConfigProperty2.DisplayName = 'cost threshold for parallelism' + $mockConfigProperty2.ConfigValue = 5 + $mockConfigProperty2.RunValue = 5 + $mockConfigProperty2.Minimum = 0 + $mockConfigProperty2.Maximum = 32767 + + $mockConfigProperty3 = [Microsoft.SqlServer.Management.Smo.ConfigProperty]::CreateTypeInstance() + $mockConfigProperty3.DisplayName = 'max degree of parallelism' + $mockConfigProperty3.ConfigValue = 0 + $mockConfigProperty3.RunValue = 0 + $mockConfigProperty3.Minimum = 0 + $mockConfigProperty3.Maximum = 32767 + + # Create configuration properties collection + $mockConfigurationProperties = [Microsoft.SqlServer.Management.Smo.ConfigPropertyCollection]::CreateTypeInstance() + $mockConfigurationProperties.Add($mockConfigProperty1) + $mockConfigurationProperties.Add($mockConfigProperty2) + $mockConfigurationProperties.Add($mockConfigProperty3) + + # Create configuration object + $mockConfiguration = [Microsoft.SqlServer.Management.Smo.Configuration]::CreateTypeInstance() + $mockConfiguration.Properties = $mockConfigurationProperties + + $script:mockServerForTabCompletion.Configuration = $mockConfiguration + + # Store the server object in a script variable that can be accessed by tab completion + $global:TestServerObject = $script:mockServerForTabCompletion + } + + AfterAll { + # Clean up global variable + if (Get-Variable -Name 'TestServerObject' -Scope Global -ErrorAction SilentlyContinue) { + Remove-Variable -Name 'TestServerObject' -Scope Global -Force + } + } + + It 'Should provide Name parameter completions through TabExpansion2' { + # This actually exercises the argument completer code through PowerShell's tab completion system + $inputScript = 'Get-SqlDscConfigurationOption -ServerObject $global:TestServerObject -Name max' + $result = TabExpansion2 -inputScript $inputScript -cursorColumn $inputScript.Length + + $result | Should -Not -BeNullOrEmpty + $result.CompletionMatches | Should -Not -BeNullOrEmpty + + # Should find configuration options containing "max" + $maxMemoryMatch = $result.CompletionMatches | Where-Object { + $_.CompletionText -eq "'max server memory (MB)'" + } + $maxMemoryMatch | Should -Not -BeNullOrEmpty + $maxMemoryMatch.ToolTip | Should -Match "Current: 2048" + + $maxDegreeMatch = $result.CompletionMatches | Where-Object { + $_.CompletionText -eq "'max degree of parallelism'" + } + $maxDegreeMatch | Should -Not -BeNullOrEmpty + $maxDegreeMatch.ToolTip | Should -Match "Current: 0" + } + + It 'Should handle partial Name completions through TabExpansion2' { + $inputScript = 'Get-SqlDscConfigurationOption -ServerObject $global:TestServerObject -Name cost' + $result = TabExpansion2 -inputScript $inputScript -cursorColumn $inputScript.Length + + $result.CompletionMatches | Should -HaveCount 1 + $result.CompletionMatches[0].CompletionText | Should -Be "'cost threshold for parallelism'" + $result.CompletionMatches[0].ToolTip | Should -Match "Current: 5" + } + + It 'Should execute Name argument completer code when retrieving command metadata' { + # This approach actually executes the argument completer code during command introspection + $command = Get-Command -Name 'Get-SqlDscConfigurationOption' + + # Create a mock parameter set to trigger argument completer execution + $parameterInfo = $command.Parameters['Name'] + $completerAttribute = $parameterInfo.Attributes | Where-Object { $_ -is [System.Management.Automation.ArgumentCompleterAttribute] } + + # This executes the completer in the module's context + $completions = & $completerAttribute.ScriptBlock 'Get-SqlDscConfigurationOption' 'Name' 'max' $null @{ + ServerObject = $script:mockServerForTabCompletion + } + + $completions | Should -Not -BeNullOrEmpty + $completions | Should -HaveCount 2 + + $completions[0].CompletionText | Should -Be "'max degree of parallelism'" + $completions[1].CompletionText | Should -Be "'max server memory (MB)'" + } + + It 'Should return sorted completions' { + $command = Get-Command -Name 'Get-SqlDscConfigurationOption' + $parameterInfo = $command.Parameters['Name'] + $completerAttribute = $parameterInfo.Attributes | Where-Object { $_ -is [System.Management.Automation.ArgumentCompleterAttribute] } + + $completions = & $completerAttribute.ScriptBlock 'Get-SqlDscConfigurationOption' 'Name' '' $null @{ + ServerObject = $script:mockServerForTabCompletion + } + + $completions | Should -HaveCount 3 + # Should be sorted alphabetically by DisplayName + $completions[0].CompletionText | Should -Be "'cost threshold for parallelism'" + $completions[1].CompletionText | Should -Be "'max degree of parallelism'" + $completions[2].CompletionText | Should -Be "'max server memory (MB)'" + } + + It 'Should provide detailed tooltip information' { + $command = Get-Command -Name 'Get-SqlDscConfigurationOption' + $parameterInfo = $command.Parameters['Name'] + $completerAttribute = $parameterInfo.Attributes | Where-Object { $_ -is [System.Management.Automation.ArgumentCompleterAttribute] } + + $completions = & $completerAttribute.ScriptBlock 'Get-SqlDscConfigurationOption' 'Name' 'max server' $null @{ + ServerObject = $script:mockServerForTabCompletion + } + + $completions | Should -HaveCount 1 + $completion = $completions[0] + $completion.ToolTip | Should -Match "Current: 2048" + $completion.ToolTip | Should -Match "Run: 2048" + $completion.ToolTip | Should -Match "Range: 0-2147483647" + } + + It 'Should handle wildcard patterns in completions' { + $command = Get-Command -Name 'Get-SqlDscConfigurationOption' + $parameterInfo = $command.Parameters['Name'] + $completerAttribute = $parameterInfo.Attributes | Where-Object { $_ -is [System.Management.Automation.ArgumentCompleterAttribute] } + + # Test with partial word that should match multiple options + $completions = & $completerAttribute.ScriptBlock 'Get-SqlDscConfigurationOption' 'Name' 'parallel' $null @{ + ServerObject = $script:mockServerForTabCompletion + } + + $completions | Should -HaveCount 2 + $completions[0].CompletionText | Should -Be "'cost threshold for parallelism'" + $completions[1].CompletionText | Should -Be "'max degree of parallelism'" + } + + It 'Should handle tab completion errors gracefully' { + # Create a server object that will cause an error + $badServer = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $badServer.Name = 'BadServer' + Add-Member -InputObject $badServer -MemberType ScriptProperty -Name 'Configuration' -Value { + throw 'Connection failed' + } -Force + $global:BadTestServerObject = $badServer + + try { + $inputScript = 'Get-SqlDscConfigurationOption -ServerObject $global:BadTestServerObject -Name test' + $result = TabExpansion2 -inputScript $inputScript -cursorColumn $inputScript.Length + + <# + Should not throw an error and should return empty from argument completer, but then + TabExpansion2 itself returns some default completions (like filesystem paths). + #> + $result | Should -BeOfType ([System.Management.Automation.CommandCompletion]) + $result.CompletionMatches.ListItemText | Should -Be 'tests' + } + finally { + Remove-Variable -Name 'BadTestServerObject' -Scope Global -Force -ErrorAction SilentlyContinue + } + } + + It 'Should handle missing ServerObject in tab completion gracefully' { + # Test tab completion without a ServerObject + $inputScript = 'Get-SqlDscConfigurationOption -Name test' + $result = TabExpansion2 -inputScript $inputScript -cursorColumn $inputScript.Length + + <# + Should not throw an error and should return empty from argument completer, but then + TabExpansion2 itself returns some default completions (like filesystem paths). + #> + $result | Should -BeOfType ([System.Management.Automation.CommandCompletion]) + $result.CompletionMatches.ListItemText | Should -Be 'tests' + } + + It 'Should handle invalid ServerObject type gracefully' { + # Test with wrong type of object + $invalidServer = 'Not a server object' + $global:InvalidTestServerObject = $invalidServer + + try { + $inputScript = 'Get-SqlDscConfigurationOption -ServerObject $global:InvalidTestServerObject -Name test' + $result = TabExpansion2 -inputScript $inputScript -cursorColumn $inputScript.Length + + <# + Should not throw an error and should return empty from argument completer, but then + TabExpansion2 itself returns some default completions (like filesystem paths). + #> + $result | Should -BeOfType ([System.Management.Automation.CommandCompletion]) + $result.CompletionMatches.ListItemText | Should -Be 'tests' + } + finally { + Remove-Variable -Name 'InvalidTestServerObject' -Scope Global -Force -ErrorAction SilentlyContinue + } + } + } } diff --git a/tests/Unit/Public/New-SqlDscAgentAlert.Tests.ps1 b/tests/Unit/Public/New-SqlDscAgentAlert.Tests.ps1 index 0cd93b6136..d47ef8051f 100644 --- a/tests/Unit/Public/New-SqlDscAgentAlert.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscAgentAlert.Tests.ps1 @@ -49,7 +49,7 @@ Describe 'New-SqlDscAgentAlert' -Tag 'Public' { It 'Should have the correct parameters in parameter set ' -ForEach @( @{ ExpectedParameterSetName = '__AllParameterSets' - ExpectedParameters = '[-ServerObject] [-Name] [[-Severity] ] [[-MessageId] ] [-PassThru] [-WhatIf] [-Confirm] []' + ExpectedParameters = '[-ServerObject] [-Name] [[-Severity] ] [[-MessageId] ] [-PassThru] [-Force] [-WhatIf] [-Confirm] []' } ) { $result = (Get-Command -Name 'New-SqlDscAgentAlert').ParameterSets | @@ -189,6 +189,50 @@ Describe 'New-SqlDscAgentAlert' -Tag 'Public' { Should -Invoke -CommandName 'Assert-BoundParameter' -Times 2 -Exactly } } + + Context 'When using pipeline input' { + It 'Should create alert with severity using pipeline input' { + $result = $script:mockServerObject | New-SqlDscAgentAlert -Name 'PipelineAlert1' -Severity 16 + + $result | Should -BeNullOrEmpty + Should -Invoke -CommandName 'Assert-BoundParameter' -Times 1 -Exactly + Should -Invoke -CommandName 'Get-AgentAlertObject' -Times 1 -Exactly + } + + It 'Should create alert with message ID using pipeline input' { + $result = $script:mockServerObject | New-SqlDscAgentAlert -Name 'PipelineAlert2' -MessageId 50001 + + $result | Should -BeNullOrEmpty + Should -Invoke -CommandName 'Assert-BoundParameter' -Times 1 -Exactly + Should -Invoke -CommandName 'Get-AgentAlertObject' -Times 1 -Exactly + } + + It 'Should create alert with PassThru using pipeline input' { + $result = $script:mockServerObject | New-SqlDscAgentAlert -Name 'PipelineAlert3' -Severity 16 -PassThru + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType [Microsoft.SqlServer.Management.Smo.Agent.Alert] + $result.Name | Should -Be 'PipelineAlert3' + $result.Severity | Should -Be 16 + } + + It 'Should create alert with Force parameter using pipeline input to cover ConfirmPreference assignment' { + $result = $script:mockServerObject | New-SqlDscAgentAlert -Name 'PipelineAlert4' -Severity 16 -Force + + $result | Should -BeNullOrEmpty + Should -Invoke -CommandName 'Assert-BoundParameter' -Times 1 -Exactly + Should -Invoke -CommandName 'Get-AgentAlertObject' -Times 1 -Exactly + } + + It 'Should create alert with Force and PassThru using pipeline input' { + $result = $script:mockServerObject | New-SqlDscAgentAlert -Name 'PipelineAlert5' -MessageId 60001 -Force -PassThru + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType [Microsoft.SqlServer.Management.Smo.Agent.Alert] + $result.Name | Should -Be 'PipelineAlert5' + $result.MessageID | Should -Be 60001 + } + } } Context 'When alert already exists' { diff --git a/tests/Unit/Public/New-SqlDscAgentOperator.Tests.ps1 b/tests/Unit/Public/New-SqlDscAgentOperator.Tests.ps1 new file mode 100644 index 0000000000..047411659c --- /dev/null +++ b/tests/Unit/Public/New-SqlDscAgentOperator.Tests.ps1 @@ -0,0 +1,348 @@ +[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 build" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' + + # Load SMO stub types + Add-Type -Path "$PSScriptRoot/../Stubs/SMO.cs" + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + Remove-Item -Path 'Env:\SqlServerDscCI' -ErrorAction 'SilentlyContinue' + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:moduleName -All | Remove-Module -Force +} + +Describe 'New-SqlDscAgentOperator' -Tag 'Public' { + Context 'When command has correct parameter sets' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = '__AllParameterSets' + ExpectedParameters = '[-ServerObject] [-Name] [[-EmailAddress] ] [[-CategoryName] ] [[-NetSendAddress] ] [[-PagerAddress] ] [[-PagerDays] ] [[-SaturdayPagerEndTime] ] [[-SaturdayPagerStartTime] ] [[-SundayPagerEndTime] ] [[-SundayPagerStartTime] ] [[-WeekdayPagerEndTime] ] [[-WeekdayPagerStartTime] ] [-PassThru] [-Force] [-Refresh] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'New-SqlDscAgentOperator').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 command has correct parameter properties' { + It 'Should have ServerObject as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'New-SqlDscAgentOperator').Parameters['ServerObject'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have ServerObject accept pipeline input' { + $parameterInfo = (Get-Command -Name 'New-SqlDscAgentOperator').Parameters['ServerObject'] + $parameterInfo.Attributes.ValueFromPipeline | Should -BeTrue + } + + It 'Should have Name as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'New-SqlDscAgentOperator').Parameters['Name'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + } + + Context 'When creating a new operator successfully' { + BeforeAll { + # Mock empty operator collection + $script:mockOperatorCollection = [Microsoft.SqlServer.Management.Smo.Agent.OperatorCollection]::CreateTypeInstance() + + # Mock JobServer object + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + $script:mockJobServer.Operators = $script:mockOperatorCollection + + # Mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.JobServer = $script:mockJobServer + $script:mockServerObject.InstanceName = 'TestInstance' + + # Mock Get-SqlDscAgentOperator to return null (operator doesn't exist) + Mock -CommandName 'Get-SqlDscAgentOperator' -MockWith { + return $null + } + } + + It 'Should call Create method on operator when creating new operator' { + # Reset counter + $script:mockJobServer.MockOperatorMethodCreateCalled = 0 + + New-SqlDscAgentOperator -Force -ServerObject $script:mockServerObject -Name 'TestOperator' -EmailAddress 'test@contoso.com' + + $script:mockJobServer.MockOperatorMethodCreateCalled | Should -Be 1 + } + + It 'Should create operator with email address when specified' { + # Reset counter + $script:mockJobServer.MockOperatorMethodCreateCalled = 0 + + $null = New-SqlDscAgentOperator -Force -ServerObject $script:mockServerObject -Name 'TestOperator' -EmailAddress 'test@contoso.com' + + $script:mockJobServer.MockOperatorMethodCreateCalled | Should -Be 1 + } + + It 'Should return operator object when PassThru is specified' { + # Reset counter + $script:mockJobServer.MockOperatorMethodCreateCalled = 0 + + $result = New-SqlDscAgentOperator -Force -ServerObject $script:mockServerObject -Name 'TestOperator' -EmailAddress 'test@contoso.com' -PassThru + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'TestOperator' + $script:mockJobServer.MockOperatorMethodCreateCalled | Should -Be 1 + } + + Context 'When using parameter WhatIf' { + It 'Should not perform any action when using WhatIf' { + # Reset counter + $script:mockJobServer.MockOperatorMethodCreateCalled = 0 + + $null = New-SqlDscAgentOperator -WhatIf -ServerObject $script:mockServerObject -Name 'TestOperator' -EmailAddress 'test@contoso.com' + + # Create method should not be called when using WhatIf + $script:mockJobServer.MockOperatorMethodCreateCalled | Should -Be 0 + } + } + + Context 'When passing parameter ServerObject over the pipeline' { + It 'Should create operator via pipeline without throwing an error' { + # Reset counter + $script:mockJobServer.MockOperatorMethodCreateCalled = 0 + + $null = $script:mockServerObject | New-SqlDscAgentOperator -Force -Name 'TestOperator' -EmailAddress 'test@contoso.com' + + $script:mockJobServer.MockOperatorMethodCreateCalled | Should -Be 1 + } + } + + Context 'When creating operator with different properties' { + It 'Should create operator with property set correctly' -ForEach @( + @{ + PropertyName = 'EmailAddress' + PropertyValue = 'test@contoso.com' + Parameters = @{ EmailAddress = 'test@contoso.com' } + } + @{ + PropertyName = 'CategoryName' + PropertyValue = 'TestCategory' + Parameters = @{ CategoryName = 'TestCategory' } + } + @{ + PropertyName = 'NetSendAddress' + PropertyValue = 'COMPUTER01' + Parameters = @{ NetSendAddress = 'COMPUTER01' } + } + @{ + PropertyName = 'PagerAddress' + PropertyValue = '555-123-4567' + Parameters = @{ PagerAddress = '555-123-4567' } + } + @{ + PropertyName = 'PagerDays' + PropertyValue = 'Weekdays' + Parameters = @{ PagerDays = 'Weekdays' } + } + @{ + PropertyName = 'SaturdayPagerEndTime' + PropertyValue = [System.TimeSpan]::new(18, 0, 0) + Parameters = @{ SaturdayPagerEndTime = [System.TimeSpan]::new(18, 0, 0) } + } + @{ + PropertyName = 'SaturdayPagerStartTime' + PropertyValue = [System.TimeSpan]::new(8, 0, 0) + Parameters = @{ SaturdayPagerStartTime = [System.TimeSpan]::new(8, 0, 0) } + } + @{ + PropertyName = 'SundayPagerEndTime' + PropertyValue = [System.TimeSpan]::new(17, 0, 0) + Parameters = @{ SundayPagerEndTime = [System.TimeSpan]::new(17, 0, 0) } + } + @{ + PropertyName = 'SundayPagerStartTime' + PropertyValue = [System.TimeSpan]::new(9, 0, 0) + Parameters = @{ SundayPagerStartTime = [System.TimeSpan]::new(9, 0, 0) } + } + @{ + PropertyName = 'WeekdayPagerEndTime' + PropertyValue = [System.TimeSpan]::new(17, 30, 0) + Parameters = @{ WeekdayPagerEndTime = [System.TimeSpan]::new(17, 30, 0) } + } + @{ + PropertyName = 'WeekdayPagerStartTime' + PropertyValue = [System.TimeSpan]::new(8, 30, 0) + Parameters = @{ WeekdayPagerStartTime = [System.TimeSpan]::new(8, 30, 0) } + } + ) { + # Reset counter + $script:mockJobServer.MockOperatorMethodCreateCalled = 0 + + # Create parameters hash with base parameters + $testParameters = @{ + Force = $true + ServerObject = $script:mockServerObject + Name = "TestOperator_$PropertyName" + PassThru = $true + } + + # Add the specific property being tested + $testParameters += $Parameters + + $result = New-SqlDscAgentOperator @testParameters + + # Verify the operator was created + $script:mockJobServer.MockOperatorMethodCreateCalled | Should -Be 1 + + # Verify the property was set correctly + $result.$PropertyName | Should -Be $PropertyValue + } + } + + Context 'When creating operator with multiple properties' { + It 'Should create operator with all properties set correctly' { + # Reset counter + $script:mockJobServer.MockOperatorMethodCreateCalled = 0 + + $testParameters = @{ + Force = $true + ServerObject = $script:mockServerObject + Name = 'CompleteTestOperator' + EmailAddress = 'admin@contoso.com' + CategoryName = 'DatabaseAdmins' + NetSendAddress = 'SQLSERVER01' + PagerAddress = '555-999-8888' + PagerDays = 'EveryDay' + SaturdayPagerStartTime = [System.TimeSpan]::new(8, 0, 0) + SaturdayPagerEndTime = [System.TimeSpan]::new(18, 0, 0) + SundayPagerStartTime = [System.TimeSpan]::new(9, 0, 0) + SundayPagerEndTime = [System.TimeSpan]::new(17, 0, 0) + WeekdayPagerStartTime = [System.TimeSpan]::new(8, 0, 0) + WeekdayPagerEndTime = [System.TimeSpan]::new(17, 0, 0) + PassThru = $true + } + + $result = New-SqlDscAgentOperator @testParameters + + # Verify the operator was created + $script:mockJobServer.MockOperatorMethodCreateCalled | Should -Be 1 + + # Verify all properties were set correctly + $result.Name | Should -Be 'CompleteTestOperator' + $result.EmailAddress | Should -Be 'admin@contoso.com' + $result.CategoryName | Should -Be 'DatabaseAdmins' + $result.NetSendAddress | Should -Be 'SQLSERVER01' + $result.PagerAddress | Should -Be '555-999-8888' + $result.PagerDays | Should -Be 'EveryDay' + $result.SaturdayPagerStartTime | Should -Be ([System.TimeSpan]::new(8, 0, 0)) + $result.SaturdayPagerEndTime | Should -Be ([System.TimeSpan]::new(18, 0, 0)) + $result.SundayPagerStartTime | Should -Be ([System.TimeSpan]::new(9, 0, 0)) + $result.SundayPagerEndTime | Should -Be ([System.TimeSpan]::new(17, 0, 0)) + $result.WeekdayPagerStartTime | Should -Be ([System.TimeSpan]::new(8, 0, 0)) + $result.WeekdayPagerEndTime | Should -Be ([System.TimeSpan]::new(17, 0, 0)) + } + } + } + + Context 'When operator already exists' { + BeforeAll { + # Mock existing operator + $script:mockExistingOperator = [Microsoft.SqlServer.Management.Smo.Agent.Operator]::CreateTypeInstance() + $script:mockExistingOperator.Name = 'ExistingOperator' + + # Mock operator collection with existing operator + $script:mockOperatorCollection = [Microsoft.SqlServer.Management.Smo.Agent.OperatorCollection]::CreateTypeInstance() + $script:mockOperatorCollection.Add($script:mockExistingOperator) + + # Mock JobServer object + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + $script:mockJobServer.Operators = $script:mockOperatorCollection + + # Mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.JobServer = $script:mockJobServer + } + + It 'Should throw when operator already exists' { + { New-SqlDscAgentOperator -Force -ServerObject $script:mockServerObject -Name 'ExistingOperator' -EmailAddress 'test@contoso.com' -ErrorAction 'Stop' } | + Should -Throw -ExpectedMessage '*SQL Agent Operator ''ExistingOperator'' already exists*' + } + } + + Context 'When create operation fails' { + BeforeAll { + # Mock empty operator collection + $script:mockOperatorCollection = [Microsoft.SqlServer.Management.Smo.Agent.OperatorCollection]::CreateTypeInstance() + + # Mock JobServer object + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + $script:mockJobServer.Operators = $script:mockOperatorCollection + + # Mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.JobServer = $script:mockJobServer + $script:mockServerObject.InstanceName = 'TestInstance' + + # Mock Get-SqlDscAgentOperator to return null (operator doesn't exist) + Mock -CommandName 'Get-SqlDscAgentOperator' -MockWith { + return $null + } + } + + It 'Should throw when create operation fails' { + { New-SqlDscAgentOperator -Force -ServerObject $script:mockServerObject -Name 'MockFailMethodCreateOperator' -EmailAddress 'test@contoso.com' -ErrorAction 'Stop' } | + Should -Throw -ExpectedMessage "*Failed to create SQL Agent Operator 'MockFailMethodCreateOperator'*" + } + + It 'Should increment Create method counter even when create operation fails' { + # Reset counter + $script:mockJobServer.MockOperatorMethodCreateCalled = 0 + + { New-SqlDscAgentOperator -Force -ServerObject $script:mockServerObject -Name 'MockFailMethodCreateOperator' -EmailAddress 'test@contoso.com' -ErrorAction 'Stop' } | + Should -Throw + + # Counter should be incremented since Create() method was called, even though it failed + $script:mockJobServer.MockOperatorMethodCreateCalled | Should -Be 1 + } + } +} diff --git a/tests/Unit/Public/Remove-SqlDscAgentOperator.Tests.ps1 b/tests/Unit/Public/Remove-SqlDscAgentOperator.Tests.ps1 new file mode 100644 index 0000000000..92ca0ce161 --- /dev/null +++ b/tests/Unit/Public/Remove-SqlDscAgentOperator.Tests.ps1 @@ -0,0 +1,254 @@ +[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 build" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' + + # Load SMO stub types + Add-Type -Path "$PSScriptRoot/../Stubs/SMO.cs" + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + Remove-Item -Path 'Env:\SqlServerDscCI' -ErrorAction 'SilentlyContinue' + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:moduleName -All | Remove-Module -Force +} + +Describe 'Remove-SqlDscAgentOperator' -Tag 'Public' { + Context 'When command has correct parameter sets' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = 'ByName' + ExpectedParameters = '-ServerObject -Name [-Force] [-Refresh] [-WhatIf] [-Confirm] []' + } + @{ + ExpectedParameterSetName = 'ByObject' + ExpectedParameters = '-OperatorObject [-Force] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'Remove-SqlDscAgentOperator').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 command has correct parameter properties' { + It 'Should have ServerObject as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'Remove-SqlDscAgentOperator').Parameters['ServerObject'] + $byNameParameterSet = $parameterInfo.ParameterSets['ByName'] + $byNameParameterSet.IsMandatory | Should -BeTrue + } + + It 'Should have ServerObject accept pipeline input' { + $parameterInfo = (Get-Command -Name 'Remove-SqlDscAgentOperator').Parameters['ServerObject'] + $parameterInfo.Attributes.ValueFromPipeline | Should -BeTrue + } + + It 'Should have OperatorObject as a mandatory parameter in ByObject parameter set' { + $parameterInfo = (Get-Command -Name 'Remove-SqlDscAgentOperator').Parameters['OperatorObject'] + $byObjectParameterSet = $parameterInfo.ParameterSets['ByObject'] + $byObjectParameterSet.IsMandatory | Should -BeTrue + } + + It 'Should have OperatorObject accept pipeline input in ByObject parameter set' { + $parameterInfo = (Get-Command -Name 'Remove-SqlDscAgentOperator').Parameters['OperatorObject'] + $parameterInfo.Attributes.ValueFromPipeline | Should -BeTrue + } + + It 'Should have Name as a mandatory parameter in ByName parameter set' { + $parameterInfo = (Get-Command -Name 'Remove-SqlDscAgentOperator').Parameters['Name'] + $byNameParameterSet = $parameterInfo.ParameterSets['ByName'] + $byNameParameterSet.IsMandatory | Should -BeTrue + } + } + + Context 'When removing operator using ByName parameter set' { + BeforeAll { + # Mock existing operator + $script:mockOperator = [Microsoft.SqlServer.Management.Smo.Agent.Operator]::CreateTypeInstance() + $script:mockOperator.Name = 'TestOperator' + $script:mockOperator.EmailAddress = 'test@contoso.com' + + # Mock operator collection with existing operator + $script:mockOperatorCollection = [Microsoft.SqlServer.Management.Smo.Agent.OperatorCollection]::CreateTypeInstance() + $script:mockOperatorCollection.Add($script:mockOperator) + + # Mock JobServer object with mock refresh method + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + $script:mockJobServer.Operators = $script:mockOperatorCollection + $script:mockOperatorCollection | Add-Member -MemberType ScriptMethod -Name 'Refresh' -Value { } -Force + + # Mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.JobServer = $script:mockJobServer + $script:mockServerObject.InstanceName = 'TestInstance' + + $script:mockMethodDropCallCount = 0 + $script:mockOperator | Add-Member -MemberType ScriptMethod -Name 'Drop' -Value { + $script:mockMethodDropCallCount++ + } -Force + + # Add Parent properties to establish the hierarchy + $script:mockOperator | Add-Member -MemberType NoteProperty -Name 'Parent' -Value $script:mockJobServer -Force + $script:mockJobServer | Add-Member -MemberType NoteProperty -Name 'Parent' -Value $script:mockServerObject -Force + } + + It 'Should remove operator when it exists' { + $script:mockMethodDropCallCount = 0 + + Mock -CommandName 'Get-AgentOperatorObject' -MockWith { return $script:mockOperator } + + $null = Remove-SqlDscAgentOperator -Force -ServerObject $script:mockServerObject -Name 'TestOperator' + + $script:mockMethodDropCallCount | Should -Be 1 + } + + It 'Should throw when operator does not exist and ErrorAction is Stop' { + { Remove-SqlDscAgentOperator -Force -ServerObject $script:mockServerObject -Name 'NonExistentOperator' -ErrorAction 'Stop' } | + Should -Throw -ExpectedMessage "*NonExistentOperator*not found*" + } + + It 'Should not throw when using SilentlyContinue and operator does not exist' { + $null = Remove-SqlDscAgentOperator -Force -ServerObject $script:mockServerObject -Name 'NonExistentOperator' -ErrorAction 'SilentlyContinue' + } + + Context 'When using parameter WhatIf' { + It 'Should not call Drop method when using WhatIf' { + $script:mockMethodDropCallCount = 0 + + $null = Remove-SqlDscAgentOperator -WhatIf -ServerObject $script:mockServerObject -Name 'TestOperator' + + $script:mockMethodDropCallCount | Should -Be 0 + } + } + + Context 'When passing parameter ServerObject over the pipeline' { + It 'Should remove operator using pipeline input' { + $script:mockMethodDropCallCount = 0 + + $script:mockServerObject | Remove-SqlDscAgentOperator -Force -Name 'TestOperator' + + $script:mockMethodDropCallCount | Should -Be 1 + } + } + } + + Context 'When removing operator using ByObject parameter set' { + BeforeAll { + # Mock existing operator with parent hierarchy + $script:mockOperator = [Microsoft.SqlServer.Management.Smo.Agent.Operator]::CreateTypeInstance() + $script:mockOperator.Name = 'TestOperator' + $script:mockOperator.EmailAddress = 'test@contoso.com' + + # Mock JobServer object + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + + # Mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.InstanceName = 'TestInstance' + + # Set up parent hierarchy + $script:mockOperator.Parent = $script:mockJobServer + $script:mockJobServer.Parent = $script:mockServerObject + + $script:mockMethodDropCallCount = 0 + $script:mockOperator | Add-Member -MemberType ScriptMethod -Name 'Drop' -Value { + $script:mockMethodDropCallCount++ + } -Force + } + + It 'Should remove operator when using operator object' { + $script:mockMethodDropCallCount = 0 + + $null = Remove-SqlDscAgentOperator -Force -OperatorObject $script:mockOperator + + $script:mockMethodDropCallCount | Should -Be 1 + } + + Context 'When passing parameter OperatorObject over the pipeline' { + It 'Should remove operator using pipeline input' { + $script:mockMethodDropCallCount = 0 + + $script:mockOperator | Remove-SqlDscAgentOperator -Force + + $script:mockMethodDropCallCount | Should -Be 1 + } + } + } + + Context 'When remove operation fails' { + BeforeAll { + # Mock existing operator + $script:mockOperator = [Microsoft.SqlServer.Management.Smo.Agent.Operator]::CreateTypeInstance() + $script:mockOperator.Name = 'TestOperator' + $script:mockOperator.EmailAddress = 'test@contoso.com' + + # Mock operator collection with existing operator + $script:mockOperatorCollection = [Microsoft.SqlServer.Management.Smo.Agent.OperatorCollection]::CreateTypeInstance() + $script:mockOperatorCollection.Add($script:mockOperator) + + # Mock JobServer object with mock refresh method + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + $script:mockJobServer.Operators = $script:mockOperatorCollection + $script:mockOperatorCollection | Add-Member -MemberType ScriptMethod -Name 'Refresh' -Value { } -Force + + # Mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.JobServer = $script:mockJobServer + $script:mockServerObject.InstanceName = 'TestInstance' + + # Set up parent hierarchy + $script:mockOperator.Parent = $script:mockJobServer + $script:mockJobServer.Parent = $script:mockServerObject + + $script:mockOperator | Add-Member -MemberType ScriptMethod -Name 'Drop' -Value { + throw 'Mocked drop failure' + } -Force + } + + It 'Should throw when drop operation fails' { + { Remove-SqlDscAgentOperator -Force -ServerObject $script:mockServerObject -Name 'TestOperator' -ErrorAction 'Stop' } | + Should -Throw -ExpectedMessage '*Failed to remove SQL Agent Operator ''TestOperator''*' + } + } +} diff --git a/tests/Unit/Public/Set-SqlDscAgentAlert.Tests.ps1 b/tests/Unit/Public/Set-SqlDscAgentAlert.Tests.ps1 index a8cdaf1d1e..0c29c5d592 100644 --- a/tests/Unit/Public/Set-SqlDscAgentAlert.Tests.ps1 +++ b/tests/Unit/Public/Set-SqlDscAgentAlert.Tests.ps1 @@ -1,4 +1,3 @@ -[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] param () @@ -132,7 +131,7 @@ Describe 'Set-SqlDscAgentAlert' -Tag 'Public' { } It 'Should update alert severity successfully' { - $null = Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 + $null = Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 -Force Should -Invoke -CommandName 'Assert-BoundParameter' -Times 1 -Exactly Should -Invoke -CommandName 'Get-AgentAlertObject' -Times 1 -Exactly @@ -145,7 +144,7 @@ Describe 'Set-SqlDscAgentAlert' -Tag 'Public' { $script:mockAlert.Severity = 0 $script:mockAlert.MessageId = 12345 - $null = Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 + $null = Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 -Force $script:mockAlert.Severity | Should -Be 16 $script:mockAlert.MessageId | Should -Be 0 @@ -156,7 +155,7 @@ Describe 'Set-SqlDscAgentAlert' -Tag 'Public' { $script:mockAlert.Severity = 15 $script:mockAlert.MessageId = 0 - $null = Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -MessageId 50001 + $null = Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -MessageId 50001 -Force $script:mockAlert.MessageId | Should -Be 50001 $script:mockAlert.Severity | Should -Be 0 @@ -170,21 +169,21 @@ Describe 'Set-SqlDscAgentAlert' -Tag 'Public' { $script:mockAlert.Severity = 10 $script:mockAlert.MessageId = 0 - $null = Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 + $null = Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 -Force $script:alterCalled | Should -BeTrue } Context 'When using PassThru parameter' { It 'Should update alert message ID successfully' { - $null = Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -MessageId 50001 + $null = Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -MessageId 50001 -Force $script:mockAlert.MessageId | Should -Be 50001 $script:mockAlert.Severity | Should -Be 0 # Should be set to 0 to avoid conflicts } It 'Should return alert object' { - $result = Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 -PassThru + $result = Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 -Force -PassThru $result | Should -BeOfType [Microsoft.SqlServer.Management.Smo.Agent.Alert] $result.Name | Should -Be 'TestAlert' @@ -192,15 +191,15 @@ Describe 'Set-SqlDscAgentAlert' -Tag 'Public' { } It 'Should not return alert object without PassThru' { - $result = Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 + $result = Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 -Force $result | Should -BeNullOrEmpty } } - Context 'When using PassThru parameter' { + Context 'When Refresh is not specified' { It 'Should not refresh server object when Refresh is not specified' { - $null = Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 + $null = Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 -Force $script:refreshCalled | Should -BeFalse } @@ -215,7 +214,7 @@ Describe 'Set-SqlDscAgentAlert' -Tag 'Public' { $script:mockAlert.Severity = 10 $script:mockAlert.MessageId = 0 - $null = Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 + $null = Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 -Force $script:alterCalled | Should -BeTrue $script:mockAlert.Severity | Should -Be 16 @@ -230,7 +229,7 @@ Describe 'Set-SqlDscAgentAlert' -Tag 'Public' { $script:mockAlert.Severity = 0 $script:mockAlert.MessageId = 12345 - $null = Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -MessageId 50001 + $null = Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -MessageId 50001 -Force $script:alterCalled | Should -BeTrue $script:mockAlert.MessageId | Should -Be 50001 @@ -251,7 +250,7 @@ Describe 'Set-SqlDscAgentAlert' -Tag 'Public' { } It 'Should update alert using AlertObject parameter' { - $null = Set-SqlDscAgentAlert -AlertObject $script:mockAlert -Severity 16 + $null = Set-SqlDscAgentAlert -AlertObject $script:mockAlert -Severity 16 -Force Should -Invoke -CommandName 'Assert-BoundParameter' -ModuleName $script:dscModuleName -Times 1 -Exactly $script:mockAlert.Severity | Should -Be 16 @@ -263,7 +262,7 @@ Describe 'Set-SqlDscAgentAlert' -Tag 'Public' { $script:mockAlert.Severity = 10 $script:mockAlert.MessageId = 12345 - $null = Set-SqlDscAgentAlert -AlertObject $script:mockAlert -Severity 16 + $null = Set-SqlDscAgentAlert -AlertObject $script:mockAlert -Severity 16 -Force $script:mockAlert.Severity | Should -Be 16 $script:mockAlert.MessageId | Should -Be 0 @@ -274,7 +273,7 @@ Describe 'Set-SqlDscAgentAlert' -Tag 'Public' { $script:mockAlert.Severity = 15 $script:mockAlert.MessageId = 0 - $null = Set-SqlDscAgentAlert -AlertObject $script:mockAlert -MessageId 50001 + $null = Set-SqlDscAgentAlert -AlertObject $script:mockAlert -MessageId 50001 -Force $script:mockAlert.MessageId | Should -Be 50001 $script:mockAlert.Severity | Should -Be 0 @@ -288,7 +287,7 @@ Describe 'Set-SqlDscAgentAlert' -Tag 'Public' { $script:mockAlert.Severity = 10 $script:mockAlert.MessageId = 0 - $null = Set-SqlDscAgentAlert -AlertObject $script:mockAlert -Severity 16 + $null = Set-SqlDscAgentAlert -AlertObject $script:mockAlert -Severity 16 -Force $script:alterCalled | Should -BeTrue } @@ -305,7 +304,7 @@ Describe 'Set-SqlDscAgentAlert' -Tag 'Public' { } It 'Should throw error when alert does not exist' { - { Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'NonExistentAlert' -Severity 16 } | + { Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'NonExistentAlert' -Severity 16 -Force } | Should -Throw -ExpectedMessage '*was not found*' } } @@ -334,7 +333,7 @@ Describe 'Set-SqlDscAgentAlert' -Tag 'Public' { $script:mockAlert.Severity = 14 $script:mockAlert.MessageId = 0 - Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 14 + Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 14 -Force $script:alterCalled | Should -BeFalse } @@ -347,7 +346,7 @@ Describe 'Set-SqlDscAgentAlert' -Tag 'Public' { $script:mockAlert.Severity = 0 $script:mockAlert.MessageId = 50001 - Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -MessageId 50001 + Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -MessageId 50001 -Force $script:alterCalled | Should -BeFalse } @@ -360,7 +359,7 @@ Describe 'Set-SqlDscAgentAlert' -Tag 'Public' { $script:mockAlert.Severity = 14 $script:mockAlert.MessageId = 0 - Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 14 -MessageId 0 + Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 14 -MessageId 0 -Force $script:alterCalled | Should -BeFalse } @@ -385,7 +384,7 @@ Describe 'Set-SqlDscAgentAlert' -Tag 'Public' { } It 'Should throw error when update fails' { - { Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 } | + { Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 -Force } | Should -Throw -ExpectedMessage '*Failed to update*' } } diff --git a/tests/Unit/Public/Set-SqlDscAgentOperator.Tests.ps1 b/tests/Unit/Public/Set-SqlDscAgentOperator.Tests.ps1 new file mode 100644 index 0000000000..6c4c76d772 --- /dev/null +++ b/tests/Unit/Public/Set-SqlDscAgentOperator.Tests.ps1 @@ -0,0 +1,539 @@ +[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 build" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' + + # Load SMO stub types + Add-Type -Path "$PSScriptRoot/../Stubs/SMO.cs" + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + Remove-Item -Path 'Env:\SqlServerDscCI' -ErrorAction 'SilentlyContinue' + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:moduleName -All | Remove-Module -Force +} + +Describe 'Set-SqlDscAgentOperator' -Tag 'Public' { + Context 'When command has correct parameter sets' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = 'ByName' + ExpectedParameters = '-ServerObject -Name [-EmailAddress ] [-CategoryName ] [-NetSendAddress ] [-PagerAddress ] [-PagerDays ] [-SaturdayPagerEndTime ] [-SaturdayPagerStartTime ] [-SundayPagerEndTime ] [-SundayPagerStartTime ] [-WeekdayPagerEndTime ] [-WeekdayPagerStartTime ] [-Force] [-WhatIf] [-Confirm] []' + } + @{ + ExpectedParameterSetName = 'ByObject' + ExpectedParameters = '-OperatorObject [-EmailAddress ] [-CategoryName ] [-NetSendAddress ] [-PagerAddress ] [-PagerDays ] [-SaturdayPagerEndTime ] [-SaturdayPagerStartTime ] [-SundayPagerEndTime ] [-SundayPagerStartTime ] [-WeekdayPagerEndTime ] [-WeekdayPagerStartTime ] [-Force] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'Set-SqlDscAgentOperator').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 command has correct parameter properties' { + It 'Should have ServerObject as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'Set-SqlDscAgentOperator').Parameters['ServerObject'] + $byNameParameterSet = $parameterInfo.ParameterSets['ByName'] + $byNameParameterSet.IsMandatory | Should -BeTrue + } + + It 'Should have ServerObject accept pipeline input' { + $parameterInfo = (Get-Command -Name 'Set-SqlDscAgentOperator').Parameters['ServerObject'] + $parameterInfo.Attributes.ValueFromPipeline | Should -BeTrue + } + + It 'Should have OperatorObject as a mandatory parameter in ByObject parameter set' { + $parameterInfo = (Get-Command -Name 'Set-SqlDscAgentOperator').Parameters['OperatorObject'] + $byObjectParameterSet = $parameterInfo.ParameterSets['ByObject'] + $byObjectParameterSet.IsMandatory | Should -BeTrue + } + + It 'Should have OperatorObject accept pipeline input in ByObject parameter set' { + $parameterInfo = (Get-Command -Name 'Set-SqlDscAgentOperator').Parameters['OperatorObject'] + $parameterInfo.Attributes.ValueFromPipeline | Should -BeTrue + } + + It 'Should have Name as a mandatory parameter in ByName parameter set' { + $parameterInfo = (Get-Command -Name 'Set-SqlDscAgentOperator').Parameters['Name'] + $byNameParameterSet = $parameterInfo.ParameterSets['ByName'] + $byNameParameterSet.IsMandatory | Should -BeTrue + } + } + + Context 'When parameter validation is performed' { + It 'Should throw when no settable parameters are provided' { + $mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + + { Set-SqlDscAgentOperator -ServerObject $mockServerObject -Name 'TestOperator' -Force } | Should -Throw -ExpectedMessage '*At least one*' + } + + It 'Should successfully execute when at least one settable parameter is provided' { + # Create proper mock structure + $mockOperator = [Microsoft.SqlServer.Management.Smo.Agent.Operator]::CreateTypeInstance() + $mockOperator.Name = 'TestOperator' + $mockOperator.EmailAddress = 'old@contoso.com' + + $mockOperatorCollection = [Microsoft.SqlServer.Management.Smo.Agent.OperatorCollection]::CreateTypeInstance() + $mockOperatorCollection.Add($mockOperator) + $mockOperatorCollection | Add-Member -MemberType ScriptMethod -Name 'Refresh' -Value { } -Force + + $mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + $mockJobServer.Operators = $mockOperatorCollection + + $mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $mockServerObject.JobServer = $mockJobServer + $mockServerObject.InstanceName = 'TestInstance' + + $mockMethodAlterCallCount = 0 + $mockOperator | Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { + $mockMethodAlterCallCount++ + } -Force + + Set-SqlDscAgentOperator -ServerObject $mockServerObject -Name 'TestOperator' -EmailAddress 'test@contoso.com' -WhatIf + + # Verify that WhatIf prevents Alter from being called + $mockMethodAlterCallCount | Should -Be 0 + # Verify that the operator object is found and accessible + $mockOperator.Name | Should -Be 'TestOperator' + } + } + + Context 'When updating operator using ByName parameter set' { + BeforeAll { + # Mock existing operator + $script:mockOperator = [Microsoft.SqlServer.Management.Smo.Agent.Operator]::CreateTypeInstance() + $script:mockOperator.Name = 'TestOperator' + $script:mockOperator.EmailAddress = 'old@contoso.com' + + # Mock operator collection with existing operator + $script:mockOperatorCollection = [Microsoft.SqlServer.Management.Smo.Agent.OperatorCollection]::CreateTypeInstance() + $script:mockOperatorCollection.Add($script:mockOperator) + + # Mock JobServer object with mock refresh method + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + $script:mockJobServer.Operators = $script:mockOperatorCollection + $script:mockOperatorCollection | Add-Member -MemberType ScriptMethod -Name 'Refresh' -Value { } -Force + + # Mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.JobServer = $script:mockJobServer + $script:mockServerObject.InstanceName = 'TestInstance' + + $script:mockMethodAlterCallCount = 0 + $script:mockOperator | Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { + $script:mockMethodAlterCallCount++ + } -Force + } + + It 'Should update operator email address when specified' { + $script:mockMethodAlterCallCount = 0 + $script:mockOperator.EmailAddress = 'old@contoso.com' + + $null = Set-SqlDscAgentOperator -Force -ServerObject $script:mockServerObject -Name 'TestOperator' -EmailAddress 'new@contoso.com' + + $script:mockOperator.EmailAddress | Should -Be 'new@contoso.com' + $script:mockMethodAlterCallCount | Should -Be 1 + } + + It 'Should update when email address is already correct (always set user-specified properties)' { + $script:mockMethodAlterCallCount = 0 + $script:mockOperator.EmailAddress = 'correct@contoso.com' + + $null = Set-SqlDscAgentOperator -Force -ServerObject $script:mockServerObject -Name 'TestOperator' -EmailAddress 'correct@contoso.com' + + $script:mockMethodAlterCallCount | Should -Be 1 + } + + It 'Should throw when operator does not exist' { + { Set-SqlDscAgentOperator -Force -ServerObject $script:mockServerObject -Name 'NonExistentOperator' -EmailAddress 'test@contoso.com' -ErrorAction 'Stop' } | + Should -Throw -ExpectedMessage '*SQL Agent Operator ''NonExistentOperator'' was not found*' + } + + Context 'When using parameter WhatIf' { + It 'Should not call Alter method when using WhatIf' { + $script:mockMethodAlterCallCount = 0 + $script:mockOperator.EmailAddress = 'old@contoso.com' + + $null = Set-SqlDscAgentOperator -WhatIf -ServerObject $script:mockServerObject -Name 'TestOperator' -EmailAddress 'new@contoso.com' + + $script:mockMethodAlterCallCount | Should -Be 0 + } + } + + Context 'When passing parameter ServerObject over the pipeline' { + It 'Should update operator email address using pipeline input' { + $script:mockMethodAlterCallCount = 0 + $script:mockOperator.EmailAddress = 'old@contoso.com' + + $script:mockServerObject | Set-SqlDscAgentOperator -Force -Name 'TestOperator' -EmailAddress 'new@contoso.com' + + $script:mockOperator.EmailAddress | Should -Be 'new@contoso.com' + $script:mockMethodAlterCallCount | Should -Be 1 + } + } + + Context 'When updating operator with different properties' { + It 'Should update operator with property set correctly' -ForEach @( + @{ + PropertyName = 'EmailAddress' + PropertyValue = 'updated@contoso.com' + Parameters = @{ EmailAddress = 'updated@contoso.com' } + } + @{ + PropertyName = 'CategoryName' + PropertyValue = 'UpdatedCategory' + Parameters = @{ CategoryName = 'UpdatedCategory' } + } + @{ + PropertyName = 'NetSendAddress' + PropertyValue = 'COMPUTER02' + Parameters = @{ NetSendAddress = 'COMPUTER02' } + } + @{ + PropertyName = 'PagerAddress' + PropertyValue = '555-987-6543' + Parameters = @{ PagerAddress = '555-987-6543' } + } + @{ + PropertyName = 'PagerDays' + PropertyValue = 64 -bor 1 # Saturday and Sunday + Parameters = @{ PagerDays = 64 -bor 1 } # Saturday and Sunday + } + @{ + PropertyName = 'SaturdayPagerEndTime' + PropertyValue = [System.TimeSpan]::new(20, 0, 0) + Parameters = @{ SaturdayPagerEndTime = [System.TimeSpan]::new(20, 0, 0) } + } + @{ + PropertyName = 'SaturdayPagerStartTime' + PropertyValue = [System.TimeSpan]::new(9, 0, 0) + Parameters = @{ SaturdayPagerStartTime = [System.TimeSpan]::new(9, 0, 0) } + } + @{ + PropertyName = 'SundayPagerEndTime' + PropertyValue = [System.TimeSpan]::new(19, 0, 0) + Parameters = @{ SundayPagerEndTime = [System.TimeSpan]::new(19, 0, 0) } + } + @{ + PropertyName = 'SundayPagerStartTime' + PropertyValue = [System.TimeSpan]::new(10, 0, 0) + Parameters = @{ SundayPagerStartTime = [System.TimeSpan]::new(10, 0, 0) } + } + @{ + PropertyName = 'WeekdayPagerEndTime' + PropertyValue = [System.TimeSpan]::new(18, 30, 0) + Parameters = @{ WeekdayPagerEndTime = [System.TimeSpan]::new(18, 30, 0) } + } + @{ + PropertyName = 'WeekdayPagerStartTime' + PropertyValue = [System.TimeSpan]::new(7, 30, 0) + Parameters = @{ WeekdayPagerStartTime = [System.TimeSpan]::new(7, 30, 0) } + } + ) { + # Reset counter and set initial values + $script:mockMethodAlterCallCount = 0 + + # Set different initial values to ensure the property is actually being updated + switch ($PropertyName) { + 'EmailAddress' { $script:mockOperator.EmailAddress = 'old@contoso.com' } + 'CategoryName' { $script:mockOperator.CategoryName = 'OldCategory' } + 'NetSendAddress' { $script:mockOperator.NetSendAddress = 'OLDCOMPUTER' } + 'PagerAddress' { $script:mockOperator.PagerAddress = '555-000-0000' } + 'PagerDays' { $script:mockOperator.PagerDays = [Microsoft.SqlServer.Management.Smo.Agent.WeekDays]::Weekdays } + 'SaturdayPagerEndTime' { $script:mockOperator.SaturdayPagerEndTime = [System.TimeSpan]::new(17, 0, 0) } + 'SaturdayPagerStartTime' { $script:mockOperator.SaturdayPagerStartTime = [System.TimeSpan]::new(8, 0, 0) } + 'SundayPagerEndTime' { $script:mockOperator.SundayPagerEndTime = [System.TimeSpan]::new(16, 0, 0) } + 'SundayPagerStartTime' { $script:mockOperator.SundayPagerStartTime = [System.TimeSpan]::new(9, 0, 0) } + 'WeekdayPagerEndTime' { $script:mockOperator.WeekdayPagerEndTime = [System.TimeSpan]::new(17, 0, 0) } + 'WeekdayPagerStartTime' { $script:mockOperator.WeekdayPagerStartTime = [System.TimeSpan]::new(8, 0, 0) } + } + + # Create parameters hash with base parameters + $testParameters = @{ + Force = $true + ServerObject = $script:mockServerObject + Name = 'TestOperator' + } + + # Add the specific property being tested + $testParameters += $Parameters + + Set-SqlDscAgentOperator @testParameters + + # Verify the operator was updated + $script:mockMethodAlterCallCount | Should -Be 1 + + # Verify the property was set correctly + $script:mockOperator.$PropertyName | Should -Be $PropertyValue + } + } + + Context 'When updating operator with multiple properties' { + It 'Should update operator with all properties set correctly' { + # Reset counter and set initial values + $script:mockMethodAlterCallCount = 0 + $script:mockOperator.EmailAddress = 'old@contoso.com' + $script:mockOperator.CategoryName = 'OldCategory' + $script:mockOperator.NetSendAddress = 'OLDCOMPUTER' + $script:mockOperator.PagerAddress = '555-000-0000' + $script:mockOperator.PagerDays = [Microsoft.SqlServer.Management.Smo.Agent.WeekDays]::Weekdays + $script:mockOperator.SaturdayPagerStartTime = [System.TimeSpan]::new(8, 0, 0) + $script:mockOperator.SaturdayPagerEndTime = [System.TimeSpan]::new(17, 0, 0) + $script:mockOperator.SundayPagerStartTime = [System.TimeSpan]::new(9, 0, 0) + $script:mockOperator.SundayPagerEndTime = [System.TimeSpan]::new(16, 0, 0) + $script:mockOperator.WeekdayPagerStartTime = [System.TimeSpan]::new(8, 0, 0) + $script:mockOperator.WeekdayPagerEndTime = [System.TimeSpan]::new(17, 0, 0) + + $testParameters = @{ + Force = $true + ServerObject = $script:mockServerObject + Name = 'TestOperator' + EmailAddress = 'admin@contoso.com' + CategoryName = 'DatabaseAdmins' + NetSendAddress = 'SQLSERVER01' + PagerAddress = '555-999-8888' + PagerDays = [Microsoft.SqlServer.Management.Smo.Agent.WeekDays]::EveryDay + SaturdayPagerStartTime = [System.TimeSpan]::new(10, 0, 0) + SaturdayPagerEndTime = [System.TimeSpan]::new(20, 0, 0) + SundayPagerStartTime = [System.TimeSpan]::new(11, 0, 0) + SundayPagerEndTime = [System.TimeSpan]::new(19, 0, 0) + WeekdayPagerStartTime = [System.TimeSpan]::new(7, 0, 0) + WeekdayPagerEndTime = [System.TimeSpan]::new(18, 0, 0) + } + + Set-SqlDscAgentOperator @testParameters + + # Verify the operator was updated + $script:mockMethodAlterCallCount | Should -Be 1 + + # Verify all properties were set correctly + $script:mockOperator.EmailAddress | Should -Be 'admin@contoso.com' + $script:mockOperator.CategoryName | Should -Be 'DatabaseAdmins' + $script:mockOperator.NetSendAddress | Should -Be 'SQLSERVER01' + $script:mockOperator.PagerAddress | Should -Be '555-999-8888' + $script:mockOperator.PagerDays | Should -Be ([Microsoft.SqlServer.Management.Smo.Agent.WeekDays]::EveryDay) + $script:mockOperator.SaturdayPagerStartTime | Should -Be ([System.TimeSpan]::new(10, 0, 0)) + $script:mockOperator.SaturdayPagerEndTime | Should -Be ([System.TimeSpan]::new(20, 0, 0)) + $script:mockOperator.SundayPagerStartTime | Should -Be ([System.TimeSpan]::new(11, 0, 0)) + $script:mockOperator.SundayPagerEndTime | Should -Be ([System.TimeSpan]::new(19, 0, 0)) + $script:mockOperator.WeekdayPagerStartTime | Should -Be ([System.TimeSpan]::new(7, 0, 0)) + $script:mockOperator.WeekdayPagerEndTime | Should -Be ([System.TimeSpan]::new(18, 0, 0)) + } + } + } + + Context 'When updating operator using ByObject parameter set' { + BeforeAll { + # Mock existing operator with parent hierarchy + $script:mockOperator = [Microsoft.SqlServer.Management.Smo.Agent.Operator]::CreateTypeInstance() + $script:mockOperator.Name = 'TestOperator' + $script:mockOperator.EmailAddress = 'old@contoso.com' + + # Mock JobServer object + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + + # Mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.InstanceName = 'TestInstance' + + # Set up parent hierarchy + $script:mockOperator.Parent = $script:mockJobServer + $script:mockJobServer.Parent = $script:mockServerObject + + $script:mockMethodAlterCallCount = 0 + $script:mockOperator | Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { + $script:mockMethodAlterCallCount++ + } -Force + } + + It 'Should update operator email address when using operator object' { + $script:mockMethodAlterCallCount = 0 + $script:mockOperator.EmailAddress = 'old@contoso.com' + + $null = Set-SqlDscAgentOperator -Force -OperatorObject $script:mockOperator -EmailAddress 'new@contoso.com' + + $script:mockOperator.EmailAddress | Should -Be 'new@contoso.com' + $script:mockMethodAlterCallCount | Should -Be 1 + } + + Context 'When passing parameter OperatorObject over the pipeline' { + It 'Should update operator email address using pipeline input' { + $script:mockMethodAlterCallCount = 0 + $script:mockOperator.EmailAddress = 'old@contoso.com' + + $script:mockOperator | Set-SqlDscAgentOperator -Force -EmailAddress 'new@contoso.com' + + $script:mockOperator.EmailAddress | Should -Be 'new@contoso.com' + $script:mockMethodAlterCallCount | Should -Be 1 + } + } + + Context 'When updating operator with different properties using ByObject parameter set' { + It 'Should update operator with property set correctly using operator object' -ForEach @( + @{ + PropertyName = 'EmailAddress' + PropertyValue = 'updated@contoso.com' + Parameters = @{ EmailAddress = 'updated@contoso.com' } + } + @{ + PropertyName = 'CategoryName' + PropertyValue = 'UpdatedCategory' + Parameters = @{ CategoryName = 'UpdatedCategory' } + } + @{ + PropertyName = 'NetSendAddress' + PropertyValue = 'COMPUTER02' + Parameters = @{ NetSendAddress = 'COMPUTER02' } + } + @{ + PropertyName = 'PagerAddress' + PropertyValue = '555-987-6543' + Parameters = @{ PagerAddress = '555-987-6543' } + } + @{ + PropertyName = 'PagerDays' + PropertyValue = 64 -bor 1 # Saturday and Sunday + Parameters = @{ PagerDays = 64 -bor 1 } # Saturday and Sunday + } + @{ + PropertyName = 'SaturdayPagerEndTime' + PropertyValue = [System.TimeSpan]::new(20, 0, 0) + Parameters = @{ SaturdayPagerEndTime = [System.TimeSpan]::new(20, 0, 0) } + } + @{ + PropertyName = 'SaturdayPagerStartTime' + PropertyValue = [System.TimeSpan]::new(9, 0, 0) + Parameters = @{ SaturdayPagerStartTime = [System.TimeSpan]::new(9, 0, 0) } + } + @{ + PropertyName = 'SundayPagerEndTime' + PropertyValue = [System.TimeSpan]::new(19, 0, 0) + Parameters = @{ SundayPagerEndTime = [System.TimeSpan]::new(19, 0, 0) } + } + @{ + PropertyName = 'SundayPagerStartTime' + PropertyValue = [System.TimeSpan]::new(10, 0, 0) + Parameters = @{ SundayPagerStartTime = [System.TimeSpan]::new(10, 0, 0) } + } + @{ + PropertyName = 'WeekdayPagerEndTime' + PropertyValue = [System.TimeSpan]::new(18, 30, 0) + Parameters = @{ WeekdayPagerEndTime = [System.TimeSpan]::new(18, 30, 0) } + } + @{ + PropertyName = 'WeekdayPagerStartTime' + PropertyValue = [System.TimeSpan]::new(7, 30, 0) + Parameters = @{ WeekdayPagerStartTime = [System.TimeSpan]::new(7, 30, 0) } + } + ) { + # Reset counter and set initial values + $script:mockMethodAlterCallCount = 0 + + # Set different initial values to ensure the property is actually being updated + switch ($PropertyName) { + 'EmailAddress' { $script:mockOperator.EmailAddress = 'old@contoso.com' } + 'CategoryName' { $script:mockOperator.CategoryName = 'OldCategory' } + 'NetSendAddress' { $script:mockOperator.NetSendAddress = 'OLDCOMPUTER' } + 'PagerAddress' { $script:mockOperator.PagerAddress = '555-000-0000' } + 'PagerDays' { $script:mockOperator.PagerDays = [Microsoft.SqlServer.Management.Smo.Agent.WeekDays]::Weekdays } + 'SaturdayPagerEndTime' { $script:mockOperator.SaturdayPagerEndTime = [System.TimeSpan]::new(17, 0, 0) } + 'SaturdayPagerStartTime' { $script:mockOperator.SaturdayPagerStartTime = [System.TimeSpan]::new(8, 0, 0) } + 'SundayPagerEndTime' { $script:mockOperator.SundayPagerEndTime = [System.TimeSpan]::new(16, 0, 0) } + 'SundayPagerStartTime' { $script:mockOperator.SundayPagerStartTime = [System.TimeSpan]::new(9, 0, 0) } + 'WeekdayPagerEndTime' { $script:mockOperator.WeekdayPagerEndTime = [System.TimeSpan]::new(17, 0, 0) } + 'WeekdayPagerStartTime' { $script:mockOperator.WeekdayPagerStartTime = [System.TimeSpan]::new(8, 0, 0) } + } + + # Create parameters hash with base parameters + $testParameters = @{ + Force = $true + OperatorObject = $script:mockOperator + } + + # Add the specific property being tested + $testParameters += $Parameters + + Set-SqlDscAgentOperator @testParameters + + # Verify the operator was updated + $script:mockMethodAlterCallCount | Should -Be 1 + + # Verify the property was set correctly + $script:mockOperator.$PropertyName | Should -Be $PropertyValue + } + } + } + + Context 'When update operation fails' { + BeforeAll { + # Mock existing operator + $script:mockOperator = [Microsoft.SqlServer.Management.Smo.Agent.Operator]::CreateTypeInstance() + $script:mockOperator.Name = 'TestOperator' + $script:mockOperator.EmailAddress = 'old@contoso.com' + + # Mock operator collection with existing operator + $script:mockOperatorCollection = [Microsoft.SqlServer.Management.Smo.Agent.OperatorCollection]::CreateTypeInstance() + $script:mockOperatorCollection.Add($script:mockOperator) + + # Mock JobServer object with mock refresh method + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + $script:mockJobServer.Operators = $script:mockOperatorCollection + $script:mockOperatorCollection | Add-Member -MemberType ScriptMethod -Name 'Refresh' -Value { } -Force + + # Mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.JobServer = $script:mockJobServer + + $script:mockOperator | Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { + throw 'Mocked alter failure' + } -Force + } + + It 'Should throw when alter operation fails' { + { Set-SqlDscAgentOperator -Force -ServerObject $script:mockServerObject -Name 'TestOperator' -EmailAddress 'new@contoso.com' -ErrorAction 'Stop' } | + Should -Throw -ExpectedMessage '*Failed to update SQL Agent Operator ''TestOperator''*' + } + } +} diff --git a/tests/Unit/Public/Set-SqlDscConfigurationOption.Tests.ps1 b/tests/Unit/Public/Set-SqlDscConfigurationOption.Tests.ps1 new file mode 100644 index 0000000000..56685d29bc --- /dev/null +++ b/tests/Unit/Public/Set-SqlDscConfigurationOption.Tests.ps1 @@ -0,0 +1,634 @@ +[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 build" first.' + } +} + +BeforeAll { + $env:SqlServerDscCI = $true + + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName -Force -ErrorAction 'Stop' + + # Load SMO stub types + Add-Type -Path "$PSScriptRoot/../Stubs/SMO.cs" + + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $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' -ErrorAction 'SilentlyContinue' +} + +Describe 'Set-SqlDscConfigurationOption' -Tag 'Public' { + Context 'When command has correct parameter sets' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = '__AllParameterSets' + ExpectedParameters = '[-ServerObject] [-Name] [-Value] [-Force] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'Set-SqlDscConfigurationOption').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 command has correct parameter properties' { + It 'Should have ServerObject as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'Set-SqlDscConfigurationOption').Parameters['ServerObject'] + $parameterInfo.Attributes.Mandatory | Should -Contain $true + } + + It 'Should have Name as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'Set-SqlDscConfigurationOption').Parameters['Name'] + $parameterInfo.Attributes.Mandatory | Should -Contain $true + } + + It 'Should have Value as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'Set-SqlDscConfigurationOption').Parameters['Value'] + $parameterInfo.Attributes.Mandatory | Should -Contain $true + } + + It 'Should have Force as an optional parameter' { + $parameterInfo = (Get-Command -Name 'Set-SqlDscConfigurationOption').Parameters['Force'] + $parameterInfo.Attributes.Mandatory | Should -Not -Contain $true + } + + It 'Should have ServerObject accepting pipeline input' { + $parameterInfo = (Get-Command -Name 'Set-SqlDscConfigurationOption').Parameters['ServerObject'] + $parameterInfo.Attributes.ValueFromPipeline | Should -Contain $true + } + + It 'Should support ShouldProcess' { + $commandInfo = Get-Command -Name 'Set-SqlDscConfigurationOption' + $commandInfo.Parameters.ContainsKey('WhatIf') | Should -BeTrue + $commandInfo.Parameters.ContainsKey('Confirm') | Should -BeTrue + } + + It 'Should have ConfirmImpact set to High' { + $commandInfo = Get-Command -Name 'Set-SqlDscConfigurationOption' + # For functions with ShouldProcess, we check if CmdletBinding attribute has ConfirmImpact + $function = Get-Item "Function:\Set-SqlDscConfigurationOption" + $attributes = $function.ScriptBlock.Attributes + $cmdletBindingAttribute = $attributes | Where-Object { $_ -is [System.Management.Automation.CmdletBindingAttribute] } + $cmdletBindingAttribute.ConfirmImpact | Should -Be 'High' + } + } + + Context 'When setting configuration option successfully' { + BeforeAll { + # Create mock configuration option + $script:mockConfigurationOption = [Microsoft.SqlServer.Management.Smo.ConfigProperty]::CreateTypeInstance() + $script:mockConfigurationOption.DisplayName = 'max degree of parallelism' + $script:mockConfigurationOption.ConfigValue = 0 + $script:mockConfigurationOption.RunValue = 0 + $script:mockConfigurationOption.Minimum = 0 + $script:mockConfigurationOption.Maximum = 32767 + + # Create mock configuration properties collection + $script:mockConfigurationProperties = [Microsoft.SqlServer.Management.Smo.ConfigPropertyCollection]::CreateTypeInstance() + $script:mockConfigurationProperties.Add($script:mockConfigurationOption) + + # Create mock configuration object + $script:mockConfiguration = [Microsoft.SqlServer.Management.Smo.Configuration]::CreateTypeInstance() + $script:mockConfiguration.Properties = $script:mockConfigurationProperties + $script:mockConfiguration | Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { } -Force + + # Create mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.Name = 'TestServer' + $script:mockServerObject.Configuration = $script:mockConfiguration + + # Mock Write-Information + Mock -CommandName Write-Information + } + + It 'Should set configuration option value successfully' { + $null = Set-SqlDscConfigurationOption -ServerObject $script:mockServerObject -Name 'max degree of parallelism' -Value 4 -Confirm:$false + + $script:mockConfigurationOption.ConfigValue | Should -Be 4 + Should -Invoke -CommandName Write-Information -Times 1 -Exactly + } + + It 'Should call Configuration.Alter() method' { + # Reset the mock + $script:mockAlterCalled = $false + $script:mockConfiguration | Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { $script:mockAlterCalled = $true } -Force + + $null = Set-SqlDscConfigurationOption -ServerObject $script:mockServerObject -Name 'max degree of parallelism' -Value 8 -Confirm:$false + + $script:mockAlterCalled | Should -BeTrue + } + + It 'Should work with pipeline input' { + $script:mockConfigurationOption.ConfigValue = 0 + + $null = $script:mockServerObject | Set-SqlDscConfigurationOption -Name 'max degree of parallelism' -Value 16 -Confirm:$false + + $script:mockConfigurationOption.ConfigValue | Should -Be 16 + } + } + + Context 'When configuration option does not exist' { + BeforeAll { + # Create empty configuration properties collection + $script:mockConfigurationProperties = [Microsoft.SqlServer.Management.Smo.ConfigPropertyCollection]::CreateTypeInstance() + + # Create mock configuration object + $script:mockConfiguration = [Microsoft.SqlServer.Management.Smo.Configuration]::CreateTypeInstance() + $script:mockConfiguration.Properties = $script:mockConfigurationProperties + + # Create mock server object with empty configuration + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.Name = 'TestServer' + $script:mockServerObject.Configuration = $script:mockConfiguration + + # Mock Write-Error + Mock -CommandName Write-Error + } + + It 'Should throw error when configuration option does not exist' { + $null = Set-SqlDscConfigurationOption -ServerObject $script:mockServerObject -Name 'NonExistentOption' -Value 1 -Confirm:$false + + Should -Invoke -CommandName Write-Error -Times 1 -Exactly -ParameterFilter { + $Message -match "There is no configuration option with the name 'NonExistentOption'" + } + } + + It 'Should use correct error details for missing configuration option' { + $null = Set-SqlDscConfigurationOption -ServerObject $script:mockServerObject -Name 'InvalidOption' -Value 1 -Confirm:$false + + Should -Invoke -CommandName Write-Error -Times 1 -Exactly -ParameterFilter { + $Category -eq 'InvalidOperation' -and + $ErrorId -eq 'SSDCO0001' -and + $TargetObject -eq 'InvalidOption' + } + } + } + + Context 'When option value is outside valid range' { + BeforeAll { + # Create mock configuration option with specific range + $script:mockConfigurationOption = [Microsoft.SqlServer.Management.Smo.ConfigProperty]::CreateTypeInstance() + $script:mockConfigurationOption.DisplayName = 'max degree of parallelism' + $script:mockConfigurationOption.Minimum = 0 + $script:mockConfigurationOption.Maximum = 32767 + + # Create mock configuration properties collection + $script:mockConfigurationProperties = [Microsoft.SqlServer.Management.Smo.ConfigPropertyCollection]::CreateTypeInstance() + $script:mockConfigurationProperties.Add($script:mockConfigurationOption) + + # Create mock configuration object + $script:mockConfiguration = [Microsoft.SqlServer.Management.Smo.Configuration]::CreateTypeInstance() + $script:mockConfiguration.Properties = $script:mockConfigurationProperties + + # Create mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.Name = 'TestServer' + $script:mockServerObject.Configuration = $script:mockConfiguration + + # Mock Write-Error + Mock -CommandName Write-Error + } + + It 'Should throw error when value is below minimum' { + $null = Set-SqlDscConfigurationOption -ServerObject $script:mockServerObject -Name 'max degree of parallelism' -Value -1 -Confirm:$false + + Should -Invoke -CommandName Write-Error -Times 1 -Exactly -ParameterFilter { + $Message -match "The value '-1' for configuration option 'max degree of parallelism' is outside the valid range of 0 to 32767" + } + } + + It 'Should throw error when value is above maximum' { + $null = Set-SqlDscConfigurationOption -ServerObject $script:mockServerObject -Name 'max degree of parallelism' -Value 40000 -Confirm:$false + + Should -Invoke -CommandName Write-Error -Times 1 -Exactly -ParameterFilter { + $Message -match "The value '40000' for configuration option 'max degree of parallelism' is outside the valid range of 0 to 32767" + } + } + + It 'Should use correct error details for invalid value' { + $null = Set-SqlDscConfigurationOption -ServerObject $script:mockServerObject -Name 'max degree of parallelism' -Value 50000 -Confirm:$false + + Should -Invoke -CommandName Write-Error -Times 1 -Exactly -ParameterFilter { + $Category -eq 'InvalidArgument' -and + $ErrorId -eq 'SSDCO0002' -and + $TargetObject -eq 50000 + } + } + + It 'Should accept value at minimum boundary' { + # Reset mocks for successful scenario + Mock -CommandName Write-Error + Mock -CommandName Write-Information + + $script:mockConfigurationOption.ConfigValue = 10 + $script:mockServerObject.Configuration | Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { } -Force + + $null = Set-SqlDscConfigurationOption -ServerObject $script:mockServerObject -Name 'max degree of parallelism' -Value 0 -Confirm:$false + + Should -Invoke -CommandName Write-Error -Times 0 -Exactly + $script:mockConfigurationOption.ConfigValue | Should -Be 0 + } + + It 'Should accept value at maximum boundary' { + # Reset mocks for successful scenario + Mock -CommandName Write-Error + Mock -CommandName Write-Information + + $script:mockConfigurationOption.ConfigValue = 10 + $script:mockServerObject.Configuration | Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { } -Force + + $null = Set-SqlDscConfigurationOption -ServerObject $script:mockServerObject -Name 'max degree of parallelism' -Value 32767 -Confirm:$false + + Should -Invoke -CommandName Write-Error -Times 0 -Exactly + $script:mockConfigurationOption.ConfigValue | Should -Be 32767 + } + } + + Context 'When Configuration.Alter() throws exception' { + BeforeAll { + # Create mock configuration option + $script:mockConfigurationOption = [Microsoft.SqlServer.Management.Smo.ConfigProperty]::CreateTypeInstance() + $script:mockConfigurationOption.DisplayName = 'max degree of parallelism' + $script:mockConfigurationOption.Minimum = 0 + $script:mockConfigurationOption.Maximum = 32767 + + # Create mock configuration that throws on Alter + $script:mockConfiguration = [Microsoft.SqlServer.Management.Smo.Configuration]::CreateTypeInstance() + $script:mockConfiguration | Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { + throw [System.Exception]::new('Database connection failed') + } -Force + + # Create mock properties collection that returns our configuration option + $script:mockConfigurationProperties = [Microsoft.SqlServer.Management.Smo.ConfigPropertyCollection]::CreateTypeInstance() + $script:mockConfigurationProperties.Add($script:mockConfigurationOption) + + $script:mockConfiguration.Properties = $script:mockConfigurationProperties + + # Create mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.Name = 'TestServer' + $script:mockServerObject.Configuration = $script:mockConfiguration + } + + BeforeEach { + # Reset mock for each test + Mock -CommandName Write-Error + } + + It 'Should handle exception from Configuration.Alter()' { + # Test that the function handles exceptions gracefully + $ErrorActionPreference = 'SilentlyContinue' + $Global:Error.Clear() + + # This should not throw, but should generate an error record via Write-Error + Set-SqlDscConfigurationOption -ServerObject $script:mockServerObject -Name 'max degree of parallelism' -Value 4 -Confirm:$false -ErrorAction SilentlyContinue + + # Should -Invoke doesn't always work with Write-Error, so check error was generated another way + Should -Invoke -CommandName Write-Error -Times 1 -Exactly + } + + It 'Should use correct error details when Alter fails' { + try { + Set-SqlDscConfigurationOption -ServerObject $script:mockServerObject -Name 'max degree of parallelism' -Value 4 -Confirm:$false -ErrorAction SilentlyContinue + } + catch { + # Ignore any exceptions for this test + } + + Should -Invoke -CommandName Write-Error -Times 1 -Exactly -ParameterFilter { + $Category -eq 'InvalidOperation' -and + $ErrorId -eq 'SSDCO0003' -and + $TargetObject -eq 'max degree of parallelism' + } + } + } + + Context 'When using ShouldProcess with WhatIf' { + BeforeAll { + # Create mock configuration option + $script:mockConfigurationOption = [Microsoft.SqlServer.Management.Smo.ConfigProperty]::CreateTypeInstance() + $script:mockConfigurationOption.DisplayName = 'max degree of parallelism' + $script:mockConfigurationOption.ConfigValue = 0 + $script:mockConfigurationOption.Minimum = 0 + $script:mockConfigurationOption.Maximum = 32767 + + # Create mock configuration properties collection + $script:mockConfigurationProperties = [Microsoft.SqlServer.Management.Smo.ConfigPropertyCollection]::CreateTypeInstance() + $script:mockConfigurationProperties.Add($script:mockConfigurationOption) + + # Create mock configuration object + $script:mockConfiguration = [Microsoft.SqlServer.Management.Smo.Configuration]::CreateTypeInstance() + $script:mockConfiguration.Properties = $script:mockConfigurationProperties + + # Track if Alter was called + $script:mockAlterCalled = $false + $script:mockConfiguration | Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { + $script:mockAlterCalled = $true + } -Force + + # Create mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.Name = 'TestServer' + $script:mockServerObject.Configuration = $script:mockConfiguration + } + + It 'Should not make changes when using WhatIf' { + $script:mockAlterCalled = $false + $originalValue = $script:mockConfigurationOption.ConfigValue + + $null = Set-SqlDscConfigurationOption -ServerObject $script:mockServerObject -Name 'max degree of parallelism' -Value 4 -WhatIf + + $script:mockAlterCalled | Should -BeFalse + $script:mockConfigurationOption.ConfigValue | Should -Be $originalValue + } + } + + Context 'When using Force parameter' { + BeforeAll { + # Create mock configuration option + $script:mockConfigurationOption = [Microsoft.SqlServer.Management.Smo.ConfigProperty]::CreateTypeInstance() + $script:mockConfigurationOption.DisplayName = 'max degree of parallelism' + $script:mockConfigurationOption.ConfigValue = 0 + $script:mockConfigurationOption.Minimum = 0 + $script:mockConfigurationOption.Maximum = 32767 + + # Create mock configuration properties collection + $script:mockConfigurationProperties = [Microsoft.SqlServer.Management.Smo.ConfigPropertyCollection]::CreateTypeInstance() + $script:mockConfigurationProperties.Add($script:mockConfigurationOption) + + # Create mock configuration object + $script:mockConfiguration = [Microsoft.SqlServer.Management.Smo.Configuration]::CreateTypeInstance() + $script:mockConfiguration.Properties = $script:mockConfigurationProperties + $script:mockConfiguration | Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { } -Force + + # Create mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.Name = 'TestServer' + $script:mockServerObject.Configuration = $script:mockConfiguration + + # Mock Write-Information + Mock -CommandName Write-Information + } + + It 'Should bypass confirmation when Force is specified' { + # This test ensures Force parameter is handled correctly in the begin block + $null = Set-SqlDscConfigurationOption -ServerObject $script:mockServerObject -Name 'max degree of parallelism' -Value 4 -Force + + $script:mockConfigurationOption.ConfigValue | Should -Be 4 + Should -Invoke -CommandName Write-Information -Times 1 -Exactly + } + } + + Context 'When validating ShouldProcess messages' { + BeforeAll { + # Create mock configuration option + $script:mockConfigurationOption = [Microsoft.SqlServer.Management.Smo.ConfigProperty]::CreateTypeInstance() + $script:mockConfigurationOption.DisplayName = 'cost threshold for parallelism' + $script:mockConfigurationOption.ConfigValue = 5 + $script:mockConfigurationOption.Minimum = 0 + $script:mockConfigurationOption.Maximum = 32767 + + # Create mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.Name = 'SQL2019' + $script:mockServerObject.Configuration = [Microsoft.SqlServer.Management.Smo.Configuration]::CreateTypeInstance() + $script:mockServerObject.Configuration | Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { } -Force + + # Mock the Where-Object pipeline to return our mock configuration option + Mock -CommandName Where-Object -MockWith { + param($FilterScript) + if ($FilterScript.ToString() -match 'DisplayName') { + return $script:mockConfigurationOption + } + return $null + } + + # Mock localized strings retrieval + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + } + + AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + } + + It 'Should use correct ShouldProcess messages' { + InModuleScope -ScriptBlock { + # Verify localized strings exist for ShouldProcess + $script:localizedData.ConfigurationOption_Set_ShouldProcessDescription | Should -Be "Set configuration option '{0}' to '{1}' on server '{2}'." + $script:localizedData.ConfigurationOption_Set_ShouldProcessConfirmation | Should -Be "Are you sure you want to set configuration option '{0}' to '{1}'?" + $script:localizedData.ConfigurationOption_Set_ShouldProcessCaption | Should -Be 'Set configuration option' + } + } + } + + Context 'When validating error messages' { + BeforeAll { + # Mock localized strings retrieval + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + } + + AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + } + + It 'Should use correct localized error messages' { + InModuleScope -ScriptBlock { + # Verify localized strings exist + $script:localizedData.ConfigurationOption_Set_Missing | Should -Be "There is no configuration option with the name '{0}'." + $script:localizedData.ConfigurationOption_Set_InvalidValue | Should -Be "The value '{1}' for configuration option '{0}' is outside the valid range of {2} to {3}." + $script:localizedData.ConfigurationOption_Set_Failed | Should -Be "Failed to set configuration option '{0}' to '{1}'. {2}" + $script:localizedData.ConfigurationOption_Set_Success | Should -Be "Successfully set configuration option '{0}' to '{1}' on server '{2}'." + } + } + } + + Context 'When testing argument completers through PowerShell tab completion system' { + BeforeAll { + # Import module to ensure tab completion is registered + Import-Module -Name $script:dscModuleName -Force + + # Create mock server object for testing + $script:mockServerForTabCompletion = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerForTabCompletion.Name = 'TestServer' + + # Create test configuration properties + $mockConfigProperty1 = [Microsoft.SqlServer.Management.Smo.ConfigProperty]::CreateTypeInstance() + $mockConfigProperty1.DisplayName = 'max server memory (MB)' + $mockConfigProperty1.ConfigValue = 2048 + $mockConfigProperty1.RunValue = 2048 + $mockConfigProperty1.Minimum = 0 + $mockConfigProperty1.Maximum = 2147483647 + + $mockConfigProperty2 = [Microsoft.SqlServer.Management.Smo.ConfigProperty]::CreateTypeInstance() + $mockConfigProperty2.DisplayName = 'cost threshold for parallelism' + $mockConfigProperty2.ConfigValue = 5 + $mockConfigProperty2.RunValue = 5 + $mockConfigProperty2.Minimum = 0 + $mockConfigProperty2.Maximum = 32767 + + # Create configuration properties collection + $mockConfigurationProperties = [Microsoft.SqlServer.Management.Smo.ConfigPropertyCollection]::CreateTypeInstance() + $mockConfigurationProperties.Add($mockConfigProperty1) + $mockConfigurationProperties.Add($mockConfigProperty2) + + # Create configuration object + $mockConfiguration = [Microsoft.SqlServer.Management.Smo.Configuration]::CreateTypeInstance() + $mockConfiguration.Properties = $mockConfigurationProperties + + $script:mockServerForTabCompletion.Configuration = $mockConfiguration + + # Store the server object in a script variable that can be accessed by tab completion + $global:TestServerObject = $script:mockServerForTabCompletion + } + + AfterAll { + # Clean up global variable + if (Get-Variable -Name 'TestServerObject' -Scope Global -ErrorAction SilentlyContinue) { + Remove-Variable -Name 'TestServerObject' -Scope Global -Force + } + } + + It 'Should provide Name parameter completions through TabExpansion2' { + # This actually exercises the argument completer code through PowerShell's tab completion system + $inputScript = 'Set-SqlDscConfigurationOption -ServerObject $global:TestServerObject -Name max' + $result = TabExpansion2 -inputScript $inputScript -cursorColumn $inputScript.Length + + $result | Should -Not -BeNullOrEmpty + $result.CompletionMatches | Should -Not -BeNullOrEmpty + + # Should find the max server memory option + $maxMemoryMatch = $result.CompletionMatches | Where-Object { + $_.CompletionText -eq "'max server memory (MB)'" + } + $maxMemoryMatch | Should -Not -BeNullOrEmpty + $maxMemoryMatch.ToolTip | Should -Match "Current: 2048" + } + + It 'Should provide Value parameter completions through TabExpansion2' { + # Test Value completion + $inputScript = 'Set-SqlDscConfigurationOption -ServerObject $global:TestServerObject -Name "max server memory (MB)" -Value ' + $result = TabExpansion2 -inputScript $inputScript -cursorColumn $inputScript.Length + + $result | Should -Not -BeNullOrEmpty + $result.CompletionMatches | Should -Not -BeNullOrEmpty + + # Should find current value and other suggestions + $currentValueMatch = $result.CompletionMatches | Where-Object { + $_.CompletionText -eq '2048' -and $_.ToolTip -match "Current ConfigValue" + } + $currentValueMatch | Should -Not -BeNullOrEmpty + } + + It 'Should handle partial Name completions through TabExpansion2' { + $inputScript = 'Set-SqlDscConfigurationOption -ServerObject $global:TestServerObject -Name cost' + $result = TabExpansion2 -inputScript $inputScript -cursorColumn $inputScript.Length + + $result.CompletionMatches | Should -HaveCount 1 + $result.CompletionMatches[0].CompletionText | Should -Be "'cost threshold for parallelism'" + } + + It 'Should execute Name argument completer code when retrieving command metadata' { + # This approach actually executes the argument completer code during command introspection + $command = Get-Command -Name 'Set-SqlDscConfigurationOption' + + # Create a mock parameter set to trigger argument completer execution + $parameterInfo = $command.Parameters['Name'] + $completerAttribute = $parameterInfo.Attributes | Where-Object { $_ -is [System.Management.Automation.ArgumentCompleterAttribute] } + + # This executes the completer in the module's context + $completions = & $completerAttribute.ScriptBlock 'Set-SqlDscConfigurationOption' 'Name' 'max' $null @{ + ServerObject = $script:mockServerForTabCompletion + } + + $completions | Should -Not -BeNullOrEmpty + $completions[0].CompletionText | Should -Be "'max server memory (MB)'" + } + + It 'Should execute Value argument completer code when retrieving command metadata' { + $command = Get-Command -Name 'Set-SqlDscConfigurationOption' + $parameterInfo = $command.Parameters['Value'] + $completerAttribute = $parameterInfo.Attributes | Where-Object { $_ -is [System.Management.Automation.ArgumentCompleterAttribute] } + + $completions = & $completerAttribute.ScriptBlock 'Set-SqlDscConfigurationOption' 'Value' '' $null @{ + ServerObject = $script:mockServerForTabCompletion + Name = 'max server memory (MB)' + } + + $completions | Should -Not -BeNullOrEmpty + $completions | Where-Object { $_.ToolTip -match "Current ConfigValue: 2048" } | Should -Not -BeNullOrEmpty + } + + It 'Should handle tab completion errors gracefully' { + # Create a server object that will cause an error + $badServer = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $badServer.Name = 'BadServer' + Add-Member -InputObject $badServer -MemberType ScriptProperty -Name 'Configuration' -Value { + throw 'Connection failed' + } -Force + $global:BadTestServerObject = $badServer + + try { + $inputScript = 'Set-SqlDscConfigurationOption -ServerObject $global:BadTestServerObject -Name test' + $result = TabExpansion2 -inputScript $inputScript -cursorColumn $inputScript.Length + + <# + Should not throw an error and should return empty from argument completer, but then + TabExpansion2 itself returns some default completions (like filesystem paths). + #> + $result | Should -BeOfType ([System.Management.Automation.CommandCompletion]) + $result.CompletionMatches.ListItemText | Should -Be 'tests' + } + finally { + Remove-Variable -Name 'BadTestServerObject' -Scope Global -Force -ErrorAction SilentlyContinue + } + } + + It 'Should handle missing ServerObject in tab completion gracefully' { + # Test tab completion without a ServerObject + $inputScript = 'Set-SqlDscConfigurationOption -Name test' + $result = TabExpansion2 -inputScript $inputScript -cursorColumn $inputScript.Length + + <# + Should not throw an error and should return empty from argument completer, but then + TabExpansion2 itself returns some default completions (like filesystem paths). + #> + $result | Should -BeOfType ([System.Management.Automation.CommandCompletion]) + $result.CompletionMatches.ListItemText | Should -Be 'tests' + } + } +} diff --git a/tests/Unit/Public/Test-SqlDscConfigurationOption.Tests.ps1 b/tests/Unit/Public/Test-SqlDscConfigurationOption.Tests.ps1 new file mode 100644 index 0000000000..6c4c00ce6b --- /dev/null +++ b/tests/Unit/Public/Test-SqlDscConfigurationOption.Tests.ps1 @@ -0,0 +1,497 @@ +[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 build" first.' + } +} + +BeforeAll { + $env:SqlServerDscCI = $true + + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName -Force -ErrorAction 'Stop' + + # Load SMO stub types + Add-Type -Path "$PSScriptRoot/../Stubs/SMO.cs" + + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $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' -ErrorAction 'SilentlyContinue' +} + +Describe 'Test-SqlDscConfigurationOption' -Tag 'Public' { + Context 'When command has correct parameter sets' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = '__AllParameterSets' + ExpectedParameters = '[-ServerObject] [-Name] [-Value] []' + } + ) { + $result = (Get-Command -Name 'Test-SqlDscConfigurationOption').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 command has correct parameter properties' { + It 'Should have ServerObject as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscConfigurationOption').Parameters['ServerObject'] + $parameterInfo.Attributes.Mandatory | Should -Contain $true + } + + It 'Should have Name as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscConfigurationOption').Parameters['Name'] + $parameterInfo.Attributes.Mandatory | Should -Contain $true + } + + It 'Should have Value as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscConfigurationOption').Parameters['Value'] + $parameterInfo.Attributes.Mandatory | Should -Contain $true + } + + It 'Should have ServerObject accepting pipeline input' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscConfigurationOption').Parameters['ServerObject'] + $parameterInfo.Attributes.ValueFromPipeline | Should -Contain $true + } + + It 'Should not support ShouldProcess' { + $commandInfo = Get-Command -Name 'Test-SqlDscConfigurationOption' + $commandInfo.Parameters.ContainsKey('WhatIf') | Should -BeFalse + $commandInfo.Parameters.ContainsKey('Confirm') | Should -BeFalse + } + + It 'Should return boolean output type' { + $commandInfo = Get-Command -Name 'Test-SqlDscConfigurationOption' + $commandInfo.OutputType[0].Type | Should -Be ([System.Boolean]) + } + } + + Context 'When testing configuration option successfully' { + BeforeAll { + # Create mock configuration option + $script:mockConfigurationOption = [Microsoft.SqlServer.Management.Smo.ConfigProperty]::CreateTypeInstance() + $script:mockConfigurationOption.DisplayName = 'max degree of parallelism' + $script:mockConfigurationOption.ConfigValue = 4 + $script:mockConfigurationOption.RunValue = 4 + $script:mockConfigurationOption.Minimum = 0 + $script:mockConfigurationOption.Maximum = 32767 + + # Create mock configuration properties collection + $script:mockConfigurationProperties = [Microsoft.SqlServer.Management.Smo.ConfigPropertyCollection]::CreateTypeInstance() + $script:mockConfigurationProperties.Add($script:mockConfigurationOption) + + # Create mock configuration object + $script:mockConfiguration = [Microsoft.SqlServer.Management.Smo.Configuration]::CreateTypeInstance() + $script:mockConfiguration.Properties = $script:mockConfigurationProperties + + # Create mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.Name = 'TestServer' + $script:mockServerObject.Configuration = $script:mockConfiguration + + # Mock Write-Verbose + Mock -CommandName Write-Verbose + } + + It 'Should return true when configuration option matches expected value' { + $result = Test-SqlDscConfigurationOption -ServerObject $script:mockServerObject -Name 'max degree of parallelism' -Value 4 + + $result | Should -BeTrue + Should -Invoke -CommandName Write-Verbose -Times 1 -Exactly + } + + It 'Should return false when configuration option does not match expected value' { + $result = Test-SqlDscConfigurationOption -ServerObject $script:mockServerObject -Name 'max degree of parallelism' -Value 8 + + $result | Should -BeFalse + Should -Invoke -CommandName Write-Verbose -Times 1 -Exactly + } + + It 'Should work with pipeline input' { + $result = $script:mockServerObject | Test-SqlDscConfigurationOption -Name 'max degree of parallelism' -Value 4 + + $result | Should -BeTrue + } + + It 'Should call Write-Verbose with correct message pattern' { + $null = Test-SqlDscConfigurationOption -ServerObject $script:mockServerObject -Name 'max degree of parallelism' -Value 4 + + Should -Invoke -CommandName Write-Verbose -Times 1 -Exactly -ParameterFilter { + $Message -match "Testing configuration option 'max degree of parallelism': Current value is '4', expected value is '4', match result is 'True' on server 'TestServer'" + } + } + } + + Context 'When configuration option does not exist' { + BeforeAll { + # Create empty configuration properties collection + $script:mockConfigurationProperties = [Microsoft.SqlServer.Management.Smo.ConfigPropertyCollection]::CreateTypeInstance() + + # Create mock configuration object + $script:mockConfiguration = [Microsoft.SqlServer.Management.Smo.Configuration]::CreateTypeInstance() + $script:mockConfiguration.Properties = $script:mockConfigurationProperties + + # Create mock server object with empty configuration + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.Name = 'TestServer' + $script:mockServerObject.Configuration = $script:mockConfiguration + + # Mock Write-Error + Mock -CommandName Write-Error + } + + It 'Should return false and write error when configuration option does not exist' { + $result = Test-SqlDscConfigurationOption -ServerObject $script:mockServerObject -Name 'NonExistentOption' -Value 1 + + $result | Should -BeFalse + Should -Invoke -CommandName Write-Error -Times 1 -Exactly -ParameterFilter { + $Message -match "There is no configuration option with the name 'NonExistentOption'" + } + } + + It 'Should use correct error details for missing configuration option' { + $null = Test-SqlDscConfigurationOption -ServerObject $script:mockServerObject -Name 'InvalidOption' -Value 1 + + Should -Invoke -CommandName Write-Error -Times 1 -Exactly -ParameterFilter { + $Category -eq 'InvalidOperation' -and + $ErrorId -eq 'TSDCO0001' -and + $TargetObject -eq 'InvalidOption' + } + } + } + + Context 'When testing various configuration values' { + BeforeAll { + # Create mock configuration option with specific values + $script:mockConfigurationOption = [Microsoft.SqlServer.Management.Smo.ConfigProperty]::CreateTypeInstance() + $script:mockConfigurationOption.DisplayName = 'cost threshold for parallelism' + $script:mockConfigurationOption.ConfigValue = 50 + $script:mockConfigurationOption.RunValue = 50 + $script:mockConfigurationOption.Minimum = 0 + $script:mockConfigurationOption.Maximum = 32767 + + # Create mock configuration properties collection + $script:mockConfigurationProperties = [Microsoft.SqlServer.Management.Smo.ConfigPropertyCollection]::CreateTypeInstance() + $script:mockConfigurationProperties.Add($script:mockConfigurationOption) + + # Create mock configuration object + $script:mockConfiguration = [Microsoft.SqlServer.Management.Smo.Configuration]::CreateTypeInstance() + $script:mockConfiguration.Properties = $script:mockConfigurationProperties + + # Create mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.Name = 'TestServer' + $script:mockServerObject.Configuration = $script:mockConfiguration + + # Mock Write-Verbose + Mock -CommandName Write-Verbose + } + + It 'Should return true for exact match at minimum boundary' { + $script:mockConfigurationOption.ConfigValue = 0 + + $result = Test-SqlDscConfigurationOption -ServerObject $script:mockServerObject -Name 'cost threshold for parallelism' -Value 0 + + $result | Should -BeTrue + } + + It 'Should return true for exact match at maximum boundary' { + $script:mockConfigurationOption.ConfigValue = 32767 + + $result = Test-SqlDscConfigurationOption -ServerObject $script:mockServerObject -Name 'cost threshold for parallelism' -Value 32767 + + $result | Should -BeTrue + } + + It 'Should return false for values that do not match' { + $script:mockConfigurationOption.ConfigValue = 25 + + $result = Test-SqlDscConfigurationOption -ServerObject $script:mockServerObject -Name 'cost threshold for parallelism' -Value 50 + + $result | Should -BeFalse + } + + It 'Should handle boolean-style configuration options (0-1 range)' { + # Create a boolean-style configuration option + $booleanOption = [Microsoft.SqlServer.Management.Smo.ConfigProperty]::CreateTypeInstance() + $booleanOption.DisplayName = 'Agent XPs' + $booleanOption.ConfigValue = 1 + $booleanOption.RunValue = 1 + $booleanOption.Minimum = 0 + $booleanOption.Maximum = 1 + + # Create new properties collection with the boolean option + $booleanProperties = [Microsoft.SqlServer.Management.Smo.ConfigPropertyCollection]::CreateTypeInstance() + $booleanProperties.Add($booleanOption) + + $booleanConfiguration = [Microsoft.SqlServer.Management.Smo.Configuration]::CreateTypeInstance() + $booleanConfiguration.Properties = $booleanProperties + + $booleanServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $booleanServerObject.Name = 'TestServer' + $booleanServerObject.Configuration = $booleanConfiguration + + # Test enabled state (1) + $result = Test-SqlDscConfigurationOption -ServerObject $booleanServerObject -Name 'Agent XPs' -Value 1 + $result | Should -BeTrue + + # Test disabled state comparison + $result = Test-SqlDscConfigurationOption -ServerObject $booleanServerObject -Name 'Agent XPs' -Value 0 + $result | Should -BeFalse + } + } + + Context 'When validating localized error and verbose messages' { + BeforeAll { + # Mock localized strings retrieval + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + } + + AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + } + + It 'Should use correct localized error messages' { + InModuleScope -ScriptBlock { + # Verify localized strings exist + $script:localizedData.ConfigurationOption_Test_Missing | Should -Be "There is no configuration option with the name '{0}'." + $script:localizedData.ConfigurationOption_Test_Result | Should -Be "Testing configuration option '{0}': Current value is '{1}', expected value is '{2}', match result is '{3}' on server '{4}'." + } + } + } + + Context 'When testing argument completers through PowerShell tab completion system' { + BeforeAll { + # Import module to ensure tab completion is registered + Import-Module -Name $script:dscModuleName -Force + + # Create mock server object for testing + $script:mockServerForTabCompletion = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerForTabCompletion.Name = 'TestServer' + + # Create test configuration properties + $mockConfigProperty1 = [Microsoft.SqlServer.Management.Smo.ConfigProperty]::CreateTypeInstance() + $mockConfigProperty1.DisplayName = 'max server memory (MB)' + $mockConfigProperty1.ConfigValue = 2048 + $mockConfigProperty1.RunValue = 2048 + $mockConfigProperty1.Minimum = 0 + $mockConfigProperty1.Maximum = 2147483647 + + $mockConfigProperty2 = [Microsoft.SqlServer.Management.Smo.ConfigProperty]::CreateTypeInstance() + $mockConfigProperty2.DisplayName = 'cost threshold for parallelism' + $mockConfigProperty2.ConfigValue = 5 + $mockConfigProperty2.RunValue = 5 + $mockConfigProperty2.Minimum = 0 + $mockConfigProperty2.Maximum = 32767 + + # Create configuration properties collection + $mockConfigurationProperties = [Microsoft.SqlServer.Management.Smo.ConfigPropertyCollection]::CreateTypeInstance() + $mockConfigurationProperties.Add($mockConfigProperty1) + $mockConfigurationProperties.Add($mockConfigProperty2) + + # Create configuration object + $mockConfiguration = [Microsoft.SqlServer.Management.Smo.Configuration]::CreateTypeInstance() + $mockConfiguration.Properties = $mockConfigurationProperties + + $script:mockServerForTabCompletion.Configuration = $mockConfiguration + + # Store the server object in a script variable that can be accessed by tab completion + $global:TestServerObject = $script:mockServerForTabCompletion + } + + AfterAll { + # Clean up global variable + if (Get-Variable -Name 'TestServerObject' -Scope Global -ErrorAction SilentlyContinue) { + Remove-Variable -Name 'TestServerObject' -Scope Global -Force + } + } + + It 'Should provide Name parameter completions through TabExpansion2' { + # This actually exercises the argument completer code through PowerShell's tab completion system + $inputScript = 'Test-SqlDscConfigurationOption -ServerObject $global:TestServerObject -Name max' + $result = TabExpansion2 -inputScript $inputScript -cursorColumn $inputScript.Length + + $result | Should -Not -BeNullOrEmpty + $result.CompletionMatches | Should -Not -BeNullOrEmpty + + # Should find the max server memory option + $maxMemoryMatch = $result.CompletionMatches | Where-Object { + $_.CompletionText -eq "'max server memory (MB)'" + } + $maxMemoryMatch | Should -Not -BeNullOrEmpty + $maxMemoryMatch.ToolTip | Should -Match "Current: 2048" + } + + It 'Should provide Value parameter completions through TabExpansion2' { + # Test Value completion + $inputScript = 'Test-SqlDscConfigurationOption -ServerObject $global:TestServerObject -Name "max server memory (MB)" -Value ' + $result = TabExpansion2 -inputScript $inputScript -cursorColumn $inputScript.Length + + $result | Should -Not -BeNullOrEmpty + $result.CompletionMatches | Should -Not -BeNullOrEmpty + + # Should find current value and other suggestions + $currentValueMatch = $result.CompletionMatches | Where-Object { + $_.CompletionText -eq '2048' -and $_.ToolTip -match "Current ConfigValue" + } + $currentValueMatch | Should -Not -BeNullOrEmpty + } + + It 'Should handle partial Name completions through TabExpansion2' { + $inputScript = 'Test-SqlDscConfigurationOption -ServerObject $global:TestServerObject -Name cost' + $result = TabExpansion2 -inputScript $inputScript -cursorColumn $inputScript.Length + + $result.CompletionMatches | Should -HaveCount 1 + $result.CompletionMatches[0].CompletionText | Should -Be "'cost threshold for parallelism'" + } + + It 'Should execute Name argument completer code when retrieving command metadata' { + # This approach actually executes the argument completer code during command introspection + $command = Get-Command -Name 'Test-SqlDscConfigurationOption' + + # Create a mock parameter set to trigger argument completer execution + $parameterInfo = $command.Parameters['Name'] + $completerAttribute = $parameterInfo.Attributes | Where-Object { $_ -is [System.Management.Automation.ArgumentCompleterAttribute] } + + # This executes the completer in the module's context + $completions = & $completerAttribute.ScriptBlock 'Test-SqlDscConfigurationOption' 'Name' 'max' $null @{ + ServerObject = $script:mockServerForTabCompletion + } + + $completions | Should -Not -BeNullOrEmpty + $completions[0].CompletionText | Should -Be "'max server memory (MB)'" + } + + It 'Should execute Value argument completer code when retrieving command metadata' { + $command = Get-Command -Name 'Test-SqlDscConfigurationOption' + $parameterInfo = $command.Parameters['Value'] + $completerAttribute = $parameterInfo.Attributes | Where-Object { $_ -is [System.Management.Automation.ArgumentCompleterAttribute] } + + $completions = & $completerAttribute.ScriptBlock 'Test-SqlDscConfigurationOption' 'Value' '' $null @{ + ServerObject = $script:mockServerForTabCompletion + Name = 'max server memory (MB)' + } + + $completions | Should -Not -BeNullOrEmpty + $completions | Where-Object { $_.ToolTip -match "Current ConfigValue: 2048" } | Should -Not -BeNullOrEmpty + } + + It 'Should handle tab completion errors gracefully' { + # Create a server object that will cause an error + $badServer = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $badServer.Name = 'BadServer' + Add-Member -InputObject $badServer -MemberType ScriptProperty -Name 'Configuration' -Value { + throw 'Connection failed' + } -Force + $global:BadTestServerObject = $badServer + + try { + $inputScript = 'Test-SqlDscConfigurationOption -ServerObject $global:BadTestServerObject -Name test' + $result = TabExpansion2 -inputScript $inputScript -cursorColumn $inputScript.Length + + <# + Should not throw an error and should return empty from argument completer, but then + TabExpansion2 itself returns some default completions (like filesystem paths). + #> + $result | Should -BeOfType ([System.Management.Automation.CommandCompletion]) + $result.CompletionMatches.ListItemText | Should -Be 'tests' + } + finally { + Remove-Variable -Name 'BadTestServerObject' -Scope Global -Force -ErrorAction SilentlyContinue + } + } + + It 'Should handle missing ServerObject in tab completion gracefully' { + # Test tab completion without a ServerObject + $inputScript = 'Test-SqlDscConfigurationOption -Name test' + $result = TabExpansion2 -inputScript $inputScript -cursorColumn $inputScript.Length + + <# + Should not throw an error and should return empty from argument completer, but then + TabExpansion2 itself returns some default completions (like filesystem paths). + #> + $result | Should -BeOfType ([System.Management.Automation.CommandCompletion]) + $result.CompletionMatches.ListItemText | Should -Be 'tests' + } + + # TODO: This tests fails because the boolean-style option tooltips are not set as CompletionMatches + # It 'Should provide Value completions for boolean-style options' { + # # Create a boolean-style configuration option + # $booleanOption = [Microsoft.SqlServer.Management.Smo.ConfigProperty]::CreateTypeInstance() + # $booleanOption.DisplayName = 'Agent XPs' + # $booleanOption.ConfigValue = 0 + # $booleanOption.RunValue = 0 + # $booleanOption.Minimum = 0 + # $booleanOption.Maximum = 1 + + # # Create new server with boolean option + # $booleanProperties = [Microsoft.SqlServer.Management.Smo.ConfigPropertyCollection]::CreateTypeInstance() + # $booleanProperties.Add($booleanOption) + + # $booleanConfiguration = [Microsoft.SqlServer.Management.Smo.Configuration]::CreateTypeInstance() + # $booleanConfiguration.Properties = $booleanProperties + + # $booleanServer = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + # $booleanServer.Name = 'BooleanTestServer' + # $booleanServer.Configuration = $booleanConfiguration + + # $global:BooleanTestServerObject = $booleanServer + + # try { + # $inputScript = 'Test-SqlDscConfigurationOption -ServerObject $global:BooleanTestServerObject -Name "Agent XPs" -Value ' + # $result = TabExpansion2 -inputScript $inputScript -cursorColumn $inputScript.Length + + # $result | Should -Not -BeNullOrEmpty + # $result.CompletionMatches | Should -Not -BeNullOrEmpty + + # # Should find both enabled and disabled options + # Write-Verbose "Completion matches: $($result.CompletionMatches | ForEach-Object { $_ } | Sort-Object | Out-String)" -Verbose + # $enabledMatch = $result.CompletionMatches | Where-Object { + # $_.CompletionText -eq '1' -and $_.ToolTip -match "Enabled" + # } + # $disabledMatch = $result.CompletionMatches | Where-Object { + # $_.CompletionText -eq '0' -and $_.ToolTip -match "Disabled" + # } + + # $enabledMatch | Should -Not -BeNullOrEmpty + # $disabledMatch | Should -Not -BeNullOrEmpty + # } + # finally { + # Remove-Variable -Name 'BooleanTestServerObject' -Scope Global -Force -ErrorAction SilentlyContinue + # } + # } + } +} diff --git a/tests/Unit/Public/Test-SqlDscIsAgentOperator.Tests.ps1 b/tests/Unit/Public/Test-SqlDscIsAgentOperator.Tests.ps1 new file mode 100644 index 0000000000..e331d30a15 --- /dev/null +++ b/tests/Unit/Public/Test-SqlDscIsAgentOperator.Tests.ps1 @@ -0,0 +1,147 @@ +[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 build" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' + + # Load SMO stub types + Add-Type -Path "$PSScriptRoot/../Stubs/SMO.cs" + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + Remove-Item -Path 'Env:\SqlServerDscCI' -ErrorAction 'SilentlyContinue' + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:moduleName -All | Remove-Module -Force +} + +Describe 'Test-SqlDscIsAgentOperator' -Tag 'Public' { + Context 'When command has correct parameter sets' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = '__AllParameterSets' + ExpectedParameters = '[-ServerObject] [-Name] [-Refresh] []' + } + ) { + $result = (Get-Command -Name 'Test-SqlDscIsAgentOperator').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 command has correct parameter properties' { + It 'Should have ServerObject as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscIsAgentOperator').Parameters['ServerObject'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have ServerObject accept pipeline input' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscIsAgentOperator').Parameters['ServerObject'] + $parameterInfo.Attributes.ValueFromPipeline | Should -BeTrue + } + + It 'Should have Name as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscIsAgentOperator').Parameters['Name'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + } + + Context 'When testing operator existence' { + BeforeAll { + # Mock existing operator + $script:mockOperator = [Microsoft.SqlServer.Management.Smo.Agent.Operator]::CreateTypeInstance() + $script:mockOperator.Name = 'TestOperator' + $script:mockOperator.EmailAddress = 'test@contoso.com' + + # Mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + } + + It 'Should return true when operator exists' { + Mock -CommandName Get-AgentOperatorObject -MockWith { + return $script:mockOperator + } + + $result = Test-SqlDscIsAgentOperator -ServerObject $script:mockServerObject -Name 'TestOperator' + + $result | Should -BeTrue + Should -Invoke -CommandName Get-AgentOperatorObject -Exactly -Times 1 -Scope It + } + + It 'Should return false when operator does not exist' { + Mock -CommandName Get-AgentOperatorObject -MockWith { + return $null + } + + $result = Test-SqlDscIsAgentOperator -ServerObject $script:mockServerObject -Name 'NonExistentOperator' + + $result | Should -BeFalse + Should -Invoke -CommandName Get-AgentOperatorObject -Exactly -Times 1 -Scope It + } + + Context 'When using pipeline input' { + It 'Should return true when operator exists using pipeline input' { + Mock -CommandName Get-AgentOperatorObject -MockWith { + return $script:mockOperator + } + + $result = $script:mockServerObject | Test-SqlDscIsAgentOperator -Name 'TestOperator' + + $result | Should -BeTrue + Should -Invoke -CommandName Get-AgentOperatorObject -Exactly -Times 1 -Scope It + } + } + + Context 'When using Refresh parameter' { + It 'Should return true when operator exists' { + Mock -CommandName Get-AgentOperatorObject -MockWith { + return $script:mockOperator + } + + $result = Test-SqlDscIsAgentOperator -ServerObject $script:mockServerObject -Name 'TestOperator' -Refresh + + $result | Should -BeTrue + Should -Invoke -CommandName Get-AgentOperatorObject -Exactly -Times 1 -Scope It + } + } + } +} diff --git a/tests/Unit/SqlServerDsc.Common.Tests.ps1 b/tests/Unit/SqlServerDsc.Common.Tests.ps1 index f6393fc8b8..c4f6fcc57f 100644 --- a/tests/Unit/SqlServerDsc.Common.Tests.ps1 +++ b/tests/Unit/SqlServerDsc.Common.Tests.ps1 @@ -1889,7 +1889,7 @@ Describe 'SqlServerDsc.Common\Test-LoginEffectivePermissions' -Tag 'TestLoginEff $mockInvokeQueryPermissionsSet = $mockAllServerPermissionsPresent.Clone() $testLoginEffectiveServerPermissionsParams.Permissions = $mockAllServerPermissionsPresent.Clone() - Test-LoginEffectivePermissions @testLoginEffectiveServerPermissionsParams | Should -Be $true + Test-LoginEffectivePermissions @testLoginEffectiveServerPermissionsParams | Should -BeTrue Should -Invoke -CommandName Invoke-SqlDscQuery -Scope It -Times 1 -Exactly } @@ -1898,7 +1898,7 @@ Describe 'SqlServerDsc.Common\Test-LoginEffectivePermissions' -Tag 'TestLoginEff $mockInvokeQueryPermissionsSet = $mockAllLoginPermissionsPresent.Clone() $testLoginEffectiveLoginPermissionsParams.Permissions = $mockAllLoginPermissionsPresent.Clone() - Test-LoginEffectivePermissions @testLoginEffectiveLoginPermissionsParams | Should -Be $true + Test-LoginEffectivePermissions @testLoginEffectiveLoginPermissionsParams | Should -BeTrue Should -Invoke -CommandName Invoke-SqlDscQuery -Scope It -Times 1 -Exactly } @@ -1909,7 +1909,7 @@ Describe 'SqlServerDsc.Common\Test-LoginEffectivePermissions' -Tag 'TestLoginEff $mockInvokeQueryPermissionsSet = $mockServerPermissionsMissing.Clone() $testLoginEffectiveServerPermissionsParams.Permissions = $mockAllServerPermissionsPresent.Clone() - Test-LoginEffectivePermissions @testLoginEffectiveServerPermissionsParams | Should -Be $false + Test-LoginEffectivePermissions @testLoginEffectiveServerPermissionsParams | Should -BeFalse Should -Invoke -CommandName Invoke-SqlDscQuery -Scope It -Times 1 -Exactly } @@ -1918,7 +1918,7 @@ Describe 'SqlServerDsc.Common\Test-LoginEffectivePermissions' -Tag 'TestLoginEff $mockInvokeQueryPermissionsSet = @() $testLoginEffectiveServerPermissionsParams.Permissions = $mockAllServerPermissionsPresent.Clone() - Test-LoginEffectivePermissions @testLoginEffectiveServerPermissionsParams | Should -Be $false + Test-LoginEffectivePermissions @testLoginEffectiveServerPermissionsParams | Should -BeFalse Should -Invoke -CommandName Invoke-SqlDscQuery -Scope It -Times 1 -Exactly } @@ -1927,7 +1927,7 @@ Describe 'SqlServerDsc.Common\Test-LoginEffectivePermissions' -Tag 'TestLoginEff $mockInvokeQueryPermissionsSet = $mockLoginPermissionsMissing.Clone() $testLoginEffectiveLoginPermissionsParams.Permissions = $mockAllLoginPermissionsPresent.Clone() - Test-LoginEffectivePermissions @testLoginEffectiveLoginPermissionsParams | Should -Be $false + Test-LoginEffectivePermissions @testLoginEffectiveLoginPermissionsParams | Should -BeFalse Should -Invoke -CommandName Invoke-SqlDscQuery -Scope It -Times 1 -Exactly } @@ -1936,7 +1936,7 @@ Describe 'SqlServerDsc.Common\Test-LoginEffectivePermissions' -Tag 'TestLoginEff $mockInvokeQueryPermissionsSet = @() $testLoginEffectiveLoginPermissionsParams.Permissions = $mockAllLoginPermissionsPresent.Clone() - Test-LoginEffectivePermissions @testLoginEffectiveLoginPermissionsParams | Should -Be $false + Test-LoginEffectivePermissions @testLoginEffectiveLoginPermissionsParams | Should -BeFalse Should -Invoke -CommandName Invoke-SqlDscQuery -Scope It -Times 1 -Exactly } @@ -2145,7 +2145,7 @@ Describe 'SqlServerDsc.Common\Test-AvailabilityReplicaSeedingModeAutomatic' -Tag It 'Should return $false when the instance version is <_>' -ForEach @(11, 12) { $mockSqlVersion = $_ - Test-AvailabilityReplicaSeedingModeAutomatic @testAvailabilityReplicaSeedingModeAutomaticParams | Should -Be $false + Test-AvailabilityReplicaSeedingModeAutomatic @testAvailabilityReplicaSeedingModeAutomaticParams | Should -BeFalse Should -Invoke -CommandName Connect-SQL -Scope It -Times 1 -Exactly Should -Invoke -CommandName Invoke-SqlDscQuery -Scope It -Times 0 -Exactly @@ -2155,7 +2155,7 @@ Describe 'SqlServerDsc.Common\Test-AvailabilityReplicaSeedingModeAutomatic' -Tag It 'Should return $false when the instance version is <_> and the replica seeding mode is manual' -ForEach @(13, 14, 15) { $mockSqlVersion = $_ - Test-AvailabilityReplicaSeedingModeAutomatic @testAvailabilityReplicaSeedingModeAutomaticParams | Should -Be $false + Test-AvailabilityReplicaSeedingModeAutomatic @testAvailabilityReplicaSeedingModeAutomaticParams | Should -BeFalse Should -Invoke -CommandName Connect-SQL -Scope It -Times 1 -Exactly Should -Invoke -CommandName Invoke-SqlDscQuery -Scope It -Times 1 -Exactly @@ -2173,7 +2173,7 @@ Describe 'SqlServerDsc.Common\Test-AvailabilityReplicaSeedingModeAutomatic' -Tag $mockSqlVersion = $_ $mockDynamic_SeedingMode = 'Automatic' - Test-AvailabilityReplicaSeedingModeAutomatic @testAvailabilityReplicaSeedingModeAutomaticParams | Should -Be $true + Test-AvailabilityReplicaSeedingModeAutomatic @testAvailabilityReplicaSeedingModeAutomaticParams | Should -BeTrue Should -Invoke -CommandName Connect-SQL -Scope It -Times 1 -Exactly Should -Invoke -CommandName Invoke-SqlDscQuery -Scope It -Times 1 -Exactly @@ -2218,28 +2218,28 @@ Describe 'SqlServerDsc.Common\Test-ImpersonatePermissions' -Tag 'TestImpersonate Context 'When impersonate permissions are present for the login' { It 'Should return true when the impersonate any login permissions are present for the login' { Mock -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ImpersonateAnyLogin_ParameterFilter -MockWith { $true } - Test-ImpersonatePermissions -ServerObject $mockServerObject | Should -Be $true + Test-ImpersonatePermissions -ServerObject $mockServerObject | Should -BeTrue Should -Invoke -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ImpersonateAnyLogin_ParameterFilter -Scope It -Times 1 -Exactly } It 'Should return true when the control server permissions are present for the login' { Mock -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ControlServer_ParameterFilter -MockWith { $true } - Test-ImpersonatePermissions -ServerObject $mockServerObject | Should -Be $true + Test-ImpersonatePermissions -ServerObject $mockServerObject | Should -BeTrue Should -Invoke -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ControlServer_ParameterFilter -Scope It -Times 1 -Exactly } It 'Should return true when the impersonate login permissions are present for the login' { Mock -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ImpersonateLogin_ParameterFilter -MockWith { $true } - Test-ImpersonatePermissions -ServerObject $mockServerObject -SecurableName 'Login1' | Should -Be $true + Test-ImpersonatePermissions -ServerObject $mockServerObject -SecurableName 'Login1' | Should -BeTrue Should -Invoke -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ImpersonateLogin_ParameterFilter -Scope It -Times 1 -Exactly } It 'Should return true when the control login permissions are present for the login' { Mock -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ControlLogin_ParameterFilter -MockWith { $true } - Test-ImpersonatePermissions -ServerObject $mockServerObject -SecurableName 'Login1' | Should -Be $true + Test-ImpersonatePermissions -ServerObject $mockServerObject -SecurableName 'Login1' | Should -BeTrue Should -Invoke -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ControlLogin_ParameterFilter -Scope It -Times 1 -Exactly } @@ -2247,7 +2247,7 @@ Describe 'SqlServerDsc.Common\Test-ImpersonatePermissions' -Tag 'TestImpersonate Context 'When impersonate permissions are missing for the login' { It 'Should return false when the server permissions are missing for the login' { - Test-ImpersonatePermissions -ServerObject $mockServerObject | Should -Be $false + Test-ImpersonatePermissions -ServerObject $mockServerObject | Should -BeFalse Should -Invoke -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ImpersonateAnyLogin_ParameterFilter -Scope It -Times 1 -Exactly Should -Invoke -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ControlServer_ParameterFilter -Scope It -Times 1 -Exactly @@ -2256,7 +2256,7 @@ Describe 'SqlServerDsc.Common\Test-ImpersonatePermissions' -Tag 'TestImpersonate } It 'Should return false when the login permissions are missing for the login' { - Test-ImpersonatePermissions -ServerObject $mockServerObject -SecurableName 'Login1' | Should -Be $false + Test-ImpersonatePermissions -ServerObject $mockServerObject -SecurableName 'Login1' | Should -BeFalse Should -Invoke -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ImpersonateAnyLogin_ParameterFilter -Scope It -Times 1 -Exactly Should -Invoke -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ControlServer_ParameterFilter -Scope It -Times 1 -Exactly @@ -2393,7 +2393,7 @@ Describe 'SqlServerDsc.Common\Connect-SQL' -Tag 'ConnectSql' { It 'Should return the correct service instance' { $databaseEngineServerObject = Connect-SQL -ServerName $mockExpectedDatabaseEngineServer -SetupCredential $mockSqlCredential -LoginType 'SqlLogin' -ErrorAction 'Stop' - $databaseEngineServerObject.ConnectionContext.LoginSecure | Should -Be $false + $databaseEngineServerObject.ConnectionContext.LoginSecure | Should -BeFalse $databaseEngineServerObject.ConnectionContext.Login | Should -Be $mockSqlCredentialUserName $databaseEngineServerObject.ConnectionContext.SecurePassword | Should -Be $mockSqlCredentialSecurePassword $databaseEngineServerObject.ConnectionContext.ServerInstance | Should -BeExactly $mockExpectedDatabaseEngineServer @@ -2436,7 +2436,7 @@ Describe 'SqlServerDsc.Common\Connect-SQL' -Tag 'ConnectSql' { It 'Should return the correct service instance' { $databaseEngineServerObject = Connect-SQL -InstanceName $mockExpectedDatabaseEngineInstance -SetupCredential $mockSqlCredential -LoginType 'SqlLogin' -ErrorAction 'Stop' - $databaseEngineServerObject.ConnectionContext.LoginSecure | Should -Be $false + $databaseEngineServerObject.ConnectionContext.LoginSecure | Should -BeFalse $databaseEngineServerObject.ConnectionContext.Login | Should -Be $mockSqlCredentialUserName $databaseEngineServerObject.ConnectionContext.SecurePassword | Should -Be $mockSqlCredentialSecurePassword $databaseEngineServerObject.ConnectionContext.ServerInstance | Should -BeExactly "$mockExpectedDatabaseEngineServer\$mockExpectedDatabaseEngineInstance" @@ -2488,11 +2488,11 @@ Describe 'SqlServerDsc.Common\Connect-SQL' -Tag 'ConnectSql' { It 'Should return the correct service instance' { $databaseEngineServerObject = Connect-SQL @testParameters -ErrorAction 'Stop' $databaseEngineServerObject.ConnectionContext.ServerInstance | Should -BeExactly "$mockExpectedDatabaseEngineServer\$mockExpectedDatabaseEngineInstance" - $databaseEngineServerObject.ConnectionContext.ConnectAsUser | Should -Be $true + $databaseEngineServerObject.ConnectionContext.ConnectAsUser | Should -BeTrue $databaseEngineServerObject.ConnectionContext.ConnectAsUserPassword | Should -BeExactly $mockWinCredential.GetNetworkCredential().Password $databaseEngineServerObject.ConnectionContext.ConnectAsUserName | Should -BeExactly $mockWinCredential.UserName - $databaseEngineServerObject.ConnectionContext.ConnectAsUser | Should -Be $true - $databaseEngineServerObject.ConnectionContext.LoginSecure | Should -Be $true + $databaseEngineServerObject.ConnectionContext.ConnectAsUser | Should -BeTrue + $databaseEngineServerObject.ConnectionContext.LoginSecure | Should -BeTrue Should -Invoke -CommandName New-Object -Exactly -Times 1 -Scope It ` -ParameterFilter $mockNewObject_MicrosoftDatabaseEngine_ParameterFilter @@ -2513,11 +2513,11 @@ Describe 'SqlServerDsc.Common\Connect-SQL' -Tag 'ConnectSql' { It 'Should return the correct service instance' { $databaseEngineServerObject = Connect-SQL @testParameters -ErrorAction 'Stop' $databaseEngineServerObject.ConnectionContext.ServerInstance | Should -BeExactly "$mockExpectedDatabaseEngineServer\$mockExpectedDatabaseEngineInstance" - $databaseEngineServerObject.ConnectionContext.ConnectAsUser | Should -Be $true + $databaseEngineServerObject.ConnectionContext.ConnectAsUser | Should -BeTrue $databaseEngineServerObject.ConnectionContext.ConnectAsUserPassword | Should -BeExactly $mockWinCredential.GetNetworkCredential().Password $databaseEngineServerObject.ConnectionContext.ConnectAsUserName | Should -BeExactly $mockWinCredential.UserName - $databaseEngineServerObject.ConnectionContext.ConnectAsUser | Should -Be $true - $databaseEngineServerObject.ConnectionContext.LoginSecure | Should -Be $true + $databaseEngineServerObject.ConnectionContext.ConnectAsUser | Should -BeTrue + $databaseEngineServerObject.ConnectionContext.LoginSecure | Should -BeTrue Should -Invoke -CommandName New-Object -Exactly -Times 1 -Scope It ` -ParameterFilter $mockNewObject_MicrosoftDatabaseEngine_ParameterFilter @@ -2537,11 +2537,11 @@ Describe 'SqlServerDsc.Common\Connect-SQL' -Tag 'ConnectSql' { It 'Should return the correct service instance' { $databaseEngineServerObject = Connect-SQL @testParameters -ErrorAction 'Stop' $databaseEngineServerObject.ConnectionContext.ServerInstance | Should -BeExactly "$mockExpectedDatabaseEngineServer\$mockExpectedDatabaseEngineInstance" - $databaseEngineServerObject.ConnectionContext.ConnectAsUser | Should -Be $true + $databaseEngineServerObject.ConnectionContext.ConnectAsUser | Should -BeTrue $databaseEngineServerObject.ConnectionContext.ConnectAsUserPassword | Should -BeExactly $mockWinFqdnCredential.GetNetworkCredential().Password $databaseEngineServerObject.ConnectionContext.ConnectAsUserName | Should -BeExactly $mockWinFqdnCredential.UserName - $databaseEngineServerObject.ConnectionContext.ConnectAsUser | Should -Be $true - $databaseEngineServerObject.ConnectionContext.LoginSecure | Should -Be $true + $databaseEngineServerObject.ConnectionContext.ConnectAsUser | Should -BeTrue + $databaseEngineServerObject.ConnectionContext.LoginSecure | Should -BeTrue Should -Invoke -CommandName New-Object -Exactly -Times 1 -Scope It ` -ParameterFilter $mockNewObject_MicrosoftDatabaseEngine_ParameterFilter @@ -2755,7 +2755,7 @@ Describe 'SqlServerDsc.Common\Test-ClusterPermissions' -Tag 'TestClusterPermissi It "Should return NullOrEmpty when 'NT SERVICE\ClusSvc' is present and has the permissions to manage availability groups" { $mockClusterServicePermissionsPresent = $true - Test-ClusterPermissions -ServerObject $mockServerObject | Should -Be $true + Test-ClusterPermissions -ServerObject $mockServerObject | Should -BeTrue Should -Invoke -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly -ParameterFilter { $LoginName -eq $clusterServiceName @@ -2768,7 +2768,7 @@ Describe 'SqlServerDsc.Common\Test-ClusterPermissions' -Tag 'TestClusterPermissi It "Should return NullOrEmpty when 'NT AUTHORITY\System' is present and has the permissions to manage availability groups" { $mockSystemPermissionsPresent = $true - Test-ClusterPermissions -ServerObject $mockServerObject | Should -Be $true + Test-ClusterPermissions -ServerObject $mockServerObject | Should -BeTrue Should -Invoke -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly -ParameterFilter { $LoginName -eq $clusterServiceName @@ -3224,15 +3224,15 @@ Describe 'SqlServerDsc.Common\Find-ExceptionByNumber' -Tag 'FindExceptionByNumbe Context 'When searching Exception objects' { It 'Should return true for main exception' { - Find-ExceptionByNumber -ExceptionToSearch $mockException -ErrorNumber 1 | Should -Be $true + Find-ExceptionByNumber -ExceptionToSearch $mockException -ErrorNumber 1 | Should -BeTrue } It 'Should return true for inner exception' { - Find-ExceptionByNumber -ExceptionToSearch $mockException -ErrorNumber 2 | Should -Be $true + Find-ExceptionByNumber -ExceptionToSearch $mockException -ErrorNumber 2 | Should -BeTrue } It 'Should return false when message not found' { - Find-ExceptionByNumber -ExceptionToSearch $mockException -ErrorNumber 3 | Should -Be $false + Find-ExceptionByNumber -ExceptionToSearch $mockException -ErrorNumber 3 | Should -BeFalse } } } @@ -3382,19 +3382,19 @@ Describe 'SqlServerDsc.Common\Get-FilePathMajorVersion' -Tag 'GetFilePathMajorVe Describe 'Test-FeatureFlag' -Tag 'TestFeatureFlag' { Context 'When no feature flags was provided' { It 'Should return $false' { - Test-FeatureFlag -FeatureFlag $null -TestFlag 'MyFlag' | Should -Be $false + Test-FeatureFlag -FeatureFlag $null -TestFlag 'MyFlag' | Should -BeFalse } } Context 'When feature flags was provided' { It 'Should return $true' { - Test-FeatureFlag -FeatureFlag @('FirstFlag', 'SecondFlag') -TestFlag 'SecondFlag' | Should -Be $true + Test-FeatureFlag -FeatureFlag @('FirstFlag', 'SecondFlag') -TestFlag 'SecondFlag' | Should -BeTrue } } Context 'When feature flags was provided, but missing' { It 'Should return $false' { - Test-FeatureFlag -FeatureFlag @('MyFlag2') -TestFlag 'MyFlag' | Should -Be $false + Test-FeatureFlag -FeatureFlag @('MyFlag2') -TestFlag 'MyFlag' | Should -BeFalse } } } diff --git a/tests/Unit/Stubs/SMO.cs b/tests/Unit/Stubs/SMO.cs index fa07aee1a6..4ed0acb68b 100644 --- a/tests/Unit/Stubs/SMO.cs +++ b/tests/Unit/Stubs/SMO.cs @@ -350,6 +350,9 @@ public void Revoke( Microsoft.SqlServer.Management.Smo.ServerPermissionSet permi // Property for SQL Agent support public Microsoft.SqlServer.Management.Smo.Agent.JobServer JobServer { get; set; } + // Property for server configuration + public Microsoft.SqlServer.Management.Smo.Configuration Configuration { get; set; } + // Fabricated constructor private Server(string name, bool dummyParam) { @@ -363,10 +366,14 @@ public static Server CreateTypeInstance() server.JobServer = new Microsoft.SqlServer.Management.Smo.Agent.JobServer { Parent = server, - Alerts = Microsoft.SqlServer.Management.Smo.Agent.AlertCollection.CreateTypeInstance() + Alerts = Microsoft.SqlServer.Management.Smo.Agent.AlertCollection.CreateTypeInstance(), + Operators = Microsoft.SqlServer.Management.Smo.Agent.OperatorCollection.CreateTypeInstance() }; server.JobServer.Alerts.Parent = server.JobServer; + server.JobServer.Operators.Parent = server.JobServer; + + server.Configuration = Microsoft.SqlServer.Management.Smo.Configuration.CreateTypeInstance(); return server; } @@ -1060,7 +1067,7 @@ public static ConfigProperty CreateTypeInstance() } } - public class ConfigPropertyCollection + public class ConfigPropertyCollection : IEnumerable { // Property public System.Int32 Count { get; set; } @@ -1068,6 +1075,21 @@ public class ConfigPropertyCollection public System.Object SyncRoot { get; set; } public Microsoft.SqlServer.Management.Smo.ConfigProperty Item { get; set; } + // For enumeration + private List _items = new List(); + + // Implement IEnumerable + public IEnumerator GetEnumerator() + { + return _items.GetEnumerator(); + } + + // Add method to add items + public void Add(Microsoft.SqlServer.Management.Smo.ConfigProperty item) + { + _items.Add(item); + } + // Fabricated constructor private ConfigPropertyCollection() { } public static ConfigPropertyCollection CreateTypeInstance() @@ -1076,6 +1098,27 @@ public static ConfigPropertyCollection CreateTypeInstance() } } + public class Configuration + { + // Property + public Microsoft.SqlServer.Management.Smo.ConfigPropertyCollection Properties { get; set; } + + // Method + public void Alter() + { + } + + // Fabricated constructor + private Configuration() { } + public static Configuration CreateTypeInstance() + { + return new Configuration() + { + Properties = ConfigPropertyCollection.CreateTypeInstance() + }; + } + } + #endregion Public Classes } @@ -1560,6 +1603,25 @@ public enum CompletionAction Always = 3 } + // TypeName: Microsoft.SqlServer.Management.Smo.Agent.WeekDays + // Used by: + // SQL Agent Operator commands unit tests + // New-SqlDscAgentOperator.Tests.ps1 + // Set-SqlDscAgentOperator.Tests.ps1 + public enum WeekDays + { + Sunday = 1, + Monday = 2, + Tuesday = 4, + Wednesday = 8, + Thursday = 16, + Friday = 32, + Weekdays = 62, + Saturday = 64, + WeekEnds = 65, + EveryDay = 127 + } + #endregion #region Public Classes @@ -1568,6 +1630,8 @@ public enum CompletionAction // Used by: // SQL Agent Alert commands unit tests // SqlAgentAlert.Tests.ps1 + // SQL Agent Operator commands unit tests + // SqlAgentOperator.Tests.ps1 public class JobServer { // Constructor @@ -1575,6 +1639,7 @@ public JobServer() { } // Property public Microsoft.SqlServer.Management.Smo.Agent.AlertCollection Alerts { get; set; } + public Microsoft.SqlServer.Management.Smo.Agent.OperatorCollection Operators { get; set; } public Microsoft.SqlServer.Management.Sdk.Sfc.Urn Urn { get; set; } public System.String Name { get; set; } public Microsoft.SqlServer.Management.Smo.PropertyCollection Properties { get; set; } @@ -1582,6 +1647,11 @@ public JobServer() { } public Microsoft.SqlServer.Management.Smo.SqlSmoState State { get; set; } public Microsoft.SqlServer.Management.Smo.Server Parent { get; set; } + // Mock property counters for tracking method calls + public System.Int32 MockOperatorMethodCreateCalled { get; set; } + public System.Int32 MockOperatorMethodDropCalled { get; set; } + public System.Int32 MockOperatorMethodAlterCalled { get; set; } + // Fabricated constructor private JobServer(Microsoft.SqlServer.Management.Smo.Server server) { } public static JobServer CreateTypeInstance() @@ -1681,6 +1751,116 @@ public static Alert CreateTypeInstance() } } + // TypeName: Microsoft.SqlServer.Management.Smo.Agent.OperatorCollection + // Used by: + // SQL Agent Operator commands unit tests + // SqlAgentOperator.Tests.ps1 + public class OperatorCollection : ICollection + { + private System.Collections.Generic.Dictionary operators = new System.Collections.Generic.Dictionary(); + + // Property + public Microsoft.SqlServer.Management.Smo.Agent.Operator this[System.String name] + { + get { return operators.ContainsKey(name) ? operators[name] : null; } + set { operators[name] = value; } + } + public Microsoft.SqlServer.Management.Smo.Agent.Operator this[System.Int32 index] + { + get { return operators.Values.ElementAtOrDefault(index); } + set { /* Not implemented for stub */ } + } + public System.Int32 Count { get { return operators.Count; } set { } } + public System.Boolean IsSynchronized { get { return false; } set { } } + public System.Object SyncRoot { get { return null; } set { } } + public Microsoft.SqlServer.Management.Smo.Agent.JobServer Parent { get; set; } + + public void Add(Microsoft.SqlServer.Management.Smo.Agent.Operator operatorObj) { operators[operatorObj.Name] = operatorObj; } + public void Remove(Microsoft.SqlServer.Management.Smo.Agent.Operator operatorObj) { operators.Remove(operatorObj.Name); } + public void CopyTo(System.Array array, System.Int32 index) { /* Not implemented for stub */ } + public System.Collections.IEnumerator GetEnumerator() { return operators.Values.GetEnumerator(); } + public void Refresh() { /* Not implemented for stub */ } + + // Fabricated constructor + private OperatorCollection() { } + public static OperatorCollection CreateTypeInstance() + { + return new OperatorCollection(); + } + } + + // TypeName: Microsoft.SqlServer.Management.Smo.Agent.Operator + // Used by: + // Get-SqlDscAgentOperator.Tests.ps1 + // New-SqlDscAgentOperator.Tests.ps1 + // Set-SqlDscAgentOperator.Tests.ps1 + // Remove-SqlDscAgentOperator.Tests.ps1 + // Test-SqlDscAgentOperator.Tests.ps1 + public class Operator + { + public Operator() { } + public Operator(Microsoft.SqlServer.Management.Smo.Agent.JobServer jobServer, System.String name) + { + this.Parent = jobServer; + this.Name = name; + } + + // Property + public System.String Name { get; set; } + public System.String EmailAddress { get; set; } + public System.String CategoryName { get; set; } + public System.String NetSendAddress { get; set; } + public System.String PagerAddress { get; set; } + public Microsoft.SqlServer.Management.Smo.Agent.WeekDays PagerDays { get; set; } + public System.TimeSpan SaturdayPagerEndTime { get; set; } + public System.TimeSpan SaturdayPagerStartTime { get; set; } + public System.TimeSpan SundayPagerEndTime { get; set; } + public System.TimeSpan SundayPagerStartTime { get; set; } + public System.TimeSpan WeekdayPagerEndTime { get; set; } + public System.TimeSpan WeekdayPagerStartTime { get; set; } + public System.Boolean Enabled { get; set; } + public Microsoft.SqlServer.Management.Sdk.Sfc.Urn Urn { get; set; } + public Microsoft.SqlServer.Management.Smo.PropertyCollection Properties { get; set; } + public System.Object UserData { get; set; } + public Microsoft.SqlServer.Management.Smo.SqlSmoState State { get; set; } + public Microsoft.SqlServer.Management.Smo.Agent.JobServer Parent { get; set; } + + // Method + public void Create() + { + if (this.Parent != null) + { + this.Parent.MockOperatorMethodCreateCalled++; + } + + // Mock failure for specific operator name used in testing + if (this.Name == "MockFailMethodCreateOperator") + { + throw new System.Exception("Simulated Create() method failure for testing purposes."); + } + } + public void Drop() + { + if (this.Parent != null) + { + this.Parent.MockOperatorMethodDropCalled++; + } + } + public void Alter() + { + if (this.Parent != null) + { + this.Parent.MockOperatorMethodAlterCalled++; + } + } + + // Fabricated constructor + private Operator(Microsoft.SqlServer.Management.Smo.Agent.JobServer jobServer, System.String name, System.Boolean dummyParam) { } + public static Operator CreateTypeInstance() + { + return new Operator(); + } + } + #endregion } -