tests/New-DFShim.Tests.ps1

BeforeAll {
    . "$PSScriptRoot/../Public/New-DFDirectory.ps1"
    . "$PSScriptRoot/../Private/Expand-DFXdgPath.ps1"
    . "$PSScriptRoot/../Private/Test-DFToolSchema.ps1"
    . "$PSScriptRoot/../Private/Import-DFToolDb.ps1"
    . "$PSScriptRoot/../Public/New-DFShim.ps1"
}

Describe 'New-DFShim' {
    BeforeEach {
        $script:DFToolDb = $null
        Remove-Variable DFConfig -Scope Global -ErrorAction Ignore

        # Set up a fake app directory and executable
        $script:AppDir  = Join-Path $TestDrive 'myapp'
        $script:FakeExe = Join-Path $script:AppDir 'myapp.exe'
        New-Item -ItemType Directory -Force -Path $script:AppDir  | Out-Null
        New-Item -ItemType File      -Force -Path $script:FakeExe | Out-Null

        # Dedicated shims dir for most tests — remove and recreate for isolation
        $script:ShimsDir = Join-Path $TestDrive 'shims'
        Remove-Item $script:ShimsDir -Recurse -Force -ErrorAction Ignore

        # Save PATH so we can restore it
        $script:SavedPath = $Env:PATH
    }

    AfterEach {
        $Env:PATH = $script:SavedPath
        Remove-Variable DFConfig -Scope Global -ErrorAction Ignore
        $script:DFToolDb = $null
    }

    It 'derives shim name from target basename when -Name is omitted' {
        New-DFShim $script:FakeExe -ShimsPath $script:ShimsDir
        Test-Path (Join-Path $script:ShimsDir 'myapp.cmd') | Should -BeTrue
    }

    It 'accepts -Target positionally and uses explicit -Name when provided' {
        New-DFShim $script:FakeExe -Name 'alias' -ShimsPath $script:ShimsDir
        Test-Path (Join-Path $script:ShimsDir 'alias.cmd') | Should -BeTrue
    }

    It 'errors when neither -Target nor -Name is given' {
        { New-DFShim -ShimsPath $script:ShimsDir -ErrorAction Stop } |
            Should -Throw
    }

    It 'creates a .cmd file in the specified shims dir' {
        New-DFShim -Name 'myapp' -Target $script:FakeExe -ShimsPath $script:ShimsDir
        Test-Path (Join-Path $script:ShimsDir 'myapp.cmd') | Should -BeTrue
    }

    It 'generated .cmd contains the correct target path' {
        New-DFShim -Name 'myapp' -Target $script:FakeExe -ShimsPath $script:ShimsDir
        $content = Get-Content (Join-Path $script:ShimsDir 'myapp.cmd') -Raw
        $content | Should -Match ([regex]::Escape("`"$($script:FakeExe)`" %*"))
    }

    It 'generated .cmd contains cd /d to the app directory' {
        New-DFShim -Name 'myapp' -Target $script:FakeExe -ShimsPath $script:ShimsDir
        $content = Get-Content (Join-Path $script:ShimsDir 'myapp.cmd') -Raw
        $content | Should -Match ([regex]::Escape("cd /d `"$($script:AppDir)`""))
    }

    It 'generated .cmd uses endlocal & exit on one line to preserve exit code' {
        New-DFShim -Name 'myapp' -Target $script:FakeExe -ShimsPath $script:ShimsDir
        $content = Get-Content (Join-Path $script:ShimsDir 'myapp.cmd') -Raw
        $content | Should -Match ([regex]::Escape('endlocal & exit /b %_exit%'))
    }

    It 'uses ShimsPath from $DFConfig when -ShimsPath is not specified' {
        $configDir = Join-Path $TestDrive 'config-shims'
        $Global:DFConfig = @{ ShimsPath = $configDir }
        New-DFShim -Name 'myapp' -Target $script:FakeExe
        Test-Path (Join-Path $configDir 'myapp.cmd') | Should -BeTrue
    }

    It 'falls back to $HOME\.local\bin when no $DFConfig ShimsPath is set' {
        $defaultDir = Join-Path $HOME '.local' 'bin'
        $shimPath   = Join-Path $defaultDir 'dfshimtest.cmd'
        try {
            New-DFShim -Name 'dfshimtest' -Target $script:FakeExe 3>$null
            Test-Path $shimPath | Should -BeTrue
        } finally {
            Remove-Item $shimPath -ErrorAction Ignore
        }
    }

    It 'warns when shims dir is not on $PATH' {
        # $script:ShimsDir is a fresh TestDrive path — not on PATH
        $warns = New-DFShim -Name 'myapp' -Target $script:FakeExe `
            -ShimsPath $script:ShimsDir 3>&1 |
            Where-Object { $_ -is [System.Management.Automation.WarningRecord] }
        $warns | Should -Not -BeNullOrEmpty
    }

    It 'does not warn when shims dir is already on $PATH' {
        $Env:PATH = $script:ShimsDir + [IO.Path]::PathSeparator + $Env:PATH
        $warns = New-DFShim -Name 'myapp' -Target $script:FakeExe `
            -ShimsPath $script:ShimsDir 3>&1 |
            Where-Object { $_ -is [System.Management.Automation.WarningRecord] }
        $warns | Should -BeNullOrEmpty
    }

    It 'resolves target from tool DB when -Target is omitted' {
        $toolsDir = Join-Path $TestDrive 'tools'
        New-Item -ItemType Directory -Force -Path $toolsDir | Out-Null
        @'
{ "name": "myapp", "executable": "myapp.exe" }
'@
 | Set-Content (Join-Path $toolsDir 'myapp.json')

        Mock Get-Command {
            [PSCustomObject]@{ Source = $script:FakeExe }
        } -ParameterFilter { $Name -eq 'myapp.exe' }

        New-DFShim -Name 'myapp' -ShimsPath $script:ShimsDir -ToolsPath $toolsDir
        $content = Get-Content (Join-Path $script:ShimsDir 'myapp.cmd') -Raw
        $content | Should -Match ([regex]::Escape($script:FakeExe))

        Remove-Item $toolsDir -Recurse -Force -ErrorAction Ignore
        $script:DFToolDb = $null
    }

    It '-Target bypasses the tool DB entirely' {
        $toolsDir = Join-Path $TestDrive 'emptytools'
        New-Item -ItemType Directory -Force -Path $toolsDir | Out-Null

        New-DFShim -Name 'directapp' -Target $script:FakeExe -ShimsPath $script:ShimsDir `
            -ToolsPath $toolsDir
        Test-Path (Join-Path $script:ShimsDir 'directapp.cmd') | Should -BeTrue

        Remove-Item $toolsDir -Recurse -Force -ErrorAction Ignore
        $script:DFToolDb = $null
    }

    It 'errors when -Target file does not exist' {
        { New-DFShim -Name 'ghost' -Target 'C:\nonexistent\ghost.exe' `
            -ShimsPath $script:ShimsDir -ErrorAction Stop } |
            Should -Throw
    }

    It 'errors when tool name is not in DB and -Target is omitted' {
        $emptyDir = Join-Path $TestDrive 'emptytools2'
        New-Item -ItemType Directory -Force -Path $emptyDir | Out-Null
        { New-DFShim -Name 'notregistered' -ShimsPath $script:ShimsDir `
            -ToolsPath $emptyDir -ErrorAction Stop } |
            Should -Throw
        $script:DFToolDb = $null
    }

    It '-Force overwrites an existing shim' {
        New-DFShim -Name 'myapp' -Target $script:FakeExe -ShimsPath $script:ShimsDir

        $newExe = Join-Path $script:AppDir 'myapp2.exe'
        New-Item -ItemType File -Force -Path $newExe | Out-Null
        New-DFShim -Name 'myapp' -Target $newExe -ShimsPath $script:ShimsDir -Force

        $content = Get-Content (Join-Path $script:ShimsDir 'myapp.cmd') -Raw
        $content | Should -Match ([regex]::Escape($newExe))
    }

    It 'errors without -Force when shim already exists' {
        New-DFShim -Name 'myapp' -Target $script:FakeExe -ShimsPath $script:ShimsDir
        { New-DFShim -Name 'myapp' -Target $script:FakeExe `
            -ShimsPath $script:ShimsDir -ErrorAction Stop } |
            Should -Throw
    }

    It '-WhatIf does not create the shim file' {
        New-DFShim -Name 'myapp' -Target $script:FakeExe `
            -ShimsPath $script:ShimsDir -WhatIf
        Test-Path (Join-Path $script:ShimsDir 'myapp.cmd') | Should -BeFalse
    }
}