tests/Update-DFCategoryDb.Tests.ps1

BeforeAll {
    . "$PSScriptRoot/../Public/New-DFDirectory.ps1"
    . "$PSScriptRoot/../Private/Test-DFCategoryDbSchema.ps1"
    . "$PSScriptRoot/../Private/Invoke-DFCategoryDbDownload.ps1"
    . "$PSScriptRoot/../Public/Update-DFCategoryDb.ps1"

    function New-FakeDoc {
        param($ToolCount = 1)
        [pscustomobject]@{
            schemaVersion = 1
            updated       = '2026-07-05'
            taxonomy      = [pscustomobject]@{ function = @('search'); worksWith = @('text') }
            tools         = [pscustomobject]@{ ripgrep = [pscustomobject]@{ function = @('search'); interface = 'cli' } }
        }
    }
}

Describe 'Update-DFCategoryDb' {
    BeforeEach {
        $script:SavedDataHome = $Env:XDG_DATA_HOME
        # Each It needs its own directory: TestDrive is only reset once per
        # Describe (at block teardown), not between individual It blocks, so a
        # fixed 'data' subfolder here would leak state (e.g. a file written by
        # an earlier It) into later Its that assert on a clean slate.
        $Env:XDG_DATA_HOME = Join-Path $TestDrive ([guid]::NewGuid().ToString()) 'data'
        $script:DestPath = Join-Path $Env:XDG_DATA_HOME 'dotforge/tool-categories.json'
    }
    AfterEach { $Env:XDG_DATA_HOME = $script:SavedDataHome }

    It 'downloads, validates, and atomically writes the destination' {
        Mock Invoke-DFCategoryDbDownload { New-FakeDoc }
        Update-DFCategoryDb -Confirm:$false
        Test-Path $script:DestPath | Should -BeTrue
        (Get-Content $script:DestPath -Raw | ConvertFrom-Json).tools.ripgrep.function | Should -Contain 'search'
        Get-ChildItem (Split-Path $script:DestPath) -Filter '*.tmp.*' | Should -BeNullOrEmpty
    }

    It 'leaves no file behind on -WhatIf' {
        Mock Invoke-DFCategoryDbDownload { New-FakeDoc }
        Update-DFCategoryDb -WhatIf
        Test-Path $script:DestPath | Should -BeFalse
    }

    It 'warns and writes nothing when the download fails' {
        Mock Invoke-DFCategoryDbDownload { throw 'network down' }
        $w = $null
        Update-DFCategoryDb -Confirm:$false -WarningVariable w -WarningAction SilentlyContinue
        "$w" | Should -Match 'failed to download'
        Test-Path $script:DestPath | Should -BeFalse
    }

    It 'warns and leaves the existing copy untouched when validation fails' {
        New-DFDirectory (Split-Path $script:DestPath)
        # -NoNewline: Set-Content appends a trailing newline by default, which
        # would make the byte-for-byte "untouched" comparison below fail even
        # when the file was correctly left alone.
        Set-Content -Path $script:DestPath -Value 'existing-good-content' -NoNewline
        Mock Invoke-DFCategoryDbDownload {
            [pscustomobject]@{ schemaVersion = 1; taxonomy = [pscustomobject]@{ function = @(); worksWith = @() }; tools = [pscustomobject]@{} }
        }
        $w = $null
        Update-DFCategoryDb -Confirm:$false -WarningVariable w -WarningAction SilentlyContinue
        "$w" | Should -Match 'validation'
        Get-Content $script:DestPath -Raw | Should -Be 'existing-good-content'
    }

    It 'warns when XDG_DATA_HOME is unset' {
        $Env:XDG_DATA_HOME = $null
        Mock Invoke-DFCategoryDbDownload { New-FakeDoc }
        $w = $null
        Update-DFCategoryDb -Confirm:$false -WarningVariable w -WarningAction SilentlyContinue
        "$w" | Should -Match 'XDG_DATA_HOME'
    }
}