tests/Core.Tests.ps1
|
#Requires -Version 7.0 #Requires -Modules Pester <# .SYNOPSIS Pester tests for IntuneCD core functions .DESCRIPTION Tests for core backup infrastructure functions: - Invoke-GraphRequest2 - Remove-VolatileKeys - Save-BackupItem - Get-ScopeTags - Resolve-Assignments - Resolve-ScopeTagNames #> BeforeAll { # import the module being tested Import-Module "$PSScriptRoot/../IntuneBackup.psm1" -Force } Describe 'Remove-VolatileKeys' { Context 'Flat object with volatile keys' { It 'removes volatile keys from a flat object' { $obj = [PSCustomObject]@{ displayName = 'Test Policy' id = 'abc123' version = 1 createdDateTime = '2024-01-01' lastModifiedDateTime = 'never' description = 'My Description' } $result = $obj | Remove-VolatileKeys $result.PSObject.Properties.Name | Should -Contain 'displayName' $result.PSObject.Properties.Name | Should -Contain 'description' $result.PSObject.Properties.Name | Should -Contain 'id' $result.PSObject.Properties.Name | Should -Not -Contain 'version' $result.PSObject.Properties.Name | Should -Not -Contain 'createdDateTime' $result.PSObject.Properties.Name | Should -Not -Contain 'lastModifiedDateTime' } } Context 'Nested objects with volatile keys' { It 'recursively removes volatile keys from nested objects' { $obj = [PSCustomObject]@{ displayName = 'Parent' id = 'parent-id' settings = [PSCustomObject]@{ name = 'Setting1' id = 'setting-id' } } $result = $obj | Remove-VolatileKeys $result.settings.PSObject.Properties.Name | Should -Contain 'id' $result.settings.PSObject.Properties.Name | Should -Contain 'name' } } Context 'Arrays of objects with volatile keys' { It 'handles arrays of objects' { $arr = @( [PSCustomObject]@{ displayName = 'Item1'; id = 'id1'; value = 'val1' }, [PSCustomObject]@{ displayName = 'Item2'; id = 'id2'; value = 'val2' } ) $result = $arr | Remove-VolatileKeys $result.Count | Should -Be 2 $result[0].PSObject.Properties.Name | Should -Contain 'id' $result[0].PSObject.Properties.Name | Should -Contain 'displayName' $result[1].PSObject.Properties.Name | Should -Contain 'id' } } Context 'Preserves non-volatile keys' { It 'does not remove non-volatile keys' { $obj = [PSCustomObject]@{ displayName = 'Policy' description = 'Description' settings = 'Some Value' enabled = $true } $result = $obj | Remove-VolatileKeys $result.displayName | Should -Be 'Policy' $result.description | Should -Be 'Description' $result.settings | Should -Be 'Some Value' $result.enabled | Should -Be $true } } Context 'AdditionalExcludedProperties parameter' { It 'removes caller-provided properties in addition to defaults' { $obj = [PSCustomObject]@{ displayName = 'Policy' id = 'keep-by-default' description = 'remove-me' createdDateTime = 'already-volatile' nested = [PSCustomObject]@{ id = 'nested-id' description = 'nested-desc' } } $result = Remove-VolatileKeys -InputObject $obj -AdditionalExcludedProperties @('id', 'description') $result.PSObject.Properties.Name | Should -Contain 'displayName' $result.PSObject.Properties.Name | Should -Not -Contain 'id' $result.PSObject.Properties.Name | Should -Not -Contain 'description' $result.PSObject.Properties.Name | Should -Not -Contain 'createdDateTime' $result.nested.PSObject.Properties.Name | Should -Not -Contain 'id' $result.nested.PSObject.Properties.Name | Should -Not -Contain 'description' } It 'keeps default behavior when no additional properties are passed' { $obj = [PSCustomObject]@{ displayName = 'Policy' id = 'kept' createdDateTime = 'remove-me' } $result = Remove-VolatileKeys -InputObject $obj $result.PSObject.Properties.Name | Should -Contain 'displayName' $result.PSObject.Properties.Name | Should -Contain 'id' $result.PSObject.Properties.Name | Should -Not -Contain 'createdDateTime' } } Context 'Null and empty inputs' { It 'handles null input' { $result = $null | Remove-VolatileKeys $result | Should -BeNullOrEmpty } It 'handles empty array' { $result = @() | Remove-VolatileKeys $result.Count | Should -Be 0 } } } Describe 'Save-BackupItem' { Context 'File creation and folder handling' { It 'creates the folder if it does not exist' { $testFolder = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "IntuneCDTest_$(Get-Random)" $testItem = [PSCustomObject]@{ displayName = 'TestItem'; value = 'test' } try { $result = Save-BackupItem -Item $testItem -Folder $testFolder Test-Path -Path $testFolder -PathType Container | Should -Be $true Test-Path -Path $result -PathType Leaf | Should -Be $true } finally { Remove-Item -Path $testFolder -Recurse -Force -ErrorAction SilentlyContinue } } } Context 'Filename handling' { It 'sanitizes illegal filesystem characters in filename' { $testFolder = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "IntuneCDTest_$(Get-Random)" $testItem = [PSCustomObject]@{ displayName = 'Test<Policy>Name:WithManyIllegalChars?'; value = 'test' } try { $result = Save-BackupItem -Item $testItem -Folder $testFolder # verify the file was created without illegal chars $fileName = Split-Path -Path $result -Leaf $fileName | Should -Not -Match '[<>:""?*|/\\]' # verify content is there Get-Content $result | ConvertFrom-Json | Should -Not -BeNullOrEmpty } finally { Remove-Item -Path $testFolder -Recurse -Force -ErrorAction SilentlyContinue } } It 'uses displayName as default filename' { $testFolder = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "IntuneCDTest_$(Get-Random)" $testItem = [PSCustomObject]@{ displayName = 'MyTestPolicy'; value = 'test' } try { $result = Save-BackupItem -Item $testItem -Folder $testFolder $fileName = [System.IO.Path]::GetFileNameWithoutExtension((Split-Path -Path $result -Leaf)) $fileName | Should -Be 'MyTestPolicy' } finally { Remove-Item -Path $testFolder -Recurse -Force -ErrorAction SilentlyContinue } } It 'uses FileName when PresetFileName is not provided' { $testFolder = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "IntuneCDTest_$(Get-Random)" $testItem = [PSCustomObject]@{ displayName = 'Original'; value = 'test' } try { $result = Save-BackupItem -Item $testItem -Folder $testFolder -FileName 'CustomName' $fileName = [System.IO.Path]::GetFileNameWithoutExtension((Split-Path -Path $result -Leaf)) $fileName | Should -Be 'CustomName' } finally { Remove-Item -Path $testFolder -Recurse -Force -ErrorAction SilentlyContinue } } It 'uses PresetFileName when provided' { $testFolder = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "IntuneCDTest_$(Get-Random)" $testItem = [PSCustomObject]@{ displayName = 'Original'; value = 'test' } try { $result = Save-BackupItem -Item $testItem -Folder $testFolder -FileName 'Ignored' -PresetFileName 'Preset' $fileName = [System.IO.Path]::GetFileNameWithoutExtension((Split-Path -Path $result -Leaf)) $fileName | Should -Be 'Preset' } finally { Remove-Item -Path $testFolder -Recurse -Force -ErrorAction SilentlyContinue } } } Context 'JSON content validation' { It 'writes valid JSON to file' { $testFolder = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "IntuneCDTest_$(Get-Random)" $testItem = [PSCustomObject]@{ displayName = 'TestItem' settings = [PSCustomObject]@{ key = 'value' } array = @(1, 2, 3) } try { $result = Save-BackupItem -Item $testItem -Folder $testFolder # read file and verify JSON $content = Get-Content -Path $result -Raw $parsed = ConvertFrom-Json -InputObject $content $parsed.displayName | Should -Be 'TestItem' $parsed.settings.key | Should -Be 'value' $parsed.array.Count | Should -Be 3 } finally { Remove-Item -Path $testFolder -Recurse -Force -ErrorAction SilentlyContinue } } } Context 'Scope tag resolution' { It 'translates roleScopeTagIds when ScopeTagMap is provided' { $testFolder = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "IntuneCDTest_$(Get-Random)" $testItem = [PSCustomObject]@{ displayName = 'Policy' roleScopeTagIds = @('0', '1') } $map = @{ '0' = 'Default'; '1' = 'Finance' } try { $result = Save-BackupItem -Item $testItem -Folder $testFolder -ScopeTagMap $map $parsed = Get-Content -Path $result -Raw | ConvertFrom-Json $parsed.roleScopeTagIds | Should -Contain 'Default' $parsed.roleScopeTagIds | Should -Contain 'Finance' $parsed.roleScopeTagIds | Should -Not -Contain '0' $parsed.roleScopeTagIds | Should -Not -Contain '1' } finally { Remove-Item -Path $testFolder -Recurse -Force -ErrorAction SilentlyContinue } } It 'does not translate roleScopeTagIds when ScopeTagMap is empty' { $testFolder = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "IntuneCDTest_$(Get-Random)" $testItem = [PSCustomObject]@{ displayName = 'Policy' roleScopeTagIds = @('0') } try { $result = Save-BackupItem -Item $testItem -Folder $testFolder $parsed = Get-Content -Path $result -Raw | ConvertFrom-Json $parsed.roleScopeTagIds | Should -Contain '0' } finally { Remove-Item -Path $testFolder -Recurse -Force -ErrorAction SilentlyContinue } } } } Describe 'Resolve-ScopeTagNames' { Context 'Scope tag ID replacement' { It 'replaces integer scope tag IDs with display names' { $scopeTagMap = @{ '1' = 'Finance' '2' = 'HR' } $obj = [PSCustomObject]@{ displayName = 'Policy' roleScopeTagIds = @(1, 2) } $result = $obj | Resolve-ScopeTagNames -ScopeTagMap $scopeTagMap $result.roleScopeTagIds | Should -Contain 'Finance' $result.roleScopeTagIds | Should -Contain 'HR' $result.roleScopeTagIds.Count | Should -Be 2 } It 'leaves unknown IDs unchanged' { $scopeTagMap = @{ '1' = 'Finance' } $obj = [PSCustomObject]@{ displayName = 'Policy' roleScopeTagIds = @(1, 99) } $result = $obj | Resolve-ScopeTagNames -ScopeTagMap $scopeTagMap $result.roleScopeTagIds | Should -Contain 'Finance' $result.roleScopeTagIds | Should -Contain '99' } } Context 'Edge cases' { It 'handles object without roleScopeTagIds' { $scopeTagMap = @{ '1' = 'Finance' } $obj = [PSCustomObject]@{ displayName = 'Policy' otherField = 'value' } $result = $obj | Resolve-ScopeTagNames -ScopeTagMap $scopeTagMap $result.displayName | Should -Be 'Policy' $result.PSObject.Properties.Name | Should -Not -Contain 'roleScopeTagIds' } It 'handles null input' { $scopeTagMap = @{ '1' = 'Finance' } $result = $null | Resolve-ScopeTagNames -ScopeTagMap $scopeTagMap $result | Should -BeNullOrEmpty } It 'handles empty scope tag map' { $obj = [PSCustomObject]@{ displayName = 'Policy' roleScopeTagIds = @(1) } $result = $obj | Resolve-ScopeTagNames -ScopeTagMap @{} # should return unchanged $result.displayName | Should -Be 'Policy' } } } |