mirror of
https://github.com/kuhyx/signal-bot.git
synced 2026-11-10 21:27:16 +01:00
CI failed on shellcheck where the local hook passed: the runner's shellcheck reports SC2015 (`A && B || C` is not if-then-else) and mine did not. Both now run with --severity=style so a version skew cannot pass locally and fail in CI. The cleanup traps are plain ifs, which is what the finding was about -- `kill` on an already-dead pid makes B fail and C run while A is true. live_smoke.sh choose_group had two bugs that only surface when it is actually run, neither reachable from the offline suite: - it fetched /v1/groups twice, once to display and once to resolve the picked index, so an unstable order would hand back a different group than the one shown. Fetched once into a variable now. - `read -r -p` wrote its prompt to stdout, which the caller captures with $(...), so the prompt text would have been swallowed into the group id. Prompt and listing both go to stderr; stdout carries only the id. Also validates that the pick is a number and in range. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013yaBT45KhHqxTKVCTA87iy
115 lines
3.4 KiB
Bash
Executable File
115 lines
3.4 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# ============================================================================
|
|
# End-to-end test: the real bot against the fake Signal server.
|
|
#
|
|
# Boots the protocol-faithful fake, boots the unmodified bot pointed at it,
|
|
# replays every scenario and exits non-zero on the first mismatch. No Signal
|
|
# account, no network, no container.
|
|
# ============================================================================
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
readonly SCRIPT_DIR
|
|
REPO_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
readonly REPO_ROOT
|
|
readonly PYTHON="${PYTHON:-$REPO_ROOT/.venv/bin/python}"
|
|
readonly FAKE_PORT="${FAKE_PORT:-8099}"
|
|
readonly BOT_PORT="${BOT_PORT:-8098}"
|
|
readonly FAKE_URL="http://127.0.0.1:${FAKE_PORT}"
|
|
|
|
readonly WATCHED_GROUP="group.e2e.watched=="
|
|
readonly REPLY_GROUP="group.e2e.reply=="
|
|
readonly OTHER_GROUP="group.e2e.other=="
|
|
|
|
FAKE_PID=""
|
|
BOT_PID=""
|
|
TEMP_DIR=""
|
|
|
|
cleanup() {
|
|
# Plain ifs, not `A && B || C`: that is not if-then-else, and C runs when
|
|
# A is true but B fails -- which is exactly the case here (kill on an
|
|
# already-dead pid).
|
|
if [[ -n "$BOT_PID" ]]; then
|
|
kill "$BOT_PID" 2>/dev/null || true
|
|
fi
|
|
if [[ -n "$FAKE_PID" ]]; then
|
|
kill "$FAKE_PID" 2>/dev/null || true
|
|
fi
|
|
wait 2>/dev/null || true
|
|
if [[ -n "$TEMP_DIR" && -d "$TEMP_DIR" ]]; then
|
|
rm -rf "$TEMP_DIR"
|
|
fi
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
wait_for_url() {
|
|
local url="$1" name="$2" attempt=0
|
|
until curl -sf "$url" >/dev/null 2>&1; do
|
|
attempt=$((attempt + 1))
|
|
if [[ $attempt -ge 100 ]]; then
|
|
echo "Error: $name did not come up at $url" >&2
|
|
exit 1
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
}
|
|
|
|
wait_for_subscription() {
|
|
local attempt=0
|
|
until [[ "$(curl -sf "${FAKE_URL}/_control/ready" | tr -dc '0-9')" != "0" ]]; do
|
|
attempt=$((attempt + 1))
|
|
if [[ $attempt -ge 100 ]]; then
|
|
echo "Error: the bot never subscribed to the receive websocket" >&2
|
|
exit 1
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
}
|
|
|
|
validate_requirements() {
|
|
if [[ ! -x "$PYTHON" ]]; then
|
|
echo "Error: no interpreter at $PYTHON. Run: uv venv .venv && uv pip install -e '.[dev]'" >&2
|
|
exit 1
|
|
fi
|
|
command -v curl >/dev/null || { echo "Error: curl is required" >&2; exit 1; }
|
|
}
|
|
|
|
main() {
|
|
validate_requirements
|
|
TEMP_DIR="$(mktemp -d)"
|
|
|
|
echo "== booting the fake Signal server on :${FAKE_PORT}"
|
|
export FAKE_PORT
|
|
"$PYTHON" -m uvicorn fake_signal.server:app \
|
|
--host 127.0.0.1 --port "$FAKE_PORT" --log-level warning &
|
|
FAKE_PID=$!
|
|
wait_for_url "${FAKE_URL}/v1/about" "fake signal server"
|
|
|
|
echo "== booting the bot on :${BOT_PORT}, pointed at the fake"
|
|
env -u CAT_API \
|
|
PHONE_NUMBER="+15550000000" \
|
|
GROUP_ID="$WATCHED_GROUP" \
|
|
GROUP_ID_SEND="$REPLY_GROUP" \
|
|
API_BASE_URL="$FAKE_URL" \
|
|
DATABASE_PATH="${TEMP_DIR}/e2e.sqlite3" \
|
|
CAT_API_URL="${FAKE_URL}/_fixtures/cat-api" \
|
|
DOG_API_URL="${FAKE_URL}/_fixtures/dog-api" \
|
|
BIND_PORT="$BOT_PORT" \
|
|
"$PYTHON" -m signal_bot &
|
|
BOT_PID=$!
|
|
wait_for_url "http://127.0.0.1:${BOT_PORT}/healthz" "bot"
|
|
wait_for_subscription
|
|
|
|
echo "== replaying scenarios"
|
|
"$PYTHON" -m fake_signal.cli \
|
|
"$REPO_ROOT"/fake_signal/scenarios/*.yaml \
|
|
--base-url "$FAKE_URL" \
|
|
--group-id "$WATCHED_GROUP" \
|
|
--group-id-send "$REPLY_GROUP" \
|
|
--other-group "$OTHER_GROUP"
|
|
}
|
|
|
|
main "$@"
|