Public/Tests/Set-ModuleConfig.Tests.ps1

BeforeAll {
    $env:TCS_CONFIG_ROOT = Join-Path -Path $TestDrive -ChildPath 'config'
    $env:TCS_SKIP_UPDATE_CHECK = '1'
    $env:TCS_TELEMETRY_OPTOUT = '1'
    $ModuleRoot = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent
    Import-Module -Name (Join-Path -Path $ModuleRoot -ChildPath 'tcs.core.psd1') -Force
}

AfterAll {
    Remove-Module -Name tcs.core -Force -ErrorAction SilentlyContinue
}

Describe 'Set-ModuleConfig' {
    BeforeEach {
        $configFile = Join-Path -Path $env:TCS_CONFIG_ROOT -ChildPath 'tcs.test/Module.Config.json'
        if (Test-Path -Path $configFile) { Remove-Item -Path $configFile -Force }
    }

    It 'Creates the settings file from defaults when it does not exist' {
        Set-ModuleConfig -ModuleName 'tcs.test' -UpdateWarning $false
        $saved = Get-Content -Path $configFile -Raw | ConvertFrom-Json
        $saved.UpdateWarning | Should -BeFalse
        $saved.Telemetry | Should -BeTrue
        $saved.UpdateCheckIntervalHours | Should -Be 24
    }

    It 'Only changes the settings that are passed' {
        Set-ModuleConfig -ModuleName 'tcs.test' -Telemetry $false
        Set-ModuleConfig -ModuleName 'tcs.test' -UpdateWarning $false
        $saved = Get-Content -Path $configFile -Raw | ConvertFrom-Json
        $saved.Telemetry | Should -BeFalse
        $saved.UpdateWarning | Should -BeFalse
    }

    It 'Converts legacy string booleans to real booleans' {
        $null = New-Item -Path (Split-Path $configFile) -ItemType Directory -Force
        '{ "UpdateWarning": "True", "BasicTelemetry": "True" }' | Set-Content -Path $configFile
        $result = Set-ModuleConfig -ModuleName 'tcs.test' -Telemetry $false -PassThru
        $result.UpdateWarning | Should -BeOfType [bool]
        $result.UpdateWarning | Should -BeTrue
    }

    It 'Restores defaults with -Reset' {
        Set-ModuleConfig -ModuleName 'tcs.test' -UpdateWarning $false -Telemetry $false
        $result = Set-ModuleConfig -ModuleName 'tcs.test' -Reset -PassThru
        $result.UpdateWarning | Should -BeTrue
        $result.Telemetry | Should -BeTrue
    }

    It 'Accepts an explicit settings file path' {
        $path = Join-Path -Path $TestDrive -ChildPath 'custom/tcs.custom/Module.Config.json'
        Set-ModuleConfig -ModuleConfigFilePath $path -UpdateCheckIntervalHours 48
        (Get-Content -Path $path -Raw | ConvertFrom-Json).UpdateCheckIntervalHours | Should -Be 48
    }

    It 'Rejects a non-HTTPS telemetry endpoint' {
        { Set-ModuleConfig -ModuleName 'tcs.test' -TelemetryUri 'http://example.com' } | Should -Throw
    }

    It 'Does not write anything with -WhatIf' {
        Set-ModuleConfig -ModuleName 'tcs.test' -UpdateWarning $false -WhatIf
        Test-Path -Path $configFile | Should -BeFalse
    }

    It 'Uses the module''s own defaults with -Reset' {
        $fakeModule = Join-Path -Path $TestDrive -ChildPath 'mods/tcs.resetfake'
        $null = New-Item -Path (Join-Path $fakeModule 'Config') -ItemType Directory -Force
        "@{ ModuleVersion = '1.0.0'; RootModule = 'tcs.resetfake.psm1' }" | Set-Content -Path (Join-Path $fakeModule 'tcs.resetfake.psd1')
        '' | Set-Content -Path (Join-Path $fakeModule 'tcs.resetfake.psm1')
        '{ "BaseUrl": "https://example.com", "UpdateWarning": false }' | Set-Content -Path (Join-Path $fakeModule 'Config/Module.Defaults.json')
        $null = Get-ModuleConfig -CommandPath (Join-Path $fakeModule 'tcs.resetfake.psm1')
        Set-ModuleConfig -ModuleName 'tcs.resetfake' -Setting @{ BaseUrl = 'https://other.example.com' } -UpdateWarning $true

        $result = Set-ModuleConfig -ModuleName 'tcs.resetfake' -Reset -PassThru
        $result.BaseUrl | Should -Be 'https://example.com'
        $result.UpdateWarning | Should -BeFalse
    }

    It 'Finds the module defaults of an available module that is not loaded' {
        $modules = Join-Path -Path $TestDrive -ChildPath 'psmodules'
        $fakeModule = Join-Path -Path $modules -ChildPath 'tcs.availfake'
        $null = New-Item -Path (Join-Path $fakeModule 'Config') -ItemType Directory -Force
        "@{ ModuleVersion = '1.0.0'; RootModule = 'tcs.availfake.psm1' }" | Set-Content -Path (Join-Path $fakeModule 'tcs.availfake.psd1')
        '' | Set-Content -Path (Join-Path $fakeModule 'tcs.availfake.psm1')
        '{ "PageSize": 50 }' | Set-Content -Path (Join-Path $fakeModule 'Config/Module.Defaults.json')
        $oldPath = $env:PSModulePath
        try {
            $env:PSModulePath = $modules + [System.IO.Path]::PathSeparator + $env:PSModulePath
            $result = Set-ModuleConfig -ModuleName 'tcs.availfake' -Reset -PassThru
            $result.PageSize | Should -Be 50
            (Set-ModuleConfig -ModuleName 'tcs.availfake' -Setting @{ PageSize = '75' } -PassThru).PageSize | Should -BeExactly 75
        }
        finally {
            $env:PSModulePath = $oldPath
        }
    }

    It 'Sets arbitrary settings with -Setting, typed against the defaults' {
        $result = Set-ModuleConfig -ModuleName 'tcs.test' -Setting @{ UpdateWarning = 'false'; CustomValue = 'abc' } -PassThru
        $result.UpdateWarning | Should -BeExactly $false
        $result.CustomValue | Should -Be 'abc'
        (Get-Content -Path $configFile -Raw | ConvertFrom-Json).CustomValue | Should -Be 'abc'
    }

    It 'Lets explicit parameters win over -Setting' {
        $result = Set-ModuleConfig -ModuleName 'tcs.test' -Setting @{ Telemetry = $true } -Telemetry $false -PassThru
        $result.Telemetry | Should -BeFalse
    }

    It 'Rejects invalid values in -Setting' {
        { Set-ModuleConfig -ModuleName 'tcs.test' -Setting @{ UpdateCheckIntervalHours = -5 } } | Should -Throw '*between 1 and 8760*'
        { Set-ModuleConfig -ModuleName 'tcs.test' -Setting @{ Telemetry = 'maybe' } } | Should -Throw
        { Set-ModuleConfig -ModuleName 'tcs.test' -Setting @{ TelemetryUri = 'http://example.com' } } | Should -Throw
        { Set-ModuleConfig -ModuleName 'tcs.test' -Setting @{ ModulePath = 'x' } } | Should -Throw
        Test-Path -Path $configFile | Should -BeFalse
    }

    It 'Rejects module names that are not a single safe folder name' -ForEach @(
        @{ Name = '../../escaped' }, @{ Name = '..' }, @{ Name = 'a/b' }, @{ Name = 'a\b' }, @{ Name = '.hidden' }
    ) {
        { Set-ModuleConfig -ModuleName $Name -Telemetry $false } | Should -Throw
        Test-Path -Path (Join-Path $TestDrive 'escaped') | Should -BeFalse
    }

    It 'Repairs an invalid stored value when saving' {
        $null = New-Item -Path (Split-Path $configFile) -ItemType Directory -Force
        '{ "UpdateCheckIntervalHours": -5 }' | Set-Content -Path $configFile
        (Set-ModuleConfig -ModuleName 'tcs.test' -Telemetry $false -PassThru).UpdateCheckIntervalHours | Should -Be 24
    }

    It 'Updates the configuration of the current session' {
        $null = Get-ModuleConfig -CommandPath (Join-Path $ModuleRoot 'tcs.core.psm1')
        Set-ModuleConfig -ModuleName 'tcs.core' -Telemetry $false
        InModuleScope tcs.core { $script:ModuleConfigCache['tcs.core'].Telemetry } | Should -BeFalse
        Set-ModuleConfig -ModuleName 'tcs.core' -Reset
    }
}

Describe 'Set-ModuleConfig telemetry API key' {
    BeforeEach {
        $configFile = Join-Path -Path $env:TCS_CONFIG_ROOT -ChildPath 'tcs.keytest/Module.Config.json'
        if (Test-Path -Path $configFile) { Remove-Item -Path $configFile -Force }
    }

    It 'Stores the key protected, never in plain text' {
        Set-ModuleConfig -ModuleName 'tcs.keytest' -TelemetryApiKey 'SECRET123'
        $raw = Get-Content -Path $configFile -Raw
        $raw | Should -Not -Match 'SECRET123'
        $stored = ($raw | ConvertFrom-Json).TelemetryApiKey
        $stored | Should -Match '^tcs:v1:'
        Unprotect-ConfigValue -EncryptedValue $stored | Should -BeExactly 'SECRET123'
    }

    It 'Masks the key in -PassThru output' {
        $result = Set-ModuleConfig -ModuleName 'tcs.keytest' -TelemetryApiKey 'SECRET123' -PassThru
        $result.TelemetryApiKey | Should -Be '********'
        ($result | Out-String) | Should -Not -Match 'SECRET123'
    }

    It 'Protects a plain-text key left by an earlier version when saving' {
        $null = New-Item -Path (Split-Path $configFile) -ItemType Directory -Force
        '{ "TelemetryApiKey": "OLDPLAIN" }' | Set-Content -Path $configFile
        Set-ModuleConfig -ModuleName 'tcs.keytest' -Telemetry $true
        (Get-Content -Path $configFile -Raw) | Should -Not -Match 'OLDPLAIN'
    }

    It 'Clears the key with an empty string' {
        Set-ModuleConfig -ModuleName 'tcs.keytest' -TelemetryApiKey 'SECRET123'
        Set-ModuleConfig -ModuleName 'tcs.keytest' -TelemetryApiKey ''
        (Get-Content -Path $configFile -Raw | ConvertFrom-Json).TelemetryApiKey | Should -BeExactly ''
    }
}