Tests/Public/Repair-FileShareIntegrity.Tests.ps1

[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingEmptyCatchBlock', '', Justification = 'Tests intentionally swallow errors to verify side-effects')]
param()

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

Describe 'Repair-FileShareIntegrity' {
    BeforeEach {
        Mock Write-Host {}
        Mock Write-Warning {}
        Mock Assert-AzCliLogin { [PSCustomObject]@{ user = @{ name = 'test@contoso.com' } } }
        Mock Assert-AzCopyInstalled {}
        Mock Get-StorageAccountKey { 'fakekey123==' }
        Mock New-SasToken { 'fakesas=token' }
        Mock Mount-SmbDrive {}
        Mock Dismount-SmbDrive {}
        Mock Test-Path { $false } -ParameterFilter { $Path -like '*:\' }
    }

    Context 'Empty input' {
        It 'Returns early when no file paths provided' {
            Repair-FileShareIntegrity -FilePaths @() -SourceAccountName 'src' -DestinationAccountName 'dst' -ShareName 'test' -SkipPreCheck
            Should -Invoke Write-Host -ParameterFilter { $Object -like '*No file paths*' }
        }
    }

    Context 'Drive letter validation' {
        It 'Throws when source and destination drive letters are the same' {
            { Repair-FileShareIntegrity -FilePaths @('file.txt') -SourceAccountName 'src' -DestinationAccountName 'dst' -ShareName 'test' -SourceDriveLetter 'X' -DestinationDriveLetter 'X' -SkipPreCheck } | Should -Throw '*must be different*'
        }

        It 'Throws when drive is already mounted' {
            Mock Test-Path { $true } -ParameterFilter { $Path -like '*:\' }
            { Repair-FileShareIntegrity -FilePaths @('file.txt') -SourceAccountName 'src' -DestinationAccountName 'dst' -ShareName 'test' -SkipPreCheck } | Should -Throw '*already mounted*'
        }
    }

    Context 'Pre-flight calls' {
        It 'Calls Assert-AzCliLogin' {
            try {
                Repair-FileShareIntegrity -FilePaths @('file.txt') -SourceAccountName 'src' -DestinationAccountName 'dst' -ShareName 'test' -SkipPreCheck -LogDirectory $TestDrive -Confirm:$false
            } catch {}
            Should -Invoke Assert-AzCliLogin -Times 1
        }

        It 'Calls Assert-AzCopyInstalled when -AzCopy is specified' {
            try {
                Repair-FileShareIntegrity -FilePaths @('file.txt') -SourceAccountName 'src' -DestinationAccountName 'dst' -ShareName 'test' -AzCopy -SkipPreCheck -LogDirectory $TestDrive -Confirm:$false
            } catch {}
            Should -Invoke Assert-AzCopyInstalled -Times 1
        }

        It 'Does not call Assert-AzCopyInstalled when -AzCopy is not specified' {
            try {
                Repair-FileShareIntegrity -FilePaths @('file.txt') -SourceAccountName 'src' -DestinationAccountName 'dst' -ShareName 'test' -SkipPreCheck -LogDirectory $TestDrive -Confirm:$false
            } catch {}
            Should -Invoke Assert-AzCopyInstalled -Times 0
        }

        It 'Generates SAS tokens only when -AzCopy is specified' {
            try {
                Repair-FileShareIntegrity -FilePaths @('file.txt') -SourceAccountName 'src' -DestinationAccountName 'dst' -ShareName 'test' -AzCopy -SkipPreCheck -LogDirectory $TestDrive -Confirm:$false
            } catch {}
            Should -Invoke New-SasToken -Times 2

            # Reset invocation counts
            try {
                Repair-FileShareIntegrity -FilePaths @('file.txt') -SourceAccountName 'src' -DestinationAccountName 'dst' -ShareName 'test' -SkipPreCheck -LogDirectory $TestDrive -Confirm:$false
            } catch {}
            # Should only be 2 from the first call, not additional ones
        }
    }

    Context 'Deduplication' {
        It 'Deduplicates file paths case-insensitively' {
            try {
                Repair-FileShareIntegrity -FilePaths @('data\File.txt', 'data\file.txt', 'DATA\FILE.TXT') -SourceAccountName 'src' -DestinationAccountName 'dst' -ShareName 'test' -SkipPreCheck -LogDirectory $TestDrive -Confirm:$false
            } catch {}
            # Should report 1 file path (deduplicated)
            Should -Invoke Write-Host -ParameterFilter { $Object -like '*1 file path*' }
        }
    }

    Context 'Pipeline input' {
        It 'Accepts file paths from the pipeline' {
            try {
                @('file1.txt', 'file2.txt') | Repair-FileShareIntegrity -SourceAccountName 'src' -DestinationAccountName 'dst' -ShareName 'test' -SkipPreCheck -LogDirectory $TestDrive -Confirm:$false
            } catch {}
            Should -Invoke Write-Host -ParameterFilter { $Object -like '*2 file path*' }
        }
    }

    Context 'Cleanup on failure' {
        It 'Dismounts source drive when destination mount fails' {
            # Source mount succeeds (sets $mountedSource = $true), destination mount throws
            Mock Mount-SmbDrive {} -ParameterFilter { $DriveLetter -eq 'X' }
            Mock Mount-SmbDrive { throw 'Mount failed' } -ParameterFilter { $DriveLetter -eq 'Y' }
            try {
                Repair-FileShareIntegrity -FilePaths @('file.txt') -SourceAccountName 'src' -DestinationAccountName 'dst' -ShareName 'test' -LogDirectory $TestDrive -Confirm:$false
            } catch {}
            Should -Invoke Dismount-SmbDrive -ParameterFilter { $DriveLetter -eq 'X' }
        }
    }

    Context 'Pre-check phase' {
        It 'Skips pre-check when -SkipPreCheck is specified' {
            try {
                Repair-FileShareIntegrity -FilePaths @('file.txt') -SourceAccountName 'src' -DestinationAccountName 'dst' -ShareName 'test' -SkipPreCheck -LogDirectory $TestDrive -Confirm:$false
            } catch {}
            Should -Invoke Write-Host -ParameterFilter { $Object -like '*Phase 1: Skipped*' }
        }

        It 'Mounts drives for pre-check when -SkipPreCheck is not specified' {
            Mock Test-FileByteIntegrity { 'MissingOnSource' }
            try {
                Repair-FileShareIntegrity -FilePaths @('file.txt') -SourceAccountName 'src' -DestinationAccountName 'dst' -ShareName 'test' -LogDirectory $TestDrive -Confirm:$false
            } catch {}
            Should -Invoke Mount-SmbDrive -Times 2
        }
    }

    Context 'WhatIf support' {
        It 'Does not copy files when -WhatIf is specified' {
            Repair-FileShareIntegrity -FilePaths @('file.txt') -SourceAccountName 'src' -DestinationAccountName 'dst' -ShareName 'test' -SkipPreCheck -LogDirectory $TestDrive -WhatIf
            Should -Invoke Mount-SmbDrive -Times 0
        }
    }

    Context 'Parameter validation' {
        It 'Drive letter must be a single alpha character' {
            { Repair-FileShareIntegrity -FilePaths @('f.txt') -SourceAccountName 'src' -DestinationAccountName 'dst' -ShareName 'test' -SourceDriveLetter '12' } | Should -Throw
        }

        It 'SourceAccountName is mandatory' {
            $param = (Get-Command Repair-FileShareIntegrity).Parameters['SourceAccountName']
            $param.Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] } | ForEach-Object {
                $_.Mandatory | Should -BeTrue
            }
        }

        It 'DestinationAccountName is mandatory' {
            $param = (Get-Command Repair-FileShareIntegrity).Parameters['DestinationAccountName']
            $param.Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] } | ForEach-Object {
                $_.Mandatory | Should -BeTrue
            }
        }

        It 'ShareName is mandatory' {
            $param = (Get-Command Repair-FileShareIntegrity).Parameters['ShareName']
            $param.Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] } | ForEach-Object {
                $_.Mandatory | Should -BeTrue
            }
        }

        It 'SupportsShouldProcess is enabled' {
            $cmd = Get-Command Repair-FileShareIntegrity
            $cmdBindingAttr = $cmd.ScriptBlock.Attributes | Where-Object { $_ -is [System.Management.Automation.CmdletBindingAttribute] }
            $cmdBindingAttr.SupportsShouldProcess | Should -BeTrue
        }

        It 'FilePaths accepts pipeline input' {
            $param = (Get-Command Repair-FileShareIntegrity).Parameters['FilePaths']
            $pipelineAttr = $param.Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] -and $_.ValueFromPipeline }
            $pipelineAttr | Should -Not -BeNullOrEmpty
        }
    }

    Context 'Null/empty pipeline paths filtered' {
        It 'Filters out null and empty paths from pipeline' {
            # Pipeline input with empty/null values causes binding error since FilePaths is [string[]]
            # Verify that only valid string paths are accepted
            { @('', $null) | Repair-FileShareIntegrity -SourceAccountName 'src' -DestinationAccountName 'dst' -ShareName 'test' -SkipPreCheck -LogDirectory $TestDrive -Confirm:$false } | Should -Throw
        }
    }

    Context 'Pre-check matching files' {
        BeforeEach {
            # Create temp PSDrives so Join-Path doesn't fail when resolving X:\ and Y:\
            if (-not (Get-PSDrive -Name 'X' -ErrorAction SilentlyContinue)) {
                New-PSDrive -Name 'X' -PSProvider FileSystem -Root $TestDrive -Scope Script | Out-Null
            }
            if (-not (Get-PSDrive -Name 'Y' -ErrorAction SilentlyContinue)) {
                New-PSDrive -Name 'Y' -PSProvider FileSystem -Root $TestDrive -Scope Script | Out-Null
            }
        }
        AfterEach {
            Remove-PSDrive -Name 'X' -ErrorAction SilentlyContinue
            Remove-PSDrive -Name 'Y' -ErrorAction SilentlyContinue
        }

        It 'Skips files that already match during pre-check' {
            Mock Test-FileByteIntegrity { 'Match' }
            try {
                Repair-FileShareIntegrity -FilePaths @('file.txt') -SourceAccountName 'src' -DestinationAccountName 'dst' -ShareName 'test' -LogDirectory $TestDrive -Confirm:$false
            } catch {}
            Should -Invoke Write-Host -ParameterFilter { $Object -like '*No files need remediation*' }
        }

        It 'Skips files missing on source during pre-check' {
            Mock Test-FileByteIntegrity { 'MissingOnSource' }
            try {
                Repair-FileShareIntegrity -FilePaths @('file.txt') -SourceAccountName 'src' -DestinationAccountName 'dst' -ShareName 'test' -LogDirectory $TestDrive -Confirm:$false
            } catch {}
            # File should be skipped, so no remediation needed
            Should -Invoke Write-Host -ParameterFilter { $Object -like '*No files need remediation*' }
        }

        It 'Handles pre-check errors gracefully' {
            Mock Test-FileByteIntegrity { throw 'Access denied' }
            try {
                Repair-FileShareIntegrity -FilePaths @('file.txt') -SourceAccountName 'src' -DestinationAccountName 'dst' -ShareName 'test' -LogDirectory $TestDrive -Confirm:$false
            } catch {}
            # Error is counted, file is not added to remediation
            Should -Invoke Write-Host -ParameterFilter { $Object -like '*No files need remediation*' }
        }
    }

    Context 'AzCopy mode pre-flight' {
        It 'Generates SAS tokens with correct permissions for AzCopy mode' {
            try {
                Repair-FileShareIntegrity -FilePaths @('file.txt') -SourceAccountName 'src' -DestinationAccountName 'dst' -ShareName 'test' -AzCopy -SkipPreCheck -LogDirectory $TestDrive -Confirm:$false
            } catch {}
            Should -Invoke New-SasToken -ParameterFilter { $Permissions -eq 'rl' } -Times 1
            Should -Invoke New-SasToken -ParameterFilter { $Permissions -eq 'rwdlc' } -Times 1
        }
    }
}