diff --git a/src/private/cli/GetNovaCliInvocationContext.ps1 b/src/private/cli/GetNovaCliInvocationContext.ps1 index c2f1d28a..58bd764a 100644 --- a/src/private/cli/GetNovaCliInvocationContext.ps1 +++ b/src/private/cli/GetNovaCliInvocationContext.ps1 @@ -1,41 +1,41 @@ -function Get-NovaCliBaseInvocationData { +function Get-NovaCliResolvedInvocationContext { [CmdletBinding()] param( + [Parameter(Mandatory)][string]$Command, + [AllowEmptyCollection()][string[]]$Arguments = @(), [Parameter(Mandatory)][hashtable]$CommonParameters, [Parameter(Mandatory)][hashtable]$MutatingCommonParameters, [Parameter(Mandatory)][string]$ModuleName, - [Parameter(Mandatory)][bool]$WhatIfEnabled + [Parameter(Mandatory)][bool]$WhatIfEnabled, + [Parameter(Mandatory)][bool]$CliConfirmEnabled, + [AllowNull()][pscustomobject]$HelpRequest = $null ) return [pscustomobject]@{ + Command = $Command + Arguments = @($Arguments) CommonParameters = $CommonParameters MutatingCommonParameters = $MutatingCommonParameters + IsHelpRequest = $null -ne $HelpRequest + HelpRequest = $HelpRequest ModuleName = $ModuleName WhatIfEnabled = $WhatIfEnabled + CliConfirmEnabled = $CliConfirmEnabled } } -function Get-NovaCliHelpInvocationContext { +function Get-NovaCliInvocationWhatIfState { [CmdletBinding()] param( - [Parameter(Mandatory)][pscustomobject]$HelpRequest, - [Parameter(Mandatory)][pscustomobject]$BaseInvocationData + [switch]$WhatIfEnabled, + [Parameter(Mandatory)][hashtable]$MutatingCommonParameters, + [switch]$RoutingWhatIfEnabled ) - return [pscustomobject]@{ - Command = $HelpRequest.Command - Arguments = @() - CommonParameters = $BaseInvocationData.CommonParameters - MutatingCommonParameters = $BaseInvocationData.MutatingCommonParameters - IsHelpRequest = $true - HelpRequest = $HelpRequest - ModuleName = $BaseInvocationData.ModuleName - WhatIfEnabled = $BaseInvocationData.WhatIfEnabled - CliConfirmEnabled = $false - } + return $WhatIfEnabled.IsPresent -or $RoutingWhatIfEnabled.IsPresent -or $MutatingCommonParameters.ContainsKey('WhatIf') } -function Get-NovaCliConfirmState { +function Get-NovaCliInvocationConfirmState { [CmdletBinding()] param( [Parameter(Mandatory)][hashtable]$MutatingCommonParameters @@ -50,27 +50,6 @@ function Get-NovaCliConfirmState { return $cliConfirmEnabled } -function Get-NovaCliRoutedInvocationContext { - [CmdletBinding()] - param( - [Parameter(Mandatory)][pscustomobject]$RoutingState, - [Parameter(Mandatory)][pscustomobject]$BaseInvocationData, - [Parameter(Mandatory)][bool]$CliConfirmEnabled - ) - - return [pscustomobject]@{ - Command = $RoutingState.Command - Arguments = $RoutingState.Arguments - CommonParameters = $BaseInvocationData.CommonParameters - MutatingCommonParameters = $BaseInvocationData.MutatingCommonParameters - IsHelpRequest = $false - HelpRequest = $null - ModuleName = $BaseInvocationData.ModuleName - WhatIfEnabled = $BaseInvocationData.WhatIfEnabled - CliConfirmEnabled = $CliConfirmEnabled - } -} - function Get-NovaCliInvocationContext { [CmdletBinding()] param( @@ -85,14 +64,14 @@ function Get-NovaCliInvocationContext { Assert-NovaCliArgumentSyntax -Arguments (@($InvocationRequest.Command) + $normalizedArguments) $helpRequest = Get-NovaCliHelpRequest -Command $InvocationRequest.Command -Arguments $normalizedArguments $moduleName = $ExecutionContext.SessionState.Module.Name - $helpBaseInvocationData = Get-NovaCliBaseInvocationData -CommonParameters $commonParameters -MutatingCommonParameters $mutatingCommonParameters -ModuleName $moduleName -WhatIfEnabled:($WhatIfEnabled.IsPresent -or $mutatingCommonParameters.ContainsKey('WhatIf')) + $whatIfState = Get-NovaCliInvocationWhatIfState -WhatIfEnabled:$WhatIfEnabled -MutatingCommonParameters $mutatingCommonParameters if ($null -ne $helpRequest) { - return Get-NovaCliHelpInvocationContext -HelpRequest $helpRequest -BaseInvocationData $helpBaseInvocationData + return Get-NovaCliResolvedInvocationContext -Command $helpRequest.Command -Arguments @() -CommonParameters $commonParameters -MutatingCommonParameters $mutatingCommonParameters -ModuleName $moduleName -WhatIfEnabled:$whatIfState -CliConfirmEnabled:$false -HelpRequest $helpRequest } $routingState = Get-NovaCliArgumentRoutingState -Command $InvocationRequest.Command -Arguments $normalizedArguments - $cliConfirmEnabled = Get-NovaCliConfirmState -MutatingCommonParameters $mutatingCommonParameters + $cliConfirmEnabled = Get-NovaCliInvocationConfirmState -MutatingCommonParameters $mutatingCommonParameters if ( $routingState.ForwardedParameters.ContainsKey('Verbose')) { $commonParameters.Verbose = $true @@ -100,7 +79,7 @@ function Get-NovaCliInvocationContext { $mutatingCommonParameters = Merge-NovaCliParameterSet -BaseParameters $mutatingCommonParameters -AdditionalParameters $routingState.ForwardedParameters $cliConfirmEnabled = $cliConfirmEnabled -or $routingState.CliConfirmEnabled - $routedBaseInvocationData = Get-NovaCliBaseInvocationData -CommonParameters $commonParameters -MutatingCommonParameters $mutatingCommonParameters -ModuleName $moduleName -WhatIfEnabled:($WhatIfEnabled.IsPresent -or $routingState.WhatIfEnabled -or $mutatingCommonParameters.ContainsKey('WhatIf')) + $whatIfState = Get-NovaCliInvocationWhatIfState -WhatIfEnabled:$WhatIfEnabled -MutatingCommonParameters $mutatingCommonParameters -RoutingWhatIfEnabled:$routingState.WhatIfEnabled - return Get-NovaCliRoutedInvocationContext -RoutingState $routingState -BaseInvocationData $routedBaseInvocationData -CliConfirmEnabled:$cliConfirmEnabled + return Get-NovaCliResolvedInvocationContext -Command $routingState.Command -Arguments $routingState.Arguments -CommonParameters $commonParameters -MutatingCommonParameters $mutatingCommonParameters -ModuleName $moduleName -WhatIfEnabled:$whatIfState -CliConfirmEnabled:$cliConfirmEnabled } diff --git a/src/private/cli/InvokeNovaCliCommandRoute.ps1 b/src/private/cli/InvokeNovaCliCommandRoute.ps1 index 6c34362f..0e5f1cbc 100644 --- a/src/private/cli/InvokeNovaCliCommandRoute.ps1 +++ b/src/private/cli/InvokeNovaCliCommandRoute.ps1 @@ -1,18 +1,3 @@ -function Get-NovaCliCommandHandler { - [CmdletBinding()] - param( - [Parameter(Mandatory)][hashtable]$CommandHandlerMap, - [Parameter(Mandatory)][string]$Command - ) - - $commandHandler = $CommandHandlerMap[$Command] - if ($null -eq $commandHandler) { - Stop-NovaOperation -Message "Unknown command: <$Command> | Use 'nova --help' to see available commands." -ErrorId 'Nova.Validation.UnknownCliCommand' -Category InvalidArgument -TargetObject $Command - } - - return $commandHandler -} - function Confirm-NovaCliRoutedCommand { [CmdletBinding()] param( @@ -156,7 +141,7 @@ function Invoke-NovaCliCommandRoute { return Get-NovaCliHelp } default { - return Get-NovaCliCommandHandler -CommandHandlerMap @{} -Command $command + Stop-NovaOperation -Message "Unknown command: <$command> | Use 'nova --help' to see available commands." -ErrorId 'Nova.Validation.UnknownCliCommand' -Category InvalidArgument -TargetObject $command } } } diff --git a/tests/CoverageGaps.Cli.Tests.ps1 b/tests/CoverageGaps.Cli.Tests.ps1 index fd8c6b72..cf443bb6 100644 --- a/tests/CoverageGaps.Cli.Tests.ps1 +++ b/tests/CoverageGaps.Cli.Tests.ps1 @@ -115,6 +115,7 @@ Describe 'Coverage gaps for CLI and installed-version internals' { } ModuleName = 'NovaModuleTools' WhatIfEnabled = $false + CliConfirmEnabled = $false } Mock Get-NovaCliCommandHelp {'package-help'} @@ -212,14 +213,33 @@ Describe 'Coverage gaps for CLI and installed-version internals' { } } - It 'Get-NovaCliCommandHandler returns the mapped handler for a known command' { + It 'Invoke-NovaCliCommandRoute throws the stable unknown-command error for unmapped commands' { InModuleScope $script:moduleName { - $expectedHandler = [pscustomobject]@{Name = 'publish-handler'} - $handlerMap = @{publish = $expectedHandler} + $invocationContext = [pscustomobject]@{ + Command = 'banana' + Arguments = @() + CommonParameters = @{} + MutatingCommonParameters = @{} + IsHelpRequest = $false + HelpRequest = $null + ModuleName = 'NovaModuleTools' + WhatIfEnabled = $false + CliConfirmEnabled = $false + } - $result = Get-NovaCliCommandHandler -CommandHandlerMap $handlerMap -Command 'publish' + $thrown = $null + try { + Invoke-NovaCliCommandRoute -InvocationContext $invocationContext + } + catch { + $thrown = $_ + } - $result | Should -Be $expectedHandler + $thrown | Should -Not -BeNullOrEmpty + $thrown.Exception.Message | Should -Be "Unknown command: | Use 'nova --help' to see available commands." + $thrown.FullyQualifiedErrorId | Should -Be 'Nova.Validation.UnknownCliCommand' + $thrown.CategoryInfo.Category | Should -Be ([System.Management.Automation.ErrorCategory]::InvalidArgument) + $thrown.TargetObject | Should -Be 'banana' } } @@ -293,6 +313,21 @@ Describe 'Coverage gaps for CLI and installed-version internals' { } } + It 'Get-NovaCliInvocationContext strips raw Confirm from mutating parameters while preserving CLI confirm intent' { + InModuleScope $script:moduleName { + $invocationRequest = [pscustomobject]@{ + Command = 'publish' + BoundParameters = @{Command = 'publish'; Arguments = @('--confirm'); Confirm = $true} + Arguments = @('--confirm') + } + $result = Get-NovaCliInvocationContext -InvocationRequest $invocationRequest + + $result.Command | Should -Be 'publish' + $result.CliConfirmEnabled | Should -BeTrue + $result.MutatingCommonParameters.ContainsKey('Confirm') | Should -BeFalse + } + } + It 'Get-NovaCliArgumentRoutingState enables CLI confirmation only for supported mutating commands' { InModuleScope $script:moduleName { $supportedCommandList = @('build', 'test', 'package', 'deploy', 'bump', 'update', 'notification', 'publish', 'release')