#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '5.0.0' } BeforeAll { $repoRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent foreach ($f in @( 'src/Normalization/New-PersonaMembershipRecord.ps1' 'src/Normalization/New-PersonaUserRecord.ps1' 'src/RuleEngine/Test-PersonaCondition.ps1' 'src/RuleEngine/Test-PersonaConditionGroup.ps1' 'src/RuleEngine/Test-PersonaRule.ps1' 'src/RuleEngine/Resolve-UserPersona.ps1' )) { . (Join-Path $repoRoot $f) } function New-MatchAllRule { param([string] $Id, [int] $Priority, [string] $Persona, [bool] $Enabled = $true, [string] $Department = 'Finance') [pscustomobject]@{ id = $Id priority = $Priority persona = $Persona enabled = $Enabled match = [pscustomobject]@{ operator = 'all' conditions = @([pscustomobject]@{ type = 'property'; property = 'Department'; operator = 'equals'; value = $Department }) } } } $script:user = New-PersonaUserRecord ` -AccountObjectId '00000000-0000-0000-0000-000000000101' ` -UserPrincipalName 'alex.employee@example.invalid' ` -Properties @{ Department = 'Finance' } } Describe 'Rule ordering and first-match (FR-008, FR-009, RE-002)' { It 'evaluates in ascending priority order and stops at the first match' { $rules = @( New-MatchAllRule -Id 'R-020' -Priority 20 -Persona 'Tier1-Admin' New-MatchAllRule -Id 'R-010' -Priority 10 -Persona 'Tier0-Admin' ) $result = Resolve-UserPersona -UserRecord $user -Rules $rules $result.CalculatedPersona | Should -Be 'Tier0-Admin' $result.MatchedRuleId | Should -Be 'R-010' } It 'stops evaluating once matched, leaving lower-priority rules unevaluated' { $rules = @( New-MatchAllRule -Id 'R-010' -Priority 10 -Persona 'Tier0-Admin' New-MatchAllRule -Id 'R-020' -Priority 20 -Persona 'Tier1-Admin' New-MatchAllRule -Id 'R-030' -Priority 30 -Persona 'Employee' ) (Resolve-UserPersona -UserRecord $user -Rules $rules).RulesEvaluated | Should -Be 1 } It 'is unaffected by the order rules appear in the collection' { $ascending = @( New-MatchAllRule -Id 'R-010' -Priority 10 -Persona 'Tier0-Admin' New-MatchAllRule -Id 'R-020' -Priority 20 -Persona 'Tier1-Admin' ) $descending = @( New-MatchAllRule -Id 'R-020' -Priority 20 -Persona 'Tier1-Admin' New-MatchAllRule -Id 'R-010' -Priority 10 -Persona 'Tier0-Admin' ) (Resolve-UserPersona -UserRecord $user -Rules $ascending).CalculatedPersona | Should -Be (Resolve-UserPersona -UserRecord $user -Rules $descending).CalculatedPersona } It 'skips disabled rules and excludes them from the evaluated count' { $rules = @( New-MatchAllRule -Id 'R-010' -Priority 10 -Persona 'Tier0-Admin' -Enabled $false New-MatchAllRule -Id 'R-020' -Priority 20 -Persona 'Tier1-Admin' ) $result = Resolve-UserPersona -UserRecord $user -Rules $rules $result.CalculatedPersona | Should -Be 'Tier1-Admin' $result.RulesEvaluated | Should -Be 1 } It 'breaks a duplicate-priority tie deterministically by rule id' { # Duplicate priorities are a validation error (VR-002). If one reaches the # engine anyway, the result must still not depend on collection order. $a = @( New-MatchAllRule -Id 'R-AAA' -Priority 10 -Persona 'Persona-A' New-MatchAllRule -Id 'R-BBB' -Priority 10 -Persona 'Persona-B' ) $b = @( New-MatchAllRule -Id 'R-BBB' -Priority 10 -Persona 'Persona-B' New-MatchAllRule -Id 'R-AAA' -Priority 10 -Persona 'Persona-A' ) (Resolve-UserPersona -UserRecord $user -Rules $a).CalculatedPersona | Should -Be 'Persona-A' (Resolve-UserPersona -UserRecord $user -Rules $b).CalculatedPersona | Should -Be 'Persona-A' } } Describe 'Outcome exclusivity (SC-001)' { It 'returns exactly one outcome for every user' { $rules = @(New-MatchAllRule -Id 'R-010' -Priority 10 -Persona 'Employee') $result = Resolve-UserPersona -UserRecord $user -Rules $rules $result.Outcome | Should -BeIn @('Matched', 'Unclassified', 'EvaluationError') } It 'populates MatchedRuleId only when matched' { $matched = Resolve-UserPersona -UserRecord $user -Rules @(New-MatchAllRule -Id 'R-010' -Priority 10 -Persona 'Employee') $matched.MatchedRuleId | Should -Be 'R-010' $unmatched = Resolve-UserPersona -UserRecord $user -Rules @(New-MatchAllRule -Id 'R-010' -Priority 10 -Persona 'Employee' -Department 'Legal') $unmatched.MatchedRuleId | Should -BeNullOrEmpty } }