Files
stack/scripts/test-discord.sh
T
jason.woltjeandClaude Fable 5.1 caaef941e6 feat(discord): binding reload without a restart, and a per-user channel allowlist (#1509)
`reload` validates the binding file and sends SIGHUP to the live owner;
the running connector re-reads it and swaps guildName, channels, users
and limits in place. name, seat, guildId, botUserId, tokenFile, engine
and context are fixed for the life of the process; a change there, an
invalid file or a channel outside the guild refuses the reload and keeps
the old binding. Every attempt is one line in reloads.jsonl. The service
unit maps `systemctl --user reload` to the same signal.

A user entry may carry `channels`, an allowlist of listed channel ids;
absent means every listed channel. Outside the list the message is
dropped as channel-not-for-user; threads count as their parent.

Suite 41/41, 101 node tests. QUEUE rows 19 and 20 opened.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-13 18:59:31 -05:00

120 lines
5.5 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# Offline suite for packages/discord (issue #1509). No token, no network,
# no model: every test drives the connector with a fake gateway, fake REST,
# fake pi and a disposable data root.
#
# scripts/test-discord.sh full run
# NO_COLOR=1 scripts/test-discord.sh plain output
set -uo pipefail
cd "$(dirname "$0")/.."
SANDBOX="$(mktemp -d)"
trap 'rm -rf "$SANDBOX"' EXIT
PASS=0
FAIL=0
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
C_OK=$'\033[0;32m'; C_FAIL=$'\033[0;31m'; C_RESET=$'\033[0m'
else
C_OK=""; C_FAIL=""; C_RESET=""
fi
check() {
if [ "$2" = "0" ]; then PASS=$((PASS+1)); echo "${C_OK}OK${C_RESET} $1"; else FAIL=$((FAIL+1)); echo "${C_FAIL}FAIL${C_RESET} $1"; fi
}
echo "toolchain: node $(node --version)"
echo
# --- syntax ---
for f in packages/discord/src/*.mjs packages/discord/tests/*.mjs packages/discord/fixtures/*.mjs scripts/discord.sh scripts/discord-service.sh; do
case "$f" in
*.sh) bash -n "$f" >/dev/null 2>&1 ;;
*) node --check "$f" >/dev/null 2>&1 ;;
esac
check "syntax: $f" $?
done
# --- no dependencies, no secrets in the package ---
node -e 'const p=require("./packages/discord/package.json"); process.exit(p.dependencies||p.devDependencies?1:0)' >/dev/null 2>&1
check "packages/discord declares no dependencies" $?
if grep -rEn '[MN][A-Za-z0-9]{22,}\.[A-Za-z0-9_-]{6}\.[A-Za-z0-9_-]{27,}' packages/discord scripts/discord.sh >/dev/null 2>&1; then
check "no bot-token-shaped string in packages/discord" 1
else
check "no bot-token-shaped string in packages/discord" 0
fi
node -e '
const b = JSON.parse(require("fs").readFileSync("packages/discord/fixtures/binding.example.json","utf8"));
const ids = JSON.stringify(b).match(/[0-9]{17,20}/g) || [];
process.exit(ids.every((id) => id.startsWith("10000000000000")) ? 0 : 1);
' >/dev/null 2>&1
check "fixture binding uses placeholder ids only" $?
# --- the fixture validates against the schema ---
node -e '
import("./packages/discord/src/binding.mjs").then((m) => {
const raw = JSON.parse(require("fs").readFileSync("packages/discord/fixtures/binding.example.json","utf8"));
m.validateBinding(raw);
}).catch((e) => { console.error(e.message); process.exit(1); });
' >/dev/null 2>&1
check "fixture binding validates" $?
# --- the seven offline groups ---
node --test --test-reporter=spec packages/discord/tests/ >"$SANDBOX/node-test.log" 2>&1
NODE_RC=$?
check "node --test packages/discord/tests/ ($(grep -E '^ℹ pass' "$SANDBOX/node-test.log" | tr -d '\n' || echo 'summary missing'))" $NODE_RC
if [ "$NODE_RC" -ne 0 ]; then
grep -E '^✖|not ok|Error|error:' "$SANDBOX/node-test.log" | head -40 >&2
fi
# --- wrapper usage paths ---
scripts/discord.sh --help >/dev/null 2>&1
check "scripts/discord.sh --help exits 0" $?
scripts/discord.sh check >/dev/null 2>&1
[ $? -eq 4 ]
check "scripts/discord.sh check without a binding exits 4" $?
scripts/discord.sh recover >/dev/null 2>&1
[ $? -eq 4 ]
check "scripts/discord.sh recover without a binding exits 4" $?
scripts/discord.sh reload >/dev/null 2>&1
[ $? -eq 4 ]
check "scripts/discord.sh reload without a binding exits 4" $?
# --- the service unit: rendered from the template, never touching systemd here ---
UNITS="$SANDBOX/units"
scripts/discord-service.sh >/dev/null 2>&1
[ $? -eq 4 ]
check "scripts/discord-service.sh without a command exits 4" $?
REPO_DIR="$(pwd)"
scripts/discord-service.sh render >"$SANDBOX/unit.rendered" 2>/dev/null \
&& grep -qF "ExecStart=$REPO_DIR/scripts/discord.sh run %i --supervised" "$SANDBOX/unit.rendered" \
&& ! grep -q '^ExecStartPre=' "$SANDBOX/unit.rendered" \
&& grep -qx 'RestartPreventExitStatus=3' "$SANDBOX/unit.rendered" \
&& grep -qx 'Restart=on-failure' "$SANDBOX/unit.rendered" \
&& grep -qx 'KillSignal=SIGTERM' "$SANDBOX/unit.rendered" \
&& grep -qx 'ExecReload=/bin/kill -HUP $MAINPID' "$SANDBOX/unit.rendered" \
&& ! grep -q '@REPO@\|@PATH@' "$SANDBOX/unit.rendered"
check "service unit renders with the repository path, a supervised run as the main process, exit 3 never retried, and reload as SIGHUP" $?
scripts/discord-service.sh install --dir "$UNITS" --no-reload >"$SANDBOX/install.1" 2>&1 \
&& [ -f "$UNITS/[email protected]" ] \
&& grep -q '^written:' "$SANDBOX/install.1" \
&& cmp -s "$UNITS/[email protected]" "$SANDBOX/unit.rendered" \
&& [ "$(stat -c %a "$UNITS/[email protected]")" = "644" ] \
&& [ -z "$(ls -A "$UNITS" | grep -v '^[email protected]$')" ]
check "service install writes the rendered unit (0644) and leaves no temp file" $?
scripts/discord-service.sh install --dir "$UNITS" --no-reload >"$SANDBOX/install.2" 2>&1 && grep -q '^unchanged:' "$SANDBOX/install.2"
check "service install a second time reports unchanged" $?
if command -v systemd-analyze >/dev/null 2>&1; then
cp "$UNITS/[email protected]" "$UNITS/[email protected]"
systemd-analyze --user verify "$UNITS/[email protected]" >"$SANDBOX/verify.log" 2>&1 && ! grep -qi 'warning\|error\|fail' "$SANDBOX/verify.log"
check "systemd-analyze verify accepts the rendered unit" $?
rm -f "$UNITS/[email protected]"
fi
scripts/discord-service.sh uninstall --dir "$UNITS" --no-reload >/dev/null 2>&1 && [ ! -e "$UNITS/[email protected]" ]
check "service uninstall removes the unit file" $?
scripts/discord-service.sh install --dir "$UNITS" --no-reload --bogus >/dev/null 2>&1
[ $? -eq 4 ]
check "service install with an unknown flag exits 4" $?
echo
echo "discord suite: $PASS passed, $FAIL failed"
[ "$FAIL" -eq 0 ]