mirror of
https://github.com/GoodOlClint/PSProxmoxVE.git
synced 2026-08-09 05:49:24 +00:00
refactor: split monolithic integration tests into numbered per-area files
Split the 1544-line Integration.Tests.ps1 into 18 focused test files with numeric prefixes for execution ordering. New shared helper (_IntegrationHelper.ps1): - Credential-based auth (root@pam) instead of API tokens - Skip helpers for env var checks - Resource discovery (Find-TestVm) for cross-file dependencies - Register-TestResource for env-var state passing between files Files created: 00_Connection, 01_Nodes, 02_Users, 03_Storage, 03a_SharedStorage, 04_Network, 05_SDN, 06_VMs, 07_Snapshots, 08_Templates, 09_CloudInit, 10_Containers, 11_LinuxVM, 12_OVA, 13_Firewall, 14_Backup, 15_Tasks, 99_Cleanup Key changes: - All tests use root@pam credentials (not API tokens) - Each file self-contained with own BeforeAll/AfterAll cleanup - Token CRUD tests now idempotent (remove-before-create) - 99_Cleanup is safety-net for any leftover pester-* resources - SharedStorage renamed to 03a_SharedStorage Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
#Requires -Module Pester
|
||||
|
||||
BeforeAll {
|
||||
. $PSScriptRoot/_IntegrationHelper.ps1
|
||||
Connect-TestPve
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
Disconnect-TestPve
|
||||
}
|
||||
|
||||
Describe 'Connection — Integration' -Tag 'Integration' {
|
||||
Context 'Connection' {
|
||||
It 'Should connect to PVE server' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
|
||||
$session = Connect-PveServer `
|
||||
-Server $script:Host_ `
|
||||
-Port $script:Port `
|
||||
-Credential (New-Object System.Management.Automation.PSCredential(
|
||||
'root@pam',
|
||||
(ConvertTo-SecureString $script:Password -AsPlainText -Force)
|
||||
)) `
|
||||
-SkipCertificateCheck `
|
||||
-PassThru
|
||||
|
||||
$session | Should -Not -BeNullOrEmpty
|
||||
$session.Hostname | Should -Be $script:Host_
|
||||
$session.AuthMode.ToString() | Should -Be 'Ticket'
|
||||
|
||||
Test-PveConnection | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should detect server version' {
|
||||
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:PveVersion) {
|
||||
$detail.ServerVersion.Major | Should -Be ([int]$script:PveVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#Requires -Module Pester
|
||||
|
||||
BeforeAll {
|
||||
. $PSScriptRoot/_IntegrationHelper.ps1
|
||||
Connect-TestPve
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
Disconnect-TestPve
|
||||
}
|
||||
|
||||
Describe 'Nodes — Integration' -Tag 'Integration' {
|
||||
Context 'Nodes' {
|
||||
It 'Should list nodes' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
|
||||
$nodes = Get-PveNode
|
||||
$nodes | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should get node status' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
|
||||
$status = Get-PveNodeStatus -Node $script:Node
|
||||
$status | Should -Not -BeNullOrEmpty
|
||||
$status.Node | Should -Be $script:Node
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
#Requires -Module Pester
|
||||
|
||||
BeforeAll {
|
||||
. $PSScriptRoot/_IntegrationHelper.ps1
|
||||
Connect-TestPve
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
if (-not $script:SkipReason) {
|
||||
# Clean up users created during this file
|
||||
foreach ($userId in @('pester-user@pam', 'pester-tokenuser@pam', 'pester-permuser@pam')) {
|
||||
try { Remove-PveUser -UserId $userId -Confirm:$false -ErrorAction SilentlyContinue } catch { }
|
||||
}
|
||||
# Clean up roles
|
||||
try { Remove-PveRole -RoleId 'PesterTestRole' -Confirm:$false -ErrorAction SilentlyContinue } catch { }
|
||||
}
|
||||
Disconnect-TestPve
|
||||
}
|
||||
|
||||
Describe 'Users — Integration' -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
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
$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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
Context 'API Token CRUD' {
|
||||
BeforeAll {
|
||||
if ($null -eq $script:SkipReason) {
|
||||
# Idempotent setup: remove existing token/user first if they exist
|
||||
try { Remove-PveApiToken -UserId 'pester-tokenuser@pam' -TokenId 'pester-token' -Confirm:$false -ErrorAction SilentlyContinue } catch { }
|
||||
try { Remove-PveUser -UserId 'pester-tokenuser@pam' -Confirm:$false -ErrorAction SilentlyContinue } catch { }
|
||||
|
||||
New-PveUser -UserId 'pester-tokenuser@pam' -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
if ($null -eq $script:SkipReason) {
|
||||
# Idempotent setup
|
||||
try { Remove-PveUser -UserId 'pester-permuser@pam' -Confirm:$false -ErrorAction SilentlyContinue } catch { }
|
||||
New-PveUser -UserId 'pester-permuser@pam' -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
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' `
|
||||
-UgId 'pester-permuser@pam' `
|
||||
-Type 'user' `
|
||||
-ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
#Requires -Module Pester
|
||||
|
||||
BeforeAll {
|
||||
. $PSScriptRoot/_IntegrationHelper.ps1
|
||||
Connect-TestPve
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
if (-not $script:SkipReason) {
|
||||
# Clean up pester-store if it still exists
|
||||
try { Remove-PveStorage -Storage 'pester-store' -Confirm:$false -ErrorAction SilentlyContinue } catch { }
|
||||
}
|
||||
Disconnect-TestPve
|
||||
}
|
||||
|
||||
Describe 'Storage — Integration' -Tag 'Integration' {
|
||||
Context 'Storage' {
|
||||
It 'Should list storage' {
|
||||
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 (Skip-IfNoStorage) { return }
|
||||
|
||||
if (-not (Test-Path $script:IsoPath)) {
|
||||
Set-ItResult -Skipped -Because "ISO file not found at PVETEST_ISO_PATH: $($script:IsoPath)"
|
||||
return
|
||||
}
|
||||
|
||||
{
|
||||
Send-PveFile `
|
||||
-Node $script:Node `
|
||||
-Storage $script:Storage `
|
||||
-Path $script:IsoPath `
|
||||
-ErrorAction Stop
|
||||
} | Should -Not -Throw
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Storage — CRUD' {
|
||||
It 'Should create a directory storage (New-PveStorage)' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
|
||||
$result = New-PveStorage -Storage 'pester-store' -Type 'dir' `
|
||||
-Path '/tmp/pester-storage' -Content 'iso,vztmpl,backup' `
|
||||
-ErrorAction Stop
|
||||
|
||||
$result | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should list and find the new storage (Get-PveStorage)' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
|
||||
$storages = Get-PveStorage -Node $script:Node
|
||||
$storages | Where-Object { $_.Storage -eq 'pester-store' } |
|
||||
Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should remove the storage (Remove-PveStorage)' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
|
||||
{ Remove-PveStorage -Storage 'pester-store' -Confirm:$false -ErrorAction Stop } |
|
||||
Should -Not -Throw
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Storage — Download' {
|
||||
It 'Should download a file from URL to storage (Invoke-PveStorageDownload)' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
|
||||
$templateUrl = 'http://download.proxmox.com/images/system/alpine-3.20-default_20240908_amd64.tar.xz'
|
||||
$task = Invoke-PveStorageDownload -Node $script:Node -Storage $script:Storage `
|
||||
-Url $templateUrl -Filename 'alpine-3.20-default_20240908_amd64.tar.xz' `
|
||||
-ContentType 'vztmpl' -Wait -ErrorAction Stop
|
||||
|
||||
$task | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-45
@@ -17,41 +17,17 @@
|
||||
#>
|
||||
|
||||
BeforeAll {
|
||||
. $PSScriptRoot/../_TestHelper.ps1
|
||||
|
||||
# --- Base integration vars ---
|
||||
$baseVars = @('PVETEST_HOST', 'PVETEST_PORT', 'PVETEST_APITOKEN', 'PVETEST_NODE')
|
||||
$script:SkipReason = $null
|
||||
|
||||
foreach ($var in $baseVars) {
|
||||
if (-not [System.Environment]::GetEnvironmentVariable($var)) {
|
||||
$script:SkipReason = "No live Proxmox VE target configured. Set: $($baseVars -join ', ')"
|
||||
break
|
||||
}
|
||||
}
|
||||
. $PSScriptRoot/_IntegrationHelper.ps1
|
||||
Connect-TestPve
|
||||
|
||||
# --- Shared storage vars ---
|
||||
$script:StorageVmIp = [System.Environment]::GetEnvironmentVariable('PVETEST_STORAGE_VM_IP')
|
||||
$script:IscsiIqn = [System.Environment]::GetEnvironmentVariable('PVETEST_ISCSI_IQN')
|
||||
$script:NfsExport = [System.Environment]::GetEnvironmentVariable('PVETEST_NFS_EXPORT')
|
||||
|
||||
$script:Host_ = [System.Environment]::GetEnvironmentVariable('PVETEST_HOST')
|
||||
$portEnv = [System.Environment]::GetEnvironmentVariable('PVETEST_PORT')
|
||||
$script:Port = [int]$(if ($portEnv) { $portEnv } else { '8006' })
|
||||
$script:ApiToken = [System.Environment]::GetEnvironmentVariable('PVETEST_APITOKEN')
|
||||
$script:Node = [System.Environment]::GetEnvironmentVariable('PVETEST_NODE')
|
||||
$script:StorageVmIp = $env:PVETEST_STORAGE_VM_IP
|
||||
$script:IscsiIqn = $env:PVETEST_ISCSI_IQN
|
||||
$script:NfsExport = $env:PVETEST_NFS_EXPORT
|
||||
|
||||
# Track created storages for cleanup
|
||||
$script:CreatedStorages = [System.Collections.Generic.List[string]]::new()
|
||||
|
||||
function script:Skip-IfNoTarget {
|
||||
if ($script:SkipReason) {
|
||||
Set-ItResult -Skipped -Because $script:SkipReason
|
||||
return $true
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
function script:Skip-IfNoSharedStorage {
|
||||
if (Skip-IfNoTarget) { return $true }
|
||||
if (-not $script:StorageVmIp -or -not $script:IscsiIqn -or -not $script:NfsExport) {
|
||||
@@ -68,26 +44,11 @@ AfterAll {
|
||||
try { Remove-PveStorage -Storage $name -Confirm:$false -ErrorAction Stop }
|
||||
catch { Write-Warning "Cleanup: failed to remove storage '$name': $_" }
|
||||
}
|
||||
Disconnect-TestPve
|
||||
}
|
||||
|
||||
Describe 'Shared Storage — Integration' -Tag 'Integration' {
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
Context 'Connection' {
|
||||
It 'Should connect to PVE node' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
|
||||
$session = Connect-PveServer `
|
||||
-Server $script:Host_ `
|
||||
-Port $script:Port `
|
||||
-ApiToken $script:ApiToken `
|
||||
-SkipCertificateCheck `
|
||||
-PassThru
|
||||
|
||||
$session | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
Context 'NFS Storage' {
|
||||
It 'Should create NFS storage' {
|
||||
@@ -0,0 +1,87 @@
|
||||
#Requires -Module Pester
|
||||
|
||||
BeforeAll {
|
||||
. $PSScriptRoot/_IntegrationHelper.ps1
|
||||
Connect-TestPve
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
if (-not $script:SkipReason) {
|
||||
# Clean up pester network bridge if it still exists
|
||||
try { Remove-PveNetwork -Node $script:Node -Iface 'vmbr99' -Confirm:$false -ErrorAction SilentlyContinue } catch { }
|
||||
try { Invoke-PveNetworkApply -Node $script:Node -Wait -ErrorAction SilentlyContinue } catch { }
|
||||
}
|
||||
Disconnect-TestPve
|
||||
}
|
||||
|
||||
Describe 'Network — Integration' -Tag 'Integration' {
|
||||
Context 'Network — Read' {
|
||||
It 'Should list networks' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
|
||||
$networks = Get-PveNetwork -Node $script:Node
|
||||
$networks | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should filter by interface type' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
|
||||
# Filter for bridges — every PVE node has at least vmbr0
|
||||
$bridges = Get-PveNetwork -Node $script:Node -Type 'bridge'
|
||||
$bridges | Should -Not -BeNullOrEmpty
|
||||
$bridges | ForEach-Object { $_.Type | Should -Be 'bridge' }
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Network — CRUD' {
|
||||
It 'Should create a Linux bridge' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
|
||||
{ New-PveNetwork `
|
||||
-Node $script:Node `
|
||||
-Iface 'vmbr99' `
|
||||
-Type 'bridge' `
|
||||
-Autostart `
|
||||
-ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should find the new bridge in pending changes' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
|
||||
$networks = Get-PveNetwork -Node $script:Node
|
||||
$networks | Where-Object { $_.Iface -eq 'vmbr99' } |
|
||||
Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should update bridge comments' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
|
||||
{ Set-PveNetwork `
|
||||
-Node $script:Node `
|
||||
-Iface 'vmbr99' `
|
||||
-Type 'bridge' `
|
||||
-Comments 'Created by Pester integration test' `
|
||||
-ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should remove the bridge' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
|
||||
{ Remove-PveNetwork `
|
||||
-Node $script:Node `
|
||||
-Iface 'vmbr99' `
|
||||
-Confirm:$false `
|
||||
-ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should revert pending changes (apply to baseline)' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
|
||||
# Apply reverts any pending changes back to the running config
|
||||
{ Invoke-PveNetworkApply `
|
||||
-Node $script:Node `
|
||||
-Wait `
|
||||
-ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
#Requires -Module Pester
|
||||
|
||||
BeforeAll {
|
||||
. $PSScriptRoot/_IntegrationHelper.ps1
|
||||
Connect-TestPve
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
if (-not $script:SkipReason) {
|
||||
# Clean up SDN resources in reverse dependency order
|
||||
try { Remove-PveSdnSubnet -Vnet 'pestervn' -Subnet 'pesterz-10.99.0.0-24' -Confirm:$false -ErrorAction SilentlyContinue } catch { }
|
||||
Start-Sleep -Seconds 2
|
||||
try { Remove-PveSdnVnet -Vnet 'pestervn' -Confirm:$false -ErrorAction SilentlyContinue } catch { }
|
||||
Start-Sleep -Seconds 2
|
||||
try { Remove-PveSdnZone -Zone 'pesterz' -Confirm:$false -ErrorAction SilentlyContinue } catch { }
|
||||
}
|
||||
Disconnect-TestPve
|
||||
}
|
||||
|
||||
Describe 'SDN — Integration' -Tag 'Integration' {
|
||||
BeforeAll {
|
||||
if ($null -eq $script:SkipReason) {
|
||||
# SDN requires PVE 8+. Check server version and skip if older.
|
||||
$detail = Test-PveConnection -Detailed
|
||||
if ($detail.ServerVersion.Major -lt 8) {
|
||||
$script:SkipSdn = 'SDN requires Proxmox VE 8.0 or later'
|
||||
} else {
|
||||
$script:SkipSdn = $null
|
||||
}
|
||||
} else {
|
||||
$script:SkipSdn = $script:SkipReason
|
||||
}
|
||||
}
|
||||
|
||||
Context 'SDN' {
|
||||
It 'Should create an SDN zone (New-PveSdnZone)' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
if ($script:SkipSdn) { Set-ItResult -Skipped -Because $script:SkipSdn; return }
|
||||
|
||||
{ New-PveSdnZone -Zone 'pesterz' -Type 'simple' -ErrorAction Stop } |
|
||||
Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should list SDN zones (Get-PveSdnZone)' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
if ($script:SkipSdn) { Set-ItResult -Skipped -Because $script:SkipSdn; return }
|
||||
|
||||
$zones = Get-PveSdnZone
|
||||
$zones | Should -Not -BeNullOrEmpty
|
||||
$zones | Where-Object { $_.Zone -eq 'pesterz' } |
|
||||
Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should create an SDN VNet (New-PveSdnVnet)' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
if ($script:SkipSdn) { Set-ItResult -Skipped -Because $script:SkipSdn; return }
|
||||
|
||||
{ New-PveSdnVnet -Vnet 'pestervn' -Zone 'pesterz' -ErrorAction Stop } |
|
||||
Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should list SDN VNets (Get-PveSdnVnet)' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
if ($script:SkipSdn) { Set-ItResult -Skipped -Because $script:SkipSdn; return }
|
||||
|
||||
$vnets = Get-PveSdnVnet
|
||||
$vnets | Should -Not -BeNullOrEmpty
|
||||
$vnets | Where-Object { $_.Vnet -eq 'pestervn' } |
|
||||
Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should create an SDN subnet (New-PveSdnSubnet)' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
if ($script:SkipSdn) { Set-ItResult -Skipped -Because $script:SkipSdn; return }
|
||||
|
||||
{ New-PveSdnSubnet -Vnet 'pestervn' -Subnet '10.99.0.0/24' -ErrorAction Stop } |
|
||||
Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should list SDN subnets (Get-PveSdnSubnet)' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
if ($script:SkipSdn) { Set-ItResult -Skipped -Because $script:SkipSdn; return }
|
||||
|
||||
$subnets = Get-PveSdnSubnet -Vnet 'pestervn'
|
||||
$subnets | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should remove SDN subnet (Remove-PveSdnSubnet)' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
if ($script:SkipSdn) { Set-ItResult -Skipped -Because $script:SkipSdn; return }
|
||||
|
||||
# PVE stores subnet IDs in the format: {zone}-{ip}-{prefix}
|
||||
{ Remove-PveSdnSubnet -Vnet 'pestervn' -Subnet 'pesterz-10.99.0.0-24' `
|
||||
-Confirm:$false -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should remove SDN VNet (Remove-PveSdnVnet)' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
if ($script:SkipSdn) { Set-ItResult -Skipped -Because $script:SkipSdn; return }
|
||||
|
||||
# Allow PVE to propagate subnet removal
|
||||
Start-Sleep -Seconds 3
|
||||
{ Remove-PveSdnVnet -Vnet 'pestervn' -Confirm:$false -ErrorAction Stop } |
|
||||
Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should remove SDN zone (Remove-PveSdnZone)' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
if ($script:SkipSdn) { Set-ItResult -Skipped -Because $script:SkipSdn; return }
|
||||
|
||||
Start-Sleep -Seconds 2
|
||||
{ Remove-PveSdnZone -Zone 'pesterz' -Confirm:$false -ErrorAction Stop } |
|
||||
Should -Not -Throw
|
||||
}
|
||||
}
|
||||
|
||||
Context 'SDN — IPAM, DNS, Controllers' {
|
||||
It 'Should list SDN IPAM plugins (Get-PveSdnIpam)' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
if ($script:SkipSdn) { Set-ItResult -Skipped -Because $script:SkipSdn; return }
|
||||
|
||||
# PVE always has a built-in 'pve' IPAM plugin
|
||||
$ipams = Get-PveSdnIpam
|
||||
$ipams | Should -Not -BeNullOrEmpty
|
||||
($ipams | Where-Object { $_.Type -eq 'pve' }) | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should list SDN DNS plugins (Get-PveSdnDns)' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
if ($script:SkipSdn) { Set-ItResult -Skipped -Because $script:SkipSdn; return }
|
||||
|
||||
# Zero DNS plugins is acceptable; just verify no throw.
|
||||
{ Get-PveSdnDns -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should list SDN controllers (Get-PveSdnController)' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
if ($script:SkipSdn) { Set-ItResult -Skipped -Because $script:SkipSdn; return }
|
||||
|
||||
# Zero controllers is acceptable; just verify no throw.
|
||||
{ Get-PveSdnController -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
#Requires -Module Pester
|
||||
|
||||
BeforeAll {
|
||||
. $PSScriptRoot/_IntegrationHelper.ps1
|
||||
Connect-TestPve
|
||||
|
||||
$script:TestVmId = $null
|
||||
|
||||
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 {
|
||||
if (-not $script:SkipReason -and $script:TestVmId) {
|
||||
# Stop and remove pester-test-vm and any clone
|
||||
foreach ($vmId in @($script:TestVmId, ($script:TestVmId + 1000))) {
|
||||
try {
|
||||
Stop-PveVm -Node $script:Node -VmId $vmId -Confirm:$false -ErrorAction SilentlyContinue | Out-Null
|
||||
Start-Sleep -Seconds 3
|
||||
Remove-PveVm -Node $script:Node -VmId $vmId -Force -Purge -Confirm:$false -ErrorAction SilentlyContinue
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
Disconnect-TestPve
|
||||
}
|
||||
|
||||
Describe 'VMs — Integration' -Tag 'Integration' {
|
||||
Context 'VMs' {
|
||||
It 'Should list VMs' {
|
||||
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 (Skip-IfNoTarget) { 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:TestVmId = $vm.VmId
|
||||
Register-TestResource -Name 'pester-test-vm' -VmId $vm.VmId
|
||||
}
|
||||
|
||||
It 'Should get and set VM config' {
|
||||
if (Skip-IfNoTestVm) { return }
|
||||
|
||||
$config = Get-PveVmConfig -Node $script:Node -VmId $script:TestVmId
|
||||
$config | Should -Not -BeNullOrEmpty
|
||||
|
||||
{ Set-PveVmConfig `
|
||||
-Node $script:Node `
|
||||
-VmId $script:TestVmId `
|
||||
-Description 'Updated by Pester integration test' `
|
||||
-ErrorAction Stop } | Should -Not -Throw
|
||||
|
||||
$updated = Get-PveVmConfig -Node $script:Node -VmId $script:TestVmId
|
||||
$updated.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 -Timeout 30
|
||||
$startTask | Should -Not -BeNullOrEmpty
|
||||
|
||||
$stopTask = Stop-PveVm -Node $script:Node -VmId $script:TestVmId -Wait -Timeout 30 -Confirm:$false
|
||||
$stopTask | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should hard-reset a running VM' {
|
||||
if (Skip-IfNoTestVm) { return }
|
||||
|
||||
# Start the VM first
|
||||
Start-PveVm -Node $script:Node -VmId $script:TestVmId -Wait -Timeout 30 | Out-Null
|
||||
|
||||
# Hard reset (no ACPI — works even without guest OS)
|
||||
$task = Reset-PveVm -Node $script:Node -VmId $script:TestVmId -Wait -Timeout 30 -Confirm:$false
|
||||
$task | Should -Not -BeNullOrEmpty
|
||||
|
||||
Stop-PveVm -Node $script:Node -VmId $script:TestVmId -Wait -Timeout 30 -Confirm:$false | Out-Null
|
||||
}
|
||||
|
||||
It 'Should clone a VM' {
|
||||
if (Skip-IfNoTestVm) { return }
|
||||
|
||||
# Pick a high VMID for the clone to avoid collisions
|
||||
$cloneId = $script:TestVmId + 1000
|
||||
|
||||
$task = Copy-PveVm `
|
||||
-SourceNode $script:Node `
|
||||
-VmId $script:TestVmId `
|
||||
-NewVmId $cloneId `
|
||||
-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
|
||||
}
|
||||
}
|
||||
|
||||
Context 'VM — Suspend / Resume / Resize' {
|
||||
# Suspend/Resume is tested on the Linux VM (Guest Agent VM — Lifecycle)
|
||||
# because an empty VM with no OS may not reliably transition to 'paused'.
|
||||
|
||||
It 'Should resize a VM disk (Resize-PveVmDisk)' {
|
||||
if (Skip-IfNoTestVm) { return }
|
||||
|
||||
# First add a small disk via Set-PveVmConfig
|
||||
{ Set-PveVmConfig -Node $script:Node -VmId $script:TestVmId `
|
||||
-AdditionalConfig @{ scsi0 = 'local-lvm:1' } `
|
||||
-ErrorAction Stop } | Should -Not -Throw
|
||||
|
||||
# Resize the disk by +1G
|
||||
$task = Resize-PveVmDisk -Node $script:Node -VmId $script:TestVmId `
|
||||
-Disk 'scsi0' -Size '+1G'
|
||||
$task | Should -Not -BeNullOrEmpty
|
||||
|
||||
# Verify config shows scsi0 exists (larger disk)
|
||||
$config = Get-PveVmConfig -Node $script:Node -VmId $script:TestVmId
|
||||
$config | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
#Requires -Module Pester
|
||||
|
||||
BeforeAll {
|
||||
. $PSScriptRoot/_IntegrationHelper.ps1
|
||||
Connect-TestPve
|
||||
|
||||
# Find the test VM created by 06_VMs.Tests.ps1
|
||||
$script:TestVmId = Find-TestVm
|
||||
|
||||
function script:Skip-IfNoTestVm {
|
||||
if (Skip-IfNoTarget) { return $true }
|
||||
if ($null -eq $script:TestVmId) {
|
||||
Set-ItResult -Skipped -Because 'No test VM found (pester-test-vm must exist from 06_VMs)'
|
||||
return $true
|
||||
}
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
# Snapshot cleanup is inline (remove within the test).
|
||||
# No VM cleanup here — 06_VMs owns pester-test-vm.
|
||||
Disconnect-TestPve
|
||||
}
|
||||
|
||||
Describe 'Snapshots — Integration' -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 -Timeout 30 -Confirm:$false | 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#Requires -Module Pester
|
||||
|
||||
BeforeAll {
|
||||
. $PSScriptRoot/_IntegrationHelper.ps1
|
||||
Connect-TestPve
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
Disconnect-TestPve
|
||||
}
|
||||
|
||||
Describe 'Templates — Integration' -Tag 'Integration' {
|
||||
Context 'Templates' {
|
||||
It 'Should list templates' {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#Requires -Module Pester
|
||||
|
||||
BeforeAll {
|
||||
. $PSScriptRoot/_IntegrationHelper.ps1
|
||||
Connect-TestPve
|
||||
|
||||
# Find a test VM for cloud-init config read test
|
||||
$script:TestVmId = Find-TestVm
|
||||
$script:LinuxVmId = Find-TestVm -Name 'pester-linux-vm'
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
# Read-only tests — no cleanup needed
|
||||
Disconnect-TestPve
|
||||
}
|
||||
|
||||
Describe 'Cloud-Init — Integration' -Tag 'Integration' {
|
||||
Context 'Cloud-Init' {
|
||||
It 'Should get cloud-init config' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
if ($null -eq $script:TestVmId) {
|
||||
Set-ItResult -Skipped -Because 'No test VM found'
|
||||
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
|
||||
}
|
||||
|
||||
It 'Should regenerate cloud-init image (Invoke-PveCloudInitRegenerate)' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
if ($null -eq $script:LinuxVmId) {
|
||||
Set-ItResult -Skipped -Because 'Linux VM was not provisioned (PVETEST_PASSWORD may not be set)'
|
||||
return
|
||||
}
|
||||
|
||||
# The Linux VM has a cloud-init drive from provisioning
|
||||
{ Invoke-PveCloudInitRegenerate -Node $script:Node -VmId $script:LinuxVmId -Wait -ErrorAction Stop } |
|
||||
Should -Not -Throw
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
#Requires -Module Pester
|
||||
|
||||
BeforeAll {
|
||||
. $PSScriptRoot/_IntegrationHelper.ps1
|
||||
Connect-TestPve
|
||||
|
||||
$script:TestContainerId = $null
|
||||
$script:CreatedContainerIds = [System.Collections.Generic.List[int]]::new()
|
||||
|
||||
function script:Skip-IfNoTestContainer {
|
||||
if (Skip-IfNoTarget) { return $true }
|
||||
if ($null -eq $script:TestContainerId) {
|
||||
Set-ItResult -Skipped -Because 'No test container was created'
|
||||
return $true
|
||||
}
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
if ($null -eq $script:SkipReason) {
|
||||
foreach ($ctId in $script:CreatedContainerIds) {
|
||||
try {
|
||||
Stop-PveContainer -Node $script:Node -VmId $ctId -Confirm:$false -ErrorAction SilentlyContinue | Out-Null
|
||||
Start-Sleep -Seconds 3
|
||||
Remove-PveContainer -Node $script:Node -VmId $ctId -Force -Purge -Confirm:$false -ErrorAction SilentlyContinue
|
||||
}
|
||||
catch { <# non-fatal #> }
|
||||
}
|
||||
}
|
||||
Disconnect-TestPve
|
||||
}
|
||||
|
||||
Describe 'Containers — Integration' -Tag 'Integration' {
|
||||
|
||||
Context 'Containers' {
|
||||
BeforeAll {
|
||||
if ($null -eq $script:SkipReason) {
|
||||
# Download a small Alpine LXC template
|
||||
$templateUrl = 'http://download.proxmox.com/images/system/alpine-3.20-default_20240908_amd64.tar.xz'
|
||||
try {
|
||||
Invoke-PveStorageDownload -Node $script:Node -Storage $script:Storage `
|
||||
-Url $templateUrl -Filename 'alpine-3.20-default_20240908_amd64.tar.xz' `
|
||||
-ContentType 'vztmpl' -Wait -ErrorAction Stop
|
||||
} catch {
|
||||
# Template may already exist from a previous run or the Storage — Download context
|
||||
}
|
||||
}
|
||||
$script:TestContainerId = $null
|
||||
}
|
||||
|
||||
It 'Should list containers (Get-PveContainer)' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
|
||||
# Zero containers is acceptable; just verify the cmdlet does not throw.
|
||||
{ Get-PveContainer -Node $script:Node -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should create a container (New-PveContainer)' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
|
||||
$securePassword = ConvertTo-SecureString 'Pester12345!' -AsPlainText -Force
|
||||
|
||||
$task = New-PveContainer `
|
||||
-Node $script:Node `
|
||||
-Hostname 'pester-ct' `
|
||||
-Memory 128 `
|
||||
-Cores 1 `
|
||||
-RootFsStorage 'local-lvm' `
|
||||
-RootFsSize '1' `
|
||||
-OsTemplate "$($script:Storage):vztmpl/alpine-3.20-default_20240908_amd64.tar.xz" `
|
||||
-Password $securePassword `
|
||||
-Bridge 'vmbr0' `
|
||||
-Wait
|
||||
|
||||
$task | Should -Not -BeNullOrEmpty
|
||||
|
||||
# Retrieve the new container ID from the cluster
|
||||
$ct = Get-PveContainer -Node $script:Node -Name 'pester-ct' |
|
||||
Select-Object -First 1
|
||||
$ct | Should -Not -BeNullOrEmpty
|
||||
|
||||
$script:TestContainerId = $ct.VmId
|
||||
$script:CreatedContainerIds.Add($ct.VmId)
|
||||
}
|
||||
|
||||
It 'Should get container config (Get-PveContainerConfig)' {
|
||||
if (Skip-IfNoTestContainer) { return }
|
||||
|
||||
$config = Get-PveContainerConfig -Node $script:Node -VmId $script:TestContainerId
|
||||
$config | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should update container config (Set-PveContainerConfig)' {
|
||||
if (Skip-IfNoTestContainer) { return }
|
||||
|
||||
{ Set-PveContainerConfig -Node $script:Node -VmId $script:TestContainerId `
|
||||
-Description 'Updated by Pester integration test' `
|
||||
-ErrorAction Stop } | Should -Not -Throw
|
||||
|
||||
$config = Get-PveContainerConfig -Node $script:Node -VmId $script:TestContainerId
|
||||
$config.Description.Trim() | Should -Be 'Updated by Pester integration test'
|
||||
}
|
||||
|
||||
It 'Should start a container (Start-PveContainer)' {
|
||||
if (Skip-IfNoTestContainer) { return }
|
||||
|
||||
$task = Start-PveContainer -Node $script:Node -VmId $script:TestContainerId -Wait -Timeout 30
|
||||
$task | Should -Not -BeNullOrEmpty
|
||||
|
||||
$ct = Get-PveContainer -Node $script:Node -VmId $script:TestContainerId
|
||||
$ct.Status | Should -Be 'running'
|
||||
}
|
||||
|
||||
It 'Should stop a container (Stop-PveContainer)' {
|
||||
if (Skip-IfNoTestContainer) { return }
|
||||
|
||||
$task = Stop-PveContainer -Node $script:Node -VmId $script:TestContainerId -Wait -Timeout 30 -Confirm:$false
|
||||
$task | Should -Not -BeNullOrEmpty
|
||||
|
||||
$ct = Get-PveContainer -Node $script:Node -VmId $script:TestContainerId
|
||||
$ct.Status | Should -Be 'stopped'
|
||||
}
|
||||
|
||||
It 'Should restart a container (Restart-PveContainer)' {
|
||||
if (Skip-IfNoTestContainer) { return }
|
||||
|
||||
# Start first so we can restart
|
||||
Start-PveContainer -Node $script:Node -VmId $script:TestContainerId -Wait -Timeout 30 | Out-Null
|
||||
|
||||
$task = Restart-PveContainer -Node $script:Node -VmId $script:TestContainerId -Wait -Timeout 30 -Confirm:$false
|
||||
$task | Should -Not -BeNullOrEmpty
|
||||
|
||||
$ct = Get-PveContainer -Node $script:Node -VmId $script:TestContainerId
|
||||
$ct.Status | Should -Be 'running'
|
||||
|
||||
# Stop for subsequent tests
|
||||
Stop-PveContainer -Node $script:Node -VmId $script:TestContainerId -Wait -Timeout 30 -Confirm:$false | Out-Null
|
||||
}
|
||||
|
||||
It 'Should clone a container (Copy-PveContainer)' {
|
||||
if (Skip-IfNoTestContainer) { return }
|
||||
|
||||
$cloneId = $script:TestContainerId + 1000
|
||||
|
||||
$task = Copy-PveContainer `
|
||||
-SourceNode $script:Node `
|
||||
-VmId $script:TestContainerId `
|
||||
-NewVmId $cloneId `
|
||||
-NewName 'pester-clone-ct' `
|
||||
-Full `
|
||||
-Wait
|
||||
|
||||
$task | Should -Not -BeNullOrEmpty
|
||||
|
||||
$cloned = Get-PveContainer -Node $script:Node -Name 'pester-clone-ct' |
|
||||
Select-Object -First 1
|
||||
$cloned | Should -Not -BeNullOrEmpty
|
||||
|
||||
$script:CreatedContainerIds.Add($cloned.VmId)
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Container Snapshots' {
|
||||
It 'Should create a container snapshot (New-PveContainerSnapshot)' {
|
||||
if (Skip-IfNoTestContainer) { return }
|
||||
|
||||
# Ensure container is stopped
|
||||
$ct = Get-PveContainer -Node $script:Node -VmId $script:TestContainerId
|
||||
if ($ct.Status -eq 'running') {
|
||||
Stop-PveContainer -Node $script:Node -VmId $script:TestContainerId -Wait -Timeout 30 -Confirm:$false | Out-Null
|
||||
}
|
||||
|
||||
$task = New-PveContainerSnapshot `
|
||||
-Node $script:Node `
|
||||
-VmId $script:TestContainerId `
|
||||
-Name 'pester-ct-snap' `
|
||||
-Description 'Created by Pester integration test' `
|
||||
-Wait
|
||||
|
||||
$task | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should list container snapshots (Get-PveContainerSnapshot)' {
|
||||
if (Skip-IfNoTestContainer) { return }
|
||||
|
||||
$snapshots = Get-PveContainerSnapshot -Node $script:Node -VmId $script:TestContainerId
|
||||
$snap = $snapshots | Where-Object { $_.Name -eq 'pester-ct-snap' }
|
||||
$snap | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should restore a container snapshot (Restore-PveContainerSnapshot)' {
|
||||
if (Skip-IfNoTestContainer) { return }
|
||||
|
||||
{ Restore-PveContainerSnapshot `
|
||||
-Node $script:Node `
|
||||
-VmId $script:TestContainerId `
|
||||
-Name 'pester-ct-snap' `
|
||||
-Confirm:$false `
|
||||
-Wait } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should remove a container snapshot (Remove-PveContainerSnapshot)' {
|
||||
if (Skip-IfNoTestContainer) { return }
|
||||
|
||||
$task = Remove-PveContainerSnapshot `
|
||||
-Node $script:Node `
|
||||
-VmId $script:TestContainerId `
|
||||
-Name 'pester-ct-snap' `
|
||||
-Confirm:$false `
|
||||
-Wait
|
||||
|
||||
$task | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
#Requires -Module Pester
|
||||
|
||||
BeforeAll {
|
||||
. $PSScriptRoot/_IntegrationHelper.ps1
|
||||
Connect-TestPve
|
||||
|
||||
$script:LinuxVmId = $null
|
||||
$script:CreatedVmIds = [System.Collections.Generic.List[int]]::new()
|
||||
|
||||
function script:Skip-IfNoLinuxVm {
|
||||
if (Skip-IfNoTarget) { return $true }
|
||||
if ($null -eq $script:LinuxVmId) {
|
||||
Set-ItResult -Skipped -Because 'Linux VM was not provisioned (PVETEST_PASSWORD may not be set)'
|
||||
return $true
|
||||
}
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
if ($null -eq $script:SkipReason) {
|
||||
foreach ($vmId in $script:CreatedVmIds) {
|
||||
try {
|
||||
Stop-PveVm -Node $script:Node -VmId $vmId -Confirm:$false -ErrorAction SilentlyContinue | Out-Null
|
||||
Start-Sleep -Seconds 3
|
||||
Remove-PveVm -Node $script:Node -VmId $vmId -Force -Purge -Confirm:$false -ErrorAction SilentlyContinue
|
||||
}
|
||||
catch { <# non-fatal #> }
|
||||
}
|
||||
}
|
||||
Disconnect-TestPve
|
||||
}
|
||||
|
||||
Describe 'Linux VM — Integration' -Tag 'Integration' {
|
||||
|
||||
Context 'Linux VM — Provisioning' {
|
||||
It 'Should upload cloud image to PVE storage (Send-PveFile)' {
|
||||
if (Skip-IfNoPassword) { return }
|
||||
|
||||
$task = Send-PveFile `
|
||||
-Node $script:Node -Storage $script:Storage `
|
||||
-Path $script:CloudImagePath `
|
||||
-ContentType 'import' -Wait
|
||||
|
||||
$task | Should -Not -BeNullOrEmpty
|
||||
$task.IsSuccessful | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should create a Linux VM (New-PveVm)' {
|
||||
if (Skip-IfNoPassword) { return }
|
||||
|
||||
$task = New-PveVm `
|
||||
-Node $script:Node `
|
||||
-Name 'pester-linux-vm' `
|
||||
-Memory 512 -Cores 1 -OsType 'l26' -Wait
|
||||
|
||||
$task | Should -Not -BeNullOrEmpty
|
||||
|
||||
$vm = Get-PveVm -Node $script:Node -Name 'pester-linux-vm' |
|
||||
Select-Object -First 1
|
||||
$vm | Should -Not -BeNullOrEmpty
|
||||
|
||||
$script:LinuxVmId = $vm.VmId
|
||||
$script:CreatedVmIds.Add($vm.VmId)
|
||||
Register-TestResource -Name 'pester-linux-vm' -VmId $vm.VmId
|
||||
}
|
||||
|
||||
It 'Should import cloud image disk (Import-PveVmDisk)' {
|
||||
if (Skip-IfNoLinuxVm) { return }
|
||||
|
||||
$cloudImageFilename = [System.IO.Path]::GetFileName($script:CloudImagePath)
|
||||
$task = Import-PveVmDisk `
|
||||
-Node $script:Node -VmId $script:LinuxVmId `
|
||||
-Disk 'scsi0' -TargetStorage 'local-lvm' `
|
||||
-Source "$($script:Storage):import/$cloudImageFilename" `
|
||||
-Wait
|
||||
|
||||
$task | Should -Not -BeNullOrEmpty
|
||||
$task.IsSuccessful | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should configure VM hardware (Set-PveVmConfig)' {
|
||||
if (Skip-IfNoLinuxVm) { return }
|
||||
|
||||
{ Set-PveVmConfig -Node $script:Node -VmId $script:LinuxVmId `
|
||||
-AdditionalConfig @{
|
||||
scsihw = 'virtio-scsi-single'
|
||||
boot = 'order=scsi0'
|
||||
serial0 = 'socket'
|
||||
agent = '1'
|
||||
net0 = 'virtio,bridge=vmbr0'
|
||||
ide2 = 'local-lvm:cloudinit'
|
||||
cicustom = "user=$($script:Storage):snippets/test-vm-userdata.yml"
|
||||
} -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should set cloud-init config (Set-PveCloudInitConfig)' {
|
||||
if (Skip-IfNoLinuxVm) { return }
|
||||
|
||||
{ Set-PveCloudInitConfig -Node $script:Node -VmId $script:LinuxVmId `
|
||||
-CiUser 'root' `
|
||||
-Password (ConvertTo-SecureString $script:Password -AsPlainText -Force) `
|
||||
-IpConfig0 'ip=dhcp' `
|
||||
-ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should start the Linux VM (Start-PveVm)' {
|
||||
if (Skip-IfNoLinuxVm) { return }
|
||||
|
||||
$task = Start-PveVm -Node $script:Node -VmId $script:LinuxVmId -Wait -Timeout 30
|
||||
$task | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should wait for guest agent (Test-PveVmGuestAgent)' {
|
||||
if (Skip-IfNoLinuxVm) { return }
|
||||
|
||||
$timeout = 300; $elapsed = 0
|
||||
$agentReady = $false
|
||||
while ($elapsed -lt $timeout) {
|
||||
if (Test-PveVmGuestAgent -Node $script:Node -VmId $script:LinuxVmId) {
|
||||
$agentReady = $true
|
||||
break
|
||||
}
|
||||
Start-Sleep -Seconds 10
|
||||
$elapsed += 10
|
||||
}
|
||||
|
||||
$agentReady | Should -BeTrue -Because "Guest agent should respond within ${timeout}s"
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Guest Agent — Cmdlets' {
|
||||
It 'Should ping guest agent (Test-PveVmGuestAgent)' {
|
||||
if (Skip-IfNoLinuxVm) { return }
|
||||
|
||||
$result = Test-PveVmGuestAgent -Node $script:Node -VmId $script:LinuxVmId
|
||||
$result | Should -BeTrue
|
||||
}
|
||||
|
||||
It 'Should discover guest network interfaces (Get-PveVmGuestNetwork)' {
|
||||
if (Skip-IfNoLinuxVm) { return }
|
||||
|
||||
$interfaces = Get-PveVmGuestNetwork -Node $script:Node -VmId $script:LinuxVmId
|
||||
$interfaces | Should -Not -BeNullOrEmpty
|
||||
|
||||
# Should have at least one interface with an IPv4 address
|
||||
$withIp = $interfaces | Where-Object {
|
||||
$_.IpAddresses | Where-Object { $_.Type -eq 'ipv4' -and $_.Address -ne '127.0.0.1' }
|
||||
}
|
||||
$withIp | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should execute a command in guest (Invoke-PveVmGuestExec)' {
|
||||
if (Skip-IfNoLinuxVm) { return }
|
||||
|
||||
$result = Invoke-PveVmGuestExec -Node $script:Node -VmId $script:LinuxVmId -Command 'hostname'
|
||||
$result | Should -Not -BeNullOrEmpty
|
||||
$result.ExitCode | Should -Be 0
|
||||
$result.Stdout | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Guest Agent VM — Lifecycle' {
|
||||
It 'Should have a running Linux VM with guest agent' {
|
||||
if (Skip-IfNoLinuxVm) { return }
|
||||
|
||||
$vm = Get-PveVm -Node $script:Node |
|
||||
Where-Object { $_.VmId -eq $script:LinuxVmId }
|
||||
$vm | Should -Not -BeNullOrEmpty
|
||||
$vm.Status | Should -Be 'running'
|
||||
}
|
||||
|
||||
It 'Should suspend and resume a running VM (Suspend-PveVm / Resume-PveVm)' {
|
||||
if (Skip-IfNoLinuxVm) { return }
|
||||
|
||||
# Suspend — -Wait -Timeout polls qmpstatus until 'paused'
|
||||
$suspendTask = Suspend-PveVm -Node $script:Node -VmId $script:LinuxVmId -Wait -Timeout 30 -Confirm:$false
|
||||
$suspendTask | Should -Not -BeNullOrEmpty
|
||||
|
||||
# Verify with -Detailed (fetches qmpstatus from status/current endpoint)
|
||||
$vm = Get-PveVm -Node $script:Node -VmId $script:LinuxVmId -Detailed
|
||||
$vm.EffectiveStatus | Should -Be 'paused'
|
||||
|
||||
# Resume — -Wait -Timeout polls qmpstatus until 'running'
|
||||
$resumeTask = Resume-PveVm -Node $script:Node -VmId $script:LinuxVmId -Wait -Timeout 30
|
||||
$resumeTask | Should -Not -BeNullOrEmpty
|
||||
|
||||
$vm = Get-PveVm -Node $script:Node -VmId $script:LinuxVmId -Detailed
|
||||
$vm.EffectiveStatus | Should -Be 'running'
|
||||
}
|
||||
|
||||
It 'Should gracefully restart a VM via ACPI (Restart-PveVm)' {
|
||||
if (Skip-IfNoLinuxVm) { return }
|
||||
|
||||
# Ensure VM is running (resume if paused from a failed suspend test)
|
||||
try { Resume-PveVm -Node $script:Node -VmId $script:LinuxVmId -Wait -Timeout 10 -ErrorAction SilentlyContinue | Out-Null }
|
||||
catch { <# already running #> }
|
||||
|
||||
$task = Restart-PveVm -Node $script:Node -VmId $script:LinuxVmId -Wait -Timeout 60 -Confirm:$false
|
||||
$task | Should -Not -BeNullOrEmpty
|
||||
|
||||
$vm = Get-PveVm -Node $script:Node |
|
||||
Where-Object { $_.VmId -eq $script:LinuxVmId }
|
||||
$vm.Status | Should -Be 'running'
|
||||
}
|
||||
|
||||
It 'Should gracefully stop a VM via ACPI (Stop-PveVm)' {
|
||||
if (Skip-IfNoLinuxVm) { return }
|
||||
|
||||
$task = Stop-PveVm -Node $script:Node -VmId $script:LinuxVmId -Wait -Timeout 30 -Confirm:$false
|
||||
$task | Should -Not -BeNullOrEmpty
|
||||
|
||||
$vm = Get-PveVm -Node $script:Node |
|
||||
Where-Object { $_.VmId -eq $script:LinuxVmId }
|
||||
$vm.Status | Should -Be 'stopped'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Templates — Convert and Clone' {
|
||||
It 'Should convert the Linux VM to a template' {
|
||||
if (Skip-IfNoLinuxVm) { return }
|
||||
|
||||
# Ensure stopped first
|
||||
$vm = Get-PveVm -Node $script:Node |
|
||||
Where-Object { $_.VmId -eq $script:LinuxVmId }
|
||||
if ($vm.Status -eq 'running') {
|
||||
Stop-PveVm -Node $script:Node -VmId $script:LinuxVmId -Wait -Timeout 30 -Confirm:$false | Out-Null
|
||||
}
|
||||
|
||||
{ New-PveTemplate -Node $script:Node -VmId $script:LinuxVmId -Confirm:$false -ErrorAction Stop } |
|
||||
Should -Not -Throw
|
||||
|
||||
# Allow a moment for the template flag to propagate
|
||||
Start-Sleep -Seconds 3
|
||||
|
||||
# Verify it appears as a template
|
||||
$templates = Get-PveTemplate -Node $script:Node
|
||||
$templates | Where-Object { $_.VmId -eq $script:LinuxVmId } |
|
||||
Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should clone a VM from the template (New-PveVmFromTemplate)' {
|
||||
if (Skip-IfNoLinuxVm) { return }
|
||||
|
||||
$cloneId = $script:LinuxVmId + 1000
|
||||
|
||||
$task = New-PveVmFromTemplate `
|
||||
-TemplateNode $script:Node `
|
||||
-VmId $script:LinuxVmId `
|
||||
-NewVmId $cloneId `
|
||||
-NewName 'pester-from-template' `
|
||||
-Full `
|
||||
-Wait
|
||||
|
||||
$task | Should -Not -BeNullOrEmpty
|
||||
|
||||
$cloned = Get-PveVm -Node $script:Node -Name 'pester-from-template' |
|
||||
Select-Object -First 1
|
||||
$cloned | Should -Not -BeNullOrEmpty
|
||||
|
||||
# Track for cleanup
|
||||
$script:CreatedVmIds.Add($cloned.VmId)
|
||||
}
|
||||
|
||||
It 'Should remove a template (Remove-PveTemplate)' {
|
||||
if (Skip-IfNoLinuxVm) { return }
|
||||
|
||||
{ Remove-PveTemplate -Node $script:Node -VmId $script:LinuxVmId `
|
||||
-Confirm:$false -ErrorAction Stop } | Should -Not -Throw
|
||||
|
||||
$script:CreatedVmIds.Remove($script:LinuxVmId) | Out-Null
|
||||
$script:LinuxVmId = $null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
#Requires -Module Pester
|
||||
|
||||
BeforeAll {
|
||||
. $PSScriptRoot/_IntegrationHelper.ps1
|
||||
Connect-TestPve
|
||||
|
||||
$script:CreatedVmIds = [System.Collections.Generic.List[int]]::new()
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
if ($null -eq $script:SkipReason) {
|
||||
foreach ($vmId in $script:CreatedVmIds) {
|
||||
try {
|
||||
Stop-PveVm -Node $script:Node -VmId $vmId -Confirm:$false -ErrorAction SilentlyContinue | Out-Null
|
||||
Start-Sleep -Seconds 3
|
||||
Remove-PveVm -Node $script:Node -VmId $vmId -Force -Purge -Confirm:$false -ErrorAction SilentlyContinue
|
||||
}
|
||||
catch { <# non-fatal #> }
|
||||
}
|
||||
}
|
||||
Disconnect-TestPve
|
||||
}
|
||||
|
||||
Describe 'OVA Import — Integration' -Tag 'Integration' {
|
||||
|
||||
Context 'OVA Import' {
|
||||
It 'Should parse OVF metadata from an OVA (client-side)' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
if (-not $script:OvaPath -or -not (Test-Path $script:OvaPath)) {
|
||||
Set-ItResult -Skipped -Because 'PVETEST_OVA_PATH not set or file not found'
|
||||
return
|
||||
}
|
||||
|
||||
$metadata = [PSProxmoxVE.Core.Models.Vms.OvfMetadata]::FromOva($script:OvaPath)
|
||||
$metadata | Should -Not -BeNullOrEmpty
|
||||
$metadata.Name | Should -Not -BeNullOrEmpty
|
||||
$metadata.CpuCount | Should -BeGreaterThan 0
|
||||
$metadata.MemoryMB | Should -BeGreaterThan 0
|
||||
$metadata.Disks.Count | Should -BeGreaterThan 0
|
||||
}
|
||||
|
||||
It 'Should import OVA as a VM with correct metadata (Import-PveOva)' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
if (-not $script:OvaPath -or -not (Test-Path $script:OvaPath)) {
|
||||
Set-ItResult -Skipped -Because 'PVETEST_OVA_PATH not set or file not found'
|
||||
return
|
||||
}
|
||||
|
||||
$vm = Import-PveOva -Node $script:Node -Storage $script:Storage `
|
||||
-Path $script:OvaPath -TargetStorage 'local-lvm' `
|
||||
-Name 'pester-ova-vm' -Wait
|
||||
|
||||
$vm | Should -Not -BeNullOrEmpty
|
||||
$script:CreatedVmIds.Add($vm.VmId)
|
||||
|
||||
# Verify VM exists with correct name
|
||||
$found = Get-PveVm -Node $script:Node -Name 'pester-ova-vm' | Select-Object -First 1
|
||||
$found | Should -Not -BeNullOrEmpty
|
||||
$found.Name | Should -Be 'pester-ova-vm'
|
||||
|
||||
# Verify config matches OVF metadata
|
||||
$config = Get-PveVmConfig -Node $script:Node -VmId $vm.VmId
|
||||
$config.Memory | Should -BeGreaterThan 0
|
||||
$config.Cores | Should -BeGreaterThan 0
|
||||
$config.OsType | Should -Be 'l26'
|
||||
$config.Scsi0 | Should -Not -BeNullOrEmpty -Because 'disk should be imported to scsi0'
|
||||
$config.Net0 | Should -Not -BeNullOrEmpty -Because 'network adapter should be configured'
|
||||
}
|
||||
|
||||
It 'Should start the OVA-imported VM' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
|
||||
$ovaVm = Get-PveVm -Node $script:Node -Name 'pester-ova-vm' -ErrorAction SilentlyContinue |
|
||||
Select-Object -First 1
|
||||
if (-not $ovaVm) {
|
||||
Set-ItResult -Skipped -Because 'OVA VM was not imported'
|
||||
return
|
||||
}
|
||||
|
||||
$task = Start-PveVm -Node $script:Node -VmId $ovaVm.VmId -Wait -Timeout 60
|
||||
$task | Should -Not -BeNullOrEmpty
|
||||
|
||||
$vm = Get-PveVm -Node $script:Node | Where-Object { $_.VmId -eq $ovaVm.VmId }
|
||||
$vm.Status | Should -Be 'running'
|
||||
|
||||
# Clean up
|
||||
Stop-PveVm -Node $script:Node -VmId $ovaVm.VmId -Wait -Timeout 30 -Confirm:$false | Out-Null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
#Requires -Module Pester
|
||||
|
||||
BeforeAll {
|
||||
. $PSScriptRoot/_IntegrationHelper.ps1
|
||||
Connect-TestPve
|
||||
|
||||
$script:FirewallTestRulePos = $null
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
if ($null -eq $script:SkipReason) {
|
||||
# Cleanup: firewall test artifacts
|
||||
try {
|
||||
$rules = Get-PveFirewallRule -Level Cluster -ErrorAction SilentlyContinue
|
||||
$testRules = $rules | Where-Object { $_.Comment -like 'pester-test*' }
|
||||
foreach ($r in $testRules) {
|
||||
Remove-PveFirewallRule -Level Cluster -Position $r.Pos -Confirm:$false -ErrorAction SilentlyContinue
|
||||
}
|
||||
} catch { }
|
||||
|
||||
# Cleanup: firewall aliases
|
||||
try {
|
||||
Remove-PveFirewallAlias -Level Cluster -Name 'pester-alias' -Confirm:$false -ErrorAction SilentlyContinue
|
||||
} catch { }
|
||||
|
||||
# Cleanup: firewall IP sets
|
||||
try {
|
||||
Remove-PveFirewallIpSet -Level Cluster -Name 'pester-ipset' -Confirm:$false -ErrorAction SilentlyContinue
|
||||
} catch { }
|
||||
}
|
||||
Disconnect-TestPve
|
||||
}
|
||||
|
||||
Describe 'Firewall — Integration' -Tag 'Integration' {
|
||||
|
||||
Context 'Firewall — Cluster Rules' {
|
||||
It 'Should create a firewall rule' {
|
||||
Skip-IfNoTarget
|
||||
{ New-PveFirewallRule -Level Cluster -Type in -Action ACCEPT -Proto tcp -Dport '8080' -Comment 'pester-test-rule' -Enable -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should list firewall rules and find the test rule' {
|
||||
Skip-IfNoTarget
|
||||
$rules = Get-PveFirewallRule -Level Cluster
|
||||
$rules | Should -Not -BeNullOrEmpty
|
||||
$testRule = $rules | Where-Object { $_.Comment -eq 'pester-test-rule' }
|
||||
$testRule | Should -Not -BeNullOrEmpty
|
||||
$script:FirewallTestRulePos = $testRule.Pos
|
||||
}
|
||||
|
||||
It 'Should update the firewall rule' {
|
||||
Skip-IfNoTarget
|
||||
if ($null -eq $script:FirewallTestRulePos) {
|
||||
Set-ItResult -Skipped -Because 'No test rule was created'
|
||||
}
|
||||
{ Set-PveFirewallRule -Level Cluster -Position $script:FirewallTestRulePos -Comment 'pester-test-updated' -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should remove the firewall rule' {
|
||||
Skip-IfNoTarget
|
||||
if ($null -eq $script:FirewallTestRulePos) {
|
||||
Set-ItResult -Skipped -Because 'No test rule was created'
|
||||
}
|
||||
{ Remove-PveFirewallRule -Level Cluster -Position $script:FirewallTestRulePos -Confirm:$false -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should get firewall options' {
|
||||
Skip-IfNoTarget
|
||||
$opts = Get-PveFirewallOptions -Level Cluster
|
||||
$opts | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Firewall — Aliases and IP Sets' {
|
||||
It 'Should create a firewall alias' {
|
||||
Skip-IfNoTarget
|
||||
{ New-PveFirewallAlias -Level Cluster -Name 'pester-alias' -Cidr '192.168.99.0/24' -Comment 'test alias' -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should list and find the alias' {
|
||||
Skip-IfNoTarget
|
||||
$aliases = Get-PveFirewallAlias -Level Cluster
|
||||
$aliases | Where-Object { $_.Name -eq 'pester-alias' } | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should remove the alias' {
|
||||
Skip-IfNoTarget
|
||||
{ Remove-PveFirewallAlias -Level Cluster -Name 'pester-alias' -Confirm:$false -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should create an IP set' {
|
||||
Skip-IfNoTarget
|
||||
{ New-PveFirewallIpSet -Level Cluster -Name 'pester-ipset' -Comment 'test ipset' -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should add an entry to the IP set' {
|
||||
Skip-IfNoTarget
|
||||
{ New-PveFirewallIpSetEntry -Level Cluster -Name 'pester-ipset' -Cidr '10.99.0.0/16' -Comment 'test entry' -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should list IP set entries' {
|
||||
Skip-IfNoTarget
|
||||
$entries = Get-PveFirewallIpSetEntry -Level Cluster -Name 'pester-ipset'
|
||||
$entries | Should -Not -BeNullOrEmpty
|
||||
($entries | Where-Object { $_.Cidr -like '10.99.0.0*' }) | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should remove the IP set entry' {
|
||||
Skip-IfNoTarget
|
||||
{ Remove-PveFirewallIpSetEntry -Level Cluster -Name 'pester-ipset' -Cidr '10.99.0.0/16' -Confirm:$false -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should remove the IP set' {
|
||||
Skip-IfNoTarget
|
||||
{ Remove-PveFirewallIpSet -Level Cluster -Name 'pester-ipset' -Confirm:$false -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
#Requires -Module Pester
|
||||
|
||||
BeforeAll {
|
||||
. $PSScriptRoot/_IntegrationHelper.ps1
|
||||
Connect-TestPve
|
||||
|
||||
$script:BackupTestJobId = $null
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
if ($null -eq $script:SkipReason) {
|
||||
try {
|
||||
if ($script:BackupTestJobId) {
|
||||
Remove-PveBackupJob -Id $script:BackupTestJobId -Confirm:$false -ErrorAction SilentlyContinue
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
Disconnect-TestPve
|
||||
}
|
||||
|
||||
Describe 'Backup Jobs — Integration' -Tag 'Integration' {
|
||||
|
||||
Context 'Backup Jobs' {
|
||||
It 'Should create a backup job' {
|
||||
Skip-IfNoTarget
|
||||
{ New-PveBackupJob -Schedule 'sat 03:00' -Storage $script:Storage -Mode snapshot -All -Comment 'pester-test-backup' -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should list backup jobs and find the test job' {
|
||||
Skip-IfNoTarget
|
||||
$jobs = Get-PveBackupJob
|
||||
$testJob = $jobs | Where-Object { $_.Comment -eq 'pester-test-backup' }
|
||||
$testJob | Should -Not -BeNullOrEmpty
|
||||
$script:BackupTestJobId = $testJob.Id
|
||||
}
|
||||
|
||||
It 'Should update the backup job' {
|
||||
Skip-IfNoTarget
|
||||
if (-not $script:BackupTestJobId) {
|
||||
Set-ItResult -Skipped -Because 'No test backup job was created'
|
||||
}
|
||||
{ Set-PveBackupJob -Id $script:BackupTestJobId -Comment 'pester-test-updated' -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should remove the backup job' {
|
||||
Skip-IfNoTarget
|
||||
if (-not $script:BackupTestJobId) {
|
||||
Set-ItResult -Skipped -Because 'No test backup job was created'
|
||||
}
|
||||
{ Remove-PveBackupJob -Id $script:BackupTestJobId -Confirm:$false -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'Should verify the backup job was removed' {
|
||||
Skip-IfNoTarget
|
||||
if (-not $script:BackupTestJobId) {
|
||||
Set-ItResult -Skipped -Because 'No test backup job was created'
|
||||
}
|
||||
$jobs = Get-PveBackupJob
|
||||
$testJob = $jobs | Where-Object { $_.Id -eq $script:BackupTestJobId }
|
||||
$testJob | Should -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#Requires -Module Pester
|
||||
|
||||
BeforeAll {
|
||||
. $PSScriptRoot/_IntegrationHelper.ps1
|
||||
Connect-TestPve
|
||||
|
||||
# Find an existing test VM to use for task tests
|
||||
$script:TestVmId = Find-TestVm
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
# Read-only tests — no cleanup needed
|
||||
Disconnect-TestPve
|
||||
}
|
||||
|
||||
Describe 'Tasks — Integration' -Tag 'Integration' {
|
||||
|
||||
Context 'Tasks' {
|
||||
It 'Should get a task by UPID and wait for completion' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
if ($null -eq $script:TestVmId) {
|
||||
Set-ItResult -Skipped -Because 'No test VM was created'
|
||||
return
|
||||
}
|
||||
|
||||
# Start the VM to get a task object
|
||||
$startResult = Start-PveVm -Node $script:Node -VmId $script:TestVmId
|
||||
$startResult | Should -Not -BeNullOrEmpty
|
||||
$startResult.Upid | Should -Not -BeNullOrEmpty
|
||||
|
||||
# Wait for the task to complete
|
||||
{ Wait-PveTask -Node $script:Node -Upid $startResult.Upid } | Should -Not -Throw
|
||||
|
||||
# Get the task status
|
||||
$task = Get-PveTask -Node $script:Node -Upid $startResult.Upid
|
||||
$task | Should -Not -BeNullOrEmpty
|
||||
$task.IsSuccessful | Should -BeTrue
|
||||
|
||||
# Clean up
|
||||
Stop-PveVm -Node $script:Node -VmId $script:TestVmId -Wait -Timeout 30 -Confirm:$false | Out-Null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
#Requires -Module Pester
|
||||
|
||||
BeforeAll {
|
||||
. $PSScriptRoot/_IntegrationHelper.ps1
|
||||
Connect-TestPve
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
Disconnect-TestPve
|
||||
}
|
||||
|
||||
Describe 'Safety-Net Cleanup — Integration' -Tag 'Integration' {
|
||||
|
||||
Context 'Resource cleanup' {
|
||||
It 'Should remove all pester-* VMs' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
|
||||
$vms = Get-PveVm -Node $script:Node -ErrorAction SilentlyContinue
|
||||
$pesterVms = $vms | Where-Object { $_.Name -like 'pester-*' }
|
||||
foreach ($vm in $pesterVms) {
|
||||
try {
|
||||
Stop-PveVm -Node $script:Node -VmId $vm.VmId -Confirm:$false -ErrorAction SilentlyContinue | Out-Null
|
||||
Start-Sleep -Seconds 3
|
||||
} catch { }
|
||||
try {
|
||||
# Try removing as template first (templates need Remove-PveTemplate)
|
||||
Remove-PveTemplate -Node $script:Node -VmId $vm.VmId -Confirm:$false -ErrorAction SilentlyContinue
|
||||
} catch {
|
||||
try {
|
||||
Remove-PveVm -Node $script:Node -VmId $vm.VmId -Force -Purge -Confirm:$false -ErrorAction SilentlyContinue
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
|
||||
# Verify
|
||||
$remaining = Get-PveVm -Node $script:Node -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.Name -like 'pester-*' }
|
||||
$remaining | Should -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should remove all pester-* containers' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
|
||||
$containers = Get-PveContainer -Node $script:Node -ErrorAction SilentlyContinue
|
||||
$pesterCts = $containers | Where-Object { $_.Name -like 'pester-*' }
|
||||
foreach ($ct in $pesterCts) {
|
||||
try {
|
||||
Stop-PveContainer -Node $script:Node -VmId $ct.VmId -Confirm:$false -ErrorAction SilentlyContinue | Out-Null
|
||||
Start-Sleep -Seconds 3
|
||||
} catch { }
|
||||
try {
|
||||
Remove-PveContainer -Node $script:Node -VmId $ct.VmId -Force -Purge -Confirm:$false -ErrorAction SilentlyContinue
|
||||
} catch { }
|
||||
}
|
||||
|
||||
# Verify
|
||||
$remaining = Get-PveContainer -Node $script:Node -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.Name -like 'pester-*' }
|
||||
$remaining | Should -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'Should remove all pester-* users' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
|
||||
$users = Get-PveUser -ErrorAction SilentlyContinue
|
||||
$pesterUsers = $users | Where-Object { $_.UserId -like 'pester-*' }
|
||||
foreach ($user in $pesterUsers) {
|
||||
try {
|
||||
Remove-PveUser -UserId $user.UserId -Confirm:$false -ErrorAction SilentlyContinue
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
|
||||
It 'Should remove all pester-* roles' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
|
||||
$roles = Get-PveRole -ErrorAction SilentlyContinue
|
||||
$pesterRoles = $roles | Where-Object { $_.RoleId -like 'pester-*' -or $_.RoleId -like 'Pester*' }
|
||||
foreach ($role in $pesterRoles) {
|
||||
try {
|
||||
Remove-PveRole -RoleId $role.RoleId -Confirm:$false -ErrorAction SilentlyContinue
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
|
||||
It 'Should remove pester firewall rules, aliases, and IP sets' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
|
||||
# Firewall rules with pester comments
|
||||
try {
|
||||
$rules = Get-PveFirewallRule -Level Cluster -ErrorAction SilentlyContinue
|
||||
$testRules = $rules | Where-Object { $_.Comment -like 'pester-*' -or $_.Comment -like 'pester *' }
|
||||
# Remove in reverse position order to avoid position shifts
|
||||
$testRules | Sort-Object -Property Pos -Descending | ForEach-Object {
|
||||
Remove-PveFirewallRule -Level Cluster -Position $_.Pos -Confirm:$false -ErrorAction SilentlyContinue
|
||||
}
|
||||
} catch { }
|
||||
|
||||
# Firewall aliases
|
||||
try {
|
||||
$aliases = Get-PveFirewallAlias -Level Cluster -ErrorAction SilentlyContinue
|
||||
$pesterAliases = $aliases | Where-Object { $_.Name -like 'pester-*' }
|
||||
foreach ($alias in $pesterAliases) {
|
||||
Remove-PveFirewallAlias -Level Cluster -Name $alias.Name -Confirm:$false -ErrorAction SilentlyContinue
|
||||
}
|
||||
} catch { }
|
||||
|
||||
# Firewall IP sets
|
||||
try {
|
||||
$ipsets = Get-PveFirewallIpSet -Level Cluster -ErrorAction SilentlyContinue
|
||||
$pesterIpsets = $ipsets | Where-Object { $_.Name -like 'pester-*' }
|
||||
foreach ($ipset in $pesterIpsets) {
|
||||
# Remove entries first
|
||||
try {
|
||||
$entries = Get-PveFirewallIpSetEntry -Level Cluster -Name $ipset.Name -ErrorAction SilentlyContinue
|
||||
foreach ($entry in $entries) {
|
||||
Remove-PveFirewallIpSetEntry -Level Cluster -Name $ipset.Name -Cidr $entry.Cidr -Confirm:$false -ErrorAction SilentlyContinue
|
||||
}
|
||||
} catch { }
|
||||
Remove-PveFirewallIpSet -Level Cluster -Name $ipset.Name -Confirm:$false -ErrorAction SilentlyContinue
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
It 'Should remove pester backup jobs' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
|
||||
try {
|
||||
$jobs = Get-PveBackupJob -ErrorAction SilentlyContinue
|
||||
$pesterJobs = $jobs | Where-Object { $_.Comment -like 'pester-*' -or $_.Comment -like 'pester *' }
|
||||
foreach ($job in $pesterJobs) {
|
||||
Remove-PveBackupJob -Id $job.Id -Confirm:$false -ErrorAction SilentlyContinue
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
It 'Should remove pester SDN resources' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
|
||||
# Subnets first (depend on vnets)
|
||||
try {
|
||||
$vnets = Get-PveSdnVnet -ErrorAction SilentlyContinue
|
||||
$pesterVnets = $vnets | Where-Object { $_.Vnet -like 'pester*' }
|
||||
foreach ($vnet in $pesterVnets) {
|
||||
try {
|
||||
$subnets = Get-PveSdnSubnet -Vnet $vnet.Vnet -ErrorAction SilentlyContinue
|
||||
foreach ($subnet in $subnets) {
|
||||
Remove-PveSdnSubnet -Vnet $vnet.Vnet -Subnet $subnet.Subnet -Confirm:$false -ErrorAction SilentlyContinue
|
||||
}
|
||||
} catch { }
|
||||
Start-Sleep -Seconds 2
|
||||
Remove-PveSdnVnet -Vnet $vnet.Vnet -Confirm:$false -ErrorAction SilentlyContinue
|
||||
}
|
||||
} catch { }
|
||||
|
||||
# Zones
|
||||
try {
|
||||
$zones = Get-PveSdnZone -ErrorAction SilentlyContinue
|
||||
$pesterZones = $zones | Where-Object { $_.Zone -like 'pester*' }
|
||||
foreach ($zone in $pesterZones) {
|
||||
Start-Sleep -Seconds 2
|
||||
Remove-PveSdnZone -Zone $zone.Zone -Confirm:$false -ErrorAction SilentlyContinue
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
It 'Should remove pester storage definitions' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
|
||||
try {
|
||||
$storages = Get-PveStorage -Node $script:Node -ErrorAction SilentlyContinue
|
||||
$pesterStorages = $storages | Where-Object { $_.Storage -like 'pester-*' }
|
||||
foreach ($storage in $pesterStorages) {
|
||||
Remove-PveStorage -Storage $storage.Storage -Confirm:$false -ErrorAction SilentlyContinue
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
It 'Should remove pester network interfaces' {
|
||||
if (Skip-IfNoTarget) { return }
|
||||
|
||||
try {
|
||||
$networks = Get-PveNetwork -Node $script:Node -ErrorAction SilentlyContinue
|
||||
$pesterNets = $networks | Where-Object { $_.Iface -like 'pester*' -or ($_.Comments -and $_.Comments -like '*pester*') }
|
||||
foreach ($net in $pesterNets) {
|
||||
Remove-PveNetwork -Node $script:Node -Iface $net.Iface -Confirm:$false -ErrorAction SilentlyContinue
|
||||
}
|
||||
if ($pesterNets) {
|
||||
Invoke-PveNetworkApply -Node $script:Node -Wait -ErrorAction SilentlyContinue
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,187 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Shared setup helper for integration tests.
|
||||
Dot-source this in each integration test file's BeforeAll block.
|
||||
|
||||
.DESCRIPTION
|
||||
Provides:
|
||||
- Module loading via _TestHelper.ps1
|
||||
- Environment variable reading (PVETEST_*)
|
||||
- Skip helper functions
|
||||
- Connect-TestPve: establishes a credential-based connection if needed
|
||||
- Resource discovery helpers for cross-file test dependencies
|
||||
#>
|
||||
|
||||
# Load the module
|
||||
. $PSScriptRoot/../_TestHelper.ps1
|
||||
|
||||
# ── Environment variables ────────────────────────────────────────────────
|
||||
$script:Host_ = $env:PVETEST_HOST
|
||||
$script:Port = if ($env:PVETEST_PORT) { [int]$env:PVETEST_PORT } else { 8006 }
|
||||
$script:Node = $env:PVETEST_NODE
|
||||
$script:Password = $env:PVETEST_PASSWORD
|
||||
$script:Storage = $env:PVETEST_STORAGE
|
||||
$script:IsoPath = $env:PVETEST_ISO_PATH
|
||||
$script:PveVersion = $env:PVETEST_PVE_VERSION
|
||||
$script:CloudImagePath = $env:PVETEST_CLOUD_IMAGE_PATH
|
||||
$script:OvaPath = $env:PVETEST_OVA_PATH
|
||||
|
||||
# Multi-node (cluster tests)
|
||||
$script:HostB = $env:PVETEST_HOST_B
|
||||
$script:PasswordB = $env:PVETEST_PASSWORD_B # Falls back to Password if not set
|
||||
if (-not $script:PasswordB) { $script:PasswordB = $script:Password }
|
||||
|
||||
# ── Skip reason ──────────────────────────────────────────────────────────
|
||||
$script:SkipReason = $null
|
||||
|
||||
$requiredVars = @('PVETEST_HOST', 'PVETEST_NODE', 'PVETEST_PASSWORD')
|
||||
foreach ($var in $requiredVars) {
|
||||
if (-not [System.Environment]::GetEnvironmentVariable($var)) {
|
||||
$script:SkipReason = "Missing required env var: $var (need: $($requiredVars -join ', '))"
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
# ── Skip helpers ─────────────────────────────────────────────────────────
|
||||
|
||||
function script:Skip-IfNoTarget {
|
||||
if ($script:SkipReason) {
|
||||
Set-ItResult -Skipped -Because $script:SkipReason
|
||||
return $true
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
function script:Skip-IfNoStorage {
|
||||
if (Skip-IfNoTarget) { return $true }
|
||||
if (-not $script:Storage -or -not $script:IsoPath) {
|
||||
Set-ItResult -Skipped -Because 'PVETEST_STORAGE and PVETEST_ISO_PATH required'
|
||||
return $true
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
function script:Skip-IfNoPassword {
|
||||
if (Skip-IfNoTarget) { return $true }
|
||||
if (-not $script:Password -or -not $script:CloudImagePath) {
|
||||
Set-ItResult -Skipped -Because 'PVETEST_PASSWORD and PVETEST_CLOUD_IMAGE_PATH required'
|
||||
return $true
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
function script:Skip-IfNoOva {
|
||||
if (Skip-IfNoTarget) { return $true }
|
||||
if (-not $script:OvaPath) {
|
||||
Set-ItResult -Skipped -Because 'PVETEST_OVA_PATH required'
|
||||
return $true
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
function script:Skip-IfNoNodeB {
|
||||
if (Skip-IfNoTarget) { return $true }
|
||||
if (-not $script:HostB -or -not $script:Password) {
|
||||
Set-ItResult -Skipped -Because 'Multi-node env vars not set (PVETEST_HOST_B, PVETEST_PASSWORD)'
|
||||
return $true
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
function script:Skip-IfNoPve9 {
|
||||
if (Skip-IfNoTarget) { return $true }
|
||||
if ($script:PveVersion -and [int]$script:PveVersion -lt 9) {
|
||||
Set-ItResult -Skipped -Because 'Requires PVE 9.0+'
|
||||
return $true
|
||||
}
|
||||
# If version not set, try to detect
|
||||
if (-not $script:PveVersion) {
|
||||
try {
|
||||
$detail = Test-PveConnection -Detailed -ErrorAction Stop
|
||||
if ($detail.ServerVersion.Major -lt 9) {
|
||||
Set-ItResult -Skipped -Because "PVE version $($detail.ServerVersion) < 9.0"
|
||||
return $true
|
||||
}
|
||||
} catch {
|
||||
Set-ItResult -Skipped -Because 'Cannot determine PVE version'
|
||||
return $true
|
||||
}
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
# ── Connection helper ────────────────────────────────────────────────────
|
||||
|
||||
function script:Connect-TestPve {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Connects to the test PVE server using root@pam credentials.
|
||||
Reuses existing connection if already connected to the same host.
|
||||
#>
|
||||
if ($script:SkipReason) { return }
|
||||
|
||||
# Check if we're already connected to the right host
|
||||
try {
|
||||
if (Test-PveConnection) {
|
||||
$detail = Test-PveConnection -Detailed
|
||||
if ($detail.Hostname -eq $script:Host_) { return }
|
||||
}
|
||||
} catch { }
|
||||
|
||||
# Connect with credentials (not API token) — all cluster/privileged ops work
|
||||
$secPw = ConvertTo-SecureString $script:Password -AsPlainText -Force
|
||||
$cred = New-Object System.Management.Automation.PSCredential('root@pam', $secPw)
|
||||
Connect-PveServer `
|
||||
-Server $script:Host_ `
|
||||
-Port $script:Port `
|
||||
-Credential $cred `
|
||||
-SkipCertificateCheck
|
||||
}
|
||||
|
||||
function script:Disconnect-TestPve {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Disconnects the test PVE session if one was established by this file.
|
||||
#>
|
||||
try { Disconnect-PveServer -ErrorAction SilentlyContinue } catch { }
|
||||
}
|
||||
|
||||
# ── Resource discovery helpers ───────────────────────────────────────────
|
||||
# These allow later test files to find resources created by earlier files
|
||||
# without sharing script-scoped variables. If the resource doesn't exist,
|
||||
# the caller gets $null and should skip.
|
||||
|
||||
function script:Find-TestVm {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Finds the pester-test-vm by checking env state or querying PVE.
|
||||
#>
|
||||
param([string]$Name = 'pester-test-vm')
|
||||
|
||||
# Check env var first (set by the file that created it)
|
||||
$envKey = "PVETEST_STATE_$($Name.Replace('-','_').ToUpper())"
|
||||
$vmId = [System.Environment]::GetEnvironmentVariable($envKey)
|
||||
if ($vmId) { return [int]$vmId }
|
||||
|
||||
# Fall back to querying PVE
|
||||
try {
|
||||
$vms = Get-PveVm -Node $script:Node -ErrorAction Stop
|
||||
$match = $vms | Where-Object { $_.Name -eq $Name } | Select-Object -First 1
|
||||
if ($match) { return [int]$match.VmId }
|
||||
} catch { }
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function script:Register-TestResource {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Registers a resource VMID in env for cross-file discovery.
|
||||
#>
|
||||
param(
|
||||
[string]$Name,
|
||||
[int]$VmId
|
||||
)
|
||||
$envKey = "PVETEST_STATE_$($Name.Replace('-','_').ToUpper())"
|
||||
[System.Environment]::SetEnvironmentVariable($envKey, $VmId.ToString())
|
||||
}
|
||||
Reference in New Issue
Block a user