tests/Get-MsecIntuneCompliancePolicy.Tests.ps1

#Requires -Module Pester
#
# Tests for Get-MsecIntuneCompliancePolicy. Verifies Platform is derived from
# @odata.type, AssignmentCount comes from $expand=assignments, and Status is
# omitted when -IncludeStatus is not passed.

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-MsecIntuneCompliancePolicy' {
    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 'lists compliance policies, deriving Platform from @odata.type and AssignmentCount from $expand' {
        $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 '/deviceCompliancePolicies\?' } -MockWith {
                [pscustomobject]@{ value = @(
                    [pscustomobject]@{
                        id = 'cp-1'; displayName = 'Win10 Compliance'
                        '@odata.type' = '#microsoft.graph.windows10CompliancePolicy'
                        assignments = @(@{ target = @{ groupId = 'g-1' } })
                    }
                    [pscustomobject]@{
                        id = 'cp-2'; displayName = 'iOS Compliance'
                        '@odata.type' = '#microsoft.graph.iosCompliancePolicy'
                        assignments = @()
                    }
                ) }
            }

            Get-MsecIntuneCompliancePolicy
        }

        $rows.Count | Should -Be 2
        ($rows | Where-Object Id -eq 'cp-1').Platform        | Should -Be 'windows10'
        ($rows | Where-Object Id -eq 'cp-1').Type            | Should -Be 'windows10CompliancePolicy'
        ($rows | Where-Object Id -eq 'cp-1').AssignmentCount | Should -Be 1
        ($rows | Where-Object Id -eq 'cp-2').Platform        | Should -Be 'iOS'
        ($rows | Where-Object Id -eq 'cp-2').AssignmentCount | Should -Be 0

        # No -IncludeStatus -> no Status column at all (not even for AssignmentCount=0 rows).
        ($rows | Where-Object Id -eq 'cp-1').PSObject.Properties.Name | Should -Not -Contain 'Status'
        ($rows | Where-Object Id -eq 'cp-2').PSObject.Properties.Name | Should -Not -Contain 'Status'
    }
}

Describe 'A compliance policy that checks nothing' {
    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 'reports ChecksNothing for an assigned policy that enforces nothing' {
        # Measured live: a macOS baseline assigned to all licensed users since 2021, with every
        # setting empty or false, reported 17 of 19 devices compliant - including two on an
        # unsupported major version. Name, platform and assignment count all looked healthy.
        $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 '/deviceCompliancePolicies\?' } -MockWith {
                [pscustomobject]@{ value = @(
                    [pscustomobject]@{
                        id = 'cp-empty'; displayName = 'Baseline macOS'
                        '@odata.type' = '#microsoft.graph.macOSCompliancePolicy'
                        assignments = @(@{ target = @{ groupId = 'g-1' } })
                        osMinimumVersion = ''
                        passwordRequired = $false
                        storageRequireEncryption = $false
                        firewallEnabled = $false
                        passwordRequiredType = 'deviceDefault'
                        passwordMinimumLength = 0
                    }
                    [pscustomobject]@{
                        id = 'cp-real'; displayName = 'LAB macOS'
                        '@odata.type' = '#microsoft.graph.macOSCompliancePolicy'
                        assignments = @()
                        osMinimumVersion = '14.6.1'
                        passwordRequired = $true
                        storageRequireEncryption = $true
                    }
                )}
            }
            @(Get-MsecIntuneCompliancePolicy)
        }

        $empty = $rows | Where-Object DisplayName -eq 'Baseline macOS'
        $empty.ChecksNothing        | Should -BeTrue
        $empty.ConfiguredCheckCount | Should -Be 0
        # Assigned and enforcing nothing - the combination that looks fine in a policy list.
        $empty.AssignmentCount      | Should -Be 1

        $real = $rows | Where-Object DisplayName -eq 'LAB macOS'
        $real.ChecksNothing         | Should -BeFalse
        $real.ConfiguredCheckCount  | Should -Be 3
        $real.OsMinimumVersion      | Should -Be '14.6.1'
    }

    It 'does not count a false boolean, a do-nothing sentinel, or a zero threshold' {
        $row = 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 '/deviceCompliancePolicies\?' } -MockWith {
                [pscustomobject]@{ value = @([pscustomobject]@{
                    id = 'cp-1'; displayName = 'Mixed'
                    '@odata.type' = '#microsoft.graph.windows10CompliancePolicy'
                    assignments = @()
                    # None of these enforce anything...
                    passwordRequired            = $false
                    passwordRequiredType        = 'deviceDefault'
                    defenderEnabled             = 'unavailable'
                    passwordMinimumLength       = 0
                    osMinimumVersion            = ''
                    # ...only this one does.
                    bitLockerEnabled            = $true
                })}
            }
            @(Get-MsecIntuneCompliancePolicy)
        }

        # False means "not required", not "required to be false".
        $row.ConfiguredCheckCount | Should -Be 1
        $row.ConfiguredChecks     | Should -Contain 'bitLockerEnabled'
        $row.ConfiguredChecks     | Should -Not -Contain 'passwordRequired'
        $row.ConfiguredChecks     | Should -Not -Contain 'passwordRequiredType'
        $row.ConfiguredChecks     | Should -Not -Contain 'defenderEnabled'
        $row.ConfiguredChecks     | Should -Not -Contain 'passwordMinimumLength'
    }

    It 'does not count identity or timestamps as compliance settings' {
        $row = 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 '/deviceCompliancePolicies\?' } -MockWith {
                [pscustomobject]@{ value = @([pscustomobject]@{
                    id = 'cp-1'; displayName = 'Named'; description = 'has text'; version = 5
                    createdDateTime = '2021-08-17T14:41:09Z'
                    '@odata.type' = '#microsoft.graph.macOSCompliancePolicy'
                    assignments = @(@{ target = @{ groupId = 'g-1' } })
                    scheduledActionsForRule = @(@{ ruleName = 'PasswordRequired' })
                })}
            }
            @(Get-MsecIntuneCompliancePolicy)
        }

        # An id and a display name are not controls. scheduledActionsForRule says what happens
        # AFTER a failure, not whether anything is checked.
        $row.ChecksNothing | Should -BeTrue
    }

    It 'attaches the raw settings only when asked' {
        $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 '/deviceCompliancePolicies\?' } -MockWith {
                [pscustomobject]@{ value = @([pscustomobject]@{
                    id = 'cp-1'; displayName = 'P'
                    '@odata.type' = '#microsoft.graph.macOSCompliancePolicy'
                    assignments = @(); osMinimumVersion = '14.0'
                })}
            }
            ,@(Get-MsecIntuneCompliancePolicy)
            ,@(Get-MsecIntuneCompliancePolicy -IncludeSettings)
        }

        $rows[0][0].PSObject.Properties.Name | Should -Not -Contain 'Settings'
        $rows[1][0].Settings['osMinimumVersion'] | Should -Be '14.0'
    }
}