modules/Devolutions.CIEM.Graph/Tests/Unit/GraphClasses.Tests.ps1

BeforeAll {
    Remove-Module Devolutions.CIEM -Force -ErrorAction SilentlyContinue
    Import-Module (Join-Path $PSScriptRoot '..' '..' '..' '..' 'Devolutions.CIEM.psd1')
}

Describe 'Graph Classes' {

    Context 'CIEMGraphNode class' {
        It 'CIEMGraphNode stores and retrieves all properties (Id, Kind, DisplayName, Provider, SubscriptionId, ResourceGroup, Properties, CollectedAt)' {
            $obj = InModuleScope Devolutions.CIEM { [CIEMGraphNode]::new() }
            $props = $obj.PSObject.Properties.Name
            $props | Should -Contain 'Id'
            $props | Should -Contain 'Kind'
            $props | Should -Contain 'DisplayName'
            $props | Should -Contain 'Provider'
            $props | Should -Contain 'SubscriptionId'
            $props | Should -Contain 'ResourceGroup'
            $props | Should -Contain 'Properties'
            $props | Should -Contain 'CollectedAt'
        }
    }

    Context 'CIEMGraphEdge class' {
        It 'CIEMGraphEdge stores and retrieves all properties (Id, SourceId, TargetId, Kind, Properties, Computed, CollectedAt)' {
            $obj = InModuleScope Devolutions.CIEM { [CIEMGraphEdge]::new() }
            $props = $obj.PSObject.Properties.Name
            $props | Should -Contain 'Id'
            $props | Should -Contain 'SourceId'
            $props | Should -Contain 'TargetId'
            $props | Should -Contain 'Kind'
            $props | Should -Contain 'Properties'
            $props | Should -Contain 'Computed'
            $props | Should -Contain 'CollectedAt'
        }
    }

    Context 'CIEMAttackPath class' {
        It 'CIEMAttackPath stores and retrieves pattern metadata (PatternId, PatternName, Severity, Category)' {
            $obj = InModuleScope Devolutions.CIEM { [CIEMAttackPath]::new() }
            $props = $obj.PSObject.Properties.Name
            $props | Should -Contain 'PatternId'
            $props | Should -Contain 'PatternName'
            $props | Should -Contain 'Severity'
            $props | Should -Contain 'Category'
        }

        It 'CIEMAttackPath stores and retrieves path data (Path, Edges)' {
            $obj = InModuleScope Devolutions.CIEM { [CIEMAttackPath]::new() }
            $props = $obj.PSObject.Properties.Name
            $props | Should -Contain 'Path'
            $props | Should -Contain 'Edges'
        }

        It 'CIEMAttackPath Path and Edges default to empty arrays' {
            $obj = InModuleScope Devolutions.CIEM {
                $a = [CIEMAttackPath]::new()
                @{ PathCount = @($a.Path).Count; EdgesCount = @($a.Edges).Count }
            }
            $obj.PathCount | Should -Be 0
            $obj.EdgesCount | Should -Be 0
        }

        It 'CIEMAttackPath accepts assigned values for all properties' {
            $obj = InModuleScope Devolutions.CIEM {
                $a = [CIEMAttackPath]::new()
                $a.PatternId = 'test-pattern'
                $a.PatternName = 'Test Pattern'
                $a.Severity = 'critical'
                $a.Category = 'identity'
                $a.Path = @([PSCustomObject]@{ id = 'node1'; kind = 'EntraUser' })
                $a.Edges = @([PSCustomObject]@{ id = 1; kind = 'HasRole' })
                @{
                    PatternId = $a.PatternId
                    PatternName = $a.PatternName
                    Severity = $a.Severity
                    Category = $a.Category
                    PathCount = $a.Path.Count
                    PathKind = $a.Path[0].kind
                    EdgeCount = $a.Edges.Count
                    EdgeKind = $a.Edges[0].kind
                }
            }
            $obj.PatternId | Should -Be 'test-pattern'
            $obj.PatternName | Should -Be 'Test Pattern'
            $obj.Severity | Should -Be 'critical'
            $obj.Category | Should -Be 'identity'
            $obj.PathCount | Should -Be 1
            $obj.PathKind | Should -Be 'EntraUser'
            $obj.EdgeCount | Should -Be 1
            $obj.EdgeKind | Should -Be 'HasRole'
        }
    }
}