#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '5.0.0' } <# Editor exit codes 0-4 (cli-edit-persona-engine-config.md). An exit code is the only thing a pipeline sees. Every code has to be reachable and has to mean what the contract says - in particular, code 3 (file unreadable) and code 4 (schema unusable) must not collapse into code 1, or a missing schema file gets reported to a rule author as "your configuration is invalid". #> BeforeAll { $repoRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent . (Join-Path $repoRoot 'tests/TestHelpers.ps1') $script:editor = Join-Path $repoRoot 'Edit-PersonaEngineConfig.ps1' $script:scratch = Join-Path ([System.IO.Path]::GetTempPath()) ("pe-ec-{0}" -f [guid]::NewGuid().ToString('N')) $null = New-Item -ItemType Directory -Path $script:scratch -Force function Invoke-Editor { param([string[]] $ArgumentList) $exe = (Get-Process -Id $PID).Path $null = & $exe -NoProfile -NonInteractive -File $script:editor @ArgumentList 2>&1 $LASTEXITCODE } } AfterAll { Remove-Item -LiteralPath $script:scratch -Recurse -Force -ErrorAction SilentlyContinue } Describe 'Editor exit codes' { It 'returns 0 for a valid configuration' { $path = Save-TestConfiguration -Document (New-TestConfigurationDocument) -Directory $script:scratch Invoke-Editor -ArgumentList @('-ConfigPath', $path, '-ValidateOnly', '-NonInteractive') | Should -Be 0 } It 'returns 1 when Error findings are present' { $document = New-TestConfigurationDocument $document.rules[1].persona = 'Undeclared-Persona' $path = Save-TestConfiguration -Document $document -Directory $script:scratch Invoke-Editor -ArgumentList @('-ConfigPath', $path, '-ValidateOnly', '-NonInteractive') | Should -Be 1 } It 'returns 2 for Warning findings under -TreatWarningsAsErrors' { # A pinned global mode plus a per-condition override: a Warning, not an Error. # VR-005 escalates it only when the caller asks. $document = New-TestConfigurationDocument $document.dataSources.groups.membershipMode = 'direct' $document.rules += @{ id = 'RULE-0030-TIER0'; name = 'Tier 0'; description = 'Tier 0 group members.' enabled = $true; priority = 30; persona = 'Tier0-Admin' match = @{ operator = 'all' conditions = @(@{ type = 'membership'; operator = 'memberOf'; membershipMode = 'transitive' groupObjectIds = @('00000000-0000-0000-0000-0000000000a0') }) } } $path = Save-TestConfiguration -Document $document -Directory $script:scratch Invoke-Editor -ArgumentList @('-ConfigPath', $path, '-ValidateOnly', '-NonInteractive') | Should -Be 0 Invoke-Editor -ArgumentList @('-ConfigPath', $path, '-ValidateOnly', '-NonInteractive', '-TreatWarningsAsErrors') | Should -Be 2 } It 'returns 3 when the configuration file is absent' { Invoke-Editor -ArgumentList @('-ConfigPath', (Join-Path $script:scratch 'absent.json'), '-ValidateOnly', '-NonInteractive') | Should -Be 3 } It 'returns 4 when the schema file is absent' { $path = Save-TestConfiguration -Document (New-TestConfigurationDocument) -Directory $script:scratch Invoke-Editor -ArgumentList @( '-ConfigPath', $path, '-ValidateOnly', '-NonInteractive', '-SchemaPath', (Join-Path $script:scratch 'no-schema.json')) | Should -Be 4 } It 'returns 4 when the schema file exists but cannot be parsed' { # Distinct from code 1 on purpose. The configuration was never actually # checked, so calling it invalid would be a guess. $brokenSchema = Join-Path $script:scratch 'broken.json' Set-Content -LiteralPath $brokenSchema -Value '{ not json' -Encoding utf8NoBOM $path = Save-TestConfiguration -Document (New-TestConfigurationDocument) -Directory $script:scratch Invoke-Editor -ArgumentList @('-ConfigPath', $path, '-ValidateOnly', '-NonInteractive', '-SchemaPath', $brokenSchema) | Should -Be 4 } It 'prefers code 3 over code 1 when the file cannot be read at all' { # An unreadable file produces a PE-SYN Error finding too. Reporting code 1 # would tell the author their rules are wrong when the file never opened. Invoke-Editor -ArgumentList @('-ConfigPath', $script:scratch, '-ValidateOnly', '-NonInteractive') | Should -Be 3 } It 'reaches every documented code across the corpus' { $reached = [System.Collections.Generic.HashSet[int]]::new() $valid = Save-TestConfiguration -Document (New-TestConfigurationDocument) -Directory $script:scratch $null = $reached.Add((Invoke-Editor -ArgumentList @('-ConfigPath', $valid, '-ValidateOnly', '-NonInteractive'))) $invalidDoc = New-TestConfigurationDocument $invalidDoc.rules[1].persona = 'Undeclared-Persona' $invalid = Save-TestConfiguration -Document $invalidDoc -Directory $script:scratch $null = $reached.Add((Invoke-Editor -ArgumentList @('-ConfigPath', $invalid, '-ValidateOnly', '-NonInteractive'))) $warnDoc = New-TestConfigurationDocument $warnDoc.dataSources.groups.membershipMode = 'direct' $warnDoc.rules += @{ id = 'RULE-0030-TIER0'; name = 'Tier 0'; description = 'Tier 0 group members.' enabled = $true; priority = 30; persona = 'Tier0-Admin' match = @{ operator = 'all'; conditions = @(@{ type = 'membership'; operator = 'memberOf'; membershipMode = 'transitive' groupObjectIds = @('00000000-0000-0000-0000-0000000000a0') }) } } $warn = Save-TestConfiguration -Document $warnDoc -Directory $script:scratch $null = $reached.Add((Invoke-Editor -ArgumentList @('-ConfigPath', $warn, '-ValidateOnly', '-NonInteractive', '-TreatWarningsAsErrors'))) $null = $reached.Add((Invoke-Editor -ArgumentList @('-ConfigPath', (Join-Path $script:scratch 'absent.json'), '-ValidateOnly', '-NonInteractive'))) $null = $reached.Add((Invoke-Editor -ArgumentList @('-ConfigPath', $valid, '-ValidateOnly', '-NonInteractive', '-SchemaPath', (Join-Path $script:scratch 'no-schema.json')))) 0..4 | ForEach-Object { $reached | Should -Contain $_ } } }