Public/Test-PatServer.ps1

function Test-PatServer {
    <#
    .SYNOPSIS
        Tests connectivity to a stored Plex server.

    .DESCRIPTION
        Validates that a stored server configuration works by attempting to connect
        and authenticate with the Plex server. Returns connection status information
        including whether the server is reachable, authenticated, and basic server details.

    .PARAMETER Name
        The name of the stored server to test. Use Get-PatStoredServer to see available servers.

    .PARAMETER Quiet
        If specified, returns only a boolean indicating success/failure instead of
        detailed connection information.

    .EXAMPLE
        Test-PatServer -Name 'Home'

        Tests connectivity to the stored server named 'Home' and returns detailed status.

    .EXAMPLE
        Test-PatServer -Name 'Home' -Quiet

        Tests connectivity and returns $true if successful, $false otherwise.

    .EXAMPLE
        Get-PatStoredServer | ForEach-Object { Test-PatServer -Name $_.name }

        Tests all stored servers and returns their connection status.

    .EXAMPLE
        if (Test-PatServer -Name 'Home' -Quiet) {
            Get-PatLibrary -ServerName 'Home'
        }

        Checks server connectivity before attempting operations.

    .OUTPUTS
        PlexAutomationToolkit.ServerTestResult (default)
        Returns an object with properties:
        - Name: Server name from configuration
        - Uri: The URI used for connection
        - IsConnected: Whether the server responded
        - IsAuthenticated: Whether authentication succeeded
        - FriendlyName: Server's friendly name (if connected)
        - Version: Plex server version (if connected)
        - Error: Error message (if connection failed)

        System.Boolean (with -Quiet)
        Returns $true if connection succeeded, $false otherwise.
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject], [bool])]
    param (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Name,

        [Parameter(Mandatory = $false)]
        [switch]
        $Quiet
    )

    process {
        $result = [PSCustomObject]@{
            PSTypeName      = 'PlexAutomationToolkit.ServerTestResult'
            Name            = $Name
            Uri             = $null
            IsConnected     = $false
            IsAuthenticated = $false
            FriendlyName    = $null
            Version         = $null
            Error           = $null
        }

        try {
            # Get the stored server configuration
            $server = Get-PatStoredServer -Name $Name -ErrorAction 'Stop'
            $result.Uri = $server.uri

            # Try to resolve context (this handles local/remote URI selection)
            $serverContext = Resolve-PatServerContext -ServerName $Name -ErrorAction 'Stop'
            $result.Uri = $serverContext.Uri

            # Try to get server info
            $uri = Join-PatUri -BaseUri $serverContext.Uri -Endpoint '/'
            $serverInfo = Invoke-PatApi -Uri $uri -Headers $serverContext.Headers -ErrorAction 'Stop'

            $result.IsConnected = $true
            $result.IsAuthenticated = $true
            $result.FriendlyName = $serverInfo.friendlyName
            $result.Version = $serverInfo.version

            Write-Verbose "Successfully connected to '$Name' at $($result.Uri)"
        }
        catch {
            $errorMessage = $_.Exception.Message
            $innerException = $_.Exception.InnerException

            # Categorize errors by checking exception types first (more robust than regex)
            $isAuthError = $false
            $isConnectionError = $false

            # Check for HTTP status code in WebException or HttpRequestException
            if ($innerException -is [System.Net.WebException]) {
                $webResponse = $innerException.Response
                if ($webResponse -and $webResponse.StatusCode -eq 401) {
                    $isAuthError = $true
                }
                elseif ($innerException.Status -eq [System.Net.WebExceptionStatus]::ConnectFailure -or
                        $innerException.Status -eq [System.Net.WebExceptionStatus]::NameResolutionFailure -or
                        $innerException.Status -eq [System.Net.WebExceptionStatus]::Timeout) {
                    $isConnectionError = $true
                }
            }
            elseif ($innerException -is [System.Net.Http.HttpRequestException]) {
                # Check for status code property (available in .NET 5+)
                if ($innerException.PSObject.Properties['StatusCode'] -and $innerException.StatusCode -eq 401) {
                    $isAuthError = $true
                }
            }

            # Fall back to message pattern matching if exception type checks didn't categorize
            if (-not $isAuthError -and -not $isConnectionError) {
                if ($errorMessage -match '\b401\b|Unauthorized') {
                    $isAuthError = $true
                }
                elseif ($errorMessage -match 'Unable to connect|ConnectFailure|NameResolutionFailure|timed?\s*out|unreachable|The remote name could not be resolved') {
                    $isConnectionError = $true
                }
            }

            # Set result based on categorization
            if ($isAuthError) {
                $result.IsConnected = $true
                $result.IsAuthenticated = $false
                $result.Error = 'Authentication failed - token may be invalid or expired'
            }
            elseif ($isConnectionError) {
                $result.IsConnected = $false
                $result.IsAuthenticated = $false
                $result.Error = "Server unreachable: $errorMessage"
            }
            else {
                $result.Error = $errorMessage
            }

            Write-Verbose "Connection test failed for '$Name': $errorMessage"
        }

        if ($Quiet) {
            $result.IsConnected -and $result.IsAuthenticated
        }
        else {
            $result
        }
    }
}