Tests/Public/Select-IntegrityReportFile.Tests.ps1

BeforeAll {
    $modulePath = "$PSScriptRoot/../.."
    Get-ChildItem -Path "$modulePath/Private/*.ps1" | ForEach-Object { . $_.FullName }
    Get-ChildItem -Path "$modulePath/Public/*.ps1"  | ForEach-Object { . $_.FullName }
}

Describe 'Select-IntegrityReportFile' {
    BeforeAll {
        $testDir = Join-Path ([System.IO.Path]::GetTempPath()) "SelectTests_$([guid]::NewGuid().ToString('N'))"
        New-Item -ItemType Directory -Path $testDir -Force | Out-Null

        # Create a sample CSV report
        $csvPath = Join-Path $testDir 'test-report.csv'
        @(
            '"RelativePath","Status","SourceSize","DestSize","SourceMD5","DestMD5","Error"'
            '"docs\readme.txt","OK","1024","1024","abc","abc",""'
            '"data\file1.xlsx","ByteMismatch","2048","2048","abc","def",""'
            '"data\file2.docx","SizeMismatch","1024","512","","",""'
            '"data\file3.pdf","HashMismatch","4096","4096","abc","xyz",""'
            '"backup\old.zip","MissingOnDestination","8192","","","",""'
            '"temp\extra.log","MissingOnSource","","512","","",""'
            '"data\subdir\report.xlsx","ByteMismatch","3072","3072","abc","ghi",""'
        ) | Set-Content -Path $csvPath -Encoding utf8
    }

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

    BeforeEach {
        Mock Write-Host {}
        Mock Write-Warning {}
    }

    Context 'Default status filter' {
        It 'Returns remediable statuses by default' {
            $result = @(Select-IntegrityReportFile -ReportPath $csvPath)
            # ByteMismatch(2) + SizeMismatch(1) + HashMismatch(1) + MissingOnDestination(1) = 5
            $result.Count | Should -Be 5
        }

        It 'Does not include OK or MissingOnSource by default' {
            $result = @(Select-IntegrityReportFile -ReportPath $csvPath)
            $result | Should -Not -Contain 'docs\readme.txt'
            $result | Should -Not -Contain 'temp\extra.log'
        }
    }

    Context 'Specific status filter' {
        It 'Returns only ByteMismatch files when specified' {
            $result = @(Select-IntegrityReportFile -ReportPath $csvPath -Status 'ByteMismatch')
            $result.Count | Should -Be 2
            $result | Should -Contain 'data\file1.xlsx'
            $result | Should -Contain 'data\subdir\report.xlsx'
        }

        It 'Returns OK files when explicitly asked' {
            $result = @(Select-IntegrityReportFile -ReportPath $csvPath -Status 'OK')
            $result.Count | Should -Be 1
            $result[0] | Should -Be 'docs\readme.txt'
        }

        It 'Returns multiple statuses' {
            $result = @(Select-IntegrityReportFile -ReportPath $csvPath -Status 'ByteMismatch', 'SizeMismatch')
            $result.Count | Should -Be 3
        }
    }

    Context 'FilterPath mode' {
        It 'Filters by literal substring (case-insensitive)' {
            $result = @(Select-IntegrityReportFile -ReportPath $csvPath -Status 'ByteMismatch' -FilterPath 'subdir')
            $result.Count | Should -Be 1
            $result[0] | Should -Be 'data\subdir\report.xlsx'
        }

        It 'Returns nothing when substring does not match' {
            $result = @(Select-IntegrityReportFile -ReportPath $csvPath -Status 'ByteMismatch' -FilterPath 'nonexistent')
            $result.Count | Should -Be 0
        }
    }

    Context 'FilterRegex mode' {
        It 'Filters by regex pattern' {
            $result = @(Select-IntegrityReportFile -ReportPath $csvPath -FilterRegex '\.xlsx$')
            $result.Count | Should -Be 2
        }

        It 'Is case-insensitive by default' {
            $result = @(Select-IntegrityReportFile -ReportPath $csvPath -FilterRegex '\.XLSX$')
            $result.Count | Should -Be 2
        }
    }

    Context 'IncludePaths mode' {
        It 'Returns only explicitly listed paths' {
            $result = @(Select-IntegrityReportFile -ReportPath $csvPath -IncludePaths @('data\file1.xlsx'))
            $result.Count | Should -Be 1
            $result[0] | Should -Be 'data\file1.xlsx'
        }

        It 'Warns about paths not found in report' {
            Select-IntegrityReportFile -ReportPath $csvPath -IncludePaths @('does\not\exist.txt') | Out-Null
            Should -Invoke Write-Warning -ParameterFilter { $Message -like '*not found*' }
        }
    }

    Context 'Mutually exclusive filter modes' {
        It 'Throws when multiple filter modes are specified' {
            { Select-IntegrityReportFile -ReportPath $csvPath -FilterPath 'data' -FilterRegex '\.xlsx$' } | Should -Throw '*Only one of*'
        }
    }

    Context 'Empty results' {
        It 'Returns nothing for a status with no matches' {
            $result = @(Select-IntegrityReportFile -ReportPath $csvPath -Status 'CheckError')
            $result.Count | Should -Be 0
        }
    }

    Context 'Semicolon-delimited report' {
        BeforeAll {
            $semiCsvPath = Join-Path $testDir 'semi-report.csv'
            @(
                '"RelativePath";"Status";"SourceSize";"DestSize";"SourceMD5";"DestMD5";"Error"'
                '"data\file1.xlsx";"ByteMismatch";"2048";"2048";"abc";"def";""'
            ) | Set-Content -Path $semiCsvPath -Encoding utf8
        }

        It 'Detects and parses semicolon delimiter' {
            $result = @(Select-IntegrityReportFile -ReportPath $semiCsvPath -Status 'ByteMismatch')
            $result.Count | Should -Be 1
        }
    }

    Context 'Empty report file' {
        BeforeAll {
            $emptyReportPath = Join-Path $testDir 'empty-report.csv'
            @(
                '"RelativePath","Status","SourceSize","DestSize","SourceMD5","DestMD5","Error"'
            ) | Set-Content -Path $emptyReportPath -Encoding utf8
        }

        It 'Returns nothing when CSV has only a header row' {
            $result = @(Select-IntegrityReportFile -ReportPath $emptyReportPath)
            $result.Count | Should -Be 0
            Should -Invoke Write-Host -ParameterFilter { $Object -like '*empty*' }
        }
    }

    Context 'Interactive mode' {
        BeforeAll {
            # Define a dummy function so Pester can mock it (it doesn't exist locally)
            function Out-ConsoleGridView { param([Parameter(ValueFromPipeline)][object]$InputObject, [string]$Title, [string]$OutputMode) }
        }

        It 'Throws when neither Out-ConsoleGridView nor Out-GridView is available' {
            Mock Get-Command { $null }
            { Select-IntegrityReportFile -ReportPath $csvPath -Interactive } | Should -Throw '*requires*'
        }

        It 'Returns nothing when user picks no items in grid view' {
            Mock Get-Command { [PSCustomObject]@{ Name = 'Out-ConsoleGridView' } } -ParameterFilter { $Name -eq 'Out-ConsoleGridView' }
            Mock Out-ConsoleGridView { $input | Out-Null; return }
            $result = @(Select-IntegrityReportFile -ReportPath $csvPath -Interactive)
            $result.Count | Should -Be 0
            Should -Invoke Write-Host -ParameterFilter { $Object -like '*No files selected*' }
        }

        It 'Returns only user-selected files from grid view' {
            Mock Get-Command { [PSCustomObject]@{ Name = 'Out-ConsoleGridView' } } -ParameterFilter { $Name -eq 'Out-ConsoleGridView' }
            Mock Out-ConsoleGridView {
                [PSCustomObject]@{ RelativePath = 'data\file1.xlsx'; Status = 'ByteMismatch'; SourceSize = '2048'; DestSize = '2048' }
            }
            $result = @(Select-IntegrityReportFile -ReportPath $csvPath -Interactive)
            $result.Count | Should -Be 1
            $result[0] | Should -Be 'data\file1.xlsx'
        }
    }

    Context 'No files matched after path filter' {
        It 'Returns nothing when FilterPath removes all status-matched rows' {
            $result = @(Select-IntegrityReportFile -ReportPath $csvPath -Status 'ByteMismatch' -FilterPath 'ZZZZZ_nonexistent')
            $result.Count | Should -Be 0
            Should -Invoke Write-Host -ParameterFilter { $Object -like '*No files matched*' }
        }

        It 'Returns nothing when FilterRegex removes all rows' {
            $result = @(Select-IntegrityReportFile -ReportPath $csvPath -Status 'ByteMismatch' -FilterRegex '^IMPOSSIBLE_PATTERN_999$')
            $result.Count | Should -Be 0
            Should -Invoke Write-Host -ParameterFilter { $Object -like '*No files matched*' }
        }
    }
}