tests/StrictModeGuard.Tests.ps1
|
#Requires -Version 7.0 #Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '5.0.0' } <# AB#5672 -- the guard against reintroducing a StrictMode weakening. The scan itself lives in scripts/Test-StrictModeGuard.ps1, with the annotated allow-list of the twenty pre-existing sites and the reason each is still there. CI runs that script as its own step (.github/workflows/ci.yml) so a violation shows up as a GitHub annotation on the offending line; this file runs the same script so the check also fires in a local `Invoke-Pester -Path tests/`, which is where anyone adding a `Set-StrictMode -Off` will actually be at the time. One shared implementation deliberately, not two: a guard that can pass in one place and fail in the other is worse than no guard. #> Describe 'StrictMode weakening guard (AB#5672)' { BeforeAll { $script:RepoRoot = Split-Path -Parent $PSScriptRoot $script:GuardPath = Join-Path $script:RepoRoot 'scripts' 'Test-StrictModeGuard.ps1' } It 'the guard script exists and is where CI expects it' { Test-Path -LiteralPath $script:GuardPath | Should -BeTrue } It 'the shipped module introduces no StrictMode weakening outside the allow-list' { # Run out-of-process: the script calls `exit`, which would take the Pester run with it. $Output = & pwsh -NoProfile -File $script:GuardPath -RepoRoot $script:RepoRoot 2>&1 $ExitCode = $LASTEXITCODE # Surfaced on failure so the reader sees WHICH site, not just that something failed. if ($ExitCode -ne 0) { Write-Host ($Output -join [Environment]::NewLine) } $ExitCode | Should -Be 0 -Because 'shipped module code must use Set-StrictMode -Version Latest; a new -Off, -Version 1.0/2.0/3.0 either needs removing or needs an allow-list entry with a written reason' } It 'catches a weakening that is NOT on the allow-list' { # A guard nobody has ever seen fail is not known to work. This proves the teeth by adding a # violation to a throwaway copy of the repo layout rather than trusting the happy path. $Sandbox = Join-Path ([System.IO.Path]::GetTempPath()) ("AZSC_StrictModeGuard_" + [guid]::NewGuid().ToString('N')) try { $ModuleDir = Join-Path $Sandbox 'Modules' New-Item -ItemType Directory -Path $ModuleDir -Force | Out-Null Set-Content -LiteralPath (Join-Path $ModuleDir 'Offender.ps1') -Value @( 'function Test-Offender {' ' Set-StrictMode -Off' '}' ) $Output = & pwsh -NoProfile -File $script:GuardPath -RepoRoot $Sandbox -IgnoreStaleAllowList 2>&1 $ExitCode = $LASTEXITCODE $ExitCode | Should -Be 1 -Because 'a Set-StrictMode -Off in a file not on the allow-list has to fail the build' ($Output -join "`n") | Should -Match 'Offender\.ps1' } finally { if (Test-Path -LiteralPath $Sandbox) { Remove-Item -LiteralPath $Sandbox -Recurse -Force } } } It 'ignores the words "Set-StrictMode -Off" in a comment' { # The collectors are full of comments discussing this exact defect class, so a grep-based # guard would fire on prose. The scanner parses the AST for that reason. $Sandbox = Join-Path ([System.IO.Path]::GetTempPath()) ("AZSC_StrictModeGuard_" + [guid]::NewGuid().ToString('N')) try { $ModuleDir = Join-Path $Sandbox 'Modules' New-Item -ItemType Directory -Path $ModuleDir -Force | Out-Null Set-Content -LiteralPath (Join-Path $ModuleDir 'Innocent.ps1') -Value @( '# This used to call Set-StrictMode -Off, which is exactly the thing AB#5672 forbids.' 'Set-StrictMode -Version Latest' '$Note = "do not Set-StrictMode -Off here"' ) $null = & pwsh -NoProfile -File $script:GuardPath -RepoRoot $Sandbox -IgnoreStaleAllowList 2>&1 $ExitCode = $LASTEXITCODE $ExitCode | Should -Be 0 -Because 'prose and string literals are not calls; only a real CommandAst counts' } finally { if (Test-Path -LiteralPath $Sandbox) { Remove-Item -LiteralPath $Sandbox -Recurse -Force } } } It 'treats -Version 1.0 / 2.0 / 3.0 as weakenings, not just -Off' -ForEach @( @{ Argument = '-Version 1.0' } @{ Argument = '-Version 2.0' } @{ Argument = '-Version 3.0' } ) { # -Version 2.0 already catches unset variables, so it LOOKS strict enough. It does not catch # an out-of-range array index, which is the fault that emptied the Security worksheet. $Sandbox = Join-Path ([System.IO.Path]::GetTempPath()) ("AZSC_StrictModeGuard_" + [guid]::NewGuid().ToString('N')) try { $ModuleDir = Join-Path $Sandbox 'Modules' New-Item -ItemType Directory -Path $ModuleDir -Force | Out-Null Set-Content -LiteralPath (Join-Path $ModuleDir 'Downgrade.ps1') -Value "Set-StrictMode $Argument" $null = & pwsh -NoProfile -File $script:GuardPath -RepoRoot $Sandbox -IgnoreStaleAllowList 2>&1 $LASTEXITCODE | Should -Be 1 -Because "Set-StrictMode $Argument is weaker than Latest" } finally { if (Test-Path -LiteralPath $Sandbox) { Remove-Item -LiteralPath $Sandbox -Recurse -Force } } } It 'fails when an allow-list entry has gone stale' { # The allow-list can only be trusted if it cannot rot. The collector StrictMode baseline once # shipped EMPTY while 24 collectors were failing; an allow-list naming files that no longer # weaken anything is the same failure mode pointing the other way. $Sandbox = Join-Path ([System.IO.Path]::GetTempPath()) ("AZSC_StrictModeGuard_" + [guid]::NewGuid().ToString('N')) try { New-Item -ItemType Directory -Path (Join-Path $Sandbox 'Modules') -Force | Out-Null # An empty tree weakens nothing at all, so EVERY allow-list entry is stale. This is the # one sandbox case that must NOT pass -IgnoreStaleAllowList, since the stale check is # exactly what is under test here. $Output = & pwsh -NoProfile -File $script:GuardPath -RepoRoot $Sandbox 2>&1 $ExitCode = $LASTEXITCODE $ExitCode | Should -Be 1 ($Output -join "`n") | Should -Match 'no longer weakens StrictMode' } finally { if (Test-Path -LiteralPath $Sandbox) { Remove-Item -LiteralPath $Sandbox -Recurse -Force } } } } |