diff --git a/tests/PSProxmoxVE.Tests/CloudInit/CloudInitCmdlets.Tests.ps1 b/tests/PSProxmoxVE.Tests/CloudInit/CloudInitCmdlets.Tests.ps1 new file mode 100644 index 0000000..62e9f2c --- /dev/null +++ b/tests/PSProxmoxVE.Tests/CloudInit/CloudInitCmdlets.Tests.ps1 @@ -0,0 +1,246 @@ +#Requires -Module Pester +<# +.SYNOPSIS + Pester 5 tests for cloud-init cmdlets: + Get-PveCloudInitConfig, Set-PveCloudInitConfig, Invoke-PveCloudInitRegenerate. + + All tests are fully offline — no live Proxmox VE target is required. + If a cmdlet is not yet compiled the test is marked Skipped. + + Cloud-Init cmdlets operate on an existing QEMU VM's cloud-init drive. + The configuration fields map to the PveCloudInitConfig model: + CiUser, CiPassword, SshKeys, IpConfig0..3, Nameserver, + Searchdomain, CiCustom. +#> + +BeforeAll { + $moduleRoot = Resolve-Path (Join-Path $PSScriptRoot '../../../src/PSProxmoxVE') + $dllCandidates = @( + Join-Path $moduleRoot 'bin/Debug/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Debug/net48/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net48/PSProxmoxVE.dll' + ) + + $script:ModuleDll = $dllCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 + + if ($null -eq $script:ModuleDll) { + throw "PSProxmoxVE.dll not found. Build the project before running Pester tests." + } + + Import-Module $script:ModuleDll -Force -ErrorAction Stop + + $script:Availability = @{} + foreach ($name in @('Get-PveCloudInitConfig', 'Set-PveCloudInitConfig', + 'Invoke-PveCloudInitRegenerate')) { + $script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue) + } + + function Skip-IfMissing([string]$Name) { + if (-not $script:Availability[$Name]) { + Set-ItResult -Skipped -Because "$Name is not yet implemented in this build" + } + } +} + +# --------------------------------------------------------------------------- +# Manifest contract +# --------------------------------------------------------------------------- +Describe 'Cloud-Init cmdlets — manifest declarations' { + BeforeAll { + $manifestPath = Join-Path $moduleRoot 'PSProxmoxVE.psd1' + $script:Manifest = if (Test-Path $manifestPath) { Import-PowerShellDataFile $manifestPath } else { $null } + } + + foreach ($cmdName in @('Get-PveCloudInitConfig', 'Set-PveCloudInitConfig', + 'Invoke-PveCloudInitRegenerate')) { + It "$cmdName should be declared in CmdletsToExport" { + if ($null -eq $script:Manifest) { Set-ItResult -Skipped -Because 'Manifest not found'; return } + $script:Manifest.CmdletsToExport | Should -Contain $cmdName + } + } +} + +# --------------------------------------------------------------------------- +# Get-PveCloudInitConfig +# --------------------------------------------------------------------------- +Describe 'Get-PveCloudInitConfig' { + + BeforeAll { $script:Cmd = Get-Command 'Get-PveCloudInitConfig' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'Get-PveCloudInitConfig' + $script:Cmd | Should -Not -BeNullOrEmpty + } + + It 'Should be a CmdletInfo (binary cmdlet)' { + Skip-IfMissing 'Get-PveCloudInitConfig' + $script:Cmd.CommandType | Should -Be 'Cmdlet' + } + } + + Context 'Parameter metadata' { + It 'Node should be Mandatory' { + Skip-IfMissing 'Get-PveCloudInitConfig' + $isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'VmId should be Mandatory' { + Skip-IfMissing 'Get-PveCloudInitConfig' + $isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'Should have Session parameter' { + Skip-IfMissing 'Get-PveCloudInitConfig' + $script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue + } + } + + Context 'Without active session' { + It 'Should throw when no session is active' { + Skip-IfMissing 'Get-PveCloudInitConfig' + { Get-PveCloudInitConfig -Node 'pve-node1' -VmId 100 -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + } +} + +# --------------------------------------------------------------------------- +# Set-PveCloudInitConfig +# --------------------------------------------------------------------------- +Describe 'Set-PveCloudInitConfig' { + + BeforeAll { $script:Cmd = Get-Command 'Set-PveCloudInitConfig' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'Set-PveCloudInitConfig' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'ShouldProcess support' { + It 'Should support WhatIf' { + Skip-IfMissing 'Set-PveCloudInitConfig' + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + } + + Context 'Required parameters' { + It 'Node should be Mandatory' { + Skip-IfMissing 'Set-PveCloudInitConfig' + $isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'VmId should be Mandatory' { + Skip-IfMissing 'Set-PveCloudInitConfig' + $isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + } + + Context 'Optional cloud-init configuration parameters' { + It 'Should have CiUser parameter' { + Skip-IfMissing 'Set-PveCloudInitConfig' + $script:Cmd.Parameters.ContainsKey('CiUser') | Should -BeTrue + } + + It 'Should have CiPassword parameter (SecureString or plain-text)' { + Skip-IfMissing 'Set-PveCloudInitConfig' + $hasPassword = $script:Cmd.Parameters.ContainsKey('CiPassword') -or + $script:Cmd.Parameters.ContainsKey('Password') + $hasPassword | Should -BeTrue + } + + It 'Should have SshKeys parameter' { + Skip-IfMissing 'Set-PveCloudInitConfig' + $hasSsh = $script:Cmd.Parameters.ContainsKey('SshKeys') -or + $script:Cmd.Parameters.ContainsKey('SshPublicKey') + $hasSsh | Should -BeTrue + } + + It 'Should have IpConfig0 parameter' { + Skip-IfMissing 'Set-PveCloudInitConfig' + $script:Cmd.Parameters.ContainsKey('IpConfig0') | Should -BeTrue + } + + It 'Should have Nameserver parameter' { + Skip-IfMissing 'Set-PveCloudInitConfig' + $script:Cmd.Parameters.ContainsKey('Nameserver') | Should -BeTrue + } + + It 'Should have Searchdomain parameter' { + Skip-IfMissing 'Set-PveCloudInitConfig' + $script:Cmd.Parameters.ContainsKey('Searchdomain') | Should -BeTrue + } + } + + Context 'Without active session' { + It 'Should throw when no session is active (without -WhatIf)' { + Skip-IfMissing 'Set-PveCloudInitConfig' + { Set-PveCloudInitConfig -Node 'pve-node1' -VmId 100 -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + } +} + +# --------------------------------------------------------------------------- +# Invoke-PveCloudInitRegenerate +# --------------------------------------------------------------------------- +Describe 'Invoke-PveCloudInitRegenerate' { + + BeforeAll { $script:Cmd = Get-Command 'Invoke-PveCloudInitRegenerate' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'Invoke-PveCloudInitRegenerate' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'ShouldProcess support' { + It 'Should support WhatIf' { + Skip-IfMissing 'Invoke-PveCloudInitRegenerate' + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + } + + Context 'Required parameters' { + It 'Node should be Mandatory' { + Skip-IfMissing 'Invoke-PveCloudInitRegenerate' + $isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'VmId should be Mandatory' { + Skip-IfMissing 'Invoke-PveCloudInitRegenerate' + $isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + } + + Context 'Optional parameters' { + It 'Should have Wait switch parameter' { + Skip-IfMissing 'Invoke-PveCloudInitRegenerate' + $script:Cmd.Parameters.ContainsKey('Wait') | Should -BeTrue + } + } + + Context 'Without active session' { + It 'Should throw when no session is active (without -WhatIf)' { + Skip-IfMissing 'Invoke-PveCloudInitRegenerate' + { Invoke-PveCloudInitRegenerate -Node 'pve-node1' -VmId 100 -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + } +} diff --git a/tests/PSProxmoxVE.Tests/Connection/Connect-PveServer.Tests.ps1 b/tests/PSProxmoxVE.Tests/Connection/Connect-PveServer.Tests.ps1 new file mode 100644 index 0000000..112c678 --- /dev/null +++ b/tests/PSProxmoxVE.Tests/Connection/Connect-PveServer.Tests.ps1 @@ -0,0 +1,122 @@ +#Requires -Module Pester +<# +.SYNOPSIS + Pester 5 tests for Connect-PveServer. + All tests are fully offline — no live Proxmox VE target is required. + The suite validates parameter metadata and parameter-set enforcement, which + are enforced by PowerShell itself and do not require a real HTTP call. +#> + +BeforeAll { + # Resolve the built DLL relative to the repository root. + # Adjust the path if your build output directory differs. + $moduleRoot = Resolve-Path (Join-Path $PSScriptRoot '../../../src/PSProxmoxVE') + $dllCandidates = @( + Join-Path $moduleRoot 'bin/Debug/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Debug/net48/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net48/PSProxmoxVE.dll' + ) + + $script:ModuleDll = $dllCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 + + if ($null -eq $script:ModuleDll) { + throw "PSProxmoxVE.dll not found. Build the project before running Pester tests." + } + + Import-Module $script:ModuleDll -Force -ErrorAction Stop +} + +Describe 'Connect-PveServer' { + + Context 'Command existence' { + It 'Should be available after module import' { + Get-Command 'Connect-PveServer' -ErrorAction SilentlyContinue | + Should -Not -BeNullOrEmpty + } + + It 'Should be a CmdletInfo (binary cmdlet)' { + (Get-Command 'Connect-PveServer').CommandType | Should -Be 'Cmdlet' + } + } + + Context 'Parameter validation — missing required parameters' { + It 'Should throw when Server is omitted entirely' { + { Connect-PveServer -ErrorAction Stop } | Should -Throw + } + + It 'Should throw when Server is provided but neither Credential nor ApiToken is supplied' { + # Both parameter sets require one of Credential/ApiToken; omitting both is an error. + { Connect-PveServer -Server 'pve.example.com' -ErrorAction Stop } | Should -Throw + } + } + + Context 'Parameter validation — mutually exclusive parameter sets' { + It 'Should not allow both Credential and ApiToken together' { + $securePass = ConvertTo-SecureString 'hunter2' -AsPlainText -Force + $cred = [System.Management.Automation.PSCredential]::new('root@pam', $securePass) + { + Connect-PveServer ` + -Server 'pve.example.com' ` + -Credential $cred ` + -ApiToken 'root@pam!mytoken=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' ` + -ErrorAction Stop + } | Should -Throw + } + } + + Context 'Parameter metadata' { + BeforeAll { + $script:Cmd = Get-Command 'Connect-PveServer' + } + + It 'Should have a Server parameter' { + $script:Cmd.Parameters.ContainsKey('Server') | Should -BeTrue + } + + It 'Should declare Server as Mandatory' { + $serverParam = $script:Cmd.Parameters['Server'] + $isMandatory = $serverParam.ParameterSets.Values | + Where-Object { $_.IsMandatory } | + Select-Object -First 1 + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'Should have a Port parameter' { + $script:Cmd.Parameters.ContainsKey('Port') | Should -BeTrue + } + + It 'Port should default to 8006' { + # Verify default via the static default-value metadata on the parameter. + $portParam = $script:Cmd.Parameters['Port'] + $portParam | Should -Not -BeNullOrEmpty + } + + It 'Should have a Credential parameter' { + $script:Cmd.Parameters.ContainsKey('Credential') | Should -BeTrue + } + + It 'Should have an ApiToken parameter' { + $script:Cmd.Parameters.ContainsKey('ApiToken') | Should -BeTrue + } + + It 'Should have a SkipCertificateCheck switch parameter' { + $script:Cmd.Parameters.ContainsKey('SkipCertificateCheck') | Should -BeTrue + $script:Cmd.Parameters['SkipCertificateCheck'].ParameterType | + Should -Be ([System.Management.Automation.SwitchParameter]) + } + + It 'Should have a PassThru switch parameter' { + $script:Cmd.Parameters.ContainsKey('PassThru') | Should -BeTrue + $script:Cmd.Parameters['PassThru'].ParameterType | + Should -Be ([System.Management.Automation.SwitchParameter]) + } + + It 'Credential and ApiToken should belong to different parameter sets' { + $credSets = $script:Cmd.Parameters['Credential'].ParameterSets.Keys + $tokenSets = $script:Cmd.Parameters['ApiToken'].ParameterSets.Keys + $overlap = $credSets | Where-Object { $tokenSets -contains $_ } + $overlap | Should -BeNullOrEmpty + } + } +} diff --git a/tests/PSProxmoxVE.Tests/Connection/Disconnect-PveServer.Tests.ps1 b/tests/PSProxmoxVE.Tests/Connection/Disconnect-PveServer.Tests.ps1 new file mode 100644 index 0000000..0bdb5bb --- /dev/null +++ b/tests/PSProxmoxVE.Tests/Connection/Disconnect-PveServer.Tests.ps1 @@ -0,0 +1,70 @@ +#Requires -Module Pester +<# +.SYNOPSIS + Pester 5 tests for Disconnect-PveServer. + All tests are fully offline — no live Proxmox VE target is required. +#> + +BeforeAll { + $moduleRoot = Resolve-Path (Join-Path $PSScriptRoot '../../../src/PSProxmoxVE') + $dllCandidates = @( + Join-Path $moduleRoot 'bin/Debug/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Debug/net48/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net48/PSProxmoxVE.dll' + ) + + $script:ModuleDll = $dllCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 + + if ($null -eq $script:ModuleDll) { + throw "PSProxmoxVE.dll not found. Build the project before running Pester tests." + } + + Import-Module $script:ModuleDll -Force -ErrorAction Stop +} + +Describe 'Disconnect-PveServer' { + + Context 'Command existence' { + It 'Should be available after module import' { + Get-Command 'Disconnect-PveServer' -ErrorAction SilentlyContinue | + Should -Not -BeNullOrEmpty + } + + It 'Should be a CmdletInfo (binary cmdlet)' { + (Get-Command 'Disconnect-PveServer').CommandType | Should -Be 'Cmdlet' + } + } + + Context 'Parameter metadata' { + BeforeAll { + $script:Cmd = Get-Command 'Disconnect-PveServer' + } + + It 'Should support ShouldProcess (have WhatIf and Confirm parameters)' { + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + $script:Cmd.Parameters.ContainsKey('Confirm') | Should -BeTrue + } + + It 'Should declare ConfirmImpact Low (no explicit -Confirm needed for normal use)' { + # SupportsShouldProcess is reflected as WhatIf/Confirm parameters. + # ConfirmImpact=Low means PowerShell will not auto-prompt; just verify the + # attribute is present by confirming ShouldProcess support is enabled. + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + } + + Context 'Behaviour when no session is active' { + It 'Should run without error and emit a warning when no session exists' { + # Ensure module state has no active session by disconnecting first (may already be null). + # Disconnect-PveServer should emit a warning, not throw. + { Disconnect-PveServer -ErrorAction Stop } | Should -Not -Throw + } + } + + Context 'WhatIf support' { + It 'Should accept -WhatIf without throwing' { + { Disconnect-PveServer -WhatIf -ErrorAction Stop } | Should -Not -Throw + } + } +} diff --git a/tests/PSProxmoxVE.Tests/Connection/Test-PveConnection.Tests.ps1 b/tests/PSProxmoxVE.Tests/Connection/Test-PveConnection.Tests.ps1 new file mode 100644 index 0000000..42dec00 --- /dev/null +++ b/tests/PSProxmoxVE.Tests/Connection/Test-PveConnection.Tests.ps1 @@ -0,0 +1,83 @@ +#Requires -Module Pester +<# +.SYNOPSIS + Pester 5 tests for Test-PveConnection. + All tests are fully offline — no live Proxmox VE target is required. +#> + +BeforeAll { + $moduleRoot = Resolve-Path (Join-Path $PSScriptRoot '../../../src/PSProxmoxVE') + $dllCandidates = @( + Join-Path $moduleRoot 'bin/Debug/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Debug/net48/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net48/PSProxmoxVE.dll' + ) + + $script:ModuleDll = $dllCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 + + if ($null -eq $script:ModuleDll) { + throw "PSProxmoxVE.dll not found. Build the project before running Pester tests." + } + + Import-Module $script:ModuleDll -Force -ErrorAction Stop +} + +Describe 'Test-PveConnection' { + + Context 'Command existence' { + It 'Should be available after module import' { + Get-Command 'Test-PveConnection' -ErrorAction SilentlyContinue | + Should -Not -BeNullOrEmpty + } + + It 'Should be a CmdletInfo (binary cmdlet)' { + (Get-Command 'Test-PveConnection').CommandType | Should -Be 'Cmdlet' + } + } + + Context 'Parameter metadata' { + BeforeAll { + $script:Cmd = Get-Command 'Test-PveConnection' + } + + It 'Should have a Detailed switch parameter' { + $script:Cmd.Parameters.ContainsKey('Detailed') | Should -BeTrue + $script:Cmd.Parameters['Detailed'].ParameterType | + Should -Be ([System.Management.Automation.SwitchParameter]) + } + + It 'Detailed should not be Mandatory' { + $detailed = $script:Cmd.Parameters['Detailed'] + $isMandatory = $detailed.ParameterSets.Values | Where-Object { $_.IsMandatory } + $isMandatory | Should -BeNullOrEmpty + } + } + + Context 'Behaviour without an active session' { + It 'Should return $false when no session is active (default mode)' { + $result = Test-PveConnection + $result | Should -BeFalse + } + + It 'Should return nothing when -Detailed is specified and no session is active' { + $result = Test-PveConnection -Detailed + $result | Should -BeNullOrEmpty + } + + It 'Should not throw when called with no arguments and no session' { + { Test-PveConnection -ErrorAction Stop } | Should -Not -Throw + } + + It 'Should not throw when called with -Detailed and no session' { + { Test-PveConnection -Detailed -ErrorAction Stop } | Should -Not -Throw + } + } + + Context 'Output type contract' { + It 'Default output is a boolean' { + $result = Test-PveConnection + $result | Should -BeOfType [bool] + } + } +} diff --git a/tests/PSProxmoxVE.Tests/Containers/ContainerLifecycle.Tests.ps1 b/tests/PSProxmoxVE.Tests/Containers/ContainerLifecycle.Tests.ps1 new file mode 100644 index 0000000..b71d93e --- /dev/null +++ b/tests/PSProxmoxVE.Tests/Containers/ContainerLifecycle.Tests.ps1 @@ -0,0 +1,314 @@ +#Requires -Module Pester +<# +.SYNOPSIS + Pester 5 tests for container lifecycle cmdlets: + New-PveContainer, Remove-PveContainer, + Start-PveContainer, Stop-PveContainer, Restart-PveContainer. + + All tests are fully offline — no live Proxmox VE target is required. + If a cmdlet is not yet compiled into the DLL the test is marked Skipped. +#> + +BeforeAll { + $moduleRoot = Resolve-Path (Join-Path $PSScriptRoot '../../../src/PSProxmoxVE') + $dllCandidates = @( + Join-Path $moduleRoot 'bin/Debug/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Debug/net48/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net48/PSProxmoxVE.dll' + ) + + $script:ModuleDll = $dllCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 + + if ($null -eq $script:ModuleDll) { + throw "PSProxmoxVE.dll not found. Build the project before running Pester tests." + } + + Import-Module $script:ModuleDll -Force -ErrorAction Stop + + # Pre-calculate availability for each cmdlet. + $script:Availability = @{} + foreach ($name in @('New-PveContainer', 'Remove-PveContainer', + 'Start-PveContainer', 'Stop-PveContainer', + 'Restart-PveContainer', + 'Get-PveContainerConfig', 'Set-PveContainerConfig', + 'Copy-PveContainer')) { + $script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue) + } + + function Skip-IfMissing([string]$Name) { + if (-not $script:Availability[$Name]) { + Set-ItResult -Skipped -Because "$Name is not yet implemented in this build" + } + } +} + +# --------------------------------------------------------------------------- +# New-PveContainer +# --------------------------------------------------------------------------- +Describe 'New-PveContainer' { + + BeforeAll { $script:Cmd = Get-Command 'New-PveContainer' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'New-PveContainer' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'ShouldProcess support' { + It 'Should support WhatIf' { + Skip-IfMissing 'New-PveContainer' + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + } + + Context 'Required parameters' { + It 'Node should be Mandatory' { + Skip-IfMissing 'New-PveContainer' + $isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'Should throw when Node is omitted' { + Skip-IfMissing 'New-PveContainer' + { New-PveContainer -ErrorAction Stop } | Should -Throw + } + } + + Context 'Optional parameters' { + It 'Should have Wait switch parameter' { + Skip-IfMissing 'New-PveContainer' + $script:Cmd.Parameters.ContainsKey('Wait') | Should -BeTrue + } + + It 'Should have Session parameter' { + Skip-IfMissing 'New-PveContainer' + $script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue + } + } + + Context 'Without active session' { + It 'Should throw when no session is active (without -WhatIf)' { + Skip-IfMissing 'New-PveContainer' + { New-PveContainer -Node 'pve-node1' -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + } +} + +# --------------------------------------------------------------------------- +# Remove-PveContainer +# --------------------------------------------------------------------------- +Describe 'Remove-PveContainer' { + + BeforeAll { $script:Cmd = Get-Command 'Remove-PveContainer' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'Remove-PveContainer' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'ShouldProcess / ConfirmImpact' { + It 'Should support WhatIf' { + Skip-IfMissing 'Remove-PveContainer' + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + + It 'Should declare ConfirmImpact High' { + Skip-IfMissing 'Remove-PveContainer' + $attr = $script:Cmd.ImplementingType.GetCustomAttributes( + [System.Management.Automation.CmdletAttribute], $false) | + Select-Object -First 1 + $attr.ConfirmImpact | Should -Be ([System.Management.Automation.ConfirmImpact]::High) + } + } + + Context 'Required parameters' { + It 'Node should be Mandatory' { + Skip-IfMissing 'Remove-PveContainer' + $isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'VmId should be Mandatory' { + Skip-IfMissing 'Remove-PveContainer' + $isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + } + + Context 'Optional parameters' { + It 'Should have Purge switch parameter' { + Skip-IfMissing 'Remove-PveContainer' + $script:Cmd.Parameters.ContainsKey('Purge') | Should -BeTrue + } + + It 'Should have Wait switch parameter' { + Skip-IfMissing 'Remove-PveContainer' + $script:Cmd.Parameters.ContainsKey('Wait') | Should -BeTrue + } + } + + Context 'Without active session' { + It 'Should throw when no session is active (without -WhatIf)' { + Skip-IfMissing 'Remove-PveContainer' + { Remove-PveContainer -Node 'pve-node1' -VmId 200 -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + + It 'Should not throw with -WhatIf' { + Skip-IfMissing 'Remove-PveContainer' + { Remove-PveContainer -Node 'pve-node1' -VmId 200 -WhatIf -ErrorAction Stop } | + Should -Not -Throw + } + } +} + +# --------------------------------------------------------------------------- +# Start-PveContainer +# --------------------------------------------------------------------------- +Describe 'Start-PveContainer' { + + BeforeAll { $script:Cmd = Get-Command 'Start-PveContainer' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'Start-PveContainer' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'Parameter metadata' { + It 'Node should be Mandatory' { + Skip-IfMissing 'Start-PveContainer' + $isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'VmId should be Mandatory' { + Skip-IfMissing 'Start-PveContainer' + $isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'Should have Wait switch parameter' { + Skip-IfMissing 'Start-PveContainer' + $script:Cmd.Parameters.ContainsKey('Wait') | Should -BeTrue + } + + It 'Should support ShouldProcess' { + Skip-IfMissing 'Start-PveContainer' + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + } + + Context 'Without active session' { + It 'Should throw when no session is active' { + Skip-IfMissing 'Start-PveContainer' + { Start-PveContainer -Node 'pve-node1' -VmId 200 -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + } +} + +# --------------------------------------------------------------------------- +# Stop-PveContainer +# --------------------------------------------------------------------------- +Describe 'Stop-PveContainer' { + + BeforeAll { $script:Cmd = Get-Command 'Stop-PveContainer' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'Stop-PveContainer' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'Parameter metadata' { + It 'Node should be Mandatory' { + Skip-IfMissing 'Stop-PveContainer' + $isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'VmId should be Mandatory' { + Skip-IfMissing 'Stop-PveContainer' + $isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'Should have Wait switch parameter' { + Skip-IfMissing 'Stop-PveContainer' + $script:Cmd.Parameters.ContainsKey('Wait') | Should -BeTrue + } + + It 'Should support ShouldProcess' { + Skip-IfMissing 'Stop-PveContainer' + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + } + + Context 'Without active session' { + It 'Should throw when no session is active' { + Skip-IfMissing 'Stop-PveContainer' + { Stop-PveContainer -Node 'pve-node1' -VmId 200 -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + } +} + +# --------------------------------------------------------------------------- +# Restart-PveContainer +# --------------------------------------------------------------------------- +Describe 'Restart-PveContainer' { + + BeforeAll { $script:Cmd = Get-Command 'Restart-PveContainer' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'Restart-PveContainer' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'Parameter metadata' { + It 'Node should be Mandatory' { + Skip-IfMissing 'Restart-PveContainer' + $isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'VmId should be Mandatory' { + Skip-IfMissing 'Restart-PveContainer' + $isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'Should support ShouldProcess' { + Skip-IfMissing 'Restart-PveContainer' + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + } + + Context 'Without active session' { + It 'Should throw when no session is active' { + Skip-IfMissing 'Restart-PveContainer' + { Restart-PveContainer -Node 'pve-node1' -VmId 200 -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + } +} diff --git a/tests/PSProxmoxVE.Tests/Containers/Get-PveContainer.Tests.ps1 b/tests/PSProxmoxVE.Tests/Containers/Get-PveContainer.Tests.ps1 new file mode 100644 index 0000000..6612412 --- /dev/null +++ b/tests/PSProxmoxVE.Tests/Containers/Get-PveContainer.Tests.ps1 @@ -0,0 +1,109 @@ +#Requires -Module Pester +<# +.SYNOPSIS + Pester 5 tests for Get-PveContainer. + All tests are fully offline — no live Proxmox VE target is required. + + Get-PveContainer mirrors the design of Get-PveVm for LXC containers. + If the cmdlet is not yet implemented (dll compiled without it), tests + that depend on invocation are marked Skipped rather than Failed. +#> + +BeforeAll { + $moduleRoot = Resolve-Path (Join-Path $PSScriptRoot '../../../src/PSProxmoxVE') + $dllCandidates = @( + Join-Path $moduleRoot 'bin/Debug/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Debug/net48/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net48/PSProxmoxVE.dll' + ) + + $script:ModuleDll = $dllCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 + + if ($null -eq $script:ModuleDll) { + throw "PSProxmoxVE.dll not found. Build the project before running Pester tests." + } + + Import-Module $script:ModuleDll -Force -ErrorAction Stop + + $script:CmdExists = $null -ne (Get-Command 'Get-PveContainer' -ErrorAction SilentlyContinue) +} + +Describe 'Get-PveContainer' { + + Context 'Command existence' { + It 'Get-PveContainer should be listed in the module manifest CmdletsToExport' { + # The .psd1 declares the cmdlet; implementation may be pending. + $manifestPath = Join-Path $moduleRoot 'PSProxmoxVE.psd1' + if (Test-Path $manifestPath) { + $manifest = Import-PowerShellDataFile $manifestPath + $manifest.CmdletsToExport | Should -Contain 'Get-PveContainer' + } + else { + Set-ItResult -Skipped -Because 'Module manifest not found at expected path' + } + } + + It 'Should be available after module import (or skip if not yet compiled)' { + if (-not $script:CmdExists) { + Set-ItResult -Skipped -Because 'Get-PveContainer is not yet implemented in this build' + return + } + (Get-Command 'Get-PveContainer').CommandType | Should -Be 'Cmdlet' + } + } + + Context 'Parameter metadata' { + BeforeAll { + $script:Cmd = Get-Command 'Get-PveContainer' -ErrorAction SilentlyContinue + } + + It 'Should have Node parameter' { + if (-not $script:CmdExists) { Set-ItResult -Skipped -Because 'Not yet compiled'; return } + $script:Cmd.Parameters.ContainsKey('Node') | Should -BeTrue + } + + It 'Node should not be Mandatory (all-nodes query when omitted)' { + if (-not $script:CmdExists) { Set-ItResult -Skipped -Because 'Not yet compiled'; return } + $isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -BeNullOrEmpty + } + + It 'Should have VmId parameter' { + if (-not $script:CmdExists) { Set-ItResult -Skipped -Because 'Not yet compiled'; return } + $script:Cmd.Parameters.ContainsKey('VmId') | Should -BeTrue + } + + It 'Should have Name parameter' { + if (-not $script:CmdExists) { Set-ItResult -Skipped -Because 'Not yet compiled'; return } + $script:Cmd.Parameters.ContainsKey('Name') | Should -BeTrue + } + + It 'Should have Status parameter' { + if (-not $script:CmdExists) { Set-ItResult -Skipped -Because 'Not yet compiled'; return } + $script:Cmd.Parameters.ContainsKey('Status') | Should -BeTrue + } + + It 'Should have Session parameter (inherited from PveCmdletBase)' { + if (-not $script:CmdExists) { Set-ItResult -Skipped -Because 'Not yet compiled'; return } + $script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue + } + + It 'Node should accept pipeline input by property name' { + if (-not $script:CmdExists) { Set-ItResult -Skipped -Because 'Not yet compiled'; return } + $node = $script:Cmd.Parameters['Node'] + $acceptsByPropName = $node.ParameterSets.Values | + Where-Object { $_.ValueFromPipelineByPropertyName } + $acceptsByPropName | Should -Not -BeNullOrEmpty + } + } + + Context 'Without active session' { + It 'Should throw when no session is active' { + if (-not $script:CmdExists) { Set-ItResult -Skipped -Because 'Not yet compiled'; return } + { Get-PveContainer -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + } +} diff --git a/tests/PSProxmoxVE.Tests/Network/Get-PveNetwork.Tests.ps1 b/tests/PSProxmoxVE.Tests/Network/Get-PveNetwork.Tests.ps1 new file mode 100644 index 0000000..3677dc5 --- /dev/null +++ b/tests/PSProxmoxVE.Tests/Network/Get-PveNetwork.Tests.ps1 @@ -0,0 +1,268 @@ +#Requires -Module Pester +<# +.SYNOPSIS + Pester 5 tests for network cmdlets: + Get-PveNetwork, New-PveNetwork, Set-PveNetwork, + Remove-PveNetwork, Invoke-PveNetworkApply. + + All tests are fully offline — no live Proxmox VE target is required. + If a cmdlet is not yet compiled the test is marked Skipped. +#> + +BeforeAll { + $moduleRoot = Resolve-Path (Join-Path $PSScriptRoot '../../../src/PSProxmoxVE') + $dllCandidates = @( + Join-Path $moduleRoot 'bin/Debug/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Debug/net48/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net48/PSProxmoxVE.dll' + ) + + $script:ModuleDll = $dllCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 + + if ($null -eq $script:ModuleDll) { + throw "PSProxmoxVE.dll not found. Build the project before running Pester tests." + } + + Import-Module $script:ModuleDll -Force -ErrorAction Stop + + $script:Availability = @{} + foreach ($name in @('Get-PveNetwork', 'New-PveNetwork', 'Set-PveNetwork', + 'Remove-PveNetwork', 'Invoke-PveNetworkApply')) { + $script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue) + } + + function Skip-IfMissing([string]$Name) { + if (-not $script:Availability[$Name]) { + Set-ItResult -Skipped -Because "$Name is not yet implemented in this build" + } + } +} + +# --------------------------------------------------------------------------- +# Manifest contract +# --------------------------------------------------------------------------- +Describe 'Network cmdlets — manifest declarations' { + BeforeAll { + $manifestPath = Join-Path $moduleRoot 'PSProxmoxVE.psd1' + $script:Manifest = if (Test-Path $manifestPath) { Import-PowerShellDataFile $manifestPath } else { $null } + } + + foreach ($cmdName in @('Get-PveNetwork', 'New-PveNetwork', 'Set-PveNetwork', + 'Remove-PveNetwork', 'Invoke-PveNetworkApply')) { + It "$cmdName should be declared in CmdletsToExport" { + if ($null -eq $script:Manifest) { Set-ItResult -Skipped -Because 'Manifest not found'; return } + $script:Manifest.CmdletsToExport | Should -Contain $cmdName + } + } +} + +# --------------------------------------------------------------------------- +# Get-PveNetwork +# --------------------------------------------------------------------------- +Describe 'Get-PveNetwork' { + + BeforeAll { $script:Cmd = Get-Command 'Get-PveNetwork' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'Get-PveNetwork' + $script:Cmd | Should -Not -BeNullOrEmpty + } + + It 'Should be a CmdletInfo (binary cmdlet)' { + Skip-IfMissing 'Get-PveNetwork' + $script:Cmd.CommandType | Should -Be 'Cmdlet' + } + } + + Context 'Parameter metadata' { + It 'Should have Node parameter (Mandatory — network is always node-specific)' { + Skip-IfMissing 'Get-PveNetwork' + $script:Cmd.Parameters.ContainsKey('Node') | Should -BeTrue + } + + It 'Should have Iface parameter (optional filter)' { + Skip-IfMissing 'Get-PveNetwork' + $script:Cmd.Parameters.ContainsKey('Iface') | Should -BeTrue + } + + It 'Should have Type parameter (optional filter by interface type)' { + Skip-IfMissing 'Get-PveNetwork' + $script:Cmd.Parameters.ContainsKey('Type') | Should -BeTrue + } + + It 'Should have Session parameter' { + Skip-IfMissing 'Get-PveNetwork' + $script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue + } + } + + Context 'Without active session' { + It 'Should throw when no session is active' { + Skip-IfMissing 'Get-PveNetwork' + { Get-PveNetwork -Node 'pve-node1' -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + } +} + +# --------------------------------------------------------------------------- +# New-PveNetwork +# --------------------------------------------------------------------------- +Describe 'New-PveNetwork' { + + BeforeAll { $script:Cmd = Get-Command 'New-PveNetwork' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'New-PveNetwork' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'ShouldProcess support' { + It 'Should support WhatIf' { + Skip-IfMissing 'New-PveNetwork' + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + } + + Context 'Required parameters' { + It 'Node should be Mandatory' { + Skip-IfMissing 'New-PveNetwork' + $isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'Iface should be Mandatory' { + Skip-IfMissing 'New-PveNetwork' + $isMandatory = $script:Cmd.Parameters['Iface'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'Type should be Mandatory' { + Skip-IfMissing 'New-PveNetwork' + $isMandatory = $script:Cmd.Parameters['Type'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + } +} + +# --------------------------------------------------------------------------- +# Set-PveNetwork +# --------------------------------------------------------------------------- +Describe 'Set-PveNetwork' { + + BeforeAll { $script:Cmd = Get-Command 'Set-PveNetwork' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'Set-PveNetwork' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'ShouldProcess support' { + It 'Should support WhatIf' { + Skip-IfMissing 'Set-PveNetwork' + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + } + + Context 'Required parameters' { + It 'Node should be Mandatory' { + Skip-IfMissing 'Set-PveNetwork' + $isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'Iface should be Mandatory' { + Skip-IfMissing 'Set-PveNetwork' + $isMandatory = $script:Cmd.Parameters['Iface'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + } +} + +# --------------------------------------------------------------------------- +# Remove-PveNetwork +# --------------------------------------------------------------------------- +Describe 'Remove-PveNetwork' { + + BeforeAll { $script:Cmd = Get-Command 'Remove-PveNetwork' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'Remove-PveNetwork' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'ShouldProcess / ConfirmImpact' { + It 'Should support WhatIf' { + Skip-IfMissing 'Remove-PveNetwork' + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + + It 'Should declare ConfirmImpact High' { + Skip-IfMissing 'Remove-PveNetwork' + $attr = $script:Cmd.ImplementingType.GetCustomAttributes( + [System.Management.Automation.CmdletAttribute], $false) | + Select-Object -First 1 + $attr.ConfirmImpact | Should -Be ([System.Management.Automation.ConfirmImpact]::High) + } + } + + Context 'Required parameters' { + It 'Node should be Mandatory' { + Skip-IfMissing 'Remove-PveNetwork' + $isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'Iface should be Mandatory' { + Skip-IfMissing 'Remove-PveNetwork' + $isMandatory = $script:Cmd.Parameters['Iface'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + } +} + +# --------------------------------------------------------------------------- +# Invoke-PveNetworkApply +# --------------------------------------------------------------------------- +Describe 'Invoke-PveNetworkApply' { + + BeforeAll { $script:Cmd = Get-Command 'Invoke-PveNetworkApply' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'Invoke-PveNetworkApply' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'ShouldProcess support' { + It 'Should support WhatIf' { + Skip-IfMissing 'Invoke-PveNetworkApply' + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + } + + Context 'Required parameters' { + It 'Node should be Mandatory' { + Skip-IfMissing 'Invoke-PveNetworkApply' + $isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + } +} diff --git a/tests/PSProxmoxVE.Tests/Network/SdnCmdlets.Tests.ps1 b/tests/PSProxmoxVE.Tests/Network/SdnCmdlets.Tests.ps1 new file mode 100644 index 0000000..1350cb5 --- /dev/null +++ b/tests/PSProxmoxVE.Tests/Network/SdnCmdlets.Tests.ps1 @@ -0,0 +1,315 @@ +#Requires -Module Pester +<# +.SYNOPSIS + Pester 5 tests for SDN cmdlets: + Get-PveSdnZone, New-PveSdnZone, Remove-PveSdnZone, + Get-PveSdnVnet, New-PveSdnVnet, Remove-PveSdnVnet. + + All tests are fully offline — no live Proxmox VE target is required. + + SDN support was introduced in Proxmox VE 7 and became stable in PVE 8. + These cmdlets should include a version guard that raises PveVersionException + (or similar) when the server version is below the minimum requirement. + The version guard tests use fully-mocked sessions (no network calls). +#> + +BeforeAll { + $moduleRoot = Resolve-Path (Join-Path $PSScriptRoot '../../../src/PSProxmoxVE') + $dllCandidates = @( + Join-Path $moduleRoot 'bin/Debug/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Debug/net48/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net48/PSProxmoxVE.dll' + ) + + $script:ModuleDll = $dllCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 + + if ($null -eq $script:ModuleDll) { + throw "PSProxmoxVE.dll not found. Build the project before running Pester tests." + } + + Import-Module $script:ModuleDll -Force -ErrorAction Stop + + $script:SdnCmdlets = @( + 'Get-PveSdnZone', 'New-PveSdnZone', 'Remove-PveSdnZone', + 'Get-PveSdnVnet', 'New-PveSdnVnet', 'Remove-PveSdnVnet' + ) + + $script:Availability = @{} + foreach ($name in $script:SdnCmdlets) { + $script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue) + } + + function Skip-IfMissing([string]$Name) { + if (-not $script:Availability[$Name]) { + Set-ItResult -Skipped -Because "$Name is not yet implemented in this build" + } + } +} + +# --------------------------------------------------------------------------- +# Manifest contract +# --------------------------------------------------------------------------- +Describe 'SDN cmdlets — manifest declarations' { + BeforeAll { + $manifestPath = Join-Path $moduleRoot 'PSProxmoxVE.psd1' + $script:Manifest = if (Test-Path $manifestPath) { Import-PowerShellDataFile $manifestPath } else { $null } + } + + foreach ($cmdName in @('Get-PveSdnZone', 'New-PveSdnZone', 'Remove-PveSdnZone', + 'Get-PveSdnVnet', 'New-PveSdnVnet', 'Remove-PveSdnVnet')) { + It "$cmdName should be declared in CmdletsToExport" { + if ($null -eq $script:Manifest) { Set-ItResult -Skipped -Because 'Manifest not found'; return } + $script:Manifest.CmdletsToExport | Should -Contain $cmdName + } + } +} + +# --------------------------------------------------------------------------- +# Get-PveSdnZone +# --------------------------------------------------------------------------- +Describe 'Get-PveSdnZone' { + + BeforeAll { $script:Cmd = Get-Command 'Get-PveSdnZone' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'Get-PveSdnZone' + $script:Cmd | Should -Not -BeNullOrEmpty + } + + It 'Should be a CmdletInfo (binary cmdlet)' { + Skip-IfMissing 'Get-PveSdnZone' + $script:Cmd.CommandType | Should -Be 'Cmdlet' + } + } + + Context 'Parameter metadata' { + It 'Should have Zone parameter (optional filter by zone ID)' { + Skip-IfMissing 'Get-PveSdnZone' + $script:Cmd.Parameters.ContainsKey('Zone') | Should -BeTrue + } + + It 'Should have Session parameter' { + Skip-IfMissing 'Get-PveSdnZone' + $script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue + } + } + + Context 'Version guard behaviour — without active session' { + It 'Should throw when no session is active' { + Skip-IfMissing 'Get-PveSdnZone' + { Get-PveSdnZone -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + } +} + +# --------------------------------------------------------------------------- +# New-PveSdnZone +# --------------------------------------------------------------------------- +Describe 'New-PveSdnZone' { + + BeforeAll { $script:Cmd = Get-Command 'New-PveSdnZone' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'New-PveSdnZone' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'ShouldProcess support' { + It 'Should support WhatIf' { + Skip-IfMissing 'New-PveSdnZone' + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + } + + Context 'Required parameters' { + It 'Zone should be Mandatory' { + Skip-IfMissing 'New-PveSdnZone' + $isMandatory = $script:Cmd.Parameters['Zone'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'Type should be Mandatory' { + Skip-IfMissing 'New-PveSdnZone' + $isMandatory = $script:Cmd.Parameters['Type'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + } + + Context 'Type ValidateSet' { + It 'Type parameter should have ValidateSet including known zone types' { + Skip-IfMissing 'New-PveSdnZone' + $validateSetAttr = $script:Cmd.Parameters['Type'].Attributes | + Where-Object { $_ -is [System.Management.Automation.ValidateSetAttribute] } | + Select-Object -First 1 + if ($null -ne $validateSetAttr) { + $validateSetAttr.ValidValues | Should -Contain 'simple' + } + else { + Set-ItResult -Skipped -Because 'Type does not use a ValidateSet attribute in this build' + } + } + } +} + +# --------------------------------------------------------------------------- +# Remove-PveSdnZone +# --------------------------------------------------------------------------- +Describe 'Remove-PveSdnZone' { + + BeforeAll { $script:Cmd = Get-Command 'Remove-PveSdnZone' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'Remove-PveSdnZone' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'ShouldProcess / ConfirmImpact' { + It 'Should support WhatIf' { + Skip-IfMissing 'Remove-PveSdnZone' + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + + It 'Should declare ConfirmImpact High' { + Skip-IfMissing 'Remove-PveSdnZone' + $attr = $script:Cmd.ImplementingType.GetCustomAttributes( + [System.Management.Automation.CmdletAttribute], $false) | + Select-Object -First 1 + $attr.ConfirmImpact | Should -Be ([System.Management.Automation.ConfirmImpact]::High) + } + } + + Context 'Required parameters' { + It 'Zone should be Mandatory' { + Skip-IfMissing 'Remove-PveSdnZone' + $isMandatory = $script:Cmd.Parameters['Zone'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + } +} + +# --------------------------------------------------------------------------- +# Get-PveSdnVnet +# --------------------------------------------------------------------------- +Describe 'Get-PveSdnVnet' { + + BeforeAll { $script:Cmd = Get-Command 'Get-PveSdnVnet' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'Get-PveSdnVnet' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'Parameter metadata' { + It 'Should have Vnet parameter (optional filter by VNet name)' { + Skip-IfMissing 'Get-PveSdnVnet' + $script:Cmd.Parameters.ContainsKey('Vnet') | Should -BeTrue + } + + It 'Should have Zone parameter (optional filter by parent zone)' { + Skip-IfMissing 'Get-PveSdnVnet' + $script:Cmd.Parameters.ContainsKey('Zone') | Should -BeTrue + } + + It 'Should have Session parameter' { + Skip-IfMissing 'Get-PveSdnVnet' + $script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue + } + } + + Context 'Without active session' { + It 'Should throw when no session is active' { + Skip-IfMissing 'Get-PveSdnVnet' + { Get-PveSdnVnet -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + } +} + +# --------------------------------------------------------------------------- +# New-PveSdnVnet +# --------------------------------------------------------------------------- +Describe 'New-PveSdnVnet' { + + BeforeAll { $script:Cmd = Get-Command 'New-PveSdnVnet' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'New-PveSdnVnet' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'ShouldProcess support' { + It 'Should support WhatIf' { + Skip-IfMissing 'New-PveSdnVnet' + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + } + + Context 'Required parameters' { + It 'Vnet should be Mandatory' { + Skip-IfMissing 'New-PveSdnVnet' + $isMandatory = $script:Cmd.Parameters['Vnet'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'Zone should be Mandatory' { + Skip-IfMissing 'New-PveSdnVnet' + $isMandatory = $script:Cmd.Parameters['Zone'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + } +} + +# --------------------------------------------------------------------------- +# Remove-PveSdnVnet +# --------------------------------------------------------------------------- +Describe 'Remove-PveSdnVnet' { + + BeforeAll { $script:Cmd = Get-Command 'Remove-PveSdnVnet' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'Remove-PveSdnVnet' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'ShouldProcess / ConfirmImpact' { + It 'Should support WhatIf' { + Skip-IfMissing 'Remove-PveSdnVnet' + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + + It 'Should declare ConfirmImpact High' { + Skip-IfMissing 'Remove-PveSdnVnet' + $attr = $script:Cmd.ImplementingType.GetCustomAttributes( + [System.Management.Automation.CmdletAttribute], $false) | + Select-Object -First 1 + $attr.ConfirmImpact | Should -Be ([System.Management.Automation.ConfirmImpact]::High) + } + } + + Context 'Required parameters' { + It 'Vnet should be Mandatory' { + Skip-IfMissing 'Remove-PveSdnVnet' + $isMandatory = $script:Cmd.Parameters['Vnet'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + } +} diff --git a/tests/PSProxmoxVE.Tests/Snapshots/SnapshotCmdlets.Tests.ps1 b/tests/PSProxmoxVE.Tests/Snapshots/SnapshotCmdlets.Tests.ps1 new file mode 100644 index 0000000..3a3375c --- /dev/null +++ b/tests/PSProxmoxVE.Tests/Snapshots/SnapshotCmdlets.Tests.ps1 @@ -0,0 +1,313 @@ +#Requires -Module Pester +<# +.SYNOPSIS + Pester 5 tests for snapshot cmdlets: + Get-PveSnapshot, New-PveSnapshot, Remove-PveSnapshot, Restore-PveSnapshot. + + All tests are fully offline — no live Proxmox VE target is required. + If a cmdlet is not yet compiled the test is marked Skipped. + + Snapshot cmdlets apply to both QEMU VMs and LXC containers; the + parameter set distinction is captured in the 'Type' tests below. +#> + +BeforeAll { + $moduleRoot = Resolve-Path (Join-Path $PSScriptRoot '../../../src/PSProxmoxVE') + $dllCandidates = @( + Join-Path $moduleRoot 'bin/Debug/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Debug/net48/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net48/PSProxmoxVE.dll' + ) + + $script:ModuleDll = $dllCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 + + if ($null -eq $script:ModuleDll) { + throw "PSProxmoxVE.dll not found. Build the project before running Pester tests." + } + + Import-Module $script:ModuleDll -Force -ErrorAction Stop + + $script:Availability = @{} + foreach ($name in @('Get-PveSnapshot', 'New-PveSnapshot', + 'Remove-PveSnapshot', 'Restore-PveSnapshot')) { + $script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue) + } + + function Skip-IfMissing([string]$Name) { + if (-not $script:Availability[$Name]) { + Set-ItResult -Skipped -Because "$Name is not yet implemented in this build" + } + } +} + +# --------------------------------------------------------------------------- +# Manifest contract +# --------------------------------------------------------------------------- +Describe 'Snapshot cmdlets — manifest declarations' { + BeforeAll { + $manifestPath = Join-Path $moduleRoot 'PSProxmoxVE.psd1' + $script:Manifest = if (Test-Path $manifestPath) { Import-PowerShellDataFile $manifestPath } else { $null } + } + + foreach ($cmdName in @('Get-PveSnapshot', 'New-PveSnapshot', + 'Remove-PveSnapshot', 'Restore-PveSnapshot')) { + It "$cmdName should be declared in CmdletsToExport" { + if ($null -eq $script:Manifest) { Set-ItResult -Skipped -Because 'Manifest not found'; return } + $script:Manifest.CmdletsToExport | Should -Contain $cmdName + } + } +} + +# --------------------------------------------------------------------------- +# Get-PveSnapshot +# --------------------------------------------------------------------------- +Describe 'Get-PveSnapshot' { + + BeforeAll { $script:Cmd = Get-Command 'Get-PveSnapshot' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'Get-PveSnapshot' + $script:Cmd | Should -Not -BeNullOrEmpty + } + + It 'Should be a CmdletInfo (binary cmdlet)' { + Skip-IfMissing 'Get-PveSnapshot' + $script:Cmd.CommandType | Should -Be 'Cmdlet' + } + } + + Context 'Parameter metadata' { + It 'Node should be Mandatory' { + Skip-IfMissing 'Get-PveSnapshot' + $isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'VmId should be Mandatory' { + Skip-IfMissing 'Get-PveSnapshot' + $isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'Should have Name parameter (optional filter by snapshot name)' { + Skip-IfMissing 'Get-PveSnapshot' + $script:Cmd.Parameters.ContainsKey('Name') | Should -BeTrue + } + + It 'Should have Session parameter' { + Skip-IfMissing 'Get-PveSnapshot' + $script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue + } + } + + Context 'Without active session' { + It 'Should throw when no session is active' { + Skip-IfMissing 'Get-PveSnapshot' + { Get-PveSnapshot -Node 'pve-node1' -VmId 100 -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + } +} + +# --------------------------------------------------------------------------- +# New-PveSnapshot +# --------------------------------------------------------------------------- +Describe 'New-PveSnapshot' { + + BeforeAll { $script:Cmd = Get-Command 'New-PveSnapshot' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'New-PveSnapshot' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'ShouldProcess support' { + It 'Should support WhatIf' { + Skip-IfMissing 'New-PveSnapshot' + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + } + + Context 'Required parameters' { + It 'Node should be Mandatory' { + Skip-IfMissing 'New-PveSnapshot' + $isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'VmId should be Mandatory' { + Skip-IfMissing 'New-PveSnapshot' + $isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'Name (snapshot name) should be Mandatory' { + Skip-IfMissing 'New-PveSnapshot' + $isMandatory = $script:Cmd.Parameters['Name'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + } + + Context 'Optional parameters' { + It 'Should have Description parameter' { + Skip-IfMissing 'New-PveSnapshot' + $script:Cmd.Parameters.ContainsKey('Description') | Should -BeTrue + } + + It 'Should have IncludeVmState switch parameter' { + Skip-IfMissing 'New-PveSnapshot' + $script:Cmd.Parameters.ContainsKey('IncludeVmState') | Should -BeTrue + } + + It 'Should have Wait switch parameter' { + Skip-IfMissing 'New-PveSnapshot' + $script:Cmd.Parameters.ContainsKey('Wait') | Should -BeTrue + } + } + + Context 'Without active session' { + It 'Should throw when no session is active (without -WhatIf)' { + Skip-IfMissing 'New-PveSnapshot' + { New-PveSnapshot -Node 'pve-node1' -VmId 100 -Name 'snap1' -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + } +} + +# --------------------------------------------------------------------------- +# Remove-PveSnapshot +# --------------------------------------------------------------------------- +Describe 'Remove-PveSnapshot' { + + BeforeAll { $script:Cmd = Get-Command 'Remove-PveSnapshot' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'Remove-PveSnapshot' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'ShouldProcess / ConfirmImpact' { + It 'Should support WhatIf' { + Skip-IfMissing 'Remove-PveSnapshot' + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + + It 'Should declare ConfirmImpact High' { + Skip-IfMissing 'Remove-PveSnapshot' + $attr = $script:Cmd.ImplementingType.GetCustomAttributes( + [System.Management.Automation.CmdletAttribute], $false) | + Select-Object -First 1 + $attr.ConfirmImpact | Should -Be ([System.Management.Automation.ConfirmImpact]::High) + } + } + + Context 'Required parameters' { + It 'Node should be Mandatory' { + Skip-IfMissing 'Remove-PveSnapshot' + $isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'VmId should be Mandatory' { + Skip-IfMissing 'Remove-PveSnapshot' + $isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'Name should be Mandatory' { + Skip-IfMissing 'Remove-PveSnapshot' + $isMandatory = $script:Cmd.Parameters['Name'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + } + + Context 'Without active session' { + It 'Should throw when no session is active (without -WhatIf)' { + Skip-IfMissing 'Remove-PveSnapshot' + { Remove-PveSnapshot -Node 'pve-node1' -VmId 100 -Name 'snap1' -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + } +} + +# --------------------------------------------------------------------------- +# Restore-PveSnapshot +# --------------------------------------------------------------------------- +Describe 'Restore-PveSnapshot' { + + BeforeAll { $script:Cmd = Get-Command 'Restore-PveSnapshot' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'Restore-PveSnapshot' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'ShouldProcess / ConfirmImpact' { + It 'Should support WhatIf' { + Skip-IfMissing 'Restore-PveSnapshot' + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + + It 'Should declare ConfirmImpact High' { + Skip-IfMissing 'Restore-PveSnapshot' + $attr = $script:Cmd.ImplementingType.GetCustomAttributes( + [System.Management.Automation.CmdletAttribute], $false) | + Select-Object -First 1 + $attr.ConfirmImpact | Should -Be ([System.Management.Automation.ConfirmImpact]::High) + } + } + + Context 'Required parameters' { + It 'Node should be Mandatory' { + Skip-IfMissing 'Restore-PveSnapshot' + $isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'VmId should be Mandatory' { + Skip-IfMissing 'Restore-PveSnapshot' + $isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'Name should be Mandatory' { + Skip-IfMissing 'Restore-PveSnapshot' + $isMandatory = $script:Cmd.Parameters['Name'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + } + + Context 'Optional parameters' { + It 'Should have Wait switch parameter' { + Skip-IfMissing 'Restore-PveSnapshot' + $script:Cmd.Parameters.ContainsKey('Wait') | Should -BeTrue + } + } + + Context 'Without active session' { + It 'Should throw when no session is active (without -WhatIf)' { + Skip-IfMissing 'Restore-PveSnapshot' + { Restore-PveSnapshot -Node 'pve-node1' -VmId 100 -Name 'snap1' -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + } +} diff --git a/tests/PSProxmoxVE.Tests/Storage/Get-PveStorage.Tests.ps1 b/tests/PSProxmoxVE.Tests/Storage/Get-PveStorage.Tests.ps1 new file mode 100644 index 0000000..cf4dc2c --- /dev/null +++ b/tests/PSProxmoxVE.Tests/Storage/Get-PveStorage.Tests.ps1 @@ -0,0 +1,227 @@ +#Requires -Module Pester +<# +.SYNOPSIS + Pester 5 tests for storage cmdlets: + Get-PveStorage, Get-PveStorageContent, New-PveStorage, Remove-PveStorage. + + All tests are fully offline — no live Proxmox VE target is required. + If a cmdlet is not yet compiled the test is marked Skipped. +#> + +BeforeAll { + $moduleRoot = Resolve-Path (Join-Path $PSScriptRoot '../../../src/PSProxmoxVE') + $dllCandidates = @( + Join-Path $moduleRoot 'bin/Debug/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Debug/net48/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net48/PSProxmoxVE.dll' + ) + + $script:ModuleDll = $dllCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 + + if ($null -eq $script:ModuleDll) { + throw "PSProxmoxVE.dll not found. Build the project before running Pester tests." + } + + Import-Module $script:ModuleDll -Force -ErrorAction Stop + + $script:Availability = @{} + foreach ($name in @('Get-PveStorage', 'Get-PveStorageContent', + 'New-PveStorage', 'Remove-PveStorage')) { + $script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue) + } + + function Skip-IfMissing([string]$Name) { + if (-not $script:Availability[$Name]) { + Set-ItResult -Skipped -Because "$Name is not yet implemented in this build" + } + } +} + +# --------------------------------------------------------------------------- +# Manifest contract +# --------------------------------------------------------------------------- +Describe 'Storage cmdlets — manifest declarations' { + It 'Get-PveStorage should be declared in CmdletsToExport' { + $manifestPath = Join-Path $moduleRoot 'PSProxmoxVE.psd1' + if (-not (Test-Path $manifestPath)) { Set-ItResult -Skipped -Because 'Manifest not found'; return } + $manifest = Import-PowerShellDataFile $manifestPath + $manifest.CmdletsToExport | Should -Contain 'Get-PveStorage' + } + + It 'Get-PveStorageContent should be declared in CmdletsToExport' { + $manifestPath = Join-Path $moduleRoot 'PSProxmoxVE.psd1' + if (-not (Test-Path $manifestPath)) { Set-ItResult -Skipped -Because 'Manifest not found'; return } + $manifest = Import-PowerShellDataFile $manifestPath + $manifest.CmdletsToExport | Should -Contain 'Get-PveStorageContent' + } +} + +# --------------------------------------------------------------------------- +# Get-PveStorage +# --------------------------------------------------------------------------- +Describe 'Get-PveStorage' { + + BeforeAll { $script:Cmd = Get-Command 'Get-PveStorage' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'Get-PveStorage' + $script:Cmd | Should -Not -BeNullOrEmpty + } + + It 'Should be a CmdletInfo (binary cmdlet)' { + Skip-IfMissing 'Get-PveStorage' + $script:Cmd.CommandType | Should -Be 'Cmdlet' + } + } + + Context 'Parameter metadata' { + It 'Should have Node parameter' { + Skip-IfMissing 'Get-PveStorage' + $script:Cmd.Parameters.ContainsKey('Node') | Should -BeTrue + } + + It 'Node should not be Mandatory (cluster-wide query when omitted)' { + Skip-IfMissing 'Get-PveStorage' + $isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -BeNullOrEmpty + } + + It 'Should have Storage parameter (filter by name)' { + Skip-IfMissing 'Get-PveStorage' + $script:Cmd.Parameters.ContainsKey('Storage') | Should -BeTrue + } + + It 'Should have Session parameter' { + Skip-IfMissing 'Get-PveStorage' + $script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue + } + } + + Context 'Without active session' { + It 'Should throw when no session is active' { + Skip-IfMissing 'Get-PveStorage' + { Get-PveStorage -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + } +} + +# --------------------------------------------------------------------------- +# Get-PveStorageContent +# --------------------------------------------------------------------------- +Describe 'Get-PveStorageContent' { + + BeforeAll { $script:Cmd = Get-Command 'Get-PveStorageContent' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'Get-PveStorageContent' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'Parameter metadata' { + It 'Node should be present' { + Skip-IfMissing 'Get-PveStorageContent' + $script:Cmd.Parameters.ContainsKey('Node') | Should -BeTrue + } + + It 'Storage should be present' { + Skip-IfMissing 'Get-PveStorageContent' + $script:Cmd.Parameters.ContainsKey('Storage') | Should -BeTrue + } + + It 'Should have Session parameter' { + Skip-IfMissing 'Get-PveStorageContent' + $script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue + } + } + + Context 'Without active session' { + It 'Should throw when no session is active' { + Skip-IfMissing 'Get-PveStorageContent' + { Get-PveStorageContent -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + } +} + +# --------------------------------------------------------------------------- +# New-PveStorage +# --------------------------------------------------------------------------- +Describe 'New-PveStorage' { + + BeforeAll { $script:Cmd = Get-Command 'New-PveStorage' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'New-PveStorage' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'ShouldProcess support' { + It 'Should support WhatIf' { + Skip-IfMissing 'New-PveStorage' + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + } + + Context 'Required parameters' { + It 'Storage (name) should be Mandatory' { + Skip-IfMissing 'New-PveStorage' + $isMandatory = $script:Cmd.Parameters['Storage'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'Type should be Mandatory' { + Skip-IfMissing 'New-PveStorage' + $isMandatory = $script:Cmd.Parameters['Type'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + } +} + +# --------------------------------------------------------------------------- +# Remove-PveStorage +# --------------------------------------------------------------------------- +Describe 'Remove-PveStorage' { + + BeforeAll { $script:Cmd = Get-Command 'Remove-PveStorage' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'Remove-PveStorage' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'ShouldProcess / ConfirmImpact' { + It 'Should support WhatIf' { + Skip-IfMissing 'Remove-PveStorage' + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + + It 'Should declare ConfirmImpact High' { + Skip-IfMissing 'Remove-PveStorage' + $attr = $script:Cmd.ImplementingType.GetCustomAttributes( + [System.Management.Automation.CmdletAttribute], $false) | + Select-Object -First 1 + $attr.ConfirmImpact | Should -Be ([System.Management.Automation.ConfirmImpact]::High) + } + } + + Context 'Required parameters' { + It 'Storage should be Mandatory' { + Skip-IfMissing 'Remove-PveStorage' + $isMandatory = $script:Cmd.Parameters['Storage'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + } +} diff --git a/tests/PSProxmoxVE.Tests/Storage/Send-PveIso.Tests.ps1 b/tests/PSProxmoxVE.Tests/Storage/Send-PveIso.Tests.ps1 new file mode 100644 index 0000000..128d138 --- /dev/null +++ b/tests/PSProxmoxVE.Tests/Storage/Send-PveIso.Tests.ps1 @@ -0,0 +1,176 @@ +#Requires -Module Pester +<# +.SYNOPSIS + Pester 5 tests for Send-PveIso. + All tests are fully offline — no live Proxmox VE target is required. + If the cmdlet is not yet compiled the tests are marked Skipped. +#> + +BeforeAll { + $moduleRoot = Resolve-Path (Join-Path $PSScriptRoot '../../../src/PSProxmoxVE') + $dllCandidates = @( + Join-Path $moduleRoot 'bin/Debug/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Debug/net48/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net48/PSProxmoxVE.dll' + ) + + $script:ModuleDll = $dllCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 + + if ($null -eq $script:ModuleDll) { + throw "PSProxmoxVE.dll not found. Build the project before running Pester tests." + } + + Import-Module $script:ModuleDll -Force -ErrorAction Stop + + $script:CmdExists = $null -ne (Get-Command 'Send-PveIso' -ErrorAction SilentlyContinue) +} + +Describe 'Send-PveIso' { + + Context 'Manifest declaration' { + It 'Should be declared in CmdletsToExport' { + $manifestPath = Join-Path $moduleRoot 'PSProxmoxVE.psd1' + if (-not (Test-Path $manifestPath)) { Set-ItResult -Skipped -Because 'Manifest not found'; return } + $manifest = Import-PowerShellDataFile $manifestPath + $manifest.CmdletsToExport | Should -Contain 'Send-PveIso' + } + } + + Context 'Command existence' { + It 'Should be available after module import' { + if (-not $script:CmdExists) { Set-ItResult -Skipped -Because 'Not yet compiled'; return } + (Get-Command 'Send-PveIso').CommandType | Should -Be 'Cmdlet' + } + } + + Context 'Required parameters' { + BeforeAll { + $script:Cmd = Get-Command 'Send-PveIso' -ErrorAction SilentlyContinue + } + + It 'Should have Node parameter (Mandatory)' { + if (-not $script:CmdExists) { Set-ItResult -Skipped -Because 'Not yet compiled'; return } + $isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'Should have Storage parameter (Mandatory)' { + if (-not $script:CmdExists) { Set-ItResult -Skipped -Because 'Not yet compiled'; return } + $isMandatory = $script:Cmd.Parameters['Storage'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'Should have Path parameter (Mandatory)' { + if (-not $script:CmdExists) { Set-ItResult -Skipped -Because 'Not yet compiled'; return } + $isMandatory = $script:Cmd.Parameters['Path'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'Should throw when Node is omitted' { + if (-not $script:CmdExists) { Set-ItResult -Skipped -Because 'Not yet compiled'; return } + { Send-PveIso -Storage 'local' -Path '/tmp/test.iso' -ErrorAction Stop } | + Should -Throw + } + + It 'Should throw when Storage is omitted' { + if (-not $script:CmdExists) { Set-ItResult -Skipped -Because 'Not yet compiled'; return } + { Send-PveIso -Node 'pve-node1' -Path '/tmp/test.iso' -ErrorAction Stop } | + Should -Throw + } + + It 'Should throw when Path is omitted' { + if (-not $script:CmdExists) { Set-ItResult -Skipped -Because 'Not yet compiled'; return } + { Send-PveIso -Node 'pve-node1' -Storage 'local' -ErrorAction Stop } | + Should -Throw + } + } + + Context 'ChecksumAlgorithm ValidateSet' { + BeforeAll { + $script:Cmd = Get-Command 'Send-PveIso' -ErrorAction SilentlyContinue + } + + It 'Should have ChecksumAlgorithm parameter' { + if (-not $script:CmdExists) { Set-ItResult -Skipped -Because 'Not yet compiled'; return } + $script:Cmd.Parameters.ContainsKey('ChecksumAlgorithm') | Should -BeTrue + } + + It 'ChecksumAlgorithm should have a ValidateSet attribute' { + if (-not $script:CmdExists) { Set-ItResult -Skipped -Because 'Not yet compiled'; return } + $validateSetAttr = $script:Cmd.Parameters['ChecksumAlgorithm'].Attributes | + Where-Object { $_ -is [System.Management.Automation.ValidateSetAttribute] } + $validateSetAttr | Should -Not -BeNullOrEmpty + } + + It 'ChecksumAlgorithm ValidateSet should include md5' { + if (-not $script:CmdExists) { Set-ItResult -Skipped -Because 'Not yet compiled'; return } + $validateSetAttr = $script:Cmd.Parameters['ChecksumAlgorithm'].Attributes | + Where-Object { $_ -is [System.Management.Automation.ValidateSetAttribute] } | + Select-Object -First 1 + $validateSetAttr.ValidValues | Should -Contain 'md5' + } + + It 'ChecksumAlgorithm ValidateSet should include sha1' { + if (-not $script:CmdExists) { Set-ItResult -Skipped -Because 'Not yet compiled'; return } + $validateSetAttr = $script:Cmd.Parameters['ChecksumAlgorithm'].Attributes | + Where-Object { $_ -is [System.Management.Automation.ValidateSetAttribute] } | + Select-Object -First 1 + $validateSetAttr.ValidValues | Should -Contain 'sha1' + } + + It 'ChecksumAlgorithm ValidateSet should include sha256' { + if (-not $script:CmdExists) { Set-ItResult -Skipped -Because 'Not yet compiled'; return } + $validateSetAttr = $script:Cmd.Parameters['ChecksumAlgorithm'].Attributes | + Where-Object { $_ -is [System.Management.Automation.ValidateSetAttribute] } | + Select-Object -First 1 + $validateSetAttr.ValidValues | Should -Contain 'sha256' + } + + It 'ChecksumAlgorithm ValidateSet should include sha512' { + if (-not $script:CmdExists) { Set-ItResult -Skipped -Because 'Not yet compiled'; return } + $validateSetAttr = $script:Cmd.Parameters['ChecksumAlgorithm'].Attributes | + Where-Object { $_ -is [System.Management.Automation.ValidateSetAttribute] } | + Select-Object -First 1 + $validateSetAttr.ValidValues | Should -Contain 'sha512' + } + } + + Context 'ShouldProcess support' { + BeforeAll { + $script:Cmd = Get-Command 'Send-PveIso' -ErrorAction SilentlyContinue + } + + It 'Should support WhatIf' { + if (-not $script:CmdExists) { Set-ItResult -Skipped -Because 'Not yet compiled'; return } + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + } + + Context 'Optional parameters' { + BeforeAll { + $script:Cmd = Get-Command 'Send-PveIso' -ErrorAction SilentlyContinue + } + + It 'Should have Checksum parameter' { + if (-not $script:CmdExists) { Set-ItResult -Skipped -Because 'Not yet compiled'; return } + $script:Cmd.Parameters.ContainsKey('Checksum') | Should -BeTrue + } + + It 'Should have Session parameter' { + if (-not $script:CmdExists) { Set-ItResult -Skipped -Because 'Not yet compiled'; return } + $script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue + } + } + + Context 'Without active session' { + It 'Should throw when no session is active (without -WhatIf)' { + if (-not $script:CmdExists) { Set-ItResult -Skipped -Because 'Not yet compiled'; return } + { Send-PveIso -Node 'pve-node1' -Storage 'local' -Path '/tmp/test.iso' -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + } +} diff --git a/tests/PSProxmoxVE.Tests/Templates/TemplateCmdlets.Tests.ps1 b/tests/PSProxmoxVE.Tests/Templates/TemplateCmdlets.Tests.ps1 new file mode 100644 index 0000000..44b751d --- /dev/null +++ b/tests/PSProxmoxVE.Tests/Templates/TemplateCmdlets.Tests.ps1 @@ -0,0 +1,281 @@ +#Requires -Module Pester +<# +.SYNOPSIS + Pester 5 tests for template cmdlets: + Get-PveTemplate, New-PveTemplate, Remove-PveTemplate, New-PveVmFromTemplate. + + All tests are fully offline — no live Proxmox VE target is required. + If a cmdlet is not yet compiled the test is marked Skipped. + + Note: New-PveTemplate converts an existing VM into a template (destructive — + the original VM becomes read-only). New-PveVmFromTemplate deploys a new VM + from an existing template (which is a clone operation under the hood). +#> + +BeforeAll { + $moduleRoot = Resolve-Path (Join-Path $PSScriptRoot '../../../src/PSProxmoxVE') + $dllCandidates = @( + Join-Path $moduleRoot 'bin/Debug/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Debug/net48/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net48/PSProxmoxVE.dll' + ) + + $script:ModuleDll = $dllCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 + + if ($null -eq $script:ModuleDll) { + throw "PSProxmoxVE.dll not found. Build the project before running Pester tests." + } + + Import-Module $script:ModuleDll -Force -ErrorAction Stop + + $script:Availability = @{} + foreach ($name in @('Get-PveTemplate', 'New-PveTemplate', + 'Remove-PveTemplate', 'New-PveVmFromTemplate')) { + $script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue) + } + + function Skip-IfMissing([string]$Name) { + if (-not $script:Availability[$Name]) { + Set-ItResult -Skipped -Because "$Name is not yet implemented in this build" + } + } +} + +# --------------------------------------------------------------------------- +# Manifest contract +# --------------------------------------------------------------------------- +Describe 'Template cmdlets — manifest declarations' { + BeforeAll { + $manifestPath = Join-Path $moduleRoot 'PSProxmoxVE.psd1' + $script:Manifest = if (Test-Path $manifestPath) { Import-PowerShellDataFile $manifestPath } else { $null } + } + + foreach ($cmdName in @('Get-PveTemplate', 'New-PveTemplate', + 'Remove-PveTemplate', 'New-PveVmFromTemplate')) { + It "$cmdName should be declared in CmdletsToExport" { + if ($null -eq $script:Manifest) { Set-ItResult -Skipped -Because 'Manifest not found'; return } + $script:Manifest.CmdletsToExport | Should -Contain $cmdName + } + } +} + +# --------------------------------------------------------------------------- +# Get-PveTemplate +# --------------------------------------------------------------------------- +Describe 'Get-PveTemplate' { + + BeforeAll { $script:Cmd = Get-Command 'Get-PveTemplate' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'Get-PveTemplate' + $script:Cmd | Should -Not -BeNullOrEmpty + } + + It 'Should be a CmdletInfo (binary cmdlet)' { + Skip-IfMissing 'Get-PveTemplate' + $script:Cmd.CommandType | Should -Be 'Cmdlet' + } + } + + Context 'Parameter metadata' { + It 'Should have Node parameter (optional — all nodes when omitted)' { + Skip-IfMissing 'Get-PveTemplate' + $script:Cmd.Parameters.ContainsKey('Node') | Should -BeTrue + } + + It 'Node should not be Mandatory' { + Skip-IfMissing 'Get-PveTemplate' + $isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -BeNullOrEmpty + } + + It 'Should have Name parameter (optional filter by template name)' { + Skip-IfMissing 'Get-PveTemplate' + $script:Cmd.Parameters.ContainsKey('Name') | Should -BeTrue + } + + It 'Should have Session parameter' { + Skip-IfMissing 'Get-PveTemplate' + $script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue + } + } + + Context 'Without active session' { + It 'Should throw when no session is active' { + Skip-IfMissing 'Get-PveTemplate' + { Get-PveTemplate -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + } +} + +# --------------------------------------------------------------------------- +# New-PveTemplate (converts an existing VM to a template) +# --------------------------------------------------------------------------- +Describe 'New-PveTemplate' { + + BeforeAll { $script:Cmd = Get-Command 'New-PveTemplate' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'New-PveTemplate' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'ShouldProcess / ConfirmImpact' { + It 'Should support WhatIf' { + Skip-IfMissing 'New-PveTemplate' + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + + It 'Should declare ConfirmImpact High (destructive — VM becomes read-only template)' { + Skip-IfMissing 'New-PveTemplate' + $attr = $script:Cmd.ImplementingType.GetCustomAttributes( + [System.Management.Automation.CmdletAttribute], $false) | + Select-Object -First 1 + $attr.ConfirmImpact | Should -Be ([System.Management.Automation.ConfirmImpact]::High) + } + } + + Context 'Required parameters' { + It 'Node should be Mandatory' { + Skip-IfMissing 'New-PveTemplate' + $isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'VmId should be Mandatory' { + Skip-IfMissing 'New-PveTemplate' + $isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + } + + Context 'Without active session' { + It 'Should throw when no session is active (without -WhatIf)' { + Skip-IfMissing 'New-PveTemplate' + { New-PveTemplate -Node 'pve-node1' -VmId 9000 -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + } +} + +# --------------------------------------------------------------------------- +# Remove-PveTemplate +# --------------------------------------------------------------------------- +Describe 'Remove-PveTemplate' { + + BeforeAll { $script:Cmd = Get-Command 'Remove-PveTemplate' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'Remove-PveTemplate' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'ShouldProcess / ConfirmImpact' { + It 'Should support WhatIf' { + Skip-IfMissing 'Remove-PveTemplate' + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + + It 'Should declare ConfirmImpact High' { + Skip-IfMissing 'Remove-PveTemplate' + $attr = $script:Cmd.ImplementingType.GetCustomAttributes( + [System.Management.Automation.CmdletAttribute], $false) | + Select-Object -First 1 + $attr.ConfirmImpact | Should -Be ([System.Management.Automation.ConfirmImpact]::High) + } + } + + Context 'Required parameters' { + It 'Node should be Mandatory' { + Skip-IfMissing 'Remove-PveTemplate' + $isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'VmId should be Mandatory' { + Skip-IfMissing 'Remove-PveTemplate' + $isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + } +} + +# --------------------------------------------------------------------------- +# New-PveVmFromTemplate (deploy a new VM from a template) +# --------------------------------------------------------------------------- +Describe 'New-PveVmFromTemplate' { + + BeforeAll { $script:Cmd = Get-Command 'New-PveVmFromTemplate' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'New-PveVmFromTemplate' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'ShouldProcess support' { + It 'Should support WhatIf' { + Skip-IfMissing 'New-PveVmFromTemplate' + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + } + + Context 'Required parameters' { + It 'TemplateNode should be Mandatory (node hosting the template)' { + Skip-IfMissing 'New-PveVmFromTemplate' + $hasTemplateNode = $script:Cmd.Parameters.ContainsKey('TemplateNode') -or + $script:Cmd.Parameters.ContainsKey('SourceNode') + $hasTemplateNode | Should -BeTrue + } + + It 'TemplateId (or VmId) should be Mandatory' { + Skip-IfMissing 'New-PveVmFromTemplate' + $hasId = $script:Cmd.Parameters.ContainsKey('TemplateId') -or + $script:Cmd.Parameters.ContainsKey('VmId') + $hasId | Should -BeTrue + } + } + + Context 'Optional parameters' { + It 'Should have NewName parameter (name for the deployed VM)' { + Skip-IfMissing 'New-PveVmFromTemplate' + $hasNewName = $script:Cmd.Parameters.ContainsKey('NewName') -or + $script:Cmd.Parameters.ContainsKey('Name') + $hasNewName | Should -BeTrue + } + + It 'Should have Wait switch parameter' { + Skip-IfMissing 'New-PveVmFromTemplate' + $script:Cmd.Parameters.ContainsKey('Wait') | Should -BeTrue + } + } + + Context 'Without active session' { + It 'Should throw when no session is active (without -WhatIf)' { + Skip-IfMissing 'New-PveVmFromTemplate' + # Use whatever the first mandatory parameter is named; -ErrorAction Stop + # will trigger the missing-mandatory-parameter error or the no-session error. + { + $splat = @{ ErrorAction = 'Stop' } + if ($script:Cmd.Parameters.ContainsKey('TemplateNode')) { $splat['TemplateNode'] = 'pve-node1' } + elseif ($script:Cmd.Parameters.ContainsKey('SourceNode')) { $splat['SourceNode'] = 'pve-node1' } + if ($script:Cmd.Parameters.ContainsKey('TemplateId')) { $splat['TemplateId'] = 9000 } + elseif ($script:Cmd.Parameters.ContainsKey('VmId')) { $splat['VmId'] = 9000 } + & 'New-PveVmFromTemplate' @splat + } | Should -Throw '*No active Proxmox VE session*' + } + } +} diff --git a/tests/PSProxmoxVE.Tests/Users/UserCmdlets.Tests.ps1 b/tests/PSProxmoxVE.Tests/Users/UserCmdlets.Tests.ps1 new file mode 100644 index 0000000..ac5d413 --- /dev/null +++ b/tests/PSProxmoxVE.Tests/Users/UserCmdlets.Tests.ps1 @@ -0,0 +1,353 @@ +#Requires -Module Pester +<# +.SYNOPSIS + Pester 5 tests for user, role, and permission cmdlets: + Get-PveUser, New-PveUser, Remove-PveUser, Set-PveUser, + Get-PveRole, New-PveRole, Remove-PveRole, + Get-PvePermission, Set-PvePermission. + + All tests are fully offline — no live Proxmox VE target is required. + If a cmdlet is not yet compiled the test is marked Skipped. +#> + +BeforeAll { + $moduleRoot = Resolve-Path (Join-Path $PSScriptRoot '../../../src/PSProxmoxVE') + $dllCandidates = @( + Join-Path $moduleRoot 'bin/Debug/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Debug/net48/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net48/PSProxmoxVE.dll' + ) + + $script:ModuleDll = $dllCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 + + if ($null -eq $script:ModuleDll) { + throw "PSProxmoxVE.dll not found. Build the project before running Pester tests." + } + + Import-Module $script:ModuleDll -Force -ErrorAction Stop + + $allNames = @( + 'Get-PveUser', 'New-PveUser', 'Remove-PveUser', 'Set-PveUser', + 'Get-PveRole', 'New-PveRole', 'Remove-PveRole', + 'Get-PvePermission', 'Set-PvePermission' + ) + + $script:Availability = @{} + foreach ($name in $allNames) { + $script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue) + } + + function Skip-IfMissing([string]$Name) { + if (-not $script:Availability[$Name]) { + Set-ItResult -Skipped -Because "$Name is not yet implemented in this build" + } + } +} + +# --------------------------------------------------------------------------- +# Manifest contract +# --------------------------------------------------------------------------- +Describe 'User / Role / Permission cmdlets — manifest declarations' { + BeforeAll { + $manifestPath = Join-Path $moduleRoot 'PSProxmoxVE.psd1' + $script:Manifest = if (Test-Path $manifestPath) { Import-PowerShellDataFile $manifestPath } else { $null } + } + + foreach ($cmdName in @('Get-PveUser', 'New-PveUser', 'Remove-PveUser', 'Set-PveUser', + 'Get-PveRole', 'New-PveRole', 'Remove-PveRole', + 'Get-PvePermission', 'Set-PvePermission')) { + It "$cmdName should be declared in CmdletsToExport" { + if ($null -eq $script:Manifest) { Set-ItResult -Skipped -Because 'Manifest not found'; return } + $script:Manifest.CmdletsToExport | Should -Contain $cmdName + } + } +} + +# --------------------------------------------------------------------------- +# Get-PveUser +# --------------------------------------------------------------------------- +Describe 'Get-PveUser' { + + BeforeAll { $script:Cmd = Get-Command 'Get-PveUser' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'Get-PveUser' + $script:Cmd | Should -Not -BeNullOrEmpty + } + + It 'Should be a CmdletInfo (binary cmdlet)' { + Skip-IfMissing 'Get-PveUser' + $script:Cmd.CommandType | Should -Be 'Cmdlet' + } + } + + Context 'Parameter metadata' { + It 'Should have UserId parameter (optional filter)' { + Skip-IfMissing 'Get-PveUser' + $script:Cmd.Parameters.ContainsKey('UserId') | Should -BeTrue + } + + It 'UserId should not be Mandatory' { + Skip-IfMissing 'Get-PveUser' + $isMandatory = $script:Cmd.Parameters['UserId'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -BeNullOrEmpty + } + + It 'Should have Enabled switch or parameter to filter by enabled state' { + Skip-IfMissing 'Get-PveUser' + # Either an 'Enabled' switch or a filter parameter is expected. + $hasEnabled = $script:Cmd.Parameters.ContainsKey('Enabled') -or + $script:Cmd.Parameters.ContainsKey('EnabledOnly') + $hasEnabled | Should -BeTrue + } + + It 'Should have Session parameter' { + Skip-IfMissing 'Get-PveUser' + $script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue + } + } + + Context 'Without active session' { + It 'Should throw when no session is active' { + Skip-IfMissing 'Get-PveUser' + { Get-PveUser -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + } +} + +# --------------------------------------------------------------------------- +# New-PveUser +# --------------------------------------------------------------------------- +Describe 'New-PveUser' { + + BeforeAll { $script:Cmd = Get-Command 'New-PveUser' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'New-PveUser' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'ShouldProcess support' { + It 'Should support WhatIf' { + Skip-IfMissing 'New-PveUser' + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + } + + Context 'Required parameters' { + It 'UserId should be Mandatory (must include realm, e.g. user@pam)' { + Skip-IfMissing 'New-PveUser' + $isMandatory = $script:Cmd.Parameters['UserId'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + } + + Context 'Optional parameters' { + It 'Should have Password parameter' { + Skip-IfMissing 'New-PveUser' + $hasPassword = $script:Cmd.Parameters.ContainsKey('Password') -or + $script:Cmd.Parameters.ContainsKey('Credential') + $hasPassword | Should -BeTrue + } + + It 'Should have Email parameter' { + Skip-IfMissing 'New-PveUser' + $script:Cmd.Parameters.ContainsKey('Email') | Should -BeTrue + } + + It 'Should have Groups parameter' { + Skip-IfMissing 'New-PveUser' + $script:Cmd.Parameters.ContainsKey('Groups') | Should -BeTrue + } + } +} + +# --------------------------------------------------------------------------- +# Remove-PveUser +# --------------------------------------------------------------------------- +Describe 'Remove-PveUser' { + + BeforeAll { $script:Cmd = Get-Command 'Remove-PveUser' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'Remove-PveUser' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'ShouldProcess / ConfirmImpact' { + It 'Should support WhatIf' { + Skip-IfMissing 'Remove-PveUser' + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + + It 'Should declare ConfirmImpact High' { + Skip-IfMissing 'Remove-PveUser' + $attr = $script:Cmd.ImplementingType.GetCustomAttributes( + [System.Management.Automation.CmdletAttribute], $false) | + Select-Object -First 1 + $attr.ConfirmImpact | Should -Be ([System.Management.Automation.ConfirmImpact]::High) + } + } + + Context 'Required parameters' { + It 'UserId should be Mandatory' { + Skip-IfMissing 'Remove-PveUser' + $isMandatory = $script:Cmd.Parameters['UserId'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + } +} + +# --------------------------------------------------------------------------- +# Set-PveUser +# --------------------------------------------------------------------------- +Describe 'Set-PveUser' { + + BeforeAll { $script:Cmd = Get-Command 'Set-PveUser' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'Set-PveUser' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'ShouldProcess support' { + It 'Should support WhatIf' { + Skip-IfMissing 'Set-PveUser' + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + } + + Context 'Required parameters' { + It 'UserId should be Mandatory' { + Skip-IfMissing 'Set-PveUser' + $isMandatory = $script:Cmd.Parameters['UserId'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + } +} + +# --------------------------------------------------------------------------- +# Get-PveRole +# --------------------------------------------------------------------------- +Describe 'Get-PveRole' { + + BeforeAll { $script:Cmd = Get-Command 'Get-PveRole' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'Get-PveRole' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'Parameter metadata' { + It 'Should have RoleId parameter (optional filter)' { + Skip-IfMissing 'Get-PveRole' + $script:Cmd.Parameters.ContainsKey('RoleId') | Should -BeTrue + } + + It 'Should have Session parameter' { + Skip-IfMissing 'Get-PveRole' + $script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue + } + } + + Context 'Without active session' { + It 'Should throw when no session is active' { + Skip-IfMissing 'Get-PveRole' + { Get-PveRole -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + } +} + +# --------------------------------------------------------------------------- +# Get-PvePermission +# --------------------------------------------------------------------------- +Describe 'Get-PvePermission' { + + BeforeAll { $script:Cmd = Get-Command 'Get-PvePermission' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'Get-PvePermission' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'Parameter metadata' { + It 'Should have UserId parameter' { + Skip-IfMissing 'Get-PvePermission' + $script:Cmd.Parameters.ContainsKey('UserId') | Should -BeTrue + } + + It 'Should have Path parameter (ACL path, optional)' { + Skip-IfMissing 'Get-PvePermission' + $script:Cmd.Parameters.ContainsKey('Path') | Should -BeTrue + } + + It 'Should have Session parameter' { + Skip-IfMissing 'Get-PvePermission' + $script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue + } + } + + Context 'Without active session' { + It 'Should throw when no session is active' { + Skip-IfMissing 'Get-PvePermission' + { Get-PvePermission -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + } +} + +# --------------------------------------------------------------------------- +# Set-PvePermission +# --------------------------------------------------------------------------- +Describe 'Set-PvePermission' { + + BeforeAll { $script:Cmd = Get-Command 'Set-PvePermission' -ErrorAction SilentlyContinue } + + Context 'Command existence' { + It 'Should be available after module import' { + Skip-IfMissing 'Set-PvePermission' + $script:Cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'ShouldProcess support' { + It 'Should support WhatIf' { + Skip-IfMissing 'Set-PvePermission' + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + } + + Context 'Required parameters' { + It 'Path should be Mandatory (the ACL path, e.g. / or /vms/100)' { + Skip-IfMissing 'Set-PvePermission' + $isMandatory = $script:Cmd.Parameters['Path'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'Role should be Mandatory' { + Skip-IfMissing 'Set-PvePermission' + $isMandatory = $script:Cmd.Parameters['Role'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + } +} diff --git a/tests/PSProxmoxVE.Tests/Vms/Get-PveVm.Tests.ps1 b/tests/PSProxmoxVE.Tests/Vms/Get-PveVm.Tests.ps1 new file mode 100644 index 0000000..352dd53 --- /dev/null +++ b/tests/PSProxmoxVE.Tests/Vms/Get-PveVm.Tests.ps1 @@ -0,0 +1,111 @@ +#Requires -Module Pester +<# +.SYNOPSIS + Pester 5 tests for Get-PveVm. + All tests are fully offline — no live Proxmox VE target is required. +#> + +BeforeAll { + $moduleRoot = Resolve-Path (Join-Path $PSScriptRoot '../../../src/PSProxmoxVE') + $dllCandidates = @( + Join-Path $moduleRoot 'bin/Debug/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Debug/net48/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net48/PSProxmoxVE.dll' + ) + + $script:ModuleDll = $dllCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 + + if ($null -eq $script:ModuleDll) { + throw "PSProxmoxVE.dll not found. Build the project before running Pester tests." + } + + Import-Module $script:ModuleDll -Force -ErrorAction Stop +} + +Describe 'Get-PveVm' { + + Context 'Command existence' { + It 'Should be available after module import' { + Get-Command 'Get-PveVm' -ErrorAction SilentlyContinue | + Should -Not -BeNullOrEmpty + } + + It 'Should be a CmdletInfo (binary cmdlet)' { + (Get-Command 'Get-PveVm').CommandType | Should -Be 'Cmdlet' + } + } + + Context 'Parameter validation' { + BeforeAll { + $script:Cmd = Get-Command 'Get-PveVm' + } + + It 'Should have Node parameter' { + $script:Cmd.Parameters.ContainsKey('Node') | Should -BeTrue + } + + It 'Node should not be Mandatory (all-nodes query when omitted)' { + $node = $script:Cmd.Parameters['Node'] + $isMandatory = $node.ParameterSets.Values | Where-Object { $_.IsMandatory } + $isMandatory | Should -BeNullOrEmpty + } + + It 'Node should accept pipeline input by property name' { + $node = $script:Cmd.Parameters['Node'] + $acceptsByPropName = $node.ParameterSets.Values | + Where-Object { $_.ValueFromPipelineByPropertyName } + $acceptsByPropName | Should -Not -BeNullOrEmpty + } + + It 'Should have VmId parameter' { + $script:Cmd.Parameters.ContainsKey('VmId') | Should -BeTrue + } + + It 'Should have Name parameter' { + $script:Cmd.Parameters.ContainsKey('Name') | Should -BeTrue + } + + It 'Should have Status parameter' { + $script:Cmd.Parameters.ContainsKey('Status') | Should -BeTrue + } + + It 'Should have Tag parameter' { + $script:Cmd.Parameters.ContainsKey('Tag') | Should -BeTrue + } + + It 'Should have TemplatesOnly switch parameter' { + $script:Cmd.Parameters.ContainsKey('TemplatesOnly') | Should -BeTrue + $script:Cmd.Parameters['TemplatesOnly'].ParameterType | + Should -Be ([System.Management.Automation.SwitchParameter]) + } + + It 'Should have Session parameter (inherited from PveCmdletBase)' { + $script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue + } + + It 'None of the filter parameters should be Mandatory' { + foreach ($paramName in @('Node', 'VmId', 'Name', 'Status', 'Tag', 'TemplatesOnly', 'Session')) { + $p = $script:Cmd.Parameters[$paramName] + $isMandatory = $p.ParameterSets.Values | Where-Object { $_.IsMandatory } + $isMandatory | Should -BeNullOrEmpty -Because "$paramName should be optional" + } + } + } + + Context 'Without active session' { + It 'Should throw when no session is active and no -Session is supplied' { + { Get-PveVm -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + + It 'Should throw PveNotConnectedException type' { + try { + Get-PveVm -ErrorAction Stop + } + catch { + $_.Exception.GetType().Name | Should -Match 'PveNotConnectedException|CmdletInvocationException' + } + } + } +} diff --git a/tests/PSProxmoxVE.Tests/Vms/New-PveVm.Tests.ps1 b/tests/PSProxmoxVE.Tests/Vms/New-PveVm.Tests.ps1 new file mode 100644 index 0000000..b026fea --- /dev/null +++ b/tests/PSProxmoxVE.Tests/Vms/New-PveVm.Tests.ps1 @@ -0,0 +1,154 @@ +#Requires -Module Pester +<# +.SYNOPSIS + Pester 5 tests for New-PveVm. + All tests are fully offline — no live Proxmox VE target is required. +#> + +BeforeAll { + $moduleRoot = Resolve-Path (Join-Path $PSScriptRoot '../../../src/PSProxmoxVE') + $dllCandidates = @( + Join-Path $moduleRoot 'bin/Debug/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Debug/net48/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net48/PSProxmoxVE.dll' + ) + + $script:ModuleDll = $dllCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 + + if ($null -eq $script:ModuleDll) { + throw "PSProxmoxVE.dll not found. Build the project before running Pester tests." + } + + Import-Module $script:ModuleDll -Force -ErrorAction Stop +} + +Describe 'New-PveVm' { + + Context 'Command existence' { + It 'Should be available after module import' { + Get-Command 'New-PveVm' -ErrorAction SilentlyContinue | + Should -Not -BeNullOrEmpty + } + + It 'Should be a CmdletInfo (binary cmdlet)' { + (Get-Command 'New-PveVm').CommandType | Should -Be 'Cmdlet' + } + } + + Context 'ShouldProcess support' { + BeforeAll { + $script:Cmd = Get-Command 'New-PveVm' + } + + It 'Should support ShouldProcess (WhatIf parameter present)' { + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + + It 'Should support ShouldProcess (Confirm parameter present)' { + $script:Cmd.Parameters.ContainsKey('Confirm') | Should -BeTrue + } + + It 'Should accept -WhatIf without throwing even when no session exists' { + # WhatIf must short-circuit before any network call or session check. + { New-PveVm -Node 'pve-node1' -WhatIf -ErrorAction Stop } | Should -Not -Throw + } + } + + Context 'Required parameter — Node' { + It 'Should throw when Node is omitted' { + { New-PveVm -ErrorAction Stop } | Should -Throw + } + + It 'Node should be Mandatory' { + $nodeParam = (Get-Command 'New-PveVm').Parameters['Node'] + $isMandatory = $nodeParam.ParameterSets.Values | Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + } + + Context 'Optional parameter metadata' { + BeforeAll { + $script:Cmd = Get-Command 'New-PveVm' + } + + It 'Should have VmId parameter' { + $script:Cmd.Parameters.ContainsKey('VmId') | Should -BeTrue + } + + It 'Should have Name parameter' { + $script:Cmd.Parameters.ContainsKey('Name') | Should -BeTrue + } + + It 'Should have Memory parameter' { + $script:Cmd.Parameters.ContainsKey('Memory') | Should -BeTrue + } + + It 'Should have Cores parameter' { + $script:Cmd.Parameters.ContainsKey('Cores') | Should -BeTrue + } + + It 'Should have Sockets parameter' { + $script:Cmd.Parameters.ContainsKey('Sockets') | Should -BeTrue + } + + It 'Should have CpuType parameter' { + $script:Cmd.Parameters.ContainsKey('CpuType') | Should -BeTrue + } + + It 'Should have Bios parameter' { + $script:Cmd.Parameters.ContainsKey('Bios') | Should -BeTrue + } + + It 'Should have Machine parameter' { + $script:Cmd.Parameters.ContainsKey('Machine') | Should -BeTrue + } + + It 'Should have DiskSize parameter' { + $script:Cmd.Parameters.ContainsKey('DiskSize') | Should -BeTrue + } + + It 'Should have DiskStorage parameter' { + $script:Cmd.Parameters.ContainsKey('DiskStorage') | Should -BeTrue + } + + It 'Should have DiskFormat parameter' { + $script:Cmd.Parameters.ContainsKey('DiskFormat') | Should -BeTrue + } + + It 'Should have Network parameter' { + $script:Cmd.Parameters.ContainsKey('Network') | Should -BeTrue + } + + It 'Should have Bridge parameter' { + $script:Cmd.Parameters.ContainsKey('Bridge') | Should -BeTrue + } + + It 'Should have OsType parameter' { + $script:Cmd.Parameters.ContainsKey('OsType') | Should -BeTrue + } + + It 'Should have Start switch parameter' { + $script:Cmd.Parameters.ContainsKey('Start') | Should -BeTrue + $script:Cmd.Parameters['Start'].ParameterType | + Should -Be ([System.Management.Automation.SwitchParameter]) + } + + It 'Should have Wait switch parameter' { + $script:Cmd.Parameters.ContainsKey('Wait') | Should -BeTrue + $script:Cmd.Parameters['Wait'].ParameterType | + Should -Be ([System.Management.Automation.SwitchParameter]) + } + + It 'Should have Session parameter (inherited from PveCmdletBase)' { + $script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue + } + } + + Context 'Without active session' { + It 'Should throw when no session is active (without -WhatIf)' { + { New-PveVm -Node 'pve-node1' -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + } +} diff --git a/tests/PSProxmoxVE.Tests/Vms/Remove-PveVm.Tests.ps1 b/tests/PSProxmoxVE.Tests/Vms/Remove-PveVm.Tests.ps1 new file mode 100644 index 0000000..0a6a9b0 --- /dev/null +++ b/tests/PSProxmoxVE.Tests/Vms/Remove-PveVm.Tests.ps1 @@ -0,0 +1,139 @@ +#Requires -Module Pester +<# +.SYNOPSIS + Pester 5 tests for Remove-PveVm. + All tests are fully offline — no live Proxmox VE target is required. +#> + +BeforeAll { + $moduleRoot = Resolve-Path (Join-Path $PSScriptRoot '../../../src/PSProxmoxVE') + $dllCandidates = @( + Join-Path $moduleRoot 'bin/Debug/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Debug/net48/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net48/PSProxmoxVE.dll' + ) + + $script:ModuleDll = $dllCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 + + if ($null -eq $script:ModuleDll) { + throw "PSProxmoxVE.dll not found. Build the project before running Pester tests." + } + + Import-Module $script:ModuleDll -Force -ErrorAction Stop +} + +Describe 'Remove-PveVm' { + + Context 'Command existence' { + It 'Should be available after module import' { + Get-Command 'Remove-PveVm' -ErrorAction SilentlyContinue | + Should -Not -BeNullOrEmpty + } + + It 'Should be a CmdletInfo (binary cmdlet)' { + (Get-Command 'Remove-PveVm').CommandType | Should -Be 'Cmdlet' + } + } + + Context 'ShouldProcess / ConfirmImpact' { + BeforeAll { + $script:Cmd = Get-Command 'Remove-PveVm' + } + + It 'Should support ShouldProcess (WhatIf present)' { + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + + It 'Should support ShouldProcess (Confirm present)' { + $script:Cmd.Parameters.ContainsKey('Confirm') | Should -BeTrue + } + + It 'Should declare ConfirmImpact High (verified via CmdletAttribute)' { + # Retrieve the CmdletAttribute from the underlying type to confirm ConfirmImpact. + $cmdletType = $script:Cmd.ImplementingType + $attr = $cmdletType.GetCustomAttributes( + [System.Management.Automation.CmdletAttribute], $false) | + Select-Object -First 1 + $attr.ConfirmImpact | Should -Be ([System.Management.Automation.ConfirmImpact]::High) + } + + It 'Should accept -WhatIf without throwing even when no session exists' { + { Remove-PveVm -Node 'pve-node1' -VmId 100 -WhatIf -ErrorAction Stop } | + Should -Not -Throw + } + } + + Context 'Required parameters' { + It 'Should throw when Node is omitted' { + { Remove-PveVm -VmId 100 -ErrorAction Stop } | Should -Throw + } + + It 'Should throw when VmId is omitted' { + { Remove-PveVm -Node 'pve-node1' -ErrorAction Stop } | Should -Throw + } + + It 'Node should be Mandatory' { + $nodeParam = (Get-Command 'Remove-PveVm').Parameters['Node'] + $isMandatory = $nodeParam.ParameterSets.Values | Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'VmId should be Mandatory' { + $vmidParam = (Get-Command 'Remove-PveVm').Parameters['VmId'] + $isMandatory = $vmidParam.ParameterSets.Values | Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + } + + Context 'Optional switch parameters' { + BeforeAll { + $script:Cmd = Get-Command 'Remove-PveVm' + } + + It 'Should have Purge switch parameter' { + $script:Cmd.Parameters.ContainsKey('Purge') | Should -BeTrue + $script:Cmd.Parameters['Purge'].ParameterType | + Should -Be ([System.Management.Automation.SwitchParameter]) + } + + It 'Should have Force switch parameter' { + $script:Cmd.Parameters.ContainsKey('Force') | Should -BeTrue + $script:Cmd.Parameters['Force'].ParameterType | + Should -Be ([System.Management.Automation.SwitchParameter]) + } + + It 'Should have Wait switch parameter' { + $script:Cmd.Parameters.ContainsKey('Wait') | Should -BeTrue + $script:Cmd.Parameters['Wait'].ParameterType | + Should -Be ([System.Management.Automation.SwitchParameter]) + } + } + + Context 'Pipeline input support' { + BeforeAll { + $script:Cmd = Get-Command 'Remove-PveVm' + } + + It 'Node should accept pipeline input by property name' { + $node = $script:Cmd.Parameters['Node'] + $acceptsByPropName = $node.ParameterSets.Values | + Where-Object { $_.ValueFromPipelineByPropertyName } + $acceptsByPropName | Should -Not -BeNullOrEmpty + } + + It 'VmId should accept pipeline input by property name' { + $vmid = $script:Cmd.Parameters['VmId'] + $acceptsByPropName = $vmid.ParameterSets.Values | + Where-Object { $_.ValueFromPipelineByPropertyName } + $acceptsByPropName | Should -Not -BeNullOrEmpty + } + } + + Context 'Without active session' { + It 'Should throw when no session is active (without -WhatIf)' { + { Remove-PveVm -Node 'pve-node1' -VmId 100 -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + } +} diff --git a/tests/PSProxmoxVE.Tests/Vms/VmLifecycle.Tests.ps1 b/tests/PSProxmoxVE.Tests/Vms/VmLifecycle.Tests.ps1 new file mode 100644 index 0000000..273291c --- /dev/null +++ b/tests/PSProxmoxVE.Tests/Vms/VmLifecycle.Tests.ps1 @@ -0,0 +1,333 @@ +#Requires -Module Pester +<# +.SYNOPSIS + Pester 5 tests for VM lifecycle cmdlets: + Start-PveVm, Stop-PveVm, Suspend-PveVm, Resume-PveVm, + Reset-PveVm, Restart-PveVm. + + All tests are fully offline — no live Proxmox VE target is required. + + NOTE: The C# source declares Reset-PveVm and Restart-PveVm both with + [Cmdlet(VerbsLifecycle.Restart, "PveVm")]. When both are compiled into + the same assembly PowerShell registers the last one loaded as + 'Restart-PveVm'. The test suite validates whichever cmdlet is actually + registered under each name, rather than assuming a specific implementing + type. A separate test records whether 'Reset-PveVm' is resolvable, which + is expected to change once the naming collision is fixed. +#> + +BeforeAll { + $moduleRoot = Resolve-Path (Join-Path $PSScriptRoot '../../../src/PSProxmoxVE') + $dllCandidates = @( + Join-Path $moduleRoot 'bin/Debug/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net8.0/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Debug/net48/PSProxmoxVE.dll' + Join-Path $moduleRoot 'bin/Release/net48/PSProxmoxVE.dll' + ) + + $script:ModuleDll = $dllCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 + + if ($null -eq $script:ModuleDll) { + throw "PSProxmoxVE.dll not found. Build the project before running Pester tests." + } + + Import-Module $script:ModuleDll -Force -ErrorAction Stop + + # Helper: assert the standard lifecycle parameter set for Node/VmId/Wait. + function Assert-StandardLifecycleParams { + param([string] $CmdletName) + $cmd = Get-Command $CmdletName -ErrorAction SilentlyContinue + if ($null -eq $cmd) { + Set-ItResult -Skipped -Because "$CmdletName is not yet registered (possible name collision in source)" + return $null + } + return $cmd + } +} + +# --------------------------------------------------------------------------- +# Start-PveVm +# --------------------------------------------------------------------------- +Describe 'Start-PveVm' { + + Context 'Command existence' { + It 'Should be available after module import' { + Get-Command 'Start-PveVm' -ErrorAction SilentlyContinue | + Should -Not -BeNullOrEmpty + } + } + + Context 'Parameter metadata' { + BeforeAll { $script:Cmd = Get-Command 'Start-PveVm' } + + It 'Node should be Mandatory' { + $isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'VmId should be Mandatory' { + $isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'Should have Wait switch parameter' { + $script:Cmd.Parameters.ContainsKey('Wait') | Should -BeTrue + $script:Cmd.Parameters['Wait'].ParameterType | + Should -Be ([System.Management.Automation.SwitchParameter]) + } + + It 'Should support ShouldProcess' { + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + } + + Context 'Without active session' { + It 'Should throw when no session is active (without -WhatIf)' { + { Start-PveVm -Node 'pve-node1' -VmId 100 -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + + It 'Should not throw with -WhatIf' { + { Start-PveVm -Node 'pve-node1' -VmId 100 -WhatIf -ErrorAction Stop } | + Should -Not -Throw + } + } +} + +# --------------------------------------------------------------------------- +# Stop-PveVm +# --------------------------------------------------------------------------- +Describe 'Stop-PveVm' { + + Context 'Command existence' { + It 'Should be available after module import' { + Get-Command 'Stop-PveVm' -ErrorAction SilentlyContinue | + Should -Not -BeNullOrEmpty + } + } + + Context 'Parameter metadata' { + BeforeAll { $script:Cmd = Get-Command 'Stop-PveVm' } + + It 'Node should be Mandatory' { + $isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'VmId should be Mandatory' { + $isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'Should have Wait switch parameter' { + $script:Cmd.Parameters.ContainsKey('Wait') | Should -BeTrue + } + + It 'Should support ShouldProcess' { + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + } + + Context 'Without active session' { + It 'Should throw when no session is active (without -WhatIf)' { + { Stop-PveVm -Node 'pve-node1' -VmId 100 -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + + It 'Should not throw with -WhatIf' { + { Stop-PveVm -Node 'pve-node1' -VmId 100 -WhatIf -ErrorAction Stop } | + Should -Not -Throw + } + } +} + +# --------------------------------------------------------------------------- +# Suspend-PveVm +# --------------------------------------------------------------------------- +Describe 'Suspend-PveVm' { + + Context 'Command existence' { + It 'Should be available after module import' { + Get-Command 'Suspend-PveVm' -ErrorAction SilentlyContinue | + Should -Not -BeNullOrEmpty + } + } + + Context 'Parameter metadata' { + BeforeAll { $script:Cmd = Get-Command 'Suspend-PveVm' } + + It 'Node should be Mandatory' { + $isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'VmId should be Mandatory' { + $isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'Should have Wait switch parameter' { + $script:Cmd.Parameters.ContainsKey('Wait') | Should -BeTrue + } + + It 'Should support ShouldProcess' { + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + } + + Context 'Without active session' { + It 'Should throw when no session is active (without -WhatIf)' { + { Suspend-PveVm -Node 'pve-node1' -VmId 100 -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + } +} + +# --------------------------------------------------------------------------- +# Resume-PveVm +# --------------------------------------------------------------------------- +Describe 'Resume-PveVm' { + + Context 'Command existence' { + It 'Should be available after module import' { + Get-Command 'Resume-PveVm' -ErrorAction SilentlyContinue | + Should -Not -BeNullOrEmpty + } + } + + Context 'Parameter metadata' { + BeforeAll { $script:Cmd = Get-Command 'Resume-PveVm' } + + It 'Node should be Mandatory' { + $isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'VmId should be Mandatory' { + $isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'Should have Wait switch parameter' { + $script:Cmd.Parameters.ContainsKey('Wait') | Should -BeTrue + } + + It 'Should support ShouldProcess' { + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + } + + Context 'Without active session' { + It 'Should throw when no session is active (without -WhatIf)' { + { Resume-PveVm -Node 'pve-node1' -VmId 100 -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + } +} + +# --------------------------------------------------------------------------- +# Reset-PveVm +# NOTE: Source declares both Reset-PveVmCmdlet and RestartPveVmCmdlet with +# [Cmdlet(VerbsLifecycle.Restart, "PveVm")]. Until the collision is resolved, +# Reset-PveVm may not be separately registered. +# --------------------------------------------------------------------------- +Describe 'Reset-PveVm' { + + Context 'Command existence' { + It 'Reset-PveVm should be registered in the module exports' { + # Per the .psd1 manifest, 'Reset-PveVm' is listed in CmdletsToExport. + # If the name-collision is present, this may resolve to Restart-PveVm's type. + $cmd = Get-Command 'Reset-PveVm' -ErrorAction SilentlyContinue + $cmd | Should -Not -BeNullOrEmpty + } + } + + Context 'Parameter metadata' { + BeforeAll { + $script:Cmd = Get-Command 'Reset-PveVm' -ErrorAction SilentlyContinue + } + + It 'Node should be present' { + if ($null -eq $script:Cmd) { Set-ItResult -Skipped -Because 'Reset-PveVm not registered'; return } + $script:Cmd.Parameters.ContainsKey('Node') | Should -BeTrue + } + + It 'VmId should be present' { + if ($null -eq $script:Cmd) { Set-ItResult -Skipped -Because 'Reset-PveVm not registered'; return } + $script:Cmd.Parameters.ContainsKey('VmId') | Should -BeTrue + } + + It 'Wait should be present' { + if ($null -eq $script:Cmd) { Set-ItResult -Skipped -Because 'Reset-PveVm not registered'; return } + $script:Cmd.Parameters.ContainsKey('Wait') | Should -BeTrue + } + + It 'Should support ShouldProcess' { + if ($null -eq $script:Cmd) { Set-ItResult -Skipped -Because 'Reset-PveVm not registered'; return } + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + } +} + +# --------------------------------------------------------------------------- +# Restart-PveVm +# --------------------------------------------------------------------------- +Describe 'Restart-PveVm' { + + Context 'Command existence' { + It 'Should be available after module import' { + Get-Command 'Restart-PveVm' -ErrorAction SilentlyContinue | + Should -Not -BeNullOrEmpty + } + } + + Context 'Parameter metadata' { + BeforeAll { $script:Cmd = Get-Command 'Restart-PveVm' } + + It 'Node should be Mandatory' { + $isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'VmId should be Mandatory' { + $isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values | + Where-Object { $_.IsMandatory } + $isMandatory | Should -Not -BeNullOrEmpty + } + + It 'Should have Timeout parameter' { + $script:Cmd.Parameters.ContainsKey('Timeout') | Should -BeTrue + } + + It 'Should have Wait switch parameter' { + $script:Cmd.Parameters.ContainsKey('Wait') | Should -BeTrue + $script:Cmd.Parameters['Wait'].ParameterType | + Should -Be ([System.Management.Automation.SwitchParameter]) + } + + It 'Should support ShouldProcess' { + $script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + } + + Context 'Without active session' { + It 'Should throw when no session is active (without -WhatIf)' { + { Restart-PveVm -Node 'pve-node1' -VmId 100 -ErrorAction Stop } | + Should -Throw '*No active Proxmox VE session*' + } + + It 'Should not throw with -WhatIf' { + { Restart-PveVm -Node 'pve-node1' -VmId 100 -WhatIf -ErrorAction Stop } | + Should -Not -Throw + } + } +}