tests/Backup-Item.Tests.ps1

# Unit tests for Private/Backup-Item.ps1.
#
# Dot-sources the Private helper directly (it's not exported) so we can test
# it in isolation without spinning up the full Invoke-BykaDrBackup machinery.

BeforeAll {
    $PrivateDir = Join-Path $PSScriptRoot '..' 'Private'
    . (Join-Path $PrivateDir 'Backup-Item.ps1')
}

Describe 'Backup-Item file mode' {

    BeforeEach {
        $script:srcDir   = Join-Path $TestDrive 'src'
        $script:backupDir = Join-Path $TestDrive 'backup'
        New-Item -ItemType Directory -Path $script:srcDir, $script:backupDir -Force | Out-Null
    }

    It 'returns OK with byte-count when the source file exists' {
        $srcFile = Join-Path $script:srcDir 'file.txt'
        'hello world' | Out-File -LiteralPath $srcFile -Encoding utf8 -NoNewline
        $expectedBytes = (Get-Item -LiteralPath $srcFile).Length

        $result = Backup-Item -Source $srcFile -BackupDir $script:backupDir -DestSubPath 'sub\file.txt' -Label 'test-file'

        $result.Status | Should -Be 'OK'
        $result.Item   | Should -Be 'test-file'
        $result.Detail | Should -Be "$expectedBytes bytes"
        Test-Path (Join-Path $script:backupDir 'sub\file.txt') | Should -BeTrue
    }

    It 'returns SKIP when the source file is missing' {
        $missingFile = Join-Path $script:srcDir 'nope.txt'
        $result = Backup-Item -Source $missingFile -BackupDir $script:backupDir -DestSubPath 'sub\nope.txt' -Label 'missing-file'

        $result.Status | Should -Be 'SKIP'
        $result.Detail | Should -Be 'Not found'
    }
}

Describe 'Backup-Item directory mode (-IsDir)' {

    BeforeEach {
        $script:srcDir   = Join-Path $TestDrive 'src'
        $script:backupDir = Join-Path $TestDrive 'backup'
        New-Item -ItemType Directory -Path $script:srcDir, $script:backupDir -Force | Out-Null
    }

    It 'returns OK with file-count when the source dir exists with 3 files' {
        $srcSub = Join-Path $script:srcDir 'mydir'
        New-Item -ItemType Directory -Path $srcSub -Force | Out-Null
        1..3 | ForEach-Object { 'x' | Out-File -LiteralPath (Join-Path $srcSub "f$_.txt") -Encoding utf8 -NoNewline }

        $result = Backup-Item -Source $srcSub -BackupDir $script:backupDir -DestSubPath 'sub\mydir' -Label 'test-dir' -IsDir

        $result.Status | Should -Be 'OK'
        $result.Detail | Should -Be '3 files'
        Test-Path (Join-Path $script:backupDir 'sub\mydir\f1.txt') | Should -BeTrue
    }

    It 'returns SKIP when the source dir is missing (-IsDir)' {
        $missingDir = Join-Path $script:srcDir 'nope-dir'
        $result = Backup-Item -Source $missingDir -BackupDir $script:backupDir -DestSubPath 'sub\nope-dir' -Label 'missing-dir' -IsDir

        $result.Status | Should -Be 'SKIP'
        $result.Detail | Should -Be 'Not found'
    }
}

Describe 'Backup-Item failure path' {

    It 'returns FAIL when destination subpath cannot be created (file blocking dir)' {
        # [G4] code-review F7 + qa F3: NUL-byte triggers are PS-version-fragile.
        # A more stable failure trigger is to place a FILE at the parent dir
        # path -- New-Item -ItemType Directory will then fail because the path
        # already exists as a non-directory. The helper's try/catch must
        # convert this to Status=FAIL, not propagate.
        $srcDir    = Join-Path $TestDrive 'src-fail'
        $backupDir = Join-Path $TestDrive 'backup-fail'
        New-Item -ItemType Directory -Path $srcDir, $backupDir -Force | Out-Null
        $srcFile = Join-Path $srcDir 'file.txt'
        'data' | Out-File -LiteralPath $srcFile -Encoding utf8 -NoNewline

        # Pre-create a FILE where Backup-Item will try to make a directory.
        $blockingFilePath = Join-Path $backupDir 'sub'
        'block' | Out-File -LiteralPath $blockingFilePath -Encoding utf8 -NoNewline

        $result = Backup-Item -Source $srcFile -BackupDir $backupDir -DestSubPath 'sub\file.txt' -Label 'fail-test'

        $result.Status | Should -Be 'FAIL'
        $result.Detail | Should -Not -BeNullOrEmpty
    }
}

Describe 'Backup-Item reparse-point handling (-IsDir)' {

    It 'refuses to follow a top-level junction whose source is a reparse point' {
        # [G4] sec F3+F10: if -Source itself is a junction, the helper must
        # SKIP and report the link target, NOT silently follow it. Documents
        # the current contract (refuse) -- if behavior ever changes to "follow",
        # this test forces an explicit update.
        $realDir   = Join-Path $TestDrive 'real'
        $junction  = Join-Path $TestDrive 'junc'
        $backupDir = Join-Path $TestDrive 'backup-junc'
        New-Item -ItemType Directory -Path $realDir, $backupDir -Force | Out-Null
        'a' | Out-File -LiteralPath (Join-Path $realDir 'f.txt') -Encoding utf8 -NoNewline

        try {
            New-Item -ItemType Junction -Path $junction -Target $realDir -ErrorAction Stop | Out-Null
        } catch {
            Set-ItResult -Skipped -Because "could not create junction: $($_.Exception.Message)"
            return
        }
        try {
            $result = Backup-Item -Source $junction -BackupDir $backupDir -DestSubPath 'sub\junc' -Label 'junction-src' -IsDir
            $result.Status | Should -Be 'SKIP'
            $result.Detail | Should -Match 'Junction|SymbolicLink'
        } finally {
            try { [System.IO.Directory]::Delete($junction, $true) } catch {}
        }
    }
}