QUEUE row 17, MVP iteration 2. scripts/discord-service.sh renders and installs mosaic-discord@<binding> from packages/discord/systemd/. The unit's main process is `run --supervised`, which applies the new recover policy first: a lock whose owner is gone is cleared and only the STOP written for that is removed; an operator STOP or a held binding refuses with exit 3, which RestartPreventExitStatus never retries. `recover` is also a CLI verb. First cut used ExecStartPre and looped live, since systemd honours the never-retry status only from the main process; replaced and re-verified before any message traffic. Suite 40/40, 95 node tests. Co-Authored-By: Claude Fable 5.1 <[email protected]>
116 lines
5.3 KiB
Bash
Executable File
116 lines
5.3 KiB
Bash
Executable File
#!/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" $?
|
||
|
||
# --- 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 -q '@REPO@\|@PATH@' "$SANDBOX/unit.rendered"
|
||
check "service unit renders with the repository path, a supervised run as the main process, and exit 3 never retried" $?
|
||
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 ]
|