Tests/PSWatchdog.Tests.ps1

# PSWatchdog.Tests.ps1
# Pester tests for PSWatchdog module

BeforeAll {
    # Import the module - handle both Windows and Linux paths
    $ModulePath = if ($PSScriptRoot) {
        Join-Path $PSScriptRoot "..\PSWatchdog.psm1"
    } else {
        Join-Path $PSCommandPath "..\PSWatchdog.psm1"
    }
    Import-Module $ModulePath -Force
    
    # Store captured events for testing
    $script:CapturedEvents = @()
    
    # Setup test directory
    $script:TestDir = Join-Path $env:TEMP "PSWatchdog_Test_$(Get-Random)"
    New-Item -ItemType Directory -Path $script:TestDir -Force | Out-Null
}

AfterAll {
    # Clean up test directory
    if (Test-Path $script:TestDir) {
        Remove-Item -Path $script:TestDir -Recurse -Force -ErrorAction SilentlyContinue
    }
    
    # Ensure all watchers are stopped
    Stop-AllFileWatchers -ErrorAction SilentlyContinue
    
    # Remove module
    Remove-Module PSWatchdog -ErrorAction SilentlyContinue
}

Describe "PSWatchdog Module Tests" {
    
    Context "Module Loading" {
        It "Should load the module successfully" {
            $module = Get-Module -Name PSWatchdog
            $module | Should -Not -BeNullOrEmpty
        }
        
        It "Should export all required functions" {
            $functions = Get-Command -Module PSWatchdog | Select-Object -ExpandProperty Name
            $functions | Should -Contain 'Start-FileWatcher'
            $functions | Should -Contain 'Get-FileWatcher'
            $functions | Should -Contain 'Stop-FileWatcher'
            $functions | Should -Contain 'Stop-AllFileWatchers'
        }
    }
    
    Context "Start-FileWatcher Parameter Validation" {
        It "Should require Path parameter" {
            { Start-FileWatcher -Path "" -ErrorAction Stop } | Should -Throw
        }
        
        It "Should require existing path" {
            { Start-FileWatcher -Path "C:\NonExistentPath_12345" -ErrorAction Stop } | Should -Throw
        }
        
        It "Should accept valid path" {
            $result = Start-FileWatcher -Path $script:TestDir -ErrorAction Stop
            $result | Should -Not -BeNullOrEmpty
            $result.Path | Should -Be $script:TestDir
            Stop-FileWatcher -Id $result.Id
        }
        
        It "Should have default filter of *.*" {
            $result = Start-FileWatcher -Path $script:TestDir -ErrorAction Stop
            $result.Filter | Should -Be "*.*"
            Stop-FileWatcher -Id $result.Id
        }
        
        It "Should accept custom filter" {
            $result = Start-FileWatcher -Path $script:TestDir -Filter "*.txt" -ErrorAction Stop
            $result.Filter | Should -Be "*.txt"
            Stop-FileWatcher -Id $result.Id
        }
        
        It "Should accept ChangeType parameter" {
            $result = Start-FileWatcher -Path $script:TestDir -ChangeType Created -ErrorAction Stop
            $result.ChangeType | Should -Be "Created"
            Stop-FileWatcher -Id $result.Id
        }
        
        It "Should accept multiple ChangeType values" {
            $result = Start-FileWatcher -Path $script:TestDir -ChangeType Created, Changed -ErrorAction Stop
            $result.ChangeType.Count | Should -Be 2
            Stop-FileWatcher -Id $result.Id
        }
        
        It "Should accept IncludeSubdirectories switch" {
            $result = Start-FileWatcher -Path $script:TestDir -IncludeSubdirectories -ErrorAction Stop
            $result.IncludeSubdirectories | Should -Be $true
            Stop-FileWatcher -Id $result.Id
        }
        
        It "Should return object with required properties" {
            $result = Start-FileWatcher -Path $script:TestDir -ErrorAction Stop
            $result.Id | Should -Not -BeNullOrEmpty
            $result.Watcher | Should -Not -BeNullOrEmpty
            Stop-FileWatcher -Id $result.Id
        }
        
        It "Should set InternalBufferSize to 65536" {
            $result = Start-FileWatcher -Path $script:TestDir -ErrorAction Stop
            $result.Watcher.InternalBufferSize | Should -Be 65536
            Stop-FileWatcher -Id $result.Id
        }
        
        It "Should enable events by default" {
            $result = Start-FileWatcher -Path $script:TestDir -ErrorAction Stop
            $result.Watcher.EnableRaisingEvents | Should -Be $true
            Stop-FileWatcher -Id $result.Id
        }
    }
    
    Context "Start-FileWatcher Action ScriptBlock" {
        It "Should accept Action parameter" {
            $action = { param($event) }
            $watcher = Start-FileWatcher -Path $script:TestDir -Filter "*.txt" -ChangeType Created -Action $action -ErrorAction Stop
            $watcher | Should -Not -BeNullOrEmpty
            Stop-FileWatcher -Id $watcher.Id
        }
        
        It "Should work without Action parameter" {
            $watcher = Start-FileWatcher -Path $script:TestDir -Filter "*.txt" -ChangeType Created -ErrorAction Stop
            $watcher.Action | Should -BeNullOrEmpty
            Stop-FileWatcher -Id $watcher.Id
        }
    }
    
    Context "Get-FileWatcher Tests" {
        It "Should return empty when no watchers active" {
            Stop-AllFileWatchers -ErrorAction SilentlyContinue
            $result = Get-FileWatcher
            $result.Count | Should -Be 0
        }
        
        It "Should return all active watchers" {
            $w1 = Start-FileWatcher -Path $script:TestDir -Filter "*.txt" -ErrorAction Stop
            $w2 = Start-FileWatcher -Path $script:TestDir -Filter "*.log" -ErrorAction Stop
            $result = Get-FileWatcher
            $result.Count | Should -BeGreaterOrEqual 2
            Stop-AllFileWatchers
        }
        
        It "Should return watcher with correct properties" {
            $watcher = Start-FileWatcher -Path $script:TestDir -Filter "*.txt" -ErrorAction Stop
            $result = Get-FileWatcher | Where-Object { $_.Id -eq $watcher.Id }
            $result | Should -Not -BeNullOrEmpty
            $result.Filter | Should -Be "*.txt"
            Stop-FileWatcher -Id $watcher.Id
        }
        
        It "Should update list when watcher is stopped" {
            $watcher = Start-FileWatcher -Path $script:TestDir -ErrorAction Stop
            $before = @(Get-FileWatcher).Count
            Stop-FileWatcher -Id $watcher.Id
            Start-Sleep -Milliseconds 100
            $after = @(Get-FileWatcher).Count
            $after | Should -Be ($before - 1)
        }
    }
    
    Context "Stop-FileWatcher Tests" {
        It "Should stop watcher by Id" {
            $watcher = Start-FileWatcher -Path $script:TestDir -ErrorAction Stop
            $result = Stop-FileWatcher -Id $watcher.Id
            $result.Status | Should -Be "Stopped"
        }
        
        It "Should stop watcher by Path" {
            $watcher = Start-FileWatcher -Path $script:TestDir -ErrorAction Stop
            $result = Stop-FileWatcher -Path $script:TestDir
            $result.Status | Should -Be "Stopped"
        }
        
        It "Should throw error for non-existent Id" {
            { Stop-FileWatcher -Id "NonExistentId_12345" -ErrorAction Stop } | Should -Throw
        }
        
        It "Should throw error for non-existent Path" {
            { Stop-FileWatcher -Path "C:\NonExistentPath_12345" -ErrorAction Stop } | Should -Throw
        }
    }
    
    Context "Stop-AllFileWatchers Tests" {
        It "Should stop all active watchers" {
            Start-FileWatcher -Path $script:TestDir -Filter "*.txt" -ErrorAction Stop | Out-Null
            Start-FileWatcher -Path $script:TestDir -Filter "*.log" -ErrorAction Stop | Out-Null
            $result = Stop-AllFileWatchers
            $result.Count | Should -BeGreaterOrEqual 2
        }
        
        It "Should return zero count when no watchers active" {
            Stop-AllFileWatchers -ErrorAction SilentlyContinue
            $result = Stop-AllFileWatchers
            $result.Count | Should -Be 0
        }
    }
    
    Context "Integration Tests" {
        It "Should handle multiple watchers on same path" {
            Start-FileWatcher -Path $script:TestDir -Filter "*.txt" -ErrorAction Stop | Out-Null
            Start-FileWatcher -Path $script:TestDir -Filter "*.log" -ErrorAction Stop | Out-Null
            $all = Get-FileWatcher
            $all.Count | Should -BeGreaterOrEqual 2
            Stop-AllFileWatchers
        }
        
        It "Should handle IncludeSubdirectories flag" {
            $watcher = Start-FileWatcher -Path $script:TestDir -Filter "*.txt" -IncludeSubdirectories -ErrorAction Stop
            $watcher.IncludeSubdirectories | Should -Be $true
            Stop-FileWatcher -Id $watcher.Id
        }
        
        It "Should create watcher with all ChangeType options" {
            $watcher = Start-FileWatcher -Path $script:TestDir -ChangeType Created, Changed, Deleted, Renamed -ErrorAction Stop
            $watcher.ChangeType.Count | Should -Be 4
            Stop-FileWatcher -Id $watcher.Id
        }
    }
    
    Context "Error Handling" {
        It "Should handle invalid path gracefully" {
            { Start-FileWatcher -Path "C:\" -ErrorAction Stop } | Should -Not -Throw
        }
        
        It "Should handle module reload correctly" {
            $watcher = Start-FileWatcher -Path $script:TestDir -ErrorAction Stop
            $reloadPath = if ($PSScriptRoot) { Join-Path $PSScriptRoot "..\PSWatchdog.psm1" } else { Join-Path $PSCommandPath "..\PSWatchdog.psm1" }
            Import-Module $reloadPath -Force
            Stop-FileWatcher -Id $watcher.Id -ErrorAction SilentlyContinue
        }
    }
}