modules/Devolutions.CIEM.Graph/Tests/Unit/CIEMGraphNode.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 + graph schemas
    New-CIEMDatabase -Path "$TestDrive/ciem.db"

    $azureSchema = Join-Path $PSScriptRoot '..' '..' '..' 'Azure' 'Infrastructure' 'Data' 'azure_schema.sql'
    Invoke-CIEMQuery -Query (Get-Content $azureSchema -Raw)

    $discoverySchema = Join-Path $PSScriptRoot '..' '..' '..' 'Azure' 'Discovery' 'Data' 'discovery_schema.sql'
    Invoke-CIEMQuery -Query (Get-Content $discoverySchema -Raw)

    $graphSchema = Join-Path $PSScriptRoot '..' '..' 'Data' 'graph_schema.sql'
    Invoke-CIEMQuery -Query (Get-Content $graphSchema -Raw)

    InModuleScope Devolutions.CIEM {
        $script:DatabasePath = "$TestDrive/ciem.db"
    }
}

Describe 'Graph Node CRUD' {

    Context 'Get-CIEMGraphNode' {
        BeforeAll {
            Invoke-CIEMQuery -Query "DELETE FROM graph_nodes"
            # Seed 4 test nodes
            Save-CIEMGraphNode -Id '/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/get-vm1' -Kind 'AzureVM' -DisplayName 'get-vm1' -Provider 'azure' -SubscriptionId 'sub1' -ResourceGroup 'rg1'
            Save-CIEMGraphNode -Id '/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/networkSecurityGroups/get-nsg1' -Kind 'AzureNSG' -DisplayName 'get-nsg1' -Provider 'azure' -SubscriptionId 'sub1' -ResourceGroup 'rg1'
            Save-CIEMGraphNode -Id '/subscriptions/sub2/resourceGroups/rg2/providers/Microsoft.Compute/virtualMachines/get-vm2' -Kind 'AzureVM' -DisplayName 'get-vm2' -Provider 'azure' -SubscriptionId 'sub2' -ResourceGroup 'rg2'
            Save-CIEMGraphNode -Id 'user-guid-1234' -Kind 'EntraUser' -DisplayName 'Test User'
        }

        It 'Returns all nodes when no filter' {
            $results = Get-CIEMGraphNode
            $results | Should -HaveCount 4
        }

        It 'Returns CIEMGraphNode typed objects (.GetType().Name -eq CIEMGraphNode)' {
            $results = Get-CIEMGraphNode
            $results | ForEach-Object { $_.GetType().Name | Should -Be 'CIEMGraphNode' }
        }

        It 'Filters by -Id' {
            $result = Get-CIEMGraphNode -Id 'user-guid-1234'
            $result | Should -Not -BeNullOrEmpty
            $result.DisplayName | Should -Be 'Test User'
        }

        It 'Filters by -Kind' {
            $results = Get-CIEMGraphNode -Kind 'AzureVM'
            $results | Should -HaveCount 2
        }

        It 'Filters by -Provider' {
            $results = Get-CIEMGraphNode -Provider 'azure'
            $results | Should -HaveCount 4
        }

        It 'Filters by -SubscriptionId' {
            $results = Get-CIEMGraphNode -SubscriptionId 'sub2'
            $results | Should -HaveCount 1
            $results[0].DisplayName | Should -Be 'get-vm2'
        }

        It 'Filters by -DisplayName' {
            $result = Get-CIEMGraphNode -DisplayName 'get-nsg1'
            $result | Should -Not -BeNullOrEmpty
            $result.Kind | Should -Be 'AzureNSG'
        }

        It 'Returns empty array when no match' {
            $results = Get-CIEMGraphNode -Id '/nonexistent'
            $results | Should -BeNullOrEmpty
        }
    }

    Context 'Save-CIEMGraphNode' {
        BeforeEach {
            Invoke-CIEMQuery -Query "DELETE FROM graph_nodes"
        }

        It 'Inserts a new node (upsert)' {
            Save-CIEMGraphNode -Id '/subscriptions/sub1/rg/vm-save-new' -Kind 'AzureVM' -DisplayName 'vm-save-new'
            $result = Get-CIEMGraphNode -Id '/subscriptions/sub1/rg/vm-save-new'
            $result | Should -Not -BeNullOrEmpty
            $result.DisplayName | Should -Be 'vm-save-new'
        }

        It 'Updates existing node with same Id (upsert)' {
            Save-CIEMGraphNode -Id '/subscriptions/sub1/rg/vm-save-up' -Kind 'AzureVM' -DisplayName 'vm-original'
            Save-CIEMGraphNode -Id '/subscriptions/sub1/rg/vm-save-up' -Kind 'AzureVM' -DisplayName 'vm-updated'
            $result = Get-CIEMGraphNode -Id '/subscriptions/sub1/rg/vm-save-up'
            $result.DisplayName | Should -Be 'vm-updated'
            # Only 1 row, not 2
            $all = Get-CIEMGraphNode
            $all | Should -HaveCount 1
        }

        It 'Accepts -InputObject via pipeline' {
            $obj = InModuleScope Devolutions.CIEM {
                $o = [CIEMGraphNode]::new()
                $o.Id = '/subscriptions/sub1/rg/vm-pipe'
                $o.Kind = 'AzureVM'
                $o.DisplayName = 'vm-pipe'
                $o.CollectedAt = (Get-Date).ToString('o')
                $o
            }
            $obj | Save-CIEMGraphNode
            $result = Get-CIEMGraphNode -Id '/subscriptions/sub1/rg/vm-pipe'
            $result | Should -Not -BeNullOrEmpty
            $result.DisplayName | Should -Be 'vm-pipe'
        }
    }

    Context 'Remove-CIEMGraphNode' {
        BeforeEach {
            Invoke-CIEMQuery -Query "DELETE FROM graph_nodes"
            Save-CIEMGraphNode -Id '/subscriptions/sub1/rg/vm-rm1' -Kind 'AzureVM' -DisplayName 'vm-rm1' -Provider 'azure'
            Save-CIEMGraphNode -Id '/subscriptions/sub1/rg/vm-rm2' -Kind 'AzureVM' -DisplayName 'vm-rm2' -Provider 'azure'
            Save-CIEMGraphNode -Id 'user-guid-rm1' -Kind 'EntraUser' -DisplayName 'user-rm1' -Provider 'azure'
        }

        It 'Removes by -Id' {
            Remove-CIEMGraphNode -Id '/subscriptions/sub1/rg/vm-rm1' -Confirm:$false
            $result = Get-CIEMGraphNode -Id '/subscriptions/sub1/rg/vm-rm1'
            $result | Should -BeNullOrEmpty
            # Other nodes still exist
            Get-CIEMGraphNode | Should -HaveCount 2
        }

        It 'Removes all by -Kind (bulk delete)' {
            Remove-CIEMGraphNode -Kind 'AzureVM' -Confirm:$false
            $vms = Get-CIEMGraphNode -Kind 'AzureVM'
            $vms | Should -BeNullOrEmpty
            # EntraUser still exists
            $users = Get-CIEMGraphNode -Kind 'EntraUser'
            $users | Should -HaveCount 1
        }

        It 'Removes all by -Provider (bulk delete)' {
            Remove-CIEMGraphNode -Provider 'azure' -Confirm:$false
            $results = Get-CIEMGraphNode -Provider 'azure'
            $results | Should -BeNullOrEmpty
        }

        It 'Removes all records with -All switch' {
            Remove-CIEMGraphNode -All -Confirm:$false
            $results = Get-CIEMGraphNode
            $results | Should -BeNullOrEmpty
        }

        It 'Removes via -InputObject' {
            $obj = Get-CIEMGraphNode -Id 'user-guid-rm1'
            Remove-CIEMGraphNode -InputObject $obj -Confirm:$false
            $result = Get-CIEMGraphNode -Id 'user-guid-rm1'
            $result | Should -BeNullOrEmpty
        }

        It 'No-ops when Id does not exist' {
            { Remove-CIEMGraphNode -Id '/nonexistent' -Confirm:$false } | Should -Not -Throw
        }
    }
}