Files
PSProxmoxVE/tests/PSProxmoxVE.Tests/Integration/Integration.Tests.ps1
T
Clint Branham dc9b253195 fix(tests): resolve all 400 unit test failures to reach green
Cmdlet fixes:
- GetPveSnapshotCmdlet: add -Name filter; fix s.SnapName → s.Name
- GetPveStorageCmdlet: add -Storage name filter
- GetPveNetworkCmdlet: add -Iface optional filter
- NewPveNetworkCmdlet: rename Interface → Iface (consistency with Set/Remove)
- SetPveNetworkCmdlet, RemovePveNetworkCmdlet: rename Interface → Iface
- RemovePveNetworkCmdlet, RemovePveSdnZoneCmdlet, RemovePveSdnVnetCmdlet,
  RemovePveUserCmdlet: add ConfirmImpact = ConfirmImpact.High
- SetPveCloudInitConfigCmdlet: rename User → CiUser; move GetSession() first;
  remove duplicate session variable
- SendPveIsoCmdlet: add sha512 to ChecksumAlgorithm ValidateSet
- GetPveUserCmdlet: add -Enabled switch; refactor into MatchesFilters(); fix
  int? comparison (Enabled != 1)
- GetPvePermissionCmdlet: rename UgId → UserId
- SetPvePermissionCmdlet: rename RoleId → Role
- NewPveTemplateCmdlet: add ConfirmImpact.High; move GetSession() before
  ShouldProcess so -WhatIf-less calls throw session error first
- NewPveVmFromTemplateCmdlet: rename Node → TemplateNode with [Alias("Node")]
- RemovePveSnapshotCmdlet, RestorePveSnapshotCmdlet: move GetSession() before
  ShouldProcess so session check precedes confirm prompt

Test fixes:
- All 18 Pester test files: update DLL candidates to net9.0
- Fix foreach+It closure capture using -TestCases pattern
- Remove-PveVm, Remove-PveContainer no-session tests: add -Confirm:$false to
  bypass ConfirmImpact.High prompt before GetSession() check
- Get-PveStorageContent test: add mandatory -Node/-Storage params
- Send-PveIso test: create temp file to satisfy FileExistsValidation
- New-PveVmFromTemplate test: add NewVmId to parameter splat

Tooling:
- Invoke-Tests.ps1: add -FromTerraform, explicit lab params, fix TFM
  auto-detection via Select-Xml, fix Pester import, fix variable scoping

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 18:27:12 -05:00

347 lines
13 KiB
PowerShell

#Requires -Module Pester
<#
.SYNOPSIS
Pester 5 integration tests for PSProxmoxVE.
These tests are tagged 'Integration' and are SKIPPED by default.
They require a live, dedicated Proxmox VE test node and the following
environment variables to be set:
PVETEST_HOST - Hostname or IP of the PVE test node
PVETEST_PORT - API port (usually 8006)
PVETEST_APITOKEN - API token in the format USER@REALM!TOKENID=UUID
PVETEST_NODE - PVE node name (e.g. pve-test1)
PVETEST_STORAGE - Storage pool name for disk/ISO operations (e.g. local)
PVETEST_ISO_PATH - Local filesystem path to a small .iso for upload tests
WARNING: These tests CREATE and DESTROY real resources (VMs, snapshots,
ISO uploads, etc.) on the target node. Never run against a production cluster.
To run:
Invoke-Pester -Path ./tests/PSProxmoxVE.Tests -Tag Integration
# or use the project's helper script:
./Invoke-Tests.ps1 -Tier Integration
#>
BeforeAll {
# -----------------------------------------------------------------------
# Resolve and import module
# -----------------------------------------------------------------------
$moduleRoot = Resolve-Path (Join-Path $PSScriptRoot '../../../src/PSProxmoxVE')
$dllCandidates = @(
Join-Path $moduleRoot 'bin/Debug/net9.0/PSProxmoxVE.dll'
Join-Path $moduleRoot 'bin/Release/net9.0/PSProxmoxVE.dll'
Join-Path $moduleRoot 'bin/Debug/net48/PSProxmoxVE.dll'
Join-Path $moduleRoot 'bin/Release/net48/PSProxmoxVE.dll'
)
$moduleDll = $dllCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1
if ($null -eq $moduleDll) {
throw "PSProxmoxVE.dll not found. Build the project before running Pester tests."
}
Import-Module $moduleDll -Force -ErrorAction Stop
# -----------------------------------------------------------------------
# Check required environment variables
# -----------------------------------------------------------------------
$requiredVars = @(
'PVETEST_HOST',
'PVETEST_PORT',
'PVETEST_APITOKEN',
'PVETEST_NODE',
'PVETEST_STORAGE',
'PVETEST_ISO_PATH'
)
$script:SkipReason = $null
foreach ($var in $requiredVars) {
if (-not [System.Environment]::GetEnvironmentVariable($var)) {
$script:SkipReason = (
"No live Proxmox VE target configured. " +
"Set environment variables: $($requiredVars -join ', ')"
)
break
}
}
# Convenience accessors (only meaningful when SkipReason is null)
$script:Host_ = [System.Environment]::GetEnvironmentVariable('PVETEST_HOST')
$script:Port = [int]([System.Environment]::GetEnvironmentVariable('PVETEST_PORT') ?? '8006')
$script:ApiToken = [System.Environment]::GetEnvironmentVariable('PVETEST_APITOKEN')
$script:Node = [System.Environment]::GetEnvironmentVariable('PVETEST_NODE')
$script:Storage = [System.Environment]::GetEnvironmentVariable('PVETEST_STORAGE')
$script:IsoPath = [System.Environment]::GetEnvironmentVariable('PVETEST_ISO_PATH')
# Track resources created during the run so AfterAll can clean up.
$script:CreatedVmIds = [System.Collections.Generic.List[int]]::new()
$script:CreatedVmSnaps = [System.Collections.Generic.List[hashtable]]::new()
}
AfterAll {
# Best-effort cleanup — remove any VMs created during the test run.
if ($null -eq $script:SkipReason -and $script:CreatedVmIds.Count -gt 0) {
foreach ($vmId in $script:CreatedVmIds) {
try {
Remove-PveVm -Node $script:Node -VmId $vmId -Force -Purge -Confirm:$false -ErrorAction SilentlyContinue
}
catch { <# non-fatal #> }
}
}
}
# ===========================================================================
# Integration Tests
# ===========================================================================
Describe 'Integration Tests' -Tag 'Integration' {
# -----------------------------------------------------------------------
Context 'Connection' {
It 'Should connect to PVE server' {
if ($script:SkipReason) { Set-ItResult -Skipped -Because $script:SkipReason; return }
$session = Connect-PveServer `
-Server $script:Host_ `
-Port $script:Port `
-ApiToken $script:ApiToken `
-SkipCertificateCheck `
-PassThru
$session | Should -Not -BeNullOrEmpty
$session.Hostname | Should -Be $script:Host_
$session.AuthMode.ToString() | Should -Be 'ApiToken'
Test-PveConnection | Should -BeTrue
}
It 'Should detect server version' {
if ($script:SkipReason) { Set-ItResult -Skipped -Because $script:SkipReason; return }
$detail = Test-PveConnection -Detailed
$detail | Should -Not -BeNullOrEmpty
$detail.ServerVersion | Should -Not -BeNullOrEmpty
$detail.ServerVersion.Major | Should -BeIn @(8, 9)
}
}
# -----------------------------------------------------------------------
Context 'Nodes' {
It 'Should list nodes' {
if ($script:SkipReason) { Set-ItResult -Skipped -Because $script:SkipReason; return }
$nodes = Get-PveNode
$nodes | Should -Not -BeNullOrEmpty
}
It 'Should get node status' {
if ($script:SkipReason) { Set-ItResult -Skipped -Because $script:SkipReason; return }
$status = Get-PveNodeStatus -Node $script:Node
$status | Should -Not -BeNullOrEmpty
$status.Node | Should -Be $script:Node
}
}
# -----------------------------------------------------------------------
Context 'VMs' {
It 'Should list VMs' {
if ($script:SkipReason) { Set-ItResult -Skipped -Because $script:SkipReason; return }
# Does not assert count — the node may have zero VMs.
{ Get-PveVm -Node $script:Node -ErrorAction Stop } | Should -Not -Throw
}
It 'Should create and remove a test VM' {
if ($script:SkipReason) { Set-ItResult -Skipped -Because $script:SkipReason; return }
$task = New-PveVm `
-Node $script:Node `
-Name 'pester-test-vm' `
-Memory 512 `
-Cores 1 `
-Wait
$task | Should -Not -BeNullOrEmpty
# Retrieve the new VM ID from the cluster.
$vm = Get-PveVm -Node $script:Node -Name 'pester-test-vm' |
Select-Object -First 1
$vm | Should -Not -BeNullOrEmpty
$script:CreatedVmIds.Add($vm.VmId)
$removeTask = Remove-PveVm `
-Node $script:Node `
-VmId $vm.VmId `
-Confirm:$false `
-Wait
$removeTask | Should -Not -BeNullOrEmpty
$script:CreatedVmIds.Remove($vm.VmId) | Out-Null
}
It 'Should start and stop a VM' {
if ($script:SkipReason) { Set-ItResult -Skipped -Because $script:SkipReason; return }
# Requires at least one VM on the test node; skip if none found.
$vm = Get-PveVm -Node $script:Node | Where-Object { $_.Status -eq 'stopped' } |
Select-Object -First 1
if ($null -eq $vm) {
Set-ItResult -Skipped -Because 'No stopped VM available on the test node'
return
}
$startTask = Start-PveVm -Node $script:Node -VmId $vm.VmId -Wait
$startTask | Should -Not -BeNullOrEmpty
$stopTask = Stop-PveVm -Node $script:Node -VmId $vm.VmId -Wait
$stopTask | Should -Not -BeNullOrEmpty
}
It 'Should clone a VM' {
if ($script:SkipReason) { Set-ItResult -Skipped -Because $script:SkipReason; return }
$templateVm = Get-PveVm -Node $script:Node -TemplatesOnly | Select-Object -First 1
if ($null -eq $templateVm) {
Set-ItResult -Skipped -Because 'No template VM available on the test node'
return
}
$task = Copy-PveVm `
-SourceNode $script:Node `
-VmId $templateVm.VmId `
-NewName 'pester-clone-vm' `
-Full `
-Wait
$task | Should -Not -BeNullOrEmpty
$cloned = Get-PveVm -Node $script:Node -Name 'pester-clone-vm' |
Select-Object -First 1
$cloned | Should -Not -BeNullOrEmpty
$script:CreatedVmIds.Add($cloned.VmId)
}
}
# -----------------------------------------------------------------------
Context 'Storage' {
It 'Should list storage' {
if ($script:SkipReason) { Set-ItResult -Skipped -Because $script:SkipReason; return }
$storage = Get-PveStorage -Node $script:Node
$storage | Should -Not -BeNullOrEmpty
}
It 'Should upload an ISO' {
if ($script:SkipReason) { Set-ItResult -Skipped -Because $script:SkipReason; return }
if (-not (Test-Path $script:IsoPath)) {
Set-ItResult -Skipped -Because "ISO file not found at PVETEST_ISO_PATH: $($script:IsoPath)"
return
}
{
Send-PveIso `
-Node $script:Node `
-Storage $script:Storage `
-Path $script:IsoPath `
-ErrorAction Stop
} | Should -Not -Throw
}
}
# -----------------------------------------------------------------------
Context 'Snapshots' {
It 'Should create and remove a snapshot' {
if ($script:SkipReason) { Set-ItResult -Skipped -Because $script:SkipReason; return }
$vm = Get-PveVm -Node $script:Node | Where-Object { $_.Status -eq 'stopped' } |
Select-Object -First 1
if ($null -eq $vm) {
Set-ItResult -Skipped -Because 'No stopped VM available for snapshot test'
return
}
$snapName = 'pester-snap'
$createTask = New-PveSnapshot `
-Node $script:Node `
-VmId $vm.VmId `
-Name $snapName `
-Description 'Created by Pester integration test' `
-Wait
$createTask | Should -Not -BeNullOrEmpty
$snapshots = Get-PveSnapshot -Node $script:Node -VmId $vm.VmId
$snapshots | Where-Object { $_.Name -eq $snapName } | Should -Not -BeNullOrEmpty
$removeTask = Remove-PveSnapshot `
-Node $script:Node `
-VmId $vm.VmId `
-Name $snapName `
-Confirm:$false `
-Wait
$removeTask | Should -Not -BeNullOrEmpty
}
}
# -----------------------------------------------------------------------
Context 'Network' {
It 'Should list networks' {
if ($script:SkipReason) { Set-ItResult -Skipped -Because $script:SkipReason; return }
$networks = Get-PveNetwork -Node $script:Node
$networks | Should -Not -BeNullOrEmpty
}
}
# -----------------------------------------------------------------------
Context 'Users' {
It 'Should list users' {
if ($script:SkipReason) { Set-ItResult -Skipped -Because $script:SkipReason; return }
$users = Get-PveUser
$users | Should -Not -BeNullOrEmpty
# root@pam is always present
$users | Where-Object { $_.UserId -eq 'root@pam' } | Should -Not -BeNullOrEmpty
}
}
# -----------------------------------------------------------------------
Context 'Templates' {
It 'Should list templates' {
if ($script:SkipReason) { Set-ItResult -Skipped -Because $script:SkipReason; return }
# Zero templates is acceptable; just verify the cmdlet does not throw.
{ Get-PveTemplate -Node $script:Node -ErrorAction Stop } | Should -Not -Throw
}
}
# -----------------------------------------------------------------------
Context 'Cloud-Init' {
It 'Should get cloud-init config' {
if ($script:SkipReason) { Set-ItResult -Skipped -Because $script:SkipReason; return }
$ciVm = Get-PveVm -Node $script:Node | Where-Object { $_.Status -eq 'stopped' } |
Select-Object -First 1
if ($null -eq $ciVm) {
Set-ItResult -Skipped -Because 'No stopped VM available for cloud-init test'
return
}
# Get-PveCloudInitConfig should not throw even if the VM has no cloud-init drive.
{ Get-PveCloudInitConfig -Node $script:Node -VmId $ciVm.VmId -ErrorAction Stop } |
Should -Not -Throw
}
}
}