tests/Write-Log.Tests.ps1

Describe 'Write-Log' {
    BeforeAll {
        $TempLogDir = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath 'WriteLogTests'
        if (-not (Test-Path -Path $TempLogDir)) {
            New-Item -Path $TempLogDir -ItemType Directory | Out-Null
        }
        $TestLogPath = Join-Path -Path $TempLogDir -ChildPath 'test.log'
    }

    AfterAll {
        Remove-Item -Path $TempLogDir -Recurse -Force -ErrorAction SilentlyContinue
    }

    BeforeEach {
        # Clean up log files before each test
        Remove-Item -Path "$TempLogDir/*.log*" -Force -ErrorAction SilentlyContinue
    }

    Context 'Basic logging functionality' {
        It 'Should write a message to the log file' {
            Write-Log -Message 'Test message' -LogPath $TestLogPath -NoConsole
            Test-Path -Path $TestLogPath | Should -Be $true
        }

        It 'Should include timestamp in correct format YYYY-MM-DD HH:MM:SS' {
            Write-Log -Message 'Timestamp test' -LogPath $TestLogPath -NoConsole
            $content = Get-Content -Path $TestLogPath -Raw
            $content | Should -Match '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}'
        }

        It 'Should include the log level in the message' {
            Write-Log -Message 'Level test' -Level INFO -LogPath $TestLogPath -NoConsole
            $content = Get-Content -Path $TestLogPath -Raw
            $content | Should -Match '\[INFO\]'
        }

        It 'Should include the actual message' {
            $testMessage = 'This is a unique test message'
            Write-Log -Message $testMessage -LogPath $TestLogPath -NoConsole
            $content = Get-Content -Path $TestLogPath -Raw
            $content | Should -Match $testMessage
        }
    }

    Context 'Log levels' {
        It 'Should support DEBUG level' {
            Write-Log -Message 'Debug' -Level DEBUG -MinLogLevel DEBUG -LogPath $TestLogPath -NoConsole
            $content = Get-Content -Path $TestLogPath -Raw
            $content | Should -Match '\[DEBUG\]'
        }

        It 'Should support INFO level' {
            Write-Log -Message 'Info' -Level INFO -LogPath $TestLogPath -NoConsole
            $content = Get-Content -Path $TestLogPath -Raw
            $content | Should -Match '\[INFO\]'
        }

        It 'Should support WARNING level' {
            Write-Log -Message 'Warning' -Level WARNING -LogPath $TestLogPath -NoConsole
            $content = Get-Content -Path $TestLogPath -Raw
            $content | Should -Match '\[WARNING\]'
        }

        It 'Should support ERROR level' {
            Write-Log -Message 'Error' -Level ERROR -LogPath $TestLogPath -NoConsole
            $content = Get-Content -Path $TestLogPath -Raw
            $content | Should -Match '\[ERROR\]'
        }

        It 'Should default to INFO level when no level specified' {
            Write-Log -Message 'Default level' -LogPath $TestLogPath -NoConsole
            $content = Get-Content -Path $TestLogPath -Raw
            $content | Should -Match '\[INFO\]'
        }
    }

    Context 'MinLogLevel filtering' {
        It 'Should filter out DEBUG messages when MinLogLevel is INFO (default)' {
            Write-Log -Message 'Debug message' -Level DEBUG -LogPath $TestLogPath -NoConsole
            Test-Path -Path $TestLogPath | Should -Be $false
        }

        It 'Should show INFO messages when MinLogLevel is INFO' {
            Write-Log -Message 'Info message' -Level INFO -MinLogLevel INFO -LogPath $TestLogPath -NoConsole
            Test-Path -Path $TestLogPath | Should -Be $true
        }

        It 'Should filter out INFO and DEBUG when MinLogLevel is WARNING' {
            Write-Log -Message 'Debug' -Level DEBUG -MinLogLevel WARNING -LogPath $TestLogPath -NoConsole
            Write-Log -Message 'Info' -Level INFO -MinLogLevel WARNING -LogPath $TestLogPath -NoConsole
            Test-Path -Path $TestLogPath | Should -Be $false
        }

        It 'Should show WARNING and ERROR when MinLogLevel is WARNING' {
            Write-Log -Message 'Warning' -Level WARNING -MinLogLevel WARNING -LogPath $TestLogPath -NoConsole
            Write-Log -Message 'Error' -Level ERROR -MinLogLevel WARNING -LogPath $TestLogPath -NoConsole
            $content = Get-Content -Path $TestLogPath -Raw
            $content | Should -Match '\[WARNING\]'
            $content | Should -Match '\[ERROR\]'
        }

        It 'Should only show ERROR when MinLogLevel is ERROR' {
            Write-Log -Message 'Debug' -Level DEBUG -MinLogLevel ERROR -LogPath $TestLogPath -NoConsole
            Write-Log -Message 'Info' -Level INFO -MinLogLevel ERROR -LogPath $TestLogPath -NoConsole
            Write-Log -Message 'Warning' -Level WARNING -MinLogLevel ERROR -LogPath $TestLogPath -NoConsole
            Write-Log -Message 'Error' -Level ERROR -MinLogLevel ERROR -LogPath $TestLogPath -NoConsole
            $content = Get-Content -Path $TestLogPath -Raw
            $content | Should -Match '\[ERROR\]'
            $content | Should -Not -Match '\[DEBUG\]'
            $content | Should -Not -Match '\[INFO\]'
            $content | Should -Not -Match '\[WARNING\]'
        }

        It 'Should show all levels when MinLogLevel is DEBUG' {
            Write-Log -Message 'Debug' -Level DEBUG -MinLogLevel DEBUG -LogPath $TestLogPath -NoConsole
            Write-Log -Message 'Info' -Level INFO -MinLogLevel DEBUG -LogPath $TestLogPath -NoConsole
            Write-Log -Message 'Warning' -Level WARNING -MinLogLevel DEBUG -LogPath $TestLogPath -NoConsole
            Write-Log -Message 'Error' -Level ERROR -MinLogLevel DEBUG -LogPath $TestLogPath -NoConsole
            $lines = Get-Content -Path $TestLogPath
            $lines.Count | Should -Be 4
        }
    }

    Context 'Console and file logging options' {
        It 'Should not write to file when LogPath is not specified' {
            # This test verifies default behavior (console only)
            # Since we cannot easily capture console output, we verify no file is created
            $defaultLogPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath 'CablersPowershellCore.log'
            Remove-Item -Path $defaultLogPath -Force -ErrorAction SilentlyContinue
            Write-Log -Message 'Console only by default'
            Test-Path -Path $defaultLogPath | Should -Be $false
        }

        It 'Should write to file when LogPath is specified' {
            Write-Log -Message 'File and console' -LogPath $TestLogPath
            Test-Path -Path $TestLogPath | Should -Be $true
        }

        It 'Should write to file only when LogPath is specified with NoConsole' {
            Write-Log -Message 'File only' -LogPath $TestLogPath -NoConsole
            Test-Path -Path $TestLogPath | Should -Be $true
        }
    }

    Context 'Log file path handling' {
        It 'Should create log directory if it does not exist' {
            $newDir = Join-Path -Path $TempLogDir -ChildPath 'newsubdir/deep/path'
            $newLogPath = Join-Path -Path $newDir -ChildPath 'test.log'
            
            Write-Log -Message 'New directory test' -LogPath $newLogPath -NoConsole
            
            Test-Path -Path $newLogPath | Should -Be $true
        }

        It 'Should use custom log path when specified' {
            $customPath = Join-Path -Path $TempLogDir -ChildPath 'custom.log'
            Write-Log -Message 'Custom path' -LogPath $customPath -NoConsole
            Test-Path -Path $customPath | Should -Be $true
        }
    }

    Context 'Log rotation' {
        It 'Should rotate log when file exceeds MaxFileSizeMB' {
            $rotationLogPath = Join-Path -Path $TempLogDir -ChildPath 'rotation.log'
            
            # Create a file larger than 1MB
            $largeContent = 'A' * 1048576  # 1MB
            Set-Content -Path $rotationLogPath -Value $largeContent
            
            # This should trigger rotation
            Write-Log -Message 'Rotation trigger' -LogPath $rotationLogPath -MaxFileSizeMB 1 -NoConsole
            
            # Check if rotation happened
            Test-Path -Path "$rotationLogPath.1" | Should -Be $true
            
            # New log should contain only the new message
            $newContent = Get-Content -Path $rotationLogPath -Raw
            $newContent | Should -Match 'Rotation trigger'
        }

        It 'Should keep only MaxLogFiles rotated files' {
            $rotationLogPath = Join-Path -Path $TempLogDir -ChildPath 'maxfiles.log'
            
            # Create initial rotated files
            for ($i = 1; $i -le 5; $i++) {
                Set-Content -Path "$rotationLogPath.$i" -Value "Old log $i"
            }
            
            # Create a large current log to trigger rotation
            $largeContent = 'A' * 1048576
            Set-Content -Path $rotationLogPath -Value $largeContent
            
            # Trigger rotation with MaxLogFiles = 3
            Write-Log -Message 'Max files test' -LogPath $rotationLogPath -MaxFileSizeMB 1 -MaxLogFiles 3 -NoConsole
            
            # Files beyond MaxLogFiles should be deleted
            Test-Path -Path "$rotationLogPath.4" | Should -Be $false
            Test-Path -Path "$rotationLogPath.5" | Should -Be $false
        }
    }

    Context 'Pipeline input' {
        It 'Should accept message from pipeline' {
            'Pipeline message' | Write-Log -LogPath $TestLogPath -NoConsole
            $content = Get-Content -Path $TestLogPath -Raw
            $content | Should -Match 'Pipeline message'
        }
    }

    Context 'Parameter validation' {
        It 'Should reject invalid Level values' {
            { Write-Log -Message 'Test' -Level 'INVALID' -LogPath $TestLogPath -NoConsole } | Should -Throw
        }

        It 'Should reject invalid MinLogLevel values' {
            { Write-Log -Message 'Test' -MinLogLevel 'INVALID' -LogPath $TestLogPath -NoConsole } | Should -Throw
        }

        It 'Should reject MaxFileSizeMB less than 1' {
            { Write-Log -Message 'Test' -MaxFileSizeMB 0 -LogPath $TestLogPath -NoConsole } | Should -Throw
        }

        It 'Should reject MaxLogFiles less than 1' {
            { Write-Log -Message 'Test' -MaxLogFiles 0 -LogPath $TestLogPath -NoConsole } | Should -Throw
        }
    }

    Context 'Empty and blank message handling' {
        It 'Should accept empty string message' {
            { Write-Log -Message '' -LogPath $TestLogPath -NoConsole } | Should -Not -Throw
            Test-Path -Path $TestLogPath | Should -Be $true
        }

        It 'Should log timestamp and level for empty message' {
            Write-Log -Message '' -LogPath $TestLogPath -NoConsole
            $content = Get-Content -Path $TestLogPath -Raw
            $content | Should -Match '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} \[INFO\]'
        }

        It 'Should accept null message from pipeline' {
            { $null | Write-Log -LogPath $TestLogPath -NoConsole } | Should -Not -Throw
        }

        It 'Should handle mixed content with blank lines from pipeline' {
            @('Line 1', '', 'Line 3') | Write-Log -LogPath $TestLogPath -NoConsole
            $lines = Get-Content -Path $TestLogPath
            $lines.Count | Should -Be 3
            $lines[0] | Should -Match 'Line 1'
            $lines[1] | Should -Match '\[INFO\]$'
            $lines[2] | Should -Match 'Line 3'
        }
    }
}