From f49f84264110ef544239ab4639dffd883150deb5 Mon Sep 17 00:00:00 2001 From: Benjamin Fuchs Date: Thu, 16 Jan 2025 09:43:44 +0100 Subject: [PATCH] Fix handling of conflicting dynamic parameters (#2592) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix handling of conflicting dynamic parameters * Apply suggestions from code review --------- Co-authored-by: Jakub Jareš --- src/functions/Mock.ps1 | 62 +++++++++++++++++++++++++++-------- tst/functions/Mock.Tests.ps1 | 63 ++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 13 deletions(-) diff --git a/src/functions/Mock.ps1 b/src/functions/Mock.ps1 index 7f299b532..2c0e04b1e 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 = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() + $conflictingParams = Get-ConflictingParameterNames + + foreach ($paramName in $DynamicParams.Keys) { + $dynamicParam = $DynamicParams[$paramName] + + if ($conflictingParams -contains $paramName) { + $newName = "_$paramName" + $dynamicParam.Name = $newName + + $aliasAttribute = [System.Management.Automation.AliasAttribute]::new($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..575b8ee56 100644 --- a/tst/functions/Mock.Tests.ps1 +++ b/tst/functions/Mock.Tests.ps1 @@ -3160,3 +3160,66 @@ 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 + } + } +}