Tests/Get-MsecEntraConditionalAccessStats.Tests.ps1
|
#Requires -Module Pester # # Tests for Get-MsecEntraConditionalAccessStats. The function is a thin # aggregation over Get-MsecEntraConditionalAccessSignInLog - we mock that # function inside the module scope so the test doesn't need to mock the Graph # layer, just feed canned sign-in events and verify the math. # Coverage: # - CA-status counts and percentages (success / failure / notApplied) # - UniqueUsers de-dupes by UPN # - Risk-level counts (high / medium) # - ReportOnlyWouldBlock counts reportOnlyFailure + reportOnlyInterrupted # - TopFailingPolicies aggregates AppliedPolicies[].result == 'failure' # - Empty result returns zero counts and 0.0 percentages (no divide-by-zero) BeforeAll { $modulePath = Join-Path $PSScriptRoot '..' 'Msec.psm1' Import-Module $modulePath -Force -ErrorAction Stop } AfterAll { Remove-Module Msec -Force -ErrorAction SilentlyContinue } Describe 'Get-MsecEntraConditionalAccessStats' { It 'aggregates volume / CA outcomes / risk / report-only / top failing policies correctly' { $stats = InModuleScope Msec { # Mock the SignInLog so the inner call returns a small, deterministic set. Mock Get-MsecEntraConditionalAccessSignInLog -MockWith { @( # 6 sign-ins total: 3 success, 2 failure, 1 notApplied [pscustomobject]@{ UserPrincipalName = 'alice@x'; ConditionalAccessStatus = 'success' RiskLevelDuringSignIn = 'none' AppliedPolicies = @( [pscustomobject]@{ displayName = 'P1: Require MFA'; result = 'success' } ) } [pscustomobject]@{ UserPrincipalName = 'alice@x'; ConditionalAccessStatus = 'success' RiskLevelDuringSignIn = 'none' AppliedPolicies = @( [pscustomobject]@{ displayName = 'P1: Require MFA'; result = 'success' } # A report-only policy that WOULD have blocked this sign-in [pscustomobject]@{ displayName = 'P3: Geo Block (RO)'; result = 'reportOnlyFailure' } ) } [pscustomobject]@{ UserPrincipalName = 'bob@x'; ConditionalAccessStatus = 'success' RiskLevelDuringSignIn = 'medium' AppliedPolicies = @() } [pscustomobject]@{ UserPrincipalName = 'attacker@external'; ConditionalAccessStatus = 'failure' RiskLevelDuringSignIn = 'high' AppliedPolicies = @( [pscustomobject]@{ displayName = 'P1: Require MFA'; result = 'failure' } [pscustomobject]@{ displayName = 'P2: Block Legacy'; result = 'failure' } ) } [pscustomobject]@{ UserPrincipalName = 'attacker@external'; ConditionalAccessStatus = 'failure' RiskLevelDuringSignIn = 'high' AppliedPolicies = @( [pscustomobject]@{ displayName = 'P1: Require MFA'; result = 'failure' } ) } [pscustomobject]@{ UserPrincipalName = 'service@x'; ConditionalAccessStatus = 'notApplied' RiskLevelDuringSignIn = 'none' AppliedPolicies = @() } ) } Get-MsecEntraConditionalAccessStats -Days 7 } # Volume $stats.TotalSignIns | Should -Be 6 $stats.UniqueUsers | Should -Be 4 # alice + bob + attacker + service # CA outcomes $stats.CaSuccess | Should -Be 3 $stats.CaFailure | Should -Be 2 $stats.CaNotApplied | Should -Be 1 $stats.CaSuccessPercent | Should -Be 50.0 # 3 / 6 $stats.CaFailurePercent | Should -Be 33.33 # 2 / 6, rounded to 2dp # Risk $stats.HighRiskSignIns | Should -Be 2 $stats.MediumRiskSignIns | Should -Be 1 # Report-only would-block - one event triggered reportOnlyFailure on P3 $stats.ReportOnlyWouldBlock | Should -Be 1 # Top failing policies (aggregated across all events): # P1 failed 2 times, P2 failed 1 time. P3 was reportOnly - NOT in this list. ($stats.TopFailingPolicies | Where Name -eq 'P1: Require MFA').Count | Should -Be 2 ($stats.TopFailingPolicies | Where Name -eq 'P2: Block Legacy').Count | Should -Be 1 $stats.TopFailingPolicies.Name | Should -Not -Contain 'P3: Geo Block (RO)' } It 'returns zero counts and 0.0 percentages when the sign-in log is empty (no divide-by-zero)' { $stats = InModuleScope Msec { Mock Get-MsecEntraConditionalAccessSignInLog -MockWith { @() } Get-MsecEntraConditionalAccessStats } $stats.TotalSignIns | Should -Be 0 $stats.CaSuccess | Should -Be 0 $stats.CaSuccessPercent | Should -Be 0.0 $stats.CaFailurePercent | Should -Be 0.0 $stats.ReportOnlyWouldBlock | Should -Be 0 $stats.TopFailingPolicies | Should -BeNullOrEmpty # Date columns still populated. $stats.StartDate | Should -Not -BeNullOrEmpty $stats.EndDate | Should -Not -BeNullOrEmpty } It 'has -Days with the same bounds as the SignInLog function (1..30)' { $param = (Get-Command Get-MsecEntraConditionalAccessStats).Parameters['Days'] $range = $param.Attributes | Where-Object { $_.TypeId.Name -eq 'ValidateRangeAttribute' } | Select-Object -First 1 $range.MinRange | Should -Be 1 $range.MaxRange | Should -Be 30 } } |