psmux 3.3.4

Terminal multiplexer for Windows - tmux alternative for PowerShell and Windows Terminal
# Issue #221: run-shell: program not found
# Tests that run-shell works correctly from ALL code paths:
#   CLI dispatch (main.rs), TCP handler (connection.rs), config (config.rs)
# Proves shell resolution, error handling, background mode, arg parsing all work.

$ErrorActionPreference = "Continue"
$PSMUX = (Get-Command psmux -EA Stop).Source
$SESSION = "test_issue221"
$psmuxDir = "$env:USERPROFILE\.psmux"
$script:TestsPassed = 0
$script:TestsFailed = 0

function Write-Pass($msg) { Write-Host "  [PASS] $msg" -ForegroundColor Green; $script:TestsPassed++ }
function Write-Fail($msg) { Write-Host "  [FAIL] $msg" -ForegroundColor Red; $script:TestsFailed++ }

function Cleanup {
    & $PSMUX kill-session -t $SESSION 2>&1 | Out-Null
    Start-Sleep -Milliseconds 500
    Remove-Item "$psmuxDir\$SESSION.*" -Force -EA SilentlyContinue
}

function Send-TcpCommand {
    param([string]$Session, [string]$Command)
    $port = (Get-Content "$psmuxDir\$Session.port" -Raw).Trim()
    $key = (Get-Content "$psmuxDir\$Session.key" -Raw).Trim()
    $tcp = [System.Net.Sockets.TcpClient]::new("127.0.0.1", [int]$port)
    $tcp.NoDelay = $true
    $stream = $tcp.GetStream()
    $writer = [System.IO.StreamWriter]::new($stream)
    $reader = [System.IO.StreamReader]::new($stream)
    $writer.Write("AUTH $key`n"); $writer.Flush()
    $authResp = $reader.ReadLine()
    if ($authResp -ne "OK") { $tcp.Close(); return "AUTH_FAILED" }
    $writer.Write("$Command`n"); $writer.Flush()
    $stream.ReadTimeout = 10000
    try { $resp = $reader.ReadLine() } catch { $resp = "TIMEOUT" }
    $tcp.Close()
    return $resp
}

# === SETUP ===
Cleanup
Start-Process -FilePath $PSMUX -ArgumentList "new-session","-s",$SESSION,"-d" -WindowStyle Hidden
Start-Sleep -Seconds 4

& $PSMUX has-session -t $SESSION 2>$null
if ($LASTEXITCODE -ne 0) {
    Write-Fail "Session creation failed"
    exit 1
}

Write-Host "`n=== Issue #221: run-shell Tests ===" -ForegroundColor Cyan

# ================================================================
# Part A: CLI Path (main.rs dispatch)
# ================================================================
Write-Host "`n--- Part A: CLI Path ---" -ForegroundColor Magenta

# Test 1: Basic echo via run-shell
Write-Host "`n[Test 1] run-shell with echo" -ForegroundColor Yellow
$output = & $PSMUX run-shell "echo MARKER_221_HELLO" 2>&1 | Out-String
if ($output -match "MARKER_221_HELLO") { Write-Pass "run-shell echo works" }
else { Write-Fail "Expected MARKER_221_HELLO in output, got: $output" }

# Test 2: run alias works same as run-shell
Write-Host "`n[Test 2] 'run' alias" -ForegroundColor Yellow
$output = & $PSMUX run "echo ALIAS_221" 2>&1 | Out-String
if ($output -match "ALIAS_221") { Write-Pass "'run' alias works" }
else { Write-Fail "Expected ALIAS_221 in output, got: $output" }

# Test 3: run-shell with no arguments shows usage
Write-Host "`n[Test 3] run-shell no args" -ForegroundColor Yellow
$output = & $PSMUX run-shell 2>&1 | Out-String
if ($output -match "usage.*run-shell") { Write-Pass "No args shows usage" }
else { Write-Fail "Expected usage message, got: $output" }

# Test 4: run-shell with background flag
Write-Host "`n[Test 4] run-shell -b (background)" -ForegroundColor Yellow
$marker = "$env:TEMP\psmux_221_bg_marker.txt"
Remove-Item $marker -Force -EA SilentlyContinue
& $PSMUX run-shell -b "echo BG_DONE > '$marker'" 2>&1 | Out-Null
Start-Sleep -Seconds 2
if (Test-Path $marker) { Write-Pass "Background run-shell created marker file" }
else { Write-Fail "Background run-shell did not create marker file" }
Remove-Item $marker -Force -EA SilentlyContinue

# Test 5: run-shell with PowerShell command (pipeline)
Write-Host "`n[Test 5] run-shell with PS pipeline" -ForegroundColor Yellow
$output = & $PSMUX run-shell "Write-Output 'PS_WORKS_221'" 2>&1 | Out-String
if ($output -match "PS_WORKS_221") { Write-Pass "PowerShell pipeline in run-shell" }
else { Write-Fail "Expected PS_WORKS_221, got: $output" }

# Test 6: run-shell with quoted command
Write-Host "`n[Test 6] run-shell with quoted command" -ForegroundColor Yellow
$output = & $PSMUX run-shell "Write-Output 'hello world'" 2>&1 | Out-String
if ($output -match "hello world") { Write-Pass "Quoted command works" }
else { Write-Fail "Expected 'hello world', got: $output" }

# Test 7: run-shell exit code forwarding
Write-Host "`n[Test 7] run-shell exit code" -ForegroundColor Yellow
& $PSMUX run-shell "exit 0" 2>&1 | Out-Null
$ec0 = $LASTEXITCODE
& $PSMUX run-shell "exit 42" 2>&1 | Out-Null
$ec42 = $LASTEXITCODE
if ($ec0 -eq 0) { Write-Pass "Exit code 0 forwarded" }
else { Write-Fail "Expected exit code 0, got $ec0" }
if ($ec42 -eq 42) { Write-Pass "Exit code 42 forwarded" }
else { Write-Fail "Expected exit code 42, got $ec42" }

# Test 8: run-shell with nonexistent command (error handling)
Write-Host "`n[Test 8] run-shell error for nonexistent cmd" -ForegroundColor Yellow
$output = & $PSMUX run-shell "nonexistent_command_xyz_221" 2>&1 | Out-String
# Should get a PowerShell error, not crash
if ($output -match "not recognized|not found|CommandNotFoundException") { 
    Write-Pass "Nonexistent command gives error, not crash" 
} else { 
    Write-Fail "Unexpected output for nonexistent cmd: $output" 
}

# ================================================================
# Part B: TCP Server Path (connection.rs)
# ================================================================
Write-Host "`n--- Part B: TCP Server Path ---" -ForegroundColor Magenta

# Test 9: TCP run-shell with echo
Write-Host "`n[Test 9] TCP run-shell echo" -ForegroundColor Yellow
$resp = Send-TcpCommand -Session $SESSION -Command "run-shell `"echo TCP_MARKER_221`""
if ($resp -match "TCP_MARKER_221") { Write-Pass "TCP run-shell echo works" }
else { Write-Fail "Expected TCP_MARKER_221, got: $resp" }

# Test 10: TCP run alias
Write-Host "`n[Test 10] TCP 'run' alias" -ForegroundColor Yellow
$resp = Send-TcpCommand -Session $SESSION -Command "run `"echo TCP_ALIAS_221`""
if ($resp -match "TCP_ALIAS_221") { Write-Pass "TCP 'run' alias works" }
else { Write-Fail "Expected TCP_ALIAS_221, got: $resp" }

# Test 11: TCP run-shell no args
Write-Host "`n[Test 11] TCP run-shell no args" -ForegroundColor Yellow
$resp = Send-TcpCommand -Session $SESSION -Command "run-shell"
if ($resp -match "usage.*run-shell") { Write-Pass "TCP no args shows usage" }
else { Write-Fail "Expected usage, got: $resp" }

# Test 12: TCP run-shell with nonexistent command
Write-Host "`n[Test 12] TCP run-shell nonexistent cmd" -ForegroundColor Yellow
$resp = Send-TcpCommand -Session $SESSION -Command "run-shell nonexistent_xyz_221"
if ($resp -match "not recognized|not found|CommandNotFoundException") { 
    Write-Pass "TCP nonexistent cmd returns error" 
} else { 
    Write-Fail "Expected error for nonexistent, got: $resp" 
}

# Test 13: TCP run-shell background mode (verifies -b flag is accepted and does not error)
Write-Host "`n[Test 13] TCP run-shell -b" -ForegroundColor Yellow
# Background mode spawns and returns immediately; we verify it did not error
# by checking the session is still alive afterward
$resp = Send-TcpCommand -Session $SESSION -Command "run-shell -b `"echo background_test`""
Start-Sleep -Seconds 1
& $PSMUX has-session -t $SESSION 2>$null
if ($LASTEXITCODE -eq 0) { Write-Pass "TCP background run-shell accepted, session alive" }
else { Write-Fail "Session died after TCP background run-shell" }

# ================================================================
# Part C: Edge Cases
# ================================================================
Write-Host "`n--- Part C: Edge Cases ---" -ForegroundColor Magenta

# Test 14: run-shell with command that writes to stderr
Write-Host "`n[Test 14] run-shell stderr capture" -ForegroundColor Yellow
$output = & $PSMUX run-shell "Write-Error 'STDERR_TEST_221' 2>&1" 2>&1 | Out-String
if ($output -match "STDERR_TEST_221") { Write-Pass "stderr captured" }
else { Write-Fail "stderr not captured, got: $output" }

# Test 15: run-shell with empty string after flags
Write-Host "`n[Test 15] run-shell -b with empty cmd" -ForegroundColor Yellow
$output = & $PSMUX run-shell -b 2>&1 | Out-String
# Should show usage or silently do nothing - not crash
Write-Pass "run-shell -b no cmd did not crash"

# Test 16: run-shell with tilde expansion
Write-Host "`n[Test 16] run-shell tilde expansion" -ForegroundColor Yellow
$output = & $PSMUX run-shell "Write-Output `$env:USERPROFILE" 2>&1 | Out-String
if ($output.Trim().Length -gt 0) { Write-Pass "run-shell can access env vars" }
else { Write-Fail "Expected USERPROFILE path, got empty" }

# Test 17: run-shell with .ps1 script
Write-Host "`n[Test 17] run-shell with .ps1 file" -ForegroundColor Yellow
$testScript = "$env:TEMP\psmux_221_test_script.ps1"
"Write-Output 'PS1_SCRIPT_WORKS_221'" | Set-Content $testScript -Encoding UTF8
$output = & $PSMUX run-shell "`"$testScript`"" 2>&1 | Out-String
if ($output -match "PS1_SCRIPT_WORKS_221") { Write-Pass ".ps1 script via run-shell works" }
else { Write-Fail "Expected PS1_SCRIPT_WORKS_221, got: $output" }
Remove-Item $testScript -Force -EA SilentlyContinue

# Test 18: run-shell with pwsh -Command prefix (should not double-wrap)
Write-Host "`n[Test 18] run-shell with 'pwsh' prefix" -ForegroundColor Yellow
$output = & $PSMUX run-shell "pwsh -NoProfile -Command `"Write-Output 'NOWRAP_221'`"" 2>&1 | Out-String
if ($output -match "NOWRAP_221") { Write-Pass "pwsh prefix not double-wrapped" }
else { Write-Fail "Expected NOWRAP_221, got: $output" }

# ================================================================
# Part D: Config File Integration
# ================================================================
Write-Host "`n--- Part D: Config with run-shell ---" -ForegroundColor Magenta

# Test 19: Config file with run-shell
Write-Host "`n[Test 19] Config file run-shell" -ForegroundColor Yellow
$cfgSession = "test_221_cfg"
$cfgMarker = "$env:TEMP\psmux_221_cfg_marker.txt"
Remove-Item $cfgMarker -Force -EA SilentlyContinue
& $PSMUX kill-session -t $cfgSession 2>&1 | Out-Null
Remove-Item "$psmuxDir\$cfgSession.*" -Force -EA SilentlyContinue

$confFile = "$env:TEMP\psmux_test_221.conf"
@"
run-shell "echo CFG_RUN > '$cfgMarker'"
"@ | Set-Content $confFile -Encoding UTF8

$env:PSMUX_CONFIG_FILE = $confFile
Start-Process -FilePath $PSMUX -ArgumentList "new-session","-s",$cfgSession,"-d" -WindowStyle Hidden
$env:PSMUX_CONFIG_FILE = $null
Start-Sleep -Seconds 5

& $PSMUX has-session -t $cfgSession 2>$null
if ($LASTEXITCODE -eq 0) { Write-Pass "Session with run-shell config started" }
else { Write-Fail "Session with run-shell config failed to start" }

if (Test-Path $cfgMarker) { Write-Pass "Config run-shell executed" }
else { Write-Fail "Config run-shell marker not found (may be async timing)" }

& $PSMUX kill-session -t $cfgSession 2>&1 | Out-Null
Remove-Item "$psmuxDir\$cfgSession.*" -Force -EA SilentlyContinue
Remove-Item $cfgMarker -Force -EA SilentlyContinue
Remove-Item $confFile -Force -EA SilentlyContinue

# Test 20: Config with run-shell calling nonexistent program (should not crash session)
Write-Host "`n[Test 20] Config with bad run-shell does not crash" -ForegroundColor Yellow
$cfgSession2 = "test_221_badcfg"
& $PSMUX kill-session -t $cfgSession2 2>&1 | Out-Null
Remove-Item "$psmuxDir\$cfgSession2.*" -Force -EA SilentlyContinue

$badConf = "$env:TEMP\psmux_test_221_bad.conf"
@"
run-shell "nonexistent_program_221_bad"
set -g status-left "[SURVIVED]"
"@ | Set-Content $badConf -Encoding UTF8

$env:PSMUX_CONFIG_FILE = $badConf
Start-Process -FilePath $PSMUX -ArgumentList "new-session","-s",$cfgSession2,"-d" -WindowStyle Hidden
$env:PSMUX_CONFIG_FILE = $null
Start-Sleep -Seconds 5

& $PSMUX has-session -t $cfgSession2 2>$null
if ($LASTEXITCODE -eq 0) { 
    Write-Pass "Session survived bad run-shell in config" 
    # Verify that subsequent config lines still applied
    $sl = (& $PSMUX show-options -g -v "status-left" -t $cfgSession2 2>&1 | Out-String).Trim()
    if ($sl -match "SURVIVED") { Write-Pass "Config lines after bad run-shell still applied" }
    else { Write-Fail "Config lines after bad run-shell not applied, got: $sl" }
} else { 
    Write-Fail "Session crashed due to bad run-shell in config" 
}

& $PSMUX kill-session -t $cfgSession2 2>&1 | Out-Null
Remove-Item "$psmuxDir\$cfgSession2.*" -Force -EA SilentlyContinue
Remove-Item $badConf -Force -EA SilentlyContinue

# ================================================================
# Part E: Win32 TUI Visual Verification
# ================================================================
Write-Host "`n--- Part E: Win32 TUI Visual Verification ---" -ForegroundColor Magenta

Write-Host ("=" * 60)
Write-Host "Win32 TUI VISUAL VERIFICATION"
Write-Host ("=" * 60)

$SESSION_TUI = "issue221_tui_proof"
& $PSMUX kill-session -t $SESSION_TUI 2>&1 | Out-Null
Remove-Item "$psmuxDir\$SESSION_TUI.*" -Force -EA SilentlyContinue
Start-Sleep -Milliseconds 500

$proc = Start-Process -FilePath $PSMUX -ArgumentList "new-session","-s",$SESSION_TUI -PassThru
Start-Sleep -Seconds 4

& $PSMUX has-session -t $SESSION_TUI 2>$null
if ($LASTEXITCODE -ne 0) {
    Write-Fail "TUI session did not start"
} else {
    # TUI Check 1: Session responds to display-message
    Write-Host "`n[TUI 1] Session responds to format variables" -ForegroundColor Yellow
    $name = (& $PSMUX display-message -t $SESSION_TUI -p '#{session_name}' 2>&1 | Out-String).Trim()
    if ($name -eq $SESSION_TUI) { Write-Pass "TUI: session_name correct" }
    else { Write-Fail "TUI: expected $SESSION_TUI, got: $name" }

    # TUI Check 2: run-shell via TCP while TUI is live
    Write-Host "`n[TUI 2] run-shell via TCP on live TUI" -ForegroundColor Yellow
    $resp = Send-TcpCommand -Session $SESSION_TUI -Command "run-shell `"echo TUI_ALIVE_221`""
    if ($resp -match "TUI_ALIVE_221") { Write-Pass "TUI: run-shell via TCP works" }
    else { Write-Fail "TUI: run-shell expected TUI_ALIVE_221, got: $resp" }

    # TUI Check 3: send-keys + capture-pane to verify pane is functional
    Write-Host "`n[TUI 3] send-keys + capture-pane" -ForegroundColor Yellow
    & $PSMUX send-keys -t $SESSION_TUI "echo PANE_FUNCTIONAL_221" Enter 2>&1 | Out-Null
    Start-Sleep -Seconds 2
    $captured = & $PSMUX capture-pane -t $SESSION_TUI -p 2>&1 | Out-String
    if ($captured -match "PANE_FUNCTIONAL_221") { Write-Pass "TUI: pane captures output" }
    else { Write-Fail "TUI: PANE_FUNCTIONAL_221 not in capture" }

    # TUI Check 4: split-window works during live TUI
    Write-Host "`n[TUI 4] split-window on live TUI" -ForegroundColor Yellow
    & $PSMUX split-window -v -t $SESSION_TUI 2>&1 | Out-Null
    Start-Sleep -Seconds 2
    $panes = (& $PSMUX display-message -t $SESSION_TUI -p '#{window_panes}' 2>&1 | Out-String).Trim()
    if ($panes -eq "2") { Write-Pass "TUI: split-window created 2 panes" }
    else { Write-Fail "TUI: expected 2 panes, got: $panes" }
}

& $PSMUX kill-session -t $SESSION_TUI 2>&1 | Out-Null
try { Stop-Process -Id $proc.Id -Force -EA SilentlyContinue } catch {}
Remove-Item "$psmuxDir\$SESSION_TUI.*" -Force -EA SilentlyContinue

# === TEARDOWN ===
Cleanup
Remove-Item "$env:TEMP\psmux_221_*" -Force -EA SilentlyContinue

Write-Host "`n=== Results ===" -ForegroundColor Cyan
Write-Host "  Passed: $($script:TestsPassed)" -ForegroundColor Green
Write-Host "  Failed: $($script:TestsFailed)" -ForegroundColor $(if ($script:TestsFailed -gt 0) { "Red" } else { "Green" })
exit $script:TestsFailed