Tests/GenXdev.AI/Get-AILLMSettings.Tests.ps1

###############################################################################
# Part of PowerShell module : GenXdev.AI
# Original cmdlet filename : Get-AILLMSettings.Tests.ps1
# Original author : René Vaessen / GenXdev
# Version : 3.30.2026
###############################################################################

Pester\Describe 'Get-AILLMSettings' {

    Pester\BeforeAll {
        # ensure clean slate — clear any leftover session vars for all 6 types
        $allTypes = @(
            'SimpleIntelligence', 'Knowledge', 'Pictures',
            'TextTranslation', 'Coding', 'ToolUse'
        )
        $allProps = @(
            'Model', 'ApiEndpoint', 'ApiKey',
            'NoSupportForJsonSchema', 'NoSupportForImageUpload', 'NoSupportForToolCalls'
        )
        foreach ($t in $allTypes) {
            foreach ($p in $allProps) {
                $varName = "AILLMSettings_${t}_${p}"
                if (Microsoft.PowerShell.Utility\Get-Variable -Name $varName -Scope Global -ErrorAction SilentlyContinue) {
                    Microsoft.PowerShell.Utility\Set-Variable -Name $varName -Value $null -Scope Global
                }
            }
        }

        # Pre-configure one LLM type so Get-AILLMSettings cross-type
        # fallback resolves without firing the interactive prompt
        $null = GenXdev\Set-AILLMSettings -LLMQueryType 'Coding' `
            -Model 'test-bootstrap-model' `
            -ApiEndpoint 'https://test-bootstrap.example.com/v1' `
            -ApiKey 'sk-test-bootstrap'

        # create a temp empty preferences database to isolate tests
        # that need to bypass persistent preferences without relying
        # on mocking C# cmdlets (which fails for in-module calls)
        $Script:TempDbDir = [System.IO.Path]::Combine(
            $env:TEMP,
            [System.IO.Path]::GetRandomFileName()
        )
        $null = [System.IO.Directory]::CreateDirectory($Script:TempDbDir)
    }

    Pester\AfterAll {
        # clean up the temp preferences database directory
        if ($Script:TempDbDir -and [System.IO.Directory]::Exists($Script:TempDbDir)) {
            [System.IO.Directory]::Delete($Script:TempDbDir, $true)
        }
    }

    Pester\AfterEach {
        # clear any session vars set during a test
        $allTypes = @(
            'SimpleIntelligence', 'Knowledge', 'Pictures',
            'TextTranslation', 'Coding', 'ToolUse'
        )
        $allProps = @(
            'Model', 'ApiEndpoint', 'ApiKey',
            'NoSupportForJsonSchema', 'NoSupportForImageUpload', 'NoSupportForToolCalls'
        )
        foreach ($t in $allTypes) {
            foreach ($p in $allProps) {
                $varName = "AILLMSettings_${t}_${p}"
                if (Microsoft.PowerShell.Utility\Get-Variable -Name $varName -Scope Global -ErrorAction SilentlyContinue) {
                    Microsoft.PowerShell.Utility\Set-Variable -Name $varName -Value $null -Scope Global
                }
            }
        }
    }

    Pester\It 'Returns a hashtable' {
        $result = GenXdev\Get-AILLMSettings
        $result | Pester\Should -BeOfType [hashtable]
    }

    Pester\It 'Returns all 6 expected keys' {
        $result = GenXdev\Get-AILLMSettings
        $expectedKeys = @(
            'Model', 'ApiEndpoint', 'ApiKey',
            'NoSupportForJsonSchema', 'NoSupportForImageUpload', 'NoSupportForToolCalls'
        )
        foreach ($key in $expectedKeys) {
            $result.ContainsKey($key) | Pester\Should -BeTrue
        }
    }

    Pester\It 'SessionOnly returns null values when nothing is configured' {
        $result = GenXdev\Get-AILLMSettings -SessionOnly
        $result.Model | Pester\Should -Be $null
        $result.ApiEndpoint | Pester\Should -Be $null
        $result.ApiKey | Pester\Should -Be $null
    }

    Pester\It 'Explicit -Model parameter takes priority' {
        $result = GenXdev\Get-AILLMSettings -Model 'test-model-override'
        $result.Model | Pester\Should -Be 'test-model-override'
    }

    Pester\It 'Explicit -ApiEndpoint parameter takes priority' {
        $result = GenXdev\Get-AILLMSettings -ApiEndpoint 'https://test.example.com/v1'
        $result.ApiEndpoint | Pester\Should -Be 'https://test.example.com/v1'
    }

    Pester\It 'Explicit -ApiKey parameter takes priority' {
        $result = GenXdev\Get-AILLMSettings -ApiKey 'sk-test-key'
        $result.ApiKey | Pester\Should -Be 'sk-test-key'
    }

    Pester\It 'Session variables are retrieved when set' {
        Microsoft.PowerShell.Utility\Set-Variable `
            -Name 'AILLMSettings_SimpleIntelligence_Model' -Value 'session-model' -Scope Global
        Microsoft.PowerShell.Utility\Set-Variable `
            -Name 'AILLMSettings_SimpleIntelligence_ApiEndpoint' -Value 'https://session.example.com' -Scope Global
        Microsoft.PowerShell.Utility\Set-Variable `
            -Name 'AILLMSettings_SimpleIntelligence_ApiKey' -Value 'sk-session-key' -Scope Global

        $result = GenXdev\Get-AILLMSettings -LLMQueryType SimpleIntelligence

        $result.Model | Pester\Should -Be 'session-model'
        $result.ApiEndpoint | Pester\Should -Be 'https://session.example.com'
        $result.ApiKey | Pester\Should -Be 'sk-session-key'
    }

    Pester\It 'SkipSession ignores session variables' {
        Microsoft.PowerShell.Utility\Set-Variable `
            -Name 'AILLMSettings_SimpleIntelligence_Model' -Value 'session-model' -Scope Global

        # with SkipSession + SessionOnly, no defaults, no prefs — everything null
        $result = GenXdev\Get-AILLMSettings -SkipSession -SessionOnly
        $result.Model | Pester\Should -Be $null
    }

    Pester\It 'ClearSession clears session variables' {
        Microsoft.PowerShell.Utility\Set-Variable `
            -Name 'AILLMSettings_SimpleIntelligence_Model' -Value 'before-clear' -Scope Global

        $null = GenXdev\Get-AILLMSettings -ClearSession -SessionOnly

        $after = GenXdev\Get-AILLMSettings -SessionOnly
        $after.Model | Pester\Should -Be $null
    }

    Pester\It 'Pictures query type prefers image-capable provider in defaults' {
        # without -SessionOnly, falls through to defaults
        # Gemini 2.5 Flash has NoSupportForImageUpload = false, should be picked
        $result = GenXdev\Get-AILLMSettings -LLMQueryType Pictures `
            -SuppressAutoPrompt

        $result.Model | Pester\Should -Not -Be $null
        $result.ApiEndpoint | Pester\Should -Not -Be $null
        # first image-capable provider should be picked
        $result.NoSupportForImageUpload | Pester\Should -BeFalse
    }

    Pester\It 'Non-Pictures query type picks first default provider' {
        $result = GenXdev\Get-AILLMSettings -LLMQueryType SimpleIntelligence `
            -SuppressAutoPrompt

        $result.Model | Pester\Should -Not -Be $null
        $result.ApiEndpoint | Pester\Should -Not -Be $null
    }

    Pester\It 'Persistent settings are retrieved from temp database' {
        # store settings in the test DB
        $null = GenXdev\Set-AILLMSettings -LLMQueryType Coding `
            -Model 'test-db-model' `
            -ApiEndpoint 'https://test-db.example.com/v1' `
            -ApiKey 'sk-test-db-key'

        # retrieve with SkipSession so we only read from prefs/defaults
        $result = GenXdev\Get-AILLMSettings -LLMQueryType Coding

        $result.Model | Pester\Should -Be 'test-db-model'
        $result.ApiEndpoint | Pester\Should -Be 'https://test-db.example.com/v1'
        $result.ApiKey | Pester\Should -Be 'sk-test-db-key'
    }

    Pester\It 'Accepts all 6 LLMQueryType values without throwing' {
        $types = @(
            'SimpleIntelligence', 'Knowledge', 'Pictures',
            'TextTranslation', 'Coding', 'ToolUse'
        )
        foreach ($type in $types) {
            { GenXdev\Get-AILLMSettings -LLMQueryType $type -ErrorAction Stop } |
                Pester\Should -Not -Throw
        }
    }

    Pester\It 'Switch parameters are passed through correctly' {
        $result = GenXdev\Get-AILLMSettings `
            -NoSupportForJsonSchema `
            -NoSupportForImageUpload `
            -NoSupportForToolCalls

        $result.NoSupportForJsonSchema | Pester\Should -BeTrue
        $result.NoSupportForImageUpload | Pester\Should -BeTrue
        $result.NoSupportForToolCalls | Pester\Should -BeTrue
    }

    Pester\It 'Default provider null ApiKey stays null, not empty hashtable' {
        # Regression: ConvertTo-HashTable was turning JSON null into @{}
        # which made $allNull false, preventing interactive setup

        # use a temp empty preferences database to prevent real user
        # preferences from leaking into this test — only defaults
        # should be tested here. Mocking Get-GenXdevPreference does
        # not work here because it is a C# cmdlet in the same module.
        $result = GenXdev\Get-AILLMSettings `
            -LLMQueryType SimpleIntelligence `
            -SuppressAutoPrompt `
            -PreferencesDatabasePath $Script:TempDbDir

        # ApiKey should be $null (from JSON null), not an empty hashtable
        $result.ApiKey | Pester\Should -Be $null
        $result.Model | Pester\Should -Not -Be $null
        $result.ApiEndpoint | Pester\Should -Not -Be $null
    }

    Pester\It 'Default provider null values remain null for all null fields' {
        # SimpleIntelligence defaults have ApiKey: null,
        # NoSupportForJsonSchema: false, etc.

        # use a temp empty preferences database to prevent real user
        # preferences from leaking into this test — only defaults
        # should be tested here. Mocking Get-GenXdevPreference does
        # not work here because it is a C# cmdlet in the same module.
        $result = GenXdev\Get-AILLMSettings `
            -LLMQueryType SimpleIntelligence `
            -SuppressAutoPrompt `
            -PreferencesDatabasePath $Script:TempDbDir

        # Boolean false should stay false (not become $null)
        $result.NoSupportForJsonSchema | Pester\Should -BeFalse
        $result.NoSupportForImageUpload | Pester\Should -BeFalse
        $result.NoSupportForToolCalls | Pester\Should -BeFalse

        # Null should stay null (not empty hashtable)
        $result.ApiKey | Pester\Should -Be $null
    }

    Pester\It 'DeepSeek providers have NoSupportForJsonSchema = true' {
        # Canary: if this breaks, default-llmproviders.json was updated and
        # the JsonSchema support flag needs manual verification
        $defaults = GenXdev\Get-AIDefaultLLMSettings -LLMQueryType SimpleIntelligence
        $deepSeekModels = $defaults | Microsoft.PowerShell.Core\Where-Object { $_.Model -like 'deepseek*' }
        $deepSeekModels.Count | Pester\Should -BeGreaterThan 0
        foreach ($ds in $deepSeekModels) {
            $ds.NoSupportForJsonSchema | Pester\Should -BeTrue `
                -Because "DeepSeek $($ds.Model) — verify manually if this changed"
        }
    }

    Pester\It 'DeepSeek providers have NoSupportForImageUpload = true' {
        # Canary: if this breaks, default-llmproviders.json was updated and
        # the image support flag needs manual verification.
        # DeepSeek is absent from Pictures because it can't handle images,
        # so query SimpleIntelligence where it does appear.
        $defaults = GenXdev\Get-AIDefaultLLMSettings -LLMQueryType SimpleIntelligence
        $deepSeekModels = $defaults | Microsoft.PowerShell.Core\Where-Object { $_.Model -like 'deepseek*' }
        $deepSeekModels.Count | Pester\Should -BeGreaterThan 0
        foreach ($ds in $deepSeekModels) {
            $ds.NoSupportForImageUpload | Pester\Should -BeTrue `
                -Because "DeepSeek $($ds.Model) — verify manually if this changed"
        }
    }

    ###########################################################################
    Pester\It ('-PromptForSettings delegates to Invoke-AILLMSettingsPrompt ' + `
            'which throws in non-interactive session') {
        {
            GenXdev\Get-AILLMSettings -LLMQueryType 'Coding' `
                -PromptForSettings -NonInteractive -ErrorAction Stop
        } | Pester\Should -Throw -ExpectedMessage '*non-interactive*'
    }
}
###############################################################################