public/Invoke-HttpUnit.ps1

function Invoke-HttpUnit {
<#
.SYNOPSIS
    A PowerShell port of httpunit.
.DESCRIPTION
    This is not a 100% accurate port of httpunit. The goal of this module is to utilize Net.Http.HttpClient to more closely simulate a .Net client application. It also provides easy access to the Windows Certificate store for client certificate authentication.
.PARAMETER Path
    Specifies a path to a configuration file with a list of tests. Supported types are .toml, .yml, and .psd1.
.PARAMETER Tag
    If specified, only runs plans that are tagged with one of the tags specified.
.PARAMETER Url
    The URL to retrieve.
.PARAMETER Code
    For http/https, the expected status code, default 200.
.PARAMETER String
    For http/https, a string we expect to find in the result.
.PARAMETER Headers
    For http/https, a hashtable to validate the response headers.
.PARAMETER Timeout
    A timeout for the test. Default is 3 seconds.
.PARAMETER Certificate
    For http/https, specifies the client certificate that is used for a secure web request. Enter a variable that contains a certificate.
.PARAMETER Method
    For http/https, the HTTP method to send.
.PARAMETER Quiet
    Do not output ErrorRecords for failed tests.
.EXAMPLE
    PS > Invoke-HttpUnit -Url https://www.google.com -Code 200
    Label : https://www.google.com/
    Result :
    Connected : True
    GotCode : True
    GotText : False
    GotRegex : False
    GotHeaders : False
    InvalidCert : False
    TimeTotal : 00:00:00.4695217

    Run an ad-hoc test against one Url.

    .EXAMPLE
    PS > Invoke-HttpUnit -Path .\example.toml
    Label : google
    Result :
    Connected : True
    GotCode : True
    GotText : False
    GotRegex : False
    GotHeaders : False
    InvalidCert : False
    TimeTotal : 00:00:00.3210709
    Label : api
    Result : Exception calling "GetResult" with "0" argument(s): "No such host is known. (api.example.com:80)"
    Connected : False
    GotCode : False
    GotText : False
    GotRegex : False
    GotHeaders : False
    InvalidCert : False
    TimeTotal : 00:00:00.0280893
    Label : redirect
    Result : Unexpected status code: NotFound
    Connected : True
    GotCode : False
    GotText : False
    GotRegex : False
    GotHeaders : False
    InvalidCert : False
    TimeTotal : 00:00:00.1021738

    Run all of the tests in a given config file.
.NOTES
    A $null Results property signifies no error and all specified
    test criteria passed.

    You can use the common variable -OutVariable to save the test results. Each TestResult object has a hidden Response property with the raw response from the server.
.LINK
    https://github.com/StackExchange/httpunit
.LINK
    https://github.com/cdhunt/Import-ConfigData
#>


    [CmdletBinding(DefaultParameterSetName = 'url')]
    [Alias('httpunit', 'Test-Http', 'ihu')]
    param (
        [Parameter(Mandatory,
            Position = 0,
            ParameterSetName = 'config-file',
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = "Path to one or more locations.")]
        [Alias('PSPath')]
        [ValidateNotNullOrEmpty()]
        [string]
        $Path,

        [Parameter(Position = 1,
            ParameterSetName = 'config-file')]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Tag,

        [Parameter(Mandatory,
            Position = 0,
            ParameterSetName = 'url',
            ValueFromPipelineByPropertyName = $true)]
        [Alias('Address', 'ComputerName')]
        [string]
        $Url,

        [Parameter(Position = 1,
            ParameterSetName = 'url',
            ValueFromPipelineByPropertyName = $true)]
        [Alias('StatusCode')]
        [string]
        $Code,

        [Parameter(Position = 2,
            ParameterSetName = 'url',
            ValueFromPipelineByPropertyName = $true)]
        [Alias('Text')]
        [string]
        $String,

        [Parameter(Position = 3,
            ParameterSetName = 'url',
            ValueFromPipelineByPropertyName = $true)]
        [hashtable]
        $Headers,

        [Parameter(Position = 4,
            ParameterSetName = 'url',
            ValueFromPipelineByPropertyName = $true)]
        [timespan]
        $Timeout,

        [Parameter(Position = 5,
            ParameterSetName = 'url',
            ValueFromPipelineByPropertyName = $true)]
        [X509Certificate]
        $Certificate,

        [Parameter(Position = 6,
            ParameterSetName = 'url',
            ValueFromPipelineByPropertyName = $true)]
        [ValidateSet('Connect', 'Delete', 'Get', 'Head', 'Options', 'Patch', 'Post', 'Put', 'Trace')]
        [String]
        $Method,

        [Parameter()]
        [Switch]
        $Quiet
    )

    if ($PSBoundParameters.ContainsKey('Path')) {
        Write-Debug "Running checks defined in '$Path'"


        $configObject = Import-ConfigData -Path $Path

        foreach ($plan in $configObject['plan']) {
            $testPlan = [TestPlan]@{
                Label = $plan['label']
            }

            switch ($plan.Keys) {
                'label' { $testPlan.Label = $plan[$_] }
                'url' { $testPlan.Url = $plan[$_] }
                'method' { $testPlan.Method = $plan[$_] }
                'code' { $testPlan.Code = $plan[$_] }
                'string' { $testPlan.Text = $plan[$_] }
                'timeout' { $testPlan.Timeout = [timespan]$plan[$_] }
                'tags' { $testPlan.Tags = $plan[$_] }
                'headers' { $testPlan.Headers = $plan[$_] }
                'certficate' {
                    $value = $plan[$_]
                    if ($value -like 'cert:\*') {
                        $testPlan.ClientCertificate = Get-Item $value
                    }
                    else {
                        $testPlan.ClientCertificate = (Get-Item "Cert:\LocalMachine\My\$value")
                    }
                }
                'insecureSkipVerify' { $testPlan.InsecureSkipVerify = $plan[$_] }
            }

            # Filter tests
            if ($PSBoundParameters.ContainsKey('Tag')) {
                $found = $false
                foreach ($t in $Tag) {
                    if ($testPlan.Tags -contains $t) { $found = $true }
                }
                if (!$found) {
                    $testTags = $testPlan.Tags -join ', '
                    $filterTags = $Tag -join ', '
                    Write-Debug "Specified tags ($filterTags) do not match defined tags ($testTags)"
                    Continue
                }
            }

            foreach ($case in $testPlan.Cases()) {
                $case.Test()
            }
        }
    }
    else {
        $plan = [TestPlan]::new()
        $plan.URL = $Url

        switch ($PSBoundParameters.Keys) {
            'Code' { $plan.Code = $Code }
            'String' { $plan.Text = $String }
            'Headers' { $plan.Headers = $Headers }
            'Timeout' { $plan.Timeout = $Timeout }
            'Certificate' { $plan.ClientCertificate = $Certificate }
            'Method' { $plan.Method = $Method }
        }

        foreach ($case in $plan.Cases()) {
            $result = $case.Test()

            Write-Output $result
            if ($null -ne $result.Result -and !$Quiet) {
                Write-Error -ErrorRecord $result.Result
            }
        }
    }
}