Tests/Get-MsecSecureScore.Tests.ps1
|
#Requires -Module Pester # # Tests for Get-MsecSecureScore (the consolidated function). Verifies: # - Default call: emits Overall + every category present in the snapshot. # - -Category Overall: skips the profile fetch entirely, emits only Overall rows. # - -Category Identity: emits only the Identity rows, but still pulls profiles # because per-category math requires the maxScore map. # - -Top N: passes through to the Graph $top query param. # - Per-category percentages are computed from (sum-achieved / sum-max) where # the max comes from secureScoreControlProfiles, not from the snapshot. BeforeAll { $modulePath = Join-Path $PSScriptRoot '..' 'Msec.psm1' Import-Module $modulePath -Force -ErrorAction Stop $script:TestThumbBytes = [byte[]](1..20) } AfterAll { Remove-Module Msec -Force -ErrorAction SilentlyContinue } Describe 'Get-MsecSecureScore' { BeforeEach { InModuleScope Msec -Parameters @{ Thumb = $script:TestThumbBytes } { param($Thumb) $script:MsecSession = @{ TenantId = 'tenant' ClientId = 'client' KeyVaultName = 'kv-test' KeyName = 'msec-app' ThumbprintBytes = $Thumb Tokens = @{} } } } It 'no -Category: emits one Overall row plus one row per category, per snapshot' { $rows = InModuleScope Msec { Mock Invoke-MsecKeyVaultSign -MockWith { [byte[]](1..10) } Mock Invoke-RestMethod -ParameterFilter { $Uri -match 'oauth2/v2.0/token' } -MockWith { [pscustomobject]@{ access_token = 'mock'; expires_in = 3600 } } Mock Invoke-RestMethod -ParameterFilter { $Uri -match 'secureScoreControlProfiles' } -MockWith { [pscustomobject]@{ value = @( [pscustomobject]@{ id = 'ctrl_id1'; maxScore = 30; controlCategory = 'Identity' } [pscustomobject]@{ id = 'ctrl_id2'; maxScore = 20; controlCategory = 'Identity' } [pscustomobject]@{ id = 'ctrl_dev1'; maxScore = 50; controlCategory = 'Device' } ) } } Mock Invoke-RestMethod -ParameterFilter { $Uri -match 'secureScores' -and $Uri -notmatch 'Profile' } -MockWith { [pscustomobject]@{ value = @( [pscustomobject]@{ createdDateTime = '2026-06-01T00:00:00Z'; currentScore = 65; maxScore = 100 controlScores = @( [pscustomobject]@{ controlName = 'ctrl_id1'; controlCategory = 'Identity'; score = 30 } [pscustomobject]@{ controlName = 'ctrl_id2'; controlCategory = 'Identity'; score = 10 } [pscustomobject]@{ controlName = 'ctrl_dev1'; controlCategory = 'Device'; score = 25 }) } ) } } Get-MsecSecureScore } # One snapshot -> Overall + Identity + Device = 3 rows. $rows.Count | Should -Be 3 $overall = $rows | Where-Object ScoreType -eq 'Overall' $overall.ScorePercent | Should -Be 65 # 65 / 100 $identity = $rows | Where-Object ScoreType -eq 'Identity' # achieved = 30+10 = 40; max = 30+20 = 50 -> 80% $identity.ScorePercent | Should -Be 80 $device = $rows | Where-Object ScoreType -eq 'Device' # achieved = 25; max = 50 -> 50% $device.ScorePercent | Should -Be 50 } It '-Category Overall skips the profile fetch and returns only the Overall rows' { $result = InModuleScope Msec { Mock Invoke-MsecKeyVaultSign -MockWith { [byte[]](1..10) } Mock Invoke-RestMethod -ParameterFilter { $Uri -match 'oauth2/v2.0/token' } -MockWith { [pscustomobject]@{ access_token = 'mock'; expires_in = 3600 } } # If this mock fires, we've done unnecessary work for -Category Overall. $script:ProfileHits = 0 Mock Invoke-RestMethod -ParameterFilter { $Uri -match 'secureScoreControlProfiles' } -MockWith { $script:ProfileHits++ [pscustomobject]@{ value = @() } } Mock Invoke-RestMethod -ParameterFilter { $Uri -match 'secureScores' -and $Uri -notmatch 'Profile' } -MockWith { [pscustomobject]@{ value = @( [pscustomobject]@{ createdDateTime = '2026-06-01T00:00:00Z'; currentScore = 81; maxScore = 100; controlScores = @() } [pscustomobject]@{ createdDateTime = '2026-05-01T00:00:00Z'; currentScore = 75; maxScore = 100; controlScores = @() } ) } } $rows = Get-MsecSecureScore -Category Overall [pscustomobject]@{ Rows = $rows; ProfileHits = $script:ProfileHits } } $result.Rows.Count | Should -Be 2 ($result.Rows.ScoreType -join ',') | Should -Be 'Overall,Overall' $result.Rows[0].ScorePercent | Should -Be 81 $result.Rows[1].ScorePercent | Should -Be 75 # The key perf claim: -Category Overall does NOT load profile maxima. $result.ProfileHits | Should -Be 0 } It '-Category Identity emits only Identity rows but still computes from the profile maxima' { $rows = InModuleScope Msec { Mock Invoke-MsecKeyVaultSign -MockWith { [byte[]](1..10) } Mock Invoke-RestMethod -ParameterFilter { $Uri -match 'oauth2/v2.0/token' } -MockWith { [pscustomobject]@{ access_token = 'mock'; expires_in = 3600 } } Mock Invoke-RestMethod -ParameterFilter { $Uri -match 'secureScoreControlProfiles' } -MockWith { [pscustomobject]@{ value = @( [pscustomobject]@{ id = 'ctrl_id1'; maxScore = 30; controlCategory = 'Identity' } [pscustomobject]@{ id = 'ctrl_id2'; maxScore = 20; controlCategory = 'Identity' } [pscustomobject]@{ id = 'ctrl_dev1'; maxScore = 50; controlCategory = 'Device' } ) } } Mock Invoke-RestMethod -ParameterFilter { $Uri -match 'secureScores' -and $Uri -notmatch 'Profile' } -MockWith { [pscustomobject]@{ value = @( [pscustomobject]@{ createdDateTime = '2026-06-01T00:00:00Z'; currentScore = 65; maxScore = 100 controlScores = @( [pscustomobject]@{ controlName = 'ctrl_id1'; controlCategory = 'Identity'; score = 30 } [pscustomobject]@{ controlName = 'ctrl_id2'; controlCategory = 'Identity'; score = 10 } [pscustomobject]@{ controlName = 'ctrl_dev1'; controlCategory = 'Device'; score = 25 }) } ) } } Get-MsecSecureScore -Category Identity } $rows.Count | Should -Be 1 $rows[0].ScoreType | Should -Be 'Identity' $rows[0].ScorePercent | Should -Be 80 # (30 + 10) / (30 + 20) } It '-Top N passes through to the Graph $top query parameter' { InModuleScope Msec { Mock Invoke-MsecKeyVaultSign -MockWith { [byte[]](1..10) } Mock Invoke-RestMethod -ParameterFilter { $Uri -match 'oauth2/v2.0/token' } -MockWith { [pscustomobject]@{ access_token = 'mock'; expires_in = 3600 } } Mock Invoke-RestMethod -ParameterFilter { $Uri -match 'secureScoreControlProfiles' } -MockWith { [pscustomobject]@{ value = @() } } $script:CapturedScoreUri = $null Mock Invoke-RestMethod -ParameterFilter { $Uri -match 'secureScores' -and $Uri -notmatch 'Profile' } -MockWith { $script:CapturedScoreUri = $Uri [pscustomobject]@{ value = @( [pscustomobject]@{ createdDateTime = '2026-06-01T00:00:00Z'; currentScore = 80; maxScore = 100; controlScores = @() } ) } } Get-MsecSecureScore -Top 1 | Out-Null $script:CapturedScoreUri | Should -Match '\$top=1$' } } } |