Tests/Intune-ComplianceReport.Tests.ps1

BeforeAll {
    $modulePath = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
    $moduleName = 'Intune-ComplianceReport'
    $manifestPath = Join-Path $modulePath "$moduleName\$moduleName.psd1"

    if (Get-Module $moduleName) { Remove-Module $moduleName -Force }
    Import-Module $manifestPath -Force
}

Describe 'Module: Intune-ComplianceReport' {

    Context 'Module Loading' {
        It 'Imports without errors' {
            { Import-Module $manifestPath -Force } | Should -Not -Throw
        }

        It 'Exports exactly 5 public functions' {
            $exported = (Get-Module $moduleName).ExportedFunctions.Keys
            $exported.Count | Should -Be 5
        }

        It 'Exports Invoke-IntuneComplianceAudit' {
            Get-Command -Module $moduleName -Name 'Invoke-IntuneComplianceAudit' | Should -Not -BeNullOrEmpty
        }

        It 'Exports Get-IntuneDeviceComplianceReport' {
            Get-Command -Module $moduleName -Name 'Get-IntuneDeviceComplianceReport' | Should -Not -BeNullOrEmpty
        }

        It 'Exports Get-IntuneAppInventory' {
            Get-Command -Module $moduleName -Name 'Get-IntuneAppInventory' | Should -Not -BeNullOrEmpty
        }

        It 'Exports Get-IntunePolicyAssignmentReview' {
            Get-Command -Module $moduleName -Name 'Get-IntunePolicyAssignmentReview' | Should -Not -BeNullOrEmpty
        }

        It 'Exports Get-IntuneAutopilotStatus' {
            Get-Command -Module $moduleName -Name 'Get-IntuneAutopilotStatus' | Should -Not -BeNullOrEmpty
        }

        It 'Does not export private functions' {
            $exported = (Get-Module $moduleName).ExportedFunctions.Keys
            $exported | Should -Not -Contain 'Test-GraphConnection'
            $exported | Should -Not -Contain 'New-HtmlDashboard'
        }
    }

    Context 'Parameter Validation' {
        It 'Invoke-IntuneComplianceAudit has OutputPath parameter' {
            (Get-Command Invoke-IntuneComplianceAudit).Parameters.Keys | Should -Contain 'OutputPath'
        }

        It 'Invoke-IntuneComplianceAudit has StaleDays with ValidateRange 1-180' {
            $param = (Get-Command Invoke-IntuneComplianceAudit).Parameters['StaleDays']
            $param | Should -Not -BeNullOrEmpty
            $rangeAttr = $param.Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateRangeAttribute] }
            $rangeAttr.MinRange | Should -Be 1
            $rangeAttr.MaxRange | Should -Be 180
        }

        It 'Invoke-IntuneComplianceAudit has SkipAutopilot switch' {
            $param = (Get-Command Invoke-IntuneComplianceAudit).Parameters['SkipAutopilot']
            $param.SwitchParameter | Should -Be $true
        }

        It 'Get-IntuneDeviceComplianceReport has OSFilter with ValidateSet' {
            $param = (Get-Command Get-IntuneDeviceComplianceReport).Parameters['OSFilter']
            $validateSet = $param.Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateSetAttribute] }
            $validateSet.ValidValues | Should -Contain 'Windows'
            $validateSet.ValidValues | Should -Contain 'iOS'
            $validateSet.ValidValues | Should -Contain 'Android'
            $validateSet.ValidValues | Should -Contain 'macOS'
            $validateSet.ValidValues | Should -Contain 'All'
        }

        It 'Get-IntuneAppInventory has FailureThresholdPercent parameter' {
            $param = (Get-Command Get-IntuneAppInventory).Parameters['FailureThresholdPercent']
            $param | Should -Not -BeNullOrEmpty
            $rangeAttr = $param.Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateRangeAttribute] }
            $rangeAttr.MinRange | Should -Be 1
            $rangeAttr.MaxRange | Should -Be 100
        }

        It 'Get-IntuneAppInventory has IncludeStore switch' {
            $param = (Get-Command Get-IntuneAppInventory).Parameters['IncludeStore']
            $param.SwitchParameter | Should -Be $true
        }
    }

    Context 'Mock-Based Execution' {
        It 'Get-IntuneDeviceComplianceReport flags non-compliant and stale devices' {
            Mock -ModuleName $moduleName Get-MgContext {
                @{ Scopes = @('DeviceManagementManagedDevices.Read.All'); Account = 'test@contoso.com'; TenantId = 'test-tenant' }
            }
            Mock -ModuleName $moduleName Get-MgDeviceManagementManagedDevice {
                @(
                    [PSCustomObject]@{
                        Id = 'dev-001'; DeviceName = 'DESKTOP-GOOD'; UserPrincipalName = 'alice@contoso.com'
                        OperatingSystem = 'Windows'; OsVersion = '10.0.22631.1234'
                        ComplianceState = 'compliant'; IsEncrypted = $true
                        LastSyncDateTime = (Get-Date).AddDays(-2); EnrolledDateTime = (Get-Date).AddMonths(-6)
                        OwnerType = 'company'; ManagementAgent = 'mdm'
                        Model = 'ThinkPad T14'; Manufacturer = 'Lenovo'; SerialNumber = 'SN001'
                    },
                    [PSCustomObject]@{
                        Id = 'dev-002'; DeviceName = 'DESKTOP-BAD'; UserPrincipalName = 'bob@contoso.com'
                        OperatingSystem = 'Windows'; OsVersion = '10.0.19045.1234'
                        ComplianceState = 'noncompliant'; IsEncrypted = $false
                        LastSyncDateTime = (Get-Date).AddDays(-60); EnrolledDateTime = (Get-Date).AddMonths(-12)
                        OwnerType = 'company'; ManagementAgent = 'mdm'
                        Model = 'OptiPlex 7090'; Manufacturer = 'Dell'; SerialNumber = 'SN002'
                    }
                )
            }

            $result = Get-IntuneDeviceComplianceReport -StaleDays 30
            $result.Count | Should -Be 2
            ($result | Where-Object { $_.Finding -eq 'OK' }).Count | Should -Be 1
            ($result | Where-Object { $_.Finding -match 'NON-COMPLIANT' }).Count | Should -Be 1
            ($result | Where-Object { $_.Finding -match 'NOT ENCRYPTED' }).Count | Should -Be 1
            ($result | Where-Object { $_.Finding -match 'STALE' }).Count | Should -Be 1
        }

        It 'Get-IntunePolicyAssignmentReview flags unassigned policies' {
            Mock -ModuleName $moduleName Get-MgContext {
                @{ Scopes = @('DeviceManagementConfiguration.Read.All'); Account = 'test@contoso.com'; TenantId = 'test-tenant' }
            }
            Mock -ModuleName $moduleName Get-MgDeviceManagementDeviceConfiguration {
                @(
                    [PSCustomObject]@{
                        Id = 'prof-001'; DisplayName = 'Assigned Profile'
                        '@odata.type' = '#microsoft.graph.windows10GeneralConfiguration'
                        LastModifiedDateTime = (Get-Date).AddDays(-30); Version = 1
                        AdditionalProperties = @{ '@odata.type' = '#microsoft.graph.windows10GeneralConfiguration' }
                    },
                    [PSCustomObject]@{
                        Id = 'prof-002'; DisplayName = 'Orphaned Profile'
                        '@odata.type' = '#microsoft.graph.windows10GeneralConfiguration'
                        LastModifiedDateTime = (Get-Date).AddDays(-400); Version = 1
                        AdditionalProperties = @{ '@odata.type' = '#microsoft.graph.windows10GeneralConfiguration' }
                    }
                )
            }
            Mock -ModuleName $moduleName Get-MgDeviceManagementDeviceConfigurationAssignment {
                param($DeviceConfigurationId)
                if ($DeviceConfigurationId -eq 'prof-001') {
                    @([PSCustomObject]@{ Target = @{ AdditionalProperties = @{ '@odata.type' = '#microsoft.graph.groupAssignmentTarget' } } })
                } else { @() }
            }
            Mock -ModuleName $moduleName Get-MgDeviceManagementDeviceConfigurationDeviceStatusOverview {
                [PSCustomObject]@{ ErrorCount = 0; ConflictCount = 0 }
            }
            Mock -ModuleName $moduleName Get-MgDeviceManagementDeviceCompliancePolicy { @() }

            $result = Get-IntunePolicyAssignmentReview
            ($result | Where-Object { $_.Finding -match 'UNASSIGNED' }).Count | Should -Be 1
            ($result | Where-Object { $_.Finding -match 'NOT MODIFIED' }).Count | Should -Be 1
        }
    }

    Context 'HTML Report Generation' {
        It 'New-HtmlDashboard creates valid HTML file' {
            $testSections = @(
                @{
                    Title   = 'Test Section'
                    Data    = @([PSCustomObject]@{ DeviceName = 'TEST-PC'; Finding = 'OK' })
                    Summary = '1 device tested'
                }
            )
            $testFile = Join-Path $TestDrive 'test-report.html'

            & (Get-Module $moduleName) { New-HtmlDashboard -Sections $args[0] -OutputFile $args[1] -ReportTitle 'Test Report' } $testSections $testFile

            Test-Path $testFile | Should -Be $true
            $content = Get-Content $testFile -Raw
            $content | Should -Match '<title>Test Report</title>'
            $content | Should -Match 'Intune-ComplianceReport'
            $content | Should -Match 'Test Section'
        }
    }

    Context 'Module Manifest' {
        It 'Has a valid manifest' {
            { Test-ModuleManifest -Path $manifestPath } | Should -Not -Throw
        }

        It 'Has the correct version' {
            (Test-ModuleManifest -Path $manifestPath).Version | Should -Be '1.0.0'
        }

        It 'Has the correct author' {
            (Test-ModuleManifest -Path $manifestPath).Author | Should -Be 'Larry Roberts'
        }

        It 'Has ProjectUri set' {
            (Test-ModuleManifest -Path $manifestPath).ProjectUri | Should -Not -BeNullOrEmpty
        }

        It 'Has LicenseUri set' {
            (Test-ModuleManifest -Path $manifestPath).LicenseUri | Should -Not -BeNullOrEmpty
        }

        It 'Has relevant tags' {
            $tags = (Test-ModuleManifest -Path $manifestPath).Tags
            $tags | Should -Contain 'Intune'
            $tags | Should -Contain 'Compliance'
        }
    }
}