Private/Runtime/Tests/Invoke-OpenApiHttpExchange.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 (Split-Path -Path $PSScriptRoot -Parent) -Parent) -Parent
    Import-Module -Name (Join-Path -Path $ModuleRoot -ChildPath 'tcs.openapi.psd1') -Force
    # Windows PowerShell 5.1 does not load System.Net.Http by default
    Add-Type -AssemblyName 'System.Net.Http'
    $RepoRoot = Split-Path -Path (Split-Path -Path $ModuleRoot -Parent) -Parent
    . (Join-Path -Path $RepoRoot -ChildPath 'tests/Helpers/TestHttpServer.ps1')
}

AfterAll {
    Remove-Module -Name tcs.openapi -Force -ErrorAction SilentlyContinue
}

Describe 'Invoke-OpenApiHttpExchange' {
    BeforeAll {
        $script:server = Start-TestHttpServer -Handler { param($Request) @{ Body = @{ ok = $true } } }
        InModuleScope -ModuleName tcs.openapi -Parameters @{ BaseUri = $script:server.BaseUri } {
            $script:testContext = New-OpenApiContext -Service 'Ex' -BaseUri $BaseUri -ApiKey ((New-Object -TypeName System.Net.NetworkCredential -ArgumentList '', 'k').SecurePassword) -MaxRetries 2
            $script:testClient = Get-OpenApiHttpClient -Context $script:testContext
            $script:testOperation = @{ Security = @(@{ q = @() }); SecuritySchemes = @{ q = @{ Type = 'apiKey'; In = 'query'; ParameterName = 'key' } } }
        }
        Mock -ModuleName tcs.openapi -CommandName Get-OpenApiRetryPolicy -MockWith {
            [pscustomobject]@{ StatusCodes = @(429, 503); MaxRetries = $MaxRetries; DelaySeconds = 0; BackoffMultiplier = 1; MaxDelaySeconds = 1; JitterPercent = 0; RetryConnectionFailure = $false }
        }
    }

    AfterAll {
        $script:server.Stop()
    }

    BeforeEach {
        $script:server.Requests.Clear()
    }

    It 'adds auth to the request and returns the response with an unread body' {
        $response = InModuleScope -ModuleName tcs.openapi -Parameters @{ BaseUri = $script:server.BaseUri } {
            Invoke-OpenApiHttpExchange -Context $script:testContext -Operation $script:testOperation -Client $script:testClient -Method 'GET' -Uri "$BaseUri/x" -Header @{ 'X-A' = '1' } -Cookie @('c=1')
        }
        $response.StatusCode | Should -Be 200
        $response.Message | Should -Not -BeNullOrEmpty
        $response.Message.Dispose()
        $script:server.Requests[0].Query | Should -Be '?key=k'
        $script:server.Requests[0].Headers['X-A'] | Should -Be '1'
        $script:server.Requests[0].Headers['Cookie'] | Should -Be 'c=1'
    }

    It 'does not add query credentials a next-page link already has' {
        $null = InModuleScope -ModuleName tcs.openapi -Parameters @{ BaseUri = $script:server.BaseUri } {
            $response = Invoke-OpenApiHttpExchange -Context $script:testContext -Operation $script:testOperation -Client $script:testClient -Method 'GET' -Uri "$BaseUri/x?page=2&key=k"
            $response.Message.Dispose()
        }
        $script:server.Requests[0].Query | Should -Be '?page=2&key=k'
    }

    It 'retries retryable statuses and returns the last response when retries are used up' {
        foreach ($i in 1..3) {
            $script:server.Enqueue(@{ StatusCode = 503; Body = "busy $i" })
        }
        $response = InModuleScope -ModuleName tcs.openapi -Parameters @{ BaseUri = $script:server.BaseUri } {
            Invoke-OpenApiHttpExchange -Context $script:testContext -Operation $script:testOperation -Client $script:testClient -Method 'GET' -Uri "$BaseUri/x"
        }
        $response.StatusCode | Should -Be 503
        [System.Text.Encoding]::UTF8.GetString($response.Body) | Should -Be 'busy 3'
        $script:server.Requests.Count | Should -Be 3
    }

    It 'rebuilds the body for every attempt' {
        $script:server.Enqueue(@{ StatusCode = 429 })
        $null = InModuleScope -ModuleName tcs.openapi -Parameters @{ BaseUri = $script:server.BaseUri } {
            $factory = { New-OpenApiHttpContent -Body @{ a = 1 } -ContentType 'application/json' }
            $response = Invoke-OpenApiHttpExchange -Context $script:testContext -Operation $script:testOperation -Client $script:testClient -Method 'POST' -Uri "$BaseUri/x" -ContentFactory $factory
            $response.Message.Dispose()
        }
        $script:server.Requests.Count | Should -Be 2
        $script:server.Requests[1].Body | Should -Be '{"a":1}'
    }

    It 'throws transport failures' {
        InModuleScope -ModuleName tcs.openapi {
            $probe = New-Object System.Net.Sockets.TcpListener -ArgumentList ([System.Net.IPAddress]::Loopback), 0
            $probe.Start()
            $port = ([System.Net.IPEndPoint]$probe.LocalEndpoint).Port
            $probe.Stop()
            { Invoke-OpenApiHttpExchange -Context $script:testContext -Operation @{ Security = @() } -Client $script:testClient -Method 'GET' -Uri "http://127.0.0.1:$port/" } | Should -Throw -ExceptionType ([System.Net.Http.HttpRequestException])
        }
    }
}