#!/bin/bash set -euo pipefail source "$(dirname "${BASH_SOURCE[0]}")/base-test.sh" require_command mise test_dir=$(mktemp -d) trap 'rm -rf "$test_dir"' EXIT migration="$ROOT/migrations/1789095456.sh" run_migration() { local test_home="$1" env -i \ HOME="$test_home" \ XDG_CACHE_HOME="$test_home/.cache" \ XDG_CONFIG_HOME="$test_home/.config" \ XDG_DATA_HOME="$test_home/.local/share" \ XDG_STATE_HOME="$test_home/.local/state" \ MISE_PARANOID="${OMARCHY_TEST_MISE_PARANOID:-false}" \ PATH=/usr/bin \ bash -euo pipefail "$migration" } run_mise() { local test_home="$1" shift env -i \ HOME="$test_home" \ XDG_CACHE_HOME="$test_home/.cache" \ XDG_CONFIG_HOME="$test_home/.config" \ XDG_DATA_HOME="$test_home/.local/share" \ XDG_STATE_HOME="$test_home/.local/state" \ MISE_PARANOID="${OMARCHY_TEST_MISE_PARANOID:-false}" \ PATH=/usr/bin \ mise "$@" } mise_environment() { local test_home="$1" local project="$2" ( cd "$project" run_mise "$test_home" env -s bash ) } mise_path_active() { local test_home="$1" local project="$2" local output if ! output=$(mise_environment "$test_home" "$project" 2>/dev/null); then return 1 fi grep -F "$project/bin" <<<"$output" >/dev/null } assert_unsafe_variant_removed() { local variant="$1" local assignment="$2" local variant_home="$test_dir/$variant-home" local variant_config="$variant_home/Work/.mise.toml" local variant_project="$variant_home/Work/tries/untrusted-repository" mkdir -p "$variant_project/bin" printf '[env]\n%s\n' "$assignment" >"$variant_config" run_mise "$variant_home" trust "$variant_config" >/dev/null mise_path_active "$variant_home" "$variant_project" || fail "$variant legacy config prepends the repository bin directory" run_migration "$variant_home" >/dev/null if mise_path_active "$variant_home" "$variant_project"; then fail "$variant repository bin directory remains in PATH after migration" fi } install_home="$test_dir/install-home" install_log="$test_dir/install-mise.log" mkdir -p "$install_home" "$test_dir/bin" cat >"$test_dir/bin/mise" <<'SH' #!/bin/bash printf '%s\n' "$*" >>"$MISE_TEST_LOG" SH chmod +x "$test_dir/bin/mise" env \ HOME="$install_home" \ MISE_TEST_LOG="$install_log" \ OMARCHY_SETUP_CONTEXT=runtime \ PATH="$test_dir/bin:/usr/bin" \ bash -euo pipefail -c 'source "$1"' bash "$ROOT/install/user/mise-work.sh" [[ -d $install_home/Work/tries ]] || fail "installer creates the work and tries directories" [[ ! -e $install_home/Work/.mise.toml ]] || fail "installer does not create a trusted Work Mise config" [[ $(<"$install_log") == "use -g node@latest" ]] || fail "installer only invokes Mise for the global Node setup" pass "new installs do not add project bin directories to PATH" # An offline install unpacks the Node tarball the ISO bundles, and the ISO # bundles the one for its own architecture. Both are on offer here; each # machine must take its own. uname and the bundle directory are stand-ins. bundle="$test_dir/bundle" mkdir -p "$bundle" "$test_dir/node-src" for platform in linux-x64 linux-arm64; do mkdir -p "$test_dir/node-src/node-v24.1.0-$platform/bin" echo "$platform" >"$test_dir/node-src/node-v24.1.0-$platform/bin/node" tar -czf "$bundle/node-v24.1.0-$platform.tar.gz" -C "$test_dir/node-src" "node-v24.1.0-$platform" done cat >"$test_dir/bin/uname" <<'SH' #!/bin/bash [[ $* == -m ]] && { echo "$UNAME_TEST_MACHINE"; exit; } exec /usr/bin/uname "$@" SH cat >"$test_dir/bin/find" <<'SH' #!/bin/bash [[ $1 == /opt/packages ]] && { shift; exec /usr/bin/find "$NODE_TEST_BUNDLE" "$@"; } exec /usr/bin/find "$@" SH chmod +x "$test_dir/bin/uname" "$test_dir/bin/find" for case in "x86_64 linux-x64" "aarch64 linux-arm64"; do read -r machine platform <<<"$case" arch_home="$test_dir/$machine-home" arch_log="$test_dir/$machine-mise.log" mkdir -p "$arch_home" env \ HOME="$arch_home" \ MISE_TEST_LOG="$arch_log" \ NODE_TEST_BUNDLE="$bundle" \ UNAME_TEST_MACHINE="$machine" \ OMARCHY_SETUP_CONTEXT=iso-chroot \ PATH="$test_dir/bin:/usr/bin" \ bash -euo pipefail -c 'source "$1"' bash "$ROOT/install/user/mise-work.sh" installed="$arch_home/.local/share/mise/installs/node/24.1.0/bin/node" [[ -f $installed && $(<"$installed") == "$platform" ]] || fail "$machine unpacks the $platform Node tarball" [[ $(head -n1 "$arch_log") == "use -g node@24.1.0" ]] || fail "$machine hands Mise the bundled Node version" done [[ ! -e $bundle/node-v24.1.0-linux-x64.tar.gz ]] || rm "$bundle/node-v24.1.0-linux-x64.tar.gz" if env HOME="$test_dir/missing-home" MISE_TEST_LOG="$test_dir/missing.log" NODE_TEST_BUNDLE="$bundle" \ UNAME_TEST_MACHINE=x86_64 OMARCHY_SETUP_CONTEXT=iso-chroot PATH="$test_dir/bin:/usr/bin" \ bash -euo pipefail -c 'source "$1"' bash "$ROOT/install/user/mise-work.sh" 2>/dev/null; then fail "an x86_64 install accepts another architecture's Node tarball" fi pass "offline installs unpack the Node tarball for the machine's architecture" stock_home="$test_dir/stock-home" stock_config="$stock_home/Work/.mise.toml" stock_project="$stock_home/Work/tries/untrusted-repository" mkdir -p "$stock_project/bin" cat >"$stock_config" <<'TOML' [env] _.path = "{{ cwd }}/bin" TOML run_mise "$stock_home" trust "$stock_config" >/dev/null mise_path_active "$stock_home" "$stock_project" || fail "legacy config prepends the repository bin directory" run_migration "$stock_home" >/dev/null [[ ! -e $stock_config ]] || fail "migration removes the stock Work Mise config" cat >"$stock_config" <<'TOML' [env] _.path = "{{ cwd }}/bin" TOML if mise_path_active "$stock_home" "$stock_project"; then fail "recreated Work config remains trusted after migration" fi run_migration "$stock_home" >/dev/null [[ ! -e $stock_config ]] || fail "stock migration is idempotent" pass "migration removes the repository bin directory and revokes the Work trust root" assert_unsafe_variant_removed inline-comment '_.path = "{{ cwd }}/bin" # Omarchy default' assert_unsafe_variant_removed single-quoted "_.path = '{{ cwd }}/bin'" pass "migration removes annotated and single-quoted project bin paths" custom_home="$test_dir/custom-home" custom_config="$custom_home/Work/.mise.toml" mkdir -p "$(dirname "$custom_config")" cat >"$custom_config" <<'TOML' [env] KEEP = "yes" _.path = "{{ cwd }}/bin" # _.path = "{{ cwd }}/bin" [tools] ruby = "latest" [other] _.path = "{{ cwd }}/bin" TOML cp "$custom_config" "$test_dir/custom-original" cat >"$test_dir/custom-expected" <<'TOML' [env] KEEP = "yes" # _.path = "{{ cwd }}/bin" [tools] ruby = "latest" [other] _.path = "{{ cwd }}/bin" TOML chmod 600 "$custom_config" run_mise "$custom_home" trust "$custom_config" >/dev/null custom_output=$(run_migration "$custom_home") cmp -s "$test_dir/custom-expected" "$custom_config" || fail "migration preserves unrelated custom Mise settings" [[ $(stat -c %a "$custom_config") == "600" ]] || fail "migration preserves custom config permissions" grep -F "mise trust $custom_config" <<<"$custom_output" >/dev/null || fail "migration explains how to review and re-trust a custom config" custom_backups=("$custom_config".bak.*) [[ -f ${custom_backups[0]} ]] || fail "migration backs up a customized Mise config" (( ${#custom_backups[@]} == 1 )) || fail "migration creates one custom config backup" cmp -s "$test_dir/custom-original" "${custom_backups[0]}" || fail "custom config backup preserves the original" run_migration "$custom_home" >/dev/null custom_backups=("$custom_config".bak.*) (( ${#custom_backups[@]} == 1 )) || fail "custom migration does not create another backup on rerun" cmp -s "$test_dir/custom-expected" "$custom_config" || fail "custom migration is idempotent" pass "custom Mise settings, permissions, and original backup survive the repair" unrelated_home="$test_dir/unrelated-home" unrelated_config="$unrelated_home/Work/.mise.toml" unrelated_project="$unrelated_home/Work/tries/untrusted-repository" mkdir -p "$unrelated_project/bin" printf '[env]\nKEEP = "yes"\n' >"$unrelated_config" cp "$unrelated_config" "$test_dir/unrelated-original" run_mise "$unrelated_home" trust "$unrelated_config" >/dev/null run_migration "$unrelated_home" >/dev/null cmp -s "$test_dir/unrelated-original" "$unrelated_config" || fail "unrelated Mise config remains unchanged" unrelated_backups=("$unrelated_config".bak.*) [[ ! -e ${unrelated_backups[0]} ]] || fail "unchanged Mise config is not backed up" printf '[env]\n_.path = "{{ cwd }}/bin"\n' >"$unrelated_config" if mise_path_active "$unrelated_home" "$unrelated_project"; then fail "safe Work config retains its old trust grant" fi absent_home="$test_dir/absent-home" absent_config="$absent_home/Work/.mise.toml" absent_project="$absent_home/Work/tries/untrusted-repository" mkdir -p "$(dirname "$absent_config")" printf '[env]\n_.path = "{{ cwd }}/bin"\n' >"$absent_config" run_mise "$absent_home" trust "$absent_config" >/dev/null rm "$absent_config" rmdir "$absent_home/Work" run_migration "$absent_home" >/dev/null [[ ! -e $absent_home/Work ]] || fail "migration does not retain a temporary Work directory" mkdir -p "$absent_project/bin" printf '[env]\n_.path = "{{ cwd }}/bin"\n' >"$absent_config" if mise_path_active "$absent_home" "$absent_project"; then fail "deleted Work directory retains its stale trust grant" fi pass "migration leaves unrelated configs alone and revokes dangling Work trust" paranoid_home="$test_dir/paranoid-home" paranoid_work="$paranoid_home/Work" paranoid_config="$paranoid_work/.mise.toml" paranoid_project="$paranoid_work/tries/untrusted-repository" mkdir -p "$paranoid_project/bin" printf '[env]\n_.path = "{{ cwd }}/bin"\n' >"$paranoid_config" OMARCHY_TEST_MISE_PARANOID=true run_mise "$paranoid_home" trust "$paranoid_config" >/dev/null OMARCHY_TEST_MISE_PARANOID=true mise_path_active "$paranoid_home" "$paranoid_project" || fail "paranoid legacy config prepends the repository bin directory" rm -r "$paranoid_work" OMARCHY_TEST_MISE_PARANOID=true run_migration "$paranoid_home" >/dev/null [[ ! -e $paranoid_work ]] || fail "paranoid migration removes its temporary Work directory" mkdir -p "$paranoid_project/bin" printf '[env]\n_.path = "{{ cwd }}/bin"\n' >"$paranoid_config" if OMARCHY_TEST_MISE_PARANOID=true mise_path_active "$paranoid_home" "$paranoid_project"; then fail "paranoid migration retains content-bound trust for the deleted legacy config" fi pass "migration revokes stale content-bound trust in Mise paranoid mode" ignored_home="$test_dir/ignored-home" ignored_config="$ignored_home/Work/.mise.toml" ignored_project="$ignored_home/Work/tries/untrusted-repository" mkdir -p "$ignored_project/bin" cat >"$ignored_config" <<'TOML' [env] _.path = "{{ cwd }}/bin" KEEP = "yes" TOML run_mise "$ignored_home" trust "$ignored_config" >/dev/null run_mise "$ignored_home" trust --ignore "$ignored_config" >/dev/null ignored_output=$(run_migration "$ignored_home") grep -F '{{ cwd }}/bin' "$ignored_config" >/dev/null && fail "ignored config retains the unsafe path" grep -Fx 'KEEP = "yes"' "$ignored_config" >/dev/null || fail "ignored config keeps unrelated settings" ignored_entries=("$ignored_home/.local/state/mise/ignored-configs/"*) [[ -L ${ignored_entries[0]} ]] || fail "migration preserves the explicit Mise ignore marker" (( ${#ignored_entries[@]} == 1 )) || fail "migration preserves exactly one Mise ignore marker" ignored_target=$(readlink "${ignored_entries[0]}") [[ $ignored_target == $ignored_home/Work || $ignored_target == $ignored_config ]] || fail "preserved Mise ignore marker still targets the Work config" grep -F "remains ignored by Mise" <<<"$ignored_output" >/dev/null || fail "migration reports that the custom config remains ignored" if mise_path_active "$ignored_home" "$ignored_project"; then fail "ignored config becomes active after migration" fi pass "migration preserves an explicit decision to ignore the Work config" symlink_home="$test_dir/symlink-home" symlink_config="$symlink_home/Work/.mise.toml" symlink_target="$test_dir/dotfiles-mise.toml" mkdir -p "$(dirname "$symlink_config")" cat >"$symlink_target" <<'TOML' [env] _.path = "{{ cwd }}/bin" KEEP = "yes" TOML ln -s "$symlink_target" "$symlink_config" run_migration "$symlink_home" >/dev/null [[ -L $symlink_config ]] || fail "migration preserves a dotfile symlink" grep -F '{{ cwd }}/bin' "$symlink_target" >/dev/null && fail "symlink target retains the unsafe path" grep -Fx 'KEEP = "yes"' "$symlink_target" >/dev/null || fail "symlink target keeps unrelated settings" pass "custom dotfile symlinks survive the repair"