tests/HttPowerShell.tests.ps1

Describe "Invoke-HTTP Tests" {
    BeforeAll {
        # Import the module or script containing Invoke-HTTP
        Import-Module "$PSScriptRoot\..\HTTPowerShell.psd1" -Force
        $TestHost = "httpbun.com"
        $TestURL = "https://$TestHost"
        # Disable WriteColour colours, as it screws up the validation.
        $env:DISABLE_WC_COLOURS = 1
    }

    Context "Basic Parameter Validation" {
        It "Should throw an error if no Uri is provided" {
            { Invoke-Http -Method GET } | Should -Throw
        }

        It "Should default to GET method if no method is provided" {
            $Result = Invoke-Http $TestURL
            $Result | Should -Not -BeNullOrEmpty
        }
    }

    Context "Alias" -Tag 'Alias' {
        It "should work with the 'web' alias" {
            $Result = web $TestURL
            $Result | Should -Not -BeNullOrEmpty
        }
    }

    Context "Display Options" -Tag 'Display' {
        BeforeAll {
            $DisplayPath = "/status/200"
            $DisplayURL = "$TestURL$DisplayPath"
        }

        It "should show request URI" {
            $Result = Invoke-Http $DisplayURL -Display U
            $Result | Should -BeLike "*$DisplayURL*"
        }

        It "should show request line" {
            $Result = Invoke-Http $DisplayURL -Display R
            $Result | Should -BeLike "GET $DisplayPath*"
        }
        
        It "should show request headers" {
            $Result = Invoke-Http $DisplayURL -Display H
            $Result | Should -Contain "Host: $TestHost"
        }
        
        It "should show the status code with version and description" {
            $Result = Invoke-Http $DisplayURL -Display s
            $Result | Should -Be "HTTP/1.1 200 OK"
        }
        It "should show just the status code" {
            $Result = Invoke-Http $DisplayURL -Display S
            $Result | Should -Be "200"
        }
        It 'should limit request headers' {
            $Result = Invoke-HTTP $DisplayURL -Display H -DisplayHeaders 'host'
            $Result[0] | Should -BeLike "Host: $TestHost*"
            $Result.count | Should -Be 2
        }
        It 'should limit response headers' {
            $Result = Invoke-HTTP $DisplayURL -Display h -DisplayHeaders 'Content-Type', 'date'
            $Result[0] | Should -BeLike 'Content-Type:*'
            $Result[1] | Should -BeLike 'Date:*'
            $Result.count | Should -Be 3
        }
    }

    Context 'Request headers' -Tag 'Headers' {
        BeforeAll {
            $HeaderName1 = 'X-Test-Header'
            $HeaderName2 = 'X-Test-Header2'
            $HeaderValue = 'TestValue'
            $RequestParams = @{
                Uri            = "$TestURL/anything"
                Display        = 'H'
                DisplayHeaders = $HeaderName1
            }
        }
        It 'should send custom request headers using standard IWR format' {
            $Result = Invoke-Http @RequestParams -Headers @{ $HeaderName1 = $HeaderValue }
            $Result[0] | Should -Be "$HeaderName1`: $HeaderValue"
        }
        It 'should send custom request headers using custom format' {
            $Result = Invoke-Http @RequestParams "$HeaderName1`: $HeaderValue"
            $Result[0] | Should -Be "$HeaderName1`: $HeaderValue"
        }
        It 'should send multiple custom request headers using custom format' {
            $Result = Invoke-Http @RequestParams "$HeaderName1`: $HeaderValue" "$HeaderName2`: $HeaderValue" -DisplayHeaders $HeaderName1, $HeaderName2
            $Result[0] | Should -Be "$HeaderName1`: $HeaderValue"
            $Result[1] | Should -Be "$HeaderName2`: $HeaderValue"
        }
    }

    Context 'Request cookies' -Tag 'Cookies' {
        BeforeAll {
            $CookieName1 = 'melove'
            $CookieName2 = 'mealsolove'
            $CookieValue = 'cookiiiiieeeee'
            $RequestParams = @{
                Uri            = "$TestURL/anything"
                Display        = 'H'
                DisplayHeaders = 'cookie'
            }
        }
        It 'should send custom request cookies' {
            $Result = Invoke-Http @RequestParams "$CookieName1`==$CookieValue"
            $Result[0] | Should -Be "cookie: $CookieName1=$CookieValue"
        }
        It 'should send multiple custom request cookies' {
            $Result = Invoke-Http @RequestParams "$CookieName1`==$CookieValue" "$CookieName2`==$CookieValue"
            $Result[0] | Should -Be "cookie: $CookieName1=$CookieValue;$CookieName2=$CookieValue"
        }
    }

    Context 'Request queryparams' -Tag 'QueryParams' {
        BeforeAll {
            $QueryParamName1 = 'question'
            $QueryParamName2 = 'anotherquestion'
            $QueryParamValue1 = 'who are you'
            $EncodedQueryParamValue1 = [System.Web.HttpUtility]::UrlEncode($QueryParamValue1)
            $QueryParamValue2 = 'what do you want'
            $EncodedQueryParamValue2 = [System.Web.HttpUtility]::UrlEncode($QueryParamValue2)
            $RequestParams = @{
                Uri            = "$TestURL/anything"
                Display        = 'U'
                DisplayHeaders = 'cookie'
            }
        }
        It 'should send custom request queryparams' {
            $Result = Invoke-Http @RequestParams "$QueryParamName1`=$QueryParamValue1"
            $Result | Should -Be "$TestURL/anything?$QueryParamName1=$EncodedQueryParamValue1"
        }
        It 'should send multiple custom request queryparams' {
            $Result = Invoke-Http @RequestParams "$QueryParamName1`=$QueryParamValue1" "$QueryParamName2`=$QueryParamValue2"
            $Result | Should -Be "$TestURL/anything?$QueryParamName1=$EncodedQueryParamValue1&$QueryParamName2=$EncodedQueryParamValue2"
        }
    }

    Context 'Combine headers, queries & cookies' -Tag 'Combo' {
        BeforeAll {
            $HeaderName = 'X-Test-Header'
            $HeaderValue = 'TestValue'
            $CookieName = 'melove'
            $CookieValue = 'cookiiiiieeeee'
            $QueryParamName = 'question'
            $QueryParamValue = 'who are you'
            $EncodedQueryParamValue = [System.Web.HttpUtility]::UrlEncode($QueryParamValue)
            $RequestParams = @{
                Uri            = "$TestURL/anything"
                Display        = 'UH'
                DisplayHeaders = 'cookie', $HeaderName
            }
        }
        It 'should send custom request headers, cookies and queryparams' {
            $Result = Invoke-Http @RequestParams "$HeaderName`: $HeaderValue" "$CookieName`==$CookieValue" "$QueryParamName`=$QueryParamValue"
            $Result[0] | Should -Be "$TestURL/anything?$QueryParamName=$EncodedQueryParamValue"
            $Result[1] | Should -Be "cookie: $CookieName=$CookieValue"
            $Result[2] | Should -Be "$HeaderName`: $HeaderValue"
        }
    }

    Context "HTTP Method Validation" -Tag 'Method' {
        It "Should accept standard HTTP methods" -ForEach @("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "TRACE") {
            $Result = Invoke-Http -Uri "$TestURL/any" -Method $_ -Display S
            $Result | Should -Be 200
        }

        It "Should complete, even for an unsupported HTTP methods" {
            Invoke-Http -Uri "https://example.com" -Method "INVALID"
        }
    }

    Context "Authentication Parameter Validation" -Tag 'Authentication' {
        Context 'Basic' -Tag 'Basic' {
            It "Should handle Basic authentication" {
                $Path = "/basic-auth/user/passwd"
                $Credentials = New-Object System.Management.Automation.PSCredential ("user", (ConvertTo-SecureString "passwd" -AsPlainText -Force))
                $Result = Invoke-Http -Uri "$TestURL$Path" -Authentication Basic -Credential $Credentials -Display S
                $Result | Should -Be 200
            }
            It "Should fail Basic authentication with invalid creds" {
                $Path = "/basic-auth/user/passwd"
                $Credentials = New-Object System.Management.Automation.PSCredential ("user", (ConvertTo-SecureString "wrongpasswd" -AsPlainText -Force))
                $Result = Invoke-Http -Uri "$TestURL$Path" -Authentication Basic -Credential $Credentials -Display S
                $Result | Should -Be 401
            }
        }
        Context 'EdgeGrid' -Tag 'EdgeGrid' {
            BeforeAll {
                $EdgeGridHost1 = 'akab-h05tnam3wl42son7nktnlnnx-11111111.luna.akamaiapis.net'
                $EdgeGridHost2 = 'akab-h05tnam3wl42son7nktnlnnx-22222222.luna.akamaiapis.net'
                $EdgeGridAccessToken = 'akab-acc35t0k3nodujqunph3w7hzp7-gtm6ij'
                $EdgeGridClientToken = 'akab-c113ntt0k3n4qtari252bfxxbsl-yvsdj'
                $EdgeGridClientSecret = 'C113nt53KR3TN6N90yVuAgICxIRwsObLi0E67/N8eRN='

                $TestEdgeRCFile = 'TestDrive:/.edgerc'
                $TestSection1 = 'default'
                $TestSection2 = 'pester'
                $ExportParams = @{
                    HostName     = $EdgeGridHost1
                    AccessToken  = $EdgeGridAccessToken
                    ClientToken  = $EdgeGridClientToken
                    ClientSecret = $EdgeGridClientSecret
                    EdgeRCFile   = $TestEdgeRCFile
                }
                Export-EdgegridCredentials @ExportParams -Section $TestSection1
                Export-EdgegridCredentials @ExportParams -Section $TestSection2 -HostName $EdgeGridHost2
            }
            Context 'Real' {
                It "Should handle EdgeGrid authentication from the default (real) .edgerc file" {
                    if ((Test-Path ~/.edgerc)) {
                        $Result = Invoke-Http -Uri '/papi/v1/contracts' -Authentication EdgeGrid -Display S
                        $Result | Should -Be 200
                    }
                    else {
                        Write-Warning "No .edgerc file found in home directory. Skipping test."
                    }
                }
            }
            Context 'Mocked' {
                BeforeAll {
                    $TestParams = @{
                        Uri            = '/papi/v1/contracts'
                        Authentication = 'EdgeGrid'
                        EdgeRCFile     = $TestEdgeRCFile
                        Display        = 'U'
                    }
                    Mock 'Invoke-WebRequest' {
                        return @{
                            StatusCode = 200
                            Content    = 'Mocked Response'
                        }
                    }
                }
                It "Should read credenials from a custom .edgerc file" {
                    $Result = Invoke-Http @TestParams
                    $Result | Should -BeLike "*https://$EdgeGridHost1/papi/v1/contracts*"
                }
                It "Should read credenials from a custom .edgerc file and section" {
                    $Result = Invoke-Http @TestParams -Section $TestSection2
                    $Result | Should -BeLike "*https://$EdgeGridHost2/papi/v1/contracts*"
                }
            }
        }

        Context 'Client Certs' -Tag 'mtls' {
            BeforeAll {
                $ClientCertPath = "$PSScriptRoot\data\badssl.com-client.pem"
                $ClientKeyPath = "$PSScriptRoot\data\badssl.com-client.key"
                $ClienEKeyPath = "$PSScriptRoot\data\badssl.com-client.ekey"
                $ClientCert = Get-Content -Raw -Path $ClientCertPath
                $ClientKey = Get-Content -Raw -Path $ClientKeyPath
                $ClientEKey = Get-Content -Raw -Path $ClienEKeyPath
                $SecureClientKeyPassword = ConvertTo-SecureString 'badssl.com' -AsPlainText -Force
            }
            It 'succeeds when reading non-encrypted data from files' {
                $TestParams = @{
                    Uri                   = 'https://client.badssl.com/'
                    ClientCertificateFile = $ClientCertPath
                    ClientKeyFile         = $ClientKeyPath
                    Display               = 'S'
                }
                $Result = Invoke-Http @TestParams
                $Result | Should -Be 200
            }
            It 'succeeds when reading non-encrypted data from variables' {
                $TestParams = @{
                    Uri               = 'https://client.badssl.com/'
                    ClientCertificate = $ClientCert
                    ClientKey         = $ClientKey
                    Display           = 'S'
                }
                $Result = Invoke-Http @TestParams
                $Result | Should -Be 200
            }
            It 'succeeds when reading data from a mixture of files and variables' {
                $TestParams = @{
                    Uri                   = 'https://client.badssl.com/'
                    ClientCertificateFile = $ClientCertPath
                    ClientKey             = $ClientKey
                    Display               = 'S'
                }
                $Result = Invoke-Http @TestParams
                $Result | Should -Be 200
            }
            It 'succeeds when using an encrypted key from a file, with a plaintext password provided' {
                $TestParams = @{
                    Uri                   = 'https://client.badssl.com/'
                    ClientCertificateFile = $ClientCertPath
                    ClientKeyFile         = $ClienEKeyPath
                    ClientKeyPassword     = 'badssl.com'
                    Display               = 'S'
                }
                $Result = Invoke-Http @TestParams
                $Result | Should -Be 200
            }
            It 'succeeds when using an encrypted key from a variable, with a plaintext password provided' {
                $TestParams = @{
                    Uri                   = 'https://client.badssl.com/'
                    ClientCertificateFile = $ClientCertPath
                    ClientKey             = $ClientEKey
                    ClientKeyPassword     = 'badssl.com'
                    Display               = 'S'
                }
                $Result = Invoke-Http @TestParams
                $Result | Should -Be 200
            }
            It 'succeeds when using an encrypted key from a file, with a secure password provided' {
                $TestParams = @{
                    Uri                     = 'https://client.badssl.com/'
                    ClientCertificateFile   = $ClientCertPath
                    ClientKeyFile           = $ClienEKeyPath
                    SecureClientKeyPassword = $SecureClientKeyPassword
                    Display                 = 'S'
                }
                $Result = Invoke-Http @TestParams
                $Result | Should -Be 200
            }
            It 'succeeds when using an encrypted key from a variable, with a secure password provided' {
                $TestParams = @{
                    Uri                     = 'https://client.badssl.com/'
                    ClientCertificateFile   = $ClientCertPath
                    ClientKey               = $ClientEKey
                    SecureClientKeyPassword = $SecureClientKeyPassword
                    Display                 = 'S'
                }
                $Result = Invoke-Http @TestParams
                $Result | Should -Be 200
            }
            # It 'succeeds when using an encrypted key from a variable, with a prompted password' {
            # $TestParams = @{
            # Uri = 'https://client.badssl.com/'
            # ClientCertificateFile = $ClientCertPath
            # ClientKey = $ClientEKey
            # Display = 'S'
            # }
            # $Result = Invoke-Http @TestParams
            # $Result | Should -Be 200
            # }
        }
    }

    Context 'HTTP Versions' -Tag 'HttpVersion' {
        BeforeAll {
            $TestParams = @{
                Uri     = "$TestURL/status/200"
                Display = 's'
            }
        }
        It 'shows HTTP 1.0 was used when specified' {
            $Result = Invoke-Http @TestParams -Http1
            $Result | Should -Be 'HTTP/1.0 200 OK'
        }
        It 'shows HTTP 1.1 was used when specified' {
            $Result = Invoke-Http @TestParams -Http11
            $Result | Should -Be 'HTTP/1.1 200 OK'
        }
        It 'shows HTTP 2.0 was used when specified' {
            $Result = Invoke-Http @TestParams -Http2
            $Result | Should -Be 'HTTP/2.0 200 OK'
        }
        # TODO: figure out how to get h3 tested. Might need a special test site
        # It 'shows HTTP 3.0 was used when specified' {
        # $Result = Invoke-Http @TestParams -Http3
        # $Result | Should -Be 'HTTP/3.0 200 OK'
        # }
        It 'defaults to HTTP 1.1 when not specified' {
            $Result = Invoke-Http @TestParams
            $Result | Should -Be 'HTTP/1.1 200 OK'
        }
        It 'accepts HTTP 1.0 when specified as a string' {
            $Result = Invoke-Http @TestParams -HttpVersion '1.0'
            $Result | Should -Be 'HTTP/1.0 200 OK'
        }
        It 'accepts HTTP 1.1 when specified as a string' {
            $Result = Invoke-Http @TestParams -HttpVersion '1.1'
            $Result | Should -Be 'HTTP/1.1 200 OK'
        }
        It 'accepts HTTP 2.0 when specified as a string' {
            $Result = Invoke-Http @TestParams -HttpVersion '2.0'
            $Result | Should -Be 'HTTP/2.0 200 OK'
        }
        # It 'accepts HTTP 3.0 when specified as a string' {
        # $Result = Invoke-Http @TestParams -HttpVersion '3.0'
        # $Result | Should -Be 'HTTP/3.0 200 OK'
        # }
    }

    Context 'Resolve' -Tag 'Resolve' {
        It 'resolves to the same hostname' {
            $TestParams = @{
                Uri     = $TestURL
                Resolve = $TestHost
                Display = 'S'
            }
            $Result = Invoke-Http @TestParams
            $Result | Should -Be 200

        }
        It 'fails when resolving to a mismatched hostname' {
            $TestParams = @{
                Uri         = $TestURL
                Resolve     = 'bananas.com'
                Display     = 'S'
                ErrorAction = 'Stop'
            }
            { Invoke-Http @TestParams -ErrorAction Stop } | Should -Throw
        }
        It 'does not fail when resolving to a mismatched hostname with -SkipCertificateCheck' {
            $TestParams = @{
                Uri                  = $TestURL
                Resolve              = 'bananas.com'
                Display              = 'S'
                ErrorAction          = 'Stop'
                SkipCertificateCheck = $true
            }
            Invoke-Http @TestParams -ErrorAction Stop
        }
        It 'resolves to Akamai Staging' {
            $TestParams = @{
                Uri            = 'https://www.akamai.com/'
                Resolve        = 'AkamaiStaging'
                Display        = 'h'
                DisplayHeaders = 'X-Akamai-Staging'
            }
            $Result = Invoke-Http @TestParams
            $Result[0] | Should -BeLike "X-Akamai-Staging: ESSL*"
        }
    }

    AfterAll {
        $env:DISABLE_WC_COLOURS = $null
    }
}