Testing/Unit/PowerShell/CyberConfig/CyberConfig.Tests.ps1
|
using module '..\..\..\..\Modules\CyberConfig\CyberConfig.psm1' Describe "CyberConfig Module Unit Tests" { BeforeAll { # Create a global mock for ConvertFrom-Yaml to avoid needing powershell-yaml module in CI/CD function global:ConvertFrom-Yaml { param($Yaml) # Simple mock that returns a hashtable (not PSCustomObject) # Handle both array and string inputs $Content = if ($Yaml -is [array]) { $Yaml -join "`n" } else { $Yaml } # Parse basic YAML syntax like "ProductNames: [aad]" or "ProductNames: [aad, exo]" if ($Content -match 'ProductNames:\s*\[([^\]]+)\]') { $ProductsString = $matches[1] $Products = $ProductsString -split ',' | ForEach-Object { $_.Trim() } return @{ ProductNames = $Products } } # Return hashtable with ProductNames if found on separate line if ($Content -match 'ProductNames:') { return @{ ProductNames = @('aad') } } # Return hashtable with at least ProductNames to satisfy validation return @{ ProductNames = @('aad') } } } AfterAll { # Clean up the global mock Remove-Item -Path Function:\ConvertFrom-Yaml -ErrorAction SilentlyContinue } BeforeEach { # Reset the instance before each test to prevent state bleed [CyberConfig]::ResetInstance() } AfterEach { # Reset the instance after each test to prevent state bleed [CyberConfig]::ResetInstance() } AfterAll { # Clean up after tests [CyberConfig]::ResetInstance() } Context "Class Structure and Properties" { It "Should be a valid PowerShell class" { [CyberConfig] | Should -Not -BeNullOrEmpty [CyberConfig].Name | Should -Be "CyberConfig" } It "Should have required static properties" { # Check that the class has static members - the private properties aren't directly accessible [CyberConfig] | Get-Member -Static | Should -Not -BeNullOrEmpty } It "Should have required instance properties" { $Instance = [CyberConfig]::GetInstance() # Instance should be created successfully and be usable $Instance | Should -Not -BeNullOrEmpty $Instance.GetType().Name | Should -Be "CyberConfig" } It "Should have required static methods" { $StaticMethods = [CyberConfig] | Get-Member -Static -MemberType Method | Select-Object -ExpandProperty Name $StaticMethods | Should -Contain "GetInstance" $StaticMethods | Should -Contain "ResetInstance" $StaticMethods | Should -Contain "InitializeValidator" $StaticMethods | Should -Contain "CyberDefault" $StaticMethods | Should -Contain "GetConfigDefaults" $StaticMethods | Should -Contain "ValidateConfigFile" $StaticMethods | Should -Contain "GetSupportedProducts" $StaticMethods | Should -Contain "GetSupportedEnvironments" $StaticMethods | Should -Contain "GetProductInfo" $StaticMethods | Should -Contain "GetPrivilegedRoles" } It "Should have required instance methods" { $Instance = [CyberConfig]::GetInstance() $InstanceMethods = $Instance | Get-Member -MemberType Method | Select-Object -ExpandProperty Name $InstanceMethods | Should -Contain "LoadConfig" $InstanceMethods | Should -Contain "ValidateConfiguration" } } Context "Singleton Pattern Implementation" { It "Should return the same instance on multiple calls" { $Instance1 = [CyberConfig]::GetInstance() $Instance2 = [CyberConfig]::GetInstance() $Instance1 | Should -Be $Instance2 $Instance1.GetHashCode() | Should -Be $Instance2.GetHashCode() } It "Should create new instance after reset" { # Load some configuration into the instance to create state $Instance1 = [CyberConfig]::GetInstance() $TempFile = [System.IO.Path]::ChangeExtension([System.IO.Path]::GetTempFileName(), '.yaml') "ProductNames: [aad]" | Set-Content -Path $TempFile try { $Instance1.LoadConfig($TempFile) $HasConfig1 = $Instance1.Configuration -ne $null [CyberConfig]::ResetInstance() $Instance2 = [CyberConfig]::GetInstance() $HasConfig2 = $Instance2.Configuration -ne $null # After reset, new instance should not have the old configuration $HasConfig1 | Should -Be $true $HasConfig2 | Should -Be $false } finally { Remove-Item -Path $TempFile -Force -ErrorAction SilentlyContinue } } It "Should properly initialize on first access" { [CyberConfig]::ResetInstance() # Should not throw when getting instance { [CyberConfig]::GetInstance() } | Should -Not -Throw # Should have initialized the validator [CyberConfig]::_ValidatorInitialized | Should -Be $true } } Context "Static Method Functionality" { It "Should initialize validator without errors" { { [CyberConfig]::InitializeValidator() } | Should -Not -Throw } It "Should get configuration defaults" { $Defaults = [CyberConfig]::GetConfigDefaults() $Defaults | Should -Not -BeNullOrEmpty $Defaults | Should -BeOfType [PSCustomObject] } It "Should get supported products as array" { $Products = [CyberConfig]::GetSupportedProducts() $Products | Should -Not -BeNullOrEmpty # Ensure it's iterable (could be array or single value) $ProductsArray = @($Products) $ProductsArray.Count | Should -BeGreaterThan 0 } It "Should get supported environments as array" { $Environments = [CyberConfig]::GetSupportedEnvironments() $Environments | Should -Not -BeNullOrEmpty # Ensure it's iterable (could be array or single value) $EnvironmentsArray = @($Environments) $EnvironmentsArray.Count | Should -BeGreaterThan 0 } It "Should get product info for valid products" { $Products = [CyberConfig]::GetSupportedProducts() $FirstProduct = $Products[0] $ProductInfo = [CyberConfig]::GetProductInfo($FirstProduct) $ProductInfo | Should -Not -BeNullOrEmpty $ProductInfo | Should -BeOfType [PSCustomObject] } It "Should get privileged roles as array" { $Roles = [CyberConfig]::GetPrivilegedRoles() $Roles | Should -Not -BeNullOrEmpty # Ensure it's iterable (could be array or single value) $RolesArray = @($Roles) $RolesArray.Count | Should -BeGreaterThan 0 } It "Should provide backward compatibility with CyberDefault method" { { [CyberConfig]::CyberDefault('DefaultOPAVersion') } | Should -Not -Throw { [CyberConfig]::CyberDefault('DefaultProductNames') } | Should -Not -Throw { [CyberConfig]::CyberDefault('DefaultM365Environment') } | Should -Not -Throw } It "Should validate configuration files" { # Create a minimal test file $TempFile = [System.IO.Path]::ChangeExtension([System.IO.Path]::GetTempFileName(), '.yaml') "ProductNames: [aad]" | Set-Content -Path $TempFile try { $Result = [CyberConfig]::ValidateConfigFile($TempFile) $Result | Should -Not -BeNullOrEmpty $Result | Should -BeOfType [PSCustomObject] $Result.PSObject.Properties.Name | Should -Contain 'IsValid' } finally { Remove-Item -Path $TempFile -Force -ErrorAction SilentlyContinue } } } Context "Instance Method Functionality" { It "Should have empty configuration initially" { $Instance = [CyberConfig]::GetInstance() $Instance.Configuration | Should -BeNullOrEmpty } It "Should load configuration files" { $Instance = [CyberConfig]::GetInstance() # Create a minimal test file $TempFile = [System.IO.Path]::ChangeExtension([System.IO.Path]::GetTempFileName(), '.yaml') "ProductNames: [aad]" | Set-Content -Path $TempFile try { { $Instance.LoadConfig($TempFile) } | Should -Not -Throw } finally { Remove-Item -Path $TempFile -Force -ErrorAction SilentlyContinue } } It "Should validate current configuration" { $Instance = [CyberConfig]::GetInstance() # Load some configuration first $TempFile = [System.IO.Path]::ChangeExtension([System.IO.Path]::GetTempFileName(), '.yaml') # Create the default OPAPath directory so validation passes $DefaultOPAPath = Join-Path -Path $env:USERPROFILE -ChildPath ".cyberassessment\Tools" $OPAPathCreated = $false if (-not (Test-Path -Path $DefaultOPAPath)) { New-Item -Path $DefaultOPAPath -ItemType Directory -Force | Out-Null $OPAPathCreated = $true } "ProductNames: [aad]" | Set-Content -Path $TempFile try { $Instance.LoadConfig($TempFile) # Method should exist and be callable { $Instance.ValidateConfiguration() } | Should -Not -Throw } finally { Remove-Item -Path $TempFile -Force -ErrorAction SilentlyContinue # Clean up the OPAPath directory if we created it if ($OPAPathCreated -and (Test-Path -Path $DefaultOPAPath)) { Remove-Item -Path $DefaultOPAPath -Recurse -Force -ErrorAction SilentlyContinue } } } It "Should support skip validation parameter in LoadConfig" { $Instance = [CyberConfig]::GetInstance() # Create a minimal test file $TempFile = [System.IO.Path]::ChangeExtension([System.IO.Path]::GetTempFileName(), '.yaml') "ProductNames: [aad]" | Set-Content -Path $TempFile try { { $Instance.LoadConfig($TempFile, $true) } | Should -Not -Throw { $Instance.LoadConfig($TempFile, $false) } | Should -Not -Throw } finally { Remove-Item -Path $TempFile -Force -ErrorAction SilentlyContinue } } } Context "Error Handling" { It "Should handle invalid file paths gracefully" { $Instance = [CyberConfig]::GetInstance() { $Instance.LoadConfig("nonexistent-file.yaml") } | Should -Throw } It "Should handle invalid product names in GetProductInfo" { { [CyberConfig]::GetProductInfo("InvalidProduct") } | Should -Not -Throw } It "Should handle invalid CyberDefault keys" { # Invalid keys should throw exceptions as designed { [CyberConfig]::CyberDefault("InvalidKey") } | Should -Throw } } Context "State Management" { It "Should maintain configuration state between calls" { $Instance = [CyberConfig]::GetInstance() # Create a test file $TempFile = [System.IO.Path]::ChangeExtension([System.IO.Path]::GetTempFileName(), '.yaml') "ProductNames: [aad]" | Set-Content -Path $TempFile try { $Instance.LoadConfig($TempFile) $Instance.Configuration | Should -Not -BeNullOrEmpty # Get same instance and check configuration persists $SameInstance = [CyberConfig]::GetInstance() $SameInstance.Configuration | Should -Not -BeNullOrEmpty } finally { Remove-Item -Path $TempFile -Force -ErrorAction SilentlyContinue } } It "Should clear state on reset" { $Instance = [CyberConfig]::GetInstance() # Load some configuration $TempFile = [System.IO.Path]::ChangeExtension([System.IO.Path]::GetTempFileName(), '.yaml') "ProductNames: [aad]" | Set-Content -Path $TempFile try { $Instance.LoadConfig($TempFile) $Instance.Configuration | Should -Not -BeNullOrEmpty # Reset and get new instance [CyberConfig]::ResetInstance() $NewInstance = [CyberConfig]::GetInstance() $NewInstance.Configuration | Should -BeNullOrEmpty } finally { Remove-Item -Path $TempFile -Force -ErrorAction SilentlyContinue } } } } |