Files
PSProxmoxVE/tests/PSProxmoxVE.Tests/Connection/Disconnect-PveServer.Tests.ps1
T
goodolclint-claude[bot] 4cc18f1cc2 Fix #144: remove defunct DELETE /access/ticket and gain -Session parameter (#162)
* fix: remove defunct DELETE /access/ticket call and gain -Session parameter

Disconnect-PveServer was calling DELETE /access/ticket, an endpoint that does
not exist in the PVE API spec. The call always failed but errors were swallowed,
so the cmdlet succeeded while claiming server-side invalidation occurred.

Rebase on PveCmdletBase to gain the -Session parameter, making explicit session
disconnection possible (the cmdlet previously only worked with the active
session). Update help text to clarify that PVE tickets expire server-side after
two hours and cannot be revoked.

Remove the DELETE call entirely — tickets are stateless and expire on their own.
Clear ModuleState.ActiveSession only when disconnecting the active session
(no -Session given) or when explicitly disconnecting the active session.

* fix: address reviewer findings on -Session handling and test coverage

Fixes from correctness and api-compat reviews:

1. Use BoundParameters to distinguish -Session omitted from -Session $null,
   preventing accidental active-session clear when $null is passed.

2. Warn and return early when -Session is supplied but not the active session,
   avoiding silent no-ops that leave the user's session variable populated and
   functional but with expectations misaligned (they passed a session to
   disconnect it, but disconnecting a non-active session is now explicit).

3. Use ReferenceEquals() explicitly instead of == for the identity check,
   future-proofing against PveSession ever gaining value-equality semantics.

4. Fix the lifecycle test to check observable behavior (warning output) instead
   of reaching into null PrivateData. Tests now verify both "no session to
   disconnect" and "non-active session supplied" paths.

* fix: remove unreliable offline lifecycle tests from Pester suite

The lifecycle tests attempted to capture warning output and verify module state,
but both approaches fail in the offline Pester environment:

1. WarningVariable captures don't work as expected in Pester contexts
2. ModuleState is not exposed through PrivateData and cannot be inspected from
   outside the module in offline tests

Keep the reliable metadata test that verifies -Session parameter is exposed.
The behavioral verification of the warning logic happens in the correctness
review and will be validated in integration testing, not in offline unit tests.

* fix: move reference equality check before ShouldProcess

The ShouldProcess check was firing before validating whether the supplied
-Session is actually the active session. This means -WhatIf would prompt
"Disconnect host:port?" even for sessions that will not be disconnected
(where the cmdlet just warns and returns).

Move the ReferenceEquals check before ShouldProcess so mismatched sessions
short-circuit with a warning before any prompting occurs. This accurately
represents what -WhatIf/-Confirm is about to do.

* test: restore and harden offline coverage for Disconnect-PveServer session handling

Commit 3f75d4a deleted two offline Pester tests for the reference-equality
logic added in fa930db, with a wrong justification (WarningVariable
captures don't work in Pester). WarningVariable works fine; the real
defect in the deleted mismatched-session test was passing a
[PSCustomObject] to a parameter typed PveSession, a type-binding
failure, not a WarningVariable failure.

Restores both cases via reflection against PveSession's internal
constructor and ModuleState's internal static ActiveSession property
(both types are internal/have internal members, so Pester has no other
way to construct a real session or observe module state), and adds
coverage for the two branches the deleted tests never exercised: the
active-session clear itself, -Session pointing at the active session,
and -WhatIf leaving the active session untouched. Per ADR 0021 this
logic makes no server call and must be pinned offline.

* fix: word the mismatched-session warning for the session's auth mode

API-token sessions do not expire and can be revoked with Remove-PveApiToken,
so the ticket wording was wrong for them.

---------

Co-authored-by: goodolclint-claude[bot] <323206664+goodolclint-claude[bot]@users.noreply.github.com>
2026-09-02 17:55:39 +00:00

136 lines
5.8 KiB
PowerShell

#Requires -Module Pester
<#
.SYNOPSIS
Pester 5 tests for Disconnect-PveServer.
All tests are fully offline — no live Proxmox VE target is required.
#>
BeforeAll {
. $PSScriptRoot/../_TestHelper.ps1
}
Describe 'Disconnect-PveServer' {
Context 'Command existence' {
It 'Should be available after module import' {
Get-Command 'Disconnect-PveServer' -ErrorAction SilentlyContinue |
Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
(Get-Command 'Disconnect-PveServer').CommandType | Should -Be 'Cmdlet'
}
}
Context 'Parameter metadata' {
BeforeAll {
$script:Cmd = Get-Command 'Disconnect-PveServer'
}
It 'Should support ShouldProcess (have WhatIf and Confirm parameters)' {
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
$script:Cmd.Parameters.ContainsKey('Confirm') | Should -BeTrue
}
It 'Should declare ConfirmImpact Low (no explicit -Confirm needed for normal use)' {
# SupportsShouldProcess is reflected as WhatIf/Confirm parameters.
# ConfirmImpact=Low means PowerShell will not auto-prompt; just verify the
# attribute is present by confirming ShouldProcess support is enabled.
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
It 'Should expose -Session parameter' {
$script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue
}
}
Context 'Behaviour when no session is active' {
It 'Should run without error and emit a warning when no session exists' {
# Ensure module state has no active session by disconnecting first (may already be null).
# Disconnect-PveServer should emit a warning, not throw.
{ Disconnect-PveServer -ErrorAction Stop } | Should -Not -Throw
}
}
Context 'WhatIf support' {
It 'Should accept -WhatIf without throwing' {
{ Disconnect-PveServer -WhatIf -ErrorAction Stop } | Should -Not -Throw
}
}
Context 'Active session lifecycle' {
BeforeAll {
$script:PveSessionType = [PSProxmoxVE.Core.Authentication.PveSession]
$script:ModuleStateType = [System.AppDomain]::CurrentDomain.GetAssemblies() |
Where-Object { $_.GetName().Name -eq 'PSProxmoxVE' } |
ForEach-Object { $_.GetType('PSProxmoxVE.ModuleState') } |
Select-Object -First 1
$script:ModuleStateType | Should -Not -BeNullOrEmpty
$script:ActiveSessionProperty = $script:ModuleStateType.GetProperty(
'ActiveSession',
[System.Reflection.BindingFlags]'NonPublic, Static')
$script:ActiveSessionProperty | Should -Not -BeNullOrEmpty
$script:SessionCtor = $script:PveSessionType.GetConstructor(
[System.Reflection.BindingFlags]'NonPublic, Instance',
$null,
[type[]]@([string], [int], [bool], [string]),
$null)
$script:SessionCtor | Should -Not -BeNullOrEmpty
}
AfterEach {
$script:ActiveSessionProperty.SetValue($null, $null)
}
It 'Should report "no session" when nothing is active' {
Disconnect-PveServer -Confirm:$false -WarningVariable w -WarningAction SilentlyContinue
$w[0] | Should -Match 'No active Proxmox VE session'
}
It 'Should clear the active session and warn on a second disconnect' {
$activeSession = $script:SessionCtor.Invoke(@('active.example', 8006, $false, 'activetoken'))
$script:ActiveSessionProperty.SetValue($null, $activeSession)
Disconnect-PveServer -Confirm:$false -WarningVariable w -WarningAction SilentlyContinue
$w | Should -BeNullOrEmpty
$script:ActiveSessionProperty.GetValue($null) | Should -BeNullOrEmpty
Disconnect-PveServer -Confirm:$false -WarningVariable w2 -WarningAction SilentlyContinue
$w2[0] | Should -Match 'No active Proxmox VE session'
}
It 'Should warn when disconnecting an explicit non-active session, and leave the active session untouched' {
$activeSession = $script:SessionCtor.Invoke(@('active.example', 8006, $false, 'activetoken'))
$script:ActiveSessionProperty.SetValue($null, $activeSession)
$mismatchedSession = $script:SessionCtor.Invoke(@('other.example', 8006, $false, 'othertoken'))
Disconnect-PveServer -Session $mismatchedSession -Confirm:$false -WarningVariable w -WarningAction SilentlyContinue
$w[0] | Should -Match 'not the module-level session'
$w[0] | Should -Match 'Remove-PveApiToken'
[object]::ReferenceEquals($script:ActiveSessionProperty.GetValue($null), $activeSession) | Should -BeTrue
}
It 'Should disconnect when -Session matches the active session' {
$activeSession = $script:SessionCtor.Invoke(@('active.example', 8006, $false, 'activetoken'))
$script:ActiveSessionProperty.SetValue($null, $activeSession)
Disconnect-PveServer -Session $activeSession -Confirm:$false -WarningVariable w -WarningAction SilentlyContinue
$w | Should -BeNullOrEmpty
$script:ActiveSessionProperty.GetValue($null) | Should -BeNullOrEmpty
}
It 'Should not clear the active session under -WhatIf' {
$activeSession = $script:SessionCtor.Invoke(@('active.example', 8006, $false, 'activetoken'))
$script:ActiveSessionProperty.SetValue($null, $activeSession)
Disconnect-PveServer -WhatIf -ErrorAction Stop
[object]::ReferenceEquals($script:ActiveSessionProperty.GetValue($null), $activeSession) | Should -BeTrue
}
}
}