#!/usr/bin/env bash # tests/unit/08_pi_config.sh -- the pi configuration bridge. # # The bridge rewrites the host agent directory instead of copying it, so what matters # most here is what does NOT cross the boundary: the credential, host commands, host # paths, auth.json. Everything is preview (`--print-policy` / `--dry-run`) plus the # refusal paths, so no container is started and no token is real. source "$TESTS/lib.sh" AGENT="$WORK/agent"; mkdir -p "$AGENT" export AISB_PI_AGENT_DIR="$AGENT" AISB_PI_TOKEN_AUTO=1 export AI_API_TOKEN=sandbox-only-token-4242 CANARY_SECRET='REALSECRET-nev3r-lef7s-the-host' CANARY_CMD='pass show services/gateway | head -1' cat > "$AGENT/models.json" < "$AGENT/settings.json" < "$AGENT/auth.json" printf '{}' > "$AGENT/models-store.json" clear_sessions() { rm -rf "${AISB_STATE_DIR:?}"/sessions/*; } gen_file() { find "$AISB_STATE_DIR/sessions" -path "*/pi/$1" -print -quit 2>/dev/null; } restore_models() { cat > "$AGENT/models.json" < /dev/null 2>&1; then _bad "the host credential appears nowhere in ai-sandbox state" else _ok "the host credential appears nowhere in ai-sandbox state" fi t_name "the token is passed by name, once, and announced" assert_contains "passing \$AI_API_TOKEN from the host" "the auto-pass is announced" assert_eq "env_names=AI_API_TOKEN" "$(printf '%s' "$OUT" | grep '^env_names=' || true)" "exactly one variable is passed" assert_absent "$AI_API_TOKEN" "and only its name is reported" t_name "--no-pi-token-env leaves it out and says so" clear_sessions sb --keep --no-pi-token-env --print-policy --net none assert_eq "env_names=" "$(printf '%s' "$OUT" | grep '^env_names=' || true)" "nothing is passed" assert_contains "will not reach the container" "preview warns instead of launching a pi with no credential" t_name "a bridged provider with no credential in reach is refused, not attempted" unset AI_API_TOKEN sb --net none --harness pi assert_rc 1 "the run refuses to start" assert_contains 'asks for $AI_API_TOKEN, but it is not going to reach the container' "it names the variable and why" assert_contains "env = AI_API_TOKEN" "it shows the config-file way" assert_contains "--no-pi-config" "and the way to opt out" assert_eq "0" "$([[ -n "$(podman ps -q --filter label=ai-sandbox=1)" ]] && echo 1 || echo 0)" "and nothing was started" t_name "an empty token is treated as missing" sb_env AI_API_TOKEN= --net none --harness pi # set, but empty assert_rc 1 "an empty credential is refused, not sent" assert_contains "unset or empty" "and it says which" sb_env AI_API_TOKEN= --net none --print-policy assert_contains "pi_token_env=AI_API_TOKEN (set but empty)" "preview distinguishes the two states" t_name "but only when pi is the thing being launched" export AI_API_TOKEN=x sb --net none --dry-run -- make test assert_absent "not going to reach the container" "a replaced command needs no credential" sb --net none --dry-run --shell assert_rc 0 "--shell works without a token" assert_absent "not going to reach the container" "and does not demand one" sb --net none --dry-run --harness codex assert_absent "not going to reach the container" "codex does not read pi's models.json" t_name "--no-pi-config opts out cleanly" sb --no-pi-config --net none --print-policy assert_absent "pi_config=" "no bridge is reported" assert_absent "not going to reach the container" "and nothing is required" clear_sessions sb --keep --no-pi-config --net none -- true if [[ -n "$(gen_file models.json)" ]]; then _bad "nothing was generated"; else _ok "nothing was generated"; fi t_name "the token variable name is yours to choose" clear_sessions sb --keep --pi-token-env GW_TOKEN --print-policy --net none assert_contains "pi_token_env=GW_TOKEN (NOT SET)" "reported under the new name" clear_sessions sb_env GW_TOKEN=whatever-gw --keep --pi-token-env GW_TOKEN --print-policy --net none assert_contains "pi_token_env=GW_TOKEN (set)" "it is recognised as in reach" assert_contains "passing \$GW_TOKEN" "and announced under that name" M2="$(gen_file models.json)" grep -qF '"apiKey": "$GW_TOKEN"' "${M2:-/nonexistent}" && _ok "the generated config references it" || _bad "the generated config references it" sb --net none --harness pi --pi-token-env GW_TOKEN # GW_TOKEN is not set assert_rc 1 "and it is enforced under that name too" t_name "a missing agent directory is a skip, not an error" sb_env AISB_PI_AGENT_DIR="$WORK/nope" --net none --print-policy assert_rc 0 "nothing to bridge, nothing demanded" assert_absent "pi_config=" "and no bridge is claimed" sb_env AISB_PI_AGENT_DIR="$WORK/nope" --net none --harness pi assert_rc 0 "pi may still be launched, with its own defaults" t_name "broken JSON is reported, not propagated" printf '{ "providers": oops' > "$AGENT/models.json" clear_sessions sb --keep --net none --print-policy assert_contains "models.json is not valid JSON" "it says the file is broken" if grep -qF 'oops' "${AISB_STATE_DIR}/sessions"/*/mnt/pi/models.json 2>/dev/null; then _bad "no invalid file is placed in front of pi" else _ok "no invalid file is placed in front of pi" fi restore_models t_name "the model endpoint has to be allowed, and says when it is not" sb --net allow --allow registry.npmjs.org --print-policy assert_contains "the model endpoint is not in the allowlist" "a pi that cannot reach its gateway is a warning, not a mystery" assert_contains "add --allow aipi.reeseapps.com" "and it names the host to add" sb --net allow --allow aipi.reeseapps.com --allow 10.20.0.7 --print-policy assert_absent "not in the allowlist" "silent once both endpoints are allowed" t_name "the bridge never widens the allowlist by itself" sb --net allow --allow aipi.reeseapps.com --print-policy assert_contains "gate_domains=aipi.reeseapps.com" "what you asked for is what you got" assert_absent "10.20.0.7 80,443" "the discovered endpoint was not added on your behalf" t_name "an invalid token variable name is rejected" sb --net none --pi-token-env '9bad name' --print-policy assert_rc 1 "rejected" assert_contains "not a valid identifier" "with a clear reason"