From 5e1864a0b0f43390512ddefc11a157bfc665268e Mon Sep 17 00:00:00 2001 From: Clint Branham Date: Thu, 19 Mar 2026 10:33:07 -0500 Subject: [PATCH] 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) --- .github/workflows/integration-tests.yml | 70 +-- .../Integration/Integration.Tests.ps1 | 433 ++++++++++++++---- 2 files changed, 380 insertions(+), 123 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index d814bc3..d9c8945 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -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 diff --git a/tests/PSProxmoxVE.Tests/Integration/Integration.Tests.ps1 b/tests/PSProxmoxVE.Tests/Integration/Integration.Tests.ps1 index 2cb7fc5..74004e6 100644 --- a/tests/PSProxmoxVE.Tests/Integration/Integration.Tests.ps1 +++ b/tests/PSProxmoxVE.Tests/Integration/Integration.Tests.ps1 @@ -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 + } + } }