20 cases: bootstrap create/idempotency, missing config, malformed JSON, unknown keys/version/backend/environment, relative and non-canonical dataRoot, filesystem root, home dir, ancestor-of-config, control chars, symlinked config file, env export resolution, validation-writes-nothing. Closes #3
143 lines
6.3 KiB
Bash
Executable File
143 lines
6.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Fast, sandboxed selftests for the configuration layer.
|
|
#
|
|
# No Docker, no network, no credentials: every case runs against a
|
|
# temporary config via MOSAIC_CONFIG. Suitable for frequent local runs.
|
|
set -uo pipefail
|
|
cd "$(dirname "$0")/.."
|
|
|
|
SANDBOX="$(mktemp -d)"
|
|
trap 'rm -rf "$SANDBOX"' EXIT
|
|
|
|
PASS=0
|
|
FAIL=0
|
|
|
|
# expect_exit NAME EXPECTED_RC -- command...
|
|
expect_exit() {
|
|
local name="$1" expected="$2"
|
|
shift 3 # name, expected, "--"
|
|
local rc
|
|
"$@" >/dev/null 2>&1
|
|
rc=$?
|
|
if [ "$rc" -eq "$expected" ]; then
|
|
PASS=$((PASS + 1))
|
|
echo "ok $name (exit $rc)"
|
|
else
|
|
FAIL=$((FAIL + 1))
|
|
echo "FAIL $name (exit $rc, expected $expected)"
|
|
fi
|
|
}
|
|
|
|
CONFIG_OP="node scripts/mosaic-config.mjs"
|
|
|
|
valid_body() {
|
|
cat <<EOF
|
|
{"configVersion":1,"environment":"development","dataRoot":"$1","execution":{"backend":"docker","provider":"zai","model":"glm-5.3-flash"}}
|
|
EOF
|
|
}
|
|
|
|
cfg() { printf '%s' "$2" > "$SANDBOX/$1"; }
|
|
|
|
DATA_ROOT="$SANDBOX/data"
|
|
|
|
# --- bootstrap ---
|
|
rm -f "$SANDBOX/config.json"
|
|
expect_exit "bootstrap creates default when absent" 0 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/config.json" $CONFIG_OP bootstrap
|
|
[ -f "$SANDBOX/config.json" ] && { PASS=$((PASS+1)); echo "ok bootstrap wrote config file"; } \
|
|
|| { FAIL=$((FAIL+1)); echo "FAIL bootstrap wrote config file"; }
|
|
|
|
SUM_BEFORE=$(sha256sum "$SANDBOX/config.json" | cut -d' ' -f1)
|
|
MTIME_BEFORE=$(stat -c %Y "$SANDBOX/config.json")
|
|
sleep 1.1
|
|
expect_exit "bootstrap is idempotent on existing config" 0 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/config.json" $CONFIG_OP bootstrap
|
|
SUM_AFTER=$(sha256sum "$SANDBOX/config.json" | cut -d' ' -f1)
|
|
MTIME_AFTER=$(stat -c %Y "$SANDBOX/config.json")
|
|
if [ "$SUM_BEFORE" = "$SUM_AFTER" ] && [ "$MTIME_BEFORE" = "$MTIME_AFTER" ]; then
|
|
PASS=$((PASS+1)); echo "ok bootstrap did not rewrite existing config"
|
|
else
|
|
FAIL=$((FAIL+1)); echo "FAIL bootstrap rewrote existing config"
|
|
fi
|
|
|
|
# --- validate ---
|
|
expect_exit "validate missing config exits 3" 3 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/absent.json" $CONFIG_OP validate
|
|
|
|
cfg invalid.json '{'
|
|
expect_exit "malformed JSON exits 2" 2 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/invalid.json" $CONFIG_OP validate
|
|
|
|
cfg badversion.json '{"configVersion":2,"environment":"development","dataRoot":"'$DATA_ROOT'","execution":{"backend":"docker","provider":"zai","model":"m"}}'
|
|
expect_exit "unsupported configVersion exits 2" 2 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/badversion.json" $CONFIG_OP validate
|
|
|
|
cfg unknownkey.json '{"configVersion":1,"environment":"development","dataRoot":"'$DATA_ROOT'","extra":true,"execution":{"backend":"docker","provider":"zai","model":"m"}}'
|
|
expect_exit "unknown top-level key exits 2" 2 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/unknownkey.json" $CONFIG_OP validate
|
|
|
|
cfg unknownexec.json '{"configVersion":1,"environment":"development","dataRoot":"'$DATA_ROOT'","execution":{"backend":"docker","provider":"zai","model":"m","extra":1}}'
|
|
expect_exit "unknown execution key exits 2" 2 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/unknownexec.json" $CONFIG_OP validate
|
|
|
|
cfg badbackend.json '{"configVersion":1,"environment":"development","dataRoot":"'$DATA_ROOT'","execution":{"backend":"podman","provider":"zai","model":"m"}}'
|
|
expect_exit "unsupported backend exits 2" 2 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/badbackend.json" $CONFIG_OP validate
|
|
|
|
cfg badenv.json '{"configVersion":1,"environment":"staging","dataRoot":"'$DATA_ROOT'","execution":{"backend":"docker","provider":"zai","model":"m"}}'
|
|
expect_exit "unsupported environment exits 2" 2 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/badenv.json" $CONFIG_OP validate
|
|
|
|
cfg relative.json '{"configVersion":1,"environment":"development","dataRoot":"relative/path","execution":{"backend":"docker","provider":"zai","model":"m"}}'
|
|
expect_exit "relative dataRoot exits 2" 2 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/relative.json" $CONFIG_OP validate
|
|
|
|
cfg traversal.json '{"configVersion":1,"environment":"development","dataRoot":"/tmp/../home/x","execution":{"backend":"docker","provider":"zai","model":"m"}}'
|
|
expect_exit "non-canonical dataRoot exits 2" 2 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/traversal.json" $CONFIG_OP validate
|
|
|
|
cfg root.json '{"configVersion":1,"environment":"development","dataRoot":"/","execution":{"backend":"docker","provider":"zai","model":"m"}}'
|
|
expect_exit "filesystem root dataRoot exits 2" 2 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/root.json" $CONFIG_OP validate
|
|
|
|
cfg home.json '{"configVersion":1,"environment":"development","dataRoot":"'$HOME'","execution":{"backend":"docker","provider":"zai","model":"m"}}'
|
|
expect_exit "home directory dataRoot exits 2" 2 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/home.json" $CONFIG_OP validate
|
|
|
|
cfg cfgdir.json '{"configVersion":1,"environment":"development","dataRoot":"'$SANDBOX'","execution":{"backend":"docker","provider":"zai","model":"m"}}'
|
|
expect_exit "dataRoot containing config dir exits 2" 2 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/cfgdir.json" $CONFIG_OP validate
|
|
|
|
cfg ctrlchar.json '{"configVersion":1,"environment":"development","dataRoot":"'$DATA_ROOT'","execution":{"backend":"docker","provider":"z\nai","model":"m"}}'
|
|
expect_exit "control character in provider exits 2" 2 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/ctrlchar.json" $CONFIG_OP validate
|
|
|
|
cfg symlink.json 'placeholder'
|
|
ln -sf "$SANDBOX/invalid.json" "$SANDBOX/symlink.json"
|
|
expect_exit "symlinked config file exits 2" 2 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/symlink.json" $CONFIG_OP validate
|
|
|
|
# --- env resolution ---
|
|
cfg valid.json "$(valid_body "$DATA_ROOT")"
|
|
EVAL_OUT="$(MOSAIC_CONFIG="$SANDBOX/valid.json" $CONFIG_OP env)" || true
|
|
if eval "$EVAL_OUT" 2>/dev/null && [ "$MOSAIC_DATA_ROOT" = "$DATA_ROOT" ] \
|
|
&& [ "$MOSAIC_PROVIDER" = "zai" ] && [ "$MOSAIC_MODEL" = "glm-5.3-flash" ]; then
|
|
PASS=$((PASS+1)); echo "ok env exports resolve correctly"
|
|
else
|
|
FAIL=$((FAIL+1)); echo "FAIL env exports resolve correctly"
|
|
fi
|
|
|
|
# --- validation must not modify the file ---
|
|
SUM_INVALID_BEFORE=$(sha256sum "$SANDBOX/invalid.json" | cut -d' ' -f1)
|
|
MOSAIC_CONFIG="$SANDBOX/invalid.json" $CONFIG_OP validate >/dev/null 2>&1
|
|
SUM_INVALID_AFTER=$(sha256sum "$SANDBOX/invalid.json" | cut -d' ' -f1)
|
|
if [ "$SUM_INVALID_BEFORE" = "$SUM_INVALID_AFTER" ]; then
|
|
PASS=$((PASS+1)); echo "ok failed validation modified nothing"
|
|
else
|
|
FAIL=$((FAIL+1)); echo "FAIL failed validation modified the file"
|
|
fi
|
|
|
|
echo
|
|
echo "selftest: $PASS passed, $FAIL failed"
|
|
[ "$FAIL" -eq 0 ]
|