tests/BackupModules.Tests.ps1
|
#Requires -Version 7.0 <# .SYNOPSIS Tests for backup modules .DESCRIPTION Pester tests for the IntuneBackup modules subset #> BeforeAll { Import-Module "$PSScriptRoot/../IntuneBackup.psm1" -Force $script:token = ConvertTo-SecureString -String 'test-token' -AsPlainText -Force } Describe "Backup-DeviceManagementSettings" { It "should handle singleton response with preset filename" { # Mock Invoke-GraphRequest2 to return a single object (not an array) Mock Invoke-GraphRequest2 -ModuleName IntuneBackup -ParameterFilter { $Uri -eq '/beta/deviceManagement/settings' } { return @{ id = 'test-id' someProperty = 'someValue' } } Mock Remove-VolatileKeys -ModuleName IntuneBackup { return $InputObject } Mock Save-BackupItem -ModuleName IntuneBackup { # Verify that Save-BackupItem was called with the correct filename if ($FileName -ne 'settings') { throw "Expected filename 'settings', got '$FileName'" } } Backup-DeviceManagementSettings -BackupPath 'TestDrive:\backup' -Token $script:token Assert-MockCalled Save-BackupItem -ModuleName IntuneBackup -Times 1 } It "should create the correct folder path" { Mock Invoke-GraphRequest2 -ModuleName IntuneBackup -ParameterFilter { $Uri -eq '/beta/deviceManagement/settings' } { return @{ id = 'test-id' } } Mock Remove-VolatileKeys -ModuleName IntuneBackup { return $InputObject } Mock Save-BackupItem -ModuleName IntuneBackup { $expectedPath = Join-Path 'TestDrive:\backup' 'Device Management Settings' if ($Folder -ne $expectedPath) { throw "Expected folder '$expectedPath', got '$Folder'" } } Backup-DeviceManagementSettings -BackupPath 'TestDrive:\backup' -Token $script:token Assert-MockCalled Save-BackupItem -ModuleName IntuneBackup -Times 1 } } Describe "Backup-Filters" { It "should handle list response with multiple items" { Mock Invoke-GraphRequest2 -ModuleName IntuneBackup -ParameterFilter { $Uri -eq '/beta/deviceManagement/assignmentFilters' } { return @( @{ id = 'filter-1'; displayName = 'Filter 1' }, @{ id = 'filter-2'; displayName = 'Filter 2' } ) } Mock Remove-VolatileKeys -ModuleName IntuneBackup { return $InputObject } Mock Save-BackupItem -ModuleName IntuneBackup Backup-Filters -BackupPath 'TestDrive:\backup' -Token $script:token Assert-MockCalled Save-BackupItem -ModuleName IntuneBackup -Times 2 } It "should not fetch assignments" { Mock Invoke-GraphRequest2 -ModuleName IntuneBackup -ParameterFilter { $Uri -eq '/beta/deviceManagement/assignmentFilters' } { return @( @{ id = 'filter-1' } ) } Mock Invoke-GraphRequest2 -ModuleName IntuneBackup -ParameterFilter { $Uri -like '*assignments' } { throw "Should not fetch assignments for filters" } Mock Remove-VolatileKeys -ModuleName IntuneBackup { return $InputObject } Mock Save-BackupItem -ModuleName IntuneBackup Backup-Filters -BackupPath 'TestDrive:\backup' -Token $script:token # Verify Invoke-GraphRequest2 was only called once (for the main URI) Assert-MockCalled Invoke-GraphRequest2 -ModuleName IntuneBackup -ParameterFilter { $Uri -eq '/beta/deviceManagement/assignmentFilters' } -Times 1 } } Describe "Backup-PowerShellScripts" { It "should decode base64 script content and save to Script Data folder" { $scriptContent = 'Write-Host "Hello"' $base64Content = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($scriptContent)) Mock Invoke-GraphRequest2 -ModuleName IntuneBackup -ParameterFilter { $Uri -eq '/beta/deviceManagement/deviceManagementScripts/' } { return @( @{ id = 'script-1' } ) } Mock Invoke-GraphRequest2 -ModuleName IntuneBackup -ParameterFilter { $Uri -like '*/deviceManagementScripts/*' -and $Uri -notlike '*assignments' } { return @{ id = 'script-1' fileName = 'test-script.ps1' displayName = 'Test Script' scriptContent = $base64Content } } Mock Invoke-GraphRequest2 -ModuleName IntuneBackup -ParameterFilter { $Uri -like '*assignments' } { return $null } Mock Remove-VolatileKeys -ModuleName IntuneBackup { return $InputObject } Mock Save-BackupItem -ModuleName IntuneBackup Mock Set-Content -ModuleName IntuneBackup Mock New-Item -ModuleName IntuneBackup Backup-PowerShellScripts -BackupPath 'TestDrive:\backup' -Token $script:token # Verify Set-Content was called with decoded content Assert-MockCalled Set-Content -ModuleName IntuneBackup -Times 1 } It "should fetch assignments" { $base64Content = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes('test')) Mock Invoke-GraphRequest2 -ModuleName IntuneBackup -ParameterFilter { $Uri -eq '/beta/deviceManagement/deviceManagementScripts/' } { return @( @{ id = 'script-1' } ) } Mock Invoke-GraphRequest2 -ModuleName IntuneBackup -ParameterFilter { $Uri -like '*/deviceManagementScripts/*' -and $Uri -notlike '*assignments' } { return @{ id = 'script-1' fileName = 'test.ps1' scriptContent = $base64Content } } Mock Resolve-Assignments -ModuleName IntuneBackup { return @( @{ id = 'assignment-1' } ) } Mock Remove-VolatileKeys -ModuleName IntuneBackup { return $InputObject } Mock Save-BackupItem -ModuleName IntuneBackup Mock Set-Content Mock New-Item -ParameterFilter { $ItemType -eq 'Directory' } Backup-PowerShellScripts -BackupPath 'TestDrive:\backup' -Token $script:token Assert-MockCalled Resolve-Assignments -ModuleName IntuneBackup -Times 1 } } Describe "Backup-CompliancePartner" { It "should filter out partners with unknown state" { Mock Invoke-GraphRequest2 -ModuleName IntuneBackup -ParameterFilter { $Uri -eq '/beta/deviceManagement/complianceManagementPartners' } { return @( @{ id = 'partner-1'; displayName = 'Partner 1'; partnerState = 'active' }, @{ id = 'partner-2'; displayName = 'Partner 2'; partnerState = 'unknown' }, @{ id = 'partner-3'; displayName = 'Partner 3'; partnerState = 'inactive' } ) } Mock Remove-VolatileKeys -ModuleName IntuneBackup { return $InputObject } Mock Save-BackupItem -ModuleName IntuneBackup Backup-CompliancePartner -BackupPath 'TestDrive:\backup' -Token $script:token # Should save only 2 items (active and inactive, not unknown) Assert-MockCalled Save-BackupItem -ModuleName IntuneBackup -Times 2 } } Describe "Build-AssignmentReport" { It "saves report.json inside 'Assignment Report' folder" { Mock Invoke-GraphRequest2 -ModuleName IntuneBackup { # Return empty list for all category list calls; handled by URI matching below return @() } Mock Invoke-GraphRequest2 -ModuleName IntuneBackup ` -ParameterFilter { $Uri -like '*/deviceHealthScripts' } { return @( [PSCustomObject]@{ id = 'pr-1'; displayName = 'My Remediation'; '@odata.type' = $null } ) } Mock Invoke-GraphRequest2 -ModuleName IntuneBackup ` -ParameterFilter { $Uri -like '*/deviceHealthScripts/pr-1/assignments' } { return @( [PSCustomObject]@{ intent = '' target = [PSCustomObject]@{ '@odata.type' = '#microsoft.graph.groupAssignmentTarget' groupId = 'grp-1' } } ) } Mock Invoke-GraphRequest2 -ModuleName IntuneBackup ` -ParameterFilter { $Uri -like '*/groups/grp-1*' } { return [PSCustomObject]@{ id = 'grp-1' displayName = 'TestGroup' groupTypes = @() membershipRule = $null } } $testFolder = Join-Path ([System.IO.Path]::GetTempPath()) "IntuneReport_$(Get-Random)" try { Build-AssignmentReport -BackupPath $testFolder -Token $script:token $reportPath = Join-Path $testFolder 'Assignment Report' 'report.json' Test-Path $reportPath | Should -Be $true $report = Get-Content $reportPath -Raw | ConvertFrom-Json $report | Should -Not -BeNullOrEmpty $report[0].groupName | Should -Be 'TestGroup' $report[0].groupType | Should -Be 'StaticMembership' $report[0].assignedTo.'Proactive Remediations'[0].name | Should -Be 'My Remediation' } finally { Remove-Item $testFolder -Recurse -Force -ErrorAction SilentlyContinue } } It "produces DynamicMembership when groupTypes contains 'DynamicMembership'" { Mock Invoke-GraphRequest2 -ModuleName IntuneBackup { return @() } Mock Invoke-GraphRequest2 -ModuleName IntuneBackup ` -ParameterFilter { $Uri -like '*/deviceHealthScripts' } { return @( [PSCustomObject]@{ id = 'pr-2'; displayName = 'DynTest'; '@odata.type' = $null } ) } Mock Invoke-GraphRequest2 -ModuleName IntuneBackup ` -ParameterFilter { $Uri -like '*/deviceHealthScripts/pr-2/assignments' } { return @( [PSCustomObject]@{ intent = '' target = [PSCustomObject]@{ '@odata.type' = '#microsoft.graph.groupAssignmentTarget' groupId = 'grp-dyn' } } ) } Mock Invoke-GraphRequest2 -ModuleName IntuneBackup ` -ParameterFilter { $Uri -like '*/groups/grp-dyn*' } { return [PSCustomObject]@{ id = 'grp-dyn' displayName = 'DynGroup' groupTypes = @('DynamicMembership') membershipRule = 'device.os -eq "Windows"' } } $testFolder = Join-Path ([System.IO.Path]::GetTempPath()) "IntuneReport_$(Get-Random)" try { Build-AssignmentReport -BackupPath $testFolder -Token $script:token $report = Get-Content (Join-Path $testFolder 'Assignment Report' 'report.json') -Raw | ConvertFrom-Json $report[0].groupType | Should -Be 'DynamicMembership' $report[0].membershipRule | Should -Be 'device.os -eq "Windows"' } finally { Remove-Item $testFolder -Recurse -Force -ErrorAction SilentlyContinue } } It "sets intent to 'apply' for Device Configurations" { Mock Invoke-GraphRequest2 -ModuleName IntuneBackup { return @() } Mock Invoke-GraphRequest2 -ModuleName IntuneBackup ` -ParameterFilter { $Uri -like '*/deviceConfigurations' } { return @( [PSCustomObject]@{ id = 'dc-1'; displayName = 'MyCfg'; '@odata.type' = '#microsoft.graph.windows10GeneralConfiguration' } ) } Mock Invoke-GraphRequest2 -ModuleName IntuneBackup ` -ParameterFilter { $Uri -like '*/deviceConfigurations/dc-1/assignments' } { return @( [PSCustomObject]@{ intent = $null target = [PSCustomObject]@{ '@odata.type' = '#microsoft.graph.groupAssignmentTarget' groupId = 'grp-dc' } } ) } Mock Invoke-GraphRequest2 -ModuleName IntuneBackup ` -ParameterFilter { $Uri -like '*/groups/grp-dc*' } { return [PSCustomObject]@{ id = 'grp-dc'; displayName = 'DCGroup'; groupTypes = @(); membershipRule = $null } } $testFolder = Join-Path ([System.IO.Path]::GetTempPath()) "IntuneReport_$(Get-Random)" try { Build-AssignmentReport -BackupPath $testFolder -Token $script:token $report = Get-Content (Join-Path $testFolder 'Assignment Report' 'report.json') -Raw | ConvertFrom-Json $report[0].assignedTo.'Device Configurations'[0].intent | Should -Be 'apply' $report[0].assignedTo.'Device Configurations'[0].type | Should -Be 'windows10GeneralConfiguration' } finally { Remove-Item $testFolder -Recurse -Force -ErrorAction SilentlyContinue } } It "reads intent from assignment for Applications" { Mock Invoke-GraphRequest2 -ModuleName IntuneBackup { return @() } Mock Invoke-GraphRequest2 -ModuleName IntuneBackup ` -ParameterFilter { $Uri -like '*/mobileApps' } { return @( [PSCustomObject]@{ id = 'app-1'; displayName = 'MyApp'; '@odata.type' = '#microsoft.graph.win32LobApp' } ) } Mock Invoke-GraphRequest2 -ModuleName IntuneBackup ` -ParameterFilter { $Uri -like '*/mobileApps/app-1/assignments' } { return @( [PSCustomObject]@{ intent = 'required' target = [PSCustomObject]@{ '@odata.type' = '#microsoft.graph.groupAssignmentTarget' groupId = 'grp-app' } } ) } Mock Invoke-GraphRequest2 -ModuleName IntuneBackup ` -ParameterFilter { $Uri -like '*/groups/grp-app*' } { return [PSCustomObject]@{ id = 'grp-app'; displayName = 'AppGroup'; groupTypes = @(); membershipRule = $null } } $testFolder = Join-Path ([System.IO.Path]::GetTempPath()) "IntuneReport_$(Get-Random)" try { Build-AssignmentReport -BackupPath $testFolder -Token $script:token $report = Get-Content (Join-Path $testFolder 'Assignment Report' 'report.json') -Raw | ConvertFrom-Json $report[0].assignedTo.'Applications'[0].intent | Should -Be 'required' } finally { Remove-Item $testFolder -Recurse -Force -ErrorAction SilentlyContinue } } It "skips allDevices / allLicensedUsers targets" { Mock Invoke-GraphRequest2 -ModuleName IntuneBackup { return @() } Mock Invoke-GraphRequest2 -ModuleName IntuneBackup ` -ParameterFilter { $Uri -like '*/deviceHealthScripts' } { return @( [PSCustomObject]@{ id = 'pr-all'; displayName = 'AllDevices'; '@odata.type' = $null } ) } Mock Invoke-GraphRequest2 -ModuleName IntuneBackup ` -ParameterFilter { $Uri -like '*/deviceHealthScripts/pr-all/assignments' } { return @( [PSCustomObject]@{ intent = '' target = [PSCustomObject]@{ '@odata.type' = '#microsoft.graph.allDevicesAssignmentTarget' groupId = $null } } ) } $testFolder = Join-Path ([System.IO.Path]::GetTempPath()) "IntuneReport_$(Get-Random)" try { Build-AssignmentReport -BackupPath $testFolder -Token $script:token $reportPath = Join-Path $testFolder 'Assignment Report' 'report.json' # Report should not be written (no group assignments) Test-Path $reportPath | Should -Be $false } finally { Remove-Item $testFolder -Recurse -Force -ErrorAction SilentlyContinue } } } Describe "Backup-ConditionalAccess" { It "should remove authenticationStrength@odata.context from grantControls" { Mock Invoke-GraphRequest2 -ModuleName IntuneBackup -ParameterFilter { $Uri -eq '/beta/identity/conditionalAccess/policies' } { return @( [PSCustomObject]@{ id = 'ca-1' displayName = 'CA Policy 1' grantControls = [PSCustomObject]@{ 'authenticationStrength@odata.context' = 'http://example.com' operator = 'AND' } } ) } Mock Remove-VolatileKeys -ModuleName IntuneBackup { return $InputObject } Mock Save-BackupItem -ModuleName IntuneBackup { # Verify the property was removed if ($Item.grantControls.'authenticationStrength@odata.context') { throw "authenticationStrength@odata.context should have been removed" } } Backup-ConditionalAccess -BackupPath 'TestDrive:\backup' -Token $script:token Assert-MockCalled Save-BackupItem -ModuleName IntuneBackup -Times 1 } } |