Tests/Connections.Tests.ps1

#Requires -Version 5.1
# Pester v5 integration tests for the N2C.GuacAdmin connection cmdlets, run
# against the local mock Guacamole server (Tests/GuacMockServer.ps1).

$script:mock = $null
$script:credential = $null
$script:session = $null

BeforeAll {
    $script:moduleRoot = Split-Path -Parent $PSScriptRoot
    Import-Module (Join-Path $script:moduleRoot 'Tests/GuacTestHelpers.psm1') -Force
    $script:mock = Start-GuacTestMock -WorkDir $PSScriptRoot
    Import-Module (Join-Path $script:moduleRoot 'N2C.GuacAdmin.psd1') -Force
    $script:credential = [PSCredential]::new(
        'guacadmin',
        (ConvertTo-SecureString 'secret' -AsPlainText -Force)
    )
    $script:session = New-GuacSession -Server $mock.BaseUrl -Credential $credential
}

AfterAll {
    if ($null -ne $mock) {
        Stop-GuacTestMock
    }
}

Describe 'Get-GuacConnection' {
    It 'lists connections with Identifier properties' {
        $connections = @(Get-GuacConnection -Session $session)
        $connections.Count | Should -BeGreaterOrEqual 2
        $names = @($connections | ForEach-Object { $_.Name })
        $names | Should -Contain 'test-connection'
        foreach ($c in $connections) {
            $c.Identifier | Should -Not -BeNullOrEmpty
        }
    }

    It 'gets a single connection by id' {
        $conn = Get-GuacConnection -Session $session -Id 'conn-1'
        $conn.Identifier | Should -Be 'conn-1'
        $conn.Name | Should -Be 'test-connection'
        $conn.Protocol | Should -Be 'rdp'
        $conn.Parameters['hostname'] | Should -Be 'host.example.com'
    }

    It 'throws a terminating error for an unknown id' {
        { Get-GuacConnection -Session $session -Id 'does-not-exist' -ErrorAction Stop } | Should -Throw
    }

    It 'resolves ParentGroupName by default for connections with a parent group' {
        # Move conn-1 to group-1 to test parent resolution
        Update-GuacConnection -Session $session -Id 'conn-1' -Replace @{
            name = 'test-connection'
            protocol = 'rdp'
            parameters = @{ hostname = 'host.example.com' }
            parentIdentifier = 'group-1'
        } | Out-Null
        $conn = Get-GuacConnection -Session $session -Id 'conn-1'
        $conn.ParentGroupName | Should -Be 'prod'
        # Move it back
        Update-GuacConnection -Session $session -Id 'conn-1' -Replace @{
            name = 'test-connection'
            protocol = 'rdp'
            parameters = @{ hostname = 'host.example.com' }
            parentIdentifier = 'ROOT'
        } | Out-Null
    }

    It 'skips ParentGroupName resolution when -ResolveParentGroupName is false' {
        $conn = Get-GuacConnection -Session $session -Id 'conn-1' -ResolveParentGroupName:$false
        $conn.PSObject.Properties['ParentGroupName'] | Should -Be $null
    }

    It 'sets ParentGroupName to ROOT for top-level connections' {
        $conn = Get-GuacConnection -Session $session -Id 'conn-2'
        $conn.ParentGroupName | Should -Be 'ROOT'
    }

    It 'always has a lastActive property (null if never used)' {
        $conn = Get-GuacConnection -Session $session -Id 'conn-1'
        $conn.PSObject.Properties['lastActive'] | Should -Not -Be $null
    }
}

Describe 'Get-GuacConnection filters' {
    It 'filters by exact name' {
        $results = @(Get-GuacConnection -Session $session -Name 'test-connection')
        $results.Count | Should -Be 1
        $results[0].Name | Should -Be 'test-connection'
    }

    It 'filters by name wildcard' {
        $results = @(Get-GuacConnection -Session $session -Name 'ssh-*')
        $results.Count | Should -Be 1
        $results[0].Name | Should -Be 'ssh-server'
    }

    It 'filters by protocol' {
        $results = @(Get-GuacConnection -Session $session -Protocol 'ssh')
        $results.Count | Should -Be 1
        $results[0].Protocol | Should -Be 'ssh'
    }

    It 'combines name and protocol filters' {
        $results = @(Get-GuacConnection -Session $session -Name 'ssh-*' -Protocol 'ssh')
        $results.Count | Should -Be 1
        $results[0].Name | Should -Be 'ssh-server'
        $results[0].Protocol | Should -Be 'ssh'
    }

    It 'returns empty when no name matches' {
        $results = @(Get-GuacConnection -Session $session -Name 'nonexistent-*')
        $results.Count | Should -Be 0
    }
}

Describe 'New-GuacConnection' {
    It 'creates a connection and returns it with a server-assigned identifier' {
        $created = New-GuacConnection -Session $session -InputObject @{
            name = 'new-conn'
            protocol = 'telnet'
            parameters = @{ hostname = 'telnet.example.com' }
            parentIdentifier = 'ROOT'
        }
        $created.Identifier | Should -Not -BeNullOrEmpty
        $created.Name | Should -Be 'new-conn'
        $created.Protocol | Should -Be 'telnet'

        $fetched = Get-GuacConnection -Session $session -Id $created.Identifier
        $fetched.Name | Should -Be 'new-conn'
    }

    It 'auto-fills all protocol parameters from schema when only minimal params are provided' {
        $created = New-GuacConnection -Session $session -Name 'auto-fill-test' -Protocol 'ssh' -Parameters @{ hostname = 'ssh-host.example.com' }
        $created.Identifier | Should -Not -BeNullOrEmpty
        $created.Protocol | Should -Be 'ssh'

        # User-provided parameter should be present
        $created.Parameters['hostname'] | Should -Be 'ssh-host.example.com'

        # Other SSH parameters from the schema should be auto-filled with empty strings
        $created.Parameters.Keys | Should -Contain 'port'
        $created.Parameters.Keys | Should -Contain 'username'
        $created.Parameters.Keys | Should -Contain 'password'
        $created.Parameters.Keys | Should -Contain 'guac-readonly'
    }

    It 'honors -WhatIf by not creating the connection' {
        $before = @(Get-GuacConnection -Session $session).Count
        # NOTE: the "What if: ..." line printed above this test is the engine's
        # WhatIf confirmation, written straight to the host UI; it is NOT part of
        # the information stream and cannot be suppressed with -InformationAction
        # or $InformationPreference (verified on pwsh 7.6). It is expected output.
        New-GuacConnection -Session $session -Name 'whatif-conn' -Protocol 'telnet' -WhatIf
        $after = @(Get-GuacConnection -Session $session).Count
        $before | Should -Be $after
    }

    It 'throws when no body is supplied' {
        { New-GuacConnection -Session $session -ErrorAction Stop } | Should -Throw
    }
}

Describe 'Update-GuacConnection' {
    It 'replaces a connection with -Replace (PUT) and re-fetches' {
        $updated = Update-GuacConnection -Session $session -Id 'conn-2' -Replace @{
            name = 'ssh-server-renamed'
            protocol = 'ssh'
            parameters = @{ hostname = 'ssh2.example.com'; port = '22' }
            parentIdentifier = 'ROOT'
        }
        $updated.Identifier | Should -Be 'conn-2'
        $updated.Name | Should -Be 'ssh-server-renamed'
        $updated.Parameters['hostname'] | Should -Be 'ssh2.example.com'
    }

    It 'applies a JSON Patch replace operation' {
        $conn = Get-GuacConnection -Session $session -Id 'conn-1'
        $conn.Name = 'renamed-conn-1'
        Update-GuacConnection -Session $session -Id 'conn-1' -Patch (
            @{ op = 'replace'; path = '/conn-1'; value = $conn }
        )
        $fetched = Get-GuacConnection -Session $session -Id 'conn-1'
        $fetched.Name | Should -Be 'renamed-conn-1'
    }

    It 'accepts a piped connection object as the target' {
        $conn = Get-GuacConnection -Session $session -Id 'conn-1'
        $conn.Name = 'piped-update'
        $conn | Update-GuacConnection -Replace $conn
        $fetched = Get-GuacConnection -Session $session -Id 'conn-1'
        $fetched.Name | Should -Be 'piped-update'
    }

    It 'rejects supplying both -Patch and -Replace' {
        { Update-GuacConnection -Session $session -Id 'conn-1' -Replace @{ name = 'x'; protocol = 'rdp' } -Patch @{ op = 'remove'; path = '/conn-1' } -ErrorAction Stop } | Should -Throw
    }
}

Describe 'Remove-GuacConnection' {
    It 'deletes a connection by id' {
        $created = New-GuacConnection -Session $session -InputObject @{
            name = 'to-delete'; protocol = 'telnet'; parameters = @{}; parentIdentifier = 'ROOT'
        }
        Remove-GuacConnection -Session $session -Id $created.Identifier -Confirm:$false
        { Get-GuacConnection -Session $session -Id $created.Identifier -ErrorAction Stop } | Should -Throw
    }

    It 'deletes a piped connection object' {
        $created = New-GuacConnection -Session $session -InputObject @{
            name = 'to-delete-2'; protocol = 'telnet'; parameters = @{}; parentIdentifier = 'ROOT'
        }
        Get-GuacConnection -Session $session -Id $created.Identifier | Remove-GuacConnection -Confirm:$false
        { Get-GuacConnection -Session $session -Id $created.Identifier -ErrorAction Stop } | Should -Throw
    }
}