test/qbuild-cache.tests.ps1

BeforeAll {
    $script:discoveryCacheEnvBackup = (Get-Item -Path env:QCONF_DiscoveryCache -ErrorAction SilentlyContinue).Value
    $script:discoveryCacheLocationEnvBackup = (Get-Item -Path env:QCONF_DiscoveryCacheLocation -ErrorAction SilentlyContinue).Value
    Remove-Item -Path env:QCONF_DiscoveryCache -ErrorAction SilentlyContinue
    Remove-Item -Path env:QCONF_DiscoveryCacheLocation -ErrorAction SilentlyContinue

    Get-Module ConfigMap -ErrorAction SilentlyContinue | Remove-Module
    Import-Module $PSScriptRoot\..\configmap.psm1 -Force
}

AfterAll {
    Remove-Item -Path env:QCONF_DiscoveryCache -ErrorAction SilentlyContinue
    Remove-Item -Path env:QCONF_DiscoveryCacheLocation -ErrorAction SilentlyContinue
    if ($null -ne $script:discoveryCacheEnvBackup) {
        Set-Item -Path env:QCONF_DiscoveryCache -Value $script:discoveryCacheEnvBackup
    }
    if ($null -ne $script:discoveryCacheLocationEnvBackup) {
        Set-Item -Path env:QCONF_DiscoveryCacheLocation -Value $script:discoveryCacheLocationEnvBackup
    }
    & (Get-Module ConfigMap) { Update-ConfigMapSettings | Out-Null }
}

Describe 'qbuild !cache' {
    BeforeEach {
        & (Get-Module ConfigMap) { Clear-ConfigMapDiscoveryCacheMemory }
        $script:tempRoot = Join-Path $TestDrive "qbuild-cache-$(New-Guid)"
        New-Item -ItemType Directory -Force -Path $script:tempRoot | Out-Null
        @'
@{
    build = @{
        exec = { "build" }
        description = "Build"
    }
    clean = { "clean" }
}
'@
 | Set-Content -Path (Join-Path $script:tempRoot '.build.map.ps1')
    }

    It 'returns status for the local map after a cache miss has written the file' {
        Push-Location $script:tempRoot
        try {
            $null = qbuild list
            $result = qbuild '!cache'

            $result.Action | Should -Be 'status'
            $result.Enabled | Should -BeTrue
            $result.Location | Should -Be 'local'
            $result.Exists | Should -BeTrue
            $result.Valid | Should -BeTrue
            $result.Source | Should -Be 'memory'
            $result.Language | Should -Be 'build'
            $result.Path | Should -Be (Join-Path $script:tempRoot '.configmap\discovery.build.cache.json')
            $result.EntryCount | Should -BeGreaterThan 0
            Test-Path $result.Path | Should -BeTrue
        }
        finally {
            Pop-Location
        }
    }

    It 'defaults to status and accepts an explicit status action' {
        Push-Location $script:tempRoot
        try {
            $null = qbuild list
            $implicit = qbuild '!cache'
            $explicit = qbuild '!cache' 'status'

            $implicit.Action | Should -Be 'status'
            $explicit.Action | Should -Be 'status'
            $explicit.Path | Should -Be $implicit.Path
        }
        finally {
            Pop-Location
        }
    }

    It 'clears the JSON file and later status shows Exists = $false' {
        Push-Location $script:tempRoot
        try {
            $null = qbuild list
            $cachePath = Join-Path $script:tempRoot '.configmap\discovery.build.cache.json'
            Test-Path $cachePath | Should -BeTrue

            $cleared = qbuild '!cache' 'clear'
            $cleared.Action | Should -Be 'clear'
            $cleared.Exists | Should -BeFalse
            $cleared.Valid | Should -BeFalse
            $cleared.Source | Should -Be 'none'
            Test-Path $cachePath | Should -BeFalse

            $status = qbuild '!cache'
            $status.Exists | Should -BeFalse
            $status.Source | Should -Be 'none'
        }
        finally {
            Pop-Location
        }
    }

    It 'rebuilds after a map edit so new entries appear' {
        Push-Location $script:tempRoot
        try {
            $null = qbuild list
            $before = qbuild '!cache'

            Set-Content -Path (Join-Path $script:tempRoot '.build.map.ps1') -Value @'
@{
    build = { "build" }
    clean = { "clean" }
    test = { "test" }
}
'@


            $rebuilt = qbuild '!cache' 'rebuild'
            $rebuilt.Action | Should -Be 'rebuild'
            $rebuilt.Exists | Should -BeTrue
            $rebuilt.Valid | Should -BeTrue
            $rebuilt.Source | Should -Be 'memory'
            $rebuilt.EntryCount | Should -BeGreaterThan $before.EntryCount

            $cache = Get-Content -LiteralPath $rebuilt.Path -Raw | ConvertFrom-Json
            @($cache.entries.hierarchical).key | Should -Contain 'test'
        }
        finally {
            Pop-Location
        }
    }

    It 'rebuilds via -Action' {
        Push-Location $script:tempRoot
        try {
            $result = qbuild '!cache' -Action rebuild
            $result.Action | Should -Be 'rebuild'
            $result.Exists | Should -BeTrue
            $result.Valid | Should -BeTrue
        }
        finally {
            Pop-Location
        }
    }

    It 'throws for an unknown action' {
        Push-Location $script:tempRoot
        try {
            { qbuild '!cache' 'bogus' } | Should -Throw -ExpectedMessage '*status*'
        }
        finally {
            Pop-Location
        }
    }

    It 'throws when more than one action is provided' {
        Push-Location $script:tempRoot
        try {
            { qbuild '!cache' 'clear' 'rebuild' } | Should -Throw -ExpectedMessage '*at most one action*'
        }
        finally {
            Pop-Location
        }
    }

    It 'throws when map is missing' {
        $emptyDir = Join-Path $TestDrive 'no-map-cache'
        New-Item -ItemType Directory -Force -Path $emptyDir | Out-Null
        Push-Location $emptyDir
        try {
            { qbuild '!cache' } | Should -Throw -ExpectedMessage '*No build map file found*'
        }
        finally {
            Pop-Location
        }
    }

    It 'includes !cache in entry completions when a map exists' {
        Push-Location $script:tempRoot
        try {
            $completer = (Get-Command qbuild).Parameters['entry'].Attributes |
                Where-Object { $_ -is [System.Management.Automation.ArgumentCompleterAttribute] } |
                Select-Object -First 1

            $completions = & $completer.ScriptBlock 'qbuild' 'entry' '' $null @{}
            $completions | Should -Contain '!cache'
        }
        finally {
            Pop-Location
        }
    }

    It 'includes !cache in entry completions without a build map' {
        $emptyDir = Join-Path $TestDrive 'no-map-cache-complete'
        New-Item -ItemType Directory -Force -Path $emptyDir | Out-Null
        Push-Location $emptyDir
        try {
            $completer = (Get-Command qbuild).Parameters['entry'].Attributes |
                Where-Object { $_ -is [System.Management.Automation.ArgumentCompleterAttribute] } |
                Select-Object -First 1

            $completions = & $completer.ScriptBlock 'qbuild' 'entry' '!ca' $null @{}
            $completions | Should -Contain '!cache'
        }
        finally {
            Pop-Location
        }
    }

    It 'exposes Action ValidateSet values for !cache' {
        InModuleScope ConfigMap {
            $map = Merge-ConfigMapCommands -Language build
            $params = Get-EntryDynamicParam -map $map -key '!cache' -command exec
            @($params.Keys) | Should -Be @('Action')

            $actionSet = $params['Action'].Attributes |
                Where-Object { $_ -is [System.Management.Automation.ValidateSetAttribute] }
            @($actionSet.ValidValues) | Should -Be @('status', 'clear', 'rebuild')
        }
    }

    It 'reports user cache path when DiscoveryCacheLocation is user' {
        $userHome = Join-Path $TestDrive 'qbuild-cache-user-home'
        New-Item -ItemType Directory -Force -Path $userHome | Out-Null
        Set-Content -Path (Join-Path $script:tempRoot '.build.map.ps1') -Value @'
@{
    _settings = @{ DiscoveryCacheLocation = 'user' }
    build = { "build" }
}
'@


        $previousHome = $env:HOME
        $env:HOME = $userHome
        Push-Location $script:tempRoot
        try {
            $result = qbuild '!cache' 'rebuild'
            $result.Location | Should -Be 'user'
            $result.Exists | Should -BeTrue
            $result.Path | Should -Match '\\.configmap\\'
            $result.Path | Should -BeLike "$userHome*"
            Test-Path (Join-Path $script:tempRoot '.configmap') | Should -BeFalse
        }
        finally {
            Pop-Location
            if ($null -eq $previousHome) {
                Remove-Item env:HOME -ErrorAction SilentlyContinue
            }
            else {
                $env:HOME = $previousHome
            }
        }
    }

    It 'reports Enabled = $false, throws on rebuild, and still clears leftover files when disabled' {
        Push-Location $script:tempRoot
        try {
            $null = qbuild list
            $cachePath = Join-Path $script:tempRoot '.configmap\discovery.build.cache.json'
            Test-Path $cachePath | Should -BeTrue

            $previous = $env:QCONF_DiscoveryCache
            $env:QCONF_DiscoveryCache = '0'
            try {
                & (Get-Module ConfigMap) { Update-ConfigMapSettings | Out-Null }

                $status = qbuild '!cache'
                $status.Enabled | Should -BeFalse
                $status.Exists | Should -BeTrue

                { qbuild '!cache' 'rebuild' } | Should -Throw -ExpectedMessage '*disabled*'

                $cleared = qbuild '!cache' 'clear'
                $cleared.Enabled | Should -BeFalse
                $cleared.Exists | Should -BeFalse
                Test-Path $cachePath | Should -BeFalse
            }
            finally {
                if ($null -eq $previous) {
                    Remove-Item env:QCONF_DiscoveryCache -ErrorAction SilentlyContinue
                }
                else {
                    $env:QCONF_DiscoveryCache = $previous
                }
                & (Get-Module ConfigMap) { Update-ConfigMapSettings | Out-Null }
            }
        }
        finally {
            Pop-Location
        }
    }
}