Files
PSProxmoxVE/.github/workflows/unit-tests.yml
T
goodolclint-claude[bot] 122e79407c fix: reap the whole generated-ISO family without over-matching, and stop building python from the filename
Two filed issues in one rewrite of preflight-cleanup.sh's ISO block, because
they are the same twenty lines.

#111 — ISO_FILENAME was interpolated into python3 -c PROGRAM TEXT inside a
single-quoted literal, so a quote in the value escaped it and executed
arbitrary Python in a container holding PVE_API_TOKEN, PVE_PASSWORD, the
Terraform state and the storage VM's SSH key. It now arrives through the
environment and is read with os.environ. The volid is passed to urllib's
quote() via argv for the same reason, and an empty encode result now skips
the volume instead of issuing a DELETE against the bare collection URL.

#105 — generated ISOs embed a hash of first-boot.sh, so every change to that
script mints a new filename. Deleting only the exact current name orphaned
each earlier ISO on the storage permanently, because force-cleanup wipes the
Terraform state that could otherwise reclaim it. The family is now swept by
rebuilding the full generated shape: the captured prefix plus twelve hex
characters plus .iso. A prefix test alone would also have matched a longer
FQDN's family and any hand-uploaded "-manual-backup.iso" sibling, which in a
script whose job is deletion is worse than the leak it fixes.

Multi-delete applies only to that family. A name that is not generated — the
storage VM's cloud image — keeps the original one-shot behaviour, since a
basename can repeat across content namespaces and a plain name carries nothing
that identifies a family.

Adds preflight-cleanup.test.sh, wired into shell-selfchecks. The script had no
coverage at all. It stubs curl and sleep, then asserts on the DELETEs issued:
the family goes, the pinned base ISO and unrelated uploads stay, a non-hash
sibling stays, the cloud image takes only itself, a quoted payload is data
rather than code, and unset storage skips only the ISO branch. Every case also
asserts the script ran to completion and removed the Terraform state, so a path
that dies early cannot pass by having issued the right DELETEs first.
2026-09-01 18:05:03 -05:00

183 lines
6.5 KiB
YAML

name: Unit Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
# ── Build module artifacts (only job that needs .NET SDK) ──────────
build:
runs-on: ${{ matrix.os }}
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
framework: netstandard2.0
- os: ubuntu-latest
framework: netstandard2.0
steps:
- uses: actions/checkout@v7
- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: '10.0.x'
- name: Build module
run: dotnet publish src/PSProxmoxVE/PSProxmoxVE.csproj --configuration Release --framework ${{ matrix.framework }} --output ./publish/${{ matrix.framework }}
- name: Clean publish output for PS module loading
shell: bash
run: rm -f ./publish/${{ matrix.framework }}/*.deps.json ./publish/${{ matrix.framework }}/*.runtimeconfig.json
- name: Upload module artifact
uses: actions/upload-artifact@v7
with:
name: module-${{ matrix.framework }}-${{ matrix.os }}
path: ./publish/${{ matrix.framework }}/
# ── Pester tests (no .NET SDK — just PowerShell + Pester) ─────────
pester-tests:
needs: build
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
ps_version: '5.1'
framework: netstandard2.0
artifact_os: windows-latest
shell: powershell
- os: windows-latest
ps_version: '7.5'
framework: netstandard2.0
artifact_os: windows-latest
shell: pwsh
- os: ubuntu-latest
ps_version: '7.5'
framework: netstandard2.0
artifact_os: ubuntu-latest
shell: pwsh
- os: macos-latest
ps_version: '7.5'
framework: netstandard2.0
artifact_os: ubuntu-latest
shell: pwsh
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v7
- name: Download module artifact
uses: actions/download-artifact@v8
with:
name: module-${{ matrix.framework }}-${{ matrix.artifact_os }}
path: ./publish/${{ matrix.framework }}/
- name: Install Pester 5 (PS 5.1)
if: matrix.ps_version == '5.1'
shell: powershell
run: |
Install-Module -Name Pester -MinimumVersion 5.0 -Force -Scope CurrentUser -SkipPublisherCheck
- name: Install Pester 5 (PS 7.x)
if: matrix.ps_version != '5.1'
shell: pwsh
run: |
Install-Module -Name Pester -MinimumVersion 5.0 -Force -Scope CurrentUser
- name: Copy module to module path (PS 5.1)
if: matrix.ps_version == '5.1'
shell: powershell
run: |
$modulePath = "$env:USERPROFILE\Documents\WindowsPowerShell\Modules\PSProxmoxVE"
New-Item -ItemType Directory -Path $modulePath -Force | Out-Null
Copy-Item -Path .\publish\${{ matrix.framework }}\* -Destination $modulePath -Recurse -Force
- name: Copy module to module path (PS 7.x, Windows)
if: matrix.ps_version != '5.1' && matrix.os == 'windows-latest'
shell: pwsh
run: |
$modulePath = "$env:USERPROFILE\Documents\PowerShell\Modules\PSProxmoxVE"
New-Item -ItemType Directory -Path $modulePath -Force | Out-Null
Copy-Item -Path .\publish\${{ matrix.framework }}\* -Destination $modulePath -Recurse -Force
- name: Copy module to module path (PS 7.x, non-Windows)
if: matrix.ps_version != '5.1' && matrix.os != 'windows-latest'
shell: pwsh
run: |
$modulePath = "$HOME/.local/share/powershell/Modules/PSProxmoxVE"
New-Item -ItemType Directory -Path $modulePath -Force | Out-Null
Copy-Item -Path ./publish/${{ matrix.framework }}/* -Destination $modulePath -Recurse -Force
- name: Run Pester tests (PS 5.1)
if: matrix.ps_version == '5.1'
shell: powershell
run: |
Import-Module Pester -MinimumVersion 5.0
$config = New-PesterConfiguration
$config.Run.Path = "tests/PSProxmoxVE.Tests"
$config.Run.Exit = $true
$config.Filter.ExcludeTag = @("Integration")
$config.Output.Verbosity = "Detailed"
$config.TestResult.Enabled = $true
$config.TestResult.OutputFormat = "NUnitXml"
$config.TestResult.OutputPath = "TestResults/pester-results.xml"
Invoke-Pester -Configuration $config
- name: Run Pester tests (PS 7.x)
if: matrix.ps_version != '5.1'
shell: pwsh
run: |
Import-Module Pester -MinimumVersion 5.0
$config = New-PesterConfiguration
$config.Run.Path = "tests/PSProxmoxVE.Tests"
$config.Run.Exit = $true
$config.Filter.ExcludeTag = @("Integration")
$config.Output.Verbosity = "Detailed"
$config.TestResult.Enabled = $true
$config.TestResult.OutputFormat = "NUnitXml"
$config.TestResult.OutputPath = "TestResults/pester-results.xml"
Invoke-Pester -Configuration $config
- name: Upload test results
if: always()
uses: actions/upload-artifact@v7
with:
name: pester-results-${{ matrix.os }}-ps${{ matrix.ps_version }}
path: TestResults/
# ── CI infrastructure shell self-checks ─────────────────────────────
# These guard the provisioning and reporting scripts, whose real exercise is
# a ~45-minute nested-PVE run. They stub ssh/gh/git and finish in seconds, so
# a regression in them is caught on the PR rather than a week later.
shell-selfchecks:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- name: prepare-test-environment self-check
run: bash tests/infrastructure/scripts/prepare-test-environment.test.sh
- name: report-package-currency self-check
run: bash tests/infrastructure/scripts/report-package-currency.test.sh
- name: preflight-cleanup self-check
run: bash tests/infrastructure/scripts/preflight-cleanup.test.sh
- name: Shell syntax check
run: |
for f in tests/infrastructure/scripts/*.sh; do
bash -n "$f" || exit 1
done