Tests/Private/Test-FileByteIntegrity.Tests.ps1

BeforeAll {
    . "$PSScriptRoot/../../Private/Test-FileByteIntegrity.ps1"
}

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

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

    Context 'When source file does not exist' {
        It 'Returns MissingOnSource' {
            $src = Join-Path $testDir 'nonexistent_src.txt'
            $dst = Join-Path $testDir 'existing_dst.txt'
            Set-Content -Path $dst -Value 'hello'

            $result = Test-FileByteIntegrity -SourcePath $src -DestinationPath $dst
            $result | Should -Be 'MissingOnSource'
        }
    }

    Context 'When destination file does not exist' {
        It 'Returns MissingOnDestination' {
            $src = Join-Path $testDir 'existing_src.txt'
            $dst = Join-Path $testDir 'nonexistent_dst.txt'
            Set-Content -Path $src -Value 'hello'

            $result = Test-FileByteIntegrity -SourcePath $src -DestinationPath $dst
            $result | Should -Be 'MissingOnDestination'
        }
    }

    Context 'When files differ in size' {
        It 'Returns SizeMismatch' {
            $src = Join-Path $testDir 'size_src.txt'
            $dst = Join-Path $testDir 'size_dst.txt'
            [System.IO.File]::WriteAllBytes($src, [byte[]](1..100))
            [System.IO.File]::WriteAllBytes($dst, [byte[]](1..50))

            $result = Test-FileByteIntegrity -SourcePath $src -DestinationPath $dst
            $result | Should -Be 'SizeMismatch'
        }
    }

    Context 'When both files are empty' {
        It 'Returns Match' {
            $src = Join-Path $testDir 'empty_src.txt'
            $dst = Join-Path $testDir 'empty_dst.txt'
            [System.IO.File]::WriteAllBytes($src, [byte[]]@())
            [System.IO.File]::WriteAllBytes($dst, [byte[]]@())

            $result = Test-FileByteIntegrity -SourcePath $src -DestinationPath $dst
            $result | Should -Be 'Match'
        }
    }

    Context 'When files are identical and small (< 8KB)' {
        It 'Returns Match' {
            $src = Join-Path $testDir 'small_match_src.bin'
            $dst = Join-Path $testDir 'small_match_dst.bin'
            $data = [byte[]](1..100)
            [System.IO.File]::WriteAllBytes($src, $data)
            [System.IO.File]::WriteAllBytes($dst, $data)

            $result = Test-FileByteIntegrity -SourcePath $src -DestinationPath $dst
            $result | Should -Be 'Match'
        }
    }

    Context 'When small files differ in content' {
        It 'Returns ByteMismatch' {
            $src = Join-Path $testDir 'small_diff_src.bin'
            $dst = Join-Path $testDir 'small_diff_dst.bin'
            $srcData = [byte[]](1..100)
            $dstData = [byte[]](1..100)
            $dstData[50] = 255  # flip one byte
            [System.IO.File]::WriteAllBytes($src, $srcData)
            [System.IO.File]::WriteAllBytes($dst, $dstData)

            $result = Test-FileByteIntegrity -SourcePath $src -DestinationPath $dst
            $result | Should -Be 'ByteMismatch'
        }
    }

    Context 'When files are identical and large (> 8KB)' {
        It 'Returns Match' {
            $src = Join-Path $testDir 'large_match_src.bin'
            $dst = Join-Path $testDir 'large_match_dst.bin'
            # 20KB of data
            $data = [byte[]]::new(20480)
            [System.Random]::new(42).NextBytes($data)
            [System.IO.File]::WriteAllBytes($src, $data)
            [System.IO.File]::WriteAllBytes($dst, $data)

            $result = Test-FileByteIntegrity -SourcePath $src -DestinationPath $dst
            $result | Should -Be 'Match'
        }
    }

    Context 'When large files differ in the first chunk' {
        It 'Returns ByteMismatch' {
            $src = Join-Path $testDir 'large_head_diff_src.bin'
            $dst = Join-Path $testDir 'large_head_diff_dst.bin'
            $data = [byte[]]::new(20480)
            [System.Random]::new(42).NextBytes($data)
            [System.IO.File]::WriteAllBytes($src, $data)

            $dataCopy = [byte[]]::new($data.Length)
            [System.Array]::Copy($data, $dataCopy, $data.Length)
            $dataCopy[10] = [byte](($dataCopy[10] + 1) % 256)
            [System.IO.File]::WriteAllBytes($dst, $dataCopy)

            $result = Test-FileByteIntegrity -SourcePath $src -DestinationPath $dst
            $result | Should -Be 'ByteMismatch'
        }
    }

    Context 'When large files differ in the last chunk' {
        It 'Returns ByteMismatch' {
            $src = Join-Path $testDir 'large_tail_diff_src.bin'
            $dst = Join-Path $testDir 'large_tail_diff_dst.bin'
            $data = [byte[]]::new(20480)
            [System.Random]::new(42).NextBytes($data)
            [System.IO.File]::WriteAllBytes($src, $data)

            $dataCopy = [byte[]]::new($data.Length)
            [System.Array]::Copy($data, $dataCopy, $data.Length)
            $dataCopy[$dataCopy.Length - 5] = [byte](($dataCopy[$dataCopy.Length - 5] + 1) % 256)
            [System.IO.File]::WriteAllBytes($dst, $dataCopy)

            $result = Test-FileByteIntegrity -SourcePath $src -DestinationPath $dst
            $result | Should -Be 'ByteMismatch'
        }
    }

    Context 'When large files differ only in the middle (blind spot)' {
        It 'Returns Match (known limitation — partial check only reads head + tail)' {
            $src = Join-Path $testDir 'large_mid_diff_src.bin'
            $dst = Join-Path $testDir 'large_mid_diff_dst.bin'
            $data = [byte[]]::new(20480)
            [System.Random]::new(42).NextBytes($data)
            [System.IO.File]::WriteAllBytes($src, $data)

            $dataCopy = [byte[]]::new($data.Length)
            [System.Array]::Copy($data, $dataCopy, $data.Length)
            # Flip a byte in the middle (between first 8KB and last 8KB)
            $dataCopy[10000] = [byte](($dataCopy[10000] + 1) % 256)
            [System.IO.File]::WriteAllBytes($dst, $dataCopy)

            $result = Test-FileByteIntegrity -SourcePath $src -DestinationPath $dst
            $result | Should -Be 'Match'
        }
    }

    Context 'When file is exactly 8KB (one chunk boundary)' {
        It 'Returns Match for identical files' {
            $src = Join-Path $testDir 'boundary_match_src.bin'
            $dst = Join-Path $testDir 'boundary_match_dst.bin'
            $data = [byte[]]::new(8192)
            [System.Random]::new(42).NextBytes($data)
            [System.IO.File]::WriteAllBytes($src, $data)
            [System.IO.File]::WriteAllBytes($dst, $data)

            $result = Test-FileByteIntegrity -SourcePath $src -DestinationPath $dst
            $result | Should -Be 'Match'
        }
    }
}