#!/usr/bin/env bash # ── Interactive Chat Test ───────────────────────────────────────────────────── # # Spins up a server + two client containers (Alice & Bob), sets up an MLS # group, and opens a tmux session with side-by-side interactive chat panes. # # Usage: # ./scripts/chat-test.sh # # Requirements: docker (or podman + podman-compose), tmux # Exit: Ctrl+D in both panes, or: tmux kill-session -t qpc-chat set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" COMPOSE_FILE="$PROJECT_ROOT/docker/docker-compose.chat-test.yml" # ── Colors ──────────────────────────────────────────────────────────────────── GREEN='\033[0;32m' CYAN='\033[0;36m' RED='\033[0;31m' NC='\033[0m' step() { echo -e "${GREEN}==> $1${NC}"; } info() { echo -e " ${CYAN}$1${NC}"; } error() { echo -e "${RED}ERROR: $1${NC}" >&2; } # ── Preflight checks ───────────────────────────────────────────────────────── # Prefer docker; fall back to podman. if command -v docker &>/dev/null; then if ! docker compose version &>/dev/null; then error "docker compose (v2 plugin) is required." echo " See: https://docs.docker.com/compose/install/" exit 1 fi COMPOSE_BIN="docker compose" elif command -v podman &>/dev/null; then if ! command -v podman-compose &>/dev/null; then error "podman-compose is required when using podman." echo " pip install podman-compose" exit 1 fi COMPOSE_BIN="podman-compose" else error "docker or podman is required but neither is installed." exit 1 fi COMPOSE="$COMPOSE_BIN -f $COMPOSE_FILE -p qpc-chat-test" if ! command -v tmux &>/dev/null; then error "tmux is required but not installed." echo " macOS: brew install tmux" echo " Linux: sudo apt-get install tmux" exit 1 fi # ── Cleanup on exit ────────────────────────────────────────────────────────── cleanup() { echo "" step "Tearing down containers..." $COMPOSE down -v --remove-orphans 2>/dev/null || true } trap cleanup EXIT INT TERM # ── Step 1: Build ───────────────────────────────────────────────────────────── step "Building Docker image (server + client)..." $COMPOSE build # ── Step 2: Start services ──────────────────────────────────────────────────── step "Starting server, alice, bob..." $COMPOSE up -d --wait # ── Step 3: Verify server reachable via QUIC ────────────────────────────────── step "Verifying QUIC connectivity from alice..." $COMPOSE exec -T alice qpq health # ── Step 4: Alice — register identity + upload KeyPackage ───────────────────── step "Alice: register-state..." $COMPOSE exec -T alice qpq register-state --state /chat/alice.bin ALICE_KEY=$($COMPOSE exec -T alice qpq whoami --state /chat/alice.bin \ | grep 'identity_key' | awk '{print $3}' | tr -d '[:space:]') info "Alice identity: ${ALICE_KEY}" if [ -z "$ALICE_KEY" ]; then error "Failed to extract Alice's identity key." exit 1 fi # ── Step 5: Bob — register identity + upload KeyPackage ─────────────────────── step "Bob: register-state..." $COMPOSE exec -T bob qpq register-state --state /chat/bob.bin BOB_KEY=$($COMPOSE exec -T bob qpq whoami --state /chat/bob.bin \ | grep 'identity_key' | awk '{print $3}' | tr -d '[:space:]') info "Bob identity: ${BOB_KEY}" if [ -z "$BOB_KEY" ]; then error "Failed to extract Bob's identity key." exit 1 fi # ── Step 6: Alice creates group ─────────────────────────────────────────────── step "Alice: create-group 'docker-chat'..." $COMPOSE exec -T alice qpq create-group \ --state /chat/alice.bin \ --group-id docker-chat # ── Step 7: Alice invites Bob ───────────────────────────────────────────────── step "Alice: invite Bob..." $COMPOSE exec -T alice qpq invite \ --state /chat/alice.bin \ --peer-key "$BOB_KEY" # ── Step 8: Bob joins ───────────────────────────────────────────────────────── step "Bob: join group..." $COMPOSE exec -T bob qpq join --state /chat/bob.bin # ── Step 9: Launch tmux ────────────────────────────────────────────────────── step "Launching interactive chat (tmux side-by-side)..." echo "" echo " Left pane: Alice" echo " Right pane: Bob" echo "" echo " Type a message + Enter to send." echo " Ctrl+D exits a pane." echo " tmux kill-session -t qpc-chat to stop." echo "" ALICE_CMD="$COMPOSE exec alice qpq chat --state /chat/alice.bin" BOB_CMD="$COMPOSE exec bob qpq chat --state /chat/bob.bin" # Kill any stale tmux session with the same name. tmux kill-session -t qpc-chat 2>/dev/null || true # Create tmux session with Alice in the first pane, Bob in a horizontal split. tmux new-session -d -s qpc-chat -x 200 -y 50 "$ALICE_CMD" tmux split-window -h -t qpc-chat "$BOB_CMD" # Label panes (tmux >= 2.6). tmux select-pane -t qpc-chat:0.0 -T "Alice" tmux select-pane -t qpc-chat:0.1 -T "Bob" tmux set-option -t qpc-chat pane-border-status top 2>/dev/null || true tmux set-option -t qpc-chat pane-border-format " #{pane_title} " 2>/dev/null || true # Attach — blocks until the user exits tmux. tmux attach-session -t qpc-chat step "Chat session ended."