Tests/VSAModule.Auth.Tests.ps1

$ModuleRoot = Split-Path -Path (Split-Path -Parent $PSScriptRoot)
$ModulePath = Join-Path -Path $ModuleRoot -ChildPath 'VSAModule.psd1'

BeforeAll {
    $script:ModuleRoot = Split-Path -Path (Split-Path -Parent $PSCommandPath)
    Import-Module (Join-Path $script:ModuleRoot 'VSAModule.psd1') -Force
    $script:Cred = New-Object System.Management.Automation.PSCredential('alice', (ConvertTo-SecureString 'PAT456' -AsPlainText -Force))
}

AfterAll {
    [Environment]::SetEnvironmentVariable('VSAConnection', $null)
}

Describe "New-VSAConnection - URL scheme validation (T-5.4)" {

    It "rejects an http:// server at parameter validation" {
        { New-VSAConnection -VSAServer 'http://vsa.example.com' -Credential $script:Cred -ErrorAction Stop } | Should -Throw
    }

    It "accepts an https:// server (fails later at auth, not at validation)" {
        InModuleScope VSAModule {
            $cred = New-Object System.Management.Automation.PSCredential('alice', (ConvertTo-SecureString 'PAT456' -AsPlainText -Force))
            Mock Get-RequestData { throw 'network down' }
            # Should reach the auth call (and throw the auth error), not the validation error.
            { New-VSAConnection -VSAServer 'https://vsa.example.com' -Credential $cred -ErrorAction Stop } |
                Should -Throw -ExpectedMessage '*Authentication failed*'
        }
    }
}

Describe "New-VSAConnection - auth failure is terminating with context (T-5.4)" {

    It "throws a terminating error naming the server and user on a bad PAT" {
        InModuleScope VSAModule {
            $cred = New-Object System.Management.Automation.PSCredential('alice', (ConvertTo-SecureString 'WRONGPAT' -AsPlainText -Force))
            Mock Get-RequestData { throw 'HTTP 401 Unauthorized' }
            $err = $null
            try { New-VSAConnection -VSAServer 'https://vsa.example.com' -Credential $cred -ErrorAction Stop }
            catch { $err = $_ }
            $err | Should -Not -BeNullOrEmpty
            $err.Exception.Message | Should -Match 'vsa\.example\.com'
            $err.Exception.Message | Should -Match 'alice'
        }
    }
}

Describe "ConvertTo-VSALocalExpiration - shared parser (T-5.4 / F-24)" {

    It "produces identical results regardless of the calling path" {
        InModuleScope VSAModule {
            $a = ConvertTo-VSALocalExpiration -SessionExpiration '2030-06-17T07:14:20Z' -OffSetInMinutes 30
            $b = ConvertTo-VSALocalExpiration -SessionExpiration '2030-06-17T07:14:20Z' -OffSetInMinutes 30
            $a | Should -Be $b
        }
    }

    It "applies the offset in minutes" {
        InModuleScope VSAModule {
            $base   = ConvertTo-VSALocalExpiration -SessionExpiration '2030-06-17T07:14:20Z' -OffSetInMinutes 0
            $offset = ConvertTo-VSALocalExpiration -SessionExpiration '2030-06-17T07:14:20Z' -OffSetInMinutes 30
            ($offset - $base).TotalMinutes | Should -Be 30
        }
    }
}

Describe "Persistent connection storage - platform-detected encryption (T-5.1 / F-60)" {

    AfterEach {
        [Environment]::SetEnvironmentVariable('VSAConnection', $null)
    }

    It "stores a blob that does not leak credentials via plain Base64 decode" {
        InModuleScope VSAModule {
            $c = [VSAConnection]::new('https://vsa.example.com', 'alice', 'TOK123', 'PAT456', [datetime]'2030-01-01T00:00:00Z', $false, $true)
            Set-VSAPersistentConnection -Connection $c
            $raw = [Environment]::GetEnvironmentVariable('VSAConnection')
            $leaked = $false
            try {
                $txt = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($raw))
                if ($txt -match 'TOK123|PAT456') { $leaked = $true }
            } catch { $leaked = $false }
            $leaked | Should -BeFalse
        }
    }

    It "round-trips all connection fields" {
        InModuleScope VSAModule {
            $c = [VSAConnection]::new('https://vsa.example.com', 'alice', 'TOK123', 'PAT456', [datetime]'2030-01-01T00:00:00Z', $true, $true)
            Set-VSAPersistentConnection -Connection $c
            Get-VSAPersistentURI      | Should -Be 'https://vsa.example.com'
            Get-VSAPersistentUserName | Should -Be 'alice'
            Get-VSAPersistentToken    | Should -Be 'TOK123'
            Get-VSAPersistentPAT      | Should -Be 'PAT456'
            Get-VSAPersistentIgnoreCertErrors | Should -BeTrue
        }
    }

    It "updates a single field in place" {
        InModuleScope VSAModule {
            $c = [VSAConnection]::new('https://vsa.example.com', 'alice', 'TOK123', 'PAT456', [datetime]'2030-01-01T00:00:00Z', $false, $true)
            Set-VSAPersistentConnection -Connection $c
            Update-VSAPersistentField -Index 1 -Value 'RENEWED'
            Get-VSAPersistentToken | Should -Be 'RENEWED'
            Get-VSAPersistentPAT   | Should -Be 'PAT456'
        }
    }
}