signal-bot/scripts/e2e_proofs.sh
Krzysztof kuhy Rudnicki d13ae42590
Some checks are pending
CI / check (push) Waiting to run
CI / web (push) Waiting to run
A switchboard: any command off, off until, or on its own cooldown
One row per live command on a new Commands tab of the management page,
each with an on/off switch, an optional "off until" moment and a cooldown
that overrides the bot's default for that command alone. Every switch is
live: the bot keeps them in its own SQLite (command_settings, a permanent
table) and reads the row on every attempt, so nothing goes through
/_reload and nothing restarts.

A command that is off answers "BEEP BOOP !kot WYŁĄCZONE." (with "DO HH:MM"
for a timed switch) rather than going quiet, so nobody reports it broken;
the refusal is filed as refused_off, which the analytics count with the
other refusals, and it neither starts the cooldown nor counts as a use.

The bot grows GET /_commands and PUT /_commands/{name}; the management app
proxies them under /api/commands and passes the bot's 404/503 through
unwrapped. !cisza and !odcisz carry no cooldown and the page says so
instead of accepting a number the bot would ignore (test_wiring_ungated
keeps that set honest against the real table).

Proven end to end: scripts/e2e.sh now brackets a PUT with switch_off.yaml
and switch_on.yaml, the same actor refused and then served, with no reload
between them. Also bumps vitest and @vitest/coverage-v8 to 5.0.0 and
@types/react-dom to 19.2.7, which the freshness gate asked for.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-04 15:55:56 +02:00

107 lines
4.9 KiB
Bash
Executable File

#!/bin/bash
# ============================================================================
# The assertions scripts/e2e.sh makes after the scenarios have replayed.
#
# Split out of e2e.sh at the 250-line cap. A filing change, not an interface
# change: these are sourced, so they still run against the same fake server
# and the same bot process the scenarios used.
# ============================================================================
# The gate that did not exist on 2026-08-29, proven against the real process:
# with signal-cli unable to reach Signal, /healthz must go red rather than
# keep answering 200 while nothing arrives.
prove_the_deafness_gate() {
local health="http://127.0.0.1:${BOT_PORT}/healthz"
echo "== cutting the fake's connection to Signal"
curl -sf -X POST "${FAKE_URL}/_control/connection" \
-H 'Content-Type: application/json' -d '{"alive": false}' >/dev/null
local code="" waited=0
while ((waited < 150)); do
code="$(curl -s -o /dev/null -w '%{http_code}' "$health")"
[[ "$code" == "503" ]] && break
sleep 1
waited=$((waited + 1))
done
[[ "$code" == "503" ]] || die "/healthz still answers ${code} with the connection cut"
curl -s "$health" | grep -q "Closed unexpectedly" ||
die "/healthz went red without saying why"
echo "== /healthz turned 503 and named the reason after ${waited}s"
echo "== restoring the connection"
curl -sf -X POST "${FAKE_URL}/_control/connection" \
-H 'Content-Type: application/json' -d '{"alive": true}' >/dev/null
waited=0
while ((waited < 150)); do
code="$(curl -s -o /dev/null -w '%{http_code}' "$health")"
[[ "$code" == "200" ]] && break
sleep 1
waited=$((waited + 1))
done
[[ "$code" == "200" ]] || die "/healthz stayed ${code} after the connection came back"
echo "== /healthz recovered on its own after ${waited}s"
}
# A person tagged while the bot is up becomes a command after POST /_reload,
# with no restart: the same process that just answered "Nie znam !delta"
# posts delta's photo a moment later. The scenarios live next to the main
# set but outside its glob, because the second one is only true after the
# reload that this proof performs between them.
prove_the_table_reloads_without_a_restart() {
local proofs="${REPO_ROOT}/fake_signal/proofs"
echo "== before the reload, !delta is nobody"
replay "${proofs}/reload_before.yaml"
echo "== tagging a fifth person into the pack, then scripts/reload.sh"
"$PYTHON" "$REPO_ROOT/tool/e2e_pack.py" \
--dir "${TEMP_DIR}/pack" --group "$WATCHED_GROUP" --extra delta >/dev/null
# The operator's script, not a bare curl: this is the one place it runs
# against a real process in CI, so its exit code is the proof.
BIND_PORT="$BOT_PORT" "$REPO_ROOT/scripts/reload.sh" ||
die "scripts/reload.sh did not get the new table live"
echo "== after the reload, !delta posts a photo"
replay "${proofs}/reload_after.yaml"
}
# A switch thrown from the management page is read on the very next attempt:
# no reload, no restart. The PUT here is what the page's Save button sends,
# aimed at the bot directly; the two scenarios bracket it the same way the
# reload pair does. Not a timed switch, because a proof that depends on the
# clock has to wait on it, and the unit tests already cover the deadline.
prove_a_switch_is_live_without_a_reload() {
local proofs="${REPO_ROOT}/fake_signal/proofs"
local switch="http://127.0.0.1:${BOT_PORT}/_commands/cat"
echo "== switching !kot off"
curl -sf -X PUT "$switch" -H 'Content-Type: application/json' \
-d '{"enabled": false}' | grep -q '"enabled":false' ||
die "PUT /_commands/cat did not report the command off"
replay "${proofs}/switch_off.yaml"
echo "== switching !kot back on"
curl -sf -X PUT "$switch" -H 'Content-Type: application/json' \
-d '{"enabled": true}' | grep -q '"enabled":true' ||
die "PUT /_commands/cat did not report the command on"
replay "${proofs}/switch_on.yaml"
}
# A budget, not a benchmark. This run was four minutes until the liveness
# tick became configurable, and nothing announced the cost -- slower is the
# one regression a green suite never reports. Exceeding it fails the run;
# whoever exceeded it decides whether the check earned its seconds or the
# budget moves. E2E_BUDGET_SECONDS raises it for a loaded machine.
enforce_the_time_budget() {
local started="$1"
local budget="${E2E_BUDGET_SECONDS:-40}"
local elapsed=$(( $(date +%s) - started ))
echo "== end to end took ${elapsed}s (budget ${budget}s)"
if ((elapsed > budget)); then
echo "Error: the offline run took ${elapsed}s, over its ${budget}s budget." >&2
echo " Either the new check earns its seconds and the budget moves," >&2
echo " or something got slower and this is the report of it." >&2
exit 1
fi
}