diff --git a/.vscode/settings.json b/.vscode/settings.json index c524097..686ef49 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -32,7 +32,10 @@ "keepachangelog", "notin", "pscmdlet", - "steppable" + "steppable", + "hashtables", + "HQRM", + "LCID" ], "[markdown]": { "files.trimTrailingWhitespace": true, diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 2991140..011f8d0 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -41,7 +41,7 @@ "label": "build", "type": "shell", "command": "&${cwd}/build.ps1", - "args": [], + "args": ["-AutoRestore", "-UseModuleFast", "-Tasks", "build"], "presentation": { "echo": true, "reveal": "always", @@ -84,7 +84,64 @@ "label": "test", "type": "shell", "command": "&${cwd}/build.ps1", - "args": ["-AutoRestore","-Tasks","test"], + "args": [ + "-AutoRestore", + "-UseModuleFast", + "-Tasks", + "test", + "-PesterPath", + "./tests/Unit/Classes,./tests/Unit/Private,./tests/Unit/Public,./tests/QA", + "-CodeCoverageThreshold", + "0" + ], + "presentation": { + "echo": true, + "reveal": "always", + "focus": true, + "panel": "dedicated", + "showReuseMessage": true, + "clear": false + }, + "problemMatcher": [ + { + "owner": "powershell", + "fileLocation": [ + "absolute" + ], + "severity": "error", + "pattern": [ + { + "regexp": "^\\s*(\\[-\\]\\s*.*?)(\\d+)ms\\s*$", + "message": 1 + }, + { + "regexp": "(.*)", + "code": 1 + }, + { + "regexp": "" + }, + { + "regexp": "^.*,\\s*(.*):\\s*line\\s*(\\d+).*", + "file": 1, + "line": 2 + } + ] + } + ] + }, + { + "label": "hqrmtest", + "type": "shell", + "command": "&${cwd}/build.ps1", + "args": [ + "-AutoRestore", + "-UseModuleFast", + "-Tasks", + "hqrmtest", + "-CodeCoverageThreshold", + "0" + ], "presentation": { "echo": true, "reveal": "always", diff --git a/CHANGELOG.md b/CHANGELOG.md index 33dae38..8118a47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- `Assert-BoundParameter` + - Added parameter set `AtLeastOne` with parameter `AtLeastOneList` to + validate that at least one parameter from a specified list is bound + [#161](https://github.com/dsccommunity/DscResource.Common/issues/161). + - Added parameter `IfEqualParameterList` to conditionally perform assertions + only when specified parameters have exact values [#160](https://github.com/dsccommunity/DscResource.Common/issues/160). - `Format-Path` - Added parameter `ExpandEnvironmentVariable` fixes [#147](https://github.com/dsccommunity/DscResource.Common/issues/147). - Added support to `Compare-DscParameterState` for comparing large hashtables @@ -322,8 +328,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - DscResource.Common - - updating the Get-LocalizedData to bypass Import-LocalizedData when in Globalization-Invariant mode. - The command throws when running on an Invariant culture on Linux in the latest PS versions. + - updating the Get-LocalizedData to bypass Import-LocalizedData when in + Globalization-Invariant mode. + The command throws when running on an Invariant culture on Linux in + the latest PS versions. ## [0.11.0] - 2022-08-01 @@ -337,9 +345,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Correction to `Compare-DscParameterState` returning false positive when parameter - with an empty hashtable or CimInstance property is passed in `DesriedValues` - fixes - [issue #65](https://github.com/dsccommunity/DscResource.Common/issues/65). -- Correction somes problems in `Compare-DscParameterState` - see [issue #70](https://github.com/dsccommunity/DscResource.Common/issues/70) : + with an empty hashtable or CimInstance property is passed in `DesiredValues`, + fixes [issue #65](https://github.com/dsccommunity/DscResource.Common/issues/65). +- Correction some problems in `Compare-DscParameterState`, see + [issue #70](https://github.com/dsccommunity/DscResource.Common/issues/70). - When you use `-ReverseCheck`, this value is used in recursive call of `Test-DscParameterState` and `Compare-DscParameterState`, and that called another time the function. @@ -398,15 +407,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Get-TargetResource function or Get() method in Class based Resources. It is based on the code of Test-DscParameterState function to get compliance between current and desired state of resources. - The OutPut of Compare-DscParameterState is a collection psobject. - The properties of psobject are Property,InDesiredState,ExpectedType,ActualType, - ExpectedValue and ActualValue. The IncludeInDesiredState parameter must be use to - add ExeptedValue and ActualValue. -- Added pester test to test the pscredential object with `Compare-DscParameterState`. + The OutPut of Compare-DscParameterState is a collection of PSObject. + The properties of each PSObject are Property, InDesiredState, ExpectedType, ActualType, + ExpectedValue, and ActualValue. The IncludeInDesiredState parameter must + be used to add ExpectedValue and ActualValue. +- Added pester test to test the PSCredential object with `Compare-DscParameterState`. ### Changed -- Cmdlet Test-DscResourceState is now calling Compare-DscParameterState. Possible breaking change. +- Cmdlet Test-DscResourceState is now calling Compare-DscParameterState. + Possible breaking change. - IncludeInDesiredState and IncludeValue parameters of Compare-DscParameterState are removed in splatting when Test-DscCompareState is called. @@ -419,15 +429,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Fixed - Correction to `Test-DscParameterState` returning false positive when parameter - with an empty array is passed in `DesriedValues` or `CurrentValues` - fixes + with an empty array is passed in `DesiredValues` or `CurrentValues` - fixes [issue #53](https://github.com/dsccommunity/DscResource.Common/issues/53). ## [0.9.2] - 2020-07-22 ### Added -- `Test-DscParameterState` can now handle scriptblocks. The parameter 'ValuesToCheck' was renamed to 'Properties' but an alias - was added so it is not a braking change. The parameter 'ExcludeProperties' was added. +- `Test-DscParameterState` can now handle script blocks. The parameter + 'ValuesToCheck' was renamed to 'Properties' but an alias + was added so it is not a braking change. The parameter 'ExcludeProperties' + was added. - Added a new test for the alias 'ValuesToCheck' pointing to 'Properties'. - Added cmdlet `Compare-ResourcePropertyState` that also introduces a new design pattern to evaluate properties in both _Test_ and _Set_ - fixes @@ -458,8 +470,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.8.0] - 2020-05-11 -- Added a default value of `en-US` to the `DefaultUICulture` parameter of the `Get-LocalizedData` function - [Issue #33](https://github.com/dsccommunity/DscResource.Common/issues/33). +- Added a default value of `en-US` to the `DefaultUICulture` parameter of + the `Get-LocalizedData` function [Issue #33](https://github.com/dsccommunity/DscResource.Common/issues/33). - Fixing a problem with the latest ModuleBuild 1.7.0 that breaks the CI pipeline. ## [0.7.1] - 2020-05-02 @@ -524,7 +536,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- Fixed the New-*Exception function unit tests to work correctly on PowerShell version 5, 6 and 7. +- Fixed the New-*Exception function unit tests to work correctly on PowerShell + version 5, 6 and 7. ## [0.4.0] - 2020-03-09 diff --git a/source/Public/Assert-BoundParameter.ps1 b/source/Public/Assert-BoundParameter.ps1 index 5a7c4c5..280323d 100644 --- a/source/Public/Assert-BoundParameter.ps1 +++ b/source/Public/Assert-BoundParameter.ps1 @@ -5,7 +5,7 @@ .DESCRIPTION This command asserts passed parameters. It takes a hashtable, normally - `$PSBoundParameters`. There are two parameter sets for this command. + `$PSBoundParameters`. There are three parameter sets for this command. >There is no built in logic to validate against parameters sets for DSC >so this can be used instead to validate the parameters that were set in @@ -23,6 +23,11 @@ if not. Optionally it can be specified that parameters are only required if a specific parameter has been passed. + **AtLeastOne** + + Assert that at least one parameter from the specified list has been bound, + and throws an exception if none are present. + .PARAMETER BoundParameterList The parameters that should be evaluated against the mutually exclusive lists MutuallyExclusiveList1 and MutuallyExclusiveList2. This parameter is @@ -47,6 +52,14 @@ If neither of the parameter names has been specified the evaluation of required parameters are not made. + .PARAMETER IfEqualParameterList + A hashtable of parameter names and their expected values. The assertion will + only be performed if all the specified parameters in the BoundParameterList + have the exact values specified in this hashtable. + + .PARAMETER AtLeastOneList + An array of parameter names where at least one must be bound. + .EXAMPLE $assertBoundParameterParameters = @{ BoundParameterList = $PSBoundParameters @@ -74,9 +87,50 @@ of the required parameters are not. .EXAMPLE - Assert-BoundParameter -BoundParameterList $PSBoundParameters -RequiredParameter @('PBStartPortRange', 'PBEndPortRange') -RequiredBehavior 'AtLeastOnce' + Assert-BoundParameter -BoundParameterList $PSBoundParameters -RequiredParameter @('PBStartPortRange', 'PBEndPortRange') -RequiredBehavior 'Any' + + Throws an exception if any of the two parameters are not present. + + .EXAMPLE + Assert-BoundParameter -BoundParameterList $PSBoundParameters -RequiredParameter @('PBStartPortRange', 'PBEndPortRange') -RequiredBehavior 'All' + + Throws an exception if all of the specified parameters are not present. + + .EXAMPLE + Assert-BoundParameter -BoundParameterList $PSBoundParameters -AtLeastOneList @('Severity', 'MessageId') + + Throws an exception if none of the parameters 'Severity' or 'MessageId' are specified. + + .EXAMPLE + $assertBoundParameterParameters = @{ + BoundParameterList = $PSBoundParameters + MutuallyExclusiveList1 = @( + 'Severity' + ) + MutuallyExclusiveList2 = @( + 'MessageId' + ) + IfEqualParameterList = @{ + Ensure = 'Present' + } + } + Assert-BoundParameter @assertBoundParameterParameters + + This example throws an exception if `$PSBoundParameters` contains both + the parameters `Severity` and `MessageId` and the parameter `Ensure` has + the value `Present`. + + .EXAMPLE + Assert-BoundParameter -BoundParameterList $PSBoundParameters -RequiredParameter @('Property2', 'Property3') -IfEqualParameterList @{ Property1 = 'SpecificValue' } + + Throws an exception if the parameter 'Property1' has the value 'SpecificValue' + and either of the required parameters are not specified. - Throws an exception if at least one of the two parameters are not specified. + .EXAMPLE + Assert-BoundParameter -BoundParameterList $PSBoundParameters -AtLeastOneList @('Severity', 'MessageId') -IfEqualParameterList @{ Ensure = 'Present' } + + Throws an exception if the parameter 'Ensure' has the value 'Present' and + none of the parameters 'Severity' or 'MessageId' are specified. #> function Assert-BoundParameter { @@ -106,9 +160,31 @@ function Assert-BoundParameter [Parameter(ParameterSetName = 'RequiredParameter')] [System.String[]] - $IfParameterPresent + $IfParameterPresent, + + [Parameter(ParameterSetName = 'MutuallyExclusiveParameters')] + [Parameter(ParameterSetName = 'RequiredParameter')] + [Parameter(ParameterSetName = 'AtLeastOne')] + [System.Collections.Hashtable] + $IfEqualParameterList, + + [Parameter(ParameterSetName = 'AtLeastOne', Mandatory = $true)] + [System.String[]] + $AtLeastOneList ) + # Early return if IfEqualParameterList conditions are not met + if ($PSBoundParameters.ContainsKey('IfEqualParameterList')) + { + foreach ($parameterName in $IfEqualParameterList.Keys) + { + if (-not $BoundParameterList.ContainsKey($parameterName) -or $BoundParameterList[$parameterName] -ne $IfEqualParameterList[$parameterName]) + { + return + } + } + } + switch ($PSCmdlet.ParameterSetName) { 'MutuallyExclusiveParameters' @@ -118,11 +194,12 @@ function Assert-BoundParameter if ($itemFoundFromList1.Count -gt 0 -and $itemFoundFromList2.Count -gt 0) { - $errorMessage = ` - $script:localizedData.ParameterUsageWrong ` - -f ($MutuallyExclusiveList1 -join "','"), ($MutuallyExclusiveList2 -join "','") + $errorMessage = $script:localizedData.ParameterUsageWrong -f ( + ($MutuallyExclusiveList1 -join "','"), + ($MutuallyExclusiveList2 -join "','") + ) - New-InvalidArgumentException -ArgumentName 'Parameters' -Message $errorMessage + New-ArgumentException -ArgumentName 'Parameters' -Message $errorMessage } break @@ -130,6 +207,11 @@ function Assert-BoundParameter 'RequiredParameter' { + if ($PSBoundParameters.ContainsKey('IfEqualParameterList')) + { + $PSBoundParameters.Remove('IfEqualParameterList') + } + if (-not $PSBoundParameters.ContainsKey('RequiredBehavior')) { $PSBoundParameters.RequiredBehavior = $RequiredBehavior @@ -138,5 +220,19 @@ function Assert-BoundParameter Assert-RequiredCommandParameter @PSBoundParameters break } + + 'AtLeastOne' + { + $boundParametersFromList = $BoundParameterList.Keys.Where({ $_ -in $AtLeastOneList }) + + if ($boundParametersFromList.Count -eq 0) + { + $errorMessage = $script:localizedData.Assert_BoundParameter_AtLeastOneParameterMustBeSet -f ($AtLeastOneList -join "','") + + New-ArgumentException -ArgumentName 'Parameters' -Message $errorMessage + } + + break + } } } diff --git a/source/Public/Assert-IPAddress.ps1 b/source/Public/Assert-IPAddress.ps1 index 32b8f4a..d0d4a77 100644 --- a/source/Public/Assert-IPAddress.ps1 +++ b/source/Public/Assert-IPAddress.ps1 @@ -53,7 +53,7 @@ function Assert-IPAddress if (-not ([System.Net.IPAddress]::TryParse($Address, [ref] $ipAddress))) { - New-InvalidArgumentException ` + New-ArgumentException ` -Message ($script:localizedData.AddressFormatError -f $Address) ` -ArgumentName 'Address' } @@ -66,7 +66,7 @@ function Assert-IPAddress { if ($ipAddress.AddressFamily -ne [System.Net.Sockets.AddressFamily]::InterNetwork.ToString()) { - New-InvalidArgumentException ` + New-ArgumentException ` -Message ($script:localizedData.AddressIPv6MismatchError -f $Address, $AddressFamily) ` -ArgumentName 'AddressFamily' } @@ -76,7 +76,7 @@ function Assert-IPAddress { if ($ipAddress.AddressFamily -ne [System.Net.Sockets.AddressFamily]::InterNetworkV6.ToString()) { - New-InvalidArgumentException ` + New-ArgumentException ` -Message ($script:localizedData.AddressIPv4MismatchError -f $Address, $AddressFamily) ` -ArgumentName 'AddressFamily' } diff --git a/source/Public/Compare-DscParameterState.ps1 b/source/Public/Compare-DscParameterState.ps1 index d933f53..9dd41b6 100644 --- a/source/Public/Compare-DscParameterState.ps1 +++ b/source/Public/Compare-DscParameterState.ps1 @@ -206,14 +206,14 @@ function Compare-DscParameterState if ($DesiredValues.GetType().FullName -notin $types) { - New-InvalidArgumentException ` + New-ArgumentException ` -Message ($script:localizedData.InvalidDesiredValuesError -f $DesiredValues.GetType().FullName) ` -ArgumentName 'DesiredValues' } if ($CurrentValues.GetType().FullName -notin $types) { - New-InvalidArgumentException ` + New-ArgumentException ` -Message ($script:localizedData.InvalidCurrentValuesError -f $CurrentValues.GetType().FullName) ` -ArgumentName 'CurrentValues' } @@ -221,7 +221,7 @@ function Compare-DscParameterState #region check if CimInstance and not have properties in parameters invoke exception if ($DesiredValues -is [Microsoft.Management.Infrastructure.CimInstance] -and -not $Properties) { - New-InvalidArgumentException ` + New-ArgumentException ` -Message $script:localizedData.InvalidPropertiesError ` -ArgumentName Properties } diff --git a/source/Public/Find-Certificate.ps1 b/source/Public/Find-Certificate.ps1 index 136207d..48b0c7c 100644 --- a/source/Public/Find-Certificate.ps1 +++ b/source/Public/Find-Certificate.ps1 @@ -125,7 +125,7 @@ function Find-Certificate if (-not (Test-Path -Path $certPath)) { # The Certificte Path is not valid - New-InvalidArgumentException ` + New-ArgumentException ` -Message ($script:localizedData.CertificatePathError -f $certPath) ` -ArgumentName 'Store' } # if diff --git a/source/en-US/DscResource.Common.strings.psd1 b/source/en-US/DscResource.Common.strings.psd1 index 73c6767..1b1b1af 100644 --- a/source/en-US/DscResource.Common.strings.psd1 +++ b/source/en-US/DscResource.Common.strings.psd1 @@ -40,6 +40,9 @@ ConvertFrom-StringData @' ## Assert-ElevatedUser ElevatedUser_UserNotElevated = This command must run in an elevated PowerShell session. (DRC0043) + ## Assert-BoundParameter + Assert_BoundParameter_AtLeastOneParameterMustBeSet = At least one of the parameters '{0}' must be specified. (DRC0052) + ## Assert-RequiredCommandParameter RequiredCommandParameter_SpecificParametersMustAllBeSet = The parameters '{0}' must all be specified. (DRC0044) RequiredCommandParameter_SpecificParametersMustAllBeSetWhenParameterExist = The parameters '{0}' must all be specified if either parameter '{1}' is specified. (DRC0045) diff --git a/tests/Integration/Commands/Assert-BoundParameter.Integration.Tests.ps1 b/tests/Integration/Commands/Assert-BoundParameter.Integration.Tests.ps1 new file mode 100644 index 0000000..450c913 --- /dev/null +++ b/tests/Integration/Commands/Assert-BoundParameter.Integration.Tests.ps1 @@ -0,0 +1,972 @@ +[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 = 'DscResource.Common' + + Import-Module -Name $script:dscModuleName -Force -ErrorAction 'Stop' +} + +Describe 'Assert-BoundParameter Integration Tests' -Tag 'AssertBoundParameterIntegration' { + Context 'When using MutuallyExclusiveParameters parameter set' { + Context 'When no conflicting parameters are bound' { + It 'Should not throw when no parameters are bound' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{} + MutuallyExclusiveList1 = @('Param1') + MutuallyExclusiveList2 = @('Param2') + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + + It 'Should not throw when only parameters from the first list are bound' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ Param1 = 'Value1' } + MutuallyExclusiveList1 = @('Param1') + MutuallyExclusiveList2 = @('Param2') + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + + It 'Should not throw when only parameters from the second list are bound' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ Param2 = 'Value2' } + MutuallyExclusiveList1 = @('Param1') + MutuallyExclusiveList2 = @('Param2') + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + + It 'Should not throw when parameters from neither list are bound' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ Param3 = 'Value3' } + MutuallyExclusiveList1 = @('Param1') + MutuallyExclusiveList2 = @('Param2') + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + } + + Context 'When conflicting parameters are bound' { + It 'Should throw when parameters from both lists are bound' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + Param1 = 'Value1' + Param2 = 'Value2' + } + MutuallyExclusiveList1 = @('Param1') + MutuallyExclusiveList2 = @('Param2') + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Throw + } + + It 'Should throw when multiple parameters from both lists are bound' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + Param1 = 'Value1' + Param1b = 'Value1b' + Param2 = 'Value2' + Param2b = 'Value2b' + } + MutuallyExclusiveList1 = @('Param1', 'Param1b') + MutuallyExclusiveList2 = @('Param2', 'Param2b') + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Throw + } + } + } + + Context 'When using RequiredParameter parameter set' { + Context 'When required parameters are bound' { + It 'Should not throw when all required parameters are bound with default behavior' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + Param1 = 'Value1' + Param2 = 'Value2' + } + RequiredParameter = @('Param1', 'Param2') + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + + It 'Should not throw when all required parameters are bound with explicit All behavior' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + Param1 = 'Value1' + Param2 = 'Value2' + } + RequiredParameter = @('Param1', 'Param2') + RequiredBehavior = 'All' + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + + It 'Should not throw when at least one required parameter is bound with Any behavior' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ Param1 = 'Value1' } + RequiredParameter = @('Param1', 'Param2') + RequiredBehavior = 'Any' + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + + It 'Should not throw when all required parameters are bound with Any behavior' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + Param1 = 'Value1' + Param2 = 'Value2' + } + RequiredParameter = @('Param1', 'Param2') + RequiredBehavior = 'Any' + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + } + + Context 'When required parameters are not bound' { + It 'Should throw when not all required parameters are bound with default behavior' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ Param1 = 'Value1' } + RequiredParameter = @('Param1', 'Param2') + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Throw + } + + It 'Should throw when not all required parameters are bound with explicit All behavior' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ Param1 = 'Value1' } + RequiredParameter = @('Param1', 'Param2') + RequiredBehavior = 'All' + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Throw + } + + It 'Should throw when no required parameters are bound with Any behavior' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ Param3 = 'Value3' } + RequiredParameter = @('Param1', 'Param2') + RequiredBehavior = 'Any' + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Throw + } + } + + Context 'When using IfParameterPresent condition' { + It 'Should not throw when condition parameter is not present' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ OtherParam = 'Value' } + RequiredParameter = @('Param1', 'Param2') + IfParameterPresent = @('TriggerParam') + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + + It 'Should not throw when condition parameter is present and required parameters are bound' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + TriggerParam = 'TriggerValue' + Param1 = 'Value1' + Param2 = 'Value2' + } + RequiredParameter = @('Param1', 'Param2') + IfParameterPresent = @('TriggerParam') + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + + It 'Should throw when condition parameter is present but required parameters are not bound' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ TriggerParam = 'TriggerValue' } + RequiredParameter = @('Param1', 'Param2') + IfParameterPresent = @('TriggerParam') + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Throw + } + + It 'Should not throw when condition parameter is present and at least one required parameter is bound with Any behavior' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + TriggerParam = 'TriggerValue' + Param1 = 'Value1' + } + RequiredParameter = @('Param1', 'Param2') + RequiredBehavior = 'Any' + IfParameterPresent = @('TriggerParam') + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + } + } + + Context 'When using AtLeastOne parameter set' { + Context 'When at least one parameter from the list is bound' { + It 'Should not throw when one parameter from the list is bound' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ Severity = 'Warning'; OtherParam = 'Value' } + AtLeastOneList = @('Severity', 'MessageId') + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + + It 'Should not throw when multiple parameters from the list are bound' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + Severity = 'Warning' + MessageId = '12345' + OtherParam = 'Value' + } + AtLeastOneList = @('Severity', 'MessageId') + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + + It 'Should not throw when all parameters from the list are bound' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + Severity = 'Warning' + MessageId = '12345' + Level = 'Info' + } + AtLeastOneList = @('Severity', 'MessageId', 'Level') + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + + It 'Should not throw when only the last parameter from the list is bound' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ MessageId = '12345'; OtherParam = 'Value' } + AtLeastOneList = @('Severity', 'MessageId') + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + } + + Context 'When no parameters from the list are bound' { + It 'Should throw when no parameters from the list are bound' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ OtherParam = 'Value' } + AtLeastOneList = @('Severity', 'MessageId') + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Throw + } + + It 'Should throw when no parameters are bound at all' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{} + AtLeastOneList = @('Severity', 'MessageId') + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Throw + } + + It 'Should throw when bound parameters do not match any in the list' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + UnrelatedParam1 = 'Value1' + UnrelatedParam2 = 'Value2' + } + AtLeastOneList = @('Severity', 'MessageId') + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Throw + } + } + } + + Context 'When using complex real-world scenarios' { + Context 'When simulating DSC resource parameter validation' { + It 'Should validate SQL Server connection parameters correctly' { + # Simulate a scenario where either ServerInstance or ConnectionString must be provided + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + ServerInstance = 'localhost' + Database = 'TestDB' + } + AtLeastOneList = @('ServerInstance', 'ConnectionString') + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + ConnectionString = 'Server=localhost;Database=TestDB' + Database = 'TestDB' + } + AtLeastOneList = @('ServerInstance', 'ConnectionString') + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ Database = 'TestDB' } + AtLeastOneList = @('ServerInstance', 'ConnectionString') + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Throw + } + + It 'Should validate certificate parameters correctly' { + # Simulate a scenario where certificate can be specified by Thumbprint or Subject, but not both + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + Thumbprint = 'ABC123' + Store = 'My' + } + MutuallyExclusiveList1 = @('Thumbprint') + MutuallyExclusiveList2 = @('Subject') + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + Subject = 'CN=Test' + Store = 'My' + } + MutuallyExclusiveList1 = @('Thumbprint') + MutuallyExclusiveList2 = @('Subject') + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + Thumbprint = 'ABC123' + Subject = 'CN=Test' + Store = 'My' + } + MutuallyExclusiveList1 = @('Thumbprint') + MutuallyExclusiveList2 = @('Subject') + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Throw + } + + It 'Should validate conditional parameters correctly' { + # Simulate a scenario where if EnableSsl is specified, then CertificateThumbprint is required + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + ServerName = 'localhost' + EnableSsl = $true + CertificateThumbprint = 'ABC123' + } + RequiredParameter = @('CertificateThumbprint') + IfParameterPresent = @('EnableSsl') + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ ServerName = 'localhost' } + RequiredParameter = @('CertificateThumbprint') + IfParameterPresent = @('EnableSsl') + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + ServerName = 'localhost' + EnableSsl = $true + } + RequiredParameter = @('CertificateThumbprint') + IfParameterPresent = @('EnableSsl') + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Throw + } + } + } + + Context 'When using IfEqualParameterList parameter' { + Context 'When using with MutuallyExclusiveParameters parameter set' { + Context 'When the IfEqualParameterList condition is met' { + It 'Should throw when mutually exclusive parameters are both present and condition is met' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + ConfigType = 'Advanced' + Param1 = 'Value1' + Param2 = 'Value2' + } + MutuallyExclusiveList1 = @('Param1') + MutuallyExclusiveList2 = @('Param2') + IfEqualParameterList = @{ ConfigType = 'Advanced' } + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Throw + } + + It 'Should not throw when mutually exclusive parameters are not both present and condition is met' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + ConfigType = 'Advanced' + Param1 = 'Value1' + } + MutuallyExclusiveList1 = @('Param1') + MutuallyExclusiveList2 = @('Param2') + IfEqualParameterList = @{ ConfigType = 'Advanced' } + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + + It 'Should throw when multiple conditions are met and mutually exclusive parameters are present' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + ConfigType = 'Advanced' + Environment = 'Production' + Param1 = 'Value1' + Param2 = 'Value2' + } + MutuallyExclusiveList1 = @('Param1') + MutuallyExclusiveList2 = @('Param2') + IfEqualParameterList = @{ + ConfigType = 'Advanced' + Environment = 'Production' + } + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Throw + } + } + + Context 'When the IfEqualParameterList condition is not met' { + It 'Should not throw even when mutually exclusive parameters are both present but condition is not met' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + ConfigType = 'Basic' + Param1 = 'Value1' + Param2 = 'Value2' + } + MutuallyExclusiveList1 = @('Param1') + MutuallyExclusiveList2 = @('Param2') + IfEqualParameterList = @{ ConfigType = 'Advanced' } + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + + It 'Should not throw when the parameter in IfEqualParameterList is not present' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + Param1 = 'Value1' + Param2 = 'Value2' + } + MutuallyExclusiveList1 = @('Param1') + MutuallyExclusiveList2 = @('Param2') + IfEqualParameterList = @{ ConfigType = 'Advanced' } + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + + It 'Should not throw when only some conditions are met' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + ConfigType = 'Advanced' + Environment = 'Development' + Param1 = 'Value1' + Param2 = 'Value2' + } + MutuallyExclusiveList1 = @('Param1') + MutuallyExclusiveList2 = @('Param2') + IfEqualParameterList = @{ + ConfigType = 'Advanced' + Environment = 'Production' + } + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + } + } + + Context 'When using with RequiredParameter parameter set' { + Context 'When the IfEqualParameterList condition is met' { + It 'Should not throw when condition is met and required parameters are bound' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + ConfigType = 'Advanced' + Param1 = 'Value1' + Param2 = 'Value2' + } + RequiredParameter = @('Param1', 'Param2') + IfEqualParameterList = @{ ConfigType = 'Advanced' } + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + + It 'Should throw when condition is met but required parameters are not bound' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + ConfigType = 'Advanced' + Param1 = 'Value1' + } + RequiredParameter = @('Param1', 'Param2') + IfEqualParameterList = @{ ConfigType = 'Advanced' } + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Throw + } + + It 'Should not throw when condition is met and at least one required parameter is bound with Any behavior' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + ConfigType = 'Advanced' + Param1 = 'Value1' + } + RequiredParameter = @('Param1', 'Param2') + RequiredBehavior = 'Any' + IfEqualParameterList = @{ ConfigType = 'Advanced' } + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + + It 'Should work with multiple conditions and IfParameterPresent together' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + ConfigType = 'Advanced' + Environment = 'Production' + TriggerParam = 'Present' + Param1 = 'Value1' + Param2 = 'Value2' + } + RequiredParameter = @('Param1', 'Param2') + IfParameterPresent = @('TriggerParam') + IfEqualParameterList = @{ + ConfigType = 'Advanced' + Environment = 'Production' + } + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + } + + Context 'When the IfEqualParameterList condition is not met' { + It 'Should not throw even when required parameters are not bound but condition is not met' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + ConfigType = 'Basic' + Param1 = 'Value1' + } + RequiredParameter = @('Param1', 'Param2') + IfEqualParameterList = @{ ConfigType = 'Advanced' } + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + + It 'Should not throw when the parameter in IfEqualParameterList is not present' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ Param1 = 'Value1' } + RequiredParameter = @('Param1', 'Param2') + IfEqualParameterList = @{ ConfigType = 'Advanced' } + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + } + } + + Context 'When using with AtLeastOne parameter set' { + Context 'When the IfEqualParameterList condition is met' { + It 'Should not throw when condition is met and at least one required parameter is present' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + ConfigType = 'Advanced' + Severity = 'Warning' + } + AtLeastOneList = @('Severity', 'MessageId') + IfEqualParameterList = @{ ConfigType = 'Advanced' } + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + + It 'Should throw when condition is met but none of the required parameters are present' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + ConfigType = 'Advanced' + OtherParam = 'Value' + } + AtLeastOneList = @('Severity', 'MessageId') + IfEqualParameterList = @{ ConfigType = 'Advanced' } + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Throw + } + + It 'Should not throw when condition is met and multiple required parameters are present' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + ConfigType = 'Advanced' + Severity = 'Warning' + MessageId = '12345' + } + AtLeastOneList = @('Severity', 'MessageId') + IfEqualParameterList = @{ ConfigType = 'Advanced' } + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + } + + Context 'When the IfEqualParameterList condition is not met' { + It 'Should not throw even when none of the required parameters are present but condition is not met' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + ConfigType = 'Basic' + OtherParam = 'Value' + } + AtLeastOneList = @('Severity', 'MessageId') + IfEqualParameterList = @{ ConfigType = 'Advanced' } + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + + It 'Should not throw when the parameter in IfEqualParameterList is not present' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ OtherParam = 'Value' } + AtLeastOneList = @('Severity', 'MessageId') + IfEqualParameterList = @{ ConfigType = 'Advanced' } + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + } + } + + Context 'When using real-world DSC resource scenarios with conditional validation' { + It 'Should validate SQL Server authentication parameters only when authentication type is specified' { + # Should not validate credentials when using Windows Authentication + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + AuthenticationType = 'Windows' + ServerInstance = 'localhost' + } + RequiredParameter = @('Username', 'Password') + IfEqualParameterList = @{ AuthenticationType = 'SQL' } + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + + # Should validate credentials when using SQL Authentication and they are provided + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + AuthenticationType = 'SQL' + ServerInstance = 'localhost' + Username = 'sa' + Password = 'password' + } + RequiredParameter = @('Username', 'Password') + IfEqualParameterList = @{ AuthenticationType = 'SQL' } + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + + # Should throw when using SQL Authentication but credentials are missing + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + AuthenticationType = 'SQL' + ServerInstance = 'localhost' + } + RequiredParameter = @('Username', 'Password') + IfEqualParameterList = @{ AuthenticationType = 'SQL' } + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Throw + } + + It 'Should validate certificate parameters only for secure connections' { + # Should not require certificate when connection is not secure + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + ConnectionType = 'Standard' + ServerName = 'localhost' + } + AtLeastOneList = @('CertificateThumbprint', 'CertificateSubject') + IfEqualParameterList = @{ ConnectionType = 'Secure' } + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + + # Should require certificate parameters when connection is secure and they are provided + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + ConnectionType = 'Secure' + ServerName = 'localhost' + CertificateThumbprint = 'ABC123' + } + AtLeastOneList = @('CertificateThumbprint', 'CertificateSubject') + IfEqualParameterList = @{ ConnectionType = 'Secure' } + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + + # Should throw when connection is secure but no certificate parameters are provided + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + ConnectionType = 'Secure' + ServerName = 'localhost' + } + AtLeastOneList = @('CertificateThumbprint', 'CertificateSubject') + IfEqualParameterList = @{ ConnectionType = 'Secure' } + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Throw + } + + It 'Should validate environment-specific parameters correctly' { + # Should allow conflicting parameters in development but not in production + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + Environment = 'Development' + DebugMode = $true + OptimizedMode = $true + } + MutuallyExclusiveList1 = @('DebugMode') + MutuallyExclusiveList2 = @('OptimizedMode') + IfEqualParameterList = @{ Environment = 'Production' } + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + + # Should throw when conflicting parameters are used in production + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + Environment = 'Production' + DebugMode = $true + OptimizedMode = $true + } + MutuallyExclusiveList1 = @('DebugMode') + MutuallyExclusiveList2 = @('OptimizedMode') + IfEqualParameterList = @{ Environment = 'Production' } + ErrorAction = 'Stop' + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Throw + } + } + } +} diff --git a/tests/Integration/Public/Set-DscMachineRebootRequired.Tests.ps1 b/tests/Integration/Commands/Set-DscMachineRebootRequired.Tests.ps1 similarity index 100% rename from tests/Integration/Public/Set-DscMachineRebootRequired.Tests.ps1 rename to tests/Integration/Commands/Set-DscMachineRebootRequired.Tests.ps1 diff --git a/tests/Integration/Public/Set-PSModulePath.Tests.ps1 b/tests/Integration/Commands/Set-PSModulePath.Tests.ps1 similarity index 100% rename from tests/Integration/Public/Set-PSModulePath.Tests.ps1 rename to tests/Integration/Commands/Set-PSModulePath.Tests.ps1 diff --git a/tests/Unit/Public/Assert-BoundParameter.Tests.ps1 b/tests/Unit/Public/Assert-BoundParameter.Tests.ps1 index 7d300cb..b34f572 100644 --- a/tests/Unit/Public/Assert-BoundParameter.Tests.ps1 +++ b/tests/Unit/Public/Assert-BoundParameter.Tests.ps1 @@ -50,12 +50,17 @@ Describe 'Assert-BoundParameter' -Tag 'AssertBoundParameter' { @{ MockParameterSetName = 'MutuallyExclusiveParameters' # cSpell: disable-next - MockExpectedParameters = '-BoundParameterList -MutuallyExclusiveList1 -MutuallyExclusiveList2 []' + MockExpectedParameters = '-BoundParameterList -MutuallyExclusiveList1 -MutuallyExclusiveList2 [-IfEqualParameterList ] []' } @{ MockParameterSetName = 'RequiredParameter' # cSpell: disable-next - MockExpectedParameters = '-BoundParameterList -RequiredParameter [-RequiredBehavior ] [-IfParameterPresent ] []' + MockExpectedParameters = '-BoundParameterList -RequiredParameter [-RequiredBehavior ] [-IfParameterPresent ] [-IfEqualParameterList ] []' + } + @{ + MockParameterSetName = 'AtLeastOne' + # cSpell: disable-next + MockExpectedParameters = '-BoundParameterList -AtLeastOneList [-IfEqualParameterList ] []' } ) { InModuleScope -Parameters $_ -ScriptBlock { @@ -186,6 +191,41 @@ Describe 'Assert-BoundParameter' -Tag 'AssertBoundParameter' { } } } + + Context 'When using the AtLeastOne parameter set' { + Context 'When at least one parameter from the list is bound' { + It 'Should not throw an error' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + Severity = 'Warning' + OtherParam = 'value' + } + AtLeastOneList = @('Severity', 'MessageId') + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + } + + Context 'When multiple parameters from the list are bound' { + It 'Should not throw an error' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + Severity = 'Warning' + MessageId = '12345' + OtherParam = 'value' + } + AtLeastOneList = @('Severity', 'MessageId') + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + } + } } Context 'When the assert fails' { @@ -237,6 +277,27 @@ Describe 'Assert-BoundParameter' -Tag 'AssertBoundParameter' { } } + Context 'When using the AtLeastOne parameter set and none of the required parameters are bound' { + It 'Should throw an error' { + $errorMessage = InModuleScope -ScriptBlock { + $script:localizedData.Assert_BoundParameter_AtLeastOneParameterMustBeSet + } + + $errorMessage = $errorMessage -f "Severity','MessageId" + + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + OtherParam = 'value' + } + AtLeastOneList = @('Severity', 'MessageId') + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Throw -ExpectedMessage "$errorMessage*" + } + } + Context 'When the required parameter is not present' { BeforeAll { Mock -CommandName Assert-RequiredCommandParameter -MockWith { @@ -252,4 +313,269 @@ Describe 'Assert-BoundParameter' -Tag 'AssertBoundParameter' { } } } + + Context 'When using IfEqualParameterList parameter' { + Context 'When using MutuallyExclusiveParameters parameter set' { + Context 'When the IfEqualParameterList condition is met' { + It 'Should throw an error when mutually exclusive parameters are both present' { + $errorMessage = InModuleScope -ScriptBlock { + $script:localizedData.ParameterUsageWrong + } + + $errorMessage = $errorMessage -f "Severity", "MessageId" + + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + Severity = 'High' + MessageId = '12345' + Ensure = 'Present' + } + MutuallyExclusiveList1 = @('Severity') + MutuallyExclusiveList2 = @('MessageId') + IfEqualParameterList = @{ + Ensure = 'Present' + } + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Throw -ExpectedMessage "$errorMessage*" + } + + It 'Should not throw an error when mutually exclusive parameters are not both present' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + Severity = 'High' + Ensure = 'Present' + } + MutuallyExclusiveList1 = @('Severity') + MutuallyExclusiveList2 = @('MessageId') + IfEqualParameterList = @{ + Ensure = 'Present' + } + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + } + + Context 'When the IfEqualParameterList condition is not met' { + It 'Should not throw an error even when mutually exclusive parameters are both present' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + Severity = 'High' + MessageId = '12345' + Ensure = 'Absent' + } + MutuallyExclusiveList1 = @('Severity') + MutuallyExclusiveList2 = @('MessageId') + IfEqualParameterList = @{ + Ensure = 'Present' + } + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + + It 'Should not throw an error when the parameter in IfEqualParameterList is not present' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + Severity = 'High' + MessageId = '12345' + } + MutuallyExclusiveList1 = @('Severity') + MutuallyExclusiveList2 = @('MessageId') + IfEqualParameterList = @{ + Ensure = 'Present' + } + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + } + + Context 'When multiple conditions in IfEqualParameterList must match' { + It 'Should throw an error when all conditions are met and mutually exclusive parameters are present' { + $errorMessage = InModuleScope -ScriptBlock { + $script:localizedData.ParameterUsageWrong + } + + $errorMessage = $errorMessage -f "Severity", "MessageId" + + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + Severity = 'High' + MessageId = '12345' + Ensure = 'Present' + Type = 'Server' + } + MutuallyExclusiveList1 = @('Severity') + MutuallyExclusiveList2 = @('MessageId') + IfEqualParameterList = @{ + Ensure = 'Present' + Type = 'Server' + } + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Throw -ExpectedMessage "$errorMessage*" + } + + It 'Should not throw an error when only some conditions are met' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + Severity = 'High' + MessageId = '12345' + Ensure = 'Present' + Type = 'Client' + } + MutuallyExclusiveList1 = @('Severity') + MutuallyExclusiveList2 = @('MessageId') + IfEqualParameterList = @{ + Ensure = 'Present' + Type = 'Server' + } + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + } + } + + Context 'When using RequiredParameter parameter set' { + BeforeAll { + Mock -CommandName Assert-RequiredCommandParameter + } + + Context 'When the IfEqualParameterList condition is met' { + It 'Should call Assert-RequiredCommandParameter when condition is met' { + InModuleScope -ScriptBlock { + $testParams = @{ + BoundParameterList = @{ + Property1 = 'SpecificValue' + Property2 = 'Value2' + } + RequiredParameter = @('Property2', 'Property3') + IfEqualParameterList = @{ + Property1 = 'SpecificValue' + } + } + + Assert-BoundParameter @testParams + } + + Should -Invoke -CommandName Assert-RequiredCommandParameter -Exactly -Times 1 -Scope It + } + } + + Context 'When the IfEqualParameterList condition is not met' { + It 'Should not call Assert-RequiredCommandParameter when condition is not met' { + InModuleScope -ScriptBlock { + $testParams = @{ + BoundParameterList = @{ + Property1 = 'DifferentValue' + Property2 = 'Value2' + } + RequiredParameter = @('Property2', 'Property3') + IfEqualParameterList = @{ + Property1 = 'SpecificValue' + } + } + + Assert-BoundParameter @testParams + } + + Should -Invoke -CommandName Assert-RequiredCommandParameter -Exactly -Times 0 -Scope It + } + } + } + + Context 'When using AtLeastOne parameter set' { + Context 'When the IfEqualParameterList condition is met' { + It 'Should throw an error when none of the required parameters are present' { + $errorMessage = InModuleScope -ScriptBlock { + $script:localizedData.Assert_BoundParameter_AtLeastOneParameterMustBeSet + } + + $errorMessage = $errorMessage -f "Severity','MessageId" + + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + Ensure = 'Present' + OtherParam = 'value' + } + AtLeastOneList = @('Severity', 'MessageId') + IfEqualParameterList = @{ + Ensure = 'Present' + } + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Throw -ExpectedMessage "$errorMessage*" + } + + It 'Should not throw an error when at least one required parameter is present' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + Severity = 'High' + Ensure = 'Present' + } + AtLeastOneList = @('Severity', 'MessageId') + IfEqualParameterList = @{ + Ensure = 'Present' + } + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + } + + Context 'When the IfEqualParameterList condition is not met' { + It 'Should not throw an error even when none of the required parameters are present' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + Ensure = 'Absent' + OtherParam = 'value' + } + AtLeastOneList = @('Severity', 'MessageId') + IfEqualParameterList = @{ + Ensure = 'Present' + } + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + + It 'Should not throw an error when the parameter in IfEqualParameterList is not present' { + { + $assertBoundParameterParameters = @{ + BoundParameterList = @{ + OtherParam = 'value' + } + AtLeastOneList = @('Severity', 'MessageId') + IfEqualParameterList = @{ + Ensure = 'Present' + } + } + + Assert-BoundParameter @assertBoundParameterParameters + } | Should -Not -Throw + } + } + } + } }