tests/Test-PromptBackNavigation.ps1

#Requires -Version 7
$ErrorActionPreference = 'Stop'
. (Join-Path $PSScriptRoot '_TestHelpers.ps1')

$wizardRoot = Split-Path $PSScriptRoot -Parent
Import-Module (Join-Path $wizardRoot 'modules/ISC.OperatorConsole.psm1') -Force
Import-Module (Join-Path $wizardRoot 'modules/IW.Core.psm1') -Force
Import-Module (Join-Path $wizardRoot 'modules/IW.Project.psm1') -Force
Import-Module (Join-Path $wizardRoot 'modules/Definitions/Google.Connector.psm1') -Force
Import-Module (Join-Path $wizardRoot 'modules/Definitions/Google.Entro.psm1') -Force

Register-IntegrationWizardPlatform -Name 'GoogleCloud' -Title 'Google Cloud' `
    -CoreOptions @('SaaSConnector', 'CiemConnector', 'GcpEntro', 'WorkspaceEntro') -CliName 'gcloud' `
    -EntroCompanions @{
        SaaSConnector = @{ Variants = @('GcpEntro', 'WorkspaceEntro') }
    }
Register-GoogleConnectorDefinitions
Register-GoogleEntroDefinitions

# Test 1: Resolve-IntegrationWizardEntroAddon handles Esc on variant prompt by looping back to Yes/No
$script:yesNoCalls = 0
$script:choiceCalls = 0
function Mock-PromptBackFlow {
    # Redefine Read-YesNo and Read-Choice in script scope
    $script:yesNoCalls = 0
    $script:choiceCalls = 0

    function global:Read-YesNo {
        param($Prompt, $Default)
        $script:yesNoCalls++
        if ($script:yesNoCalls -eq 1) { return $true }
        return $false
    }

    function global:Read-Choice {
        param($Prompt, $Options, $Labels, $Default)
        $script:choiceCalls++
        if ($script:choiceCalls -eq 1) {
            Invoke-PromptBack
        }
        return 'GcpEntro'
    }
}

Mock-PromptBackFlow
$ctx = @{ Wizard = $true }
$connDef = Get-IntegrationWizardDefinition -Id 'google-saas-connector'
Resolve-IntegrationWizardEntroAddon -Platform 'GoogleCloud' -Context $ctx -ConnectorDefinition $connDef -Wizard

Assert-Equal 2 $script:yesNoCalls 'Read-YesNo was called twice after Esc on variants'
Assert-Equal 1 $script:choiceCalls 'Read-Choice threw PROMPT_BACK once'
Assert-True (-not $ctx['GcpEntro']) 'GcpEntro not set because user chose No on re-prompt'

# Test 2: Resolve-IntegrationWizardEntroAddon recovers and sets chosen variant on second try
$script:yesNoCalls = 0
$script:choiceCalls = 0
function global:Read-YesNo {
    param($Prompt, $Default)
    $script:yesNoCalls++
    return $true
}
function global:Read-Choice {
    param($Prompt, $Options, $Labels, $Default)
    $script:choiceCalls++
    if ($script:choiceCalls -eq 1) {
        Invoke-PromptBack
    }
    return 'GcpEntro'
}

$ctx2 = @{ Wizard = $true }
Resolve-IntegrationWizardEntroAddon -Platform 'GoogleCloud' -Context $ctx2 -ConnectorDefinition $connDef -Wizard
Assert-Equal 2 $script:yesNoCalls 'Read-YesNo called twice when user confirms Yes second time'
Assert-Equal 2 $script:choiceCalls 'Read-Choice called second time after Esc'
Assert-True ([bool]$ctx2['GcpEntro']) 'GcpEntro set after successful second try'

# Test 3: Start-IntegrationWizardFlow catches PROMPT_BACK on PickTarget without crashing
function global:Read-Choice {
    param($Prompt, $Options, $Labels, $Default)
    Invoke-PromptBack
}

$startThrown = $false
try {
    Start-IntegrationWizardFlow -Platform 'GoogleCloud' -ScriptName 'Google Cloud.ps1' -CoreOptionHelp @{} `
        -Context @{ Wizard = $true; Project = 'test-proj' } `
        -PickTarget { Invoke-PromptBack } `
        -Discover { @() } `
        -Execute { }
}
catch {
    $startThrown = $true
}
Assert-True (-not $startThrown) 'Start-IntegrationWizardFlow handles PROMPT_BACK cleanly without throwing'

# Test 4: Invoke-SailPointConnect catches PROMPT_BACK on platform menu without throwing
Import-Module (Join-Path $wizardRoot 'SailPoint.Connect.psd1') -Force
function global:Read-Choice {
    param($Prompt, $Options, $Labels, $Default)
    Invoke-PromptBack
}
$connectThrown = $false
try {
    Invoke-SailPointConnect
}
catch {
    $connectThrown = $true
}
Assert-True (-not $connectThrown) 'Invoke-SailPointConnect handles PROMPT_BACK on root menu cleanly'

# Test 5: Invoke-PromptBack throws a typed [PromptBackException]
$caughtTyped = $false
$caughtType = $null
try {
    Invoke-PromptBack
}
catch [PromptBackException] {
    $caughtTyped = $true
    $caughtType = $_.Exception.GetType().Name
}
catch {
    $caughtType = $_.Exception.GetType().Name
}
Assert-True $caughtTyped 'Invoke-PromptBack throws dedicated [PromptBackException] class'
Assert-Equal 'PromptBackException' $caughtType 'Exception type name is PromptBackException'

# Test 6: Test-PromptBack recognizes [PromptBackException] and legacy string 'PROMPT_BACK'
$exTyped = $null
try {
    Invoke-PromptBack
}
catch {
    $exTyped = $_
}
Assert-True (Test-PromptBack $exTyped) 'Test-PromptBack returns true for typed [PromptBackException] ErrorRecord'
Assert-True (Test-PromptBack $exTyped.Exception) 'Test-PromptBack returns true for typed [PromptBackException] instance'

$exLegacy = $null
try {
    throw 'PROMPT_BACK'
}
catch {
    $exLegacy = $_
}
Assert-True (Test-PromptBack $exLegacy) 'Test-PromptBack returns true for legacy string-based PROMPT_BACK'

# Test 7: Standard catch-all correctly differentiates PromptBackException from arbitrary exceptions
$otherError = $null
try {
    throw [System.InvalidOperationException]::new('Unexpected operational failure')
}
catch {
    $otherError = $_
}
Assert-True (-not (Test-PromptBack $otherError)) 'Test-PromptBack returns false for standard operational exceptions'

Write-Host "PASS ($script:AssertionCount assertions)"