#!/usr/bin/env bash set -euo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" fail() { echo "FAIL: $*" >&2 exit 1 } assert_file_contains() { local file="$1" local pattern="$2" grep -Fq "$pattern" "$file" || fail "Expected '$pattern' in $file" } assert_not_exists() { local path="$1" [ ! -e "$path" ] || fail "Path should not exist: $path" } setup_fixture() { local fixture_root repo_root bin_dir fixture_root="$(mktemp -d /tmp/dotfiles-install-test.XXXXXX)" repo_root="$fixture_root/repo" bin_dir="$fixture_root/bin" mkdir -p "$repo_root" "$bin_dir" cp -a \ "$REPO_ROOT/install.sh" \ "$REPO_ROOT/bash" \ "$REPO_ROOT/tmux" \ "$REPO_ROOT/vim" \ "$REPO_ROOT/zed" \ "$REPO_ROOT/vscode" \ "$REPO_ROOT/agents" \ "$REPO_ROOT/opencode" \ "$REPO_ROOT/codex" \ "$REPO_ROOT/gemini" \ "$REPO_ROOT/claude" \ "$REPO_ROOT/scripts" \ "$repo_root/" cat > "$bin_dir/fake-git" <<'EOF' #!/usr/bin/env bash set -euo pipefail if [ "${1:-}" = "-C" ]; then shift 2 fi case "${1:-}" in rev-parse) exit 0 ;; show) printf '%s' "${FAKE_GIT_SHOW_CONTENT:-}" ;; *) echo "unexpected git invocation: $*" >&2 exit 1 ;; esac EOF cat > "$bin_dir/fake-sudo" <<'EOF' #!/usr/bin/env bash set -euo pipefail exec "$@" EOF chmod +x "$bin_dir/fake-git" "$bin_dir/fake-sudo" printf '%s\n' "$fixture_root" } run_install() { local repo_root="$1" local home_dir="$2" shift 2 HOME="$home_dir" \ GIT_BIN="$repo_root/../bin/fake-git" \ SUDO_BIN="$repo_root/../bin/fake-sudo" \ "$@" \ "$repo_root/install.sh" } test_fresh_install_uses_home_target() { local fixture repo_root home_dir fixture="$(setup_fixture)" repo_root="$fixture/repo" home_dir="$fixture/home" mkdir -p "$home_dir" run_install "$repo_root" "$home_dir" env WSL_DISTRO_NAME= INSTALL_INTERACTIVE_MODE=never >/tmp/test-install.out [ -L "$home_dir/.bash_custom" ] || fail "Expected .bash_custom symlink in HOME" [ -L "$home_dir/.claude/skills" ] || fail "Expected ~/.claude/skills symlink" assert_file_contains "$home_dir/.bashrc" "[ -f ~/.bash_custom ] && source ~/.bash_custom" assert_not_exists "$fixture/.bash_custom" } test_dirty_repo_settings_copy_uses_committed_version() { local fixture repo_root home_dir fixture="$(setup_fixture)" repo_root="$fixture/repo" home_dir="$fixture/home" mkdir -p "$home_dir" chmod u+w "$repo_root/claude/.claude/settings.json" cat > "$repo_root/claude/.claude/settings.json" <<'EOF' {"source":"working-tree"} EOF FAKE_GIT_SHOW_CONTENT='{"source":"head"}' \ run_install "$repo_root" "$home_dir" env WSL_DISTRO_NAME= INSTALL_INTERACTIVE_MODE=never >/tmp/test-install.out assert_file_contains "$home_dir/.claude/settings.json" '"source":"head"' assert_file_contains "$repo_root/claude/.claude/settings.json" '"source":"working-tree"' } test_rerun_does_not_duplicate_bashrc_line() { local fixture repo_root home_dir count fixture="$(setup_fixture)" repo_root="$fixture/repo" home_dir="$fixture/home" mkdir -p "$home_dir" run_install "$repo_root" "$home_dir" env WSL_DISTRO_NAME= INSTALL_INTERACTIVE_MODE=never >/tmp/test-install.out run_install "$repo_root" "$home_dir" env WSL_DISTRO_NAME= INSTALL_INTERACTIVE_MODE=never >/tmp/test-install.out count="$(grep -Fxc '[ -f ~/.bash_custom ] && source ~/.bash_custom' "$home_dir/.bashrc")" [ "$count" -eq 1 ] || fail "Expected bash_custom line once, got $count" } test_repairs_nested_skills_directory_bug() { local fixture repo_root home_dir fixture="$(setup_fixture)" repo_root="$fixture/repo" home_dir="$fixture/home" mkdir -p "$home_dir/.claude/skills" ln -s "$repo_root/agents/.agents/skills" "$home_dir/.claude/skills/skills" run_install "$repo_root" "$home_dir" env WSL_DISTRO_NAME= INSTALL_INTERACTIVE_MODE=never >/tmp/test-install.out [ -L "$home_dir/.claude/skills" ] || fail "Expected repaired ~/.claude/skills symlink" assert_not_exists "$home_dir/.claude/skills/skills" } test_preserves_nonempty_skills_directory() { local fixture repo_root home_dir fixture="$(setup_fixture)" repo_root="$fixture/repo" home_dir="$fixture/home" mkdir -p "$home_dir/.claude/skills" echo local > "$home_dir/.claude/skills/local.txt" run_install "$repo_root" "$home_dir" env WSL_DISTRO_NAME= INSTALL_INTERACTIVE_MODE=never >/tmp/test-install.out [ -d "$home_dir/.claude/skills" ] || fail "Expected ~/.claude/skills directory to be preserved" [ -f "$home_dir/.claude/skills/local.txt" ] || fail "Expected local skill data to be preserved" } test_broken_claude_symlink_does_not_fail() { local fixture repo_root home_dir fixture="$(setup_fixture)" repo_root="$fixture/repo" home_dir="$fixture/home" mkdir -p "$home_dir" ln -s "$fixture/missing-claude-dir" "$home_dir/.claude" run_install "$repo_root" "$home_dir" env WSL_DISTRO_NAME= INSTALL_INTERACTIVE_MODE=never >/tmp/test-install.out [ -d "$home_dir/.claude" ] || fail "Expected ~/.claude directory after migration" [ ! -L "$home_dir/.claude" ] || fail "Expected ~/.claude to stop being a symlink" } test_wsl_noninteractive_skips_file_changes() { local fixture repo_root home_dir wsl_conf output fixture="$(setup_fixture)" repo_root="$fixture/repo" home_dir="$fixture/home" wsl_conf="$fixture/wsl.conf" mkdir -p "$home_dir" printf '[automount]\n' > "$wsl_conf" output="$( run_install "$repo_root" "$home_dir" \ env WSL_DISTRO_NAME=Ubuntu INSTALL_INTERACTIVE_MODE=never WSL_CONF_PATH="$wsl_conf" )" assert_file_contains "$wsl_conf" "[automount]" if grep -Fq '[boot]' "$wsl_conf"; then fail "WSL config should not be modified in non-interactive mode" fi printf '%s' "$output" | grep -Fq 'Добавьте вручную:' || fail "Expected manual WSL hint" } test_wsl_interactive_adds_boot_command() { local fixture repo_root home_dir wsl_conf command_line fixture="$(setup_fixture)" repo_root="$fixture/repo" home_dir="$fixture/home" wsl_conf="$fixture/wsl.conf" mkdir -p "$home_dir" printf '[automount]\n' > "$wsl_conf" command_line="command = \"$repo_root/scripts/setup-hproxy.sh\"" printf 'y\n' | run_install "$repo_root" "$home_dir" \ env WSL_DISTRO_NAME=Ubuntu INSTALL_INTERACTIVE_MODE=always WSL_CONF_PATH="$wsl_conf" >/tmp/test-install.out assert_file_contains "$wsl_conf" "[boot]" assert_file_contains "$wsl_conf" "$command_line" } test_wsl_existing_boot_command_is_preserved() { local fixture repo_root home_dir wsl_conf output fixture="$(setup_fixture)" repo_root="$fixture/repo" home_dir="$fixture/home" wsl_conf="$fixture/wsl.conf" mkdir -p "$home_dir" cat > "$wsl_conf" <<'EOF' [boot] command = "/usr/local/bin/existing.sh" EOF output="$( printf 'y\n' | run_install "$repo_root" "$home_dir" \ env WSL_DISTRO_NAME=Ubuntu INSTALL_INTERACTIVE_MODE=always WSL_CONF_PATH="$wsl_conf" )" assert_file_contains "$wsl_conf" 'command = "/usr/local/bin/existing.sh"' if grep -Fq 'setup-hproxy.sh' "$wsl_conf"; then fail "Expected existing boot command to stay untouched" fi printf '%s' "$output" | grep -Fq 'уже содержит [boot] command' || fail "Expected warning about existing boot command" } main() { command -v stow >/dev/null 2>&1 || fail "stow is required for tests" test_fresh_install_uses_home_target test_dirty_repo_settings_copy_uses_committed_version test_rerun_does_not_duplicate_bashrc_line test_repairs_nested_skills_directory_bug test_preserves_nonempty_skills_directory test_broken_claude_symlink_does_not_fail test_wsl_noninteractive_skips_file_changes test_wsl_interactive_adds_boot_command test_wsl_existing_boot_command_is_preserved echo "All install tests passed" } main "$@"