From 81cd54f070edb6e2d0ba5ce0427b37212874b2dd Mon Sep 17 00:00:00 2001 From: Chrissy LeMaire Date: Fri, 25 Oct 2024 15:19:45 +0200 Subject: [PATCH] Pester migration - First Batch (#9529) --- .aider/aider.psm1 | 256 +++++++++++++++---- .aider/prompts/conventions.md | 271 +++++++++++---------- private/testing/Get-TestConfig.ps1 | 9 + tests/Add-DbaAgDatabase.Tests.ps1 | 62 ++++- tests/Add-DbaAgListener.Tests.ps1 | 59 ++++- tests/Add-DbaAgReplica.Tests.ps1 | 88 +++++-- tests/Add-DbaComputerCertificate.Tests.ps1 | 53 ++-- tests/appveyor.pester.ps1 | 1 + tests/dbatools.Tests.ps1 | 4 +- 9 files changed, 561 insertions(+), 242 deletions(-) diff --git a/.aider/aider.psm1 b/.aider/aider.psm1 index 76a4d749d7..3fb002de71 100644 --- a/.aider/aider.psm1 +++ b/.aider/aider.psm1 @@ -8,6 +8,10 @@ function Update-PesterTest { and converts them to use the newer Pester v5 parameter validation syntax. It skips files that have already been converted or exceed the specified size limit. + .PARAMETER InputObject + Array of objects that can be either file paths, FileInfo objects, or command objects (from Get-Command). + If not specified, will process commands from the dbatools module. + .PARAMETER First Specifies the maximum number of commands to process. Defaults to 1000. @@ -20,7 +24,6 @@ function Update-PesterTest { .PARAMETER CacheFilePath The path to the file containing cached conventions. - Defaults to "/workspace/.aider/prompts/conventions.md". .PARAMETER MaxFileSize The maximum size of test files to process, in bytes. Files larger than this will be skipped. @@ -37,64 +40,134 @@ function Update-PesterTest { .EXAMPLE PS C:\> Update-PesterTest -First 10 -Skip 5 Updates 10 test files starting from the 6th command, skipping the first 5. + + .EXAMPLE + PS C:\> "C:\tests\Get-DbaDatabase.Tests.ps1", "C:\tests\Get-DbaBackup.Tests.ps1" | Update-PesterTest + Updates the specified test files to v5 format. + + .EXAMPLE + PS C:\> Get-Command -Module dbatools -Name "*Database*" | Update-PesterTest + Updates test files for all commands in dbatools module that match "*Database*". + + .EXAMPLE + PS C:\> Get-ChildItem ./tests/Add-DbaRegServer.Tests.ps1 | Update-PesterTest -Verbose + Updates the specific test file from a Get-ChildItem result. #> [CmdletBinding(SupportsShouldProcess)] param ( + [Parameter(ValueFromPipeline)] + [PSObject[]]$InputObject, [int]$First = 1000, [int]$Skip = 0, [string[]]$PromptFilePath = "/workspace/.aider/prompts/template.md", - [string[]]$CacheFilePath = "/workspace/.aider/prompts/conventions.md", + [string[]]$CacheFilePath = @("/workspace/.aider/prompts/conventions.md","/workspace/private/testing/Get-TestConfig.ps1"), [int]$MaxFileSize = 8kb ) - # Full prompt path - if (-not (Get-Module dbatools.library -ListAvailable)) { - Write-Warning "dbatools.library not found, installing" - Install-Module dbatools.library -Scope CurrentUser -Force - } - Import-Module /workspace/dbatools.psm1 -Force - - $promptTemplate = Get-Content $PromptFilePath - $commands = Get-Command -Module dbatools -Type Function, Cmdlet | Select-Object -First $First -Skip $Skip - - $commonParameters = [System.Management.Automation.PSCmdlet]::CommonParameters - - foreach ($command in $commands) { - $cmdName = $command.Name - $filename = "/workspace/tests/$cmdName.Tests.ps1" - - if (-not (Test-Path $filename)) { - Write-Warning "No tests found for $cmdName" - Write-Warning "$filename not found" - continue + begin { + # Full prompt path + if (-not (Get-Module dbatools.library -ListAvailable)) { + Write-Warning "dbatools.library not found, installing" + Install-Module dbatools.library -Scope CurrentUser -Force } + Import-Module /workspace/dbatools.psm1 -Force - # if it matches Should -HaveParameter then skip becuase it's been done - if (Select-String -Path $filename -Pattern "Should -HaveParameter") { - Write-Warning "Skipping $cmdName because it's already been converted to Pester v5" - continue - } + $promptTemplate = Get-Content $PromptFilePath + $commonParameters = [System.Management.Automation.PSCmdlet]::CommonParameters + $commandsToProcess = @() + } - # if file is larger than 8kb, skip - if ((Get-Item $filename).Length -gt $MaxFileSize) { - Write-Warning "Skipping $cmdName because it's too large" - continue + process { + if ($InputObject) { + foreach ($item in $InputObject) { + Write-Verbose "Processing input object of type: $($item.GetType().FullName)" + + if ($item -is [System.Management.Automation.CommandInfo]) { + $commandsToProcess += $item + } elseif ($item -is [System.IO.FileInfo]) { + $path = $item.FullName + Write-Verbose "Processing FileInfo path: $path" + if (Test-Path $path) { + $cmdName = [System.IO.Path]::GetFileNameWithoutExtension($path) -replace '\.Tests$', '' + Write-Verbose "Extracted command name: $cmdName" + $cmd = Get-Command -Name $cmdName -ErrorAction SilentlyContinue + if ($cmd) { + $commandsToProcess += $cmd + } else { + Write-Warning "Could not find command for test file: $path" + } + } + } elseif ($item -is [string]) { + Write-Verbose "Processing string path: $item" + if (Test-Path $item) { + $cmdName = [System.IO.Path]::GetFileNameWithoutExtension($item) -replace '\.Tests$', '' + Write-Verbose "Extracted command name: $cmdName" + $cmd = Get-Command -Name $cmdName -ErrorAction SilentlyContinue + if ($cmd) { + $commandsToProcess += $cmd + } else { + Write-Warning "Could not find command for test file: $item" + } + } else { + Write-Warning "File not found: $item" + } + } else { + Write-Warning "Unsupported input type: $($item.GetType().FullName)" + } + } } + } - $parameters = $command.Parameters.Values | Where-Object Name -notin $commonParameters - $cmdPrompt = $promptTemplate -replace "--CMDNAME--", $cmdName - $cmdPrompt = $cmdPrompt -replace "--PARMZ--", ($parameters.Name -join "`n") - $cmdprompt = $cmdPrompt -join "`n" - - $aiderParams = @{ - Message = $cmdPrompt - File = $filename - YesAlways = $true - Stream = $false - CachePrompts = $true - ReadFile = $CacheFilePath + end { + if (-not $commandsToProcess) { + Write-Verbose "No input objects provided, getting commands from dbatools module" + $commandsToProcess = Get-Command -Module dbatools -Type Function, Cmdlet | Select-Object -First $First -Skip $Skip } - Invoke-Aider @aiderParams + foreach ($command in $commandsToProcess) { + $cmdName = $command.Name + $filename = "/workspace/tests/$cmdName.Tests.ps1" + + Write-Verbose "Processing command: $cmdName" + Write-Verbose "Test file path: $filename" + + if (-not (Test-Path $filename)) { + Write-Warning "No tests found for $cmdName" + Write-Warning "$filename not found" + continue + } + + <# Check if it's already been converted + if (Select-String -Path $filename -Pattern "Should -HaveParameter") { + Write-Warning "Skipping $cmdName because it's already been converted to Pester v5" + continue + } + #> + + # if file is larger than MaxFileSize, skip + if ((Get-Item $filename).Length -gt $MaxFileSize) { + Write-Warning "Skipping $cmdName because it's too large" + continue + } + + $parameters = $command.Parameters.Values | Where-Object Name -notin $commonParameters + $cmdPrompt = $promptTemplate -replace "--CMDNAME--", $cmdName + $cmdPrompt = $cmdPrompt -replace "--PARMZ--", ($parameters.Name -join "`n") + $cmdprompt = $cmdPrompt -join "`n" + + if ($PSCmdlet.ShouldProcess($filename, "Update Pester test to v5 format and/or style")) { + $aiderParams = @{ + Message = $cmdPrompt + File = $filename + YesAlways = $true + Stream = $false + CachePrompts = $true + ReadFile = $CacheFilePath + } + + Write-Verbose "Invoking Aider to update test file" + Invoke-Aider @aiderParams + } + } } } @@ -286,8 +359,11 @@ function Invoke-Aider { .PARAMETER NoPretty Disable pretty, colorized output. + .PARAMETER Stream + Enable streaming responses. Cannot be used with -NoStream. + .PARAMETER NoStream - Disable streaming responses. + Disable streaming responses. Cannot be used with -Stream. .PARAMETER YesAlways Automatically confirm all prompts. @@ -352,6 +428,9 @@ function Invoke-Aider { [string]$Model, [string]$EditorModel, [switch]$NoPretty, + [Parameter(ParameterSetName = 'Stream')] + [switch]$Stream, + [Parameter(ParameterSetName = 'NoStream')] [switch]$NoStream, [switch]$YesAlways, [switch]$CachePrompts, @@ -392,7 +471,9 @@ function Invoke-Aider { $params += "--no-pretty" } - if ($NoStream) { + if ($Stream) { + # Stream is enabled, so don't add --no-stream + } elseif ($NoStream) { $params += "--no-stream" } @@ -455,3 +536,86 @@ function Invoke-Aider { aider @params } + +function Repair-Error { + <# + .SYNOPSIS + Repairs errors in dbatools Pester test files. + + .DESCRIPTION + Processes and repairs errors found in dbatools Pester test files. This function reads error + information from a JSON file and attempts to fix the identified issues in the test files. + + .PARAMETER First + Specifies the maximum number of commands to process. Defaults to 1000. + + .PARAMETER Skip + Specifies the number of commands to skip before processing. Defaults to 0. + + .PARAMETER PromptFilePath + The path to the template file containing the prompt structure. + Defaults to "/workspace/.aider/prompts/fix-errors.md". + + .PARAMETER CacheFilePath + The path to the file containing cached conventions. + Defaults to "/workspace/.aider/prompts/conventions.md". + + .PARAMETER ErrorFilePath + The path to the JSON file containing error information. + Defaults to "/workspace/.aider/prompts/errors.json". + + .NOTES + Tags: Testing, Pester, ErrorHandling + Author: dbatools team + + .EXAMPLE + PS C:\> Repair-Error + Processes and attempts to fix all errors found in the error file using default parameters. + + .EXAMPLE + PS C:\> Repair-Error -ErrorFilePath "custom-errors.json" + Processes and repairs errors using a custom error file. + #> + [CmdletBinding()] + param ( + [int]$First = 1000, + [int]$Skip = 0, + [string[]]$PromptFilePath = "/workspace/.aider/prompts/fix-errors.md", + [string[]]$CacheFilePath = "/workspace/.aider/prompts/conventions.md", + [string]$ErrorFilePath = "/workspace/.aider/prompts/errors.json" + ) + + $promptTemplate = Get-Content $PromptFilePath + $testerrors = Get-Content $ErrorFilePath | ConvertFrom-Json + $commands = $testerrors | Select-Object -ExpandProperty Command -Unique | Sort-Object + + foreach ($command in $commands) { + $filename = "/workspace/tests/$command.Tests.ps1" + Write-Output "Processing $command" + + if (-not (Test-Path $filename)) { + Write-Warning "No tests found for $command" + Write-Warning "$filename not found" + continue + } + + $cmdPrompt = $promptTemplate -replace "--CMDNAME--", $command + + $testerr = $testerrors | Where-Object Command -eq $command + foreach ($err in $testerr) { + $cmdPrompt += "`n`n" + $cmdPrompt += "Error: $($err.ErrorMessage)`n" + $cmdPrompt += "Line: $($err.LineNumber)`n" + } + + $aiderParams = @{ + Message = $cmdPrompt + File = $filename + Stream = $false + CachePrompts = $true + ReadFile = $CacheFilePath + } + + Invoke-Aider @aiderParams + } +} \ No newline at end of file diff --git a/.aider/prompts/conventions.md b/.aider/prompts/conventions.md index 48a778af33..d779b7d7ad 100644 --- a/.aider/prompts/conventions.md +++ b/.aider/prompts/conventions.md @@ -1,172 +1,173 @@ -# Tasks +# Pester v5 Test Standards -1. **Restructure Test Code:** - - Move all test code into appropriate blocks: `It`, `BeforeAll`, `BeforeEach`, `AfterAll`, or `AfterEach`. - - Place any file setup code, including the import of `constants.ps1`, into the appropriate blocks at the beginning of each test file. - -2. **Update `Describe` and `Context` Blocks:** - - Ensure that no test code is directly inside `Describe` or `Context` blocks. - - Properly nest `Context` blocks within `Describe` blocks. +## Core Requirements +```powershell +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param($ModuleName = "dbatools") +$global:TestConfig = Get-TestConfig +``` +These three lines must start every test file. -3. **Refactor Skip Conditions:** - - Move skip logic outside of `BeforeAll` blocks. - - Use global read-only variables for skip conditions where appropriate. - - Ensure that `-Skip` parameters evaluate to `$true` or `$false`, not a string. +## Test Structure -4. **Update `TestCases`:** - - Define `TestCases` in a way that is compatible with Pester v5's discovery phase. +### Describe Blocks +- Name your Describe blocks with static command names from the primary command being tested +- Include appropriate tags (`-Tag "UnitTests"` or `-Tag "IntegrationTests"`) -5. **Update Assertion Syntax:** - - Replace assertions like `Should Be` with `Should -Be`. - - Update other assertion operators as needed (e.g., `Should Throw` to `Should -Throw`). +```powershell +Describe "Get-DbaDatabase" -Tag "UnitTests" { + # tests here +} +``` -6. **Modify `InModuleScope` Usage:** - - Remove `InModuleScope` from around `Describe` and `It` blocks. - - Use the `-ModuleName` parameter on `Mock` commands where possible. +### Context Blocks +- Describe specific scenarios or states +- Use clear, descriptive names that explain the test scenario +- Example: "When getting all databases", "When database is offline" -7. **Update `Invoke-Pester` Calls:** - - Modify `Invoke-Pester` parameters to align with Pester v5's simple or advanced interface. - - **Do not use the Legacy parameter set**, as it is deprecated and may not work correctly. +### Test Code Placement +- All setup code goes in `BeforeAll` or `BeforeEach` blocks +- All cleanup code goes in `AfterAll` or `AfterEach` blocks +- All test assertions go in `It` blocks +- No loose code in `Describe` or `Context` blocks -8. **Adjust Mocking Syntax:** - - Update any mock definitions to Pester v5 syntax. +```powershell +Describe "Get-DbaDatabase" -Tag "IntegrationTests" { + Context "When getting all databases" { + BeforeAll { + $results = Get-DbaDatabase + } -9. **Remove Parameter Testing Using `knownparameters`:** - - Identify any existing "Validate parameters" contexts that use `knownparameters` sections - - Remove the entire "Validate parameters" context and replace it with the Pester v5 approach using `Should -HaveParameter`, as shown in the example Pester v5 test script. + It "Returns results" { + $results | Should -Not -BeNullOrEmpty + } + } +} +``` -10. **Use TestCases Whenever Possible:** - - Look for opportunities to use TestCases in the test code. - - Convert existing tests to use TestCases when applicable. - - Define TestCases using the `ForEach` parameter in the `It` block, as shown in the example below. +## TestCases +Use the `-ForEach` parameter in `It` blocks for multiple test cases: -## Instructions +```powershell +It "Should calculate correctly" -ForEach @( + @{ Input = 1; Expected = 2 } + @{ Input = 2; Expected = 4 } + @{ Input = 3; Expected = 6 } +) { + $result = Get-Double -Number $Input + $result | Should -Be $Expected +} +``` -- **Importing Constants:** - - Include the contents of `constants.ps1` at the appropriate place in the test script. - - Since the variables defined in `constants.ps1` are needed during the discovery phase (e.g., for `-ForEach` loops), import `constants.ps1` within the `BeforeDiscovery` block. - - This ensures that all global variables are available during both the discovery and execution phases. +## Style Guidelines +- Use double quotes for strings (we're a SQL Server module) +- Array declarations should be on multiple lines: +```powershell +$array = @( + "Item1", + "Item2", + "Item3" +) +``` +- Skip conditions must evaluate to `$true` or `$false`, not strings +- Use `$global:` instead of `$script:` for test configuration variables when required for Pester v5 scoping +- Avoid script blocks in Where-Object when possible: -- **Variable Scoping:** - - Replace all `$script:` variable scopes with `$global:` to align with Pester v5 scoping rules. +```powershell +# Good - direct property comparison +$master = $databases | Where-Object Name -eq "master" +$systemDbs = $databases | Where-Object Name -in "master", "model", "msdb", "tempdb" -- **Comments and Debugging Notes:** - - Leave comments like `#$script:instance2 for appveyor` intact for debugging purposes. - - But change `$script:instance2` to `$global:instance2` for proper scoping. - - So it should look like this: `#$global:instance2 for appveyor`. +# Required - script block for Parameters.Keys +$actualParameters = $command.Parameters.Keys | Where-Object { $PSItem -notin "WhatIf", "Confirm" } +``` -- **Consistency with Example:** - - Follow the structure and conventions used in the example Pester v5 test script provided below. +## DO NOT +- DO NOT use `$MyInvocation.MyCommand.Name` to get command names +- DO NOT use the old `knownParameters` validation approach +- DO NOT include loose code outside of proper test blocks +- DO NOT remove comments like "#TestConfig.instance3" or "#$TestConfig.instance2 for appveyor" -- **SQL Server-Specific Scenarios:** - - If you encounter any SQL Server-specific testing scenarios that require special handling, implement the necessary adjustments while maintaining the integrity of the tests. +## Examples -## Example Pester v5 Test Script +### Good Parameter Test ```powershell -param($ModuleName = 'dbatools') - -Describe "Connect-DbaInstance" { - BeforeDiscovery { - . (Join-Path $PSScriptRoot 'constants.ps1') - } - - Context "Validate parameters" { - BeforeAll { - $command = Get-Command Connect-DbaInstance - } - $parms = @( - "SqlInstance", - "SqlCredential", - "Database" - ) - It "Has required parameter: <_>" -ForEach $parms { - $command | Should -HaveParameter $PSItem - } - } +Describe "Get-DbaDatabase" -Tag "UnitTests" { + Context "Parameter validation" { + BeforeAll { + $command = Get-Command Get-DbaDatabase + $expectedParameters = $TestConfig.CommonParameters + + $expectedParameters += @( + "SqlInstance", + "SqlCredential", + "Database" + ) + } + + It "Should have exactly the expected parameters" { + $actualParameters = $command.Parameters.Keys | Where-Object { $PSItem -notin "WhatIf", "Confirm" } + Compare-Object -ReferenceObject $expectedParameters -DifferenceObject $actualParameters | Should -BeNullOrEmpty + } + + It "Has parameter: <_>" -ForEach $expectedParameters { + $command | Should -HaveParameter $PSItem + } + } +} +``` - Context "Connects using newly created login" -ForEach $global:instances { +### Good Integration Test +```powershell +Describe "Get-DbaDatabase" -Tag "IntegrationTests" { + Context "When connecting to SQL Server" -ForEach $TestConfig.Instances { BeforeAll { - $loginName = "dbatoolsci_login_$(Get-Random)" - $securePassword = ConvertTo-SecureString -String "P@ssw0rd$(Get-Random)" -AsPlainText -Force - $credential = [PSCredential]::new($loginName, $securePassword) - New-DbaLogin -SqlInstance $PSItem -Login $loginName -Password $securePassword -Confirm:$false + $databases = Get-DbaDatabase -SqlInstance $PSItem } - AfterAll { - Remove-DbaLogin -SqlInstance $PSItem -Login $loginName -Confirm:$false + It "Returns database objects with required properties" { + $databases | Should -BeOfType Microsoft.SqlServer.Management.Smo.Database + $databases[0].Name | Should -Not -BeNullOrEmpty } - It "Connects successfully" { - $instance = Connect-DbaInstance -SqlInstance $PSItem -SqlCredential $credential - $instance.Name | Should -Be $PSItem.Split('\')[0] + It "Always includes system databases" { + $systemDbs = $databases | Where-Object Name -in "master", "model", "msdb", "tempdb" + $systemDbs.Count | Should -Be 4 } } } ``` -## Example Pester v5 Test Script with TestCases +### Parameter & Variable Naming Rules +1. Use direct parameters for 1-3 parameters +2. Use `$splat` for 4+ parameters (never plain `$splat`) +3. Use unique, descriptive variable names across scopes ```powershell -param($ModuleName = 'dbatools') - -Describe "Add-Numbers" { - It "Should calculate the correct result" -ForEach @( - @{ Input1 = 1; Input2 = 2; Expected = 3 } - @{ Input1 = 2; Input2 = 3; Expected = 5 } - @{ Input1 = 3; Input2 = 4; Expected = 7 } - ) { - $result = Add-Numbers -Number1 $Input1 -Number2 $Input2 - $result | Should -Be $Expected - } +# Direct parameters +$ag = New-DbaLogin -SqlInstance $instance -Login $loginName -Password $password + +# Splat with purpose suffix +$splatPrimary = @{ + Primary = $TestConfig.instance3 + Name = $primaryAgName # Descriptive variable name + ClusterType = "None" + FailoverMode = "Manual" + Certificate = "dbatoolsci_AGCert" + Confirm = $false } -``` +$primaryAg = New-DbaAvailabilityGroup @splatPrimary -## Additional Guidelines -* Start with `param($ModuleName = 'dbatools')` like in the example above. -* -Skip:(whatever) should return true or false, not a string - - -## Style and instructions - -Remember to REMOVE the knownparameters and validate parameters this way: - -Context "Validate parameters" { +# Unique names across scopes +Describe "New-DbaAvailabilityGroup" { BeforeAll { - $command = Get-Command Connect-DbaInstance + $primaryAgName = "primaryAG" } - $parms = @( - "SqlInstance", - "SqlCredential", - "Database" - ) - It "Has required parameter: <_>" -ForEach $parms { - $command | Should -HaveParameter $PSItem + Context "Adding replica" { + BeforeAll { + $replicaAgName = "replicaAG" + } } } - -## DO NOT list parameters like this - -```powershell -$parms = @('SqlInstance','SqlCredential','Database') -``` - -## DO list parameters like this - -```powershell -$parms = @( - 'SqlInstance', - 'SqlCredential', - 'Database' -) -``` - -## DO use the $parms variable when referencing parameters - -## more instructions - -DO NOT USE: -$CommandName = $MyInvocation.MyCommand.Name.Replace(".Tests.ps1", "") - -DO USE: -The static command name provided in the prompt \ No newline at end of file +``` \ No newline at end of file diff --git a/private/testing/Get-TestConfig.ps1 b/private/testing/Get-TestConfig.ps1 index cd7e8d38cd..02787491a2 100644 --- a/private/testing/Get-TestConfig.ps1 +++ b/private/testing/Get-TestConfig.ps1 @@ -58,5 +58,14 @@ function Get-TestConfig { } } + # derive the command name from the CALLING script's filename + $config['CommandName'] = ($MyInvocation.MyCommand.Name | Split-Path -Leaf).Replace(".Tests.ps1", "") + + if (-not $config['CommandName']) { + $config['CommandName'] = "Unknown" + } + + $config['CommonParameters'] = [System.Management.Automation.PSCmdlet]::CommonParameters + [pscustomobject]$config } \ No newline at end of file diff --git a/tests/Add-DbaAgDatabase.Tests.ps1 b/tests/Add-DbaAgDatabase.Tests.ps1 index 59717a15f6..92e8af11aa 100644 --- a/tests/Add-DbaAgDatabase.Tests.ps1 +++ b/tests/Add-DbaAgDatabase.Tests.ps1 @@ -1,19 +1,41 @@ -$CommandName = $MyInvocation.MyCommand.Name.Replace(".Tests.ps1", "") -Write-Host -Object "Running $PSCommandpath" -ForegroundColor Cyan +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param($ModuleName = "dbatools") $global:TestConfig = Get-TestConfig -Describe "$CommandName Unit Tests" -Tag 'UnitTests' { - Context "Validate parameters" { - [object[]]$params = (Get-Command $CommandName).Parameters.Keys | Where-Object {$_ -notin ('whatif', 'confirm')} - [object[]]$knownParameters = 'SqlInstance', 'SqlCredential', 'AvailabilityGroup', 'Database', 'Secondary', 'SecondarySqlCredential', 'InputObject', 'SeedingMode', 'SharedPath', 'UseLastBackup', 'AdvancedBackupParams', 'EnableException' - $knownParameters += [System.Management.Automation.PSCmdlet]::CommonParameters - It "Should only contain our specific parameters" { - (@(Compare-Object -ReferenceObject ($knownParameters | Where-Object {$_}) -DifferenceObject $params).Count ) | Should Be 0 +Describe "Add-DbaAgDatabase" -Tag "UnitTests" { + Context "Parameter validation" { + BeforeAll { + $command = Get-Command Add-DbaAgDatabase + $expectedParameters = $TestConfig.CommonParameters + + $expectedParameters += @( + "SqlInstance", + "SqlCredential", + "AvailabilityGroup", + "Database", + "Secondary", + "SecondarySqlCredential", + "InputObject", + "SeedingMode", + "SharedPath", + "UseLastBackup", + "AdvancedBackupParams", + "EnableException" + ) + } + + It "Should have exactly the expected parameters" { + $actualParameters = $command.Parameters.Keys | Where-Object { $PSItem -notin "WhatIf", "Confirm" } + Compare-Object -ReferenceObject $expectedParameters -DifferenceObject $actualParameters | Should -BeNullOrEmpty + } + + It "Has parameter: <_>" -ForEach $expectedParameters { + $command | Should -HaveParameter $PSItem } } } -Describe "$commandname Integration Tests" -Tag "IntegrationTests" { +Describe "Add-DbaAgDatabase" -Tag "IntegrationTests" { BeforeAll { $null = Get-DbaProcess -SqlInstance $TestConfig.instance3 -Program 'dbatools PowerShell module - dbatools.io' | Stop-DbaProcess -WarningAction SilentlyContinue $server = Connect-DbaInstance -SqlInstance $TestConfig.instance3 @@ -22,17 +44,31 @@ Describe "$commandname Integration Tests" -Tag "IntegrationTests" { $newdbname = "dbatoolsci_addag_agroupdb_2" $server.Query("create database $dbname") $backup = Get-DbaDatabase -SqlInstance $TestConfig.instance3 -Database $dbname | Backup-DbaDatabase - $ag = New-DbaAvailabilityGroup -Primary $TestConfig.instance3 -Name $agname -ClusterType None -FailoverMode Manual -Database $dbname -Confirm:$false -Certificate dbatoolsci_AGCert + $splatNewAg = @{ + Primary = $TestConfig.instance3 + Name = $agname + ClusterType = "None" + FailoverMode = "Manual" + Database = $dbname + Confirm = $false + Certificate = "dbatoolsci_AGCert" + } + $ag = New-DbaAvailabilityGroup @splatNewAg } + AfterAll { $null = Remove-DbaAvailabilityGroup -SqlInstance $server -AvailabilityGroup $agname -Confirm:$false $null = Remove-DbaDatabase -SqlInstance $server -Database $dbname, $newdbname -Confirm:$false } - Context "adds ag db" { - It "returns proper results" { + + Context "When adding AG database" { + BeforeAll { $server.Query("create database $newdbname") $backup = Get-DbaDatabase -SqlInstance $TestConfig.instance3 -Database $newdbname | Backup-DbaDatabase $results = Add-DbaAgDatabase -SqlInstance $TestConfig.instance3 -AvailabilityGroup $agname -Database $newdbname -Confirm:$false + } + + It "Returns proper results" { $results.AvailabilityGroup | Should -Be $agname $results.Name | Should -Be $newdbname $results.IsJoined | Should -Be $true diff --git a/tests/Add-DbaAgListener.Tests.ps1 b/tests/Add-DbaAgListener.Tests.ps1 index bb709e865b..8acdaf329a 100644 --- a/tests/Add-DbaAgListener.Tests.ps1 +++ b/tests/Add-DbaAgListener.Tests.ps1 @@ -1,32 +1,65 @@ -$CommandName = $MyInvocation.MyCommand.Name.Replace(".Tests.ps1", "") -Write-Host -Object "Running $PSCommandpath" -ForegroundColor Cyan +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param($ModuleName = "dbatools") $global:TestConfig = Get-TestConfig -Describe "$CommandName Unit Tests" -Tag 'UnitTests' { - Context "Validate parameters" { - [object[]]$params = (Get-Command $CommandName).Parameters.Keys | Where-Object { $_ -notin ('whatif', 'confirm') } - [object[]]$knownParameters = 'SqlInstance', 'SqlCredential', 'AvailabilityGroup', 'Name', 'IPAddress', 'SubnetIP', 'SubnetMask', 'Port', 'Dhcp', 'Passthru', 'InputObject', 'EnableException' - $knownParameters += [System.Management.Automation.PSCmdlet]::CommonParameters - It "Should only contain our specific parameters" { - (@(Compare-Object -ReferenceObject ($knownParameters | Where-Object { $_ }) -DifferenceObject $params).Count ) | Should Be 0 +Describe "Add-DbaAgListener" -Tag "UnitTests" { + Context "Parameter validation" { + BeforeAll { + $command = Get-Command Add-DbaAgListener + $expectedParameters = $TestConfig.CommonParameters + + $expectedParameters += @( + "SqlInstance", + "SqlCredential", + "AvailabilityGroup", + "Name", + "IPAddress", + "SubnetIP", + "SubnetMask", + "Port", + "Dhcp", + "Passthru", + "InputObject", + "EnableException" + ) + } + + It "Should have exactly the expected parameters" { + $actualParameters = $command.Parameters.Keys | Where-Object { $PSItem -notin "WhatIf", "Confirm" } + Compare-Object -ReferenceObject $expectedParameters -DifferenceObject $actualParameters | Should -BeNullOrEmpty + } + + It "Has parameter: <_>" -ForEach $expectedParameters { + $command | Should -HaveParameter $PSItem } } } -Describe "$commandname Integration Tests" -Tag "IntegrationTests" { +Describe "Add-DbaAgListener" -Tag "IntegrationTests" { BeforeAll { $agname = "dbatoolsci_ag_newlistener" $listenerName = 'dbatoolsci_listener' - $ag = New-DbaAvailabilityGroup -Primary $TestConfig.instance3 -Name $agname -ClusterType None -FailoverMode Manual -Confirm:$false -Certificate dbatoolsci_AGCert + $splatPrimary = @{ + Primary = $TestConfig.instance3 + Name = $agname + ClusterType = "None" + FailoverMode = "Manual" + Certificate = "dbatoolsci_AGCert" + Confirm = $false + } + $ag = New-DbaAvailabilityGroup @splatPrimary } + AfterEach { $null = Remove-DbaAgListener -SqlInstance $TestConfig.instance3 -Listener $listenerName -AvailabilityGroup $agname -Confirm:$false } + AfterAll { $null = Remove-DbaAvailabilityGroup -SqlInstance $TestConfig.instance3 -AvailabilityGroup $agname -Confirm:$false } - Context "creates a listener" { - It "returns results with proper data" { + + Context "When creating a listener" { + It "Returns results with proper data" { $results = $ag | Add-DbaAgListener -Name $listenerName -IPAddress 127.0.20.1 -Port 14330 -Confirm:$false $results.PortNumber | Should -Be 14330 } diff --git a/tests/Add-DbaAgReplica.Tests.ps1 b/tests/Add-DbaAgReplica.Tests.ps1 index 085d9329bc..b8e1494da2 100644 --- a/tests/Add-DbaAgReplica.Tests.ps1 +++ b/tests/Add-DbaAgReplica.Tests.ps1 @@ -1,40 +1,91 @@ -$CommandName = $MyInvocation.MyCommand.Name.Replace(".Tests.ps1", "") -Write-Host -Object "Running $PSCommandpath" -ForegroundColor Cyan +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param($ModuleName = "dbatools") $global:TestConfig = Get-TestConfig -Describe "$commandname Unit Tests" -Tag 'UnitTests' { - Context "Validate parameters" { - [object[]]$params = (Get-Command $CommandName).Parameters.Keys | Where-Object { $_ -notin ('whatif', 'confirm') } - [object[]]$knownParameters = 'SqlInstance', 'SqlCredential', 'Name', 'ClusterType', 'AvailabilityMode', 'FailoverMode', 'BackupPriority', 'ConnectionModeInPrimaryRole', 'ConnectionModeInSecondaryRole', 'SeedingMode', 'Endpoint', 'EndpointUrl', 'Passthru', 'ReadOnlyRoutingList', 'ReadonlyRoutingConnectionUrl', 'Certificate', 'ConfigureXESession', 'SessionTimeout', 'InputObject', 'EnableException' - $knownParameters += [System.Management.Automation.PSCmdlet]::CommonParameters - It "Should only contain our specific parameters" { - (@(Compare-Object -ReferenceObject ($knownParameters | Where-Object { $_ }) -DifferenceObject $params).Count ) | Should Be 0 +Describe "Add-DbaAgReplica" -Tag "UnitTests" { + Context "Parameter validation" { + BeforeAll { + $command = Get-Command Add-DbaAgReplica + $expectedParameters = $TestConfig.CommonParameters + + $expectedParameters += @( + "SqlInstance", + "SqlCredential", + "Name", + "ClusterType", + "AvailabilityMode", + "FailoverMode", + "BackupPriority", + "ConnectionModeInPrimaryRole", + "ConnectionModeInSecondaryRole", + "SeedingMode", + "Endpoint", + "EndpointUrl", + "Passthru", + "ReadOnlyRoutingList", + "ReadonlyRoutingConnectionUrl", + "Certificate", + "ConfigureXESession", + "SessionTimeout", + "InputObject", + "EnableException" + ) + } + + It "Should have exactly the expected parameters" { + $actualParameters = $command.Parameters.Keys | Where-Object { $PSItem -notin "WhatIf", "Confirm" } + Compare-Object -ReferenceObject $expectedParameters -DifferenceObject $actualParameters | Should -BeNullOrEmpty + } + + It "Has parameter: <_>" -ForEach $expectedParameters { + $command | Should -HaveParameter $PSItem } } } -Describe "$commandname Integration Tests" -Tag "IntegrationTests" { + +Describe "Add-DbaAgReplica" -Tag "IntegrationTests" { BeforeAll { $agname = "dbatoolsci_agroup" - $ag = New-DbaAvailabilityGroup -Primary $TestConfig.instance3 -Name $agname -ClusterType None -FailoverMode Manual -Certificate dbatoolsci_AGCert -Confirm:$false + $splatPrimary = @{ + Primary = $TestConfig.instance3 + Name = $agname + ClusterType = "None" + FailoverMode = "Manual" + Certificate = "dbatoolsci_AGCert" + Confirm = $false + } + $ag = New-DbaAvailabilityGroup @splatPrimary $replicaName = $ag.PrimaryReplica } + AfterAll { $null = Remove-DbaAvailabilityGroup -SqlInstance $TestConfig.instance3 -AvailabilityGroup $agname -Confirm:$false } - Context "gets ag replicas" { - # the only way to test, really, is to call New-DbaAvailabilityGroup which calls Add-DbaAgReplica - $agname = "dbatoolsci_add_replicagroup" - $ag = New-DbaAvailabilityGroup -Primary $TestConfig.instance3 -Name $agname -ClusterType None -FailoverMode Manual -Certificate dbatoolsci_AGCert -Confirm:$false - $replicaName = $ag.PrimaryReplica - It "returns results with proper data" { + Context "When adding AG replicas" { + BeforeAll { + $agname = "dbatoolsci_add_replicagroup" + $splatNewAg = @{ + Primary = $TestConfig.instance3 + Name = $agname + ClusterType = "None" + FailoverMode = "Manual" + Certificate = "dbatoolsci_AGCert" + Confirm = $false + } + $ag = New-DbaAvailabilityGroup @splatNewAg + $replicaName = $ag.PrimaryReplica + } + + It "Returns results with proper data" { $results = Get-DbaAgReplica -SqlInstance $TestConfig.instance3 $results.AvailabilityGroup | Should -Contain $agname $results.Role | Should -Contain 'Primary' $results.AvailabilityMode | Should -Contain 'SynchronousCommit' $results.FailoverMode | Should -Contain 'Manual' } - It "returns just one result" { + + It "Returns just one result" { $results = Get-DbaAgReplica -SqlInstance $TestConfig.instance3 -Replica $replicaName -AvailabilityGroup $agname $results.AvailabilityGroup | Should -Be $agname $results.Role | Should -Be 'Primary' @@ -43,4 +94,3 @@ Describe "$commandname Integration Tests" -Tag "IntegrationTests" { } } } #$TestConfig.instance2 for appveyor - diff --git a/tests/Add-DbaComputerCertificate.Tests.ps1 b/tests/Add-DbaComputerCertificate.Tests.ps1 index df9646380c..f0875493f6 100644 --- a/tests/Add-DbaComputerCertificate.Tests.ps1 +++ b/tests/Add-DbaComputerCertificate.Tests.ps1 @@ -1,30 +1,53 @@ -$CommandName = $MyInvocation.MyCommand.Name.Replace(".Tests.ps1", "") -Write-Host -Object "Running $PSCommandPath" -ForegroundColor Cyan +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} +param($ModuleName = "dbatools") $global:TestConfig = Get-TestConfig -Describe "$CommandName Unit Tests" -Tag 'UnitTests' { - Context "Validate parameters" { - [object[]]$params = (Get-Command $CommandName).Parameters.Keys | Where-Object {$_ -notin ('whatif', 'confirm')} - [object[]]$knownParameters = 'ComputerName', 'Credential', 'SecurePassword', 'Certificate', 'Path', 'Store', 'Folder', 'Flag', 'EnableException' - $knownParameters += [System.Management.Automation.PSCmdlet]::CommonParameters - It "Should only contain our specific parameters" { - (@(Compare-Object -ReferenceObject ($knownParameters | Where-Object {$_}) -DifferenceObject $params).Count ) | Should Be 0 +Describe "Add-DbaComputerCertificate" -Tag "UnitTests" { + Context "Parameter validation" { + BeforeAll { + $command = Get-Command Add-DbaComputerCertificate + $expectedParameters = $TestConfig.CommonParameters + + $expectedParameters += @( + "ComputerName", + "Credential", + "SecurePassword", + "Certificate", + "Path", + "Store", + "Folder", + "Flag", + "EnableException" + ) + } + + It "Should have exactly the expected parameters" { + $actualParameters = $command.Parameters.Keys | Where-Object { $PSItem -notin "WhatIf", "Confirm" } + Compare-Object -ReferenceObject $expectedParameters -DifferenceObject $actualParameters | Should -BeNullOrEmpty + } + + It "Has parameter: <_>" -ForEach $expectedParameters { + $command | Should -HaveParameter $PSItem } } } -Describe "$commandname Integration Tests" -Tags "IntegrationTests" { +Describe "Add-DbaComputerCertificate" -Tag "IntegrationTests" { Context "Certificate is added properly" { - $results = Add-DbaComputerCertificate -Path "$($TestConfig.appveyorlabrepo)\certificates\localhost.crt" -Confirm:$false + BeforeAll { + $results = Add-DbaComputerCertificate -Path "$($TestConfig.appveyorlabrepo)\certificates\localhost.crt" -Confirm:$false + } + + AfterAll { + Remove-DbaComputerCertificate -Thumbprint 29C469578D6C6211076A09CEE5C5797EEA0C2713 -Confirm:$false + } It "Should show the proper thumbprint has been added" { - $results.Thumbprint | Should Be "29C469578D6C6211076A09CEE5C5797EEA0C2713" + $results.Thumbprint | Should -Be "29C469578D6C6211076A09CEE5C5797EEA0C2713" } It "Should be in LocalMachine\My Cert Store" { - $results.PSParentPath | Should Be "Microsoft.PowerShell.Security\Certificate::LocalMachine\My" + $results.PSParentPath | Should -Be "Microsoft.PowerShell.Security\Certificate::LocalMachine\My" } - - Remove-DbaComputerCertificate -Thumbprint 29C469578D6C6211076A09CEE5C5797EEA0C2713 -Confirm:$false } } diff --git a/tests/appveyor.pester.ps1 b/tests/appveyor.pester.ps1 index a505853f14..6fdfe48bd2 100644 --- a/tests/appveyor.pester.ps1 +++ b/tests/appveyor.pester.ps1 @@ -271,6 +271,7 @@ if (-not $Finalize) { } else { $appvTestName = "$($f.Name), attempt #$trialNo" } + Write-Host -Object "Running $($f.FullName)" -ForegroundColor Cyan Add-AppveyorTest -Name $appvTestName -Framework NUnit -FileName $f.FullName -Outcome Running $PesterRun = Invoke-Pester -Configuration $pester5config $PesterRun | Export-Clixml -Path "$ModuleBase\Pester5Results$PSVersion$Counter.xml" diff --git a/tests/dbatools.Tests.ps1 b/tests/dbatools.Tests.ps1 index d1b40df797..c6475e007f 100644 --- a/tests/dbatools.Tests.ps1 +++ b/tests/dbatools.Tests.ps1 @@ -157,7 +157,9 @@ Describe "$ModuleName Tests missing" -Tag 'Tests' { } If (Test-Path "tests\$($f.basename).tests.ps1") { It "$($f.basename) has validate parameters unit test" { - "tests\$($f.basename).tests.ps1" | should FileContentMatch 'Context "Validate parameters"' + $testFile = Get-Content "tests\$($f.basename).Tests.ps1" -Raw + $hasValidation = $testFile -match 'Context "Validate parameters"' -or $testFile -match 'Context "Parameter validation"' + $hasValidation | Should -Be $true -Because "Test file must have parameter validation" } } }