security: add gitleaks scanning + 2026-04-18 delta review #556
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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' | |
| }); | |
| } |