#!/usr/bin/env bats

load 'helpers'

setup() {
  setup_party_sandbox
  load_party_lib
}

# party name validation
# =====================

@test "validate_party_name accepts plain names" {
  validate_party_name "debug-the-deploy"
  validate_party_name "mob_coding"
  validate_party_name "p"
  validate_party_name "Party-2026"
}

@test "validate_party_name rejects empty" {
  run validate_party_name ""
  [ "$status" -ne 0 ]
}

@test "validate_party_name rejects path separators" {
  run validate_party_name "../etc"
  [ "$status" -ne 0 ]
  run validate_party_name "a/b"
  [ "$status" -ne 0 ]
}

@test "validate_party_name rejects names over 63 chars" {
  long=$(printf 'a%.0s' $(seq 1 64))
  run validate_party_name "$long"
  [ "$status" -ne 0 ]
}

@test "validate_party_name rejects whitespace and shell metas" {
  for bad in "a b" "a;b" 'a$b' 'a`b' "a'b" 'a"b'; do
    run validate_party_name "$bad"
    [ "$status" -ne 0 ] || { echo "should have rejected: $bad"; false; }
  done
}

@test "validate_party_name rejects dots (tmux rewrites '.' in session names)" {
  # tmux session_check_name() silently maps '.' and ':' to '_', so a
  # dotted party name desyncs from its tmux session and every later
  # `-t <name>` target fails. Verified on tmux 3.3a through 3.6b.
  for bad in "my.party" "." ".." "a.d"; do
    run validate_party_name "$bad"
    [ "$status" -ne 0 ] || { echo "should have rejected: $bad"; false; }
  done
}

@test "validate_party_name rejects leading dash (unusable through the CLI)" {
  run validate_party_name "-flag"
  [ "$status" -ne 0 ]
}

# time
# ====

@test "iso_now produces ISO 8601 UTC with Z suffix" {
  out=$(iso_now)
  case "$out" in
    [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]Z) ;;
    *) echo "bad timestamp: $out"; false ;;
  esac
}

# user existence
# ==============

@test "user_exists returns 0 for the running user" {
  user_exists "$USER"
}

@test "user_exists returns nonzero for a fictional user" {
  ! user_exists "nonexistent_$(date +%s)_user"
}

# liveness
# ========
#
# is_party_alive must not probe the PID with kill -0. POSIX kill(2) is
# allowed to return EPERM when the caller can't signal the target, and
# illumos/BSD/macOS honor that — so kill -0 against another user's PID
# returns nonzero even when the process exists. Linux and Linux-ABI
# zones (LX-branded) are the outliers that return 0; that's why the
# bug hid in the test matrix until a native illumos run surfaced it.
# Discovery now relies on `tmux -S list-clients` alone, which is
# strictly stronger (filters PID-reuse, dead servers, AF_UNIX impostors).

@test "is_party_alive does not reach for kill -0 (cross-user discovery)" {
  body=$(declare -f is_party_alive)
  [[ "$body" != *"kill "* ]] || { echo "$body"; false; }
}

@test "is_party_alive rejects non-numeric PID without invoking tmux" {
  ! PARTY_TMUX=/nonexistent/should-not-be-called \
      is_party_alive "" /tmp/should-not-matter
  ! PARTY_TMUX=/nonexistent/should-not-be-called \
      is_party_alive "abc" /tmp/should-not-matter
}

@test "is_party_alive returns nonzero when tmux server is absent" {
  # Numeric PID, but no live tmux server at the socket path: tmux call
  # fails the handshake and the function returns nonzero. Uses PID 1
  # to also confirm a foreign-uid PID is not preventing the result.
  ! is_party_alive 1 "$PARTY_TMP/no-such-sock"
}

@test "validate_socket_dir_parent refuses whitespace in PARTY_SOCKET_DIR" {
  spaced="$PARTY_TMP/with space"
  mkdir -p "$spaced"
  chmod 0700 "$spaced"
  PARTY_SOCKET_DIR="$spaced" run validate_socket_dir_parent
  [ "$status" -ne 0 ]
  [[ "$output" == *"whitespace"* ]]
}

@test "pick_live_party without a tty fails with guidance, not a silent death" {
  # Two live-looking parties force the numbered picker; stdin is not a
  # tty under bats, so the guard must fire before read.
  stub="$PARTY_TMP/tmux-ok"
  printf '#!/bin/sh\nexit 0\n' > "$stub"
  chmod +x "$stub"
  export PARTY_TMUX="$stub"
  ensure_party_dir "$USER" one
  ensure_party_dir "$USER" two
  for n in one two; do
    d="$PARTY_SOCKET_DIR/party-$USER:$n.d"
    printf 'SERVER_PID=1\nGROUP=%s\n' "$(id -gn)" > "$d/roster"
  done
  run pick_live_party
  [ "$status" -ne 0 ]
  [[ "$output" == *"pass a name"* ]]
}

@test "party_conn_state classifies ok / unauthorized / dead" {
  stub="$PARTY_TMP/conn-stub"
  cat > "$stub" <<'S'
#!/bin/sh
case "${MODE:-}" in
  ok)    exit 0 ;;
  deny)  echo "access not allowed" >&2; exit 0 ;;
  deny2) echo "connection declined by policy (hypothetical future wording)" >&2; exit 0 ;;
  *)     echo "error connecting to /x (No such file or directory)" >&2; exit 1 ;;
esac
S
  chmod +x "$stub"
  export PARTY_TMUX="$stub"
  export MODE=ok;   [ "$(party_conn_state /x)" = "ok" ]
  # The trap this function exists for: tmux answers unauthorized users
  # with EXIT 0 + a stderr message, so rc alone says "authorized".
  export MODE=deny; [ "$(party_conn_state /x)" = "unauthorized" ]
  # Classification must not depend on tmux's denial wording (adversarial
  # review finding): rc 0 with ANY stderr output is a refusal.
  export MODE=deny2; [ "$(party_conn_state /x)" = "unauthorized" ]
  export MODE=dead; [ "$(party_conn_state /x)" = "dead" ]
}
