#!/usr/bin/env bash # Tests para fleet_send_text. Levanta un socket tmux PROPIO de test # (fleet_test_, nunca el socket "fleet" real) con un pane `cat` vivo, y # verifica: envio + verificacion via capture-pane (golden), supervivencia al # focus-swap (break-pane preserva el pane_id), resolucion por sessionId fake, # y los paths de error/guard. No toca la flota real ni ningun agente. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/fleet_send_text.sh" PASS=0 FAIL=0 assert_contains() { local test_name="$1" needle="$2" haystack="$3" if echo "$haystack" | grep -qF "$needle"; then echo "PASS: $test_name" PASS=$((PASS+1)) else echo "FAIL: $test_name — expected to contain '$needle'" echo " got: $haystack" FAIL=$((FAIL+1)) fi } assert_not_contains() { local test_name="$1" needle="$2" haystack="$3" if echo "$haystack" | grep -qF "$needle"; then echo "FAIL: $test_name — should NOT contain '$needle'" echo " got: $haystack" FAIL=$((FAIL+1)) else echo "PASS: $test_name" PASS=$((PASS+1)) fi } assert_rc() { local test_name="$1" expected="$2" actual="$3" if [[ "$actual" == "$expected" ]]; then echo "PASS: $test_name (rc=$actual)" PASS=$((PASS+1)) else echo "FAIL: $test_name — expected rc=$expected, got rc=$actual" FAIL=$((FAIL+1)) fi } command -v tmux >/dev/null 2>&1 || { echo "SKIP: tmux no instalado"; exit 0; } # --- Socket de test PROPIO + pane `cat` vivo (con echo de tty) --- SOCK="fleet_test_$$" TMP="$(mktemp -d)" SESS="$TMP/sessions" mkdir -p "$SESS" cleanup() { tmux -L "$SOCK" kill-server 2>/dev/null || true rm -rf "$TMP" } trap cleanup EXIT tmux -L "$SOCK" new-session -d -s t -x 120 -y 30 'cat' sleep 0.4 PANE_PID="$(tmux -L "$SOCK" list-panes -a -F '#{pane_pid}' | head -n1)" PANE_ID0="$(tmux -L "$SOCK" list-panes -a -F '#{pane_pid} #{pane_id}' | awk -v p="$PANE_PID" '$1==p{print $2}')" WIN_ID0="$(tmux -L "$SOCK" list-panes -a -F '#{pane_pid} #{window_id}' | awk -v p="$PANE_PID" '$1==p{print $2}')" echo "INFO: socket=$SOCK pane_pid=$PANE_PID pane_id=$PANE_ID0 window_id=$WIN_ID0" # self_pid forzado a un PID que nunca sera target en los tests golden. export FN_FLEET_SELF_PID=1 export FN_FLEET_SESSIONS_DIR="$SESS" # --- Test 1 (golden): enviar por PID, verificar via capture-pane --- set +e out=$(fleet_send_text "$PANE_PID" "HOLA_FLEET_123" --socket "$SOCK" --no-enter --retries 1 2>&1); rc=$? set -e assert_rc "golden: envio por PID sale 0" 0 "$rc" assert_contains "golden: reporta status=ok" "status=ok" "$out" assert_contains "golden: reporta el pane_id estable" "pane=$PANE_ID0" "$out" cap="$(tmux -L "$SOCK" capture-pane -p -t "$PANE_ID0")" assert_contains "golden: el texto llego al pane (capture-pane)" "HOLA_FLEET_123" "$cap" # limpiar input del cat tmux -L "$SOCK" send-keys -t "$PANE_ID0" C-u; sleep 0.2 tmux -L "$SOCK" send-keys -t "$PANE_ID0" C-l 2>/dev/null || true; sleep 0.2 # --- Test 2 (edge focus-swap): mover el pane a otra window, pane_id NO migra --- # Anadimos un segundo pane para poder break-pane el nuestro a una window nueva. tmux -L "$SOCK" split-window -t "$WIN_ID0" -d 'cat'; sleep 0.3 tmux -L "$SOCK" break-pane -d -s "$PANE_ID0"; sleep 0.3 WIN_ID1="$(tmux -L "$SOCK" list-panes -a -F '#{pane_pid} #{window_id}' | awk -v p="$PANE_PID" '$1==p{print $2}')" PANE_ID1="$(tmux -L "$SOCK" list-panes -a -F '#{pane_pid} #{pane_id}' | awk -v p="$PANE_PID" '$1==p{print $2}')" echo "INFO: tras break-pane: pane_id=$PANE_ID1 (era $PANE_ID0) window_id=$WIN_ID1 (era $WIN_ID0)" assert_contains "edge: pane_id NO cambia tras mover de window" "$PANE_ID0" "$PANE_ID1" set +e out=$(fleet_send_text "$PANE_PID" "TRAS_MOVER_456" --socket "$SOCK" --no-enter --retries 1 2>&1); rc=$? set -e assert_rc "edge: reenvio tras focus-swap sale 0" 0 "$rc" cap="$(tmux -L "$SOCK" capture-pane -p -t "$PANE_ID1")" assert_contains "edge: el texto sigue llegando tras mover de window" "TRAS_MOVER_456" "$cap" tmux -L "$SOCK" send-keys -t "$PANE_ID1" C-u; sleep 0.2 # --- Test 3 (edge): resolver por sessionId (sessions/.json fake) --- echo "{\"sessionId\":\"test-sid-aaa-111\",\"cwd\":\"/tmp/x\"}" > "$SESS/$PANE_PID.json" set +e out=$(fleet_send_text "test-sid-aaa" "VIA_SID_789" --socket "$SOCK" --no-enter --retries 1 2>&1); rc=$? set -e assert_rc "edge: resolucion por prefijo de sessionId sale 0" 0 "$rc" cap="$(tmux -L "$SOCK" capture-pane -p -t "$PANE_ID1")" assert_contains "edge: texto llego resolviendo por sessionId" "VIA_SID_789" "$cap" tmux -L "$SOCK" send-keys -t "$PANE_ID1" C-u; sleep 0.2 # --- Test 4 (edge): --dry-run no envia nada --- set +e out=$(fleet_send_text "$PANE_PID" "NO_DEBE_APARECER_000" --socket "$SOCK" --no-enter --dry-run 2>&1); rc=$? set -e assert_rc "edge: dry-run sale 0" 0 "$rc" assert_contains "edge: dry-run reporta status=dry-run" "status=dry-run" "$out" cap="$(tmux -L "$SOCK" capture-pane -p -t "$PANE_ID1")" assert_not_contains "edge: dry-run NO inyecto texto" "NO_DEBE_APARECER_000" "$cap" # --- Test 5 (error): sessionId que no resuelve a PID -> rc 2 --- set +e out=$(fleet_send_text "sid-inexistente-zzz" "x" --socket "$SOCK" 2>&1); rc=$? set -e assert_rc "error: sessionId no resuelto sale 2" 2 "$rc" assert_contains "error: mensaje de target no resuelto" "no se pudo resolver" "$out" # --- Test 6 (error): falta el texto -> rc 2 --- set +e out=$(fleet_send_text "$PANE_PID" --socket "$SOCK" 2>&1); rc=$? set -e assert_rc "error: falta texto sale 2" 2 "$rc" # --- Test 7 (guard anti-self): target == self_pid -> rc 3 --- set +e out=$(FN_FLEET_SELF_PID="$PANE_PID" fleet_send_text "$PANE_PID" "x" --socket "$SOCK" 2>&1); rc=$? set -e assert_rc "guard: enviar a la sesion actual sale 3" 3 "$rc" assert_contains "guard: mensaje anti-self" "No me autoenvio" "$out" # --- Test 8 (error): PID sin pane vivo -> rc 4 --- set +e out=$(fleet_send_text 999999 "x" --socket "$SOCK" 2>&1); rc=$? set -e assert_rc "error: PID sin pane vivo sale 4" 4 "$rc" assert_contains "error: mensaje no pane vivo" "no se encontro un pane vivo" "$out" # --- Resumen --- echo "" echo "================================" echo "PASS: $PASS FAIL: $FAIL" echo "================================" [[ "$FAIL" -eq 0 ]]