tests/Write-Log.Tests.ps1
|
BeforeAll { $modulePath = (Resolve-Path "$PSScriptRoot\..\modules\Core.psm1").Path Import-Module $modulePath -Force } AfterAll { Remove-Module Core -Force -ErrorAction SilentlyContinue } Describe "Write-Log" { BeforeEach { $logDir = "$PSScriptRoot\..\logs" if (Test-Path $logDir) { Remove-Item $logDir -Recurse -Force -ErrorAction SilentlyContinue Start-Sleep -Milliseconds 100 } } Context "Error level logging" { It "logs message with correct format" { $testMessage = "Test error message" Write-Log -Message $testMessage -Level Error $logFile = Get-ChildItem -Path $logDir -Filter "log_*.csv" | Select-Object -First 1 $logFile | Should -Not -BeNullOrEmpty $content = @(Get-Content -Path $logFile.FullName) $content[-1] | Should -Match "ERROR" $content[-1] | Should -Match $testMessage } It "creates logs directory if not exists" { $logDir = "$PSScriptRoot\..\logs" Write-Log -Message "Test" -Level Error Test-Path $logDir | Should -Be $true } } Context "Warning level logging" { It "logs warning message" { $testMessage = "Test warning" Write-Log -Message $testMessage -Level Warning $logFile = Get-ChildItem -Path "$PSScriptRoot\..\logs" -Filter "log_*.csv" | Select-Object -First 1 $content = @(Get-Content -Path $logFile.FullName) $content[-1] | Should -Match "WARNING" } } Context "Info level logging" { It "logs info message" { $testMessage = "Test info" Write-Log -Message $testMessage -Level Info $logFile = Get-ChildItem -Path "$PSScriptRoot\..\logs" -Filter "log_*.csv" | Select-Object -First 1 $content = @(Get-Content -Path $logFile.FullName) $content[-1] | Should -Match "INFO" } } Context "Sensitive data masking" { It "masks password in log message" { $testMessage = "User login with password=SecureP@ssw0rd" Write-Log -Message $testMessage -Level Info $logFile = Get-ChildItem -Path "$PSScriptRoot\..\logs" -Filter "log_*.csv" | Select-Object -First 1 $content = @(Get-Content -Path $logFile.FullName) $content[-1] | Should -Match "password=\*\*\*" $content[-1] | Should -Not -Match "SecureP@ssw0rd" } It "masks token in log message" { $testMessage = "API token=abc123xyz789" Write-Log -Message $testMessage -Level Info $logFile = Get-ChildItem -Path "$PSScriptRoot\..\logs" -Filter "log_*.csv" | Select-Object -First 1 $content = @(Get-Content -Path $logFile.FullName) $content[-1] | Should -Match "token=\*\*\*" $content[-1] | Should -Not -Match "abc123xyz789" } It "masks api_key in log message" { $testMessage = "Configure api_key=secret_key_12345" Write-Log -Message $testMessage -Level Info $logFile = Get-ChildItem -Path "$PSScriptRoot\..\logs" -Filter "log_*.csv" | Select-Object -First 1 $content = @(Get-Content -Path $logFile.FullName) $content[-1] | Should -Match "api_key=\*\*\*" $content[-1] | Should -Not -Match "secret_key" } } Context "WhatIf behavior" { It "handles -WhatIf parameter without writing file" { $logDir = "$PSScriptRoot\..\logs" Write-Log -Message "WhatIf test" -Level Info -WhatIf $logFile = Get-ChildItem -Path $logDir -Filter "log_*.csv" -ErrorAction SilentlyContinue $logFile | Should -BeNullOrEmpty } It "outputs verbose message with WhatIf" { $output = Write-Log -Message "WhatIf test" -Level Info -WhatIf -Verbose 4>&1 $output | Should -Match "WhatIf" } } Context "CSV escaping" { It "properly escapes double quotes in message" { $testMessage = 'Message with "quoted" text' Write-Log -Message $testMessage -Level Info $logFile = Get-ChildItem -Path "$PSScriptRoot\..\logs" -Filter "log_*.csv" | Select-Object -First 1 $content = Get-Content -Path $logFile.FullName -Raw $content | Should -Match 'Message with ""quoted"" text' } It "properly handles messages with commas" { $testMessage = "Data: value1, value2, value3" Write-Log -Message $testMessage -Level Info $logFile = Get-ChildItem -Path "$PSScriptRoot\..\logs" -Filter "log_*.csv" | Select-Object -First 1 $content = Get-Content -Path $logFile.FullName -Raw $content | Should -Match '"Data: value1, value2, value3"' } It "handles carriage returns in message" { $testMessage = "Data with`rcarriage return" Write-Log -Message $testMessage -Level Info $logFile = Get-ChildItem -Path "$PSScriptRoot\..\logs" -Filter "log_*.csv" | Select-Object -First 1 $content = Get-Content -Path $logFile.FullName $content.Count | Should -BeGreaterThan 1 } } Context "File operations" { It "creates CSV header with correct columns" { $logDir = "$PSScriptRoot\..\logs" Write-Log -Message "First entry" -Level Info $logFile = Get-ChildItem -Path $logDir -Filter "log_*.csv" | Select-Object -First 1 $content = @(Get-Content -Path $logFile.FullName) $content[0] | Should -Match "Timestamp" $content[0] | Should -Match "Level" $content[0] | Should -Match "Caller" $content[0] | Should -Match "Function" $content[0] | Should -Match "LineNumber" $content[0] | Should -Match "Message" } It "appends to existing log file" { $logDir = "$PSScriptRoot\..\logs" $dateString = (Get-Date -Format 'yyyy-MM-dd') $logFile = Join-Path -Path $logDir -ChildPath "log_$dateString.csv" Write-Log -Message "First message" -Level Info $firstCount = @(Get-Content -Path $logFile).Count Write-Log -Message "Second message" -Level Info $secondCount = @(Get-Content -Path $logFile).Count $secondCount | Should -BeGreaterThan $firstCount } } Context "Log levels" { It "logs Debug level" { Write-Log -Message "Debug test" -Level Debug $logFile = Get-ChildItem -Path "$PSScriptRoot\..\logs" -Filter "log_*.csv" | Select-Object -First 1 $content = @(Get-Content -Path $logFile.FullName) $content[-1] | Should -Match "Debug" } It "logs Verbose level" { Write-Log -Message "Verbose test" -Level Verbose $logFile = Get-ChildItem -Path "$PSScriptRoot\..\logs" -Filter "log_*.csv" | Select-Object -First 1 $content = @(Get-Content -Path $logFile.FullName) $content[-1] | Should -Match "Verbose" } } Context "Edge cases" { It "handles very long messages" { $testMessage = [string]::new('x', 5000) Write-Log -Message $testMessage -Level Info $logFile = Get-ChildItem -Path "$PSScriptRoot\..\logs" -Filter "log_*.csv" | Select-Object -First 1 $content = @(Get-Content -Path $logFile.FullName) $content[-1] | Should -Match "x{100}" } It "auto-detects caller from call stack" { $logDir = "$PSScriptRoot\..\logs" $dateString = (Get-Date -Format 'yyyy-MM-dd') $logFile = Join-Path -Path $logDir -ChildPath "log_$dateString.csv" Write-Log -Message "Test" -Level Info $logFile | Should -Not -BeNullOrEmpty Test-Path $logFile | Should -Be $true $content = @(Get-Content -Path $logFile) $content.Count | Should -BeGreaterThan 1 } It "handles custom caller parameter" { Write-Log -Message "Test" -Level Info -Caller "CustomCaller" $logFile = Get-ChildItem -Path "$PSScriptRoot\..\logs" -Filter "log_*.csv" | Select-Object -First 1 $content = @(Get-Content -Path $logFile.FullName) $content[-1] | Should -Match "CustomCaller" } } Context "Error handling and edge scenarios" { It "handles write errors gracefully" { $logDir = "$PSScriptRoot\..\logs" Write-Log -Message "Test" -Level Info # Make log directory read-only to force error $logFile = Get-ChildItem -Path $logDir -Filter "log_*.csv" | Select-Object -First 1 $acl = Get-Acl -Path $logDir $rule = New-Object System.Security.AccessControl.FileSystemAccessRule( [System.Security.Principal.WindowsIdentity]::GetCurrent().User, [System.Security.AccessControl.FileSystemRights]::Write, [System.Security.AccessControl.AccessControlType]::Deny ) $acl.AddAccessRule($rule) Set-Acl -Path $logDir -AclObject $acl -ErrorAction SilentlyContinue # Try to write (should fail gracefully) Write-Log -Message "This should fail" -Level Info -ErrorAction SilentlyContinue # Restore permissions $acl = Get-Acl -Path $logDir $acl.RemoveAccessRule($rule) | Out-Null Set-Acl -Path $logDir -AclObject $acl -ErrorAction SilentlyContinue # Verify directory still exists Test-Path $logDir | Should -Be $true } It "outputs Debug level messages with -Verbose flag" { $output = Write-Log -Message "Debug test message" -Level Debug -Verbose 4>&1 $logFile = Get-ChildItem -Path "$PSScriptRoot\..\logs" -Filter "log_*.csv" | Select-Object -First 1 $content = Get-Content -Path $logFile.FullName -Raw $content | Should -Match "Debug" } It "outputs Verbose level messages with -Verbose flag" { $output = Write-Log -Message "Verbose test message" -Level Verbose -Verbose 4>&1 $logFile = Get-ChildItem -Path "$PSScriptRoot\..\logs" -Filter "log_*.csv" | Select-Object -First 1 $content = Get-Content -Path $logFile.FullName -Raw $content | Should -Match "Verbose" } It "handles empty message with ValidateNotNullOrEmpty" { { Write-Log -Message "" -Level Info } | Should -Throw } It "verifies all CSV columns are present in header" { $logDir = "$PSScriptRoot\..\logs" $dateString = (Get-Date -Format 'yyyy-MM-dd') $logFile = Join-Path -Path $logDir -ChildPath "log_$dateString.csv" Write-Log -Message "Test" -Level Info $header = @(Get-Content -Path $logFile)[0] $columns = $header -split ',' $columns.Count | Should -Be 6 $columns | Should -Contain "Timestamp" $columns | Should -Contain "Level" $columns | Should -Contain "Caller" $columns | Should -Contain "Function" $columns | Should -Contain "LineNumber" $columns | Should -Contain "Message" } } } |