diff --git a/.github/instructions/SqlServerDsc-guidelines.instructions.md b/.github/instructions/SqlServerDsc-guidelines.instructions.md index 5f9d373ecc..5202dbd54a 100644 --- a/.github/instructions/SqlServerDsc-guidelines.instructions.md +++ b/.github/instructions/SqlServerDsc-guidelines.instructions.md @@ -7,6 +7,7 @@ applyTo: "**" ## Naming - Public commands: `{Verb}-SqlDsc{Noun}` format +- Private function: `{Verb}-{Noun}` format ## Resources - Database Engine resources: inherit `SqlResourceBase` @@ -24,8 +25,15 @@ applyTo: "**" ## Test Requirements - Unit tests: Add `$env:SqlServerDscCI = $true` in `BeforeAll`, remove in `AfterAll` -- Integration tests: Use `Disconnect-SqlDscDatabaseEngine` after `Connect-SqlDscDatabaseEngine` -- Test config: tests/Integration/Commands/README.md and tests/Integration/Resources/README.md -- Integration test script files must be added to a group -within the test stage in ./azure-pipelines.yml. -- Choose the appropriate group number based on the required dependencies +- Integration tests: + - If requiring SQL Server DB, start the Windows service in `BeforeAll`, stop it in `AfterAll`. + - Use `Connect-SqlDscDatabaseEngine` for SQL Server DB session, and always with correct CI credentials + - Use `Disconnect-SqlDscDatabaseEngine` after `Connect-SqlDscDatabaseEngine` + - Test config: tests/Integration/Commands/README.md and tests/Integration/Resources/README.md + - Integration test script files must be added to a group within the test stage in ./azure-pipelines.yml. + - Choose the appropriate group number based on the required dependencies + +## Unit tests +- When unit test uses SMO types, ensure they are properly stubbed in SMO.cs +- Load stub types from SMO.cs in unit test files, e.g. `Add-Type -Path "$PSScriptRoot/../Stubs/SMO.cs"` +- After changing SMO stub types, run tests in a new PowerShell session for changes to take effect. diff --git a/.github/instructions/dsc-community-style-guidelines-class-resource.instructions.md b/.github/instructions/dsc-community-style-guidelines-class-resource.instructions.md index 1e7d36116f..e018d98166 100644 --- a/.github/instructions/dsc-community-style-guidelines-class-resource.instructions.md +++ b/.github/instructions/dsc-community-style-guidelines-class-resource.instructions.md @@ -8,60 +8,73 @@ applyTo: "source/[cC]lasses/**/*.ps1" **Applies to:** Classes with `[DscResource(...)]` decoration only. ## Requirements -- File: `source/Classes/{ResourceName}.ps1` +- File: `source/Classes/020.{ResourceName}.ps1` - Decoration: `[DscResource(RunAsCredential = 'Optional')]` (replace with `'Mandatory'` if required) - Inheritance: Must inherit `ResourceBase` (part of module DscResource.Base) - `$this.localizedData` hashtable auto-populated by `ResourceBase` from localization file +## Required constructor + +```powershell +MyResourceName () : base ($PSScriptRoot) +{ + # Property names where state cannot be enforced, e.g Ensure + $this.ExcludeDscProperties = @() +} +``` + ## Required Method Pattern ```powershell [MyResourceName] Get() { + # Call base implementation to get current state $currentState = ([ResourceBase] $this).Get() - # If needed, post-processing based on returned current state before returning to user + # If needed, post-processing on current state that can not be handled by GetCurrentState() return $currentState } [System.Boolean] Test() { + # Call base implementation to test current state $inDesiredState = ([ResourceBase] $this).Test() - # If needed, post-processing based on returned test result before returning to user + # If needed, post-processing on test result that can not be handled by base Test() return $inDesiredState } [void] Set() { + # Call base implementation to set desired state ([ResourceBase] $this).Set() - # If needed, additional state changes that could not be handled by Modify() + # If needed, additional state changes that can not be handled by Modify() } hidden [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) { - # Return current state as hashtable - # Variable $properties contains the key properties (key-value pairs). + # Always return current state as hashtable, $properties contains key properties } hidden [void] Modify([System.Collections.Hashtable] $properties) { - # Set desired state for non-compliant properties only - # Variable $properties contains the properties (key-value pairs) that are not in desired state. + # Always set desired state, $properties contain those that must change state } +``` +## Optional Method Pattern + +```powershell hidden [void] AssertProperties([System.Collections.Hashtable] $properties) { - # Validate user-provided properties - # Variable $properties contains properties user assigned values. + # Validate user-provided properties, $properties contains user assigned values } hidden [void] NormalizeProperties([System.Collections.Hashtable] $properties) { - # Normalize user-provided properties - # Variable $properties contains properties user assigned values. + # Normalize user-provided properties, $properties contains user assigned values } ``` diff --git a/.github/instructions/dsc-community-style-guidelines-integration-tests.instructions.md b/.github/instructions/dsc-community-style-guidelines-integration-tests.instructions.md index 07293b682c..affb1a3804 100644 --- a/.github/instructions/dsc-community-style-guidelines-integration-tests.instructions.md +++ b/.github/instructions/dsc-community-style-guidelines-integration-tests.instructions.md @@ -14,6 +14,7 @@ applyTo: "tests/[iI]ntegration/**/*.[iI]ntegration.[tT]ests.ps1" - Avoid `ExpectedMessage` for `Should -Throw` assertions - Only run integration tests in CI unless explicitly instructed. - Call commands with `-Force` parameter where applicable (avoids prompting). +- Use `-ErrorAction Stop` on commands so failures surface immediately ## Required Setup Block diff --git a/.github/instructions/dsc-community-style-guidelines-localization.instructions.md b/.github/instructions/dsc-community-style-guidelines-localization.instructions.md index fe8e52ce4b..ad9bc1687b 100644 --- a/.github/instructions/dsc-community-style-guidelines-localization.instructions.md +++ b/.github/instructions/dsc-community-style-guidelines-localization.instructions.md @@ -14,9 +14,8 @@ applyTo: "source/**/*.ps1" - Commands/functions: `source/en-US/SqlServerDsc.strings.psd1` - Class resources: `source/en-US/{ResourceClassName}.strings.psd1` -## Key Naming -- Format: `FunctionName_Description` (underscore separators) -- Example: `Get_SqlDscDatabase_ConnectingToDatabase` +## Key Naming Patterns +- Format: `Verb_FunctionName_Action` (underscore separators), e.g. `Get_SqlDscDatabase_ConnectingToDatabase` ## String Format ```powershell diff --git a/.github/instructions/dsc-community-style-guidelines-markdown.instructions.md b/.github/instructions/dsc-community-style-guidelines-markdown.instructions.md index 36d2ae8d7b..4b5d496a0b 100644 --- a/.github/instructions/dsc-community-style-guidelines-markdown.instructions.md +++ b/.github/instructions/dsc-community-style-guidelines-markdown.instructions.md @@ -10,6 +10,7 @@ applyTo: "**/*.md" - Use '1.' for all items in ordered lists (1/1/1 numbering style) - Disable `MD013` rule by adding a comment for tables/code blocks exceeding 80 characters - Empty lines required before/after code blocks and headings (except before line 1) +- Escape backslashes in file paths only (not in code blocks) - Code blocks must specify language identifiers ## Text Formatting diff --git a/.github/instructions/dsc-community-style-guidelines-pester.instructions.md b/.github/instructions/dsc-community-style-guidelines-pester.instructions.md index b2b4052763..fc9c2fe2fe 100644 --- a/.github/instructions/dsc-community-style-guidelines-pester.instructions.md +++ b/.github/instructions/dsc-community-style-guidelines-pester.instructions.md @@ -12,7 +12,7 @@ applyTo: "**/*.[Tt]ests.ps1" - One `Describe` block per file matching the tested entity name - Test code only inside `Describe` blocks - Assertions only in `It` blocks -- Never test `Write-Verbose`, `Write-Debug`, or parameter binding behavior +- Never test verbose messages, debug messages or parameter binding behavior - Pass all mandatory parameters to avoid prompts ## Structure & Scope @@ -25,11 +25,15 @@ applyTo: "**/*.[Tt]ests.ps1" ## Syntax Rules - PascalCase: `Describe`, `Context`, `It`, `Should`, `BeforeAll`, `BeforeEach`, `AfterAll`, `AfterEach` -- `It` descriptions start with 'Should' - `Context` descriptions start with 'When' +- `It` descriptions start with 'Should', must not contain 'when' - Mock variables prefix: 'mock' - Prefer `-BeTrue`/`-BeFalse` over `-Be $true`/`-Be $false` - No `Should -Not -Throw` - invoke commands directly +- Never add an empty `-MockWith` block +- Omit `-MockWith` when returning `$null` +- Set `$PSDefaultParameterValues` for `Mock:ModuleName`, `Should:ModuleName`, `InModuleScope:ModuleName` +- Omit `-ModuleName` parameter on Pester commands ## File Organization - Class resources: `tests/Unit/Classes/{Name}.Tests.ps1` @@ -42,7 +46,7 @@ applyTo: "**/*.[Tt]ests.ps1" - Keep scope close to usage context ## Best Practices -- Assign unused return objects to `$null` +- Inside `It` blocks, assign unused return objects to `$null` (unless part of pipeline) - Tested entity must be called from within the `It` blocks - Keep results and assertions in same `It` block - Cover all scenarios and code paths diff --git a/.github/instructions/dsc-community-style-guidelines-powershell.instructions.md b/.github/instructions/dsc-community-style-guidelines-powershell.instructions.md index c335f5aa08..0ff1b6b903 100644 --- a/.github/instructions/dsc-community-style-guidelines-powershell.instructions.md +++ b/.github/instructions/dsc-community-style-guidelines-powershell.instructions.md @@ -15,6 +15,10 @@ applyTo: "**/*.ps?(m|d)1" - Classes: PascalCase - Include scope for script/global/environment variables: `$script:`, `$global:`, `$env:` +## File naming + +- Class files: `###.ClassName.ps1` format (e.g. `001.SqlReason.ps1`, `004.StartupParameters.ps1`) + ## Formatting ### Indentation & Spacing @@ -71,8 +75,10 @@ applyTo: "**/*.ps?(m|d)1" - Avoid `Write-Output` (use `return` instead) - Avoid `ConvertTo-SecureString -AsPlainText` in production code - Don't redefine reserved parameters (Verbose, Debug, etc.) -- Include a `Force` parameter for functions that support `$PSCmdlet.ShouldContinue` or that use `$PSCmdlet.ShouldProcess` +- 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 + - `$PSCmdlet.ShouldProcess` must use required pattern - Use `$PSCmdlet.ThrowTerminatingError()` for terminating errors, use relevant error category - Use `Write-Error` for non-terminating errors, use relevant error category - Use `Write-Warning` for warnings @@ -81,6 +87,32 @@ applyTo: "**/*.ps?(m|d)1" - Use `Write-Information` for informational messages. - Never use backtick as line continuation in production code. +## ShouldProcess Required Pattern + +- Ensure `$descriptionMessage` explains what will happen +- Ensure `$confirmationMessage` succinctly asks for confirmation +- Keep `$captionMessage` short and descriptive (no trailing `.`) + +```powershell +$descriptionMessage = $script:localizedData.FunctionName_Action_ShouldProcessDescription -f $param1, $param2 +$confirmationMessage = $script:localizedData.FunctionName_Action_ShouldProcessConfirmation -f $param1 +$captionMessage = $script:localizedData.FunctionName_Action_ShouldProcessCaption + +if ($PSCmdlet.ShouldProcess($descriptionMessage, $confirmationMessage, $captionMessage)) +{ + # state changing code +} +``` + +## Force Parameter Pattern + +```powershell +if ($Force.IsPresent -and -not $Confirm) +{ + $ConfirmPreference = 'None' +} +``` + ### Structure ```powershell @@ -107,7 +139,7 @@ applyTo: "**/*.ps?(m|d)1" function Get-Something { [CmdletBinding()] - [OutputType([String])] + [OutputType([System.String])] param ( [Parameter(Mandatory = $true)] @@ -128,7 +160,7 @@ function Get-Something - Include `[CmdletBinding()]` on every function - Parameter block at top - Parameter block: `param ()` if empty, else opening/closing parentheses on own lines -- `[OutputType()]` for functions with output +- `[OutputType({return type})]` for functions with output, no output use `[OutputType()]` - All parameters use `[Parameter()]` attribute, mandatory parameters use `[Parameter(Mandatory = $true)]` - Parameter attributes on separate lines - Parameter type on line above parameter name diff --git a/.github/instructions/dsc-community-style-guidelines.instructions.md b/.github/instructions/dsc-community-style-guidelines.instructions.md index 4c273d6ac2..4d66738fb4 100644 --- a/.github/instructions/dsc-community-style-guidelines.instructions.md +++ b/.github/instructions/dsc-community-style-guidelines.instructions.md @@ -25,7 +25,7 @@ applyTo: "**" ## Requirements - Follow guidelines over existing code patterns - Always update CHANGELOG.md Unreleased section -- Localize all strings using string keys +- Localize all strings using string keys; remove any orphaned string keys - Check DscResource.Common before creating private functions - Separate reusable logic into private functions - Add unit tests for all commands/functions/resources diff --git a/.vscode/settings.json b/.vscode/settings.json index 1e6fedf669..804a27000c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -110,7 +110,7 @@ "/@prkelly/g", "/@glennsarti/g", "/tempdb/g", - "/sqlcmd\\.exe/g" + "/\\b[A-Za-z]{2,}\\d{4}\\b/g" ], "[markdown]": { "files.trimTrailingWhitespace": true, diff --git a/CHANGELOG.md b/CHANGELOG.md index 86aa8e395f..22054de4d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Throws a terminating error if the specified principal does not exist as a login. - Supports pipeline input and provides detailed error messages with localization. - Uses `Test-SqlDscIsLogin` command for login validation following module patterns. -- Added `Get-SqlDscLogin`, `Get-SqlDscRole`, `New-SqlDscLogin`, `New-SqlDscRole`, `Remove-SqlDscRole`, and `Remove-SqlDscLogin` commands for retrieving and managing SQL Server logins and roles with support for refresh, pipeline input, and ShouldProcess. +- Added `Get-SqlDscLogin`, `Get-SqlDscRole`, `New-SqlDscLogin`, `New-SqlDscRole`, + `Remove-SqlDscRole`, and `Remove-SqlDscLogin` commands for retrieving and managing + SQL Server logins and roles with support for refresh, pipeline input, and ShouldProcess. +- Added `Get-SqlDscAgentAlert`, `New-SqlDscAgentAlert`, + `Set-SqlDscAgentAlert`, `Remove-SqlDscAgentAlert`, and `Test-SqlDscAgentAlert` + to manage SQL Agent alerts on a Database Engine instance. ### Changed @@ -77,7 +82,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - DSC community style guidelines - Added requirement to follow guidelines over existing code patterns. - Improved markdown, pester, powershell, and changelog instructions. - - Fixed `Ingore` that seems in edge-cases fail. + - Fixed `Ignore` that seems in edge-cases fail. + - Improved markdown and changelog instructions. ## [17.1.0] - 2025-05-22 diff --git a/azure-pipelines.yml b/azure-pipelines.yml index cb39f5d209..b007ecf0ec 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -301,6 +301,12 @@ stages: 'tests/Integration/Commands/Get-SqlDscRole.Integration.Tests.ps1' 'tests/Integration/Commands/Remove-SqlDscRole.Integration.Tests.ps1' 'tests/Integration/Commands/Remove-SqlDscLogin.Integration.Tests.ps1' + 'tests/Integration/Commands/Get-SqlDscAgentAlert.Integration.Tests.ps1' + '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/Remove-SqlDscAgentAlert.Integration.Tests.ps1' + # Group 9 'tests/Integration/Commands/Uninstall-SqlDscServer.Integration.Tests.ps1' ) diff --git a/source/Private/Get-AgentAlertObject.ps1 b/source/Private/Get-AgentAlertObject.ps1 new file mode 100644 index 0000000000..339d7b5b11 --- /dev/null +++ b/source/Private/Get-AgentAlertObject.ps1 @@ -0,0 +1,50 @@ +<# + .SYNOPSIS + Gets a SQL Agent Alert object from the JobServer. + + .DESCRIPTION + Gets a SQL Agent Alert object from the JobServer based on the specified name. + + .PARAMETER ServerObject + Specifies the SQL Server object. + + .PARAMETER Name + Specifies the name of the SQL Agent Alert. + + .OUTPUTS + Microsoft.SqlServer.Management.Smo.Agent.Alert + + Returns the SQL Agent Alert object when an alert with the specified name is found. + + .OUTPUTS + None. + + When no alert with the specified name is found. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine + Get-AgentAlertObject -ServerObject $serverObject -Name 'MyAlert' + + Gets the SQL Agent Alert named 'MyAlert'. +#> +function Get-AgentAlertObject +{ + [CmdletBinding()] + [OutputType([Microsoft.SqlServer.Management.Smo.Agent.Alert])] + param + ( + [Parameter(Mandatory = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(Mandatory = $true)] + [System.String] + $Name + ) + + Write-Verbose -Message ($script:localizedData.Get_AgentAlertObject_GettingAlert -f $Name) + + $alertObject = $ServerObject.JobServer.Alerts | Where-Object -FilterScript { $_.Name -eq $Name } + + return $alertObject +} diff --git a/source/Public/Get-SqlDscAgentAlert.ps1 b/source/Public/Get-SqlDscAgentAlert.ps1 new file mode 100644 index 0000000000..7814860cea --- /dev/null +++ b/source/Public/Get-SqlDscAgentAlert.ps1 @@ -0,0 +1,92 @@ +<# + .SYNOPSIS + Returns SQL Agent Alert information. + + .DESCRIPTION + Returns SQL Agent Alert information from a SQL Server Database Engine instance. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER Name + Specifies the name of the SQL Agent Alert to retrieve. If not specified, + all alerts are returned. + + .INPUTS + Microsoft.SqlServer.Management.Smo.Server + + SQL Server Database Engine instance object. + + .OUTPUTS + Microsoft.SqlServer.Management.Smo.Agent.Alert + + When using the ByName parameter set, returns a single SQL Agent Alert object. + + .OUTPUTS + Microsoft.SqlServer.Management.Smo.Agent.Alert[] + + When using the All parameter set, returns an array of SQL Agent Alert objects. + + .OUTPUTS + None + Returns nothing when no alerts are found for the specified criteria. + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine + Get-SqlDscAgentAlert -ServerObject $serverObject + + Returns all SQL Agent Alerts from the server. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine + Get-SqlDscAgentAlert -ServerObject $serverObject -Name 'MyAlert' + + Returns the SQL Agent Alert named 'MyAlert'. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine + $serverObject | Get-SqlDscAgentAlert -Name 'MyAlert' + + Returns the SQL Agent Alert named 'MyAlert' using pipeline input. +#> +function Get-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(DefaultParameterSetName = 'All')] + [OutputType([Microsoft.SqlServer.Management.Smo.Agent.Alert], ParameterSetName = 'ByName')] + [OutputType([Microsoft.SqlServer.Management.Smo.Agent.Alert[]], 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 GSAA + process + { + switch ($PSCmdlet.ParameterSetName) + { + 'ByName' + { + return Get-AgentAlertObject -ServerObject $ServerObject -Name $Name + } + + 'All' + { + Write-Verbose -Message ($script:localizedData.Get_SqlDscAgentAlert_GettingAlerts -f $ServerObject.InstanceName) + + $alertCollection = $ServerObject.JobServer.Alerts + + Write-Verbose -Message ($script:localizedData.Get_SqlDscAgentAlert_ReturningAllAlerts -f $alertCollection.Count) + + return [Microsoft.SqlServer.Management.Smo.Agent.Alert[]] $alertCollection + } + } + } +} diff --git a/source/Public/New-SqlDscAgentAlert.ps1 b/source/Public/New-SqlDscAgentAlert.ps1 new file mode 100644 index 0000000000..4269232f23 --- /dev/null +++ b/source/Public/New-SqlDscAgentAlert.ps1 @@ -0,0 +1,142 @@ +<# + .SYNOPSIS + Creates a new SQL Agent Alert. + + .DESCRIPTION + This command creates a new SQL Agent Alert on a SQL Server Database Engine instance. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER Name + Specifies the name of the SQL Agent Alert to create. + + .PARAMETER Severity + Specifies the severity level for the SQL Agent Alert. Valid range is 0 to 25. + Cannot be used together with MessageId. + + .PARAMETER MessageId + Specifies the message ID for the SQL Agent Alert. Valid range is 0 to 2147483647. + Cannot be used together with Severity. + + .PARAMETER PassThru + If specified, the created alert object will be returned. + + .INPUTS + Microsoft.SqlServer.Management.Smo.Server + + SQL Server Database Engine instance object. + + .OUTPUTS + `[Microsoft.SqlServer.Management.Smo.Agent.Alert]` if passing parameter **PassThru**, + otherwise none. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + New-SqlDscAgentAlert -ServerObject $serverObject -Name 'MyAlert' -Severity 16 + + Creates a new SQL Agent Alert named 'MyAlert' with severity level 16. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | New-SqlDscAgentAlert -Name 'MyAlert' -MessageId 50001 + + Creates a new SQL Agent Alert named 'MyAlert' for message ID 50001 using pipeline input. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $alertObject = $serverObject | New-SqlDscAgentAlert -Name 'MyAlert' -Severity 16 -PassThru + + Creates a new SQL Agent Alert and returns the created object. + + .NOTES + Either -Severity or -MessageId must be specified (mutually exclusive). +#> +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)] + [OutputType([Microsoft.SqlServer.Management.Smo.Agent.Alert])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Name, + + [Parameter()] + [ValidateRange(0, 25)] + [System.Int32] + $Severity, + + [Parameter()] + [ValidateRange(0, 2147483647)] + [System.Int32] + $MessageId, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $PassThru + ) + + # cSpell: ignore NSAA + process + { + # Validate that both Severity and MessageId are not specified + Assert-BoundParameter -BoundParameterList $PSBoundParameters -MutuallyExclusiveList1 @('Severity') -MutuallyExclusiveList2 @('MessageId') + + # Check if alert already exists + $existingAlert = Get-AgentAlertObject -ServerObject $ServerObject -Name $Name + + if ($existingAlert) + { + $errorMessage = $script:localizedData.New_SqlDscAgentAlert_AlertAlreadyExists -f $Name + New-InvalidOperationException -Message $errorMessage + } + + $verboseDescriptionMessage = $script:localizedData.New_SqlDscAgentAlert_CreateShouldProcessVerboseDescription -f $Name, $ServerObject.InstanceName + $verboseWarningMessage = $script:localizedData.New_SqlDscAgentAlert_CreateShouldProcessVerboseWarning -f $Name + $captionMessage = $script:localizedData.New_SqlDscAgentAlert_CreateShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage)) + { + try + { + Write-Verbose -Message ($script:localizedData.New_SqlDscAgentAlert_CreatingAlert -f $Name) + + # Create the new alert SMO object + $newAlertObject = [Microsoft.SqlServer.Management.Smo.Agent.Alert]::new($ServerObject.JobServer, $Name) + + if ($PSBoundParameters.ContainsKey('Severity')) + { + Write-Verbose -Message ($script:localizedData.New_SqlDscAgentAlert_SettingSeverity -f $Severity, $Name) + $newAlertObject.Severity = $Severity + } + + if ($PSBoundParameters.ContainsKey('MessageId')) + { + Write-Verbose -Message ($script:localizedData.New_SqlDscAgentAlert_SettingMessageId -f $MessageId, $Name) + $newAlertObject.MessageId = $MessageId + } + + $newAlertObject.Create() + + Write-Verbose -Message ($script:localizedData.New_SqlDscAgentAlert_AlertCreated -f $Name) + + if ($PassThru.IsPresent) + { + return $newAlertObject + } + } + catch + { + $errorMessage = $script:localizedData.New_SqlDscAgentAlert_CreateFailed -f $Name + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + } + } + } +} diff --git a/source/Public/Remove-SqlDscAgentAlert.ps1 b/source/Public/Remove-SqlDscAgentAlert.ps1 new file mode 100644 index 0000000000..05e6e8a9e2 --- /dev/null +++ b/source/Public/Remove-SqlDscAgentAlert.ps1 @@ -0,0 +1,135 @@ +<# + .SYNOPSIS + Removes a SQL Agent Alert. + + .DESCRIPTION + This command removes a SQL Agent Alert from a SQL Server Database Engine instance. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER AlertObject + Specifies an alert object to remove. + + .PARAMETER Name + Specifies the name of the SQL Agent Alert to remove. + + .PARAMETER Force + Specifies that the alert should be removed without any confirmation. + + .PARAMETER Refresh + Specifies that the **ServerObject**'s alerts should be refreshed before + trying to remove the alert object. This is helpful when alerts could have + been modified outside of the **ServerObject**, for example through T-SQL. + + .INPUTS + Microsoft.SqlServer.Management.Smo.Server + + SQL Server Database Engine instance object. + + Microsoft.SqlServer.Management.Smo.Agent.Alert + + SQL Agent Alert object to remove. + + .OUTPUTS + None. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $alertObject = $serverObject | Get-SqlDscAgentAlert -Name 'MyAlert' + $alertObject | Remove-SqlDscAgentAlert + + Removes the alert named **MyAlert**. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | Remove-SqlDscAgentAlert -Name 'MyAlert' + + Removes the alert named **MyAlert**. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | Remove-SqlDscAgentAlert -Name 'MyAlert' -Force + + Removes the alert named **MyAlert** without confirmation. +#> +function Remove-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.')] + [OutputType()] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] + param + ( + [Parameter(ParameterSetName = 'ServerObject', Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(ParameterSetName = 'AlertObject', Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Agent.Alert] + $AlertObject, + + [Parameter(ParameterSetName = 'ServerObject', Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Name, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force, + + [Parameter(ParameterSetName = 'ServerObject')] + [System.Management.Automation.SwitchParameter] + $Refresh + ) + + # cSpell: ignore RSAA + process + { + if ($Force.IsPresent -and -not $Confirm) + { + $ConfirmPreference = 'None' + } + + if ($PSCmdlet.ParameterSetName -eq 'ServerObject') + { + if ($Refresh.IsPresent) + { + Write-Verbose -Message ($script:localizedData.Remove_SqlDscAgentAlert_RefreshingServerObject) + $ServerObject.JobServer.Alerts.Refresh() + } + + $alertObjectToRemove = Get-AgentAlertObject -ServerObject $ServerObject -Name $Name + + if (-not $alertObjectToRemove) + { + Write-Verbose -Message ($script:localizedData.Remove_SqlDscAgentAlert_AlertNotFound -f $Name) + return + } + } + else + { + $alertObjectToRemove = $AlertObject + } + + $verboseDescriptionMessage = $script:localizedData.Remove_SqlDscAgentAlert_RemoveShouldProcessVerboseDescription -f $alertObjectToRemove.Name, $alertObjectToRemove.Parent.Parent.InstanceName + $verboseWarningMessage = $script:localizedData.Remove_SqlDscAgentAlert_RemoveShouldProcessVerboseWarning -f $alertObjectToRemove.Name + $captionMessage = $script:localizedData.Remove_SqlDscAgentAlert_RemoveShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage)) + { + try + { + Write-Verbose -Message ($script:localizedData.Remove_SqlDscAgentAlert_RemovingAlert -f $alertObjectToRemove.Name) + + $alertObjectToRemove.Drop() + + Write-Verbose -Message ($script:localizedData.Remove_SqlDscAgentAlert_AlertRemoved -f $alertObjectToRemove.Name) + } + catch + { + $errorMessage = $script:localizedData.Remove_SqlDscAgentAlert_RemoveFailed -f $alertObjectToRemove.Name + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + } + } + } +} diff --git a/source/Public/Set-SqlDscAgentAlert.ps1 b/source/Public/Set-SqlDscAgentAlert.ps1 new file mode 100644 index 0000000000..10046c069e --- /dev/null +++ b/source/Public/Set-SqlDscAgentAlert.ps1 @@ -0,0 +1,216 @@ +<# + .SYNOPSIS + Updates a SQL Agent Alert. + + .DESCRIPTION + This command updates an existing SQL Agent Alert on a SQL Server Database Engine + instance. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER AlertObject + Specifies an alert object to update. + + .PARAMETER Name + Specifies the name of the SQL Agent Alert to update. + + .PARAMETER Severity + Specifies the severity level for the SQL Agent Alert. Valid range is 0 to 25. + Cannot be used together with MessageId. + + .PARAMETER MessageId + Specifies the message ID for the SQL Agent Alert. Valid range is 0 to 2147483647. + Cannot be used together with Severity. + + .PARAMETER PassThru + If specified, the updated alert object will be returned. + + .PARAMETER Refresh + Specifies that the alert object should be refreshed before updating. This + is helpful when alerts could have been modified outside of the **ServerObject**, + for example through T-SQL. + + .PARAMETER Force + Specifies that the alert should be updated without prompting for confirmation. + + .INPUTS + Microsoft.SqlServer.Management.Smo.Server + + SQL Server Database Engine instance object. + + Microsoft.SqlServer.Management.Smo.Agent.Alert + + SQL Agent Alert object to update. + + .OUTPUTS + Microsoft.SqlServer.Management.Smo.Agent.Alert + Returned when parameter **PassThru** is specified. + + None + No output is returned unless **PassThru** is specified. + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + Set-SqlDscAgentAlert -ServerObject $serverObject -Name 'MyAlert' -Severity 16 + + Updates the SQL Agent Alert named 'MyAlert' to severity level 16. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $alertObject = $serverObject | Get-SqlDscAgentAlert -Name 'MyAlert' + $alertObject | Set-SqlDscAgentAlert -MessageId 50001 + + Updates the SQL Agent Alert using pipeline input with alert object. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $updatedAlert = $serverObject | Set-SqlDscAgentAlert -Name 'MyAlert' -Severity 16 -PassThru + + Updates the alert and returns the updated object. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + Set-SqlDscAgentAlert -ServerObject $serverObject -Name 'MyAlert' -Severity 16 -Force + + Updates the SQL Agent Alert named 'MyAlert' to severity level 16 without prompting for confirmation. +#> +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)] + [OutputType([Microsoft.SqlServer.Management.Smo.Agent.Alert])] + param + ( + [Parameter(ParameterSetName = 'ServerObject', Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(ParameterSetName = 'AlertObject', Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Agent.Alert] + $AlertObject, + + [Parameter(ParameterSetName = 'ServerObject', Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Name, + + [Parameter()] + [ValidateRange(0, 25)] + [System.Int32] + $Severity, + + [Parameter()] + [ValidateRange(0, 2147483647)] + [System.Int32] + $MessageId, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $PassThru, + + [Parameter(ParameterSetName = 'ServerObject')] + [System.Management.Automation.SwitchParameter] + $Refresh, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force + ) + + # cSpell: ignore SSAA + process + { + if ($Force.IsPresent -and -not $Confirm) + { + $ConfirmPreference = 'None' + } + + # Validate that both Severity and MessageId are not specified + Assert-BoundParameter -BoundParameterList $PSBoundParameters -MutuallyExclusiveList1 @('Severity') -MutuallyExclusiveList2 @('MessageId') + + if ($PSCmdlet.ParameterSetName -eq 'ServerObject') + { + if ($Refresh.IsPresent) + { + Write-Verbose -Message ($script:localizedData.Set_SqlDscAgentAlert_RefreshingServerObject) + $ServerObject.JobServer.Alerts.Refresh() + } + + $alertObjectToUpdate = Get-AgentAlertObject -ServerObject $ServerObject -Name $Name + + if ($null -eq $alertObjectToUpdate) + { + $errorMessage = $script:localizedData.Set_SqlDscAgentAlert_AlertNotFound -f $Name + New-ObjectNotFoundException -Message $errorMessage + } + } + else + { + $alertObjectToUpdate = $AlertObject + } + + $verboseDescriptionMessage = $script:localizedData.Set_SqlDscAgentAlert_UpdateShouldProcessVerboseDescription -f $alertObjectToUpdate.Name, $alertObjectToUpdate.Parent.Parent.InstanceName + $verboseWarningMessage = $script:localizedData.Set_SqlDscAgentAlert_UpdateShouldProcessVerboseWarning -f $alertObjectToUpdate.Name + $captionMessage = $script:localizedData.Set_SqlDscAgentAlert_UpdateShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage)) + { + try + { + Write-Verbose -Message ($script:localizedData.Set_SqlDscAgentAlert_UpdatingAlert -f $alertObjectToUpdate.Name) + + $hasChanges = $false + + if ($PSBoundParameters.ContainsKey('Severity')) + { + if ($alertObjectToUpdate.Severity -ne $Severity) + { + Write-Verbose -Message ($script:localizedData.Set_SqlDscAgentAlert_SettingSeverity -f $Severity, $alertObjectToUpdate.Name) + $alertObjectToUpdate.Severity = $Severity + $alertObjectToUpdate.MessageId = 0 # Must set any conflicting properties to 0 + $hasChanges = $true + } + else + { + Write-Verbose -Message ($script:localizedData.Set_SqlDscAgentAlert_SeverityAlreadyCorrect -f $Severity, $alertObjectToUpdate.Name) + } + } + + if ($PSBoundParameters.ContainsKey('MessageId')) + { + if ($alertObjectToUpdate.MessageId -ne $MessageId) + { + Write-Verbose -Message ($script:localizedData.Set_SqlDscAgentAlert_SettingMessageId -f $MessageId, $alertObjectToUpdate.Name) + $alertObjectToUpdate.MessageId = $MessageId + $alertObjectToUpdate.Severity = 0 # Must set any conflicting properties to 0 + $hasChanges = $true + } + else + { + Write-Verbose -Message ($script:localizedData.Set_SqlDscAgentAlert_MessageIdAlreadyCorrect -f $MessageId, $alertObjectToUpdate.Name) + } + } + + if ($hasChanges) + { + $alertObjectToUpdate.Alter() + Write-Verbose -Message ($script:localizedData.Set_SqlDscAgentAlert_AlertUpdated -f $alertObjectToUpdate.Name) + } + else + { + Write-Verbose -Message ($script:localizedData.Set_SqlDscAgentAlert_NoChangesNeeded -f $alertObjectToUpdate.Name) + } + + if ($PassThru.IsPresent) + { + return $alertObjectToUpdate + } + } + catch + { + $errorMessage = $script:localizedData.Set_SqlDscAgentAlert_UpdateFailed -f $alertObjectToUpdate.Name + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + } + } + } +} diff --git a/source/Public/Test-SqlDscAgentAlert.ps1 b/source/Public/Test-SqlDscAgentAlert.ps1 new file mode 100644 index 0000000000..4301bc20c6 --- /dev/null +++ b/source/Public/Test-SqlDscAgentAlert.ps1 @@ -0,0 +1,137 @@ +<# + .SYNOPSIS + Tests if a SQL Agent Alert exists and has the desired properties. + + .DESCRIPTION + This command tests if a SQL Agent Alert exists on a SQL Server Database Engine + instance and optionally validates its properties. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER Name + Specifies the name of the SQL Agent Alert to test. + + .PARAMETER Severity + Specifies the expected severity level for the SQL Agent Alert. Valid range is 0 to 25. + If specified, the command will return $true only if the alert exists and has this severity. + + .PARAMETER MessageId + Specifies the expected message ID for the SQL Agent Alert. Valid range is 0 to 2147483647. + If specified, the command will return $true only if the alert exists and has this message ID. + + .INPUTS + Microsoft.SqlServer.Management.Smo.Server + + SQL Server Database Engine instance object. + + .OUTPUTS + [System.Boolean] + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + Test-SqlDscAgentAlert -ServerObject $serverObject -Name 'MyAlert' + + Tests if the SQL Agent Alert named 'MyAlert' exists. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | Test-SqlDscAgentAlert -Name 'MyAlert' -Severity 16 + + Tests if the SQL Agent Alert named 'MyAlert' exists and has severity level 16. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | Test-SqlDscAgentAlert -Name 'MyAlert' -MessageId 50001 + + Tests if the SQL Agent Alert named 'MyAlert' exists and has message ID 50001. +#> +function Test-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()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Name, + + [Parameter()] + [ValidateRange(0, 25)] + [System.Int32] + $Severity, + + [Parameter()] + [ValidateRange(0, 2147483647)] + [System.Int32] + $MessageId + ) + + # cSpell: ignore TSAA + process + { + # Validate that both Severity and MessageId are not specified + Assert-BoundParameter -BoundParameterList $PSBoundParameters -MutuallyExclusiveList1 @('Severity') -MutuallyExclusiveList2 @('MessageId') + + Write-Verbose -Message ($script:localizedData.Test_SqlDscAgentAlert_TestingAlert -f $Name) + + $alertObject = Get-AgentAlertObject -ServerObject $ServerObject -Name $Name + + if ($null -eq $alertObject) + { + Write-Verbose -Message ($script:localizedData.Test_SqlDscAgentAlert_AlertNotFound -f $Name) + + return $false + } + + Write-Verbose -Message ($script:localizedData.Test_SqlDscAgentAlert_AlertFound -f $Name) + + # If no specific properties are specified, just return true (alert exists) + if (-not $PSBoundParameters.ContainsKey('Severity') -and -not $PSBoundParameters.ContainsKey('MessageId')) + { + Write-Verbose -Message ($script:localizedData.Test_SqlDscAgentAlert_NoPropertyTest) + + return $true + } + + # Test severity if specified + if ($PSBoundParameters.ContainsKey('Severity')) + { + if ($alertObject.Severity -ne $Severity) + { + Write-Verbose -Message ($script:localizedData.Test_SqlDscAgentAlert_SeverityMismatch -f $alertObject.Severity, $Severity) + + return $false + } + else + { + Write-Verbose -Message ($script:localizedData.Test_SqlDscAgentAlert_SeverityMatch -f $Severity) + } + } + + # Test message ID if specified + if ($PSBoundParameters.ContainsKey('MessageId')) + { + if ($alertObject.MessageId -ne $MessageId) + { + Write-Verbose -Message ($script:localizedData.Test_SqlDscAgentAlert_MessageIdMismatch -f $alertObject.MessageId, $MessageId) + + return $false + } + else + { + Write-Verbose -Message ($script:localizedData.Test_SqlDscAgentAlert_MessageIdMatch -f $MessageId) + } + } + + Write-Verbose -Message ($script:localizedData.Test_SqlDscAgentAlert_AllTestsPassed -f $Name) + + return $true + } +} diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index adb2deb2ff..c4e6f719a1 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -288,4 +288,61 @@ ConvertFrom-StringData @' Login_Add_ShouldProcessCaption = Create login on instance Login_Add_LoginCreated = Successfully created login '{0}' on the instance '{1}'. Login_Add_LoginAlreadyExists = The login '{0}' already exists on the instance '{1}'. + + ## Get-AgentAlertObject + Get_AgentAlertObject_GettingAlert = Getting SQL Agent Alert '{0}'. (GAAO0001) + + ## Get-SqlDscAgentAlert + Get_SqlDscAgentAlert_GettingAlerts = Getting SQL Agent Alerts from instance '{0}'. (GSAA0001) + Get_SqlDscAgentAlert_ReturningAllAlerts = Returning all {0} SQL Agent Alerts. (GSAA0005) + + ## New-SqlDscAgentAlert + New_SqlDscAgentAlert_AlertAlreadyExists = SQL Agent Alert '{0}' already exists. (NSAA0001) + New_SqlDscAgentAlert_CreatingAlert = Creating SQL Agent Alert '{0}'. (NSAA0002) + New_SqlDscAgentAlert_AlertCreated = SQL Agent Alert '{0}' was created successfully. (NSAA0003) + New_SqlDscAgentAlert_CreateFailed = Failed to create SQL Agent Alert '{0}'. (NSAA0004) + New_SqlDscAgentAlert_SettingSeverity = Setting severity '{0}' for SQL Agent Alert '{1}'. (NSAA0005) + New_SqlDscAgentAlert_SettingMessageId = Setting message ID '{0}' for SQL Agent Alert '{1}'. (NSAA0006) + New_SqlDscAgentAlert_CreateShouldProcessVerboseDescription = Creating the SQL Agent Alert '{0}' on the instance '{1}'. + New_SqlDscAgentAlert_CreateShouldProcessVerboseWarning = Are you sure you want to create the SQL Agent Alert '{0}'? + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + New_SqlDscAgentAlert_CreateShouldProcessCaption = Create SQL Agent Alert on instance + + ## Set-SqlDscAgentAlert + Set_SqlDscAgentAlert_RefreshingServerObject = Refreshing server object's alerts collection. (SSAA0001) + Set_SqlDscAgentAlert_AlertNotFound = SQL Agent Alert '{0}' was not found. (SSAA0002) + Set_SqlDscAgentAlert_UpdatingAlert = Updating SQL Agent Alert '{0}'. (SSAA0003) + Set_SqlDscAgentAlert_SettingSeverity = Setting severity '{0}' for SQL Agent Alert '{1}'. (SSAA0004) + Set_SqlDscAgentAlert_SettingMessageId = Setting message ID '{0}' for SQL Agent Alert '{1}'. (SSAA0005) + Set_SqlDscAgentAlert_AlertUpdated = SQL Agent Alert '{0}' was updated successfully. (SSAA0006) + Set_SqlDscAgentAlert_NoChangesNeeded = No changes needed for SQL Agent Alert '{0}'. (SSAA0007) + Set_SqlDscAgentAlert_UpdateFailed = Failed to update SQL Agent Alert '{0}'. (SSAA0008) + Set_SqlDscAgentAlert_SeverityAlreadyCorrect = Severity '{0}' for SQL Agent Alert '{1}' is already correct. (SSAA0009) + Set_SqlDscAgentAlert_MessageIdAlreadyCorrect = Message ID '{0}' for SQL Agent Alert '{1}' is already correct. (SSAA0010) + Set_SqlDscAgentAlert_UpdateShouldProcessVerboseDescription = Updating the SQL Agent Alert '{0}' on the instance '{1}'. + Set_SqlDscAgentAlert_UpdateShouldProcessVerboseWarning = Are you sure you want to update the SQL Agent Alert '{0}'? + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + Set_SqlDscAgentAlert_UpdateShouldProcessCaption = Update SQL Agent Alert on instance + + ## Remove-SqlDscAgentAlert + Remove_SqlDscAgentAlert_RefreshingServerObject = Refreshing server object's alerts collection. (RSAA0001) + Remove_SqlDscAgentAlert_AlertNotFound = SQL Agent Alert '{0}' was not found. (RSAA0002) + Remove_SqlDscAgentAlert_RemovingAlert = Removing SQL Agent Alert '{0}'. (RSAA0003) + Remove_SqlDscAgentAlert_AlertRemoved = SQL Agent Alert '{0}' was removed successfully. (RSAA0004) + Remove_SqlDscAgentAlert_RemoveFailed = Failed to remove SQL Agent Alert '{0}'. (RSAA0005) + Remove_SqlDscAgentAlert_RemoveShouldProcessVerboseDescription = Removing the SQL Agent Alert '{0}' on the instance '{1}'. + Remove_SqlDscAgentAlert_RemoveShouldProcessVerboseWarning = Are you sure you want to remove the SQL Agent Alert '{0}'? + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + Remove_SqlDscAgentAlert_RemoveShouldProcessCaption = Remove SQL Agent Alert on instance + + ## Test-SqlDscAgentAlert + Test_SqlDscAgentAlert_TestingAlert = Testing if the SQL Agent Alert '{0}' exists and has the desired properties. (TSAA0001) + Test_SqlDscAgentAlert_AlertNotFound = SQL Agent Alert '{0}' was not found. (TSAA0002) + Test_SqlDscAgentAlert_AlertFound = SQL Agent Alert '{0}' was found. (TSAA0003) + Test_SqlDscAgentAlert_NoPropertyTest = No specific properties to test, alert exists. (TSAA0004) + Test_SqlDscAgentAlert_SeverityMismatch = Severity mismatch: current '{0}', expected '{1}'. (TSAA0005) + Test_SqlDscAgentAlert_SeverityMatch = Severity matches expected value '{0}'. (TSAA0006) + 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) '@ diff --git a/tests/Integration/Commands/Get-SqlDscAgentAlert.Integration.Tests.ps1 b/tests/Integration/Commands/Get-SqlDscAgentAlert.Integration.Tests.ps1 new file mode 100644 index 0000000000..49271e857f --- /dev/null +++ b/tests/Integration/Commands/Get-SqlDscAgentAlert.Integration.Tests.ps1 @@ -0,0 +1,120 @@ +[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:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName -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:dscModuleName -All | Remove-Module -Force +} + +Describe 'Get-SqlDscAgentAlert' -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' + + # Add SQL Server system message for testing message ID alerts + $addMessageQuery = @' +IF NOT EXISTS (SELECT 1 FROM sys.messages WHERE message_id = 50001 AND language_id = 1033) +BEGIN + EXECUTE sp_addmessage + 50001, + 16, + N'Mock message 50001'; +END +'@ + $script:sqlServerObject | Invoke-SqlDscQuery -DatabaseName 'master' -Query $addMessageQuery -Verbose -Force -ErrorAction 'Stop' + + # Create test alerts for getting + $script:sqlServerObject | New-SqlDscAgentAlert -Name 'IntegrationTest_GetAlert1' -Severity 16 -ErrorAction Stop + $script:sqlServerObject | New-SqlDscAgentAlert -Name 'IntegrationTest_GetAlert2' -MessageId 50001 -ErrorAction Stop + } + + AfterAll { + $script:sqlServerObject | Remove-SqlDscAgentAlert -Name 'IntegrationTest_GetAlert1' -Force -ErrorAction 'SilentlyContinue' + $script:sqlServerObject | Remove-SqlDscAgentAlert -Name 'IntegrationTest_GetAlert2' -Force -ErrorAction 'SilentlyContinue' + +# TODO: Keep this commented until we have a correct command to remove messages, and we can have another integration tests keeping a persistent message +# # Remove SQL Server system message +# $removeMessageQuery = @' +# EXECUTE sp_dropmessage 50001; +# '@ +# $script:sqlServerObject | Invoke-SqlDscQuery -DatabaseName 'master' -Query $removeMessageQuery -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 alerts' { + $alerts = $script:sqlServerObject | Get-SqlDscAgentAlert + + $alerts | Should -Not -BeNullOrEmpty + $alerts | Should -BeOfType [Microsoft.SqlServer.Management.Smo.Agent.Alert] + } + + It 'Should get specific alert by name' { + $alert = $script:sqlServerObject | Get-SqlDscAgentAlert -Name 'IntegrationTest_GetAlert1' + + $alert | Should -Not -BeNullOrEmpty + $alert.Name | Should -Be 'IntegrationTest_GetAlert1' + $alert.Severity | Should -Be 16 + } + + It 'Should return null for non-existent alert' { + $alert = $script:sqlServerObject | Get-SqlDscAgentAlert -Name 'NonExistentAlert' + + $alert | Should -BeNull + } + + It 'Should get alert with message ID' { + $alert = $script:sqlServerObject | Get-SqlDscAgentAlert -Name 'IntegrationTest_GetAlert2' + + $alert | Should -Not -BeNullOrEmpty + $alert.Name | Should -Be 'IntegrationTest_GetAlert2' + $alert.MessageId | Should -Be 50001 + } +} diff --git a/tests/Integration/Commands/Get-SqlDscRole.Integration.Tests.ps1 b/tests/Integration/Commands/Get-SqlDscRole.Integration.Tests.ps1 index b92a92a687..1da2e702e0 100644 --- a/tests/Integration/Commands/Get-SqlDscRole.Integration.Tests.ps1 +++ b/tests/Integration/Commands/Get-SqlDscRole.Integration.Tests.ps1 @@ -35,7 +35,6 @@ Describe 'Get-SqlDscRole' -Tag @('Integration_SQL2016', 'Integration_SQL2017', ' Start-Service -Name 'MSSQL$DSCSQLTEST' -Verbose -ErrorAction 'Stop' $script:mockInstanceName = 'DSCSQLTEST' - $script:mockComputerName = Get-ComputerName $mockSqlAdministratorUserName = 'SqlAdmin' # Using computer name as NetBIOS name throw exception. $mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force diff --git a/tests/Integration/Commands/New-SqlDscAgentAlert.Integration.Tests.ps1 b/tests/Integration/Commands/New-SqlDscAgentAlert.Integration.Tests.ps1 new file mode 100644 index 0000000000..14b57256b5 --- /dev/null +++ b/tests/Integration/Commands/New-SqlDscAgentAlert.Integration.Tests.ps1 @@ -0,0 +1,131 @@ +[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:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName -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:dscModuleName -All | Remove-Module -Force +} + +Describe 'New-SqlDscAgentAlert' -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 { + # Clean up test alerts + $testAlerts = @( + 'IntegrationTest_SeverityAlert', + 'IntegrationTest_MessageIdAlert', + 'IntegrationTest_PassThruAlert', + 'IntegrationTest_DuplicateAlert', + 'IntegrationTest_InvalidAlert' + ) + + foreach ($alertName in $testAlerts) + { + $null = $script:sqlServerObject | + Remove-SqlDscAgentAlert -Name $alertName -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 alert with severity' { + $null = $script:sqlServerObject | New-SqlDscAgentAlert -Name 'IntegrationTest_SeverityAlert' -Severity 14 -ErrorAction Stop + + $alert = $script:sqlServerObject | Get-SqlDscAgentAlert -Name 'IntegrationTest_SeverityAlert' + $alert | Should -Not -BeNullOrEmpty + $alert.Name | Should -Be 'IntegrationTest_SeverityAlert' + $alert.Severity | Should -Be 14 + } + + It 'Should create alert with message ID' { + $addMessageQuery = @' +IF NOT EXISTS (SELECT 1 FROM sys.messages WHERE message_id = 50002 AND language_id = 1033) +BEGIN + EXECUTE sp_addmessage + 50002, + 16, + N'Mock message 50002'; +END +'@ + $script:sqlServerObject | Invoke-SqlDscQuery -DatabaseName 'master' -Query $addMessageQuery -Verbose -Force -ErrorAction 'Stop' + + $null = $script:sqlServerObject | New-SqlDscAgentAlert -Name 'IntegrationTest_MessageIdAlert' -MessageId 50002 -ErrorAction Stop + + $alert = $script:sqlServerObject | Get-SqlDscAgentAlert -Name 'IntegrationTest_MessageIdAlert' + $alert | Should -Not -BeNullOrEmpty + $alert.Name | Should -Be 'IntegrationTest_MessageIdAlert' + $alert.MessageId | Should -Be 50002 + } + + It 'Should return alert object when PassThru is specified' { + $result = $script:sqlServerObject | New-SqlDscAgentAlert -Name 'IntegrationTest_PassThruAlert' -Severity 16 -PassThru -ErrorAction Stop + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'IntegrationTest_PassThruAlert' + $result.Severity | Should -Be 16 + } + + It 'Should throw error when alert already exists' { + # First create the alert + $script:sqlServerObject | New-SqlDscAgentAlert -Name 'IntegrationTest_DuplicateAlert' -Severity 15 -ErrorAction Stop + + # Try to create it again - should throw + { $script:sqlServerObject | New-SqlDscAgentAlert -Name 'IntegrationTest_DuplicateAlert' -Severity 14 -ErrorAction Stop } | + Should -Throw + } + + It 'Should throw error when both Severity and MessageId are specified' { + { $script:sqlServerObject | New-SqlDscAgentAlert -Name 'IntegrationTest_InvalidAlert' -Severity 16 -MessageId 50001 -ErrorAction Stop } | + Should -Throw + } +} diff --git a/tests/Integration/Commands/Remove-SqlDscAgentAlert.Integration.Tests.ps1 b/tests/Integration/Commands/Remove-SqlDscAgentAlert.Integration.Tests.ps1 new file mode 100644 index 0000000000..8a1cc43068 --- /dev/null +++ b/tests/Integration/Commands/Remove-SqlDscAgentAlert.Integration.Tests.ps1 @@ -0,0 +1,97 @@ +[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:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName -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:dscModuleName -All | Remove-Module -Force +} + +Describe 'Remove-SqlDscAgentAlert' -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 { + # 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 removing by ServerObject' { + It 'Should remove alert' { + # Create alert for this test + $script:sqlServerObject | New-SqlDscAgentAlert -Name 'IntegrationTest_RemoveAlert' -Severity 16 -ErrorAction Stop + + $null = $script:sqlServerObject | Remove-SqlDscAgentAlert -Name 'IntegrationTest_RemoveAlert' -Force -ErrorAction Stop + + $alert = $script:sqlServerObject | Get-SqlDscAgentAlert -Name 'IntegrationTest_RemoveAlert' + $alert | Should -BeNull + } + } + + Context 'When removing by AlertObject' { + It 'Should remove alert' { + # Create a new alert for this test + $script:sqlServerObject | New-SqlDscAgentAlert -Name 'IntegrationTest_RemoveAlert2' -Severity 16 -ErrorAction Stop + $alert = $script:sqlServerObject | Get-SqlDscAgentAlert -Name 'IntegrationTest_RemoveAlert2' + + $null = $alert | Remove-SqlDscAgentAlert -Force -ErrorAction Stop + + $removedAlert = $script:sqlServerObject | Get-SqlDscAgentAlert -Name 'IntegrationTest_RemoveAlert2' + $removedAlert | Should -BeNull + } + } + + Context 'When alert does not exist' { + It 'Should not throw error' { + $null = $script:sqlServerObject | Remove-SqlDscAgentAlert -Name 'NonExistentAlert' -Force -ErrorAction Stop + } + } +} diff --git a/tests/Integration/Commands/Set-SqlDscAgentAlert.Integration.Tests.ps1 b/tests/Integration/Commands/Set-SqlDscAgentAlert.Integration.Tests.ps1 new file mode 100644 index 0000000000..9f5ae71f70 --- /dev/null +++ b/tests/Integration/Commands/Set-SqlDscAgentAlert.Integration.Tests.ps1 @@ -0,0 +1,131 @@ +[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:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName -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:dscModuleName -All | Remove-Module -Force +} + +Describe 'Set-SqlDscAgentAlert' -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 alert for updating + $script:sqlServerObject | New-SqlDscAgentAlert -Name 'IntegrationTest_UpdateAlert' -Severity 14 -ErrorAction Stop + } + + AfterAll { + $null = $script:sqlServerObject | Remove-SqlDscAgentAlert -Name 'IntegrationTest_UpdateAlert' -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 update alert severity using ServerObject parameter set' { + $null = $script:sqlServerObject | Set-SqlDscAgentAlert -Name 'IntegrationTest_UpdateAlert' -Severity 16 -ErrorAction Stop + + $alert = $script:sqlServerObject | Get-SqlDscAgentAlert -Name 'IntegrationTest_UpdateAlert' + $alert.Severity | Should -Be 16 + $alert.MessageId | Should -Be 0 + } + + It 'Should update alert message ID using ServerObject parameter set' { + # Add SQL Server system message for testing message ID alerts + $addMessageQuery = @' +IF NOT EXISTS (SELECT 1 FROM sys.messages WHERE message_id = 50003 AND language_id = 1033) +BEGIN + EXECUTE sp_addmessage + 50003, + 16, + N'Mock message 50003'; +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 + + $alert = $script:sqlServerObject | Get-SqlDscAgentAlert -Name 'IntegrationTest_UpdateAlert' + $alert.MessageId | Should -Be 50003 + $alert.Severity | Should -Be 0 + } + + 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 + + $alert = $script:sqlServerObject | Get-SqlDscAgentAlert -Name 'IntegrationTest_UpdateAlert' + + $null = $alert | Set-SqlDscAgentAlert -Severity 18 -ErrorAction Stop + + # Refresh the alert to get updated values + $updatedAlert = $script:sqlServerObject | Get-SqlDscAgentAlert -Name 'IntegrationTest_UpdateAlert' + $updatedAlert.Severity | Should -Be 18 + $updatedAlert.MessageId | Should -Be 0 + } + + It 'Should return updated alert when PassThru is specified' { + $result = $script:sqlServerObject | Set-SqlDscAgentAlert -Name 'IntegrationTest_UpdateAlert' -Severity 20 -PassThru -ErrorAction Stop + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'IntegrationTest_UpdateAlert' + $result.Severity | Should -Be 20 + } + + It 'Should throw error when alert does not exist' { + { $script:sqlServerObject | Set-SqlDscAgentAlert -Name 'NonExistentAlert' -Severity 16 } | + Should -Throw + } + + It 'Should throw error when both Severity and MessageId are specified' { + { $script:sqlServerObject | Set-SqlDscAgentAlert -Name 'IntegrationTest_UpdateAlert' -Severity 16 -MessageId 50001 } | + Should -Throw + } +} diff --git a/tests/Integration/Commands/Test-SqlDscAgentAlert.Integration.Tests.ps1 b/tests/Integration/Commands/Test-SqlDscAgentAlert.Integration.Tests.ps1 new file mode 100644 index 0000000000..650ae263ae --- /dev/null +++ b/tests/Integration/Commands/Test-SqlDscAgentAlert.Integration.Tests.ps1 @@ -0,0 +1,122 @@ +[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:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName -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:dscModuleName -All | Remove-Module -Force +} + +Describe 'Test-SqlDscAgentAlert' -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 alerts for testing + $script:sqlServerObject | New-SqlDscAgentAlert -Name 'IntegrationTest_SeverityAlert' -Severity 16 -ErrorAction Stop + $script:sqlServerObject | New-SqlDscAgentAlert -Name 'IntegrationTest_MessageIdAlert' -MessageId 50001 -ErrorAction Stop + } + + AfterAll { + # Clean up test alerts + $script:sqlServerObject | Remove-SqlDscAgentAlert -Name 'IntegrationTest_SeverityAlert' -Force -ErrorAction 'SilentlyContinue' + $script:sqlServerObject | Remove-SqlDscAgentAlert -Name 'IntegrationTest_MessageIdAlert' -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 checking existence only' { + It 'Should return true for existing alert' { + $result = $script:sqlServerObject | Test-SqlDscAgentAlert -Name 'IntegrationTest_SeverityAlert' + + $result | Should -BeTrue + } + + It 'Should return false for non-existent alert' { + $result = $script:sqlServerObject | Test-SqlDscAgentAlert -Name 'NonExistentAlert' + + $result | Should -BeFalse + } + } + + Context 'When checking severity' { + It 'Should return true for matching severity' { + $result = $script:sqlServerObject | Test-SqlDscAgentAlert -Name 'IntegrationTest_SeverityAlert' -Severity 16 + + $result | Should -BeTrue + } + + It 'Should return false for non-matching severity' { + $result = $script:sqlServerObject | Test-SqlDscAgentAlert -Name 'IntegrationTest_SeverityAlert' -Severity 14 + + $result | Should -BeFalse + } + } + + Context 'When checking message ID' { + It 'Should return true for matching message ID' { + $result = $script:sqlServerObject | Test-SqlDscAgentAlert -Name 'IntegrationTest_MessageIdAlert' -MessageId 50001 + + $result | Should -BeTrue + } + + It 'Should return false for non-matching message ID' { + $result = $script:sqlServerObject | Test-SqlDscAgentAlert -Name 'IntegrationTest_MessageIdAlert' -MessageId 50002 + + $result | Should -BeFalse + } + } + + Context 'When both Severity and MessageId are specified' { + It 'Should throw error for both Severity and MessageId parameters' { + { $script:sqlServerObject | Test-SqlDscAgentAlert -Name 'IntegrationTest_SeverityAlert' -Severity 16 -MessageId 50001 } | + Should -Throw + } + } +} diff --git a/tests/Unit/Private/Get-AgentAlertObject.Tests.ps1 b/tests/Unit/Private/Get-AgentAlertObject.Tests.ps1 new file mode 100644 index 0000000000..b790c6c157 --- /dev/null +++ b/tests/Unit/Private/Get-AgentAlertObject.Tests.ps1 @@ -0,0 +1,116 @@ +[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 { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName -Force -ErrorAction 'Stop' + + # Load SMO stub types + Add-Type -Path "$PSScriptRoot/../Stubs/SMO.cs" + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Get-AgentAlertObject' -Tag 'Private' { + Context 'When getting an alert object' { + BeforeAll { + InModuleScope -ScriptBlock { + # Mock the alert object using SMO stub types + $script:mockAlertObject = [Microsoft.SqlServer.Management.Smo.Agent.Alert]::CreateTypeInstance() + $script:mockAlertObject.Name = 'TestAlert' + $script:mockAlertObject.Severity = 16 + $script:mockAlertObject.MessageID = 0 + + # Mock alert collection + $script:mockAlertCollection = [Microsoft.SqlServer.Management.Smo.Agent.AlertCollection]::CreateTypeInstance() + $script:mockAlertCollection.Add($script:mockAlertObject) + + # Mock JobServer object + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + $script:mockJobServer.Alerts = $script:mockAlertCollection + + # Mock the server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.JobServer = $script:mockJobServer + } + } + + It 'Should return the correct alert object when alert exists' { + InModuleScope -ScriptBlock { + $result = Get-AgentAlertObject -ServerObject $script:mockServerObject -Name 'TestAlert' + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'TestAlert' + $result.Severity | Should -Be '16' + } + } + + It 'Should return null when alert does not exist' { + InModuleScope -ScriptBlock { + $result = Get-AgentAlertObject -ServerObject $script:mockServerObject -Name 'NonExistentAlert' + + $result | Should -BeNull + } + } + } + + Context 'When server object has no alerts' { + BeforeAll { + InModuleScope -ScriptBlock { + # Mock alert collection (empty) + $script:mockEmptyAlertCollection = [Microsoft.SqlServer.Management.Smo.Agent.AlertCollection]::CreateTypeInstance() + + # Mock JobServer object + $script:mockEmptyJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + $script:mockEmptyJobServer.Alerts = $script:mockEmptyAlertCollection + + # Mock server object with empty alerts collection + $script:mockEmptyServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockEmptyServerObject.JobServer = $script:mockEmptyJobServer + } + } + + It 'Should return null when no alerts exist' { + InModuleScope -ScriptBlock { + $result = Get-AgentAlertObject -ServerObject $script:mockEmptyServerObject -Name 'TestAlert' + + $result | Should -BeNull + } + } + } +} diff --git a/tests/Unit/Public/Get-SqlDscAgentAlert.Tests.ps1 b/tests/Unit/Public/Get-SqlDscAgentAlert.Tests.ps1 new file mode 100644 index 0000000000..7d53327660 --- /dev/null +++ b/tests/Unit/Public/Get-SqlDscAgentAlert.Tests.ps1 @@ -0,0 +1,176 @@ +[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 { + $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 +} + +Describe 'Get-SqlDscAgentAlert' -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-SqlDscAgentAlert').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-SqlDscAgentAlert').Parameters['ServerObject'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have ServerObject accept pipeline input' { + $parameterInfo = (Get-Command -Name 'Get-SqlDscAgentAlert').Parameters['ServerObject'] + $parameterInfo.Attributes.ValueFromPipeline | Should -BeTrue + } + + It 'Should have Name as a mandatory parameter in ByName parameter set' { + $parameterInfo = (Get-Command -Name 'Get-SqlDscAgentAlert').Parameters['Name'] + $byNameParameterSet = $parameterInfo.ParameterSets['ByName'] + $byNameParameterSet.IsMandatory | Should -BeTrue + } + } + + Context 'When getting all alerts' { + BeforeAll { + # Mock alert objects using SMO stub types + $script:mockAlert1 = [Microsoft.SqlServer.Management.Smo.Agent.Alert]::CreateTypeInstance() + $script:mockAlert1.Name = 'Alert1' + $script:mockAlert1.Severity = 16 + + $script:mockAlert2 = [Microsoft.SqlServer.Management.Smo.Agent.Alert]::CreateTypeInstance() + $script:mockAlert2.Name = 'Alert2' + $script:mockAlert2.MessageID = 50001 + + # Mock alert collection + $script:mockAlertCollection = [Microsoft.SqlServer.Management.Smo.Agent.AlertCollection]::CreateTypeInstance() + $script:mockAlertCollection.Add($script:mockAlert1) + $script:mockAlertCollection.Add($script:mockAlert2) + + # Mock JobServer object + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + $script:mockJobServer.Alerts = $script:mockAlertCollection + + # Mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.JobServer = $script:mockJobServer + } + + It 'Should return all alerts when no name is specified' { + $result = Get-SqlDscAgentAlert -ServerObject $script:mockServerObject + + $result | Should -HaveCount 2 + $result[0].Name | Should -Be 'Alert1' + $result[1].Name | Should -Be 'Alert2' + } + } + + Context 'When getting a specific alert' { + BeforeAll { + # Mock alert objects using SMO stub types + $script:mockAlert1 = [Microsoft.SqlServer.Management.Smo.Agent.Alert]::CreateTypeInstance() + $script:mockAlert1.Name = 'TestAlert' + $script:mockAlert1.Severity = 16 + + # Mock alert collection + $script:mockAlertCollection = [Microsoft.SqlServer.Management.Smo.Agent.AlertCollection]::CreateTypeInstance() + $script:mockAlertCollection.Add($script:mockAlert1) + + # Mock JobServer object + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + $script:mockJobServer.Alerts = $script:mockAlertCollection + + # Mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.JobServer = $script:mockJobServer + } + + It 'Should return specific alert when name matches' { + $result = Get-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' + + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'TestAlert' + $result.Severity | Should -Be 16 + } + + It 'Should return null when alert does not exist' { + $result = Get-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'NonExistentAlert' + + $result | Should -BeNull + } + } + + Context 'When using pipeline input' { + BeforeAll { + # Mock alert collection + $script:mockAlertCollection = [Microsoft.SqlServer.Management.Smo.Agent.AlertCollection]::CreateTypeInstance() + + # Mock JobServer object + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + $script:mockJobServer.Alerts = $script:mockAlertCollection + + # 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-SqlDscAgentAlert + $result | Should -BeNullOrEmpty + } + } +} diff --git a/tests/Unit/Public/New-SqlDscAgentAlert.Tests.ps1 b/tests/Unit/Public/New-SqlDscAgentAlert.Tests.ps1 new file mode 100644 index 0000000000..0cd93b6136 --- /dev/null +++ b/tests/Unit/Public/New-SqlDscAgentAlert.Tests.ps1 @@ -0,0 +1,245 @@ +[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 { + $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 +} + +Describe 'New-SqlDscAgentAlert' -Tag 'Public' { + Context 'When command has correct parameter sets' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = '__AllParameterSets' + ExpectedParameters = '[-ServerObject] [-Name] [[-Severity] ] [[-MessageId] ] [-PassThru] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'New-SqlDscAgentAlert').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-SqlDscAgentAlert').Parameters['ServerObject'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have Name as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'New-SqlDscAgentAlert').Parameters['Name'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have Severity as an optional parameter' { + $parameterInfo = (Get-Command -Name 'New-SqlDscAgentAlert').Parameters['Severity'] + $parameterInfo.Attributes.Mandatory | Should -BeFalse + } + + It 'Should have MessageId as an optional parameter' { + $parameterInfo = (Get-Command -Name 'New-SqlDscAgentAlert').Parameters['MessageId'] + $parameterInfo.Attributes.Mandatory | Should -BeFalse + } + } + + Context 'When validating parameter ranges' { + It 'Should accept valid Severity values (0-25)' { + $command = Get-Command -Name 'New-SqlDscAgentAlert' + $severityParam = $command.Parameters['Severity'] + $validateRangeAttribute = $severityParam.Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateRangeAttribute] } + + $validateRangeAttribute.MinRange | Should -Be 0 + $validateRangeAttribute.MaxRange | Should -Be 25 + } + + It 'Should accept valid MessageId values (0-2147483647)' { + $command = Get-Command -Name 'New-SqlDscAgentAlert' + $messageIdParam = $command.Parameters['MessageId'] + $validateRangeAttribute = $messageIdParam.Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateRangeAttribute] } + + $validateRangeAttribute.MinRange | Should -Be 0 + $validateRangeAttribute.MaxRange | Should -Be 2147483647 + } + } + + Context 'When creating a new alert' { + BeforeAll { + # Mock alert collection (empty for new alert creation) + $script:mockAlertCollection = [Microsoft.SqlServer.Management.Smo.Agent.AlertCollection]::CreateTypeInstance() + + # Mock the JobServer using SMO stub types + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + $script:mockJobServer.Alerts = $script:mockAlertCollection + + # Mock server object using SMO stub types + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.JobServer = $script:mockJobServer + + # Mock the alert object that will be created + $script:mockNewAlert = [Microsoft.SqlServer.Management.Smo.Agent.Alert]::CreateTypeInstance() + $script:mockNewAlert.Name = 'TestAlert' + $script:mockNewAlert.Severity = 16 + $script:mockNewAlert.MessageID = 0 + + # Mock the private functions + Mock -CommandName 'Get-AgentAlertObject' + Mock -CommandName 'Assert-BoundParameter' + } + + Context 'When using basic parameters' { + It 'Should create alert with severity successfully' { + $null = New-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 + + Should -Invoke -CommandName 'Assert-BoundParameter' -Times 1 -Exactly + Should -Invoke -CommandName 'Get-AgentAlertObject' -Times 1 -Exactly + } + + It 'Should create alert with message ID successfully' { + $null = New-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -MessageId 50001 + + Should -Invoke -CommandName 'Assert-BoundParameter' -Times 1 -Exactly + Should -Invoke -CommandName 'Get-AgentAlertObject' -Times 1 -Exactly + } + } + + Context 'When using PassThru parameter' { + It 'Should return alert object for PassThru' { + $result = New-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 -PassThru + + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType [Microsoft.SqlServer.Management.Smo.Agent.Alert] + $result.Name | Should -Be 'TestAlert' + $result.Severity | Should -Be 16 + } + + It 'Should not return alert object without PassThru' { + $result = New-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 + + $result | Should -BeNullOrEmpty + } + } + + Context 'When using boundary values' { + It 'Should create alert with boundary severity values' { + # Test minimum value (0) - should complete without errors + $null = New-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert1' -Severity 0 + + # Test maximum value (25) - should complete without errors + $null = New-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert2' -Severity 25 + + # Verify that Get-AgentAlertObject was called for each alert creation to check existence + Should -Invoke -CommandName 'Get-AgentAlertObject' -Times 2 -Exactly + # Verify that Assert-BoundParameter was called for each alert creation + Should -Invoke -CommandName 'Assert-BoundParameter' -Times 2 -Exactly + } + + It 'Should create alert with boundary message ID values' { + # Test minimum value (0) - should complete without errors + $null = New-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert3' -MessageId 0 + + # Test maximum value (2147483647) - should complete without errors + $null = New-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert4' -MessageId 2147483647 + + # Verify that Get-AgentAlertObject was called for each alert creation to check existence + Should -Invoke -CommandName 'Get-AgentAlertObject' -Times 2 -Exactly + # Verify that Assert-BoundParameter was called for each alert creation + Should -Invoke -CommandName 'Assert-BoundParameter' -Times 2 -Exactly + } + } + } + + Context 'When alert already exists' { + BeforeAll { + # Mock alert collection with existing alert + $script:mockExistingAlert = [Microsoft.SqlServer.Management.Smo.Agent.Alert]::CreateTypeInstance() + $script:mockExistingAlert.Name = 'ExistingAlert' + + $script:mockAlertCollection = [Microsoft.SqlServer.Management.Smo.Agent.AlertCollection]::CreateTypeInstance() + $script:mockAlertCollection.Add($script:mockExistingAlert) + + # Mock JobServer object + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + $script:mockJobServer.Alerts = $script:mockAlertCollection + + # Mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.JobServer = $script:mockJobServer + + Mock -CommandName 'Get-AgentAlertObject' -MockWith { return $script:mockExistingAlert } + Mock -CommandName 'Assert-BoundParameter' + } + + It 'Should throw error' { + { New-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'ExistingAlert' -Severity 16 } | + Should -Throw -ExpectedMessage '*already exists*' + } + } + + Context 'When using WhatIf' { + BeforeAll { + # Mock alert collection (empty) + $script:mockAlertCollection = [Microsoft.SqlServer.Management.Smo.Agent.AlertCollection]::CreateTypeInstance() + + # Mock JobServer object + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + $script:mockJobServer.Alerts = $script:mockAlertCollection + + # Mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.JobServer = $script:mockJobServer + + # Mock alert object + $script:mockAlert = [Microsoft.SqlServer.Management.Smo.Agent.Alert]::CreateTypeInstance() + + Mock -CommandName 'Get-AgentAlertObject' + Mock -CommandName 'Assert-BoundParameter' + } + + It 'Should not create alert' { + $null = New-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 -WhatIf + } + } +} diff --git a/tests/Unit/Public/Remove-SqlDscAgentAlert.Tests.ps1 b/tests/Unit/Public/Remove-SqlDscAgentAlert.Tests.ps1 new file mode 100644 index 0000000000..0cffde4a2d --- /dev/null +++ b/tests/Unit/Public/Remove-SqlDscAgentAlert.Tests.ps1 @@ -0,0 +1,289 @@ +[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 { + $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 +} + +Describe 'Remove-SqlDscAgentAlert' -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 = 'AlertObject' + ExpectedParameters = '-AlertObject [-Force] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'Remove-SqlDscAgentAlert').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 'Remove-SqlDscAgentAlert').Parameters['ServerObject'] + $serverObjectParameterSet = $parameterInfo.ParameterSets['ServerObject'] + $serverObjectParameterSet.IsMandatory | Should -BeTrue + } + + It 'Should have AlertObject as a mandatory parameter in AlertObject parameter set' { + $parameterInfo = (Get-Command -Name 'Remove-SqlDscAgentAlert').Parameters['AlertObject'] + $alertObjectParameterSet = $parameterInfo.ParameterSets['AlertObject'] + $alertObjectParameterSet.IsMandatory | Should -BeTrue + } + + It 'Should support ShouldProcess' { + $commandInfo = Get-Command -Name 'Remove-SqlDscAgentAlert' + $commandInfo.Parameters.ContainsKey('WhatIf') | Should -BeTrue + $commandInfo.Parameters.ContainsKey('Confirm') | Should -BeTrue + } + + It 'Should have ConfirmImpact set to High' { + $commandInfo = Get-Command -Name 'Remove-SqlDscAgentAlert' + $commandInfo.Parameters['WhatIf'].Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] } | Should -Not -BeNullOrEmpty + $commandInfo.Parameters['Confirm'].Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] } | Should -Not -BeNullOrEmpty + } + } + + Context 'When removing alert using ServerObject parameter set' { + BeforeAll { + # Mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.InstanceName = 'TestInstance' + + # Mock JobServer object + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + + # Mock alert collection + $script:mockAlertCollection = [Microsoft.SqlServer.Management.Smo.Agent.AlertCollection]::CreateTypeInstance() + $script:mockJobServer.Alerts = $script:mockAlertCollection + + # Set up the hierarchy + $script:mockServerObject.JobServer = $script:mockJobServer + + # Mock alert object using SMO stub types + $script:mockAlert = [Microsoft.SqlServer.Management.Smo.Agent.Alert]::CreateTypeInstance() + $script:mockAlert.Name = 'TestAlert' + + # Add Parent properties to establish the hierarchy + $script:mockAlert | Add-Member -MemberType NoteProperty -Name 'Parent' -Value $script:mockJobServer -Force + $script:mockJobServer | Add-Member -MemberType NoteProperty -Name 'Parent' -Value $script:mockServerObject -Force + + Mock -CommandName 'Get-AgentAlertObject' -MockWith { return $script:mockAlert } + } + + It 'Should remove alert successfully' { + $null = Remove-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Force + + Should -Invoke -CommandName 'Get-AgentAlertObject' -Times 1 -Exactly + } + + It 'Should refresh server object when Refresh is specified' { + $null = Remove-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Refresh -Force + + # Verify that Refresh was called on the Alerts collection + # This would need to be mocked more specifically to verify the call + } + } + + Context 'When removing alert using AlertObject parameter set' { + BeforeAll { + # Mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.InstanceName = 'TestInstance' + + # Mock JobServer object + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + + # Set up the hierarchy + $script:mockJobServer | Add-Member -MemberType NoteProperty -Name 'Parent' -Value $script:mockServerObject -Force + + # Mock alert object using SMO stub types + $script:mockAlert = [Microsoft.SqlServer.Management.Smo.Agent.Alert]::CreateTypeInstance() + $script:mockAlert.Name = 'TestAlert' + + # Add Parent property to establish the hierarchy + $script:mockAlert | Add-Member -MemberType NoteProperty -Name 'Parent' -Value $script:mockJobServer -Force + } + + It 'Should remove alert using AlertObject parameter' { + $null = Remove-SqlDscAgentAlert -AlertObject $script:mockAlert -Force + } + } + + Context 'When alert does not exist' { + BeforeAll { + # Mock alert collection + $script:mockAlertCollection = [Microsoft.SqlServer.Management.Smo.Agent.AlertCollection]::CreateTypeInstance() + + # Mock JobServer object + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + $script:mockJobServer.Alerts = $script:mockAlertCollection + + # Mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.JobServer = $script:mockJobServer + + Mock -CommandName 'Get-AgentAlertObject' + } + + It 'Should not throw error when alert does not exist' { + $null = Remove-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'NonExistentAlert' -Force + + Should -Invoke -CommandName 'Get-AgentAlertObject' -Times 1 -Exactly + } + } + + Context 'When removal fails' { + BeforeAll { + # Mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.InstanceName = 'TestInstance' + + # Mock JobServer object + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + + # Mock alert collection + $script:mockAlertCollection = [Microsoft.SqlServer.Management.Smo.Agent.AlertCollection]::CreateTypeInstance() + $script:mockJobServer.Alerts = $script:mockAlertCollection + + # Set up the hierarchy + $script:mockServerObject.JobServer = $script:mockJobServer + + # Mock alert object that will fail on Drop using SMO stub types + $script:mockFailingAlert = [Microsoft.SqlServer.Management.Smo.Agent.Alert]::CreateTypeInstance() + $script:mockFailingAlert.Name = 'TestAlert' + + # Add Parent properties to establish the hierarchy + $script:mockFailingAlert | Add-Member -MemberType NoteProperty -Name 'Parent' -Value $script:mockJobServer -Force + $script:mockJobServer | Add-Member -MemberType NoteProperty -Name 'Parent' -Value $script:mockServerObject -Force + + Mock -CommandName 'Get-AgentAlertObject' -MockWith { return $script:mockFailingAlert } + + # Mock the Drop method to throw an error + $script:mockFailingAlert | Add-Member -MemberType ScriptMethod -Name 'Drop' -Value { throw 'Removal failed' } -Force + } + + It 'Should throw error when removal fails' { + { Remove-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Force } | + Should -Throw -ExpectedMessage '*Failed to remove*' + } + } + + Context 'When using WhatIf' { + BeforeAll { + # Mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.InstanceName = 'TestInstance' + + # Mock JobServer object + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + + # Mock alert collection + $script:mockAlertCollection = [Microsoft.SqlServer.Management.Smo.Agent.AlertCollection]::CreateTypeInstance() + $script:mockJobServer.Alerts = $script:mockAlertCollection + + # Set up the hierarchy + $script:mockServerObject.JobServer = $script:mockJobServer + + # Mock alert object using SMO stub types + $script:mockAlert = [Microsoft.SqlServer.Management.Smo.Agent.Alert]::CreateTypeInstance() + $script:mockAlert.Name = 'TestAlert' + + # Add Parent properties to establish the hierarchy + $script:mockAlert | Add-Member -MemberType NoteProperty -Name 'Parent' -Value $script:mockJobServer -Force + $script:mockJobServer | Add-Member -MemberType NoteProperty -Name 'Parent' -Value $script:mockServerObject -Force + + Mock -CommandName 'Get-AgentAlertObject' -MockWith { return $script:mockAlert } + } + + It 'Should not remove alert when WhatIf is specified' { + $null = Remove-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -WhatIf + + # The Drop method should not be called with WhatIf + # This would need more sophisticated mocking to verify + } + } + + Context 'When Force parameter affects confirmation' { + BeforeAll { + # Mock server object + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.InstanceName = 'TestInstance' + + # Mock JobServer object + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + + # Mock alert collection + $script:mockAlertCollection = [Microsoft.SqlServer.Management.Smo.Agent.AlertCollection]::CreateTypeInstance() + $script:mockJobServer.Alerts = $script:mockAlertCollection + + # Set up the hierarchy + $script:mockServerObject.JobServer = $script:mockJobServer + + # Mock alert object using SMO stub types + $script:mockAlert = [Microsoft.SqlServer.Management.Smo.Agent.Alert]::CreateTypeInstance() + $script:mockAlert.Name = 'TestAlert' + + # Add Parent properties to establish the hierarchy + $script:mockAlert | Add-Member -MemberType NoteProperty -Name 'Parent' -Value $script:mockJobServer -Force + $script:mockJobServer | Add-Member -MemberType NoteProperty -Name 'Parent' -Value $script:mockServerObject -Force + + Mock -CommandName 'Get-AgentAlertObject' -MockWith { return $script:mockAlert } + } + + It 'Should remove alert without confirmation when Force is specified' { + # This test verifies that Force parameter works, but full confirmation testing + # would require more complex mocking of the confirmation system + $null = Remove-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Force + } + } +} diff --git a/tests/Unit/Public/Set-SqlDscAgentAlert.Tests.ps1 b/tests/Unit/Public/Set-SqlDscAgentAlert.Tests.ps1 new file mode 100644 index 0000000000..a8cdaf1d1e --- /dev/null +++ b/tests/Unit/Public/Set-SqlDscAgentAlert.Tests.ps1 @@ -0,0 +1,392 @@ +[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 { + $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 +} + +Describe 'Set-SqlDscAgentAlert' -Tag 'Public' { + Context 'When command has correct parameter sets' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = 'ServerObject' + ExpectedParameters = '-ServerObject -Name [-Severity ] [-MessageId ] [-PassThru] [-Refresh] [-Force] [-WhatIf] [-Confirm] []' + } + @{ + ExpectedParameterSetName = 'AlertObject' + ExpectedParameters = '-AlertObject [-Severity ] [-MessageId ] [-PassThru] [-Force] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'Set-SqlDscAgentAlert').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 'Set-SqlDscAgentAlert').Parameters['ServerObject'] + $serverObjectParameterSet = $parameterInfo.ParameterSets['ServerObject'] + $serverObjectParameterSet.IsMandatory | Should -BeTrue + } + + It 'Should have AlertObject as a mandatory parameter in AlertObject parameter set' { + $parameterInfo = (Get-Command -Name 'Set-SqlDscAgentAlert').Parameters['AlertObject'] + $alertObjectParameterSet = $parameterInfo.ParameterSets['AlertObject'] + $alertObjectParameterSet.IsMandatory | Should -BeTrue + } + } + + Context 'When validating parameter ranges' { + It 'Should accept valid Severity values (0-25)' { + $command = Get-Command -Name 'Set-SqlDscAgentAlert' + $severityParam = $command.Parameters['Severity'] + $validateRangeAttribute = $severityParam.Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateRangeAttribute] } + + $validateRangeAttribute.MinRange | Should -Be 0 + $validateRangeAttribute.MaxRange | Should -Be 25 + } + + It 'Should accept valid MessageId values (0-2147483647)' { + $command = Get-Command -Name 'Set-SqlDscAgentAlert' + $messageIdParam = $command.Parameters['MessageId'] + $validateRangeAttribute = $messageIdParam.Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateRangeAttribute] } + + $validateRangeAttribute.MinRange | Should -Be 0 + $validateRangeAttribute.MaxRange | Should -Be 2147483647 + } + } + + Context 'When updating alert using ServerObject parameter set' { + BeforeAll { + # Mock the alert object using SMO stub types + $script:mockAlert = [Microsoft.SqlServer.Management.Smo.Agent.Alert]::CreateTypeInstance() + $script:mockAlert.Name = 'TestAlert' + $script:mockAlert.Severity = 14 + $script:mockAlert.MessageId = 0 + + # Mock alert collection using SMO stub types with refresh tracking + $script:mockAlertCollection = [Microsoft.SqlServer.Management.Smo.Agent.AlertCollection]::CreateTypeInstance() + $script:refreshCalled = $false + $script:mockAlertCollection | Add-Member -MemberType ScriptMethod -Name 'Refresh' -Value { $script:refreshCalled = $true } -Force + + # Mock the JobServer using SMO stub types + $script:mockJobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + $script:mockJobServer.Alerts = $script:mockAlertCollection + + # Mock server object using SMO stub types + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.JobServer = $script:mockJobServer + + Mock -CommandName 'Assert-BoundParameter' + Mock -CommandName 'Get-AgentAlertObject' -MockWith { return $script:mockAlert } + } + + BeforeEach { + # Reset the refresh tracking before each test + $script:refreshCalled = $false + } + + It 'Should update alert severity successfully' { + $null = Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 + + Should -Invoke -CommandName 'Assert-BoundParameter' -Times 1 -Exactly + Should -Invoke -CommandName 'Get-AgentAlertObject' -Times 1 -Exactly + $script:mockAlert.Severity | Should -Be 16 + $script:mockAlert.MessageId | Should -Be 0 + } + + It 'Should set MessageId to 0 when updating Severity to ensure no conflicts' { + # Set up alert with existing MessageId + $script:mockAlert.Severity = 0 + $script:mockAlert.MessageId = 12345 + + $null = Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 + + $script:mockAlert.Severity | Should -Be 16 + $script:mockAlert.MessageId | Should -Be 0 + } + + It 'Should set Severity to 0 when updating MessageId to ensure no conflicts' { + # Set up alert with existing Severity + $script:mockAlert.Severity = 15 + $script:mockAlert.MessageId = 0 + + $null = Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -MessageId 50001 + + $script:mockAlert.MessageId | Should -Be 50001 + $script:mockAlert.Severity | Should -Be 0 + } + + It 'Should call Alter method when changes are made' { + $script:alterCalled = $false + $script:mockAlert | Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { $script:alterCalled = $true } -Force + + # Set initial values different from what we'll set + $script:mockAlert.Severity = 10 + $script:mockAlert.MessageId = 0 + + $null = Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 + + $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 + + $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 | Should -BeOfType [Microsoft.SqlServer.Management.Smo.Agent.Alert] + $result.Name | Should -Be 'TestAlert' + $result.Severity | Should -Be 16 + } + + It 'Should not return alert object without PassThru' { + $result = Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 + + $result | Should -BeNullOrEmpty + } + } + + Context 'When using PassThru parameter' { + It 'Should not refresh server object when Refresh is not specified' { + $null = Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 + + $script:refreshCalled | Should -BeFalse + } + } + + Context 'When testing change detection logic' { + It 'Should detect changes when Severity is different' { + $script:alterCalled = $false + $script:mockAlert | Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { $script:alterCalled = $true } -Force + + # Set up alert with different severity + $script:mockAlert.Severity = 10 + $script:mockAlert.MessageId = 0 + + $null = Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 + + $script:alterCalled | Should -BeTrue + $script:mockAlert.Severity | Should -Be 16 + $script:mockAlert.MessageId | Should -Be 0 # Should be reset to 0 + } + + It 'Should detect changes when MessageId is different' { + $script:alterCalled = $false + $script:mockAlert | Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { $script:alterCalled = $true } -Force + + # Set up alert with different message ID + $script:mockAlert.Severity = 0 + $script:mockAlert.MessageId = 12345 + + $null = Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -MessageId 50001 + + $script:alterCalled | Should -BeTrue + $script:mockAlert.MessageId | Should -Be 50001 + $script:mockAlert.Severity | Should -Be 0 # Should be reset to 0 + } + } + } + + Context 'When updating alert using AlertObject parameter set' { + BeforeAll { + # Mock the alert object using SMO stub types + $script:mockAlert = [Microsoft.SqlServer.Management.Smo.Agent.Alert]::CreateTypeInstance() + $script:mockAlert.Name = 'TestAlert' + $script:mockAlert.Severity = 14 + $script:mockAlert.MessageId = 0 + + Mock -CommandName 'Assert-BoundParameter' -ModuleName $script:dscModuleName + } + + It 'Should update alert using AlertObject parameter' { + $null = Set-SqlDscAgentAlert -AlertObject $script:mockAlert -Severity 16 + + Should -Invoke -CommandName 'Assert-BoundParameter' -ModuleName $script:dscModuleName -Times 1 -Exactly + $script:mockAlert.Severity | Should -Be 16 + $script:mockAlert.MessageId | Should -Be 0 + } + + It 'Should set MessageId to 0 when updating Severity using AlertObject' { + # Set up alert with existing MessageId + $script:mockAlert.Severity = 10 + $script:mockAlert.MessageId = 12345 + + $null = Set-SqlDscAgentAlert -AlertObject $script:mockAlert -Severity 16 + + $script:mockAlert.Severity | Should -Be 16 + $script:mockAlert.MessageId | Should -Be 0 + } + + It 'Should set Severity to 0 when updating MessageId using AlertObject' { + # Set up alert with existing Severity + $script:mockAlert.Severity = 15 + $script:mockAlert.MessageId = 0 + + $null = Set-SqlDscAgentAlert -AlertObject $script:mockAlert -MessageId 50001 + + $script:mockAlert.MessageId | Should -Be 50001 + $script:mockAlert.Severity | Should -Be 0 + } + + It 'Should call Alter method when changes are made using AlertObject' { + $script:alterCalled = $false + $script:mockAlert | Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { $script:alterCalled = $true } -Force + + # Set initial values different from what we'll set + $script:mockAlert.Severity = 10 + $script:mockAlert.MessageId = 0 + + $null = Set-SqlDscAgentAlert -AlertObject $script:mockAlert -Severity 16 + + $script:alterCalled | Should -BeTrue + } + } + + Context 'When alert does not exist' { + BeforeAll { + # Mock server object using SMO stub types + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.JobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + + Mock -CommandName 'Assert-BoundParameter' -ModuleName $script:dscModuleName + Mock -CommandName 'Get-AgentAlertObject' -ModuleName $script:dscModuleName + } + + It 'Should throw error when alert does not exist' { + { Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'NonExistentAlert' -Severity 16 } | + Should -Throw -ExpectedMessage '*was not found*' + } + } + + Context 'When no changes are needed' { + BeforeAll { + # Mock the alert object using SMO stub types + $script:mockAlert = [Microsoft.SqlServer.Management.Smo.Agent.Alert]::CreateTypeInstance() + $script:mockAlert.Name = 'TestAlert' + $script:mockAlert.Severity = 14 + $script:mockAlert.MessageId = 0 + + # Mock server object using SMO stub types + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.JobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + + Mock -CommandName 'Assert-BoundParameter' -ModuleName $script:dscModuleName + Mock -CommandName 'Get-AgentAlertObject' -ModuleName $script:dscModuleName -MockWith { return $script:mockAlert } + } + + It 'Should not call Alter when Severity is already correct' { + $script:alterCalled = $false + $script:mockAlert | Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { $script:alterCalled = $true } -Force + + # Set up alert with the same severity we're going to set + $script:mockAlert.Severity = 14 + $script:mockAlert.MessageId = 0 + + Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 14 + + $script:alterCalled | Should -BeFalse + } + + It 'Should not call Alter when MessageId is already correct' { + $script:alterCalled = $false + $script:mockAlert | Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { $script:alterCalled = $true } -Force + + # Set up alert with the same message ID we're going to set + $script:mockAlert.Severity = 0 + $script:mockAlert.MessageId = 50001 + + Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -MessageId 50001 + + $script:alterCalled | Should -BeFalse + } + + It 'Should not call Alter when both Severity and MessageId parameters are provided but values are unchanged' { + $script:alterCalled = $false + $script:mockAlert | Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { $script:alterCalled = $true } -Force + + # Set up alert with same values we're going to set + $script:mockAlert.Severity = 14 + $script:mockAlert.MessageId = 0 + + Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 14 -MessageId 0 + + $script:alterCalled | Should -BeFalse + } + } + + Context 'When update fails' { + BeforeAll { + # Mock the alert object that will fail on Alter using SMO stub types + $script:mockFailingAlert = [Microsoft.SqlServer.Management.Smo.Agent.Alert]::CreateTypeInstance() + $script:mockFailingAlert.Name = 'TestAlert' + $script:mockFailingAlert.Severity = 14 + $script:mockFailingAlert.MessageId = 0 + # Mock the Alter method to throw an exception + $script:mockFailingAlert | Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { throw 'Update failed' } -Force + + # Mock server object using SMO stub types + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + $script:mockServerObject.JobServer = [Microsoft.SqlServer.Management.Smo.Agent.JobServer]::CreateTypeInstance() + + Mock -CommandName 'Assert-BoundParameter' -ModuleName $script:dscModuleName + Mock -CommandName 'Get-AgentAlertObject' -ModuleName $script:dscModuleName -MockWith { return $script:mockFailingAlert } + } + + It 'Should throw error when update fails' { + { Set-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 } | + Should -Throw -ExpectedMessage '*Failed to update*' + } + } +} diff --git a/tests/Unit/Public/Test-SqlDscAgentAlert.Tests.ps1 b/tests/Unit/Public/Test-SqlDscAgentAlert.Tests.ps1 new file mode 100644 index 0000000000..cc1b642f62 --- /dev/null +++ b/tests/Unit/Public/Test-SqlDscAgentAlert.Tests.ps1 @@ -0,0 +1,235 @@ +[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 { + $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 +} + +Describe 'Test-SqlDscAgentAlert' -Tag 'Public' { + Context 'When command has correct parameter sets' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = '__AllParameterSets' + ExpectedParameters = '[-ServerObject] [-Name] [[-Severity] ] [[-MessageId] ] []' + } + ) { + $result = (Get-Command -Name 'Test-SqlDscAgentAlert').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-SqlDscAgentAlert').Parameters['ServerObject'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have ServerObject accept pipeline input' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscAgentAlert').Parameters['ServerObject'] + $parameterInfo.Attributes.ValueFromPipeline | Should -BeTrue + } + + It 'Should have Name as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscAgentAlert').Parameters['Name'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have Severity as an optional parameter' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscAgentAlert').Parameters['Severity'] + $parameterInfo.Attributes.Mandatory | Should -BeFalse + } + + It 'Should have MessageId as an optional parameter' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscAgentAlert').Parameters['MessageId'] + $parameterInfo.Attributes.Mandatory | Should -BeFalse + } + } + + Context 'When validating parameter ranges' { + It 'Should accept valid Severity values (0-25)' { + $command = Get-Command -Name 'Test-SqlDscAgentAlert' + $severityParam = $command.Parameters['Severity'] + $validateRangeAttribute = $severityParam.Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateRangeAttribute] } + + $validateRangeAttribute.MinRange | Should -Be 0 + $validateRangeAttribute.MaxRange | Should -Be 25 + } + + It 'Should accept valid MessageId values (0-2147483647)' { + $command = Get-Command -Name 'Test-SqlDscAgentAlert' + $messageIdParam = $command.Parameters['MessageId'] + $validateRangeAttribute = $messageIdParam.Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateRangeAttribute] } + + $validateRangeAttribute.MinRange | Should -Be 0 + $validateRangeAttribute.MaxRange | Should -Be 2147483647 + } + } + + Context 'When testing alert existence only' { + BeforeAll { + # Mock the alert object using SMO stub types + $script:mockAlert = [Microsoft.SqlServer.Management.Smo.Agent.Alert]::CreateTypeInstance() + $script:mockAlert.Name = 'TestAlert' + $script:mockAlert.Severity = 16 + $script:mockAlert.MessageId = 0 + + # Mock server object using SMO stub types + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + + Mock -CommandName 'Assert-BoundParameter' -ModuleName $script:dscModuleName + Mock -CommandName 'Get-AgentAlertObject' -ModuleName $script:dscModuleName -MockWith { return $script:mockAlert } + } + + It 'Should return true when alert exists and no properties are specified' { + $result = Test-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' + + $result | Should -BeTrue + Should -Invoke -CommandName 'Assert-BoundParameter' -ModuleName $script:dscModuleName -Times 1 -Exactly + Should -Invoke -CommandName 'Get-AgentAlertObject' -ModuleName $script:dscModuleName -Times 1 -Exactly + } + } + + Context 'When testing alert with severity' { + BeforeAll { + # Mock the alert object with specific severity using SMO stub types + $script:mockAlert = [Microsoft.SqlServer.Management.Smo.Agent.Alert]::CreateTypeInstance() + $script:mockAlert.Name = 'TestAlert' + $script:mockAlert.Severity = 16 + $script:mockAlert.MessageId = 0 + + # Mock server object using SMO stub types + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + + Mock -CommandName 'Assert-BoundParameter' -ModuleName $script:dscModuleName + Mock -CommandName 'Get-AgentAlertObject' -ModuleName $script:dscModuleName -MockWith { return $script:mockAlert } + } + + It 'Should return true when alert exists and severity matches' { + $result = Test-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 + + $result | Should -BeTrue + } + + It 'Should return false when alert exists but severity does not match' { + $result = Test-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 14 + + $result | Should -BeFalse + } + } + + Context 'When testing alert with message ID' { + BeforeAll { + # Mock the alert object with specific message ID using SMO stub types + $script:mockAlert = [Microsoft.SqlServer.Management.Smo.Agent.Alert]::CreateTypeInstance() + $script:mockAlert.Name = 'TestAlert' + $script:mockAlert.Severity = 0 + $script:mockAlert.MessageId = 50001 + + # Mock server object using SMO stub types + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + + Mock -CommandName 'Assert-BoundParameter' -ModuleName $script:dscModuleName + Mock -CommandName 'Get-AgentAlertObject' -ModuleName $script:dscModuleName -MockWith { return $script:mockAlert } + } + + It 'Should return true when alert exists and message ID matches' { + $result = Test-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -MessageId 50001 + + $result | Should -BeTrue + } + + It 'Should return false when alert exists but message ID does not match' { + $result = Test-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -MessageId 50002 + + $result | Should -BeFalse + } + } + + Context 'When alert does not exist' { + BeforeAll { + # Mock server object using SMO stub types + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + + Mock -CommandName 'Assert-BoundParameter' -ModuleName $script:dscModuleName + Mock -CommandName 'Get-AgentAlertObject' -ModuleName $script:dscModuleName + } + + It 'Should return false when alert does not exist' { + $result = Test-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'NonExistentAlert' + + $result | Should -BeFalse + Should -Invoke -CommandName 'Get-AgentAlertObject' -ModuleName $script:dscModuleName -Times 1 -Exactly + } + } + + Context 'When parameter validation is called' { + BeforeAll { + # Mock server object using SMO stub types + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + + Mock -CommandName 'Assert-BoundParameter' -ModuleName $script:dscModuleName + Mock -CommandName 'Get-AgentAlertObject' -ModuleName $script:dscModuleName + } + + It 'Should call parameter validation with both severity and message ID' { + $null = Test-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 -MessageId 50001 + + Should -Invoke -CommandName 'Assert-BoundParameter' -ModuleName $script:dscModuleName -ParameterFilter { + $BoundParameterList.ContainsKey('Severity') -and $BoundParameterList.ContainsKey('MessageId') + } -Times 1 -Exactly + } + + It 'Should call parameter validation with only severity' { + $null = Test-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 + + Should -Invoke -CommandName 'Assert-BoundParameter' -ModuleName $script:dscModuleName -ParameterFilter { + $BoundParameterList.ContainsKey('Severity') -and -not $BoundParameterList.ContainsKey('MessageId') + } -Times 1 -Exactly + } + } +} diff --git a/tests/Unit/Stubs/SMO.cs b/tests/Unit/Stubs/SMO.cs index f755785e9e..e2089ab3da 100644 --- a/tests/Unit/Stubs/SMO.cs +++ b/tests/Unit/Stubs/SMO.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; +using System.Linq; using System.Security; using System.Runtime.InteropServices; @@ -345,6 +346,30 @@ public void Grant( Microsoft.SqlServer.Management.Smo.ServerPermissionSet permis public void Revoke( Microsoft.SqlServer.Management.Smo.ServerPermissionSet permission, string granteeName ) { } + + // Property for SQL Agent support + public Microsoft.SqlServer.Management.Smo.Agent.JobServer JobServer { get; set; } + + // Fabricated constructor + private Server(string name, bool dummyParam) + { + this.Name = name; + } + + public static Server CreateTypeInstance() + { + var server = new Server(); + + server.JobServer = new Microsoft.SqlServer.Management.Smo.Agent.JobServer + { + Parent = server, + Alerts = Microsoft.SqlServer.Management.Smo.Agent.AlertCollection.CreateTypeInstance() + }; + + server.JobServer.Alerts.Parent = server.JobServer; + + return server; + } } // TypeName: Microsoft.SqlServer.Management.Smo.Login @@ -1470,3 +1495,155 @@ public ManagedComputer(System.String machineName, System.String userName, System #endregion } + +namespace Microsoft.SqlServer.Management.Smo.Agent +{ + #region Public Enums + + // TypeName: Microsoft.SqlServer.Management.Smo.Agent.AlertType + // Used by: + // Get-SqlDscAgentAlert.Tests.ps1 + // New-SqlDscAgentAlert.Tests.ps1 + // Set-SqlDscAgentAlert.Tests.ps1 + // Remove-SqlDscAgentAlert.Tests.ps1 + // Test-SqlDscAgentAlert.Tests.ps1 + public enum AlertType + { + SqlServerEvent = 1, + SqlServerPerformanceCondition = 2, + NonSqlServerEvent = 3, + WmiEvent = 4 + } + + // TypeName: Microsoft.SqlServer.Management.Smo.Agent.CompletionAction + // Used by: + // SQL Agent Alert commands unit tests + public enum CompletionAction + { + Never = 0, + OnSuccess = 1, + OnFailure = 2, + Always = 3 + } + + #endregion + + #region Public Classes + + // TypeName: Microsoft.SqlServer.Management.Smo.Agent.JobServer + // Used by: + // SQL Agent Alert commands unit tests + public class JobServer + { + // Constructor + public JobServer() { } + + // Property + public Microsoft.SqlServer.Management.Smo.Agent.AlertCollection Alerts { 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; } + public System.Object UserData { get; set; } + public Microsoft.SqlServer.Management.Smo.SqlSmoState State { get; set; } + public Microsoft.SqlServer.Management.Smo.Server Parent { get; set; } + + // Fabricated constructor + private JobServer(Microsoft.SqlServer.Management.Smo.Server server) { } + public static JobServer CreateTypeInstance() + { + return new JobServer(); + } + } + + // TypeName: Microsoft.SqlServer.Management.Smo.Agent.AlertCollection + // Used by: + // SQL Agent Alert commands unit tests + public class AlertCollection : ICollection + { + private System.Collections.Generic.Dictionary alerts = new System.Collections.Generic.Dictionary(); + + // Property + public Microsoft.SqlServer.Management.Smo.Agent.Alert this[System.String name] + { + get { return alerts.ContainsKey(name) ? alerts[name] : null; } + set { alerts[name] = value; } + } + public Microsoft.SqlServer.Management.Smo.Agent.Alert this[System.Int32 index] + { + get { return alerts.Values.ElementAtOrDefault(index); } + set { /* Not implemented for stub */ } + } + public System.Int32 Count { get { return alerts.Count; } set { } } + public System.Boolean IsSynchronized { get; set; } + public System.Object SyncRoot { get; set; } + public Microsoft.SqlServer.Management.Smo.Agent.JobServer Parent { get; set; } + + // Method + public void Add(Microsoft.SqlServer.Management.Smo.Agent.Alert alert) { alerts[alert.Name] = alert; } + public void Remove(Microsoft.SqlServer.Management.Smo.Agent.Alert alert) { alerts.Remove(alert.Name); } + public void Remove(System.String name) { alerts.Remove(name); } + public void CopyTo(System.Array array, System.Int32 index) { } + public IEnumerator GetEnumerator() { return alerts.Values.GetEnumerator(); } + public void Refresh() { /* Stub implementation for refreshing alerts */ } + + // Fabricated constructor + private AlertCollection() { } + public static AlertCollection CreateTypeInstance() + { + return new AlertCollection(); + } + } + + // TypeName: Microsoft.SqlServer.Management.Smo.Agent.Alert + // Used by: + // Get-SqlDscAgentAlert.Tests.ps1 + // New-SqlDscAgentAlert.Tests.ps1 + // Set-SqlDscAgentAlert.Tests.ps1 + // Remove-SqlDscAgentAlert.Tests.ps1 + // Test-SqlDscAgentAlert.Tests.ps1 + public class Alert + { + // Constructor + public Alert() { } + public Alert(Microsoft.SqlServer.Management.Smo.Agent.JobServer jobServer, System.String name) + { + this.Name = name; + } + + // Property + public System.String Name { get; set; } + public System.Boolean IsEnabled { get; set; } + public Microsoft.SqlServer.Management.Smo.Agent.AlertType AlertType { get; set; } + public System.String DatabaseName { get; set; } + public System.String DelayBetweenResponses { get; set; } + public System.String EventDescriptionKeyword { get; set; } + public System.String EventSource { get; set; } + public System.Boolean HasNotification { get; set; } + public System.Boolean IncludeEventDescription { get; set; } + public System.Int32 MessageID { get; set; } + public System.String NotificationMessage { get; set; } + public System.String PerformanceCondition { get; set; } + public System.Int32 Severity { get; set; } + public System.String WmiEventNamespace { get; set; } + public System.String WmiEventQuery { 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() { } + public void Drop() { } + public void Alter() { } + + // Fabricated constructor + private Alert(Microsoft.SqlServer.Management.Smo.Agent.JobServer jobServer, System.String name, System.Boolean dummyParam) { } + public static Alert CreateTypeInstance() + { + return new Alert(); + } + } + + #endregion +}