Files
personaEngine2/tests/Test-EnginePurity.ps1
T

103 lines
4.0 KiB
PowerShell
Raw Normal View History

<#
.SYNOPSIS
Fails if the rule engine has acquired a dependency it must not have.
.DESCRIPTION
Constitution Principle IV: the pure rule engine must not depend on Microsoft
Graph, authentication, Azure Automation, or console rendering. Directory
structure alone does not enforce that — one convenient call is all it takes to
make the engine untestable offline, and the failure is silent until someone
tries to run the tests without a tenant.
This check is the enforcement. It runs in CI (pipelines/validate.yml) and is
cheap enough to run locally on every change.
.EXAMPLE
./tests/Test-EnginePurity.ps1
#>
[CmdletBinding()]
param(
[string] $EnginePath = (Join-Path (Split-Path $PSScriptRoot -Parent) 'src/RuleEngine'),
[switch] $PassThru
)
$ErrorActionPreference = 'Stop'
$forbidden = @(
@{ Name = 'Microsoft Graph call'; Regex = 'Invoke-MgGraphRequest|Connect-MgGraph|graph\.microsoft\.com|Invoke-PersonaGraphRequest' }
@{ Name = 'Authentication layer'; Regex = 'Connect-Persona\w+' }
@{ Name = 'Data provider layer'; Regex = 'Get-Persona(Users|GroupMembership|DirectoryRoles)' }
@{ Name = 'Persistence layer'; Regex = 'Set-UserPersonaAttribute|New-PersonaWriteBody|Compare-PersonaValue' }
@{ Name = 'Console rendering'; Regex = 'Write-Host|Write-UserPersonaResult|Write-PersonaSummary' }
@{ Name = 'Direct HTTP'; Regex = 'Invoke-RestMethod|Invoke-WebRequest|System\.Net\.Http' }
@{ Name = 'Filesystem access'; Regex = 'Get-Content|Set-Content|Out-File|Export-Csv' }
@{ Name = 'Non-deterministic input'; Regex = 'Get-Random|Get-Date|\[datetime\]::(Now|UtcNow|Today)|New-Guid' }
)
if (-not (Test-Path $EnginePath)) {
Write-Host "Engine path '$EnginePath' does not exist yet - nothing to check." -ForegroundColor Yellow
if ($PassThru) { return @() }
exit 0
}
$findings = [System.Collections.Generic.List[object]]::new()
foreach ($file in Get-ChildItem -Path $EnginePath -Filter '*.ps1' -File -Recurse) {
# Tokenize rather than scan raw text. Comments in this codebase legitimately
# name the forbidden functions when explaining why the engine does not call
# them, and a text scan cannot tell a trailing comment from code. The parser
# can, exactly.
$tokens = $null
$parseErrors = $null
$null = [System.Management.Automation.Language.Parser]::ParseFile(
$file.FullName, [ref]$tokens, [ref]$parseErrors)
if ($parseErrors.Count -gt 0) {
$findings.Add([pscustomobject]@{
File = $file.Name
Line = $parseErrors[0].Extent.StartLineNumber
Dependency = 'Parse error'
Text = $parseErrors[0].Message
})
continue
}
# Rebuild each line from its non-comment tokens.
$codeByLine = @{}
foreach ($token in $tokens) {
if ($token.Kind -eq 'Comment') { continue }
$line = $token.Extent.StartLineNumber
if (-not $codeByLine.ContainsKey($line)) { $codeByLine[$line] = [System.Text.StringBuilder]::new() }
$null = $codeByLine[$line].Append($token.Text).Append(' ')
}
foreach ($line in ($codeByLine.Keys | Sort-Object)) {
$code = $codeByLine[$line].ToString()
foreach ($rule in $forbidden) {
if ($code -match $rule.Regex) {
$findings.Add([pscustomobject]@{
File = $file.Name
Line = $line
Dependency = $rule.Name
Text = $code.Trim()
})
}
}
}
}
if ($findings.Count -gt 0) {
Write-Host "Engine purity check FAILED - $($findings.Count) violation(s) of Principle IV:" -ForegroundColor Red
$findings | Format-Table -AutoSize | Out-String | Write-Host
Write-Host 'The rule engine must remain testable offline with synthetic data.' -ForegroundColor Red
if ($PassThru) { return $findings }
exit 1
}
Write-Host 'Engine purity check passed: the rule engine has no forbidden dependencies.' -ForegroundColor Green
if ($PassThru) { return @() }
exit 0