modules/Azure/Discovery/Tests/Unit/CIEMAzureEntraResource.Tests.ps1
|
BeforeAll { Remove-Module Devolutions.CIEM -Force -ErrorAction SilentlyContinue Import-Module (Join-Path $PSScriptRoot '..' '..' '..' '..' '..' 'Devolutions.CIEM.psd1') # Create isolated test DB with base + azure + discovery schemas New-CIEMDatabase -Path "$TestDrive/ciem.db" InModuleScope Devolutions.CIEM { $script:DatabasePath = "$TestDrive/ciem.db" } foreach ($schemaPath in @( (Join-Path $PSScriptRoot '..' '..' '..' 'Infrastructure' 'Data' 'azure_schema.sql'), (Join-Path $PSScriptRoot '..' '..' 'Data' 'discovery_schema.sql') )) { foreach ($statement in ((Get-Content $schemaPath -Raw) -split ';\s*\n' | Where-Object { $_.Trim() })) { $trimmed = $statement.Trim() try { Invoke-CIEMQuery -Query $trimmed -AsNonQuery | Out-Null } catch { if ($trimmed -match 'ALTER\s+TABLE' -and $_.Exception.Message -match 'duplicate column') { continue } throw } } } } Describe 'Entra Resource CRUD' { Context 'New-CIEMAzureEntraResource' { BeforeEach { Invoke-CIEMQuery -Query "DELETE FROM azure_entra_resources" } It 'Creates a resource and returns CIEMAzureEntraResource object' { $result = New-CIEMAzureEntraResource -Id 'aaaabbbb-cccc-dddd-eeee-ffffffffffff' ` -Type 'User' ` -DisplayName 'Test User' ` -Properties '{"userPrincipalName":"test@contoso.com"}' $result | Should -Not -BeNullOrEmpty $result.GetType().Name | Should -Be 'CIEMAzureEntraResource' $result.Id | Should -Be 'aaaabbbb-cccc-dddd-eeee-ffffffffffff' $result.DisplayName | Should -Be 'Test User' } It 'Throws when resource with same Id already exists' { New-CIEMAzureEntraResource -Id 'dup-entra-id' -Type 'User' -DisplayName 'Original' { New-CIEMAzureEntraResource -Id 'dup-entra-id' -Type 'Group' -DisplayName 'Duplicate' } | Should -Throw } It 'Accepts -InputObject parameter set' { $obj = InModuleScope Devolutions.CIEM { $o = [CIEMAzureEntraResource]::new() $o.Id = 'input-entra-id' $o.Type = 'ServicePrincipal' $o.DisplayName = 'Test SP' $o } $result = New-CIEMAzureEntraResource -InputObject $obj $result | Should -Not -BeNullOrEmpty $result.Id | Should -Be 'input-entra-id' } } Context 'Get-CIEMAzureEntraResource' { BeforeAll { Invoke-CIEMQuery -Query "DELETE FROM azure_entra_resources" # Seed test data New-CIEMAzureEntraResource -Id 'user-get1' -Type 'User' -DisplayName 'Alice' New-CIEMAzureEntraResource -Id 'user-get2' -Type 'User' -DisplayName 'Bob' New-CIEMAzureEntraResource -Id 'group-get1' -Type 'Group' -DisplayName 'Engineering' -ParentId 'tenant-root' New-CIEMAzureEntraResource -Id 'sp-get1' -Type 'ServicePrincipal' -DisplayName 'Alice' -ParentId 'app-reg-1' } It 'Returns all resources when no filter' { $results = Get-CIEMAzureEntraResource $results | Should -HaveCount 4 } It 'Returns CIEMAzureEntraResource typed objects (.GetType().Name -eq CIEMAzureEntraResource)' { $results = Get-CIEMAzureEntraResource $results | ForEach-Object { $_.GetType().Name | Should -Be 'CIEMAzureEntraResource' } } It 'Filters by -Id' { $result = Get-CIEMAzureEntraResource -Id 'user-get1' $result | Should -Not -BeNullOrEmpty $result.DisplayName | Should -Be 'Alice' $result.Type | Should -Be 'User' } It 'Filters by -Type' { $results = Get-CIEMAzureEntraResource -Type 'User' $results | Should -HaveCount 2 } It 'Filters by -ParentId' { $results = Get-CIEMAzureEntraResource -ParentId 'tenant-root' $results | Should -HaveCount 1 $results[0].DisplayName | Should -Be 'Engineering' } It 'Filters by -DisplayName' { $results = Get-CIEMAzureEntraResource -DisplayName 'Alice' $results | Should -HaveCount 2 # User Alice + SP Alice } It 'Returns empty array when no match' { $results = Get-CIEMAzureEntraResource -Id 'nonexistent-id' $results | Should -BeNullOrEmpty } } Context 'Update-CIEMAzureEntraResource' { BeforeEach { Invoke-CIEMQuery -Query "DELETE FROM azure_entra_resources" New-CIEMAzureEntraResource -Id 'entra-upd' -Type 'User' -DisplayName 'Original Name' -Properties '{"mail":"old@contoso.com"}' } It 'Updates Properties field' { Update-CIEMAzureEntraResource -Id 'entra-upd' -Properties '{"mail":"new@contoso.com"}' $result = Get-CIEMAzureEntraResource -Id 'entra-upd' $result.Properties | Should -Be '{"mail":"new@contoso.com"}' } It 'Partial update does not overwrite unspecified fields' { Update-CIEMAzureEntraResource -Id 'entra-upd' -Properties '{"updated":true}' $result = Get-CIEMAzureEntraResource -Id 'entra-upd' $result.DisplayName | Should -Be 'Original Name' $result.Type | Should -Be 'User' } It 'Returns nothing without -PassThru' { $result = Update-CIEMAzureEntraResource -Id 'entra-upd' -Properties '{"x":1}' $result | Should -BeNullOrEmpty } It 'Returns updated object with -PassThru' { $result = Update-CIEMAzureEntraResource -Id 'entra-upd' -Properties '{"passthru":true}' -PassThru $result | Should -Not -BeNullOrEmpty $result.GetType().Name | Should -Be 'CIEMAzureEntraResource' $result.Properties | Should -Be '{"passthru":true}' } It 'Accepts -InputObject for full object update' { $obj = Get-CIEMAzureEntraResource -Id 'entra-upd' $obj.DisplayName = 'Updated Name' $obj.ParentId = 'new-parent' Update-CIEMAzureEntraResource -InputObject $obj $result = Get-CIEMAzureEntraResource -Id 'entra-upd' $result.DisplayName | Should -Be 'Updated Name' $result.ParentId | Should -Be 'new-parent' } } Context 'Save-CIEMAzureEntraResource' { BeforeEach { Invoke-CIEMQuery -Query "DELETE FROM azure_entra_resources" } It 'Inserts new record (upsert)' { Save-CIEMAzureEntraResource -Id 'entra-save-new' -Type 'Group' -DisplayName 'New Group' $result = Get-CIEMAzureEntraResource -Id 'entra-save-new' $result | Should -Not -BeNullOrEmpty $result.DisplayName | Should -Be 'New Group' } It 'Updates existing record (upsert)' { Save-CIEMAzureEntraResource -Id 'entra-save-up' -Type 'User' -DisplayName 'Original' Save-CIEMAzureEntraResource -Id 'entra-save-up' -Type 'User' -DisplayName 'Updated' $result = Get-CIEMAzureEntraResource -Id 'entra-save-up' $result.DisplayName | Should -Be 'Updated' # Only 1 row, not 2 $all = Get-CIEMAzureEntraResource $all | Should -HaveCount 1 } It 'Accepts -InputObject via pipeline' { $obj = InModuleScope Devolutions.CIEM { $o = [CIEMAzureEntraResource]::new() $o.Id = 'entra-pipe' $o.Type = 'ServicePrincipal' $o.DisplayName = 'Piped SP' $o.CollectedAt = (Get-Date).ToString('o') $o } $obj | Save-CIEMAzureEntraResource $result = Get-CIEMAzureEntraResource -Id 'entra-pipe' $result | Should -Not -BeNullOrEmpty $result.DisplayName | Should -Be 'Piped SP' } } Context 'Remove-CIEMAzureEntraResource' { BeforeEach { Invoke-CIEMQuery -Query "DELETE FROM azure_entra_resources" New-CIEMAzureEntraResource -Id 'entra-rm1' -Type 'User' -DisplayName 'User RM1' New-CIEMAzureEntraResource -Id 'entra-rm2' -Type 'User' -DisplayName 'User RM2' New-CIEMAzureEntraResource -Id 'entra-rm3' -Type 'Group' -DisplayName 'Group RM1' } It 'Removes by -Id' { Remove-CIEMAzureEntraResource -Id 'entra-rm1' -Confirm:$false $result = Get-CIEMAzureEntraResource -Id 'entra-rm1' $result | Should -BeNullOrEmpty # Other resources still exist Get-CIEMAzureEntraResource | Should -HaveCount 2 } It 'Bulk removes by -Type' { Remove-CIEMAzureEntraResource -Type 'User' -Confirm:$false $users = Get-CIEMAzureEntraResource -Type 'User' $users | Should -BeNullOrEmpty # Group still exists $groups = Get-CIEMAzureEntraResource -Type 'Group' $groups | Should -HaveCount 1 } It 'Removes all records with -All switch' { Remove-CIEMAzureEntraResource -All -Confirm:$false $results = Get-CIEMAzureEntraResource $results | Should -BeNullOrEmpty } It 'Removes via -InputObject' { $obj = Get-CIEMAzureEntraResource -Id 'entra-rm3' Remove-CIEMAzureEntraResource -InputObject $obj -Confirm:$false $result = Get-CIEMAzureEntraResource -Id 'entra-rm3' $result | Should -BeNullOrEmpty } It 'No-ops when Id does not exist' { { Remove-CIEMAzureEntraResource -Id 'nonexistent-entra' -Confirm:$false } | Should -Not -Throw } } } |