The image ships pi with no provider and no preferences, so a sandboxed run
meant configuring the model twice. ai-sandbox now reads the host
~/.pi/agent/{models,settings}.json and rewrites them instead of copying them:
every provider apiKey becomes "$AI_API_TOKEN" (pi resolves an env reference
itself, so no value is written to disk), '!command' header values are dropped
rather than executed, settings that point into $HOME are dropped because that
path is not mounted, and auth.json / models-store.json / sessions / extensions
are never read.
The rewritten pair is seeded into /home/sandbox/.pi/agent at container start,
owned by uid 1000 and mode 600, and is treated as managed: a host change is
re-seeded, an edit made inside the sandbox is kept and reported, a deletion
takes the host version again.
The token follows the existing environment rules -- one variable, passed by
name through the 0600 env file, announced when auto-passed. A bridged run
whose token variable is unset or empty is refused instead of starting a pi
that cannot reach its model, and the bridge never widens the allowlist: it
warns with the exact --allow to add.
Docs and examples now use a self-hosted gateway (https://aipi.reeseapps.com)
instead of vendor API endpoints, since that is the only thing this tool is
used against. 391 assertions pass (make lint, ./tests/run.sh --e2e).
LLM-Generated: true
85 lines
2.9 KiB
Bash
85 lines
2.9 KiB
Bash
#!/usr/bin/env bash
|
|
# tests/lib.sh -- tiny assertion helpers for the ai-sandbox test suite.
|
|
#
|
|
# Every test file gets: AISB (path to the script), WORK (scratch dir), and uses
|
|
# sb() to run the sandbox with fully redirected XDG dirs so nothing touches the
|
|
# real machine.
|
|
|
|
PASS=0
|
|
FAIL=0
|
|
FAILED_NAMES=()
|
|
_current=""
|
|
|
|
t_name() { _current="$1"; printf '\n\033[1m# %s\033[0m\n' "$1"; }
|
|
|
|
_ok() { PASS=$((PASS + 1)); printf ' \033[32mok\033[0m %s\n' "$1"; }
|
|
_bad() { FAIL=$((FAIL + 1)); FAILED_NAMES+=("$_current :: $1"); printf ' \033[31mFAIL\033[0m %s\n' "$1"; shift || true;
|
|
[[ -n "${1:-}" ]] && printf ' %s\n' "$1"; return 0; }
|
|
|
|
# sb [args...] -> sets RC, OUT (stdout+stderr)
|
|
sb() {
|
|
OUT="$("$AISB" "$@" 2>&1)" && RC=0 || RC=$?
|
|
return 0
|
|
}
|
|
|
|
# sb_timeout <seconds> [args...] -- like sb, but bounded. Use it when a test expects
|
|
# the script to refuse: the refusal happens before podman, and the timeout is there in
|
|
# case a regression turns "refused instantly" into "hung forever".
|
|
sb_timeout() {
|
|
local t="$1"; shift
|
|
OUT="$(timeout "$t" "$AISB" "$@" 2>&1 </dev/null)" && RC=$? || RC=$?
|
|
return 0
|
|
}
|
|
|
|
# sb_env VAR=val ... -- args (env prefix, without leaking into the test shell)
|
|
sb_env() {
|
|
local -a envs=()
|
|
while (($#)) && [[ "$1" != -* && "$1" == *=* ]]; do envs+=("$1"); shift; done
|
|
OUT="$(env "${envs[@]}" "$AISB" "$@" 2>&1)" && RC=0 || RC=$?
|
|
return 0
|
|
}
|
|
|
|
assert_rc() { # assert_rc <expected> <label>
|
|
if [[ "$RC" == "$1" ]]; then _ok "$2"
|
|
else _bad "$2" "expected rc=$1 got rc=$RC; output: $(printf '%s' "$OUT" | head -3 | tr '\n' ' ')"; fi
|
|
}
|
|
|
|
assert_contains() { # assert_contains <needle> <label>
|
|
if printf '%s' "$OUT" | grep -qF -- "$1"; then _ok "$2"
|
|
else _bad "$2" "missing: '$1' in: $(printf '%s' "$OUT" | head -5 | tr '\n' ' ')"; fi
|
|
}
|
|
|
|
assert_absent() { # assert_absent <needle> <label>
|
|
if printf '%s' "$OUT" | grep -qF -- "$1"; then
|
|
_bad "$2" "unexpectedly present: '$1'"
|
|
else _ok "$2"; fi
|
|
}
|
|
|
|
assert_eq() { # assert_eq <expected> <actual> <label>
|
|
if [[ "$1" == "$2" ]]; then _ok "$3"
|
|
else _bad "$3" "expected '$1' got '$2'"; fi
|
|
}
|
|
|
|
assert_count() { # assert_count <expected> <needle> <label>
|
|
local n
|
|
n="$(printf '%s' "$OUT" | grep -oF -- "$2" | wc -l | tr -d ' ')"
|
|
if [[ "$n" == "$1" ]]; then _ok "$3"
|
|
else _bad "$3" "expected $1 occurrence(s) of '$2', got $n"; fi
|
|
}
|
|
|
|
assert_file_mode() { # assert_file_mode <path> <mode> <label>
|
|
local m; m="$(stat -c '%a' "$1" 2>/dev/null || echo missing)"
|
|
if [[ "$m" == "$2" ]]; then _ok "$3"; else _bad "$3" "expected mode $2 got $m"; fi
|
|
}
|
|
|
|
summary() {
|
|
printf '\n------------------------------------------------------------\n'
|
|
[[ -n "${AISB_TEST_COUNTS:-}" ]] && printf '%s %s\n' "$PASS" "$FAIL" >> "$AISB_TEST_COUNTS"
|
|
if (( FAIL )); then
|
|
printf '\033[31m%d passed, %d FAILED\033[0m\n' "$PASS" "$FAIL"
|
|
local f; for f in "${FAILED_NAMES[@]}"; do printf ' - %s\n' "$f"; done
|
|
return 1
|
|
fi
|
|
printf '\033[32m%d passed, 0 failed\033[0m\n' "$PASS"
|
|
}
|