#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '5.0.0' } <# SC-002: a second consecutive run over unchanged input proposes zero changes. Idempotence is what makes the engine safe to schedule. A run that rewrites the same value every time produces directory churn, floods the audit trail, and makes a genuine change indistinguishable from routine noise. #> 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 } Describe 'Idempotence across consecutive runs (SC-002)' -Tag 'Safety' { BeforeEach { Mock Write-Host { } # A mutable population, so the second run genuinely sees what the first wrote # rather than a fresh copy of the original fixtures. Re-reading the same # unchanged fixtures would prove nothing about idempotence. $script:store = @{} foreach ($user in (New-TestPopulation -Count 20 -TargetAttribute $script:target)) { $script:store[$user['id']] = $user } Mock Get-PersonaUsers { $script:store.Values } Mock Set-UserPersonaAttribute { $script:store[$UserObjectId][$AttributeName] = $Value [pscustomobject]@{ Succeeded = $true; AccountObjectId = $UserObjectId; Value = $Value; PreviousValue = $PreviousValue } } } It 'proposes zero changes on the second run' { $first = Invoke-PersonaEngineRun -Configuration $config -TargetAttribute $target ` -Context (New-TestAuditContext -Mode 'Enforce') -IsEnforcing ` -ShouldProcessGate { param($t, $d) $true } $first.Counters.Updated | Should -BeGreaterThan 0 $second = Invoke-PersonaEngineRun -Configuration $config -TargetAttribute $target ` -Context (New-TestAuditContext -Mode 'Enforce') -IsEnforcing ` -ShouldProcessGate { param($t, $d) $true } $second.Counters.Updated | Should -Be 0 $second.Counters.WouldUpdate | Should -Be 0 } It 'issues no write request at all on the second run' { $null = Invoke-PersonaEngineRun -Configuration $config -TargetAttribute $target ` -Context (New-TestAuditContext -Mode 'Enforce') -IsEnforcing ` -ShouldProcessGate { param($t, $d) $true } $writesAfterFirst = 0 Should -Invoke Set-UserPersonaAttribute -Times 0 -Exactly -Scope It -ParameterFilter { $false } $before = $script:store.Values | ForEach-Object { $_[$script:target] } $second = Invoke-PersonaEngineRun -Configuration $config -TargetAttribute $target ` -Context (New-TestAuditContext -Mode 'Enforce') -IsEnforcing ` -ShouldProcessGate { param($t, $d) $true } $after = $script:store.Values | ForEach-Object { $_[$script:target] } ($after -join '|') | Should -Be ($before -join '|') $second.Counters.Unchanged | Should -Be $second.Counters.Processed } It 'produces identical counters on the second and third runs' { $null = Invoke-PersonaEngineRun -Configuration $config -TargetAttribute $target ` -Context (New-TestAuditContext -Mode 'Enforce') -IsEnforcing ` -ShouldProcessGate { param($t, $d) $true } $second = Invoke-PersonaEngineRun -Configuration $config -TargetAttribute $target ` -Context (New-TestAuditContext -Mode 'Enforce') -IsEnforcing ` -ShouldProcessGate { param($t, $d) $true } $third = Invoke-PersonaEngineRun -Configuration $config -TargetAttribute $target ` -Context (New-TestAuditContext -Mode 'Enforce') -IsEnforcing ` -ShouldProcessGate { param($t, $d) $true } $third.Counters.Matched | Should -Be $second.Counters.Matched $third.Counters.Unclassified | Should -Be $second.Counters.Unclassified $third.Counters.Unchanged | Should -Be $second.Counters.Unchanged $third.Counters.Updated | Should -Be $second.Counters.Updated } It 'treats a case-only difference as a real change, so it converges rather than oscillating' { # Change detection is ordinal (FR-015). A stored 'employee' against a # calculated 'Employee' is corrected once and then stays corrected - the # failure mode this guards against is a run that rewrites it every time. foreach ($user in $script:store.Values) { if ($user[$script:target]) { $user[$script:target] = ([string]$user[$script:target]).ToLowerInvariant() } } $first = Invoke-PersonaEngineRun -Configuration $config -TargetAttribute $target ` -Context (New-TestAuditContext -Mode 'Enforce') -IsEnforcing ` -ShouldProcessGate { param($t, $d) $true } $second = Invoke-PersonaEngineRun -Configuration $config -TargetAttribute $target ` -Context (New-TestAuditContext -Mode 'Enforce') -IsEnforcing ` -ShouldProcessGate { param($t, $d) $true } $first.Counters.Updated | Should -BeGreaterThan 0 $second.Counters.Updated | Should -Be 0 } }