Skip to content

security: add gitleaks scanning + 2026-04-18 delta review #556

security: add gitleaks scanning + 2026-04-18 delta review

security: add gitleaks scanning + 2026-04-18 delta review #556

Workflow file for this run

name: Tests
on:
push:
branches: [dev, main, master]
pull_request:
branches: [dev, main, master]
permissions:
contents: read
jobs:
test:
name: Pester Tests (${{ matrix.os }}, ${{ matrix.pwsh && 'PS 7.x' || 'PS 5.1' }})
runs-on: ${{ matrix.os }}
permissions:
contents: read
issues: write
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
pwsh: [true]
include:
# Also test PowerShell 5.1 (Desktop) on Windows
- os: windows-latest
pwsh: false
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Cache PowerShell modules
uses: actions/cache@v4
id: ps-cache
with:
path: ${{ runner.os != 'Windows' && '~/.local/share/powershell/Modules' || (matrix.pwsh && '~/Documents/PowerShell/Modules' || '~/Documents/WindowsPowerShell/Modules') }}
key: ps-modules-${{ matrix.os }}-${{ matrix.pwsh }}-v1
- name: Install dependencies (pwsh)
if: matrix.pwsh && steps.ps-cache.outputs.cache-hit != 'true'
shell: pwsh
run: |
# Register PSGallery if not present (required for Windows Server 2025)
if (-not (Get-PSRepository -Name PSGallery -ErrorAction SilentlyContinue)) {
Register-PSRepository -Default
}
Set-PSRepository PSGallery -InstallationPolicy Trusted
Install-Module Pester -Force -Scope CurrentUser -MinimumVersion 5.0.0
Install-Module PSScriptAnalyzer -Force -Scope CurrentUser
- name: Install dependencies (powershell)
if: "!matrix.pwsh && steps.ps-cache.outputs.cache-hit != 'true'"
shell: powershell
run: |
# Register PSGallery if not present (required for Windows Server 2025)
if (-not (Get-PSRepository -Name PSGallery -ErrorAction SilentlyContinue)) {
Register-PSRepository -Default
}
Set-PSRepository PSGallery -InstallationPolicy Trusted
Install-Module Pester -Force -Scope CurrentUser -MinimumVersion 5.0.0
Install-Module PSScriptAnalyzer -Force -Scope CurrentUser
- name: Build module (pwsh)
if: matrix.pwsh
shell: pwsh
run: ./deploy.ps1 -Environment dev -SkipVersion
- name: Build module (powershell)
if: '!matrix.pwsh'
shell: powershell
run: ./deploy.ps1 -Environment dev -SkipVersion
- name: Run Pester tests (pwsh)
if: matrix.pwsh
shell: pwsh
run: |
$config = New-PesterConfiguration
$config.Run.Path = './Tests/*.Tests.ps1'
$config.Run.PassThru = $true
$config.Filter.ExcludeTag = @('Integration', 'Live')
$config.TestResult.Enabled = $true
$config.TestResult.OutputPath = 'TestResults.xml'
$config.TestResult.OutputFormat = 'NUnitXml'
$config.Output.Verbosity = 'Detailed'
# Code coverage configuration
$config.CodeCoverage.Enabled = $true
$config.CodeCoverage.Path = @('./PowerNetbox/PowerNetbox.psm1')
$config.CodeCoverage.OutputPath = 'coverage.xml'
$config.CodeCoverage.OutputFormat = 'JaCoCo'
$config.CodeCoverage.CoveragePercentTarget = 70
$config.CodeCoverage.UseBreakpoints = $false
$result = Invoke-Pester -Configuration $config
# Fail on test failures
if ($result.FailedCount -gt 0) {
Write-Error "$($result.FailedCount) test(s) failed"
exit 1
}
# Enforce coverage threshold on Linux runner (single canonical measurement)
if ($env:RUNNER_OS -eq 'Linux') {
$coverage = [math]::Round($result.CodeCoverage.CoveragePercent, 2)
Write-Host "Code coverage: $coverage% (threshold: 70%)"
if ($coverage -lt 70) {
Write-Error "Code coverage ($coverage%) is below the 70% threshold"
exit 1
}
Write-Host "Coverage threshold passed"
}
- name: Run Pester tests (powershell)
if: '!matrix.pwsh'
shell: powershell
run: |
$config = New-PesterConfiguration
$config.Run.Path = './Tests/*.Tests.ps1'
$config.Run.Exit = $true
$config.Filter.ExcludeTag = @('Integration', 'Live')
$config.TestResult.Enabled = $true
$config.TestResult.OutputPath = 'TestResults.xml'
$config.TestResult.OutputFormat = 'NUnitXml'
$config.Output.Verbosity = 'Detailed'
# Code coverage disabled on PS 5.1: profiler requires PS 7+,
# and breakpoint-based coverage is too slow on 40K-line built module
Invoke-Pester -Configuration $config
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results-${{ matrix.os }}-${{ matrix.pwsh && 'pwsh' || 'ps51' }}
path: TestResults.xml
retention-days: 30
- name: Upload coverage report
uses: actions/upload-artifact@v4
if: always() && matrix.pwsh
with:
name: coverage-${{ matrix.os }}
path: coverage.xml
retention-days: 30
- name: Create issue on failure
if: failure() && github.event_name == 'push'
uses: actions/github-script@v7
with:
script: |
const os = '${{ matrix.os }}';
const ps = '${{ matrix.pwsh }}' === 'true' ? '7.x (Core)' : '5.1 (Desktop)';
const runUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`;
const issues = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
labels: 'test-failure,automated'
});
const existingIssue = issues.data.find(i =>
i.title.includes(context.sha.substring(0, 7))
);
const body = `## Test Failure Report
| Field | Value |
|-------|-------|
| **Commit** | ${context.sha.substring(0, 7)} |
| **Branch** | ${context.ref.replace('refs/heads/', '')} |
| **Platform** | ${os} |
| **PowerShell** | ${ps} |
| **Workflow Run** | [View Details](${runUrl}) |
---
*This issue was automatically created by GitHub Actions*`;
if (existingIssue) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: existingIssue.number,
body: `### Additional failure on ${os} (${ps})\n\n${body}`
});
} else {
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `🔴 Test Failure: ${context.sha.substring(0, 7)} on ${os}`,
body: body,
labels: ['test-failure', 'automated', 'bug']
});
}
close-issues:
name: Close Resolved Issues
runs-on: ubuntu-latest
needs: test
if: success() && github.event_name == 'push'
permissions:
contents: read
issues: write
steps:
- name: Close test-failure issues
uses: actions/github-script@v7
with:
script: |
const issues = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
labels: 'test-failure,automated'
});
for (const issue of issues.data) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: `## ✅ Tests Passing\n\nAll tests are now passing on commit ${context.sha.substring(0, 7)}.\n\nAutomatically closing this issue.`
});
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
state: 'closed'
});
}