From bff78d76291ee28094dc8cb7daffdab65a96326a Mon Sep 17 00:00:00 2001 From: Benjamin Fuchs Date: Fri, 20 Dec 2024 10:55:35 +0100 Subject: [PATCH] Fix handling of conflicting dynamic parameters --- src/functions/Mock.ps1 | 62 +++++++++++++++++++++++++------- tst/functions/Mock.Tests.ps1 | 69 ++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 13 deletions(-) diff --git a/src/functions/Mock.ps1 b/src/functions/Mock.ps1 index 7f299b532..b7d7ba42e 100644 --- a/src/functions/Mock.ps1 +++ b/src/functions/Mock.ps1 @@ -1339,21 +1339,20 @@ function Get-ContextToDefine { } else { # the parameter is not defined in the parameter set, - # it is probably dynamic, let's see if I can get away with just adding - # it to the list of stuff to define + # it is probably dynamic, try remove "_" since the conflicting names + # are already handled to properly print the debug message - $name = if ($param.Key -in $script:ConflictingParameterNames) { - if ($PesterPreference.Debug.WriteDebugMessages.Value) { - Write-PesterDebugMessage -Scope Mock -Message "! Variable `$$($param.Key) is a built-in variable, rewriting it to `$_$($param.Key). Use the version with _ in your -ParameterFilter." + if ($param.Key.StartsWith('_')) { + $originalName = $param.Key.TrimStart('_') + if ($originalName -in $script:ConflictingParameterNames) { + if ($PesterPreference.Debug.WriteDebugMessages.Value) { + Write-PesterDebugMessage -Scope Mock -Message "! Variable `$$($originalName) is a built-in variable, rewriting it to `$_$($originalName). Use the version with _ in your -ParameterFilter." + } } - "_$($param.Key)" - } - else { - $param.Key } - if (-not $r.ContainsKey($name)) { - $r.Add($name, $param.Value) + if (-not $r.ContainsKey($param.Key)) { + $r.Add($param.Key, $param.Value) } } } @@ -1457,13 +1456,50 @@ function Get-MockDynamicParameter { switch ($PSCmdlet.ParameterSetName) { 'Cmdlet' { - Get-DynamicParametersForCmdlet -CmdletName $CmdletName -Parameters $Parameters + $dynamicParams = Get-DynamicParametersForCmdlet -CmdletName $CmdletName -Parameters $Parameters } 'Function' { - Get-DynamicParametersForMockedFunction -DynamicParamScriptBlock $DynamicParamScriptBlock -Parameters $Parameters -Cmdlet $Cmdlet + $dynamicParams = Get-DynamicParametersForMockedFunction -DynamicParamScriptBlock $DynamicParamScriptBlock -Parameters $Parameters -Cmdlet $Cmdlet + } + } + + if ($null -eq $dynamicParams) { + return + } + + Repair-ConflictingDynamicParameters -DynamicParams $dynamicParams +} + +function Repair-ConflictingDynamicParameters { + [OutputType([System.Management.Automation.RuntimeDefinedParameterDictionary])] + param ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.RuntimeDefinedParameterDictionary] + $DynamicParams + ) + + $repairedDynamicParams = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary + $conflictingParams = Get-ConflictingParameterNames + + foreach ($paramName in $DynamicParams.Keys) { + $dynamicParam = $DynamicParams[$paramName] + + if ($conflictingParams -contains $paramName) { + $newName = "_$paramName" + $dynamicParam.Name = $newName + + $aliasAttribute = New-Object System.Management.Automation.AliasAttribute -ArgumentList $paramName + $dynamicParam.Attributes.Add($aliasAttribute) + + $repairedDynamicParams[$newName] = $dynamicParam + } + else { + $repairedDynamicParams[$paramName] = $dynamicParam } } + + return $repairedDynamicParams } function Get-DynamicParametersForCmdlet { diff --git a/tst/functions/Mock.Tests.ps1 b/tst/functions/Mock.Tests.ps1 index b3853dca6..30ef09907 100644 --- a/tst/functions/Mock.Tests.ps1 +++ b/tst/functions/Mock.Tests.ps1 @@ -3160,3 +3160,72 @@ Describe 'Mocking with nested Pester runs' { Get-Command Get-ChildItem | Should -Not -Be 2 } } + +Describe 'Usage of Alias in DynamicParams' { + # https://github.com/pester/Pester/issues/1274 + + BeforeAll { + function New-DynamicAttr($ParamDictionary, $Name, $Alias = $null) { + $attr = New-Object -Type ` + System.Management.Automation.ParameterAttribute + $attr.Mandatory = $false + $attr.ParameterSetName = '__AllParameterSets' + $attributeCollection = New-Object ` + -Type System.Collections.ObjectModel.Collection[System.Attribute] + $attributeCollection.Add($attr) + + if ($null -ne $Alias) { + $attr = New-Object -Type ` + System.Management.Automation.AliasAttribute -ArgumentList @($Alias) + $attributeCollection.Add($attr) + } + + $dynParam1 = New-Object -Type ` + System.Management.Automation.RuntimeDefinedParameter($Name, [string], + $attributeCollection) + + $ParamDictionary.Add($Name, $dynParam1) + } + + function Test-DynamicParam { + [CmdletBinding()] + param( + [String]$Name + ) + + dynamicparam { + if ($Name.StartsWith("Hello")) { + $paramDictionary = New-Object ` + -Type System.Management.Automation.RuntimeDefinedParameterDictionary + New-DynamicAttr -ParamDictionary $paramDictionary -Name "PSEdition" + + return $paramDictionary + } + } + + process { + if ($PSBoundParameters.PSEdition) { + Write-Host "PSEdition value: $($PSBoundParameters.PSEdition)" + } + } + } + } + + Context 'Mocking with ParameterFilter' { + It 'Mocks Test-DynamicParam with PSEdition set to Desktop' { + Mock Test-DynamicParam { "World" } -ParameterFilter { $_PSEdition -eq 'Desktop' } + + Test-DynamicParam -Name "Hello" -PSEdition 'Desktop' | Should -Be 'World' + } + } + + Context 'Validating Mock Invocation' { + It 'Invokes Test-DynamicParam with correct parameters' { + Mock Test-DynamicParam { "World" } + + Test-DynamicParam -Name "Hello" -PSEdition 'Desktop' | Should -Be 'World' + + Should -Invoke Test-DynamicParam -Exactly 1 -Scope It + } + } +}