Testing/Unit/PowerShell/CyberConfigApp/CyberConfigAppHelpers.Tests.ps1
|
Describe -tag "Helpers" -name 'CyberConfigApp Helper Modules Validation' { BeforeAll { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'helpersPath')] $helpersPath = "$PSScriptRoot\..\..\..\..\Modules\CyberConfigApp\CyberConfigAppHelpers" } Context 'Helper Module Files Existence' { It 'Should have all required helper module files' { $expectedHelpers = @( 'CyberConfigAppAutoSaveHelper.psm1', 'CyberConfigAppDebugHelper.psm1', 'CyberConfigAppResultsHelper.psm1', 'CyberConfigAppChangeLogHelper.psm1', 'CyberConfigAppCyberRunHelper.psm1', 'CyberConfigAppDynamicCardHelper.psm1', 'CyberConfigAppResetHelper.psm1', 'CyberConfigAppBaselineHelper.psm1', 'CyberConfigAppGlobalSettingsHelper.psm1', 'CyberConfigAppGraphHelper.psm1', 'CyberConfigAppImportHelper.psm1', 'CyberConfigAppSettingsDataHelper.psm1', 'CyberConfigAppProductHelper.psm1', 'CyberConfigAppSearchHelper.psm1', 'CyberConfigAppToolTipHelper.psm1', 'CyberConfigAppCommonUIHelper.psm1', 'CyberConfigAppUpdateUIHelper.psm1', 'CyberConfigAppYamlPreviewHelper.psm1' ) foreach ($helper in $expectedHelpers) { $helperPath = Join-Path $helpersPath $helper Test-Path $helperPath | Should -BeTrue -Because "Helper module '$helper' should exist" } } It 'Should have syntactically valid helper modules' { $helperFiles = Get-ChildItem -Path $helpersPath -Filter "*.psm1" foreach ($helperFile in $helperFiles) { { $tokens = $null $errors = $null [System.Management.Automation.Language.Parser]::ParseFile($helperFile.FullName, [ref]$tokens, [ref]$errors) if ($errors.Count -gt 0) { throw "Parse errors in $($helperFile.Name): $($errors -join '; ')" } } | Should -Not -Throw -Because "Helper module '$($helperFile.Name)' should have valid PowerShell syntax" } } } Context 'Helper Module Unicode Character Validation' { It 'Should not contain problematic Unicode characters in PowerShell code' { $helperFiles = Get-ChildItem -Path $helpersPath -Filter "*.psm1" $problematicPatterns = @( [regex]'[🐛🎉✓⚠✗👤⚡📊📄📁🔒▶▼](?=.*\$)' # Unicode chars near PowerShell variables ) foreach ($helperFile in $helperFiles) { $content = Get-Content $helperFile.FullName -Raw foreach ($pattern in $problematicPatterns) { $regexMatches = $pattern.Matches($content) $regexMatches.Count | Should -Be 0 -Because "Helper module '$($helperFile.Name)' should not contain problematic Unicode characters that could cause parsing errors" } } } It 'Should not contain Unicode characters that could cause string parsing issues' { $helperFiles = Get-ChildItem -Path $helpersPath -Filter "*.psm1" foreach ($helperFile in $helperFiles) { $content = Get-Content $helperFile.FullName -Raw # Check for common problematic Unicode characters in PowerShell strings $problematicChars = @( @{ Char = '🐛'; Name = 'Bug emoji' }, @{ Char = '🎉'; Name = 'Party emoji' }, @{ Char = '✓'; Name = 'Check mark' }, @{ Char = '⚠'; Name = 'Warning sign' }, @{ Char = '✗'; Name = 'Cross mark' }, @{ Char = '👤'; Name = 'User silhouette' }, @{ Char = '⚡'; Name = 'Lightning bolt' }, @{ Char = '📊'; Name = 'Bar chart' }, @{ Char = '📄'; Name = 'Document' }, @{ Char = '📁'; Name = 'Folder' }, @{ Char = '🔒'; Name = 'Lock' } ) foreach ($charInfo in $problematicChars) { if ($content.Contains($charInfo.Char)) { # Check if it's in a PowerShell string context (between quotes) $lines = $content -split "`n" for ($i = 0; $i -lt $lines.Count; $i++) { if ($lines[$i].Contains($charInfo.Char)) { # Skip if it's in a comment if ($lines[$i].Trim().StartsWith('#')) { continue } # Check if it's in a string that could cause parsing issues if ($lines[$i] -match '(\$\w+.*?' + [regex]::Escape($charInfo.Char) + '|' + [regex]::Escape($charInfo.Char) + '.*?\$\w+)') { $false | Should -BeTrue -Because "Helper module '$($helperFile.Name)' contains $($charInfo.Name) character '$($charInfo.Char)' near PowerShell variables on line $($i + 1) which could cause parsing errors" } } } } } } } } Context 'Helper Module Content Validation' { It 'Should contain proper PowerShell function definitions' { $helperFiles = Get-ChildItem -Path $helpersPath -Filter "*.psm1" foreach ($helperFile in $helperFiles) { $content = Get-Content $helperFile.FullName -Raw # Each helper should contain at least one function $content | Should -Match 'Function\s+[\w-]+\s*\{' -Because "Helper module '$($helperFile.Name)' should contain at least one function definition" } } } Context 'Helper Module Import Validation' { It 'Should be importable without errors' { $helperFiles = Get-ChildItem -Path $helpersPath -Filter "*.psm1" foreach ($helperFile in $helperFiles) { { # Try to import the module Import-Module $helperFile.FullName -Force -ErrorAction Stop # Remove the module to clean up $moduleName = [System.IO.Path]::GetFileNameWithoutExtension($helperFile.Name) Remove-Module $moduleName -Force -ErrorAction SilentlyContinue } | Should -Not -Throw -Because "Helper module '$($helperFile.Name)' should be importable without errors" } } } Context 'Helper Module Function Inventory Validation' { It 'Should contain all expected functions per helper module' { # Define expected functions for each helper module $expectedFunctions = @{ 'CyberConfigAppAutoSaveHelper.psm1' = @( 'Get-AutoSaveDirectory', 'Test-AutoSaveEnabled', 'Save-AutoSavePolicy', 'Remove-AutoSavePolicy', 'Get-AutoSavePolicies', 'Restore-AutoSavePolicies', 'Save-AutoSaveSettings', 'Show-AutoSaveRestorePrompt', 'Restore-AutoSaveWithProgress', 'Show-AutoSaveRestoreProgress', 'Remove-AutoSaveData', 'Restore-AutoSaveSettings', 'Clear-AutoSaveData' ) 'CyberConfigAppBaselineHelper.psm1' = @( 'Get-CyberConfigExclusionMappingsFromMarkdown', 'Update-CyberConfigBaselineWithMarkdown', 'Get-CyberBaselinePolicy', 'Get-CyberPolicyContent' ) 'CyberConfigAppChangeLogHelper.psm1' = @( 'Show-ChangelogWindow' ) 'CyberConfigAppDebugHelper.psm1' = @( 'Get-DebugSanitizedValue', 'Get-DebugSanitizedString', 'Export-DebugLog', 'Write-DebugOutput', 'Show-DebugWindow', 'Hide-DebugWindow', 'Update-DebugWindow', 'Update-DebugDisplayFilter' ) 'CyberConfigAppDynamicCardHelper.psm1' = @( 'Test-RequiredField', 'Test-FieldValidation', 'New-FieldListControl', 'Add-FieldListControl', 'New-FieldListCard' ) 'CyberConfigAppGlobalSettingsHelper.psm1' = @( 'New-GlobalSettingsControls', 'Update-GlobalSettingsCards' ) 'CyberConfigAppGraphHelper.psm1' = @( 'Update-GraphStatusIndicator', 'Initialize-GraphStatusIndicator', 'Invoke-GraphQueryWithFilter', 'Get-GraphEntityConfig', 'Show-GraphProgressWindow', 'Show-GraphSelector', 'Show-UISelectionWindow', 'Add-GraphButton', 'Add-GraphButtonToTextBox' ) 'CyberConfigAppImportHelper.psm1' = @( 'Show-YamlImportProgress', 'Invoke-YamlImportWithProgress', 'Import-YamlToDataStructures' ) 'CyberConfigAppSettingsDataHelper.psm1' = @( 'Set-SettingsDataForGeneralSection', 'Set-SettingsDataForAdvancedSection', 'Set-SettingsDataForGlobalSection' ) 'CyberConfigAppProductHelper.psm1' = @( 'Update-ProductNames', 'Get-ProductNamesForYaml', 'New-ProductPolicyCards' ) 'CyberConfigAppResetHelper.psm1' = @( 'Clear-FieldValue' ) 'CyberConfigAppResultsHelper.psm1' = @( 'Initialize-ResultsTab', 'Update-ResultsTab', 'Test-ResultsDataValidity', 'New-ResultsReportTab', 'New-ResultsContent', 'New-ResultsGroupExpanderXaml', 'New-ResultsNoDataTab', 'Update-ResultsCount', 'Get-ResultsReportTimeStamp', 'Get-ResultsRelativeTime', 'Open-ResultsFolder' ) 'CyberConfigAppCyberRunHelper.psm1' = @( 'New-CyberRunParameterControls', 'Initialize-CyberRunTab', 'Update-CyberRunStatus', 'Test-CyberRunReadiness', 'Start-CyberAssessmentExecution', 'Export-TempYamlConfiguration', 'Build-CyberAssessmentCommand', 'Start-CyberAssessmentJob', 'Write-TimestampedOutput', 'Find-CyberAssessmentResultFolder', 'Start-CyberAssessmentMonitoringRealTime', 'Stop-CyberAssessmentExecution', 'Complete-CyberAssessmentExecution', 'Start-CyberAssessmentMonitoring', 'Reset-CyberRunUI' ) 'CyberConfigAppSearchHelper.psm1' = @( 'Show-SearchAndFilterControl', 'Hide-SearchAndFilterControl', 'Add-SearchAndFilterCapability', 'Set-SearchAndFilter', 'Test-SearchAndFilter' ) 'CyberConfigAppToolTipHelper.psm1' = @( 'Add-ToolTipHoverPopup', 'Initialize-ToolTipHelp' ) 'CyberConfigAppCommonUIHelper.psm1' = @( 'Get-UIConfigCriticalValues', 'Find-UIControlInContainer', 'Find-UIControlElement', 'Add-UIControlEventHandler', 'Find-UIFieldBySettingName', 'Find-UIControlByName', 'Set-UIControlValue', 'Find-UIListContainer', 'Find-UICheckBox', 'Find-UITextBox', 'Find-UIDatePicker', 'Set-UIComboBoxValue', 'Confirm-UIRequiredField', 'Initialize-PlaceholderTextBox' ) 'CyberConfigAppUpdateUIHelper.psm1' = @( 'Update-UIFromSettingsData', 'Update-GeneralSettingsFromData', 'Update-AdvancedSettingsFromData', 'Update-ProductNameCheckboxFromData' ) 'CyberConfigAppYamlPreviewHelper.psm1' = @( 'Format-YamlMultilineString', 'New-YamlPreviewConvert', 'New-YamlPreview' ) } foreach ($helperModule in $expectedFunctions.Keys) { $helperPath = Join-Path $helpersPath $helperModule if (Test-Path $helperPath) { $content = Get-Content $helperPath -Raw $expectedFunctionList = $expectedFunctions[$helperModule] foreach ($expectedFunction in $expectedFunctionList) { # Check for function definition (case insensitive) $escapedFunction = [regex]::Escape($expectedFunction) $functionPattern = "(?i)function\s+$escapedFunction\s*[\{\(]" $content | Should -Match $functionPattern -Because "Helper module '$helperModule' should contain function '$expectedFunction'" } # Verify function count matches expected count $actualFunctions = [regex]::Matches($content, '(?i)function\s+([\w-]+)', [System.Text.RegularExpressions.RegexOptions]::IgnoreCase) $actualFunctionNames = $actualFunctions | ForEach-Object { $_.Groups[1].Value } $uniqueActualFunctions = $actualFunctionNames | Sort-Object | Get-Unique # Allow some tolerance for potential duplicate function definitions or helper functions $uniqueActualFunctions.Count | Should -BeGreaterOrEqual $expectedFunctionList.Count -Because "Helper module '$helperModule' should have at least $($expectedFunctionList.Count) unique functions (found $($uniqueActualFunctions.Count))" } } } It 'Should not contain duplicate function definitions' { $helperFiles = Get-ChildItem -Path $helpersPath -Filter "*.psm1" foreach ($helperFile in $helperFiles) { $content = Get-Content $helperFile.FullName -Raw # Find all function definitions (more precise regex to avoid comments) $functionMatches = [regex]::Matches($content, '^\s*function\s+([\w-]+)', [System.Text.RegularExpressions.RegexOptions]::IgnoreCase -bor [System.Text.RegularExpressions.RegexOptions]::Multiline) $functionNames = $functionMatches | ForEach-Object { $_.Groups[1].Value.ToLower() } # Check for duplicates $duplicates = $functionNames | Group-Object | Where-Object { $_.Count -gt 1 } if ($duplicates) { $duplicateList = ($duplicates | ForEach-Object { "$($_.Name) ($($_.Count) times)" }) -join ', ' $false | Should -BeTrue -Because "Helper module '$($helperFile.Name)' contains duplicate function definitions: $duplicateList" } } } } Context 'Helper Module Dependencies Validation' { It 'Should not have circular dependencies between helpers' { $helperFiles = Get-ChildItem -Path $helpersPath -Filter "*.psm1" foreach ($helperFile in $helperFiles) { $content = Get-Content $helperFile.FullName -Raw $currentModuleName = [System.IO.Path]::GetFileNameWithoutExtension($helperFile.Name) # Remove comment blocks to avoid false positives from example code $contentWithoutComments = $content -replace '(?s)<#.*?#>', '' # Check if this module tries to import itself (excluding comments) $contentWithoutComments | Should -Not -Match "Import-Module.*$currentModuleName" -Because "Helper module '$($helperFile.Name)' should not import itself" # Check for potential circular references foreach ($otherHelper in $helperFiles) { if ($otherHelper.Name -eq $helperFile.Name) { continue } $otherModuleName = [System.IO.Path]::GetFileNameWithoutExtension($otherHelper.Name) $otherContent = Get-Content $otherHelper.FullName -Raw # If current module imports other module, other module should not import current if ($content -match "Import-Module.*$otherModuleName" -and $otherContent -match "Import-Module.*$currentModuleName") { $false | Should -BeTrue -Because "Circular dependency detected between '$($helperFile.Name)' and '$($otherHelper.Name)'" } } } } } Context 'Helper Module Documentation Validation' { It 'Should contain proper function documentation' { $helperFiles = Get-ChildItem -Path $helpersPath -Filter "*.psm1" foreach ($helperFile in $helperFiles) { $content = Get-Content $helperFile.FullName -Raw # If the file contains functions, it should have at least some documentation if ($content -match 'Function\s+[\w-]+') { $content | Should -Match '\.SYNOPSIS|\.DESCRIPTION' -Because "Helper module '$($helperFile.Name)' should contain function documentation with SYNOPSIS or DESCRIPTION" } } } } } |