Files
PSProxmoxVE/tests/PSProxmoxVE.Tests/Connection/Connect-PveServer.Tests.ps1
T
goodolclint-claude[bot] c8a7e809bb fix: take the API token as a SecureString and stop exposing session credentials (#240)
Connect-PveServer -ApiToken was a plain string, so the token landed verbatim
in PSReadLine history and any transcript, and PveSession published ApiToken,
Ticket and CsrfToken as public getters, so Format-List *, ConvertTo-Json and
Export-Clixml of a session printed them.

The parameter is now a SecureString, extracted at the cmdlet boundary with the
Marshal/ZeroFree pattern ADR 0002 established for passwords. A plain string
still binds for one minor release through an argument transformation, and the
cmdlet warns that the string form goes away in the next major; the marker
lives in a ConditionalWeakTable keyed on the converted instance, so an
abandoned binding neither retains the secret nor mislabels a later call.

The three session getters become internal. PveHttpClient is in the same
assembly and the xUnit project already has InternalsVisibleTo, so the header
construction and its tests are unchanged.

Refs ADR 0028, issue #147.

Co-authored-by: goodolclint-claude[bot] <323206664+goodolclint-claude[bot]@users.noreply.github.com>
2026-09-03 21:34:11 +00:00

158 lines
6.7 KiB
PowerShell

#Requires -Module Pester
<#
.SYNOPSIS
Pester 5 tests for Connect-PveServer.
All tests are fully offline — no live Proxmox VE target is required.
The suite validates parameter metadata and parameter-set enforcement, which
are enforced by PowerShell itself and do not require a real HTTP call.
#>
BeforeAll {
# Resolve the built DLL relative to the repository root.
# Adjust the path if your build output directory differs.
. $PSScriptRoot/../_TestHelper.ps1
}
Describe 'Connect-PveServer' {
Context 'Parameter validation — required parameters' {
It 'Server should be Mandatory' {
$param = (Get-Command 'Connect-PveServer').Parameters['Server']
$isMandatory = $param.ParameterSets.Values | Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'Should require either Credential or ApiToken (both parameter sets exist)' {
$cmd = Get-Command 'Connect-PveServer'
$cmd.ParameterSets.Count | Should -BeGreaterOrEqual 2
}
}
Context 'Parameter validation — mutually exclusive parameter sets' {
It 'Should not allow both Credential and ApiToken together' {
$securePass = ConvertTo-SecureString 'hunter2' -AsPlainText -Force
$cred = [System.Management.Automation.PSCredential]::new('root@pam', $securePass)
{
Connect-PveServer `
-Server 'pve.example.com' `
-Credential $cred `
-ApiToken (ConvertTo-SecureString 'root@pam!mytoken=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' -AsPlainText -Force) `
-ErrorAction Stop
} | Should -Throw
}
}
Context 'ApiToken accepts a SecureString and warns on a plain string' {
BeforeAll {
# Rejected by the token-format check before any HTTP call, so these cases stay offline.
$script:BadToken = 'not-a-valid-token'
}
It 'ApiToken should be typed SecureString' {
(Get-Command 'Connect-PveServer').Parameters['ApiToken'].ParameterType |
Should -Be ([System.Security.SecureString])
}
It 'Should bind a plain string and warn that it is deprecated' {
$warnings = @()
$err = $null
try {
Connect-PveServer -Server 'pve.example.com' -ApiToken $script:BadToken `
-ErrorAction Stop -WarningVariable +warnings
} catch { $err = $_ }
$err.FullyQualifiedErrorId | Should -Match 'PveAuthenticationFailed'
($warnings -join "`n") | Should -Match 'deprecated'
}
It 'Should not warn about deprecation when given a SecureString' {
$secure = ConvertTo-SecureString $script:BadToken -AsPlainText -Force
$warnings = @()
$err = $null
try {
Connect-PveServer -Server 'pve.example.com' -ApiToken $secure `
-ErrorAction Stop -WarningVariable +warnings
} catch { $err = $_ }
$err.FullyQualifiedErrorId | Should -Match 'PveAuthenticationFailed'
($warnings -join "`n") | Should -Not -Match 'deprecated'
}
It 'Should not warn when a SecureString follows a binding failure that transformed a string' {
$securePass = ConvertTo-SecureString 'hunter2' -AsPlainText -Force
$cred = [System.Management.Automation.PSCredential]::new('root@pam', $securePass)
try {
Connect-PveServer -Server 'pve.example.com' -Credential $cred `
-ApiToken $script:BadToken -ErrorAction Stop
} catch { }
$secure = ConvertTo-SecureString $script:BadToken -AsPlainText -Force
$warnings = @()
try {
Connect-PveServer -Server 'pve.example.com' -ApiToken $secure `
-ErrorAction Stop -WarningVariable +warnings
} catch { }
($warnings -join "`n") | Should -Not -Match 'deprecated'
}
It 'Should reject an empty SecureString as an invalid argument' {
$err = $null
try {
Connect-PveServer -Server 'pve.example.com' `
-ApiToken (New-Object System.Security.SecureString) -ErrorAction Stop
} catch { $err = $_ }
$err.FullyQualifiedErrorId | Should -Match 'ApiTokenEmpty'
}
}
Context 'Parameter metadata' {
BeforeAll {
$script:Cmd = Get-Command 'Connect-PveServer'
}
It 'Should declare Server as Mandatory' {
$serverParam = $script:Cmd.Parameters['Server']
$isMandatory = $serverParam.ParameterSets.Values |
Where-Object { $_.IsMandatory } |
Select-Object -First 1
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'Port should default to 8006' {
# Verify default via the static default-value metadata on the parameter.
$portParam = $script:Cmd.Parameters['Port']
$portParam | Should -Not -BeNullOrEmpty
}
It 'Should have a SkipCertificateCheck switch parameter' {
$script:Cmd.Parameters.ContainsKey('SkipCertificateCheck') | Should -BeTrue
$script:Cmd.Parameters['SkipCertificateCheck'].ParameterType |
Should -Be ([System.Management.Automation.SwitchParameter])
}
It 'Should have a PassThru switch parameter (deprecated, hidden)' {
$script:Cmd.Parameters.ContainsKey('PassThru') | Should -BeTrue
$script:Cmd.Parameters['PassThru'].ParameterType |
Should -Be ([System.Management.Automation.SwitchParameter])
}
It 'Should have a Quiet switch parameter' {
$script:Cmd.Parameters.ContainsKey('Quiet') | Should -BeTrue
$script:Cmd.Parameters['Quiet'].ParameterType |
Should -Be ([System.Management.Automation.SwitchParameter])
}
It 'Credential and ApiToken should belong to different parameter sets' {
$credSets = $script:Cmd.Parameters['Credential'].ParameterSets.Keys
$tokenSets = $script:Cmd.Parameters['ApiToken'].ParameterSets.Keys
$overlap = $credSets | Where-Object { $tokenSets -contains $_ }
$overlap | Should -BeNullOrEmpty
}
It 'TimeoutSeconds should reject negative values' {
$securePass = ConvertTo-SecureString 'hunter2' -AsPlainText -Force
$cred = [System.Management.Automation.PSCredential]::new('root@pam', $securePass)
{
Connect-PveServer -Server 'pve.example.com' -Credential $cred -TimeoutSeconds -1 -ErrorAction Stop
} | Should -Throw
}
}
}