Tests/Private/PivTool.Tests.ps1
|
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'Test fixture values, not real secrets; SecureString-typed parameters require a real SecureString even in unit tests.')] param() BeforeDiscovery { Import-Module (Resolve-Path "$PSScriptRoot\..\..\Posh-YBKPIV.psd1") -Force } BeforeAll { Import-Module (Resolve-Path "$PSScriptRoot\..\..\Posh-YBKPIV.psd1") -Force } AfterAll { Remove-Module Posh-YBKPIV -ErrorAction SilentlyContinue } InModuleScope Posh-YBKPIV { BeforeAll { $script:Wrapping = [PSCustomObject]@{ options = [PSCustomObject]@{ slot = [PSCustomObject]@{ cliFlag = '--slot'; type = 'string'; allowedValues = @('9a', '9c', '9d', '9e', 'f9'); allowedPattern = '^(8[2-9]|9[0-5])$' } pin = [PSCustomObject]@{ cliFlag = '--pin'; type = 'secureString' } compress = [PSCustomObject]@{ cliFlag = '--compress'; type = 'switch' } validDays = [PSCustomObject]@{ cliFlag = '--valid-days'; type = 'int'; default = 365 } algorithm = [PSCustomObject]@{ cliFlag = '--algorithm'; type = 'string'; allowedValues = @('RSA2048', 'ECCP256') } } actions = [PSCustomObject]@{ 'generate' = [PSCustomObject]@{ cliAction = 'generate'; requiredOptions = @('slot'); optionalOptions = @('algorithm', 'compress') } 'verify-pin' = [PSCustomObject]@{ cliAction = 'verify-pin'; requiredOptions = @('pin'); optionalOptions = @() } } } } Describe 'ConvertTo-PWSHYBKPIVParameterName' -Tag Unit { It 'Capitalizes the first letter of a camelCase name' { ConvertTo-PWSHYBKPIVParameterName -Name 'pinPolicy' | Should -Be 'PinPolicy' } It 'Handles single-word names' { ConvertTo-PWSHYBKPIVParameterName -Name 'reader' | Should -Be 'Reader' } It 'Handles names with an embedded acronym-like segment' { ConvertTo-PWSHYBKPIVParameterName -Name 'objectId' | Should -Be 'ObjectId' } It 'Prefixes "Tool" onto names colliding with a PowerShell common parameter' { ConvertTo-PWSHYBKPIVParameterName -Name 'verbose' | Should -Be 'ToolVerbose' } } Describe 'ConvertFrom-PWSHYBKPIVSecureString' -Tag Unit { It 'Returns the plain-text value of a SecureString' { $secure = ConvertTo-SecureString -String 'my-secret-pin' -AsPlainText -Force ConvertFrom-PWSHYBKPIVSecureString -SecureString $secure | Should -Be 'my-secret-pin' } } Describe 'Get-PWSHYBKPIVActionParameter' -Tag Unit { It 'Throws on an unknown action' { { Get-PWSHYBKPIVActionParameter -Action 'not-a-real-action' -CmdletWrapping $script:Wrapping } | Should -Throw -ExpectedMessage '*not-a-real-action*' } It 'Builds one dynamic parameter per required and optional option' { $dict = Get-PWSHYBKPIVActionParameter -Action 'generate' -CmdletWrapping $script:Wrapping $dict.Keys | Sort-Object | Should -Be @('Algorithm', 'Compress', 'Slot') } It 'Marks required options as Mandatory and optional options as not' { $dict = Get-PWSHYBKPIVActionParameter -Action 'generate' -CmdletWrapping $script:Wrapping $dict['Slot'].Attributes[0].Mandatory | Should -BeTrue $dict['Algorithm'].Attributes[0].Mandatory | Should -BeFalse } It 'Maps switch/secureString/default types to the correct .NET type' { $generateDict = Get-PWSHYBKPIVActionParameter -Action 'generate' -CmdletWrapping $script:Wrapping $generateDict['Compress'].ParameterType | Should -Be ([switch]) $pinDict = Get-PWSHYBKPIVActionParameter -Action 'verify-pin' -CmdletWrapping $script:Wrapping $pinDict['Pin'].ParameterType | Should -Be ([System.Security.SecureString]) } It 'Builds a ValidateSet when only allowedValues is present' { $dict = Get-PWSHYBKPIVActionParameter -Action 'generate' -CmdletWrapping $script:Wrapping $validateSet = $dict['Algorithm'].Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateSetAttribute] } $validateSet.ValidValues | Should -Be @('RSA2048', 'ECCP256') } It 'Combines allowedValues and allowedPattern with OR semantics via ValidateScript' { $dict = Get-PWSHYBKPIVActionParameter -Action 'generate' -CmdletWrapping $script:Wrapping $validateScript = $dict['Slot'].Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateScriptAttribute] } $validateScript | Should -Not -BeNullOrEmpty $results = @('9a', '83', 'zz') | ForEach-Object -Process $validateScript.ScriptBlock $results | Should -Be @($true, $true, $false) } } Describe 'Invoke-PWSHYBKPIVTool' -Tag Unit { BeforeEach { $script:FakeToolOk = Join-Path -Path $TestDrive -ChildPath 'fake-tool-ok.cmd' Set-Content -Path $script:FakeToolOk -Value "@echo off`r`necho %*`r`nexit /b 0" $script:FakeToolFail = Join-Path -Path $TestDrive -ChildPath 'fake-tool-fail.cmd' Set-Content -Path $script:FakeToolFail -Value "@echo off`r`necho tool error output`r`nexit /b 1" } It 'Throws on an unknown action' { { Invoke-PWSHYBKPIVTool -ExePath $script:FakeToolOk -Action 'not-a-real-action' -CmdletWrapping $script:Wrapping -BoundParameters @{} } | Should -Throw -ExpectedMessage '*not-a-real-action*' } It 'Throws when a required option is missing from BoundParameters' { { Invoke-PWSHYBKPIVTool -ExePath $script:FakeToolOk -Action 'generate' -CmdletWrapping $script:Wrapping -BoundParameters @{} } | Should -Throw -ExpectedMessage '*-Slot*' } It 'Builds --action plus the required and bound optional flags' { $result = Invoke-PWSHYBKPIVTool -ExePath $script:FakeToolOk -Action 'generate' -CmdletWrapping $script:Wrapping -BoundParameters @{ Slot = '9a'; Algorithm = 'ECCP256' } $result.ExitCode | Should -Be 0 ($result.Output -join ' ') | Should -Match '--action generate --slot 9a --algorithm ECCP256' } It 'Adds a switch flag only when the bound value is true' { $result = Invoke-PWSHYBKPIVTool -ExePath $script:FakeToolOk -Action 'generate' -CmdletWrapping $script:Wrapping -BoundParameters @{ Slot = '9a'; Compress = $true } ($result.Output -join ' ') | Should -Match '--compress' } It 'Omits the switch flag when the bound value is false' { $result = Invoke-PWSHYBKPIVTool -ExePath $script:FakeToolOk -Action 'generate' -CmdletWrapping $script:Wrapping -BoundParameters @{ Slot = '9a'; Compress = $false } ($result.Output -join ' ') | Should -Not -Match '--compress' } It 'Converts a SecureString option to plain text before adding it to the argument list' { $securePin = ConvertTo-SecureString -String '123456' -AsPlainText -Force $result = Invoke-PWSHYBKPIVTool -ExePath $script:FakeToolOk -Action 'verify-pin' -CmdletWrapping $script:Wrapping -BoundParameters @{ Pin = $securePin } ($result.Output -join ' ') | Should -Match '--pin 123456' } It 'Throws with the tool output when the exit code is non-zero' { { Invoke-PWSHYBKPIVTool -ExePath $script:FakeToolFail -Action 'verify-pin' -CmdletWrapping $script:Wrapping -BoundParameters @{ Pin = (ConvertTo-SecureString -String 'x' -AsPlainText -Force) } } | Should -Throw -ExpectedMessage '*tool error output*' } } } |