Tests/PSMultiLog.tests.ps1

$ParentPath = Split-Path -Path (Split-Path -Path $MyInvocation.MyCommand.Definition -Parent) -Parent
Import-Module -Name (Join-Path -Path $ParentPath -ChildPath "PSMultiLog.psm1")


#-------------------------------------------------------------------------------
# File Logging
#-------------------------------------------------------------------------------
Describe Start-FileLog {
    It "Enables File Logging" {
        InModuleScope PSMultiLog {
            Mock Test-Path { return $false }
            Mock New-Item {}
            Stop-FileLog
            $Script:Settings["File"].Enabled | Should Be $false

            Start-FileLog -FilePath "C:\NotARealFile.log"
            $Script:Settings["File"].Enabled | Should Be $true

            Stop-FileLog
        }
    }

    It "Creates log file" {
        InModuleScope PSMultiLog {
            Mock Test-Path { return $false }
            Mock New-Item {}

            Start-FileLog -FilePath "C:\NotARealFile.log"
            Assert-MockCalled -Scope It New-Item -Exactly 1

            Stop-FileLog
        }
    }

    It "Removes existing file" {
        InModuleScope PSMultiLog {
            Mock Test-Path { return $true }
            Mock Remove-Item {}
            Mock New-Item {}

            Start-FileLog -FilePath "C:\NotARealFile.log"
            Assert-MockCalled -Scope It Remove-Item -Exactly 1

            Stop-FileLog
        }
    }
}

Describe Stop-FileLog {
    It "Disables File Logging" {
        InModuleScope PSMultiLog {
            Mock Test-Path { return $false }
            Mock New-Item {}
            Start-FileLog -FilePath "C:\NotARealFile.log"
            $Script:Settings["File"].Enabled | Should Be $true

            Stop-FileLog
            $Script:Settings["File"].Enabled | Should Be $false
        }
    }
}

Describe Write-FileLog {
    It "Writes to a file" {
        InModuleScope PSMultiLog {
            $Timestamp = Get-Date
            $TimestampString = $Timestamp.ToString("u")
            $InfoEntry = New-Object -TypeName psobject -Property @{
                Timestamp = $Timestamp
                EntryType = "Information"
                LogLevel = 2
                Message = "Hello, World!"
                Exception = $null
                EventId = 1000
            }

            Mock Out-File {}

            Write-FileLog -Entry $InfoEntry -FilePath "C:\NotARealFile.log"
            Assert-MockCalled -Scope It Out-File -Exactly 1
        }
    }
}

#-------------------------------------------------------------------------------
# E-mail Logging
#-------------------------------------------------------------------------------
Describe Start-EmailLog {
    InModuleScope PSMultiLog {
        It "Enables E-mail Logging" {
            $Script:Settings["Email"].Enabled | Should Be $false
            Start-EmailLog
            $Script:Settings["Email"].Enabled | Should Be $true
            Stop-EmailLog
        }

        It "Clears the entry cache" {
            $Script:LogEntries.Count | Should Be 0
            Start-EmailLog
            Write-Log -EntryType Information -Message "Test"
            Stop-EmailLog -RetainEntryCache
            $Script:LogEntries.Count | Should Be 1
            Start-EmailLog -ClearEntryCache
            $Script:LogEntries.Count | Should Be 0
            Stop-EmailLog
        }
    }
}

Describe Stop-EmailLog {
    InModuleScope PSMultiLog {
        It "Disables Email Logging" {
            Start-EmailLog
            $Script:Settings["Email"].Enabled | Should Be $true
            Stop-EmailLog
            $Script:Settings["Email"].Enabled | Should Be $false

        }

        It "Clears the entry cache" {
            Start-EmailLog -ClearEntryCache
            Write-Log -EntryType Information -Message "Test"
            $Script:LogEntries.Count | Should Be 1

            Stop-EmailLog
            $Script:LogEntries.Count | Should Be 0
        }

        It "Retains the entry cache" {
            Start-EmailLog -ClearEntryCache
            Write-Log -EntryType Information -Message "Test"
            $Script:LogEntries.Count | Should Be 1

            Stop-EmailLog -RetainEntryCache
            $Script:LogEntries.Count | Should Be 1

            # Just to make sure the entry cache is cleared.
            Stop-EmailLog
        }
    }
}

Describe Send-EmailLog {
    $SmtpServer = "Fake"
    $To = "Fake"
    $From = "Fake"
    $Subject = "Fake"

    Mock -ModuleName PSMultiLog Send-MailMessage {}

    It "Sends e-mail" {
        Start-EmailLog
        Write-Log -EntryType Error -Message "Test message"    

        Send-EmailLog -SmtpServer $SmtpServer -To $To -From $From -Subject $Subject
        Assert-MockCalled -ModuleName PSMultiLog -Scope It Send-MailMessage -Exactly 1

        Stop-EmailLog
    }

    It "Sends on empty" {
        Send-EmailLog -SmtpServer $SmtpServer -To $To -From $From -Subject $Subject -SendOnEmpty
        Assert-MockCalled -ModuleName PSMultiLog -Scope It Send-MailMessage -Exactly 1
    }

    It "Does not send on empty" {
        Send-EmailLog -SmtpServer $SmtpServer -To $To -From $From -Subject $Subject
        Assert-MockCalled -ModuleName PSMultiLog -Scope It Send-MailMessage -Exactly 0
    }

    It "Triggers based on log level" {
        Start-EmailLog -ClearEntryCache
        Write-Log -EntryType Warning -Message "Test message"

        Send-EmailLog -SmtpServer $SmtpServer -To $To -From $From -Subject $Subject
        Assert-MockCalled -ModuleName PSMultiLog -Scope It Send-MailMessage 0

        Write-Log -EntryType Warning -Message "Test message"
        Send-EmailLog -SmtpServer $SmtpServer -To $To -From $From -Subject $Subject -TriggerLogLevel Warning -SendLogLevel Information
        Assert-MockCalled -ModuleName PSMultiLog -Scope It Send-MailMessage -Exactly 1

        Stop-EmailLog
    }

    It "Retains log entry cache" {
        Start-EmailLog -ClearEntryCache

        Write-Log -EntryType Warning -Message "Test message"
        Send-EmailLog -SmtpServer $SmtpServer -To $To -From $From -Subject $Subject -RetainEntryCache

        InModuleScope PSMultiLog {
            $Script:LogEntries.Count | Should Be 1
        }

        Stop-EmailLog
    }

    It "Empties log entry cache" {
        Start-EmailLog -ClearEntryCache

        Write-Log -EntryType Warning -Message "Test message"
        Send-EmailLog -SmtpServer $SmtpServer -To $To -From $From -Subject $Subject

        InModuleScope PSMultiLog {
            $Script:LogEntries.Count | Should Be 0
        }

        Stop-EmailLog
    }
}

Describe Write-EmailLog {
    InModuleScope PSMultiLog {
        It "Writes to the entry cache" {
            Start-EmailLog -ClearEntryCache
            $Script:LogEntries.Count | Should Be 0

            Write-EmailLog -Entry "Dummy Entry"
            $Script:LogEntries.Count | Should Be 1

            Stop-EmailLog
        }
    }
}

#-------------------------------------------------------------------------------
# Event Log Logging
#-------------------------------------------------------------------------------
Describe Start-EventLogLog {
    Mock -ModuleName PSMultiLog New-EventLog {}
    Mock -ModuleName PSMultiLog Test-EventLogSource { return $true }
    Mock -ModuleName PSMultiLog Get-LogName { return "Application" }

    It "Enables Event Log Logging" {
        InModuleScope PSMultiLog {
            $Script:Settings["EventLog"].Enabled | Should Be $false

            Start-EventLogLog -Source "Fake"
            $Script:Settings["EventLog"].Enabled | Should Be $true

            Stop-EventLogLog
        }
    }

    
    It "Writes an error if source is in wrong log" {
        Mock -ModuleName PSMultiLog Write-Error {}
        Start-EventLogLog -Source "Fake" -LogName "Fake"
        Assert-MockCalled -ModuleName PSMultiLog -Scope It Write-Error -Exactly 1
    }

    It "Creates a new log if necessary" {
        Mock -ModuleName PSMultiLog Test-EventLogSource { return $false }
        Start-EventLogLog -Source "Fake"
        Assert-MockCalled -ModuleName PSMultiLog -Scope It New-EventLog -Exactly 1
    }
}

Describe Stop-EventLogLog {
    Mock -ModuleName PSMultiLog New-EventLog {}
    Mock -ModuleName PSMultiLog Test-EventLogSource { return $true }
    Mock -ModuleName PSMultiLog Get-LogName { return "Application" }

    It "Disables Event Log Logging" {
        InModuleScope PSMultiLog {
            Start-EventLogLog -Source "Fake"
            $Script:Settings["EventLog"].Enabled | Should Be $true

            Stop-EventLogLog
            $Script:Settings["EventLog"].Enabled | Should Be $false
        }
    }
}

Describe Write-EventLogLog {
    It "Writes to the Event Log" {
        InModuleScope PSMultiLog {
            Mock Write-EventLog

            $Timestamp = Get-Date
            $TimestampString = $Timestamp.ToString("u")
            $InfoEntry = New-Object -TypeName psobject -Property @{
                Timestamp = $Timestamp
                EntryType = "Information"
                LogLevel = 2
                Message = "Hello, World!"
                Exception = $null
                EventId = 1000
            }

            Write-EventLogLog -Entry $InfoEntry -LogName "Fake" -Source "Fake"
            Assert-MockCalled -Scope It Write-EventLog -Exactly 1
        }
    }
}

#-------------------------------------------------------------------------------
# Host Logging
#-------------------------------------------------------------------------------
Describe Start-HostLog {
    InModuleScope PSMultiLog {
        It "Enables Host Logging" {
            $Script:Settings["Host"].Enabled | Should Be $false
            Start-HostLog
            $Script:Settings["Host"].Enabled | Should Be $true
        }

        It "Sets the log level" {
            Start-HostLog
            $Script:Settings["Host"].LogLevel | Should Be 0

            Start-HostLog -LogLevel "Error"
            $Script:Settings["Host"].LogLevel | Should Be 0

            Start-HostLog -LogLevel "Warning"
            $Script:Settings["Host"].LogLevel | Should Be 1

            Start-HostLog -LogLevel "Information"
            $Script:Settings["Host"].LogLevel | Should Be 2
        }
    }
}

Describe Stop-HostLog {
    InModuleScope PSMultiLog {
        It "Disables Host Logging" {
            Start-HostLog
            $Script:Settings["Host"].Enabled | Should Be $true

            Stop-HostLog
            $Script:Settings["Host"].Enabled | Should Be $false
        }
    }
}

Describe Write-HostLog {
    InModuleScope PSMultiLog {
        $Timestamp = Get-Date
        $TimestampString = $Timestamp.ToString("u")
        $Entry = New-Object -TypeName psobject -Property @{
            Timestamp = $Timestamp
            EntryType = "Information"
            LogLevel = 2
            Message = "Hello, World!"
            Exception = $null
            EventId = 1000
        }

        It "Writes to the host" {
            Mock Write-Host {}
            Write-HostLog -Entry $Entry
            Assert-MockCalled -Scope It Write-Host -Exactly 3
        }
    }
}

#-------------------------------------------------------------------------------
# PassThru Logging
#-------------------------------------------------------------------------------
Describe Start-PassThruLog {
    InModuleScope PSMultiLog {
        It "Enables PassThru Logging" {
            $Script:Settings["PassThru"].Enabled | Should Be $false
            Start-PassThruLog
            $Script:Settings["PassThru"].Enabled | Should Be $true
        }

        It "Sets the log level" {
            Start-PassThruLog
            $Script:Settings["PassThru"].LogLevel | Should Be 0

            Start-PassThruLog -LogLevel "Error"
            $Script:Settings["PassThru"].LogLevel | Should Be 0

            Start-PassThruLog -LogLevel "Warning"
            $Script:Settings["PassThru"].LogLevel | Should Be 1

            Start-PassThruLog -LogLevel "Information"
            $Script:Settings["PassThru"].LogLevel | Should Be 2
        }
    }
}

Describe Stop-PassThruLog {
    InModuleScope PSMultiLog {
        It "Disables PassThru Logging" {
            Start-PassThruLog
            $Script:Settings["PassThru"].Enabled | Should Be $true

            Stop-PassThruLog
            $Script:Settings["PassThru"].Enabled | Should Be $false
        }
    }
}

Describe Write-PassThruLog {
    InModuleScope PSMultiLog {
        $Timestamp = Get-Date
        $TimestampString = $Timestamp.ToString("u")
        $InfoEntry = New-Object -TypeName psobject -Property @{
            Timestamp = $Timestamp
            EntryType = "Information"
            LogLevel = 2
            Message = "Hello, World!"
            Exception = $null
            EventId = 1000
        }

        $WarningEntry = New-Object -TypeName psobject -Property @{
            Timestamp = $Timestamp
            EntryType = "Warning"
            LogLevel = 1
            Message = "This is a warning."
            Exception = $null
            EventId = 1000
        }

        $ErrorEntry = New-Object -TypeName psobject -Property @{
            Timestamp = $Timestamp
            EntryType = "Error"
            LogLevel = 0
            Message = "This is an error."
            Exception = New-Object -TypeName Exception -ArgumentList "Exception message."
            EventId = 1000
        }

        $ErrorEntryNoException = New-Object -TypeName psobject -Property @{
            Timestamp = $Timestamp
            EntryType = "Error"
            LogLevel = 0
            Message = "This is an error."
            Exception = $null
            EventId = 1000
        }

        It "Writes to the Verbose or Information stream" {
            Mock Write-Verbose {}
            Mock Write-Information {}
            Write-PassThruLog -Entry $InfoEntry

            if ($PSVersionTable -and $PSVersionTable.PSVersion.Major -ge 5) {
                Assert-MockCalled -Scope It Write-Information -Exactly 1
                Assert-MockCalled -Scope It Write-Verbose -Exactly 0
            } else {
                Assert-MockCalled -Scope It Write-Information -Exactly 0
                Assert-MockCalled -Scope It Write-Verbose -Exactly 1
            }
        }

        It "Writes to the Warning stream" {
            Mock Write-Warning {}
            Write-PassThruLog -Entry $WarningEntry
            Assert-MockCalled -Scope It Write-Warning -Exactly 1
        }

        It "Writes to the Error stream" {
            Mock Write-Error {}
            Write-PassThruLog -Entry $ErrorEntry
            Assert-MockCalled -Scope It Write-Error -Exactly 1
        }

        It "Writes to the Error stream when no Exception is included" {
            Mock Write-Error {}
            Write-PassThruLog -Entry $ErrorEntryNoException
            Assert-MockCalled -Scope It Write-Error -Exactly 1
        }
    }
}

#-------------------------------------------------------------------------------
# Slack Logging
#-------------------------------------------------------------------------------
Describe Start-SlackLog {
    It "Enables Slack Logging" {
        InModuleScope PSMultiLog {
            Stop-SlackLog
            $Script:Settings["Slack"].Enabled | Should Be $false

            Start-SlackLog -Uri "https://FakeUrl"
            $Script:Settings["Slack"].Enabled | Should Be $true
        }
    }
}

Describe Stop-SlackLog {
    It "Disables Slack Logging" {
        InModuleScope PSMultiLog {
            Start-SlackLog -Uri "https://FakeUrl"
            $Script:Settings["Slack"].Enabled | Should Be $true

            Stop-SlackLog
            $Script:Settings["Slack"].Enabled | Should Be $false
        }
    }
}

Describe Write-SlackLog {
    It "Calls the Slack webhook" {
        InModuleScope PSMultiLog {
            Mock Invoke-WebRequest {}

            Start-SlackLog -Uri "https://FakeUrl"

            Write-Log -EntryType Error -Message "Test Error"
            Assert-MockCalled -Scope It Invoke-WebRequest -Exactly 1

            Stop-SlackLog
        }
    }
}

#-------------------------------------------------------------------------------
# Write-Log, the main event
#-------------------------------------------------------------------------------
Describe Write-Log {
    Mock -ModuleName PSMultiLog Write-FileLog {}
    Mock -ModuleName PSMultiLog Write-EventLogLog {}
    Mock -ModuleName PSMultiLog Write-EmailLog {}
    Mock -ModuleName PSMultiLog Write-HostLog {}
    Mock -ModuleName PSMultiLog Write-PassThruLog {}

    It "Calls Write- Cmdlet for enable logging methods" {
        Write-Log -EntryType Information -Message "Test message"
        Assert-MockCalled -ModuleName PSMultiLog -Scope It Write-FileLog 0
        Assert-MockCalled -ModuleName PSMultiLog -Scope It Write-EventLogLog 0
        Assert-MockCalled -ModuleName PSMultiLog -Scope It Write-EmailLog 0
        Assert-MockCalled -ModuleName PSMultiLog -Scope It Write-HostLog 0
        Assert-MockCalled -ModuleName PSMultiLog -Scope It Write-PassThruLog 0

        Mock -ModuleNae PSMultiLog Write-Verbose {}
        Mock -ModuleName PSMultiLog Write-Host {}
        Start-EmailLog
        Start-HostLog -LogLevel Information
        Start-PassThruLog -LogLevel Information

        Write-Log -EntryType Information -Message "Test message"
        Assert-MockCalled -ModuleName PSMultiLog -Scope It Write-FileLog 0
        Assert-MockCalled -ModuleName PSMultiLog -Scope It Write-EventLogLog 0
        Assert-MockCalled -ModuleName PSMultiLog -Scope It Write-EmailLog -Exactly 1
        Assert-MockCalled -ModuleName PSMultiLog -Scope It Write-HostLog -Exactly 1
        Assert-MockCalled -ModuleName PSMultiLog -Scope It Write-PassThruLog -Exactly 1
    }
}

#-------------------------------------------------------------------------------
# Internal Functions
#-------------------------------------------------------------------------------
Describe Get-LogLevel {
    InModuleScope PSMultiLog {
        It "Returns expected values" {
            Get-LogLevel -EntryType "Information" | Should Be 2
            Get-LogLevel -EntryType "Warning" | Should Be 1
            Get-LogLevel -EntryType "Error" | Should Be 0
            Get-LogLevel -EntryType "None" | Should Be -1
        }

        It "Throws on invalid EntryType" {
            {Get-LogLevel -EntryType "Foo"} | Should Throw
        }
    }
}

Describe Format-LogMessage {
    InModuleScope PSMultiLog {
        $Timestamp = Get-Date
        $TimestampString = $Timestamp.ToString("u")
        $InfoEntry = New-Object -TypeName psobject -Property @{
            Timestamp = $Timestamp
            EntryType = "Information"
            LogLevel = 2
            Message = "Hello, World!"
            Exception = $null
            EventId = 1000
        }

        $ErrorEntry = New-Object -TypeName psobject -Property @{
            Timestamp = $Timestamp
            EntryType = "Error"
            LogLevel = 0
            Message = "This is an error."
            Exception = New-Object -TypeName Exception -ArgumentList "Exception message."
            EventId = 1000
        }

    
        It "Formats a message" {
            Format-LogMessage -Entry $InfoEntry | Should BeExactly "[$TimestampString] - Hello, World!"
        }

        It "Includes Exception information" {
            Format-LogMessage -Entry $ErrorEntry -Exception | Should BeExactly "[$TimestampString] - This is an error. - Exception: Exception message."
        }

        It "Includes type information" {
            Format-LogMessage -Entry $InfoEntry -Type | Should BeExactly "[$TimestampString] - INFO - Hello, World!"
        }

        It "Includes type and Exception information" {
            Format-LogMessage -Entry $ErrorEntry -Type -Exception | Should BeExactly "[$TimestampString] - ERRR - This is an error. - Exception: Exception message."
        }
    }
}

Describe ConvertTo-HtmlUnorderedList {
    InModuleScope PSMultiLog {
        $Objects = @("One", "Two", "Three")

        It "Performs basic formatting" {
            ConvertTo-HtmlUnorderedList -InputObject $Objects | Should BeExactly "<ul>`n<li>One</li>`n<li>Two</li>`n<li>Three</li>`n</ul>`n"
        }

        It "Executes a ScriptBlock to perform formatting" {
            ConvertTo-HtmlUnorderedList -FormatScript {Param($s) "TEST! $s"} -InputObject $Objects | Should BeExactly "<ul>`n<li>TEST! One</li>`n<li>TEST! Two</li>`n<li>TEST! Three</li>`n</ul>`n"
        }
    }
}

Remove-Module PSMultiLog