diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 94d2d514e8..72b236f3da 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,2 +1,2 @@ # Requirements -- SqlServerDsc-specific guidelines override general project guidelines +- SqlServerDsc-specific guidelines and requirements override general project guidelines and requirements diff --git a/.github/instructions/SqlServerDsc-guidelines.instructions.md b/.github/instructions/SqlServerDsc-guidelines.instructions.md index cc8ec878ae..ec64ae1271 100644 --- a/.github/instructions/SqlServerDsc-guidelines.instructions.md +++ b/.github/instructions/SqlServerDsc-guidelines.instructions.md @@ -3,7 +3,15 @@ description: SqlServerDsc-specific guidelines for AI development. applyTo: "**" --- -# SqlServerDsc Guidelines +# SqlServerDsc Requirements + +## Build & Test Workflow Requirements +- Run PowerShell script files from repository root +- Setup build and test environment (once per `pwsh` session): `./build.ps1 -Tasks noop` +- Build project before running tests: `./build.ps1 -Tasks build` +- Run tests without coverage (wildcards allowed): `Invoke-PesterJob -Path '{tests filepath}' -SkipCodeCoverage` +- Run QA tests: `Invoke-PesterJob -Path 'tests/QA' -SkipCodeCoverage` +- Never run integration tests locally ## Naming - Public commands: `{Verb}-SqlDsc{Noun}` format @@ -25,7 +33,7 @@ applyTo: "**" - Reporting Services: instance `SSRS` - Power BI Report Server: instance `PBIRS` -## Test Requirements +## Tests Requirements - Unit tests: Add `$env:SqlServerDscCI = $true` in `BeforeAll`, remove in `AfterAll` - Integration tests: - If requiring SQL Server DB, start the Windows service in `BeforeAll`, stop it in `AfterAll`. diff --git a/.github/instructions/dsc-community-style-guidelines-changelog.instructions.md b/.github/instructions/dsc-community-style-guidelines-changelog.instructions.md index 5041db8fa0..58c292307d 100644 --- a/.github/instructions/dsc-community-style-guidelines-changelog.instructions.md +++ b/.github/instructions/dsc-community-style-guidelines-changelog.instructions.md @@ -10,4 +10,5 @@ applyTo: "CHANGELOG.md" - Describe notable changes briefly, ≤2 items per change type - Reference issues using format [issue #](https://github.com///issues/) - No empty lines between list items in same section -- Do not add item if there are already an existing item for the same change +- Skip adding entry if same change already exists in Unreleased section +- No duplicate sections or items in Unreleased section diff --git a/.github/instructions/dsc-community-style-guidelines-powershell.instructions.md b/.github/instructions/dsc-community-style-guidelines-powershell.instructions.md index 62db458d43..1fd755cc10 100644 --- a/.github/instructions/dsc-community-style-guidelines-powershell.instructions.md +++ b/.github/instructions/dsc-community-style-guidelines-powershell.instructions.md @@ -64,8 +64,8 @@ applyTo: "**/*.ps?(m|d)1" - Comment-based help: SYNOPSIS, DESCRIPTION (40+ chars), PARAMETER, EXAMPLE sections before function/class - Comment-based help indentation: keywords 4 spaces, text 8 spaces - Include examples for all parameter sets and combinations -- INPUTS: List each pipeline‑accepted type (one per line) with a 1‑line description. -- OUTPUTS: List each return type (one per line) with a 1‑line description. Must match both [OutputType()] and actual returns. +- INPUTS: List each pipeline‑accepted type (one per line) with a 1‑line description. Repeat keyword for each input type. +- OUTPUTS: List each return type (one per line) with a 1‑line description. Repeat keyword for each output type. Must match both `[OutputType()]` and actual returns. - .NOTES: Include only if it conveys critical info (constraints, side effects, security, version compatibility, breaking behavior). Keep to ≤2 short sentences. ## Functions @@ -83,6 +83,7 @@ applyTo: "**/*.ps?(m|d)1" - Inside `$PSCmdlet.ShouldProcess`-block, avoid using `Write-Verbose` - Never use backtick as line continuation in production code. - Set `$ErrorActionPreference = 'Stop'` before commands using `-ErrorAction 'Stop'`; restore previous value directly after invocation (do not use try-catch-finally) +- Use `[Alias()]` attribute for function aliases, never `Set-Alias` or `New-Alias` ## Output streams @@ -92,7 +93,10 @@ applyTo: "**/*.ps?(m|d)1" - Use `Write-Information` for: User-facing status updates; Important operational messages; Non-error state changes - Use `Write-Warning` for: Non-fatal issues requiring attention; Deprecated functionality usage; Configuration problems that don't block execution - Use `$PSCmdlet.ThrowTerminatingError()` for terminating errors (except for classes), use relevant error category, in try-catch include exception with localized message -- Use `Write-Error` for non-terminating errors, use relevant error category; always use `return` after `Write-Error` to avoid further processing +- Use `Write-Error` for non-terminating errors + - Always include `-Message` (localized string), `-Category` (relevant error category), `-ErrorId` (unique ID matching localized string ID), `-TargetObject` (object causing error) + - In catch blocks, pass original exception using `-Exception` + - Always use `return` after `Write-Error` to avoid further processing ## ShouldProcess Required Pattern @@ -134,14 +138,25 @@ if ($Force.IsPresent -and -not $Confirm) Parameter description .INPUTS - TypeName + TypeName1 - Description + Description1 + + .INPUTS + TypeName2 + + Description2 .OUTPUTS - TypeName + TypeName1 + + Description1 + + .OUTPUTS + TypeName2 + + Description2 - Description #> function Get-Something { diff --git a/.github/instructions/dsc-community-style-guidelines.instructions.md b/.github/instructions/dsc-community-style-guidelines.instructions.md index 1db020e9d5..f0d5748090 100644 --- a/.github/instructions/dsc-community-style-guidelines.instructions.md +++ b/.github/instructions/dsc-community-style-guidelines.instructions.md @@ -10,10 +10,11 @@ applyTo: "**" - **Function**: Private function - **Resource**: DSC class-based resource -## Build & Test Workflow -- Run in PowerShell, from repository root -- Build before running tests: `.\build.ps1 -Tasks build` -- Always run tests in new PowerShell session: `Invoke-Pester -Path @({test paths}) -Output Detailed` +## Build & Test Workflow Requirements +- Run PowerShell script files from repository root +- Setup build and test environment (once per `pwsh` session): `./build.ps1 -Tasks noop` +- Build project before running tests: `./build.ps1 -Tasks build` +- Always run tests in new `pwsh` session: `Invoke-Pester -Path @({test paths}) -Output Detailed` ## File Organization - Public commands: `source/Public/{CommandName}.ps1` diff --git a/CHANGELOG.md b/CHANGELOG.md index adf537753e..a0e28db0a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -92,7 +92,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `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` + `Set-SqlDscAgentAlert`, `Remove-SqlDscAgentAlert`, and `Test-SqlDscIsAgentAlert` to manage SQL Agent alerts on a Database Engine instance. - Added new public commands for SQL Agent Operator management: - `Get-SqlDscAgentOperator` - Get SQL Agent Operators from a SQL Server @@ -115,6 +115,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 patterns - Database objects can also be used as pipeline input for Set and Remove operations - Commands include comprehensive validation, localization, and ShouldProcess support +- `Test-SqlDscAgentAlertProperty` + - New command to test specific properties of SQL Agent alerts. + - Supports testing severity and message ID properties. + - Requires at least one property parameter to be specified. + - Supports pipeline input of + `[Microsoft.SqlServer.Management.Smo.Agent.Alert]` objects. - Added private function `Get-CommandParameter` to filter command parameters by excluding specified parameter names and common parameters, providing a reusable way to determine settable properties on objects. @@ -197,6 +203,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `source/Examples/Resources/SqlSetup/5-InstallNamedInstanceInFailoverClusterSecondNode.ps1` - Removed redundant `$SqlAdministratorCredential` parameter from example configuration. +- `New-SqlDscAgentAlert` + - Updated the command to use `Test-SqlDscIsAgentAlert` instead of directly + calling `Get-AgentAlertObject` when checking if an alert already exists + (issue [#2202](https://github.com/dsccommunity/SqlServerDsc/issues/2202)). +- `Test-SqlDscIsAgentAlert` + - Removed optional `Severity` and `MessageId` parameters - use + `Test-SqlDscAgentAlertProperty` instead for property testing. + - Now only tests for alert existence. + - Added support for pipeline input of + `[Microsoft.SqlServer.Management.Smo.Agent.Alert]` objects. + - Updated examples and documentation to reflect the simplified functionality. ## [17.1.0] - 2025-05-22 diff --git a/RequiredModules.psd1 b/RequiredModules.psd1 index 4bc2c97598..1dc6b38d44 100644 --- a/RequiredModules.psd1 +++ b/RequiredModules.psd1 @@ -66,4 +66,7 @@ } } PlatyPS = 'latest' + + # For development + 'Viscalyx.Common' = 'latest' # Invoke-PesterJob to run tests } diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b998996ba3..d1cc058d0c 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -316,7 +316,8 @@ stages: '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/Test-SqlDscAgentAlertProperty.Integration.Tests.ps1' + 'tests/Integration/Commands/Test-SqlDscIsAgentAlert.Integration.Tests.ps1' 'tests/Integration/Commands/Get-SqlDscAgentOperator.Integration.Tests.ps1' 'tests/Integration/Commands/New-SqlDscAgentOperator.Integration.Tests.ps1' 'tests/Integration/Commands/Set-SqlDscAgentOperator.Integration.Tests.ps1' diff --git a/source/Public/New-SqlDscAgentAlert.ps1 b/source/Public/New-SqlDscAgentAlert.ps1 index 83f0c8f993..31cc46bb8c 100644 --- a/source/Public/New-SqlDscAgentAlert.ps1 +++ b/source/Public/New-SqlDscAgentAlert.ps1 @@ -105,9 +105,9 @@ function New-SqlDscAgentAlert Assert-BoundParameter -BoundParameterList $PSBoundParameters -MutuallyExclusiveList1 @('Severity') -MutuallyExclusiveList2 @('MessageId') # Check if alert already exists - $existingAlert = Get-AgentAlertObject -ServerObject $ServerObject -Name $Name + $alertExists = Test-SqlDscIsAgentAlert -ServerObject $ServerObject -Name $Name - if ($existingAlert) + if ($alertExists) { $errorMessage = $script:localizedData.New_SqlDscAgentAlert_AlertAlreadyExists -f $Name diff --git a/source/Public/Test-SqlDscAgentAlert.ps1 b/source/Public/Test-SqlDscAgentAlertProperty.ps1 similarity index 56% rename from source/Public/Test-SqlDscAgentAlert.ps1 rename to source/Public/Test-SqlDscAgentAlertProperty.ps1 index 4301bc20c6..475107c3ab 100644 --- a/source/Public/Test-SqlDscAgentAlert.ps1 +++ b/source/Public/Test-SqlDscAgentAlertProperty.ps1 @@ -1,10 +1,11 @@ <# .SYNOPSIS - Tests if a SQL Agent Alert exists and has the desired properties. + Tests if a SQL Agent Alert has the specified properties. .DESCRIPTION - This command tests if a SQL Agent Alert exists on a SQL Server Database Engine - instance and optionally validates its properties. + This command tests if a SQL Agent Alert on a SQL Server Database Engine + instance has the specified properties. At least one property parameter + must be specified. .PARAMETER ServerObject Specifies current server connection object. @@ -12,6 +13,9 @@ .PARAMETER Name Specifies the name of the SQL Agent Alert to test. + .PARAMETER AlertObject + Specifies the SQL Agent Alert object 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. @@ -25,43 +29,52 @@ SQL Server Database Engine instance object. + .INPUTS + Microsoft.SqlServer.Management.Smo.Agent.Alert + + SQL Agent Alert object. + .OUTPUTS [System.Boolean] .EXAMPLE $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' - Test-SqlDscAgentAlert -ServerObject $serverObject -Name 'MyAlert' + Test-SqlDscAgentAlertProperty -ServerObject $serverObject -Name 'MyAlert' -Severity 16 - Tests if the SQL Agent Alert named 'MyAlert' exists. + 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' -Severity 16 + $serverObject | Test-SqlDscAgentAlertProperty -Name 'MyAlert' -MessageId 50001 - Tests if the SQL Agent Alert named 'MyAlert' exists and has severity level 16. + Tests if the SQL Agent Alert named 'MyAlert' exists and has message ID 50001. .EXAMPLE $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' - $serverObject | Test-SqlDscAgentAlert -Name 'MyAlert' -MessageId 50001 + $alertObject = $serverObject | Get-SqlDscAgentAlert -Name 'MyAlert' + $alertObject | Test-SqlDscAgentAlertProperty -Severity 16 - Tests if the SQL Agent Alert named 'MyAlert' exists and has message ID 50001. + Tests if the SQL Agent Alert has severity level 16 using alert object pipeline input. #> -function Test-SqlDscAgentAlert +function Test-SqlDscAgentAlertProperty { [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()] + [CmdletBinding(DefaultParameterSetName = 'ByServerAndName')] [OutputType([System.Boolean])] param ( - [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [Parameter(ParameterSetName = 'ByServerAndName', Mandatory = $true, ValueFromPipeline = $true)] [Microsoft.SqlServer.Management.Smo.Server] $ServerObject, - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] + [Parameter(ParameterSetName = 'ByServerAndName', Mandatory = $true)] [System.String] $Name, + [Parameter(ParameterSetName = 'ByAlertObject', Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Agent.Alert] + $AlertObject, + [Parameter()] [ValidateRange(0, 25)] [System.Int32] @@ -73,31 +86,31 @@ function Test-SqlDscAgentAlert $MessageId ) - # cSpell: ignore TSAA + # cSpell: ignore TSAAP process { + # Ensure at least one property parameter is specified + Assert-BoundParameter -BoundParameterList $PSBoundParameters -AtLeastOneList @('Severity', 'MessageId') + # 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) + if ($PSCmdlet.ParameterSetName -eq 'ByAlertObject') { - Write-Verbose -Message ($script:localizedData.Test_SqlDscAgentAlert_AlertNotFound -f $Name) - - return $false + $alertObject = $AlertObject } + else + { + $alertObject = Get-AgentAlertObject -ServerObject $ServerObject -Name $Name - Write-Verbose -Message ($script:localizedData.Test_SqlDscAgentAlert_AlertFound -f $Name) + if ($null -eq $alertObject) + { + $errorMessage = $script:localizedData.Test_SqlDscAgentAlertProperty_AlertNotFound -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) + Write-Error -Message $errorMessage -Category 'ObjectNotFound' -ErrorId 'TSDAAP0001' -TargetObject $Name - return $true + return $false + } } # Test severity if specified @@ -105,14 +118,8 @@ function Test-SqlDscAgentAlert { 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 @@ -120,18 +127,10 @@ function Test-SqlDscAgentAlert { 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/Public/Test-SqlDscIsAgentAlert.ps1 b/source/Public/Test-SqlDscIsAgentAlert.ps1 new file mode 100644 index 0000000000..69a61597b5 --- /dev/null +++ b/source/Public/Test-SqlDscIsAgentAlert.ps1 @@ -0,0 +1,70 @@ +<# + .SYNOPSIS + Tests if a SQL Agent Alert exists. + + .DESCRIPTION + This command tests if a SQL Agent Alert exists 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 test. + + .INPUTS + Microsoft.SqlServer.Management.Smo.Server + + SQL Server Database Engine instance object. + + .OUTPUTS + [System.Boolean] + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + Test-SqlDscIsAgentAlert -ServerObject $serverObject -Name 'MyAlert' + + Tests if the SQL Agent Alert named 'MyAlert' exists. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | Test-SqlDscIsAgentAlert -Name 'MyAlert' + + Tests if the SQL Agent Alert named 'MyAlert' exists using pipeline input. +#> +function Test-SqlDscIsAgentAlert +{ + [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.')] + [Alias('Test-SqlDscAgentAlert')] + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Name + ) + + process + { + Write-Verbose -Message ($script:localizedData.Test_SqlDscIsAgentAlert_TestingAlert -f $Name) + + $alertObject = Get-AgentAlertObject -ServerObject $ServerObject -Name $Name -ErrorAction 'SilentlyContinue' + + if ($null -eq $alertObject) + { + Write-Verbose -Message ($script:localizedData.Test_SqlDscIsAgentAlert_AlertNotFound -f $Name) + + return $false + } + + Write-Verbose -Message ($script:localizedData.Test_SqlDscIsAgentAlert_AlertFound -f $Name) + + return $true + } +} diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index aa66f40964..bf276cb8ee 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -462,16 +462,13 @@ ConvertFrom-StringData @' # 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) + ## Test-SqlDscIsAgentAlert + Test_SqlDscIsAgentAlert_TestingAlert = Testing if the SQL Agent Alert '{0}' exists. (TSIAA0001) + Test_SqlDscIsAgentAlert_AlertNotFound = SQL Agent Alert '{0}' was not found. (TSIAA0002) + Test_SqlDscIsAgentAlert_AlertFound = SQL Agent Alert '{0}' was found. (TSIAA0003) + + ## Test-SqlDscAgentAlertProperty + Test_SqlDscAgentAlertProperty_AlertNotFound = SQL Agent Alert '{0}' was not found. (TSDAAP0001) ## Get-SqlDscAgentOperator Get_SqlDscAgentOperator_GettingOperator = Getting SQL Agent Operator '{0}'. (GSAO0003) diff --git a/tests/Integration/Commands/README.md b/tests/Integration/Commands/README.md index 5b480faed5..594f10decd 100644 --- a/tests/Integration/Commands/README.md +++ b/tests/Integration/Commands/README.md @@ -68,7 +68,8 @@ Test-SqlDscDatabase | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTE Get-SqlDscAgentAlert | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - New-SqlDscAgentAlert | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | Test alerts Set-SqlDscAgentAlert | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - -Test-SqlDscAgentAlert | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - +Test-SqlDscAgentAlertProperty | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - +Test-SqlDscIsAgentAlert | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - Get-SqlDscAgentOperator | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - New-SqlDscAgentOperator | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | SqlDscIntegrationTestOperator_Persistent operator Set-SqlDscAgentOperator | 2 | 1 (Install-SqlDscServer), 0 (Prerequisites) | DSCSQLTEST | - diff --git a/tests/Integration/Commands/Test-SqlDscAgentAlertProperty.Integration.Tests.ps1 b/tests/Integration/Commands/Test-SqlDscAgentAlertProperty.Integration.Tests.ps1 new file mode 100644 index 0000000000..8590dcea1e --- /dev/null +++ b/tests/Integration/Commands/Test-SqlDscAgentAlertProperty.Integration.Tests.ps1 @@ -0,0 +1,145 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' +} + +Describe 'Test-SqlDscAgentAlertProperty' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022'){ + BeforeAll { + # Integration tests are run on the DSCSQLTEST instance + $script:sqlServerInstance = 'DSCSQLTEST' + + # 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 severity' { + It 'Should return true for matching severity' { + $result = $script:sqlServerObject | Test-SqlDscAgentAlertProperty -Name 'IntegrationTest_SeverityAlert' -Severity 16 -ErrorAction 'Stop' + + $result | Should -BeTrue + } + + It 'Should return false for non-matching severity' { + $result = $script:sqlServerObject | Test-SqlDscAgentAlertProperty -Name 'IntegrationTest_SeverityAlert' -Severity 14 -ErrorAction 'Stop' + + $result | Should -BeFalse + } + } + + Context 'When checking message ID' { + It 'Should return true for matching message ID' { + $result = $script:sqlServerObject | Test-SqlDscAgentAlertProperty -Name 'IntegrationTest_MessageIdAlert' -MessageId 50001 -ErrorAction 'Stop' + + $result | Should -BeTrue + } + + It 'Should return false for non-matching message ID' { + $result = $script:sqlServerObject | Test-SqlDscAgentAlertProperty -Name 'IntegrationTest_MessageIdAlert' -MessageId 50002 -ErrorAction 'Stop' + + $result | Should -BeFalse + } + } + + Context 'When alert does not exist' { + It 'Should throw an exception for non-existent alert with severity' { + { + $null = $script:sqlServerObject | + Test-SqlDscAgentAlertProperty -Name 'NonExistentAlert' -Severity 16 -ErrorAction 'Stop' + } | Should -Throw + } + + It 'Should throw an exception for non-existent alert with message ID' { + { + $null = $script:sqlServerObject | + Test-SqlDscAgentAlertProperty -Name 'NonExistentAlert' -MessageId 50001 -ErrorAction 'Stop' + } | Should -Throw + } + } + + Context 'When no properties are specified' { + It 'Should throw error when no property parameters are specified' { + { $script:sqlServerObject | Test-SqlDscAgentAlertProperty -Name 'IntegrationTest_SeverityAlert' -ErrorAction 'Stop' } | + Should -Throw + } + } + + Context 'When using AlertObject parameter' { + It 'Should return true when alert object has matching severity' { + $alertObject = $script:sqlServerObject | Get-SqlDscAgentAlert -Name 'IntegrationTest_SeverityAlert' -ErrorAction 'Stop' + $result = $alertObject | Test-SqlDscAgentAlertProperty -Severity 16 -ErrorAction 'Stop' + + $result | Should -BeTrue + } + + It 'Should return false when alert object has non-matching severity' { + $alertObject = $script:sqlServerObject | Get-SqlDscAgentAlert -Name 'IntegrationTest_SeverityAlert' -ErrorAction 'Stop' + $result = $alertObject | Test-SqlDscAgentAlertProperty -Severity 14 -ErrorAction 'Stop' + + $result | Should -BeFalse + } + + It 'Should return true when alert object has matching message ID' { + $alertObject = $script:sqlServerObject | Get-SqlDscAgentAlert -Name 'IntegrationTest_MessageIdAlert' -ErrorAction 'Stop' + $result = $alertObject | Test-SqlDscAgentAlertProperty -MessageId 50001 -ErrorAction 'Stop' + + $result | Should -BeTrue + } + } + + Context 'When both Severity and MessageId are specified' { + It 'Should throw error for both Severity and MessageId parameters' { + { $script:sqlServerObject | Test-SqlDscAgentAlertProperty -Name 'IntegrationTest_SeverityAlert' -Severity 16 -MessageId 50001 -ErrorAction 'Stop' } | + Should -Throw + } + } +} diff --git a/tests/Integration/Commands/Test-SqlDscAgentAlert.Integration.Tests.ps1 b/tests/Integration/Commands/Test-SqlDscIsAgentAlert.Integration.Tests.ps1 similarity index 66% rename from tests/Integration/Commands/Test-SqlDscAgentAlert.Integration.Tests.ps1 rename to tests/Integration/Commands/Test-SqlDscIsAgentAlert.Integration.Tests.ps1 index e99a97ce3e..1229eb23d2 100644 --- a/tests/Integration/Commands/Test-SqlDscAgentAlert.Integration.Tests.ps1 +++ b/tests/Integration/Commands/Test-SqlDscIsAgentAlert.Integration.Tests.ps1 @@ -29,7 +29,7 @@ BeforeAll { Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' } -Describe 'Test-SqlDscAgentAlert' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022'){ +Describe 'Test-SqlDscIsAgentAlert' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022'){ BeforeAll { # Integration tests are run on the DSCSQLTEST instance $script:sqlServerInstance = 'DSCSQLTEST' @@ -64,50 +64,15 @@ Describe 'Test-SqlDscAgentAlert' -Tag @('Integration_SQL2017', 'Integration_SQL2 Context 'When checking existence only' { It 'Should return true for existing alert' { - $result = $script:sqlServerObject | Test-SqlDscAgentAlert -Name 'IntegrationTest_SeverityAlert' + $result = $script:sqlServerObject | Test-SqlDscIsAgentAlert -Name 'IntegrationTest_SeverityAlert' -ErrorAction 'Stop' $result | Should -BeTrue } It 'Should return false for non-existent alert' { - $result = $script:sqlServerObject | Test-SqlDscAgentAlert -Name 'NonExistentAlert' + $result = $script:sqlServerObject | Test-SqlDscIsAgentAlert -Name 'NonExistentAlert' -ErrorAction 'Stop' $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/Classes/SqlAgentAlert.Tests.ps1 b/tests/Unit/Classes/SqlAgentAlert.Tests.ps1 index 5fa1d76cbb..c3c483d171 100644 --- a/tests/Unit/Classes/SqlAgentAlert.Tests.ps1 +++ b/tests/Unit/Classes/SqlAgentAlert.Tests.ps1 @@ -1,4 +1,3 @@ -[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] param () diff --git a/tests/Unit/Private/Get-AgentAlertObject.Tests.ps1 b/tests/Unit/Private/Get-AgentAlertObject.Tests.ps1 index b790c6c157..480567b14c 100644 --- a/tests/Unit/Private/Get-AgentAlertObject.Tests.ps1 +++ b/tests/Unit/Private/Get-AgentAlertObject.Tests.ps1 @@ -1,4 +1,3 @@ -[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] param () diff --git a/tests/Unit/Public/Deny-SqlDscServerPermission.Tests.ps1 b/tests/Unit/Public/Deny-SqlDscServerPermission.Tests.ps1 index 785c317747..a210aa7e80 100644 --- a/tests/Unit/Public/Deny-SqlDscServerPermission.Tests.ps1 +++ b/tests/Unit/Public/Deny-SqlDscServerPermission.Tests.ps1 @@ -1,4 +1,3 @@ -[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] param () diff --git a/tests/Unit/Public/Get-SqlDscAgentAlert.Tests.ps1 b/tests/Unit/Public/Get-SqlDscAgentAlert.Tests.ps1 index 7d53327660..10a04400dd 100644 --- a/tests/Unit/Public/Get-SqlDscAgentAlert.Tests.ps1 +++ b/tests/Unit/Public/Get-SqlDscAgentAlert.Tests.ps1 @@ -1,4 +1,3 @@ -[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] param () diff --git a/tests/Unit/Public/New-SqlDscAgentAlert.Tests.ps1 b/tests/Unit/Public/New-SqlDscAgentAlert.Tests.ps1 index d47ef8051f..cfa35b8cbb 100644 --- a/tests/Unit/Public/New-SqlDscAgentAlert.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscAgentAlert.Tests.ps1 @@ -1,4 +1,3 @@ -[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] param () @@ -27,6 +26,8 @@ BeforeDiscovery { BeforeAll { $script:dscModuleName = 'SqlServerDsc' + $env:SqlServerDscCI = $true + Import-Module -Name $script:dscModuleName -Force -ErrorAction 'Stop' # Load SMO stub types @@ -42,6 +43,8 @@ AfterAll { # Unload the module being tested so that it doesn't impact any other tests. Get-Module -Name $script:dscModuleName -All | Remove-Module -Force + + Remove-Item -Path 'env:SqlServerDscCI' -ErrorAction 'SilentlyContinue' } Describe 'New-SqlDscAgentAlert' -Tag 'Public' { @@ -125,7 +128,7 @@ Describe 'New-SqlDscAgentAlert' -Tag 'Public' { $script:mockNewAlert.MessageID = 0 # Mock the private functions - Mock -CommandName 'Get-AgentAlertObject' + Mock -CommandName 'Test-SqlDscIsAgentAlert' -MockWith { return $false } Mock -CommandName 'Assert-BoundParameter' } @@ -134,14 +137,14 @@ Describe 'New-SqlDscAgentAlert' -Tag 'Public' { $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 + Should -Invoke -CommandName 'Test-SqlDscIsAgentAlert' -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 + Should -Invoke -CommandName 'Test-SqlDscIsAgentAlert' -Times 1 -Exactly } } @@ -170,8 +173,8 @@ Describe 'New-SqlDscAgentAlert' -Tag 'Public' { # 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 Test-SqlDscIsAgentAlert was called for each alert creation to check existence + Should -Invoke -CommandName 'Test-SqlDscIsAgentAlert' -Times 2 -Exactly # Verify that Assert-BoundParameter was called for each alert creation Should -Invoke -CommandName 'Assert-BoundParameter' -Times 2 -Exactly } @@ -183,8 +186,8 @@ Describe 'New-SqlDscAgentAlert' -Tag 'Public' { # 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 Test-SqlDscIsAgentAlert was called for each alert creation to check existence + Should -Invoke -CommandName 'Test-SqlDscIsAgentAlert' -Times 2 -Exactly # Verify that Assert-BoundParameter was called for each alert creation Should -Invoke -CommandName 'Assert-BoundParameter' -Times 2 -Exactly } @@ -196,7 +199,7 @@ Describe 'New-SqlDscAgentAlert' -Tag 'Public' { $result | Should -BeNullOrEmpty Should -Invoke -CommandName 'Assert-BoundParameter' -Times 1 -Exactly - Should -Invoke -CommandName 'Get-AgentAlertObject' -Times 1 -Exactly + Should -Invoke -CommandName 'Test-SqlDscIsAgentAlert' -Times 1 -Exactly } It 'Should create alert with message ID using pipeline input' { @@ -204,7 +207,7 @@ Describe 'New-SqlDscAgentAlert' -Tag 'Public' { $result | Should -BeNullOrEmpty Should -Invoke -CommandName 'Assert-BoundParameter' -Times 1 -Exactly - Should -Invoke -CommandName 'Get-AgentAlertObject' -Times 1 -Exactly + Should -Invoke -CommandName 'Test-SqlDscIsAgentAlert' -Times 1 -Exactly } It 'Should create alert with PassThru using pipeline input' { @@ -221,7 +224,7 @@ Describe 'New-SqlDscAgentAlert' -Tag 'Public' { $result | Should -BeNullOrEmpty Should -Invoke -CommandName 'Assert-BoundParameter' -Times 1 -Exactly - Should -Invoke -CommandName 'Get-AgentAlertObject' -Times 1 -Exactly + Should -Invoke -CommandName 'Test-SqlDscIsAgentAlert' -Times 1 -Exactly } It 'Should create alert with Force and PassThru using pipeline input' { @@ -252,7 +255,7 @@ Describe 'New-SqlDscAgentAlert' -Tag 'Public' { $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() $script:mockServerObject.JobServer = $script:mockJobServer - Mock -CommandName 'Get-AgentAlertObject' -MockWith { return $script:mockExistingAlert } + Mock -CommandName 'Test-SqlDscIsAgentAlert' -MockWith { return $true } Mock -CommandName 'Assert-BoundParameter' } @@ -278,7 +281,7 @@ Describe 'New-SqlDscAgentAlert' -Tag 'Public' { # Mock alert object $script:mockAlert = [Microsoft.SqlServer.Management.Smo.Agent.Alert]::CreateTypeInstance() - Mock -CommandName 'Get-AgentAlertObject' + Mock -CommandName 'Test-SqlDscIsAgentAlert' -MockWith { return $false } Mock -CommandName 'Assert-BoundParameter' } diff --git a/tests/Unit/Public/Remove-SqlDscAgentAlert.Tests.ps1 b/tests/Unit/Public/Remove-SqlDscAgentAlert.Tests.ps1 index 0cffde4a2d..cdc94f1ba0 100644 --- a/tests/Unit/Public/Remove-SqlDscAgentAlert.Tests.ps1 +++ b/tests/Unit/Public/Remove-SqlDscAgentAlert.Tests.ps1 @@ -1,4 +1,3 @@ -[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] param () diff --git a/tests/Unit/Public/Test-SqlDscAgentAlert.Tests.ps1 b/tests/Unit/Public/Test-SqlDscAgentAlertProperty.Tests.ps1 similarity index 58% rename from tests/Unit/Public/Test-SqlDscAgentAlert.Tests.ps1 rename to tests/Unit/Public/Test-SqlDscAgentAlertProperty.Tests.ps1 index cc1b642f62..191ba05c39 100644 --- a/tests/Unit/Public/Test-SqlDscAgentAlert.Tests.ps1 +++ b/tests/Unit/Public/Test-SqlDscAgentAlertProperty.Tests.ps1 @@ -1,4 +1,3 @@ -[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] param () @@ -44,15 +43,19 @@ AfterAll { Get-Module -Name $script:dscModuleName -All | Remove-Module -Force } -Describe 'Test-SqlDscAgentAlert' -Tag 'Public' { +Describe 'Test-SqlDscAgentAlertProperty' -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] ] []' + ExpectedParameterSetName = 'ByServerAndName' + ExpectedParameters = '-ServerObject -Name [-Severity ] [-MessageId ] []' + }, + @{ + ExpectedParameterSetName = 'ByAlertObject' + ExpectedParameters = '-AlertObject [-Severity ] [-MessageId ] []' } ) { - $result = (Get-Command -Name 'Test-SqlDscAgentAlert').ParameterSets | + $result = (Get-Command -Name 'Test-SqlDscAgentAlertProperty').ParameterSets | Where-Object -FilterScript { $_.Name -eq $ExpectedParameterSetName } | Select-Object -Property @( @{ Name = 'ParameterSetName'; Expression = { $_.Name } }, @@ -65,34 +68,44 @@ Describe 'Test-SqlDscAgentAlert' -Tag 'Public' { Context 'When command has correct parameter properties' { It 'Should have ServerObject as a mandatory parameter' { - $parameterInfo = (Get-Command -Name 'Test-SqlDscAgentAlert').Parameters['ServerObject'] + $parameterInfo = (Get-Command -Name 'Test-SqlDscAgentAlertProperty').Parameters['ServerObject'] $parameterInfo.Attributes.Mandatory | Should -BeTrue } It 'Should have ServerObject accept pipeline input' { - $parameterInfo = (Get-Command -Name 'Test-SqlDscAgentAlert').Parameters['ServerObject'] + $parameterInfo = (Get-Command -Name 'Test-SqlDscAgentAlertProperty').Parameters['ServerObject'] $parameterInfo.Attributes.ValueFromPipeline | Should -BeTrue } - It 'Should have Name as a mandatory parameter' { - $parameterInfo = (Get-Command -Name 'Test-SqlDscAgentAlert').Parameters['Name'] + It 'Should have Name as a mandatory parameter in ByServerAndName parameter set' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscAgentAlertProperty').Parameters['Name'] $parameterInfo.Attributes.Mandatory | Should -BeTrue } + It 'Should have AlertObject as a mandatory parameter in ByAlertObject parameter set' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscAgentAlertProperty').Parameters['AlertObject'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have AlertObject accept pipeline input' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscAgentAlertProperty').Parameters['AlertObject'] + $parameterInfo.Attributes.ValueFromPipeline | Should -BeTrue + } + It 'Should have Severity as an optional parameter' { - $parameterInfo = (Get-Command -Name 'Test-SqlDscAgentAlert').Parameters['Severity'] + $parameterInfo = (Get-Command -Name 'Test-SqlDscAgentAlertProperty').Parameters['Severity'] $parameterInfo.Attributes.Mandatory | Should -BeFalse } It 'Should have MessageId as an optional parameter' { - $parameterInfo = (Get-Command -Name 'Test-SqlDscAgentAlert').Parameters['MessageId'] + $parameterInfo = (Get-Command -Name 'Test-SqlDscAgentAlertProperty').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' + $command = Get-Command -Name 'Test-SqlDscAgentAlertProperty' $severityParam = $command.Parameters['Severity'] $validateRangeAttribute = $severityParam.Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateRangeAttribute] } @@ -101,7 +114,7 @@ Describe 'Test-SqlDscAgentAlert' -Tag 'Public' { } It 'Should accept valid MessageId values (0-2147483647)' { - $command = Get-Command -Name 'Test-SqlDscAgentAlert' + $command = Get-Command -Name 'Test-SqlDscAgentAlertProperty' $messageIdParam = $command.Parameters['MessageId'] $validateRangeAttribute = $messageIdParam.Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateRangeAttribute] } @@ -110,27 +123,20 @@ Describe 'Test-SqlDscAgentAlert' -Tag 'Public' { } } - Context 'When testing alert existence only' { + Context 'When no property parameters are specified' { 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 } + Mock -CommandName 'Assert-BoundParameter' -ModuleName $script:dscModuleName -MockWith { throw 'At least one parameter required' } + Mock -CommandName 'Get-AgentAlertObject' -ModuleName $script:dscModuleName } - It 'Should return true when alert exists and no properties are specified' { - $result = Test-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' + It 'Should throw an error when no property parameters are specified' { + { Test-SqlDscAgentAlertProperty -ServerObject $script:mockServerObject -Name 'TestAlert' } | + Should -Throw - $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 } } @@ -150,13 +156,15 @@ Describe 'Test-SqlDscAgentAlert' -Tag 'Public' { } It 'Should return true when alert exists and severity matches' { - $result = Test-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 + $result = Test-SqlDscAgentAlertProperty -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 $result | Should -BeTrue + Should -Invoke -CommandName 'Assert-BoundParameter' -ModuleName $script:dscModuleName -Times 2 -Exactly + Should -Invoke -CommandName 'Get-AgentAlertObject' -ModuleName $script:dscModuleName -Times 1 -Exactly } It 'Should return false when alert exists but severity does not match' { - $result = Test-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 14 + $result = Test-SqlDscAgentAlertProperty -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 14 $result | Should -BeFalse } @@ -178,13 +186,13 @@ Describe 'Test-SqlDscAgentAlert' -Tag 'Public' { } It 'Should return true when alert exists and message ID matches' { - $result = Test-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -MessageId 50001 + $result = Test-SqlDscAgentAlertProperty -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 = Test-SqlDscAgentAlertProperty -ServerObject $script:mockServerObject -Name 'TestAlert' -MessageId 50002 $result | Should -BeFalse } @@ -197,13 +205,32 @@ Describe 'Test-SqlDscAgentAlert' -Tag 'Public' { Mock -CommandName 'Assert-BoundParameter' -ModuleName $script:dscModuleName Mock -CommandName 'Get-AgentAlertObject' -ModuleName $script:dscModuleName + Mock -CommandName 'Write-Error' -ModuleName $script:dscModuleName + } + + It 'Should return false when alert does not exist (with Severity)' { + $result = Test-SqlDscAgentAlertProperty -ServerObject $script:mockServerObject -Name 'NonExistentAlert' -Severity 16 + + $result | Should -BeFalse + Should -Invoke -CommandName 'Get-AgentAlertObject' -ModuleName $script:dscModuleName -Times 1 -Exactly + Should -Invoke -CommandName 'Write-Error' -ModuleName $script:dscModuleName -Times 1 -Exactly } - It 'Should return false when alert does not exist' { - $result = Test-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'NonExistentAlert' + It 'Should return false when alert does not exist (with MessageId)' { + $result = Test-SqlDscAgentAlertProperty -ServerObject $script:mockServerObject -Name 'NonExistentAlert' -MessageId 50001 $result | Should -BeFalse Should -Invoke -CommandName 'Get-AgentAlertObject' -ModuleName $script:dscModuleName -Times 1 -Exactly + Should -Invoke -CommandName 'Write-Error' -ModuleName $script:dscModuleName -Times 1 -Exactly + } + + It 'Should call Write-Error with correct parameters when alert does not exist' { + $result = Test-SqlDscAgentAlertProperty -ServerObject $script:mockServerObject -Name 'NonExistentAlert' -Severity 16 + + $result | Should -BeFalse + Should -Invoke -CommandName 'Write-Error' -ModuleName $script:dscModuleName -ParameterFilter { + $Category -eq 'ObjectNotFound' -and $ErrorId -eq 'TSDAAP0001' -and $TargetObject -eq 'NonExistentAlert' + } -Times 1 -Exactly } } @@ -212,24 +239,64 @@ Describe 'Test-SqlDscAgentAlert' -Tag 'Public' { # Mock server object using SMO stub types $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() - Mock -CommandName 'Assert-BoundParameter' -ModuleName $script:dscModuleName + Mock -CommandName 'Assert-BoundParameter' -ModuleName $script:dscModuleName -ParameterFilter { $MutuallyExclusiveList1 } -MockWith { throw 'Mutually exclusive parameters' } + Mock -CommandName 'Assert-BoundParameter' -ModuleName $script:dscModuleName -ParameterFilter { $AtLeastOneList } 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 + It 'Should call parameter validation with both severity and message ID and throw' { + { Test-SqlDscAgentAlertProperty -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 -MessageId 50001 } | + Should -Throw - Should -Invoke -CommandName 'Assert-BoundParameter' -ModuleName $script:dscModuleName -ParameterFilter { - $BoundParameterList.ContainsKey('Severity') -and $BoundParameterList.ContainsKey('MessageId') - } -Times 1 -Exactly + Should -Invoke -CommandName 'Assert-BoundParameter' -ModuleName $script:dscModuleName -Times 2 -Exactly + } + } + + Context 'When 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 = 16 + $script:mockAlert.MessageId = 0 + + Mock -CommandName 'Assert-BoundParameter' -ModuleName $script:dscModuleName } - It 'Should call parameter validation with only severity' { - $null = Test-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' -Severity 16 + It 'Should return true when alert object has matching severity' { + $result = $script:mockAlert | Test-SqlDscAgentAlertProperty -Severity 16 - Should -Invoke -CommandName 'Assert-BoundParameter' -ModuleName $script:dscModuleName -ParameterFilter { - $BoundParameterList.ContainsKey('Severity') -and -not $BoundParameterList.ContainsKey('MessageId') - } -Times 1 -Exactly + $result | Should -BeTrue + Should -Invoke -CommandName 'Assert-BoundParameter' -ModuleName $script:dscModuleName -Times 2 -Exactly + } + + It 'Should return false when alert object has non-matching severity' { + $result = $script:mockAlert | Test-SqlDscAgentAlertProperty -Severity 14 + + $result | Should -BeFalse + } + } + + Context 'When using pipeline input' { + 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 + + # Mock the 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 work with pipeline input' { + $result = $script:mockServerObject | Test-SqlDscAgentAlertProperty -Name 'TestAlert' -Severity 16 + + $result | Should -BeTrue + Should -Invoke -CommandName 'Assert-BoundParameter' -ModuleName $script:dscModuleName -Times 2 -Exactly + Should -Invoke -CommandName 'Get-AgentAlertObject' -ModuleName $script:dscModuleName -Times 1 -Exactly } } } diff --git a/tests/Unit/Public/Test-SqlDscIsAgentAlert.Tests.ps1 b/tests/Unit/Public/Test-SqlDscIsAgentAlert.Tests.ps1 new file mode 100644 index 0000000000..275c255ad9 --- /dev/null +++ b/tests/Unit/Public/Test-SqlDscIsAgentAlert.Tests.ps1 @@ -0,0 +1,141 @@ +[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' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:dscModuleName -Force -ErrorAction 'Stop' + + # Load SMO stub types + Add-Type -Path "$PSScriptRoot/../Stubs/SMO.cs" + + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force + + Remove-Item -Path 'Env:SqlServerDscCI' -ErrorAction 'SilentlyContinue' +} + +Describe 'Test-SqlDscIsAgentAlert' -Tag 'Public' { + Context 'When command has correct parameter sets' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = '__AllParameterSets' + ExpectedParameters = '[-ServerObject] [-Name] []' + } + ) { + $result = (Get-Command -Name 'Test-SqlDscIsAgentAlert').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-SqlDscIsAgentAlert').Parameters['ServerObject'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have ServerObject accept pipeline input' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscIsAgentAlert').Parameters['ServerObject'] + $parameterInfo.Attributes.ValueFromPipeline | Should -BeTrue + } + + It 'Should have Name as a mandatory parameter' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscIsAgentAlert').Parameters['Name'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + } + + 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' + + # Mock server object using SMO stub types + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + + Mock -CommandName 'Get-AgentAlertObject' -ModuleName $script:dscModuleName -MockWith { return $script:mockAlert } + } + + It 'Should return true when alert exists' { + $result = Test-SqlDscIsAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' + + $result | Should -BeTrue + Should -Invoke -CommandName 'Get-AgentAlertObject' -ModuleName $script:dscModuleName -Times 1 -Exactly + } + } + + Context 'When alert does not exist' { + BeforeAll { + # Mock server object using SMO stub types + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + + Mock -CommandName 'Get-AgentAlertObject' -ModuleName $script:dscModuleName + } + + It 'Should return false when alert does not exist' { + $result = Test-SqlDscIsAgentAlert -ServerObject $script:mockServerObject -Name 'NonExistentAlert' + + $result | Should -BeFalse + Should -Invoke -CommandName 'Get-AgentAlertObject' -ModuleName $script:dscModuleName -Times 1 -Exactly + } + } + + Context 'When using the alias Test-SqlDscAgentAlert' { + BeforeAll { + # Mock the alert object using SMO stub types + $script:mockAlert = [Microsoft.SqlServer.Management.Smo.Agent.Alert]::CreateTypeInstance() + $script:mockAlert.Name = 'TestAlert' + + # Mock the server object using SMO stub types + $script:mockServerObject = [Microsoft.SqlServer.Management.Smo.Server]::CreateTypeInstance() + + Mock -CommandName 'Get-AgentAlertObject' -ModuleName $script:dscModuleName -MockWith { return $script:mockAlert } + } + + It 'Should work with alias Test-SqlDscAgentAlert' { + $result = Test-SqlDscAgentAlert -ServerObject $script:mockServerObject -Name 'TestAlert' + + $result | Should -BeTrue + Should -Invoke -CommandName 'Get-AgentAlertObject' -ModuleName $script:dscModuleName -Times 1 -Exactly + } + } +} diff --git a/tests/Unit/Stubs/SMO.cs b/tests/Unit/Stubs/SMO.cs index 6390ea18ae..bcfc5c12bd 100644 --- a/tests/Unit/Stubs/SMO.cs +++ b/tests/Unit/Stubs/SMO.cs @@ -1646,7 +1646,7 @@ namespace Microsoft.SqlServer.Management.Smo.Agent // New-SqlDscAgentAlert.Tests.ps1 // Set-SqlDscAgentAlert.Tests.ps1 // Remove-SqlDscAgentAlert.Tests.ps1 - // Test-SqlDscAgentAlert.Tests.ps1 + // Test-SqlDscIsAgentAlert.Tests.ps1 // SqlAgentAlert.Tests.ps1 public enum AlertType { @@ -1771,7 +1771,7 @@ public static AlertCollection CreateTypeInstance() // New-SqlDscAgentAlert.Tests.ps1 // Set-SqlDscAgentAlert.Tests.ps1 // Remove-SqlDscAgentAlert.Tests.ps1 - // Test-SqlDscAgentAlert.Tests.ps1 + // Test-SqlDscIsAgentAlert.Tests.ps1 public class Alert { // Constructor