Public/Api/Test-ApiCredential.ps1

function Test-ApiCredential {

    <#
        .SYNOPSIS
            Tests Azure DevOps API credentials to verify they are valid and have required permissions.

        .DESCRIPTION
            Validates API credentials by making a test call to Azure DevOps. Returns detailed information
            about the connection status, authenticated user, and permissions.

            This function is useful for:
            - Troubleshooting authentication issues (401, 403 errors)
            - Verifying PAT tokens before use
            - Checking token expiration
            - Validating permissions

        .PARAMETER CollectionUri
            The URI of the Azure DevOps collection to test against.
            If not specified, $global:AzureDevOpsApi_CollectionUri (set by Set-ApiVariables) is used.

        .PARAMETER ApiCredential
            The credentials to test.
            If not specified, $global:AzureDevOpsApi_ApiCredential (set by Set-ApiVariables) is used.

        .PARAMETER Token
            Personal Access Token (PAT) to test.
            If specified, creates a temporary ApiCredential for testing.

        .PARAMETER Quiet
            If specified, returns only $true or $false instead of detailed information.
            Useful for scripting.

        .OUTPUTS
            PSCustomObject with the following properties:
            - Success: $true if credentials are valid
            - StatusCode: HTTP status code (200 = success, 401 = unauthorized, 403 = forbidden)
            - User: Authenticated user information (if successful)
            - CollectionUri: The collection URI tested
            - ErrorMessage: Error details (if failed)

            When -Quiet is specified, returns $true or $false.

        .EXAMPLE
            Test-ApiCredential

            Tests the currently configured credentials (from Set-ApiVariables).

        .EXAMPLE
            Test-ApiCredential -Token "your-pat-token-here" -CollectionUri "https://dev.azure.com/myorg"

            Tests a specific PAT token against a collection.

        .EXAMPLE
            if (Test-ApiCredential -Quiet) {
                Write-Host "$(Get-Emoji checkmark) Credentials are valid"
            } else {
                Write-Host "$(Get-Emoji cross) Credentials are invalid"
            }

            Quick validation in a script.

        .EXAMPLE
            $result = Test-ApiCredential
            if ($result.Success) {
                Write-Host "$(Get-Emoji checkmark) Connected as: $($result.User.displayName)"
                Write-Host " Email: $($result.User.mailAddress)"
            } else {
                Write-Host "$(Get-Emoji cross) Failed: $($result.ErrorMessage)"
            }

            Detailed validation with user information.

        .LINK
            Add-ApiCredential
        .LINK
            Set-ApiVariables
    #>


    [CmdletBinding(DefaultParameterSetName = 'Default')]
    [OutputType([PSCustomObject])]
    [OutputType([bool])]
    param(
        [Parameter(ParameterSetName = 'Default')]
        [Parameter(ParameterSetName = 'Token')]
        [AllowNull()]
        [AllowEmptyString()]
        [Alias('Uri')]
        $CollectionUri,

        [Parameter(ParameterSetName = 'Default')]
        [AllowNull()]
        [PSTypeName('PSTypeNames.AzureDevOpsApi.ApiCredential')]
        [PSCustomObject] $ApiCredential,

        [Parameter(ParameterSetName = 'Token', Mandatory)]
        [string] $Token,

        [switch] $Quiet
    )

    process {

        # If Token is provided, create temporary ApiCredential
        if ($PSCmdlet.ParameterSetName -eq 'Token') {
            $ApiCredential = New-ApiCredential -Token $Token -Authorization 'PAT'
        }

        # Build result object
        $result = [PSCustomObject]@{
            PSTypeName    = 'PSTypeNames.AzureDevOpsApi.TestCredentialResult'
            Success       = $false
            StatusCode    = $null
            User          = $null
            CollectionUri = $CollectionUri
            ErrorMessage  = $null
        }

        try {
            # Attempt to get current user (simple API call that requires authentication)
            $user = Get-CurrentUser `
                -CollectionUri $CollectionUri `
                -ApiCredential $ApiCredential

            # If we got here, credentials are valid
            $result.Success = $true
            $result.StatusCode = 200
            $result.User = $user

            if (-not $Quiet) {
                Write-Verbose "$(Get-Emoji checkmark) Credentials are valid"

                # Build user info string (properties may be null with default credentials)
                $userInfo = @()
                if ($user.displayName) { $userInfo += $user.displayName }
                if ($user.mailAddress) { $userInfo += "($($user.mailAddress))" }
                if ($user.providerDisplayName -and -not $user.displayName) { $userInfo += $user.providerDisplayName }

                if ($userInfo.Count -gt 0) {
                    Write-Verbose " User: $($userInfo -join ' ')"
                }
                Write-Verbose " Collection: $($result.CollectionUri)"
            }

        }
        catch {
            # Parse error information
            $result.Success = $false
            $result.ErrorMessage = $_.Exception.Message

            # Try to extract HTTP status code
            if ($_.Exception.Response) {
                $result.StatusCode = [int]$_.Exception.Response.StatusCode
            }
            elseif ($_.Exception.Message -match '\((\d{3})\)') {
                $result.StatusCode = [int]$matches[1]
            }

            # Provide helpful error messages
            if (-not $Quiet) {
                switch ($result.StatusCode) {
                    401 {
                        Write-Warning "$(Get-Emoji cross) 401 Unauthorized: Credentials are invalid or expired"
                        Write-Warning " Check that your PAT token is set correctly and hasn't expired"
                    }
                    403 {
                        Write-Warning "$(Get-Emoji cross) 403 Forbidden: Credentials lack required permissions"
                        Write-Warning " Ensure your PAT has at least 'Read' access to the organization"
                    }
                    default {
                        Write-Warning "$(Get-Emoji cross) Connection failed: $($result.ErrorMessage)"
                    }
                }
            }

            Write-Verbose "Error details: $($_.Exception | Format-List * | Out-String)"
        }

        # Return result
        if ($Quiet) {
            return $result.Success
        }
        else {
            return $result
        }
    }
}