xworkspace-console/tests/setup-ai-workspace-all-in-one-test.sh

309 lines
11 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BOOTSTRAP="$SCRIPT_DIR/../scripts/setup-ai-workspace-all-in-one.sh"
export AI_WORKSPACE_BOOTSTRAP_LIB_ONLY=true
# shellcheck source=/dev/null
source "$BOOTSTRAP"
fail() {
printf 'not ok - %s\n' "$1" >&2
exit 1
}
test_root_does_not_require_sudo() (
# shellcheck disable=SC2329
id() {
[ "${1:-}" = "-u" ] && printf '0\n'
}
# shellcheck disable=SC2329
command() {
if [ "${1:-}" = "-v" ] && [ "${2:-}" = "sudo" ]; then
return 1
fi
builtin command "$@"
}
probe_file="$(mktemp)"
# The positional parameter is intentionally expanded by the child shell.
# shellcheck disable=SC2016
run_as_root sh -c 'printf root > "$1"' sh "$probe_file"
[ "$(cat "$probe_file")" = "root" ] || fail "root command was not executed directly"
rm -f "$probe_file"
)
test_non_root_uses_sudo() (
# shellcheck disable=SC2329
id() {
[ "${1:-}" = "-u" ] && printf '1000\n'
}
# shellcheck disable=SC2329
sudo() {
printf '%s\n' "$*" > "$sudo_log"
}
# shellcheck disable=SC2329
command() {
if [ "${1:-}" = "-v" ] && [ "${2:-}" = "sudo" ]; then
return 0
fi
builtin command "$@"
}
sudo_log="$(mktemp)"
run_as_root apt-get update -y
[ "$(cat "$sudo_log")" = "apt-get update -y" ] || fail "non-root command did not use sudo"
rm -f "$sudo_log"
)
test_non_root_without_sudo_fails_cleanly() (
# shellcheck disable=SC2329
id() {
[ "${1:-}" = "-u" ] && printf '1000\n'
}
# shellcheck disable=SC2329
command() {
if [ "${1:-}" = "-v" ] && [ "${2:-}" = "sudo" ]; then
return 1
fi
builtin command "$@"
}
run_as_root apt-get update -y
)
test_forced_offline_mode_does_not_refresh_repositories() (
# shellcheck disable=SC2329
git() {
fail "forced offline mode invoked git"
}
AI_WORKSPACE_OFFLINE_MODE=force refresh_offline_package_repositories /nonexistent
)
test_auto_offline_mode_refreshes_packaged_repositories() (
package_root="$(mktemp -d)"
mkdir -p "$package_root/repos/xworkspace-console/.git" "$package_root/repos/playbooks/.git"
git_log="$(mktemp)"
# shellcheck disable=SC2329
curl() {
return 0
}
# shellcheck disable=SC2329
git() {
printf '%s\n' "$*" >> "$git_log"
if [ "${3:-}" = "symbolic-ref" ]; then
printf 'main\n'
fi
}
AI_WORKSPACE_OFFLINE_MODE=auto refresh_offline_package_repositories "$package_root"
[ "$(grep -c 'fetch origin main' "$git_log")" -eq 2 ] || fail "packaged repositories were not fetched"
[ "$(grep -c 'reset --hard origin/main' "$git_log")" -eq 2 ] || fail "packaged repositories were not updated"
rm -rf "$package_root" "$git_log"
)
test_offline_refresh_installs_git_on_fresh_debian() (
install_log="$(mktemp)"
# shellcheck disable=SC2329
command() {
if [ "${1:-}" = "-v" ] && [ "${2:-}" = "git" ]; then
return 1
fi
builtin command "$@"
}
# shellcheck disable=SC2329
wait_for_apt_locks() { :; }
# shellcheck disable=SC2329
run_as_root() { printf '%s\n' "$*" >> "$install_log"; }
ensure_git_for_offline_refresh "debian 13 amd64"
grep -q '^apt-get update -y$' "$install_log" || fail "fresh Debian did not refresh apt metadata"
grep -q '^env DEBIAN_FRONTEND=noninteractive apt-get install -y git$' "$install_log" || fail "fresh Debian did not install Git"
rm -f "$install_log"
)
test_ubuntu_2604_offline_package_requires_npm() (
package_root="$(mktemp -d)"
mkdir -p "$package_root/packages/apt"
if validate_offline_package_requirements "$package_root" "ubuntu 26.04 amd64" 2>/dev/null; then
fail "Ubuntu 26.04 package without npm was accepted"
fi
touch "$package_root/packages/apt/npm_9.2.0_all.deb"
validate_offline_package_requirements "$package_root" "ubuntu 26.04 amd64"
validate_offline_package_requirements "$package_root" "debian 12 amd64"
rm -rf "$package_root"
)
test_dynamic_parallel_limit_avoids_awk_reserved_names() (
# shellcheck disable=SC2329
online_cpu_count() { printf '4\n'; }
# shellcheck disable=SC2329
one_minute_load_average() { printf '1.2\n'; }
[ "$(AI_WORKSPACE_MAX_PARALLEL_JOBS=auto dynamic_parallel_job_limit)" = "6" ] || fail "dynamic parallel limit was calculated incorrectly"
)
test_offline_installer_gets_scoped_git_config() (
installer_root="$(mktemp -d)"
mkdir -p "$installer_root/scripts"
cat > "$installer_root/scripts/ai-workspace-offline-install.sh" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
[ "${GIT_CONFIG_COUNT:-}" = "1" ]
[ "${GIT_CONFIG_KEY_0:-}" = "safe.directory" ]
[ "${GIT_CONFIG_VALUE_0:-}" = "*" ]
EOF
chmod +x "$installer_root/scripts/ai-workspace-offline-install.sh"
# shellcheck disable=SC2329
validate_offline_package_target() { :; }
# shellcheck disable=SC2329
id() {
[ "${1:-}" = "-u" ] && printf '0\n'
}
run_offline_installer "$installer_root" "debian 13 amd64"
rm -rf "$installer_root"
)
test_linux_root_defaults_to_ubuntu_home() (
# shellcheck disable=SC2329
id() {
if [ "${1:-}" = "-u" ]; then
printf '0\n'
elif [ "${1:-}" = "-un" ]; then
printf 'root\n'
fi
}
# shellcheck disable=SC2329
getent() {
return 1
}
user="$(linux_default_console_user)"
home="$(linux_default_console_home "$user")"
[ "$user" = "ubuntu" ] || fail "root Linux default user was not ubuntu"
[ "$home" = "/home/ubuntu" ] || fail "root Linux default home was not /home/ubuntu"
)
test_linux_non_root_uses_current_user_home() (
# shellcheck disable=SC2329
id() {
if [ "${1:-}" = "-u" ]; then
printf '501\n'
elif [ "${1:-}" = "-un" ]; then
printf 'shenlan\n'
fi
}
# shellcheck disable=SC2329
getent() {
[ "${1:-}" = "passwd" ] && [ "${2:-}" = "shenlan" ] || return 1
printf 'shenlan:x:501:20::/Users/shenlan:/bin/zsh\n'
}
user="$(linux_default_console_user)"
home="$(linux_default_console_home "$user")"
[ "$user" = "shenlan" ] || fail "non-root Linux default user was not current user"
[ "$home" = "/Users/shenlan" ] || fail "non-root Linux default home did not come from passwd"
)
test_linux_identity_vars_can_be_overridden() (
export XWORKSPACE_CONSOLE_USER=deploy
export XWORKSPACE_CONSOLE_HOME=/srv/deploy
user="$(linux_default_console_user)"
home="$(linux_default_console_home "$user")"
[ "$user" = "deploy" ] || fail "explicit console user was ignored"
[ "$home" = "/srv/deploy" ] || fail "explicit console home was ignored"
ANSIBLE_EXTRA_VARS=()
append_linux_console_identity_vars "$user" "$home"
printf '%s\n' "${ANSIBLE_EXTRA_VARS[@]}" | grep -q '^xworkspace_console_user=deploy$' || fail "console user extra var missing"
printf '%s\n' "${ANSIBLE_EXTRA_VARS[@]}" | grep -q '^xworkspace_console_home=/srv/deploy$' || fail "console home extra var missing"
printf '%s\n' "${ANSIBLE_EXTRA_VARS[@]}" | grep -q '^xworkspace_console_repo_dir=/srv/deploy/xworkspace-console$' || fail "console repo extra var missing"
)
test_provider_api_keys_use_secret_logging() {
local env_name ansible_var
while read -r env_name ansible_var; do
grep -Fq "append_secret_var \"$ansible_var\" \"\${$env_name:-}\"" "$BOOTSTRAP" ||
fail "$env_name is not passed through the masked secret logger"
if grep -Fq "append_var \"$env_name\"" "$BOOTSTRAP"; then
fail "$env_name is still passed through the plain-text parameter logger"
fi
done <<'EOF'
DEEPSEEK_API_KEY litellm_deepseek_api_key
NVIDIA_API_KEY litellm_nvidia_api_key
OLLAMA_API_KEY litellm_ollama_api_key
GEMINI_API_KEY litellm_gemini_api_key
OPENAI_API_KEY litellm_openai_api_key
ANTHROPIC_API_KEY litellm_anthropic_api_key
EOF
}
test_macos_plugin_patch_uses_stable_directory() {
local patcher="$SCRIPT_DIR/../scripts/patch-macos-playbooks.py"
grep -Fq 'Remove legacy temporary plugin symlink (macOS)' "$patcher" ||
fail "macOS plugin patch does not migrate the legacy temporary symlink"
grep -Fq 'Ensure stable openclaw-multi-session-plugins directory (macOS)' "$patcher" ||
fail "macOS plugin patch does not create a stable extension directory"
grep -Fq 'Copy built openclaw-multi-session-plugins into stable directory (macOS)' "$patcher" ||
fail "macOS plugin patch does not copy the built plugin into stable storage"
grep -Fq 'Record stable openclaw-multi-session-plugins install (macOS)' "$patcher" ||
fail "macOS plugin patch does not record stable OpenClaw provenance"
if grep -Fq 'Link openclaw-multi-session-plugins to extensions (macOS)' "$patcher"; then
fail "macOS plugin patch still installs the extension as a temporary symlink"
fi
}
test_local_bootstrap_prefers_local_macos_patcher() {
local checkout workdir
checkout="$(mktemp -d)"
workdir="$(mktemp -d)"
mkdir -p "$checkout/scripts"
printf 'local-patcher-marker\n' > "$checkout/scripts/patch-macos-playbooks.py"
(
XWORKSPACE_CONSOLE_DIR="$checkout"
cd "$workdir"
# shellcheck disable=SC2329
python3() {
grep -q '^local-patcher-marker$' "$1" ||
fail "patch function did not execute the checked-in patcher"
}
patch_playbooks_for_macos
)
rm -rf "$checkout" "$workdir"
grep -Fq '"${raw_url}?rev=$(date +%s)"' "$BOOTSTRAP" ||
fail "remote macOS patcher download is not cache-busted"
}
test_root_does_not_require_sudo
printf 'ok - root execution does not require sudo\n'
test_non_root_uses_sudo
printf 'ok - non-root execution uses sudo\n'
set +e
privilege_error="$(test_non_root_without_sudo_fails_cleanly 2>&1)"
privilege_status=$?
set -e
[ "$privilege_status" -ne 0 ] || fail "non-root execution without sudo unexpectedly succeeded"
printf '%s' "$privilege_error" | grep -q "Root privileges are required" || fail "missing sudo error was not actionable"
printf 'ok - missing sudo reports a privilege error\n'
test_forced_offline_mode_does_not_refresh_repositories
printf 'ok - forced offline mode does not refresh packaged repositories\n'
test_auto_offline_mode_refreshes_packaged_repositories
printf 'ok - auto offline mode refreshes packaged repositories\n'
test_offline_refresh_installs_git_on_fresh_debian
printf 'ok - fresh Debian installs Git before repository refresh\n'
test_ubuntu_2604_offline_package_requires_npm
printf 'ok - Ubuntu 26.04 offline package requires npm\n'
test_dynamic_parallel_limit_avoids_awk_reserved_names
printf 'ok - dynamic parallel limit is compatible with modern gawk\n'
test_offline_installer_gets_scoped_git_config
printf 'ok - offline installer receives scoped Git ownership compatibility\n'
test_linux_root_defaults_to_ubuntu_home
printf 'ok - Linux root deployment defaults to ubuntu home\n'
test_linux_non_root_uses_current_user_home
printf 'ok - Linux non-root deployment uses passwd home\n'
test_linux_identity_vars_can_be_overridden
printf 'ok - Linux deployment identity can be overridden\n'
test_provider_api_keys_use_secret_logging
printf 'ok - provider API keys use masked secret logging\n'
test_macos_plugin_patch_uses_stable_directory
printf 'ok - macOS plugin patch uses stable extension storage\n'
test_local_bootstrap_prefers_local_macos_patcher
printf 'ok - local bootstrap prefers the checked-in macOS patcher\n'