Tests/Unit/Private/Resolve-SCAError.Tests.ps1

Import-Module (Join-Path $PSScriptRoot '../../../psSCA.psd1') -Force

Describe 'Resolve-SCAError' {
    InModuleScope psSCA {
        BeforeAll {
            function Get-TestHttpErrorRecord {
                param([System.Net.HttpStatusCode]$StatusCode, [string]$Body)

                $responseMessage = [System.Net.Http.HttpResponseMessage]::new($StatusCode)
                $exception = [Microsoft.PowerShell.Commands.HttpResponseException]::new($StatusCode.ToString(), $responseMessage)
                $errorRecord = [System.Management.Automation.ErrorRecord]::new(
                    $exception, 'WebCmdletWebResponseException', [System.Management.Automation.ErrorCategory]::InvalidOperation, $null)
                $errorRecord.ErrorDetails = [System.Management.Automation.ErrorDetails]::new($Body)
                return $errorRecord
            }
        }

        It 'extracts status code, message, and request id from a CyberArk error body' {
            $errorRecord = Get-TestHttpErrorRecord -StatusCode 403 -Body '{"message":"Insufficient permissions.","code":"E403","requestId":"req-abc-123"}'

            $resolved = Resolve-SCAError -ErrorRecord $errorRecord -Service 'SCA' -Operation 'Get-SCAAccessRequest' -Uri 'https://contoso.sca.cyberark.cloud/api/access/sessions'

            $resolved.Exception.Message | Should -Match 'HTTP Status : 403'
            $resolved.Exception.Message | Should -Match 'Insufficient permissions\.'
            $resolved.Exception.Message | Should -Match 'req-abc-123'
            $resolved.Exception.Message | Should -Match 'Get-SCAAccessRequest'
            $resolved.CategoryInfo.Category | Should -Be 'PermissionDenied'
        }

        It 'maps HTTP status codes to the expected PowerShell error category' {
            (Resolve-SCAError -ErrorRecord (Get-TestHttpErrorRecord -StatusCode 401 -Body '{"message":"Unauthorized"}') -Service 'SCA' -Operation 'Test' -Uri 'https://x').CategoryInfo.Category |
                Should -Be 'AuthenticationError'

            (Resolve-SCAError -ErrorRecord (Get-TestHttpErrorRecord -StatusCode 404 -Body '{"message":"Not found"}') -Service 'SCA' -Operation 'Test' -Uri 'https://x').CategoryInfo.Category |
                Should -Be 'ObjectNotFound'

            (Resolve-SCAError -ErrorRecord (Get-TestHttpErrorRecord -StatusCode 429 -Body '{"message":"Rate limited"}') -Service 'SCA' -Operation 'Test' -Uri 'https://x').CategoryInfo.Category |
                Should -Be 'LimitsExceeded'
        }

        It 'redacts bearer-token-shaped text in a non-JSON error body via Protect-SCASecret' {
            $errorRecord = Get-TestHttpErrorRecord -StatusCode 401 -Body 'Gateway rejected Authorization: Bearer super-secret-token-value'

            $resolved = Resolve-SCAError -ErrorRecord $errorRecord -Service 'SCA' -Operation 'Test' -Uri 'https://contoso.sca.cyberark.cloud/api/access/sessions'

            $resolved.Exception.Message | Should -Not -Match 'super-secret-token-value'
            $resolved.Exception.Message | Should -Match 'REDACTED'
        }

        It 'falls back to the exception message when the body is not JSON' {
            $errorRecord = Get-TestHttpErrorRecord -StatusCode 500 -Body 'Internal Server Error (not JSON)'

            $resolved = Resolve-SCAError -ErrorRecord $errorRecord -Service 'SCA' -Operation 'Test' -Uri 'https://contoso.sca.cyberark.cloud/api/access/sessions'

            $resolved.Exception.Message | Should -Match 'Internal Server Error'
            $resolved.CategoryInfo.Category | Should -Be 'InvalidOperation'
        }

        It 'includes the operation, service, and endpoint in the error message' {
            $errorRecord = Get-TestHttpErrorRecord -StatusCode 403 -Body '{"message":"denied"}'

            $resolved = Resolve-SCAError -ErrorRecord $errorRecord -Service 'UAP' -Operation 'Get-SCAAccessPolicy' -Uri 'https://contoso.uap.cyberark.cloud/api/policies'

            $resolved.Exception.Message | Should -Match 'Service : UAP'
            $resolved.Exception.Message | Should -Match 'Operation : Get-SCAAccessPolicy'
            $resolved.Exception.Message | Should -Match 'Endpoint : https://contoso.uap.cyberark.cloud/api/policies'
        }

        It 'handles a non-HTTP exception (e.g. a connection failure) without a status code' {
            $exception = [System.Net.Http.HttpRequestException]::new('Connection refused')
            $errorRecord = [System.Management.Automation.ErrorRecord]::new(
                $exception, 'ConnectionFailure', [System.Management.Automation.ErrorCategory]::ConnectionError, $null)

            $resolved = Resolve-SCAError -ErrorRecord $errorRecord -Service 'SCA' -Operation 'Test' -Uri 'https://contoso.sca.cyberark.cloud/api/access/sessions'

            $resolved.Exception.Message | Should -Not -Match 'HTTP Status'
            $resolved.Exception.Message | Should -Match 'Connection refused'
            $resolved.FullyQualifiedErrorId | Should -Be 'psSCA.SCA.Test'
            $resolved.CategoryInfo.Category | Should -Be 'InvalidOperation'
        }
    }
}