feat(test): add PVE 8+9 matrix and expand integration test coverage

Workflow changes:
- Convert to matrix strategy testing PVE 8.4 and 9.1 sequentially
- Each version gets unique VMID (99908/99909) and VM name
- Pass PVETEST_PVE_VERSION env var for version-specific assertions
- Version-specific artifact names for test results

New integration tests:
- User CRUD: create, list, update (Set-PveUser), remove
- API Token CRUD: create, list, remove (on dedicated test user)
- Role CRUD: create with privileges, list, remove
- Permissions: list and set (Get/Set-PvePermission)
- VM config: get and set config, resize disk
- VM lifecycle: restart, explicit remove
- Snapshots: full lifecycle (create, list, restore, remove)
- Storage: list content (Get-PveStorageContent)
- Tasks: list recent tasks (Get-PveTask)
- Version assertion: verify PVE major version matches expected

Coverage improved from 15 to ~30 cmdlets tested against live API.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Clint Branham
2026-03-19 10:33:07 -05:00
parent 6de73d8447
commit 5e1864a0b0
2 changed files with 380 additions and 123 deletions
+37 -33
View File
@@ -1,8 +1,9 @@
name: Integration Tests
# Real integration tests against a live Proxmox VE instance.
# By default, provisions a throwaway nested PVE VM via Terraform, runs tests,
# then destroys it. Can also run against a pre-existing PVE with skip_provision.
# By default, provisions throwaway nested PVE VMs via Terraform for both
# PVE 8 and PVE 9, runs tests against each, then destroys them.
# Can also run against a pre-existing PVE with skip_provision.
#
# Required repository secrets (for provisioning):
# PVE_ENDPOINT - Parent PVE API URL (e.g. https://pve.example.com:8006)
@@ -13,8 +14,8 @@ name: Integration Tests
# PVETEST_HOST - Hostname or IP of a pre-existing nested PVE
# PVETEST_APITOKEN - API token for the pre-existing nested PVE
#
# WARNING: Integration tests create and destroy real VMs, upload files,
# and modify network configuration. Never run against production.
# WARNING: Integration tests create and destroy real resources (VMs, users,
# tokens, network config, etc.) on the target node. Never run against production.
on:
push:
@@ -23,14 +24,6 @@ on:
branches: [ main ]
workflow_dispatch:
inputs:
pve_version:
description: 'PVE version to test (8 or 9)'
required: false
type: choice
options:
- '9'
- '8'
default: '9'
skip_provision:
description: 'Skip provisioning (use existing PVE from PVETEST_HOST secret)'
required: false
@@ -46,6 +39,22 @@ jobs:
integration:
runs-on: [self-hosted, proxmox, integration]
timeout-minutes: 45
strategy:
fail-fast: false
max-parallel: 1 # Single self-hosted runner — provision one at a time
matrix:
pve_version: ['9', '8']
include:
- pve_version: '9'
iso_base: /opt/pve-isos/proxmox-ve_9.1-1.iso
iso_filename: proxmox-ve_9.1-1-auto.iso
vm_id: 99909
vm_name: pve-test-pve9
- pve_version: '8'
iso_base: /opt/pve-isos/proxmox-ve_8.4-1.iso
iso_filename: proxmox-ve_8.4-1-auto.iso
vm_id: 99908
vm_name: pve-test-pve8
steps:
- uses: actions/checkout@v5
@@ -55,24 +64,14 @@ jobs:
- name: Pre-flight cleanup
if: inputs.skip_provision != true
shell: bash
id: iso
env:
PVE_API_TOKEN: ${{ secrets.PVE_API_TOKEN }}
run: |
VERSION="${{ inputs.pve_version || '9' }}"
if [ "$VERSION" = "9" ]; then
echo "base=/opt/pve-isos/proxmox-ve_9.1-1.iso" >> "$GITHUB_OUTPUT"
echo "filename=proxmox-ve_9.1-1-auto.iso" >> "$GITHUB_OUTPUT"
else
echo "base=/opt/pve-isos/proxmox-ve_8.4-1.iso" >> "$GITHUB_OUTPUT"
echo "filename=proxmox-ve_8.4-1-auto.iso" >> "$GITHUB_OUTPUT"
fi
bash ${SCRIPTS_DIR}/preflight-cleanup.sh \
"${{ secrets.PVE_ENDPOINT }}" \
"${PVE_API_TOKEN}" \
99900 \
"auto.iso" \
${{ matrix.vm_id }} \
"${{ matrix.iso_filename }}" \
"${INFRA_DIR}"
# ── Provision nested PVE ───────────────────────────────────────────
@@ -91,10 +90,10 @@ jobs:
shell: bash
run: |
bash ${SCRIPTS_DIR}/prepare-auto-iso.sh \
"${{ steps.iso.outputs.base }}" \
"${{ matrix.iso_base }}" \
${RUNNER_TEMP}/answer.toml \
${SCRIPTS_DIR}/first-boot.sh \
"${{ runner.temp }}/${{ steps.iso.outputs.filename }}"
"${{ runner.temp }}/${{ matrix.iso_filename }}"
- name: Terraform init
if: inputs.skip_provision != true
@@ -111,8 +110,10 @@ jobs:
TF_VAR_proxmox_endpoint: ${{ secrets.PVE_ENDPOINT }}
TF_VAR_proxmox_api_token: ${{ secrets.PVE_API_TOKEN }}
TF_VAR_target_node: ${{ secrets.PVE_TARGET_NODE }}
TF_VAR_iso_local_path: ${{ runner.temp }}/${{ steps.iso.outputs.filename }}
TF_VAR_iso_local_path: ${{ runner.temp }}/${{ matrix.iso_filename }}
TF_VAR_test_vm_password: ${{ env.PVE_PASSWORD }}
TF_VAR_vm_id: ${{ matrix.vm_id }}
TF_VAR_vm_name: ${{ matrix.vm_name }}
run: |
terraform apply -auto-approve -input=false
echo "vm_id=$(terraform output -raw pve_test_vm_id)" >> "$GITHUB_OUTPUT"
@@ -135,7 +136,7 @@ jobs:
# Mask sensitive values before they appear in logs
echo "::add-mask::${VM_IP}"
echo "::add-mask::${VM_TOKEN}"
echo "Nested PVE ready at ${VM_IP}"
echo "Nested PVE ${{ matrix.pve_version }} ready at ${VM_IP}"
echo "host=${VM_IP}" >> "$GITHUB_OUTPUT"
echo "token=${VM_TOKEN}" >> "$GITHUB_OUTPUT"
@@ -165,7 +166,7 @@ jobs:
HOST="${{ steps.target.outputs.host }}"
PORT="${{ steps.target.outputs.port }}"
TOKEN="${{ steps.target.outputs.token }}"
echo "Testing connectivity to PVE API at ${HOST}:${PORT}..."
echo "Testing connectivity to PVE ${{ matrix.pve_version }} API at ${HOST}:${PORT}..."
if curl -sk --connect-timeout 10 \
-H "Authorization: PVEAPIToken=${TOKEN}" \
"https://${HOST}:${PORT}/api2/json/nodes" | grep -q '"node"'; then
@@ -207,6 +208,7 @@ jobs:
PVETEST_NODE: ${{ steps.target.outputs.node }}
PVETEST_STORAGE: ${{ steps.target.outputs.storage }}
PVETEST_ISO_PATH: ${{ runner.temp }}/pvetest.iso
PVETEST_PVE_VERSION: ${{ matrix.pve_version }}
run: |
Import-Module Pester -MinimumVersion 5.0
$config = New-PesterConfiguration
@@ -222,7 +224,7 @@ jobs:
if: always()
uses: actions/upload-artifact@v7
with:
name: integration-test-results
name: integration-test-results-pve${{ matrix.pve_version }}
path: TestResults/
# ── Teardown ───────────────────────────────────────────────────────
@@ -235,8 +237,10 @@ jobs:
TF_VAR_proxmox_endpoint: ${{ secrets.PVE_ENDPOINT }}
TF_VAR_proxmox_api_token: ${{ secrets.PVE_API_TOKEN }}
TF_VAR_target_node: ${{ secrets.PVE_TARGET_NODE }}
TF_VAR_iso_local_path: ${{ runner.temp }}/${{ steps.iso.outputs.filename }}
TF_VAR_iso_local_path: ${{ runner.temp }}/${{ matrix.iso_filename }}
TF_VAR_test_vm_password: ${{ env.PVE_PASSWORD }}
TF_VAR_vm_id: ${{ matrix.vm_id }}
TF_VAR_vm_name: ${{ matrix.vm_name }}
run: |
terraform init -input=false
terraform destroy -auto-approve -input=false || true
@@ -251,6 +255,6 @@ jobs:
bash ${SCRIPTS_DIR}/preflight-cleanup.sh \
"${{ secrets.PVE_ENDPOINT }}" \
"${PVE_API_TOKEN}" \
99900 \
"${{ steps.iso.outputs.filename }}" \
${{ matrix.vm_id }} \
"${{ matrix.iso_filename }}" \
"${INFRA_DIR}" || true
@@ -13,9 +13,11 @@
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
PVETEST_PVE_VERSION - (optional) Expected PVE major version (8 or 9)
WARNING: These tests CREATE and DESTROY real resources (VMs, snapshots,
ISO uploads, etc.) on the target node. Never run against a production cluster.
WARNING: These tests CREATE and DESTROY real resources (VMs, users, tokens,
roles, snapshots, ISO uploads, etc.) on the target node.
Never run against a production cluster.
To run:
Invoke-Pester -Path ./tests/PSProxmoxVE.Tests -Tag Integration
@@ -61,23 +63,56 @@ BeforeAll {
$script:Node = [System.Environment]::GetEnvironmentVariable('PVETEST_NODE')
$script:Storage = [System.Environment]::GetEnvironmentVariable('PVETEST_STORAGE')
$script:IsoPath = [System.Environment]::GetEnvironmentVariable('PVETEST_ISO_PATH')
$script:ExpectedPveVersion = [System.Environment]::GetEnvironmentVariable('PVETEST_PVE_VERSION')
# Track resources created during the run so AfterAll can clean up.
$script:CreatedVmIds = [System.Collections.Generic.List[int]]::new()
$script:CreatedUsers = [System.Collections.Generic.List[string]]::new()
$script:CreatedRoles = [System.Collections.Generic.List[string]]::new()
$script:TestVmId = $null
# Helper functions for skip logic
function script:Skip-IfNoTarget {
if ($script:SkipReason) {
Set-ItResult -Skipped -Because $script:SkipReason
return $true
}
return $false
}
function script:Skip-IfNoTestVm {
if (Skip-IfNoTarget) { return $true }
if ($null -eq $script:TestVmId) {
Set-ItResult -Skipped -Because 'No test VM was created'
return $true
}
return $false
}
}
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 {
Stop-PveVm -Node $script:Node -VmId $vmId -ErrorAction SilentlyContinue | Out-Null
Start-Sleep -Seconds 3
Remove-PveVm -Node $script:Node -VmId $vmId -Force -Purge -Confirm:$false -ErrorAction SilentlyContinue
}
catch { <# non-fatal #> }
if ($null -ne $script:SkipReason) { return }
# Best-effort cleanup — remove resources created during the test run.
# Order matters: tokens/permissions removed with users, VMs last.
foreach ($userId in $script:CreatedUsers) {
try { Remove-PveUser -UserId $userId -Confirm:$false -ErrorAction SilentlyContinue }
catch { <# non-fatal #> }
}
foreach ($roleId in $script:CreatedRoles) {
try { Remove-PveRole -RoleId $roleId -Confirm:$false -ErrorAction SilentlyContinue }
catch { <# non-fatal #> }
}
foreach ($vmId in $script:CreatedVmIds) {
try {
Stop-PveVm -Node $script:Node -VmId $vmId -ErrorAction SilentlyContinue | Out-Null
Start-Sleep -Seconds 3
Remove-PveVm -Node $script:Node -VmId $vmId -Force -Purge -Confirm:$false -ErrorAction SilentlyContinue
}
catch { <# non-fatal #> }
}
}
@@ -89,7 +124,7 @@ Describe 'Integration Tests' -Tag 'Integration' {
# -----------------------------------------------------------------------
Context 'Connection' {
It 'Should connect to PVE server' {
if ($script:SkipReason) { Set-ItResult -Skipped -Because $script:SkipReason; return }
if (Skip-IfNoTarget) { return }
$session = Connect-PveServer `
-Server $script:Host_ `
@@ -106,26 +141,31 @@ Describe 'Integration Tests' -Tag 'Integration' {
}
It 'Should detect server version' {
if ($script:SkipReason) { Set-ItResult -Skipped -Because $script:SkipReason; return }
if (Skip-IfNoTarget) { return }
$detail = Test-PveConnection -Detailed
$detail | Should -Not -BeNullOrEmpty
$detail.ServerVersion | Should -Not -BeNullOrEmpty
$detail.ServerVersion.Major | Should -BeIn @(8, 9)
# If we know the expected version, verify it matches
if ($script:ExpectedPveVersion) {
$detail.ServerVersion.Major | Should -Be ([int]$script:ExpectedPveVersion)
}
}
}
# -----------------------------------------------------------------------
Context 'Nodes' {
It 'Should list nodes' {
if ($script:SkipReason) { Set-ItResult -Skipped -Because $script:SkipReason; return }
if (Skip-IfNoTarget) { return }
$nodes = Get-PveNode
$nodes | Should -Not -BeNullOrEmpty
}
It 'Should get node status' {
if ($script:SkipReason) { Set-ItResult -Skipped -Because $script:SkipReason; return }
if (Skip-IfNoTarget) { return }
$status = Get-PveNodeStatus -Node $script:Node
$status | Should -Not -BeNullOrEmpty
@@ -133,17 +173,161 @@ Describe 'Integration Tests' -Tag 'Integration' {
}
}
# -----------------------------------------------------------------------
Context 'User CRUD' {
It 'Should create a new user' {
if (Skip-IfNoTarget) { return }
{ New-PveUser -UserId 'pester-user@pam' -ErrorAction Stop } |
Should -Not -Throw
$script:CreatedUsers.Add('pester-user@pam')
}
It 'Should list users and find the new user' {
if (Skip-IfNoTarget) { return }
$users = Get-PveUser
$users | Should -Not -BeNullOrEmpty
$users | Where-Object { $_.UserId -eq 'pester-user@pam' } |
Should -Not -BeNullOrEmpty
}
It 'Should update user properties' {
if (Skip-IfNoTarget) { return }
{ Set-PveUser -UserId 'pester-user@pam' `
-Comment 'Updated by Pester integration test' `
-ErrorAction Stop } | Should -Not -Throw
$user = Get-PveUser | Where-Object { $_.UserId -eq 'pester-user@pam' }
$user.Comment | Should -Be 'Updated by Pester integration test'
}
It 'Should remove the user' {
if (Skip-IfNoTarget) { return }
{ Remove-PveUser -UserId 'pester-user@pam' -Confirm:$false -ErrorAction Stop } |
Should -Not -Throw
$script:CreatedUsers.Remove('pester-user@pam') | Out-Null
$users = Get-PveUser
$users | Where-Object { $_.UserId -eq 'pester-user@pam' } |
Should -BeNullOrEmpty
}
}
# -----------------------------------------------------------------------
Context 'Role CRUD' {
It 'Should create a new role' {
if (Skip-IfNoTarget) { return }
{ New-PveRole -RoleId 'PesterTestRole' `
-Privileges @('VM.Audit', 'VM.Console') `
-ErrorAction Stop } | Should -Not -Throw
$script:CreatedRoles.Add('PesterTestRole')
}
It 'Should list roles and find the new role' {
if (Skip-IfNoTarget) { return }
$roles = Get-PveRole
$roles | Should -Not -BeNullOrEmpty
$roles | Where-Object { $_.RoleId -eq 'PesterTestRole' } |
Should -Not -BeNullOrEmpty
}
It 'Should remove the role' {
if (Skip-IfNoTarget) { return }
{ Remove-PveRole -RoleId 'PesterTestRole' -Confirm:$false -ErrorAction Stop } |
Should -Not -Throw
$script:CreatedRoles.Remove('PesterTestRole') | Out-Null
}
}
# -----------------------------------------------------------------------
Context 'API Token CRUD' {
BeforeAll {
# Create a user to hold the test token
if ($null -eq $script:SkipReason) {
New-PveUser -UserId 'pester-tokenuser@pam' -ErrorAction SilentlyContinue
$script:CreatedUsers.Add('pester-tokenuser@pam')
}
}
It 'Should create an API token' {
if (Skip-IfNoTarget) { return }
$result = New-PveApiToken `
-UserId 'pester-tokenuser@pam' `
-TokenId 'pester-token' `
-ErrorAction Stop
$result | Should -Not -BeNullOrEmpty
}
It 'Should list API tokens for the user' {
if (Skip-IfNoTarget) { return }
$tokens = Get-PveApiToken -UserId 'pester-tokenuser@pam'
$tokens | Should -Not -BeNullOrEmpty
$tokens | Where-Object { $_.TokenId -eq 'pester-token' } |
Should -Not -BeNullOrEmpty
}
It 'Should remove the API token' {
if (Skip-IfNoTarget) { return }
{ Remove-PveApiToken `
-UserId 'pester-tokenuser@pam' `
-TokenId 'pester-token' `
-Confirm:$false `
-ErrorAction Stop } | Should -Not -Throw
}
}
# -----------------------------------------------------------------------
Context 'Permissions' {
BeforeAll {
# Create a user and role for permission testing
if ($null -eq $script:SkipReason) {
New-PveUser -UserId 'pester-permuser@pam' -ErrorAction SilentlyContinue
$script:CreatedUsers.Add('pester-permuser@pam')
}
}
It 'Should list permissions' {
if (Skip-IfNoTarget) { return }
{ Get-PvePermission -ErrorAction Stop } | Should -Not -Throw
}
It 'Should set a permission' {
if (Skip-IfNoTarget) { return }
{ Set-PvePermission `
-Path '/' `
-Role 'PVEAuditor' `
-Users 'pester-permuser@pam' `
-ErrorAction Stop } | Should -Not -Throw
}
}
# -----------------------------------------------------------------------
Context 'VMs' {
It 'Should list VMs' {
if ($script:SkipReason) { Set-ItResult -Skipped -Because $script:SkipReason; return }
if (Skip-IfNoTarget) { return }
# Does not assert count — the node may have zero VMs.
{ Get-PveVm -Node $script:Node -ErrorAction Stop } | Should -Not -Throw
}
It 'Should create a test VM' {
if ($script:SkipReason) { Set-ItResult -Skipped -Because $script:SkipReason; return }
if (Skip-IfNoTarget) { return }
$task = New-PveVm `
-Node $script:Node `
@@ -163,13 +347,29 @@ Describe 'Integration Tests' -Tag 'Integration' {
$script:CreatedVmIds.Add($vm.VmId)
}
It 'Should start and stop a VM' {
if ($script:SkipReason) { Set-ItResult -Skipped -Because $script:SkipReason; return }
It 'Should get VM config' {
if (Skip-IfNoTestVm) { return }
if ($null -eq $script:TestVmId) {
Set-ItResult -Skipped -Because 'No test VM was created'
return
}
$config = Get-PveVmConfig -Node $script:Node -VmId $script:TestVmId
$config | Should -Not -BeNullOrEmpty
$config.Name | Should -Be 'pester-test-vm'
}
It 'Should set VM config' {
if (Skip-IfNoTestVm) { return }
{ Set-PveVmConfig `
-Node $script:Node `
-VmId $script:TestVmId `
-Description 'Updated by Pester integration test' `
-ErrorAction Stop } | Should -Not -Throw
$config = Get-PveVmConfig -Node $script:Node -VmId $script:TestVmId
$config.Description | Should -Be 'Updated by Pester integration test'
}
It 'Should start and stop a VM' {
if (Skip-IfNoTestVm) { return }
$startTask = Start-PveVm -Node $script:Node -VmId $script:TestVmId -Wait
$startTask | Should -Not -BeNullOrEmpty
@@ -178,19 +378,39 @@ Describe 'Integration Tests' -Tag 'Integration' {
$stopTask | Should -Not -BeNullOrEmpty
}
It 'Should restart a VM' {
if (Skip-IfNoTestVm) { return }
# Start first, then restart
Start-PveVm -Node $script:Node -VmId $script:TestVmId -Wait | Out-Null
$task = Restart-PveVm -Node $script:Node -VmId $script:TestVmId -Wait
$task | Should -Not -BeNullOrEmpty
Stop-PveVm -Node $script:Node -VmId $script:TestVmId -Wait | Out-Null
}
It 'Should resize a VM disk' {
if (Skip-IfNoTestVm) { return }
# First add a disk to resize
Set-PveVmConfig -Node $script:Node -VmId $script:TestVmId `
-Settings @{ scsi0 = "$($script:Storage):1" } -ErrorAction Stop
{ Resize-PveVmDisk `
-Node $script:Node `
-VmId $script:TestVmId `
-Disk 'scsi0' `
-Size '+1G' `
-ErrorAction Stop } | Should -Not -Throw
}
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
}
if (Skip-IfNoTestVm) { return }
$task = Copy-PveVm `
-SourceNode $script:Node `
-VmId $templateVm.VmId `
-VmId $script:TestVmId `
-NewName 'pester-clone-vm' `
-Full `
-Wait
@@ -205,17 +425,74 @@ Describe 'Integration Tests' -Tag 'Integration' {
}
}
# -----------------------------------------------------------------------
Context 'Snapshots' {
It 'Should create, list, and remove a snapshot' {
if (Skip-IfNoTestVm) { return }
# Ensure the VM is stopped for snapshot
$vm = Get-PveVm -Node $script:Node | Where-Object { $_.VmId -eq $script:TestVmId }
if ($vm.Status -eq 'running') {
Stop-PveVm -Node $script:Node -VmId $script:TestVmId -Wait | Out-Null
}
$snapName = 'pester-snap'
# Create
$createTask = New-PveSnapshot `
-Node $script:Node `
-VmId $script:TestVmId `
-Name $snapName `
-Description 'Created by Pester integration test' `
-Wait
$createTask | Should -Not -BeNullOrEmpty
# List and verify
$snapshots = Get-PveSnapshot -Node $script:Node -VmId $script:TestVmId
$snap = $snapshots | Where-Object { $_.Name -eq $snapName }
$snap | Should -Not -BeNullOrEmpty
# Restore
{ Restore-PveSnapshot `
-Node $script:Node `
-VmId $script:TestVmId `
-Name $snapName `
-Confirm:$false `
-Wait } | Should -Not -Throw
# Remove
$removeTask = Remove-PveSnapshot `
-Node $script:Node `
-VmId $script:TestVmId `
-Name $snapName `
-Confirm:$false `
-Wait
$removeTask | Should -Not -BeNullOrEmpty
}
}
# -----------------------------------------------------------------------
Context 'Storage' {
It 'Should list storage' {
if ($script:SkipReason) { Set-ItResult -Skipped -Because $script:SkipReason; return }
if (Skip-IfNoTarget) { return }
$storage = Get-PveStorage -Node $script:Node
$storage | Should -Not -BeNullOrEmpty
}
It 'Should list storage content' {
if (Skip-IfNoTarget) { return }
{ Get-PveStorageContent `
-Node $script:Node `
-Storage $script:Storage `
-ErrorAction Stop } | Should -Not -Throw
}
It 'Should upload an ISO' {
if ($script:SkipReason) { Set-ItResult -Skipped -Because $script:SkipReason; return }
if (Skip-IfNoTarget) { return }
if (-not (Test-Path $script:IsoPath)) {
Set-ItResult -Skipped -Because "ISO file not found at PVETEST_ISO_PATH: $($script:IsoPath)"
@@ -233,72 +510,29 @@ Describe 'Integration Tests' -Tag 'Integration' {
}
# -----------------------------------------------------------------------
Context 'Snapshots' {
It 'Should create and remove a snapshot' {
if ($script:SkipReason) { Set-ItResult -Skipped -Because $script:SkipReason; return }
Context 'Tasks' {
It 'Should list recent tasks' {
if (Skip-IfNoTarget) { return }
if ($null -eq $script:TestVmId) {
Set-ItResult -Skipped -Because 'No test VM was created'
return
}
# Ensure the VM is stopped for snapshot
$vm = Get-PveVm -Node $script:Node | Where-Object { $_.VmId -eq $script:TestVmId }
if ($vm.Status -eq 'running') {
Stop-PveVm -Node $script:Node -VmId $script:TestVmId -Wait | Out-Null
}
$snapName = 'pester-snap'
$createTask = New-PveSnapshot `
-Node $script:Node `
-VmId $script:TestVmId `
-Name $snapName `
-Description 'Created by Pester integration test' `
-Wait
$createTask | Should -Not -BeNullOrEmpty
$snapshots = Get-PveSnapshot -Node $script:Node -VmId $script:TestVmId
$snapshots | Where-Object { $_.Name -eq $snapName } | Should -Not -BeNullOrEmpty
$removeTask = Remove-PveSnapshot `
-Node $script:Node `
-VmId $script:TestVmId `
-Name $snapName `
-Confirm:$false `
-Wait
$removeTask | Should -Not -BeNullOrEmpty
$tasks = Get-PveTask -Node $script:Node
$tasks | Should -Not -BeNullOrEmpty
}
}
# -----------------------------------------------------------------------
Context 'Network' {
It 'Should list networks' {
if ($script:SkipReason) { Set-ItResult -Skipped -Because $script:SkipReason; return }
if (Skip-IfNoTarget) { 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 }
if (Skip-IfNoTarget) { return }
# Zero templates is acceptable; just verify the cmdlet does not throw.
{ Get-PveTemplate -Node $script:Node -ErrorAction Stop } | Should -Not -Throw
@@ -308,16 +542,35 @@ Describe 'Integration Tests' -Tag 'Integration' {
# -----------------------------------------------------------------------
Context 'Cloud-Init' {
It 'Should get cloud-init config' {
if ($script:SkipReason) { Set-ItResult -Skipped -Because $script:SkipReason; return }
if ($null -eq $script:TestVmId) {
Set-ItResult -Skipped -Because 'No test VM available for cloud-init test'
return
}
if (Skip-IfNoTestVm) { return }
# Get-PveCloudInitConfig should not throw even if the VM has no cloud-init drive.
{ Get-PveCloudInitConfig -Node $script:Node -VmId $script:TestVmId -ErrorAction Stop } |
Should -Not -Throw
}
}
# -----------------------------------------------------------------------
Context 'VM Cleanup' {
It 'Should remove a VM' {
if (Skip-IfNoTestVm) { return }
# Ensure stopped
$vm = Get-PveVm -Node $script:Node | Where-Object { $_.VmId -eq $script:TestVmId }
if ($vm.Status -eq 'running') {
Stop-PveVm -Node $script:Node -VmId $script:TestVmId -Wait | Out-Null
}
{ Remove-PveVm `
-Node $script:Node `
-VmId $script:TestVmId `
-Force `
-Purge `
-Confirm:$false `
-ErrorAction Stop } | Should -Not -Throw
$script:CreatedVmIds.Remove($script:TestVmId) | Out-Null
$script:TestVmId = $null
}
}
}