Tests/Unit/Public/Authentication.Tests.ps1

BeforeAll {
    $moduleRoot = (Resolve-Path (Join-Path $PSScriptRoot '../../..')).Path
    Import-Module (Join-Path $moduleRoot 'psSIA.psd1') -Force
}

Describe 'New-SIASession' {
    AfterEach {
        InModuleScope psSIA { $script:SIASession = $null }
    }

    It 'requests a token from the identity tenant and starts a session' {
        InModuleScope psSIA {
            Mock Invoke-RestMethod {
                param($Uri, $Body, $ContentType)
                $Uri.Host | Should -Be 'contoso.id.cyberark.cloud'
                $ContentType | Should -Be 'application/x-www-form-urlencoded'
                $Body | Should -Match 'grant_type=client_credentials'
                [pscustomobject]@{ access_token = 'abc123'; token_type = 'Bearer'; expires_in = 900 }
            }

            $cred = [PSCredential]::new('svc-account', (ConvertTo-SecureString 'p@ss' -AsPlainText -Force))
            $session = New-SIASession -Subdomain 'contoso' -IdentityTenantId 'contoso' -Credential $cred

            $session.Subdomain | Should -Be 'contoso'
            $session.ClientId | Should -Be 'svc-account'
            $session.PSObject.TypeNames | Should -Contain 'psSIA.Session'
        }
    }

    It 'never exposes the plaintext access token on the returned object' {
        InModuleScope psSIA {
            Mock Invoke-RestMethod {
                [pscustomobject]@{ access_token = 'super-secret-token'; token_type = 'Bearer'; expires_in = 900 }
            }

            $cred = [PSCredential]::new('svc-account', (ConvertTo-SecureString 'p@ss' -AsPlainText -Force))
            $session = New-SIASession -Subdomain 'contoso' -IdentityTenantId 'contoso' -Credential $cred

            $session.AccessToken | Should -BeOfType [System.Security.SecureString]
            ($session | Out-String) | Should -Not -Match 'super-secret-token'
        }
    }

    It 'throws a readable error when authentication fails' {
        InModuleScope psSIA {
            Mock Invoke-RestMethod {
                $response = [System.Net.Http.HttpResponseMessage]::new([System.Net.HttpStatusCode]::Unauthorized)
                $exception = [System.Exception]::new('Unauthorized')
                $exception | Add-Member -NotePropertyName Response -NotePropertyValue $response
                throw $exception
            }

            $cred = [PSCredential]::new('svc-account', (ConvertTo-SecureString 'wrong' -AsPlainText -Force))
            { New-SIASession -Subdomain 'contoso' -IdentityTenantId 'contoso' -Credential $cred } |
                Should -Throw '*HTTP 401*'
        }
    }
}

Describe 'Get-SIASession' {
    AfterEach {
        InModuleScope psSIA { $script:SIASession = $null }
    }

    It 'returns nothing when there is no active session' {
        InModuleScope psSIA {
            Get-SIASession | Should -BeNullOrEmpty
        }
    }

    It 'reports IsExpired based on the stored expiry' {
        InModuleScope psSIA {
            $script:SIASession = [pscustomobject]@{
                PSTypeName  = 'psSIA.Session'
                Subdomain   = 'contoso'
                AccessToken = (ConvertTo-SecureString 'x' -AsPlainText -Force)
                ExpiresAt   = (Get-Date).ToUniversalTime().AddMinutes(-5)
            }

            (Get-SIASession).IsExpired | Should -Be $true
        }
    }
}

Describe 'Close-SIASession' {
    It 'clears the active session' {
        InModuleScope psSIA {
            $script:SIASession = [pscustomobject]@{ Subdomain = 'contoso' }
            Close-SIASession -Confirm:$false
            $script:SIASession | Should -BeNullOrEmpty
        }
    }

    It 'does nothing under -WhatIf' {
        InModuleScope psSIA {
            $script:SIASession = [pscustomobject]@{ Subdomain = 'contoso' }
            Close-SIASession -WhatIf
            $script:SIASession | Should -Not -BeNullOrEmpty
        }
    }
}