Public/Tests/Get-HttpErrorDetail.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 'Get-HttpErrorDetail' {
    BeforeAll {
        $RepoRoot = Split-Path -Path (Split-Path -Path $ModuleRoot -Parent) -Parent
        . (Join-Path -Path $RepoRoot -ChildPath 'tests/Helpers/HttpTestDoubles.ps1')
    }

    Context 'Windows PowerShell 5.1 (WebException with HttpWebResponse)' {
        It 'Returns the status code, description, URI and the body read from the response' {
            $record = New-TestErrorRecord -Exception (New-TestWebException -StatusCode 404 -Description 'Not Found' -Body '{"error":"missing"}')
            $detail = Get-HttpErrorDetail -ErrorRecord $record
            $detail.StatusCode | Should -BeExactly 404
            $detail.StatusDescription | Should -Be 'Not Found'
            $detail.Body | Should -Be '{"error":"missing"}'
            $detail.Uri | Should -Be 'https://api.example.com/items'
            $detail.RetryAfterSeconds | Should -BeNullOrEmpty
        }

        It 'Prefers ErrorDetails.Message for the body' {
            $record = New-TestErrorRecord -Exception (New-TestWebException -StatusCode 400 -Body 'stream body') -ErrorDetails 'details body'
            (Get-HttpErrorDetail -ErrorRecord $record).Body | Should -Be 'details body'
        }

        It 'Reads Retry-After in seconds and as an HTTP date' {
            (Get-HttpErrorDetail -ErrorRecord (New-TestErrorRecord -Exception (New-TestWebException -StatusCode 429 -RetryAfter '12'))).RetryAfterSeconds | Should -Be 12
            $date = [datetime]::UtcNow.AddSeconds(60).ToString('r', [System.Globalization.CultureInfo]::InvariantCulture)
            $seconds = (Get-HttpErrorDetail -ErrorRecord (New-TestErrorRecord -Exception (New-TestWebException -StatusCode 503 -RetryAfter $date))).RetryAfterSeconds
            $seconds | Should -BeGreaterThan 55
            $seconds | Should -BeLessOrEqual 60
        }

        It 'Accepts an exception and finds a wrapped response' {
            $wrapped = New-Object System.Management.Automation.MethodInvocationException -ArgumentList 'Exception calling "GetResponse"', (New-TestWebException -StatusCode 502)
            (Get-HttpErrorDetail -ErrorRecord $wrapped).StatusCode | Should -Be 502
        }
    }

    Context 'PowerShell 7 (HttpResponseException with HttpResponseMessage)' -Skip:($PSVersionTable.PSEdition -eq 'Desktop') {
        It 'Returns the status code, reason, URI, body and Retry-After' {
            $exception = New-TestHttpResponseException -StatusCode 429 -Reason 'Too Many Requests' -Body 'slow down' -RetryAfter ([timespan]::FromSeconds(3))
            $detail = Get-HttpErrorDetail -ErrorRecord (New-TestErrorRecord -Exception $exception)
            $detail.StatusCode | Should -BeExactly 429
            $detail.StatusDescription | Should -Be 'Too Many Requests'
            $detail.Body | Should -Be 'slow down'
            $detail.RetryAfterSeconds | Should -Be 3
            $detail.Uri | Should -Be 'https://api.example.com/items'
        }

        It 'Prefers ErrorDetails.Message for the body' {
            $record = New-TestErrorRecord -Exception (New-TestHttpResponseException -StatusCode 500 -Body 'content') -ErrorDetails '{"message":"boom"}'
            (Get-HttpErrorDetail -ErrorRecord $record).Body | Should -Be '{"message":"boom"}'
        }

        It 'Reads the status code of an HttpRequestException' {
            $exception = New-Object System.Net.Http.HttpRequestException -ArgumentList 'failed', $null, ([System.Net.HttpStatusCode]::BadGateway)
            (Get-HttpErrorDetail -ErrorRecord $exception).StatusCode | Should -Be 502
        }
    }

    It 'Returns nothing for an error without an HTTP response' {
        try { throw [System.Net.Sockets.SocketException]::new() } catch { $record = $_ }
        Get-HttpErrorDetail -ErrorRecord $record | Should -BeNullOrEmpty
        $noResponse = New-Object System.Net.WebException -ArgumentList 'Name resolution failed', ([System.Net.WebExceptionStatus]::NameResolutionFailure)
        Get-HttpErrorDetail -ErrorRecord $noResponse | Should -BeNullOrEmpty
    }

    It 'Accepts errors from the pipeline' {
        $records = @(
            (New-TestErrorRecord -Exception (New-TestWebException -StatusCode 401)),
            (New-TestErrorRecord -Exception (New-TestWebException -StatusCode 403))
        )
        ($records | Get-HttpErrorDetail).StatusCode | Should -Be @(401, 403)
    }
}