#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '5.0.0' } <# The write gate has exactly one origin: ShouldProcess. The tests that matter here are the negative ones. -Debug and -Verbose are the two switches an operator is most likely to reach for believing they make a run safe, and neither does. If that ever changes silently, someone will run an enforcing pass believing they are looking rather than touching. #> BeforeAll { $repoRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent . (Join-Path $repoRoot 'tests/TestHelpers.ps1') foreach ($file in (Get-PersonaSourceFile -RepoRoot $repoRoot)) { . $file } $script:target = 'extension__' $script:config = New-TestRuntimeConfiguration -TargetAttribute $target $script:population = @(New-TestPopulation -Count 12 -TargetAttribute $target) $script:entryScript = Join-Path $repoRoot 'Invoke-PersonaEngine.ps1' } Describe 'Mode derives from the gate alone' -Tag 'Safety' { BeforeEach { Mock Get-PersonaUsers { $script:population } Mock Write-Host { } # Two of these tests raise the verbose and debug preferences deliberately. # Without this the per-user diagnostic lines flood the whole suite's output. Mock Write-Verbose { } Mock Set-UserPersonaAttribute { [pscustomobject]@{ Succeeded = $true } } } It 'writes when -Debug is active and the gate allows it' { # -Debug must not imply read-only. An operator who believed otherwise would # reach for it as a safety control and get an enforcing run. $DebugPreference = 'Continue' $outcome = Invoke-PersonaEngineRun -Configuration $config -TargetAttribute $target ` -Context (New-TestAuditContext -Mode 'Enforce') ` -IsEnforcing -Tracing ` -ShouldProcessGate { param($t, $d) $true } $outcome.Counters.Updated | Should -BeGreaterThan 0 Should -Invoke Set-UserPersonaAttribute -Times $outcome.Counters.Updated -Exactly } It 'writes when -Verbose is active and the gate allows it' { $VerbosePreference = 'Continue' $outcome = Invoke-PersonaEngineRun -Configuration $config -TargetAttribute $target ` -Context (New-TestAuditContext -Mode 'Enforce') ` -IsEnforcing ` -ShouldProcessGate { param($t, $d) $true } $outcome.Counters.Updated | Should -BeGreaterThan 0 } It 'refuses every write when the gate refuses, regardless of IsEnforcing' { # IsEnforcing shapes the Action label; the gate decides the write. A # disagreement between them must resolve in favour of not writing. $outcome = Invoke-PersonaEngineRun -Configuration $config -TargetAttribute $target ` -Context (New-TestAuditContext -Mode 'Enforce') ` -IsEnforcing ` -ShouldProcessGate { param($t, $d) $false } Should -Invoke Set-UserPersonaAttribute -Times 0 -Exactly $outcome.Counters.Updated | Should -Be 0 $outcome.Counters.WouldUpdate | Should -BeGreaterThan 0 } It 'honours a gate that allows some accounts and refuses others' { # A per-account gate, as ShouldProcess is when the operator answers "Yes" # rather than "Yes to All". Both buckets must be populated in one run. $script:gateCalls = 0 $outcome = Invoke-PersonaEngineRun -Configuration $config -TargetAttribute $target ` -Context (New-TestAuditContext -Mode 'Enforce') ` -IsEnforcing ` -ShouldProcessGate { param($t, $d) ($script:gateCalls++ % 2) -eq 0 } ($outcome.Counters.Updated + $outcome.Counters.WouldUpdate) | Should -BeGreaterThan 0 $outcome.Counters.Updated | Should -BeGreaterThan 0 $outcome.Counters.WouldUpdate | Should -BeGreaterThan 0 } } Describe 'The entry script declares the safety contract it promises' -Tag 'Safety' { BeforeAll { $script:entryText = Get-Content -LiteralPath $script:entryScript -Raw } It 'declares SupportsShouldProcess with a High confirm impact' { $entryText | Should -Match 'SupportsShouldProcess\s*=\s*\$true' $entryText | Should -Match "ConfirmImpact\s*=\s*'High'" } It 'does not declare a preview or no-write parameter of its own' { # Two sources of truth for the write gate is the defect class Principle III # exists to prevent. -WhatIf is the only approved control. $entryText | Should -Not -Match '\[switch\]\s*\$Preview' $entryText | Should -Not -Match '\[switch\]\s*\$NoWrite' $entryText | Should -Not -Match '\[switch\]\s*\$ReadOnly' $entryText | Should -Not -Match '\[switch\]\s*\$DryRun' } It 'derives the mode from ShouldProcess' { $entryText | Should -Match '\$PSCmdlet\.ShouldProcess\(' } It 'does not derive the mode from DebugPreference or WhatIfPreference' { # Reading the preference variables directly would reintroduce a second source # of truth by the back door. $entryText | Should -Not -Match '\$WhatIfPreference' $entryText | Should -Not -Match 'if\s*\(\s*\$DebugPreference' } }