#!/usr/bin/env bats
#
# Uninvited-guest semantics. tmux >= 3.3 answers a non-allowlisted
# user's connection with "access not allowed" on stderr and EXIT 0
# (verified live on 3.3a and 3.5a with two real users), for every
# command — including has-session, which then reports any target as
# existing. Exit-code-based checks therefore misclassified
# "unauthorized" as "authorized": party status reported uninvited
# members as joined, party leave silently "succeeded", and party list
# printed bogus "0 attendee(s)" rows. These tests pin the corrected
# classification.

load 'helpers'

setup() {
  setup_party_sandbox
  export TMUX_PARTY_GROUP="$(id -gn)"

  # Stub tmux that answers every command the way a real server answers
  # a non-allowlisted user: message on stderr, exit 0.
  cat > "$PARTY_TMP/tmux-denied" <<'STUB'
#!/bin/sh
echo "access not allowed" >&2
exit 0
STUB
  chmod +x "$PARTY_TMP/tmux-denied"
  export PARTY_TMUX="$PARTY_TMP/tmux-denied"

  # Plant a live-looking party dir + roster. Ownership is ours (bats
  # can't fake a foreign uid without root), so cmd_status will also see
  # this fixture as "hosting" — that's orthogonal to the joined/leave
  # misreporting under test.
  ensure_party_dir "$USER" fiesta
  d="$PARTY_SOCKET_DIR/party-$USER:fiesta.d"
  cat > "$d/roster" <<EOF
HOST_USER=$USER
PARTY_NAME=fiesta
SOCKET=$d/sock
SERVER_PID=12345
GROUP=$TMUX_PARTY_GROUP
CREATED=2026-07-03T00:00:00Z
EOF
  chmod 0640 "$d/roster"
}

teardown() { teardown_party_sandbox; }

@test "party list marks an unauthorized party invite-only, not 0 attendees" {
  run "$PARTY_BIN" list
  [ "$status" -eq 0 ]
  [[ "$output" == *"fiesta"* ]]
  [[ "$output" == *"invite-only"* ]]
  [[ "$output" != *"0 attendee"* ]]
}

@test "party join an invite-only party says whom to ask and exits 13" {
  # Regression lock: this already worked (the preflight greps stdout,
  # which is empty for unauthorized callers) and must keep working.
  run "$PARTY_BIN" join fiesta
  [ "$status" -eq 13 ]
  [[ "$output" == *"invite"* ]]
}

@test "party status does not report unauthorized parties as joined" {
  run "$PARTY_BIN" status
  [ "$status" -eq 0 ]
  [[ "$output" != *"joined"* ]]
}

@test "party leave on unauthorized parties says 'not joined', not success" {
  run "$PARTY_BIN" leave
  [ "$status" -ne 0 ]
  [[ "$output" == *"not joined"* ]]
}

@test "a dead socket is still treated as dead" {
  cat > "$PARTY_TMP/tmux-dead" <<'STUB'
#!/bin/sh
echo "no server running" >&2
exit 1
STUB
  chmod +x "$PARTY_TMP/tmux-dead"
  export PARTY_TMUX="$PARTY_TMP/tmux-dead"
  run "$PARTY_BIN" list
  [ "$status" -eq 0 ]
  [[ "$output" == *"no parties found"* ]]
}

@test "read-only invitee joining active falls back to passive with a notice" {
  # tmux refuses new-session from a read-only client ("client is
  # read-only", exit 1 — verified 3.3a/3.5a), so an active join for an
  # -r invitee died mid-command under set -eu. cmd_join must detect the
  # R flag on its own ACL line and attach passive instead. The unquoted
  # STUB delimiter is deliberate: $USER expands at write time, \$* at
  # run time.
  cat > "$PARTY_TMP/tmux-ro" <<STUB
#!/bin/sh
case "\$*" in
  *"server-access -l"*) printf '%s (R)\nsomehost (W)\n' "$USER"; exit 0 ;;
esac
exit 0
STUB
  chmod +x "$PARTY_TMP/tmux-ro"
  export PARTY_TMUX="$PARTY_TMP/tmux-ro"
  PARTY_DRY_RUN=1 run "$PARTY_BIN" join fiesta
  [ "$status" -eq 0 ]
  [[ "$output" == *"read-only"* ]]
  [[ "$output" == *"attach-session -r -t fiesta"* ]]
}

@test "read-only invitee (U,R) flag format also falls back to passive" {
  # tmux refuses new-session from a read-only client ("client is
  # read-only", exit 1 — verified 3.3a/3.5a), so an active join for an
  # -r invitee died mid-command under set -eu. cmd_join must detect the
  # R flag on its own ACL line and attach passive instead. The unquoted
  # STUB delimiter is deliberate: $USER expands at write time, \$* at
  # run time.
  cat > "$PARTY_TMP/tmux-ro" <<STUB
#!/bin/sh
case "\$*" in
  *"server-access -l"*) printf '%s (U,R)\nsomehost (W)\n' "$USER"; exit 0 ;;
esac
exit 0
STUB
  chmod +x "$PARTY_TMP/tmux-ro"
  export PARTY_TMUX="$PARTY_TMP/tmux-ro"
  PARTY_DRY_RUN=1 run "$PARTY_BIN" join fiesta
  [ "$status" -eq 0 ]
  [[ "$output" == *"read-only"* ]]
  [[ "$output" == *"attach-session -r -t fiesta"* ]]
}

@test "unauthorized classification does not depend on tmux's denial wording" {
  # Adversarial-review finding: matching the English "access not
  # allowed" would silently reopen the false-authorized paths if the
  # wording ever drifts. The classifier keys on rc 0 + any stderr
  # output instead; this stub proves a drifted message still counts.
  cat > "$PARTY_TMP/tmux-denied-drift" <<'STUB'
#!/bin/sh
echo "connection declined by policy" >&2
exit 0
STUB
  chmod +x "$PARTY_TMP/tmux-denied-drift"
  export PARTY_TMUX="$PARTY_TMP/tmux-denied-drift"
  run "$PARTY_BIN" list
  [ "$status" -eq 0 ]
  [[ "$output" == *"invite-only"* ]]
  [[ "$output" != *"0 attendee"* ]]
}
