diff --git a/Functions/Assertions/HaveParameter.Tests.ps1 b/Functions/Assertions/HaveParameter.Tests.ps1 new file mode 100644 index 000000000..4c90e4999 --- /dev/null +++ b/Functions/Assertions/HaveParameter.Tests.ps1 @@ -0,0 +1,435 @@ +Set-StrictMode -Version Latest + +InModuleScope Pester { + + if ($PSVersionTable.PSVersion.Major -ge 5) { + function Invoke-DummyFunction { + param( + [Parameter(Mandatory = $true)] + $MandatoryParam, + + [ValidateNotNullOrEmpty()] + [DateTime]$ParamWithNotNullOrEmptyValidation = (Get-Date), + + [Parameter()] + [ValidateScript( + { + if (-not (Test-Path $_)) { + $errorItem = [System.Management.Automation.ErrorRecord]::new( + ([System.ArgumentException]"Path not found"), + 'ParameterValue.FileNotFound', + [System.Management.Automation.ErrorCategory]::ObjectNotFound, + $_ + ) + $errorItem.ErrorDetails = "Invalid path '$_'." + $PSCmdlet.ThrowTerminatingError($errorItem) + } + else { + return $true + } + } + )] + [String]$ParamWithScriptValidation = ".", + + [Parameter()] + [ValidateNotNullOrEmpty()] + [ArgumentCompleter( + { + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) + & Get-ChildItem | + Where-Object { $_.Name -like "$wordToComplete*" } | + ForEach-Object { [System.Management.Automation.CompletionResult]::new( $_.Name, $_.Name, [System.Management.Automation.CompletionResultType]::ParameterValue, $_.Name ) } + } + )] + [String]$ParamWithArgumentCompleter = "./.git" + ) + } + } + else { + function Invoke-DummyFunction { + param( + [Parameter(Mandatory = $true)] + $MandatoryParam, + + [ValidateNotNullOrEmpty()] + [DateTime]$ParamWithNotNullOrEmptyValidation = (Get-Date), + + # argument completer is PowerShell v5+ only + [Parameter()] + [ValidateScript( + { + if (-not (Test-Path $_)) { + $errorItem = [System.Management.Automation.ErrorRecord]::new( + ([System.ArgumentException]"Path not found"), + 'ParameterValue.FileNotFound', + [System.Management.Automation.ErrorCategory]::ObjectNotFound, + $_ + ) + $errorItem.ErrorDetails = "Invalid path '$_'." + $PSCmdlet.ThrowTerminatingError($errorItem) + } + else { + return $true + } + } + )] + [String]$ParamWithScriptValidation = "." + ) + } + } + + function Invoke-EmptyFunction { + param() + } + + Describe "Should -HaveParameter" { + + It "passes if the parameter exists" -TestCases @( + @{ParameterName = "MandatoryParam"} + @{ParameterName = "ParamWithNotNullOrEmptyValidation"} + @{ParameterName = "ParamWithScriptValidation"} + if ($PSVersionTable.PSVersion.Major -ge 5) { + @{ParameterName = "ParamWithArgumentCompleter"} + } + ) { + param($ParameterName) + Get-Command "Invoke-DummyFunction" | Should -HaveParameter $ParameterName + } + + It "passes if the parameter is mandatory" -TestCases @( + @{ParameterName = "MandatoryParam"} + ) { + param($ParameterName) + Get-Command "Invoke-DummyFunction" | Should -HaveParameter $ParameterName -Mandatory + } + + It "passes if the parameter is of type " -TestCases @( + @{ParameterName = "MandatoryParam"; ExpectedType = [System.Object]} + @{ParameterName = "ParamWithNotNullOrEmptyValidation"; ExpectedType = [DateTime]} + @{ParameterName = "ParamWithScriptValidation"; ExpectedType = "String"} + if ($PSVersionTable.PSVersion.Major -ge 5) { + @{ParameterName = "ParamWithArgumentCompleter"; ExpectedType = "String"} + } + ) { + param($ParameterName, $ExpectedType) + Get-Command "Invoke-DummyFunction" | Should -HaveParameter $ParameterName -Type $ExpectedType + } + + if ($PSVersionTable.PSVersion.Major -ge 5) { + It "passes if the parameter has an ArgumentCompleter" -TestCases @( + @{ParameterName = "ParamWithArgumentCompleter"} + ) { + param($ParameterName) + Get-Command "Invoke-DummyFunction" | Should -HaveParameter $ParameterName -HasArgumentCompleter + } + } + + It "passes if the parameter has a default value ''" -TestCases @( + @{ParameterName = "MandatoryParam"; ExpectedValue = ""} + @{ParameterName = "ParamWithNotNullOrEmptyValidation"; ExpectedValue = "(Get-Date)"} + @{ParameterName = "ParamWithScriptValidation"; ExpectedValue = "."} + if ($PSVersionTable.PSVersion.Major -ge 5) { + @{ParameterName = "ParamWithArgumentCompleter"; ExpectedValue = "./.git"} + } + ) { + param($ParameterName, $ExpectedValue) + Get-Command "Invoke-DummyFunction" | Should -HaveParameter $ParameterName -DefaultValue $ExpectedValue + } + + It "passes if the parameter exists, is of type and has a default value ''" -TestCases @( + @{ParameterName = "ParamWithNotNullOrEmptyValidation"; ExpectedType = [DateTime]; ExpectedValue = "(Get-Date)"} + @{ParameterName = "ParamWithScriptValidation"; ExpectedType = [String]; ExpectedValue = "."} + if ($PSVersionTable.PSVersion.Major -ge 5) { + @{ParameterName = "ParamWithArgumentCompleter"; ExpectedType = "String"; ExpectedValue = "./.git"} + } + ) { + param($ParameterName, $ExpectedType, $ExpectedValue) + Get-Command "Invoke-DummyFunction" | Should -HaveParameter $ParameterName -Type $ExpectedType -DefaultValue $ExpectedValue + } + + if ($PSVersionTable.PSVersion.Major -ge 5) { + It "passes if the parameter exists, is of type , has a default value '' and has an ArgumentCompleter" -TestCases @( + @{ParameterName = "ParamWithArgumentCompleter"; ExpectedType = [String]; ExpectedValue = "./.git"} + ) { + param($ParameterName, $ExpectedType, $ExpectedValue) + Get-Command "Invoke-DummyFunction" | Should -HaveParameter $ParameterName -Type $ExpectedType -DefaultValue $ExpectedValue -HasArgumentCompleter + } + } + + It "fails if the command does not have any parameters" { + { Get-Command "Invoke-EmptyFunction" | Should -HaveParameter "imaginary" } | Verify-AssertionFailed + } + + It "fails if the parameter does not exists" -TestCases @( + @{ParameterName = "InputObject"} + @{ParameterName = "Date"} + @{ParameterName = "Path"} + ) { + param($ParameterName) + { Get-Command "Invoke-DummyFunction" | Should -HaveParameter $ParameterName } | Verify-AssertionFailed + } + + It "fails if the parameter is not mandatory or does not exist" -TestCases @( + @{ParameterName = "ParamWithNotNullOrEmptyValidation"} + @{ParameterName = "ParamWithScriptValidation"} + if ($PSVersionTable.PSVersion.Major -ge 5) { + @{ParameterName = "ParamWithArgumentCompleter"} + } + @{ParameterName = "InputObject"} + ) { + param($ParameterName) + { Get-Command "Invoke-DummyFunction" | Should -HaveParameter $ParameterName -Mandatory } | Verify-AssertionFailed + } + + It "fails if the parameter is not of type or does not exist" -TestCases @( + @{ParameterName = "MandatoryParam"; ExpectedType = [Int32]} + @{ParameterName = "ParamWithNotNullOrEmptyValidation"; ExpectedType = [Int32]} + @{ParameterName = "ParamWithScriptValidation"; ExpectedType = [DateTime]} + if ($PSVersionTable.PSVersion.Major -ge 5) { + @{ParameterName = "ParamWithArgumentCompleter"; ExpectedType = "DateTime"} + } + @{ParameterName = "InputObject"; ExpectedType = [String]} + ) { + param($ParameterName, $ExpectedType) + { Get-Command "Invoke-DummyFunction" | Should -HaveParameter $ParameterName -Type $ExpectedType } | Verify-AssertionFailed + } + + if ($PSVersionTable.PSVersion.Major -ge 5) { + It "fails if the parameter has not an ArgumentCompleter or does not exist" -TestCases @( + @{ParameterName = "MandatoryParam"} + @{ParameterName = "ParamWithNotNullOrEmptyValidation"} + @{ParameterName = "ParamWithScriptValidation"} + @{ParameterName = "InputObject"} + ) { + param($ParameterName) + { Get-Command "Invoke-DummyFunction" | Should -HaveParameter $ParameterName -HasArgumentCompleter } | Verify-AssertionFailed + } + } + + It "fails if the parameter has a default value other than '' or does not exist" -TestCases @( + @{ParameterName = "MandatoryParam"; ExpectedValue = "."} + @{ParameterName = "ParamWithNotNullOrEmptyValidation"; ExpectedValue = "(Get-Item)"} + @{ParameterName = "ParamWithScriptValidation"; ExpectedValue = ""} + if ($PSVersionTable.PSVersion.Major -ge 5) { + @{ParameterName = "ParamWithArgumentCompleter"; ExpectedValue = "."} + } + @{ParameterName = "InputObject"; ExpectedValue = ""} + ) { + param($ParameterName, $ExpectedValue) + { Get-Command "Invoke-DummyFunction" | Should -HaveParameter $ParameterName -DefaultValue $ExpectedValue } | Verify-AssertionFailed + } + + It "fails if the parameter does not exist, is not of type or has a default value other than ''" -TestCases @( + @{ParameterName = "MandatoryParam"; ExpectedType = [DateTime]; ExpectedValue = "(Get-Item)"} + @{ParameterName = "ParamWithNotNullOrEmptyValidation"; ExpectedType = [DateTime]; ExpectedValue = "(Get-Item)"} + @{ParameterName = "ParamWithScriptValidation"; ExpectedType = [DateTime]; ExpectedValue = "."} + if ($PSVersionTable.PSVersion.Major -ge 5) { + @{ParameterName = "ParamWithArgumentCompleter"; ExpectedType = "String"; ExpectedValue = ""} + } + @{ParameterName = "InputObject"; ExpectedType = [String]; ExpectedValue = ""} + ) { + param($ParameterName, $ExpectedType, $ExpectedValue) + { Get-Command "Invoke-DummyFunction" | Should -HaveParameter $ParameterName -Type $ExpectedType -DefaultValue $ExpectedValue } | Verify-AssertionFailed + } + + if ($PSVersionTable.PSVersion.Major -ge 5) { + It "fails if the parameter does not exist, is not of type , has a default value other than '' or has not an ArgumentCompleter" -TestCases @( + @{ParameterName = "MandatoryParam"; ExpectedType = [Object]; ExpectedValue = ""} + @{ParameterName = "ParamWithNotNullOrEmptyValidation"; ExpectedType = [DateTime]; ExpectedValue = "."} + @{ParameterName = "ParamWithScriptValidation"; ExpectedType = [String]; ExpectedValue = "."} + @{ParameterName = "ParamWithArgumentCompleter"; ExpectedType = [String]; ExpectedValue = "."} + @{ParameterName = "InputObject"; ExpectedType = [String]; ExpectedValue = "."} + ) { + param($ParameterName, $ExpectedType, $ExpectedValue) + { Get-Command "Invoke-DummyFunction" | Should -HaveParameter $ParameterName -Type $ExpectedType -DefaultValue $ExpectedValue -HasArgumentCompleter } | Verify-AssertionFailed + } + } + + It "returns the correct assertion message when the command does not have any parameters" { + $err = { Get-Command "Invoke-EmptyFunction" | Should -HaveParameter "imaginary" } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal "Expected command Invoke-EmptyFunction to have a parameter imaginary, but the parameter is missing." + } + + It "returns the correct assertion message when parameter ParamWithNotNullOrEmptyValidation is not mandatory" { + $err = { Get-Command "Invoke-DummyFunction" | Should -HaveParameter ParamWithNotNullOrEmptyValidation -Mandatory } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal "Expected command Invoke-DummyFunction to have a parameter ParamWithNotNullOrEmptyValidation, which is mandatory, but it wasn't mandatory." + } + + It "returns the correct assertion message when parameter ParamWithNotNullOrEmptyValidation is not mandatory, of the wrong type and has a different default value than expected" { + $err = { Get-Command "Invoke-DummyFunction" | Should -HaveParameter ParamWithNotNullOrEmptyValidation -Mandatory -Type [TimeSpan] -DefaultValue "wrong value" -Because 'of reasons' } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal "Expected command Invoke-DummyFunction to have a parameter ParamWithNotNullOrEmptyValidation, which is mandatory, of type [System.TimeSpan] and the default value to be 'wrong value', because of reasons, but it wasn't mandatory, it was of type [System.DateTime] and the default value was '(Get-Date)'." + } + + if ($PSVersionTable.PSVersion.Major -ge 5) { + It "returns the correct assertion message when parameter ParamWithNotNullOrEmptyValidation is not mandatory, of the wrong type, has a different default value than expected and has no ArgumentCompleter" { + $err = { Get-Command "Invoke-DummyFunction" | Should -HaveParameter ParamWithNotNullOrEmptyValidation -Mandatory -Type [TimeSpan] -DefaultValue "wrong value" -HasArgumentCompleter -Because 'of reasons' } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal "Expected command Invoke-DummyFunction to have a parameter ParamWithNotNullOrEmptyValidation, which is mandatory, of type [System.TimeSpan], the default value to be 'wrong value' and has ArgumentCompletion, because of reasons, but it wasn't mandatory, it was of type [System.DateTime], the default value was '(Get-Date)' and has no ArgumentCompletion." + } + } + } + + Describe "Should -Not -HavePameter" { + + It "passes if the parameter does not exists" -TestCases @( + @{ParameterName = "FirstParam"} + @{ParameterName = "InputObject"} + ) { + param($ParameterName) + Get-Command "Invoke-DummyFunction" | Should -Not -HaveParameter $ParameterName + } + + It "passes if the parameter does not exist or is not mandatory" -TestCases @( + @{ParameterName = "ParamWithNotNullOrEmptyValidation"} + @{ParameterName = "ParamWithScriptValidation"} + if ($PSVersionTable.PSVersion.Major -ge 5) { + @{ParameterName = "ParamWithArgumentCompleter"} + } + @{ParameterName = "InputObject"} + ) { + param($ParameterName) + Get-Command "Invoke-DummyFunction" | Should -Not -HaveParameter $ParameterName -Mandatory + } + + It "passes if the parameter does not exist, is not mandatory or is not of type "-TestCases @( + @{ParameterName = "ParamWithNotNullOrEmptyValidation"; ExpectedType = "[TimeSpan]"} + @{ParameterName = "ParamWithScriptValidation"; ExpectedType = "[TimeSpan]"} + if ($PSVersionTable.PSVersion.Major -ge 5) { + @{ParameterName = "ParamWithArgumentCompleter"; ExpectedType = [TimeSpan]} + } + @{ParameterName = "InputObject"; ExpectedType = "[Object]"} + ) { + param($ParameterName, $ExpectedType) + Get-Command "Invoke-DummyFunction" | Should -Not -HaveParameter $ParameterName -Mandatory -Type $ExpectedType + } + + It "passes if the parameter does not exist, is not mandatory, is not of type or the default value is not "-TestCases @( + @{ParameterName = "MandatoryParam"; ExpectedType = "[TimeSpan]"; ExpectedValue = "wrong"} + @{ParameterName = "ParamWithNotNullOrEmptyValidation"; ExpectedType = "[TimeSpan]"; ExpectedValue = ""} + @{ParameterName = "ParamWithScriptValidation"; ExpectedType = "[Int32]"; ExpectedValue = ".."} + if ($PSVersionTable.PSVersion.Major -ge 5) { + @{ParameterName = "ParamWithArgumentCompleter"; ExpectedType = [TimeSpan]; ExpectedValue = "."} + } + @{ParameterName = "InputObject"; ExpectedType = "[Object]"; ExpectedValue = ""} + ) { + param($ParameterName, $ExpectedType, $ExpectedValue) + Get-Command "Invoke-DummyFunction" | Should -Not -HaveParameter $ParameterName -Type $ExpectedType -DefaultValue $ExpectedValue + } + + if ($PSVersionTable.PSVersion.Major -ge 5) { + It "passes if the parameter does not exist, has not an ArgumentCompleter" -TestCases @( + @{ParameterName = "MandatoryParam"} + @{ParameterName = "ParamWithNotNullOrEmptyValidation"} + @{ParameterName = "ParamWithScriptValidation"} + @{ParameterName = "InputObject"} + ) { + param($ParameterName) + Get-Command "Invoke-DummyFunction" | Should -Not -HaveParameter -HasArgumentCompleter + } + } + + It "fails if the parameter exists" -TestCases @( + @{ParameterName = "MandatoryParam"} + @{ParameterName = "ParamWithNotNullOrEmptyValidation"} + @{ParameterName = "ParamWithScriptValidation"} + if ($PSVersionTable.PSVersion.Major -ge 5) { + @{ParameterName = "ParamWithArgumentCompleter"} + } + ) { + param($ParameterName) + { Get-Command "Invoke-DummyFunction" | Should -Not -HaveParameter $ParameterName } | Verify-AssertionFailed + } + + It "fails if the parameter is mandatory" -TestCases @( + @{ParameterName = "MandatoryParam"} + ) { + param($ParameterName) + { Get-Command "Invoke-DummyFunction" | Should -Not -HaveParameter $ParameterName -Mandatory } | Verify-AssertionFailed + } + + It "fails if the parameter is of type " -TestCases @( + @{ParameterName = "MandatoryParam"; ExpectedType = [Object]} + @{ParameterName = "ParamWithNotNullOrEmptyValidation"; ExpectedType = [DateTime]} + @{ParameterName = "ParamWithScriptValidation"; ExpectedType = [String]} + if ($PSVersionTable.PSVersion.Major -ge 5) { + @{ParameterName = "ParamWithArgumentCompleter"; ExpectedType = "String"} + } + ) { + param($ParameterName, $ExpectedType) + { Get-Command "Invoke-DummyFunction" | Should -Not -HaveParameter $ParameterName -Type $ExpectedType } | Verify-AssertionFailed + } + + It "fails if the parameter is of type or the default value is "-TestCases @( + @{ParameterName = "MandatoryParam"; ExpectedType = "[Object]"; ExpectedValue = ""} + @{ParameterName = "ParamWithNotNullOrEmptyValidation"; ExpectedType = "[DateTime]"; ExpectedValue = ""} + @{ParameterName = "ParamWithScriptValidation"; ExpectedType = "[String]"; ExpectedValue = ".."} + if ($PSVersionTable.PSVersion.Major -ge 5) { + @{ParameterName = "ParamWithArgumentCompleter"; ExpectedType = [String]; ExpectedValue = "."} + } + ) { + param($ParameterName, $ExpectedType, $ExpectedValue) + { Get-Command "Invoke-DummyFunction" | Should -Not -HaveParameter $ParameterName -Type $ExpectedType -DefaultValue $ExpectedValue } | Verify-AssertionFailed + } + + if ($PSVersionTable.PSVersion.Major -ge 5) { + It "fails if the parameter has an ArgumentCompleter" -TestCases @( + @{ParameterName = "ParamWithArgumentCompleter"} + ) { + param($ParameterName) + { Get-Command "Invoke-DummyFunction" | Should -Not -HaveParameter $ParameterName -HasArgumentCompleter } | Verify-AssertionFailed + } + } + + It "fails if the parameter has a default value of ''" -TestCases @( + @{ParameterName = "MandatoryParam"; ExpectedValue = ""} + @{ParameterName = "ParamWithNotNullOrEmptyValidation"; ExpectedValue = "(Get-Date)"} + @{ParameterName = "ParamWithScriptValidation"; ExpectedValue = "."} + if ($PSVersionTable.PSVersion.Major -ge 5) { + @{ParameterName = "ParamWithArgumentCompleter"; ExpectedValue = "./.git"} + } + ) { + param($ParameterName, $ExpectedValue) + { Get-Command "Invoke-DummyFunction" | Should -Not -HaveParameter $ParameterName -DefaultValue $ExpectedValue } | Verify-AssertionFailed + } + + It "fails if the parameter is of type or has a default value of ''" -TestCases @( + @{ParameterName = "MandatoryParam"; ExpectedType = [Object]; ExpectedValue = ""} + @{ParameterName = "ParamWithNotNullOrEmptyValidation"; ExpectedType = [DateTime]; ExpectedValue = "(Get-Date)"} + @{ParameterName = "ParamWithScriptValidation"; ExpectedType = [String]; ExpectedValue = "."} + if ($PSVersionTable.PSVersion.Major -ge 5) { + @{ParameterName = "ParamWithArgumentCompleter"; ExpectedType = "String"; ExpectedValue = "./.git"} + } + ) { + param($ParameterName, $ExpectedType, $ExpectedValue) + { Get-Command "Invoke-DummyFunction" | Should -Not -HaveParameter $ParameterName -Type $ExpectedType -DefaultValue $ExpectedValue } | Verify-AssertionFailed + } + + if ($PSVersionTable.PSVersion.Major -ge 5) { + It "fails if the parameter is of type , has a default value of '' or has an ArgumentCompleter" -TestCases @( + @{ParameterName = "ParamWithArgumentCompleter"; ExpectedType = [String]; ExpectedValue = "./.git"} + @{ParameterName = "ParamWithArgumentCompleter"; ExpectedType = [DateTime]; ExpectedValue = "./.git"} + @{ParameterName = "ParamWithArgumentCompleter"; ExpectedType = [DateTime]; ExpectedValue = ""} + + ) { + param($ParameterName, $ExpectedType, $ExpectedValue) + { Get-Command "Invoke-DummyFunction" | Should -Not -HaveParameter $ParameterName -Type $ExpectedType -DefaultValue $ExpectedValue -HasArgumentCompleter } | Verify-AssertionFailed + } + } + + It "returns the correct assertion message when parameter ParamWithNotNullOrEmptyValidation is not mandatory" { + $err = { Get-Command "Invoke-DummyFunction" | Should -Not -HaveParameter ParamWithNotNullOrEmptyValidation -Type [DateTime] } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal "Expected command Invoke-DummyFunction to not have a parameter ParamWithNotNullOrEmptyValidation, not of type [System.DateTime], but it was of type [System.DateTime]." + } + + It "returns the correct assertion message when parameter ParamWithNotNullOrEmptyValidation is not mandatory, of the wrong type and has a different default value than expected" { + $err = { Get-Command "Invoke-DummyFunction" | Should -Not -HaveParameter MandatoryParam -Mandatory -Type [Object] -DefaultValue "" -Because 'of reasons' } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal "Expected command Invoke-DummyFunction to not have a parameter MandatoryParam, which is not mandatory, not of type [System.Object] and the default value not to be , because of reasons, but it was mandatory, it was of type [System.Object] and the default value was ." + } + + if ($PSVersionTable.PSVersion.Major -ge 5) { + It "returns the correct assertion message when parameter ParamWithNotNullOrEmptyValidation is not mandatory, of the wrong type, has a different default value than expected and has no ArgumentCompleter" { + $err = { Get-Command "Invoke-DummyFunction" | Should -Not -HaveParameter ParamWithArgumentCompleter -Type [String] -DefaultValue "./.git" -HasArgumentCompleter -Because 'of reasons' } | Verify-AssertionFailed + $err.Exception.Message | Verify-Equal "Expected command Invoke-DummyFunction to not have a parameter ParamWithArgumentCompleter, not of type [System.String], the default value not to be './.git' and has ArgumentCompletion, because of reasons, but it was of type [System.String], the default value was './.git' and has ArgumentCompletion." + } + } + } +} diff --git a/Functions/Assertions/HaveParameter.ps1 b/Functions/Assertions/HaveParameter.ps1 new file mode 100644 index 000000000..f125c4aa0 --- /dev/null +++ b/Functions/Assertions/HaveParameter.ps1 @@ -0,0 +1,229 @@ +function Should-HaveParameter ( + $ActualValue, + [String] $ParameterName, + $Type, + [String]$DefaultValue, + [Switch]$Mandatory, + [Switch]$HasArgumentCompleter, + [Switch]$Negate, + [String]$Because ) { + <# + .SYNOPSIS + Asserts that a command has the expected parameter. + + .EXAMPLE + Get-Command "Invoke-WebRequest" | Should -HaveParameter Uri -Mandatory + This test passes, because it expected the parameter URI to exist and to + be mandatory. + .NOTES + The attribute [ArgumentCompleter] was added with PSv5. Previouse this + assertion will not be able to use the -HasArgumentCompleter parameter + if the attribute does not exist. + #> + + if ($null -eq $ActualValue -or $ActualValue -isnot [Management.Automation.CommandInfo]) { + throw "Input value must be non-null CommandInfo object. You can get one by calling Get-Command." + } + + if ($null -eq $ParameterName) { + throw "The ParameterName can't be empty" + } + + #region HelperFunctions + function Join-And ($Items, $Threshold = 2) { + + if ($null -eq $items -or $items.count -lt $Threshold) { + $items -join ', ' + } + else { + $c = $items.count + ($items[0..($c - 2)] -join ', ') + ' and ' + $items[-1] + } + } + + function Add-SpaceToNonEmptyString ([string]$Value) { + if ($Value) { + " $Value" + } + } + + function Get-ParameterInfo { + param( + [Parameter( Mandatory = $true )] + [Management.Automation.CommandInfo]$Command + ) + <# + .SYNOPSIS + Use Tokenize to get information about the parameter block of a command + .DESCRIPTION + In order to get information about the parameter block of a command, + several tools can be used (Get-Command, AST, etc). + In order to get the default value of a parameter, AST is the easiest + way to go; but AST was only introduced with PSv3. + This function creates an object with information about parameters + using the Tokenize + .NOTES + Author: Chris Dent + #> + + function Get-TokenGroup { + param( + [Parameter( Mandatory = $true )] + [System.Management.Automation.PSToken[]]$tokens + ) + $i = $j = 0 + do { + $token = $tokens[$i] + if ($token.Type -eq 'GroupStart') { + $j++ + } + if ($token.Type -eq 'GroupEnd') { + $j-- + } + if (-not $token.PSObject.Properties.Item('Depth')) { + $token | Add-Member Depth -MemberType NoteProperty -Value $j + } + $token + + $i++ + } until ($j -eq 0 -or $i -ge $tokens.Count) + } + + $errors = $null + $tokens = [System.Management.Automation.PSParser]::Tokenize($Command.Definition, [Ref]$errors) + + # Find param block + $start = $tokens.IndexOf(($tokens | Where-Object { $_.Content -eq 'param' } | Select-Object -First 1)) + 1 + $paramBlock = Get-TokenGroup $tokens[$start..($tokens.Count - 1)] + + for ($i = 0; $i -lt $paramBlock.Count; $i++) { + $token = $paramBlock[$i] + + if ($token.Depth -eq 1 -and $token.Type -eq 'Variable') { + $paramInfo = New-Object PSObject -Property @{ + Name = $token.Content + } | Select-Object Name, Type, DefaultValue, DefaultValueType + + if ($paramBlock[$i + 1].Content -ne ',') { + $value = $paramBlock[$i + 2] + if ($value.Type -eq 'GroupStart') { + $tokenGroup = Get-TokenGroup $paramBlock[($i + 2)..($paramBlock.Count - 1)] + $paramInfo.DefaultValue = [String]::Join('', ($tokenGroup | ForEach-Object { $_.Content })) + $paramInfo.DefaultValueType = 'Expression' + } + else { + $paramInfo.DefaultValue = $value.Content + $paramInfo.DefaultValueType = $value.Type + } + } + if ($paramBlock[$i - 1].Type -eq 'Type') { + $paramInfo.Type = $paramBlock[$i - 1].Content + } + $paramInfo + } + } + } + + if ($Type -is [string]) { + # parses type that is provided as a string in brackets (such as [int]) + $parsedType = ($Type -replace '^\[(.*)\]$', '$1') -as [Type] + if ($null -eq $parsedType) { + throw [ArgumentException]"Could not find type [$ParsedType]. Make sure that the assembly that contains that type is loaded." + } + + $Type = $parsedType + } + #endregion HelperFunctions + + $buts = @() + $filters = @() + + $null = $ActualValue.Parameters # necessary for PSv2 + $hasKey = $ActualValue.Parameters.PSBase.ContainsKey($ParameterName) + $filters += "to$(if ($Negate) {" not"}) have a parameter $ParameterName" + + if (-not $Negate -and -not $hasKey) { + $buts += "the parameter is missing" + } + elseif ($Negate -and -not $hasKey) { + return New-Object PSObject -Property @{ Succeeded = $true } + } + elseif ($Negate -and $hasKey -and -not ($Mandatory -or $Type -or $DefaultValue -or $HasArgumentCompleter)) { + $buts += "the parameter exists" + } + else { + $attributes = $ActualValue.Parameters[$ParameterName].Attributes + + if ($Mandatory) { + $testMandatory = $attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] -and $_.Mandatory } + $filters += "which is$(if ($Negate) {" not"}) mandatory" + + if (-not $Negate -and -not $testMandatory) { + $buts += "it wasn't mandatory" + } + elseif ($Negate -and $testMandatory) { + $buts += "it was mandatory" + } + } + + if ($Type) { + # This block is not using `Format-Nicely`, as in PSv2 the output differs. Eg: + # PS2> [System.DateTime] + # PS5> [datetime] + [type]$actualType = $ActualValue.Parameters[$ParameterName].ParameterType + $testType = ($Type -eq $actualType) + $filters += "$(if ($Negate) {"not "})of type [$($Type.FullName)]" + + if (-not $Negate -and -not $testType) { + $buts += "it was of type [$($actualType.FullName)]" + } + elseif ($Negate -and $testType) { + $buts += "it was of type [$($Type.FullName)]" + } + } + + if ($PSBoundParameters.Keys -contains "DefaultValue") { + $parameterMetadata = Get-ParameterInfo $ActualValue | Where-Object { $_.Name -eq $ParameterName } + $actualDefault = if ($parameterMetadata.DefaultValue) { $parameterMetadata.DefaultValue } else { "" } + $testDefault = ($actualDefault -eq $DefaultValue) + $filters += "the default value$(if ($Negate) {" not"}) to be $(Format-Nicely $DefaultValue)" + + if (-not $Negate -and -not $testDefault) { + $buts += "the default value was $(Format-Nicely $actualDefault)" + } + elseif ($Negate -and $testDefault) { + $buts += "the default value was $(Format-Nicely $DefaultValue)" + } + } + + if ($HasArgumentCompleter) { + $testArgumentCompleter = $attributes | Where-Object {$_ -is [ArgumentCompleter]} + $filters += "has ArgumentCompletion" + + if (-not $Negate -and -not $testArgumentCompleter) { + $buts += "has no ArgumentCompletion" + } + elseif ($Negate -and $testArgumentCompleter) { + $buts += "has ArgumentCompletion" + } + } + } + + if ($buts.Count -ne 0) { + $filter = Add-SpaceToNonEmptyString ( Join-And $filters -Threshold 3 ) + $but = Join-And $buts + $failureMessage = "Expected command $($ActualValue.Name)$filter,$(Format-Because $Because) but $but." + + return New-Object PSObject -Property @{ + Succeeded = $false + FailureMessage = $failureMessage + } + } + else { + return New-Object PSObject -Property @{ Succeeded = $true } + } +} + +Add-AssertionOperator -Name HaveParameter ` + -InternalName Should-HaveParameter ` + -Test ${function:Should-HaveParameter}