Candidate manifest 68c60a79914692f3f6d4923f7bcff41de109ccca33bf6fba687fb88efa24bcac from Rocko's round 1 packet (a94a943c9), on BASE72c9ac36c. No manifest path changed between BASE and the landing target. Filbert approved round 1 in 28497 with no findings; the TOOLS.md paragraph break is a doc-only follow-up (decision 137). Sage reran the full gate at the landing target with the candidate applied and Docker live. Nothing is installed or restarted; the live switch is Mos's step after the SetSpark freeze lifts. Co-Authored-By: Claude Opus 5.5 <[email protected]>
271 lines
19 KiB
Bash
Executable File
271 lines
19 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/bin/*.mjs packages/discord/extension/*.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 read-only tools extension against the real pi, offline, no model call ---
|
||
# A probe extension loaded next to ours reports pi's active tool list on
|
||
# session_start; the flags are the ones buildPiArgs emits with tools.
|
||
PI_BIN="$(pwd)/node_modules/.bin/pi"
|
||
EXT_DIR="$(pwd)/packages/discord/extension"
|
||
PROBE="$SANDBOX/probe-ext.mjs"
|
||
cat >"$PROBE" <<'EOF_PROBE'
|
||
export default function (pi) {
|
||
pi.on("session_start", async () => { process.stderr.write(`PROBE ${JSON.stringify(pi.getActiveTools().sort())}\n`); });
|
||
}
|
||
EOF_PROBE
|
||
mkdir -p "$SANDBOX/toolroot/plans"
|
||
echo "row one" >"$SANDBOX/toolroot/plans/QUEUE.md"
|
||
TOOLS_JSON="{\"roots\":[{\"name\":\"docs\",\"path\":\"$SANDBOX/toolroot\"}],\"maxFileBytes\":4096,\"maxCallsPerTurn\":8}"
|
||
PI_COMMON="--mode rpc --no-extensions --no-context-files --no-skills --no-prompt-templates --no-themes --offline --no-session --provider zai --model glm-5.3"
|
||
if [ -x "$PI_BIN" ]; then
|
||
printf '{"type":"get_state","id":"a"}\n' | MOSAIC_DISCORD_TOOLS="$TOOLS_JSON" timeout 60 "$PI_BIN" $PI_COMMON --no-builtin-tools \
|
||
--extension "$EXT_DIR/tools.mjs" --extension "$PROBE" --tools list_dir,read_file,search \
|
||
>"$SANDBOX/pi-tools.out" 2>"$SANDBOX/pi-tools.err"
|
||
grep -qxF 'PROBE ["list_dir","read_file","search"]' "$SANDBOX/pi-tools.err" && grep -q '"command":"get_state","success":true' "$SANDBOX/pi-tools.out"
|
||
check "real pi with the extension exposes exactly list_dir, read_file, search and no built-in tool" $?
|
||
mkdir -p "$SANDBOX/vault/Businesses"
|
||
TOOLS_RW="{\"roots\":[{\"name\":\"docs\",\"path\":\"$SANDBOX/toolroot\"},{\"name\":\"vault\",\"path\":\"$SANDBOX/vault\",\"write\":true}],\"maxFileBytes\":4096,\"maxCallsPerTurn\":8}"
|
||
printf '{"type":"get_state","id":"a"}\n' | MOSAIC_DISCORD_TOOLS="$TOOLS_RW" timeout 60 "$PI_BIN" $PI_COMMON --no-builtin-tools \
|
||
--extension "$EXT_DIR/tools.mjs" --extension "$PROBE" --tools list_dir,read_file,search,write_file,edit_file \
|
||
>"$SANDBOX/pi-rw.out" 2>"$SANDBOX/pi-rw.err"
|
||
grep -qxF 'PROBE ["edit_file","list_dir","read_file","search","write_file"]' "$SANDBOX/pi-rw.err" && grep -q '"command":"get_state","success":true' "$SANDBOX/pi-rw.out" && [ ! -e "$SANDBOX/vault/Businesses/x" ] && [ -z "$(ls -A "$SANDBOX/vault/Businesses")" ]
|
||
check "real pi with a writable root exposes exactly the three reads plus write_file and edit_file, and writes nothing at start" $?
|
||
TOOLS_WEB="{\"roots\":[{\"name\":\"docs\",\"path\":\"$SANDBOX/toolroot\"}],\"maxFileBytes\":4096,\"maxCallsPerTurn\":8,\"web\":{\"searxng\":\"http://127.0.0.1:1\"}}"
|
||
printf '{"type":"get_state","id":"a"}\n' | MOSAIC_DISCORD_TOOLS="$TOOLS_WEB" timeout 60 "$PI_BIN" $PI_COMMON --no-builtin-tools \
|
||
--extension "$EXT_DIR/tools.mjs" --extension "$PROBE" --tools list_dir,read_file,search,web_fetch,web_search \
|
||
>"$SANDBOX/pi-web.out" 2>"$SANDBOX/pi-web.err"
|
||
grep -qxF 'PROBE ["list_dir","read_file","search","web_fetch","web_search"]' "$SANDBOX/pi-web.err" && grep -q '"command":"get_state","success":true' "$SANDBOX/pi-web.out"
|
||
check "real pi with a web key exposes the three reads plus web_fetch and web_search, and no write tool without a writable root" $?
|
||
# A git root (row 24): a sandbox work tree with an origin, a 0600 token
|
||
# file whose content is shaped nothing like a real token.
|
||
GITROOT="$SANDBOX/gitroot"
|
||
git init -q -b main "$GITROOT" && git -C "$GITROOT" -c user.name=t -c user.email=t@t commit -q --allow-empty -m init
|
||
git init -q --bare -b main "$SANDBOX/gitremote.git" && git -C "$GITROOT" remote add origin "$SANDBOX/gitremote.git"
|
||
printf 'not_a_real_token_just_a_test_value_x\n' >"$SANDBOX/gittoken" && chmod 0600 "$SANDBOX/gittoken"
|
||
GIT_JSON="{\"branch\":\"main\",\"identity\":\"sage\",\"tokenFile\":\"$SANDBOX/gittoken\",\"author\":\"Sage <[email protected]>\"}"
|
||
TOOLS_GIT="{\"roots\":[{\"name\":\"docs\",\"path\":\"$SANDBOX/toolroot\"},{\"name\":\"ss\",\"path\":\"$GITROOT\",\"write\":true,\"git\":$GIT_JSON}],\"maxFileBytes\":4096,\"maxCallsPerTurn\":8}"
|
||
printf '{"type":"get_state","id":"a"}\n' | MOSAIC_DISCORD_TOOLS="$TOOLS_GIT" timeout 60 "$PI_BIN" $PI_COMMON --no-builtin-tools \
|
||
--extension "$EXT_DIR/tools.mjs" --extension "$PROBE" --tools list_dir,read_file,search,write_file,edit_file,git_status,git_commit,git_pull,git_push \
|
||
>"$SANDBOX/pi-git.out" 2>"$SANDBOX/pi-git.err"
|
||
grep -qxF 'PROBE ["edit_file","git_commit","git_pull","git_push","git_status","list_dir","read_file","search","write_file"]' "$SANDBOX/pi-git.err" && grep -q '"command":"get_state","success":true' "$SANDBOX/pi-git.out" && [ "$(git -C "$GITROOT" rev-list --count HEAD)" = "1" ] && ! grep -rq 'not_a_real_token' "$SANDBOX/pi-git.out" "$SANDBOX/pi-git.err"
|
||
check "real pi with a git root exposes the reads, writes and the four git verbs, commits nothing at start, and never shows the token" $?
|
||
TOOLS_VAULT="{\"roots\":[{\"name\":\"ss\",\"path\":\"$GITROOT\",\"write\":true,\"git\":${GIT_JSON%\}},\"protocol\":\"vault\"}}],\"maxFileBytes\":4096,\"maxCallsPerTurn\":8}"
|
||
printf '{"type":"get_state","id":"a"}\n' | MOSAIC_DISCORD_TOOLS="$TOOLS_VAULT" timeout 60 "$PI_BIN" $PI_COMMON --no-builtin-tools \
|
||
--extension "$EXT_DIR/tools.mjs" --extension "$PROBE" --tools list_dir,read_file,search,write_file,edit_file,git_status,git_commit,git_pull,git_push,reserve_id \
|
||
>"$SANDBOX/pi-vault.out" 2>"$SANDBOX/pi-vault.err"
|
||
grep -qxF 'PROBE ["edit_file","git_commit","git_pull","git_push","git_status","list_dir","read_file","reserve_id","search","write_file"]' "$SANDBOX/pi-vault.err" && grep -q '"command":"get_state","success":true' "$SANDBOX/pi-vault.out"
|
||
check "real pi with protocol vault adds reserve_id to the git verbs" $?
|
||
printf 'not_a_real_key_just_a_test_value_x\n' >"$SANDBOX/setspark.key" && chmod 0600 "$SANDBOX/setspark.key"
|
||
TOOLS_SETSPARK="{\"roots\":[{\"name\":\"docs\",\"path\":\"$SANDBOX/toolroot\"}],\"maxFileBytes\":4096,\"maxCallsPerTurn\":8,\"setspark\":{\"baseUrl\":\"https://api.setspark.invalid\",\"keyFile\":\"$SANDBOX/setspark.key\",\"principal\":\"sage\"}}"
|
||
printf '{"type":"get_state","id":"a"}\n' | MOSAIC_DISCORD_TOOLS="$TOOLS_SETSPARK" timeout 60 "$PI_BIN" $PI_COMMON --no-builtin-tools \
|
||
--extension "$EXT_DIR/tools.mjs" --extension "$PROBE" --tools list_dir,read_file,search,record_list,record_get,record_create,record_update,resolve_id,open_approval_request,get_approval_request,create_document \
|
||
>"$SANDBOX/pi-setspark.out" 2>"$SANDBOX/pi-setspark.err"
|
||
grep -qxF 'PROBE ["create_document","get_approval_request","list_dir","open_approval_request","read_file","record_create","record_get","record_list","record_update","resolve_id","search"]' "$SANDBOX/pi-setspark.err" && grep -q '"command":"get_state","success":true' "$SANDBOX/pi-setspark.out" && ! grep -rq 'not_a_real_key' "$SANDBOX/pi-setspark.out" "$SANDBOX/pi-setspark.err"
|
||
check "real pi with a setspark key exposes the reads and the eight record verbs, no counters, and never shows the key" $?
|
||
TOOLS_GIT_RO="{\"roots\":[{\"name\":\"ss\",\"path\":\"$GITROOT\",\"git\":$GIT_JSON}],\"maxFileBytes\":4096,\"maxCallsPerTurn\":8}"
|
||
printf '{"type":"get_state","id":"a"}\n' | MOSAIC_DISCORD_TOOLS="$TOOLS_GIT_RO" timeout 60 "$PI_BIN" $PI_COMMON --no-builtin-tools \
|
||
--extension "$EXT_DIR/tools.mjs" --extension "$PROBE" --tools list_dir,read_file,search \
|
||
>"$SANDBOX/pi-gitro.out" 2>"$SANDBOX/pi-gitro.err"
|
||
GITRO_RC=$?
|
||
[ "$GITRO_RC" -ne 0 ] && grep -q 'git needs write: true' "$SANDBOX/pi-gitro.err" && ! grep -q 'PROBE' "$SANDBOX/pi-gitro.err"
|
||
check "real pi refuses a git key on a read-only root (fail closed)" $?
|
||
printf '{"type":"get_state","id":"a"}\n' | timeout 60 "$PI_BIN" $PI_COMMON --no-tools --extension "$PROBE" \
|
||
>"$SANDBOX/pi-notools.out" 2>"$SANDBOX/pi-notools.err"
|
||
grep -qxF 'PROBE []' "$SANDBOX/pi-notools.err"
|
||
check "real pi with the pilot flags (--no-tools) exposes no tool at all" $?
|
||
printf '{"type":"get_state","id":"a"}\n' | env -u MOSAIC_DISCORD_TOOLS timeout 60 "$PI_BIN" $PI_COMMON --no-builtin-tools \
|
||
--extension "$EXT_DIR/tools.mjs" --extension "$PROBE" --tools list_dir,read_file,search \
|
||
>"$SANDBOX/pi-noenv.out" 2>"$SANDBOX/pi-noenv.err"
|
||
NOENV_RC=$?
|
||
[ "$NOENV_RC" -ne 0 ] && grep -q 'MOSAIC_DISCORD_TOOLS is not set' "$SANDBOX/pi-noenv.err" && ! grep -q 'PROBE' "$SANDBOX/pi-noenv.err" && [ ! -s "$SANDBOX/pi-noenv.out" ]
|
||
check "real pi exits non-zero without MOSAIC_DISCORD_TOOLS: no session, no tools (fail closed)" $?
|
||
else
|
||
check "pi binary present at node_modules/.bin/pi for the extension checks" 1
|
||
fi
|
||
|
||
# --- the seven offline groups ---
|
||
# A nested `node --test` inherits a parent runner's NODE_TEST_CONTEXT, reports
|
||
# to that runner and exits 0 whatever its tests do, so the suite clears it
|
||
# (#1508 N13). The planted failing test proves a failure still fails here.
|
||
node_tests() { env -u NODE_TEST_CONTEXT node --test "$@"; }
|
||
mkdir -p "$SANDBOX/planted"
|
||
printf '%s\n' 'import { test } from "node:test";' 'import assert from "node:assert/strict";' 'test("planted failure", () => assert.equal(1, 2));' >"$SANDBOX/planted/planted.test.mjs"
|
||
NODE_TEST_CONTEXT=child-v8 node_tests "$SANDBOX/planted/" >"$SANDBOX/planted.log" 2>&1
|
||
[ $? -eq 1 ] && grep -q '^✖ planted failure' "$SANDBOX/planted.log"
|
||
check "a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT" $?
|
||
node_tests --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" $?
|
||
mkdir -p "$SANDBOX/units-nouser"
|
||
env -u USER scripts/discord-service.sh install --dir "$SANDBOX/units-nouser" --no-reload >"$SANDBOX/install.nouser" 2>&1 \
|
||
&& [ -f "$SANDBOX/units-nouser/[email protected]" ] \
|
||
&& grep -qF "enable-linger $(id -un)" "$SANDBOX/install.nouser"
|
||
check "service install with USER unset finishes and names the account for lingering" $?
|
||
|
||
# --- pinned copy service, fixture paths only ---
|
||
COPY_ID="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||
COPIES="$SANDBOX/copies"
|
||
COPY="$COPIES/$COPY_ID"
|
||
mkdir -p "$COPY/packages/discord/src" "$COPY/node_modules/.bin"
|
||
printf '// fixture\n' > "$COPY/packages/discord/src/cli.mjs"
|
||
printf '#!/bin/sh\nexit 0\n' > "$COPY/node_modules/.bin/pi"
|
||
chmod 755 "$COPY/node_modules/.bin/pi"
|
||
scripts/discord-service.sh render --copy "$COPY_ID" --copies "$COPIES" >"$SANDBOX/unit.copy" 2>&1 \
|
||
&& grep -qxF "WorkingDirectory=$COPY" "$SANDBOX/unit.copy" \
|
||
&& grep -qxF "ExecStart=$(command -v node) $COPY/packages/discord/src/cli.mjs run %i --supervised --repo $COPY" "$SANDBOX/unit.copy" \
|
||
&& ! sed -n '/^WorkingDirectory=/p;/^ExecStart=/p' "$SANDBOX/unit.copy" | grep -qF "$REPO_DIR"
|
||
check "pinned render runs copy CLI with copy cwd/repo and no checkout executable" $?
|
||
scripts/discord-service.sh install --copy "$COPY_ID" --copies "$COPIES" --dir "$SANDBOX/pinned-units" --no-reload >"$SANDBOX/pinned-install" 2>&1 \
|
||
&& cmp -s "$SANDBOX/pinned-units/[email protected]" "$SANDBOX/unit.copy"
|
||
check "pinned install writes the rendered unit in a temporary unit directory" $?
|
||
for kind in short missing cli pi; do
|
||
id="$COPY_ID"
|
||
case "$kind" in short) id=short;; missing) id=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb;; cli) rm "$COPY/packages/discord/src/cli.mjs";; pi) printf '// fixture\n' > "$COPY/packages/discord/src/cli.mjs";rm "$COPY/node_modules/.bin/pi";; esac
|
||
scripts/discord-service.sh install --copy "$id" --copies "$COPIES" --dir "$SANDBOX/refused-$kind" --no-reload >"$SANDBOX/refused-$kind.log" 2>&1
|
||
rc=$?
|
||
[ "$rc" -eq 1 ] && [ ! -e "$SANDBOX/refused-$kind/[email protected]" ]
|
||
check "pinned install refuses $kind prerequisite and writes no unit" $?
|
||
done
|
||
printf '#!/bin/sh\nexit 0\n' > "$COPY/node_modules/.bin/pi"
|
||
mkdir -p "$SANDBOX/fake-bin"
|
||
cat >"$SANDBOX/fake-bin/systemctl" <<EOF_STATUS
|
||
#!/bin/sh
|
||
case "\$*" in *show*) echo 'WorkingDirectory=$COPY';; *)echo inactive;; esac
|
||
EOF_STATUS
|
||
chmod 755 "$SANDBOX/fake-bin/systemctl"
|
||
HOME="$SANDBOX/home" XDG_CONFIG_HOME="$SANDBOX/config-home" PATH="$SANDBOX/fake-bin:$PATH" scripts/discord-service.sh status fixture >"$SANDBOX/status.copy" 2>&1 \
|
||
&& grep -qF "copy: $COPY_ID" "$SANDBOX/status.copy" \
|
||
&& grep -qF "tree: $COPY" "$SANDBOX/status.copy"
|
||
check "status names the copy from fake systemctl effective cwd" $?
|
||
grep -qF "checkout: $REPO_DIR" "$SANDBOX/install.1" && grep -qF -- '--copy' "$SANDBOX/install.1"
|
||
check "checkout install explicitly recommends --copy" $?
|
||
|
||
# Offline wrapper entrypoint. Only a synthetic token/binding/config are used.
|
||
node - "$SANDBOX" <<'EOF_OFFLINE'
|
||
const fs=require('fs'),p=require('path'),root=process.argv[2];
|
||
const data=p.join(root,'offline-data');fs.mkdirSync(p.join(data,'discord'),{recursive:true});
|
||
const b=JSON.parse(fs.readFileSync('packages/discord/fixtures/binding.example.json','utf8'));
|
||
b.name='offline-fixture';b.tokenFile=p.join(root,'offline-token');b.context.files=['packages/discord/README.md'];delete b.tools;
|
||
fs.writeFileSync(b.tokenFile,'MTAw.abcdefghijklmnopqrstuvwxyz0123456789\n',{mode:0o600});
|
||
fs.writeFileSync(p.join(data,'discord',b.name+'.json'),JSON.stringify(b),{mode:0o600});
|
||
fs.writeFileSync(p.join(root,'offline-config'),JSON.stringify({dataRoot:data}));
|
||
fs.writeFileSync(p.join(root,'offline-trap.mjs'),`globalThis.fetch=()=>{throw Error('network forbidden in offline fixture')};globalThis.WebSocket=class{constructor(){throw Error('gateway forbidden in offline fixture')}};`);
|
||
EOF_OFFLINE
|
||
NODE_OPTIONS="--import=$SANDBOX/offline-trap.mjs" scripts/discord.sh check offline-fixture --config "$SANDBOX/offline-config" --offline >"$SANDBOX/offline.out" 2>&1 \
|
||
&& grep -qF 'offline check passed; no REST or gateway calls were made' "$SANDBOX/offline.out"
|
||
check "wrapper check --offline passes local fixture and makes no network call" $?
|
||
NODE_OPTIONS="--import=$SANDBOX/offline-trap.mjs" scripts/discord.sh run offline-fixture --config "$SANDBOX/offline-config" --offline >"$SANDBOX/offline-run.out" 2>&1
|
||
[ $? -eq 4 ]
|
||
check "wrapper run --offline refuses usage" $?
|
||
|
||
echo
|
||
echo "discord suite: $PASS passed, $FAIL failed"
|
||
[ "$FAIL" -eq 0 ]
|