diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4f1530f..567d338 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,6 +25,7 @@ jobs: env: MOLE_PERF_BYTES_TO_HUMAN_LIMIT_MS: "6000" MOLE_PERF_GET_FILE_SIZE_LIMIT_MS: "3000" + BATS_FORMATTER: pretty run: ./scripts/test.sh compatibility: diff --git a/scripts/test.sh b/scripts/test.sh index d6eea56..4f0ce40 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -49,20 +49,11 @@ if command -v bats > /dev/null 2>&1 && [ -d "tests" ]; then if [[ $# -eq 0 ]]; then set -- tests fi - if [[ -t 1 ]]; then - if bats -p "$@" | sed -e 's/^ok /OK /' -e 's/^not ok /FAIL /'; then - printf "${GREEN}${ICON_SUCCESS} Unit tests passed${NC}\n" - else - printf "${RED}${ICON_ERROR} Unit tests failed${NC}\n" - ((FAILED++)) - fi + if TERM="${TERM:-xterm-256color}" bats --formatter "${BATS_FORMATTER:-pretty}" "$@"; then + printf "${GREEN}${ICON_SUCCESS} Unit tests passed${NC}\n" else - if TERM="${TERM:-xterm-256color}" bats --tap "$@" | sed -e 's/^ok /OK /' -e 's/^not ok /FAIL /'; then - printf "${GREEN}${ICON_SUCCESS} Unit tests passed${NC}\n" - else - printf "${RED}${ICON_ERROR} Unit tests failed${NC}\n" - ((FAILED++)) - fi + printf "${RED}${ICON_ERROR} Unit tests failed${NC}\n" + ((FAILED++)) fi else printf "${YELLOW}${ICON_WARNING} bats not installed or no tests found, skipping${NC}\n" diff --git a/tests/app_protection.bats b/tests/app_protection.bats deleted file mode 100644 index b475934..0000000 --- a/tests/app_protection.bats +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env bats - -setup_file() { - PROJECT_ROOT="$(cd "${BATS_TEST_DIRNAME}/.." && pwd)" - export PROJECT_ROOT -} - -@test "is_critical_system_component matches known system services" { - run bash --noprofile --norc <<'EOF' -set -euo pipefail -source "$PROJECT_ROOT/lib/core/app_protection.sh" -is_critical_system_component "backgroundtaskmanagement" && echo "yes" -is_critical_system_component "SystemSettings" && echo "yes" -EOF - [ "$status" -eq 0 ] - [[ "${lines[0]}" == "yes" ]] - [[ "${lines[1]}" == "yes" ]] -} - -@test "is_critical_system_component ignores non-system names" { - run bash --noprofile --norc <<'EOF' -set -euo pipefail -source "$PROJECT_ROOT/lib/core/app_protection.sh" -if is_critical_system_component "myapp"; then - echo "bad" -else - echo "ok" -fi -EOF - [ "$status" -eq 0 ] - [[ "$output" == "ok" ]] -} diff --git a/tests/apps_module.bats b/tests/apps_module.bats index c898482..9955117 100644 --- a/tests/apps_module.bats +++ b/tests/apps_module.bats @@ -88,3 +88,29 @@ EOF [ "$status" -eq 0 ] [[ "$output" == *"Skipped: No permission"* ]] } + +@test "is_critical_system_component matches known system services" { + run bash --noprofile --norc <<'EOF' +set -euo pipefail +source "$PROJECT_ROOT/lib/core/app_protection.sh" +is_critical_system_component "backgroundtaskmanagement" && echo "yes" +is_critical_system_component "SystemSettings" && echo "yes" +EOF + [ "$status" -eq 0 ] + [[ "${lines[0]}" == "yes" ]] + [[ "${lines[1]}" == "yes" ]] +} + +@test "is_critical_system_component ignores non-system names" { + run bash --noprofile --norc <<'EOF' +set -euo pipefail +source "$PROJECT_ROOT/lib/core/app_protection.sh" +if is_critical_system_component "myapp"; then + echo "bad" +else + echo "ok" +fi +EOF + [ "$status" -eq 0 ] + [[ "$output" == "ok" ]] +} diff --git a/tests/cli.bats b/tests/cli.bats index 5cc5896..5ed897b 100644 --- a/tests/cli.bats +++ b/tests/cli.bats @@ -20,6 +20,30 @@ teardown_file() { fi } +create_fake_utils() { + local dir="$1" + mkdir -p "$dir" + + cat > "$dir/sudo" <<'SCRIPT' +#!/usr/bin/env bash +if [[ "$1" == "-n" || "$1" == "-v" ]]; then + exit 0 +fi +exec "$@" +SCRIPT + chmod +x "$dir/sudo" + + cat > "$dir/bioutil" <<'SCRIPT' +#!/usr/bin/env bash +if [[ "$1" == "-r" ]]; then + echo "Touch ID: 1" + exit 0 +fi +exit 0 +SCRIPT + chmod +x "$dir/bioutil" +} + setup() { rm -rf "$HOME/.config" mkdir -p "$HOME" @@ -65,3 +89,90 @@ setup() { skip "analyze-go binary not built" fi } + +@test "mo clean --debug creates debug log file" { + mkdir -p "$HOME/.config/mole" + run env HOME="$HOME" TERM="xterm-256color" MOLE_TEST_MODE=1 MO_DEBUG=1 "$PROJECT_ROOT/mole" clean --dry-run + [ "$status" -eq 0 ] + MOLE_OUTPUT="$output" + + DEBUG_LOG="$HOME/.config/mole/mole_debug_session.log" + [ -f "$DEBUG_LOG" ] + + run grep "Mole Debug Session" "$DEBUG_LOG" + [ "$status" -eq 0 ] + + [[ "$MOLE_OUTPUT" =~ "Debug session log saved to" ]] +} + +@test "mo clean without debug does not show debug log path" { + mkdir -p "$HOME/.config/mole" + run env HOME="$HOME" TERM="xterm-256color" MOLE_TEST_MODE=1 MO_DEBUG=0 "$PROJECT_ROOT/mole" clean --dry-run + [ "$status" -eq 0 ] + + [[ "$output" != *"Debug session log saved to"* ]] +} + +@test "mo clean --debug logs system info" { + mkdir -p "$HOME/.config/mole" + run env HOME="$HOME" TERM="xterm-256color" MOLE_TEST_MODE=1 MO_DEBUG=1 "$PROJECT_ROOT/mole" clean --dry-run + [ "$status" -eq 0 ] + + DEBUG_LOG="$HOME/.config/mole/mole_debug_session.log" + + run grep "User:" "$DEBUG_LOG" + [ "$status" -eq 0 ] + + run grep "Architecture:" "$DEBUG_LOG" + [ "$status" -eq 0 ] +} + +@test "touchid status reflects pam file contents" { + pam_file="$HOME/pam_test" + cat > "$pam_file" <<'EOF' +auth sufficient pam_opendirectory.so +EOF + + run env MOLE_PAM_SUDO_FILE="$pam_file" "$PROJECT_ROOT/bin/touchid.sh" status + [ "$status" -eq 0 ] + [[ "$output" == *"not configured"* ]] + + cat > "$pam_file" <<'EOF' +auth sufficient pam_tid.so +EOF + + run env MOLE_PAM_SUDO_FILE="$pam_file" "$PROJECT_ROOT/bin/touchid.sh" status + [ "$status" -eq 0 ] + [[ "$output" == *"enabled"* ]] +} + +@test "enable_touchid inserts pam_tid line in pam file" { + pam_file="$HOME/pam_enable" + cat > "$pam_file" <<'EOF' +auth sufficient pam_opendirectory.so +EOF + + fake_bin="$HOME/fake-bin" + create_fake_utils "$fake_bin" + + run env PATH="$fake_bin:$PATH" MOLE_PAM_SUDO_FILE="$pam_file" "$PROJECT_ROOT/bin/touchid.sh" enable + [ "$status" -eq 0 ] + grep -q "pam_tid.so" "$pam_file" + [[ -f "${pam_file}.mole-backup" ]] +} + +@test "disable_touchid removes pam_tid line" { + pam_file="$HOME/pam_disable" + cat > "$pam_file" <<'EOF' +auth sufficient pam_tid.so +auth sufficient pam_opendirectory.so +EOF + + fake_bin="$HOME/fake-bin-disable" + create_fake_utils "$fake_bin" + + run env PATH="$fake_bin:$PATH" MOLE_PAM_SUDO_FILE="$pam_file" "$PROJECT_ROOT/bin/touchid.sh" disable + [ "$status" -eq 0 ] + run grep "pam_tid.so" "$pam_file" + [ "$status" -ne 0 ] +} diff --git a/tests/common.bats b/tests/common.bats index 834e425..105a4ca 100644 --- a/tests/common.bats +++ b/tests/common.bats @@ -174,3 +174,30 @@ EOF ) [[ "$result" == *"done"* ]] } + +@test "read_key maps j/k/h/l to navigation" { + run bash -c "export MOLE_BASE_LOADED=1; source '$PROJECT_ROOT/lib/core/ui.sh'; echo -n 'j' | read_key" + [ "$output" = "DOWN" ] + + run bash -c "export MOLE_BASE_LOADED=1; source '$PROJECT_ROOT/lib/core/ui.sh'; echo -n 'k' | read_key" + [ "$output" = "UP" ] + + run bash -c "export MOLE_BASE_LOADED=1; source '$PROJECT_ROOT/lib/core/ui.sh'; echo -n 'h' | read_key" + [ "$output" = "LEFT" ] + + run bash -c "export MOLE_BASE_LOADED=1; source '$PROJECT_ROOT/lib/core/ui.sh'; echo -n 'l' | read_key" + [ "$output" = "RIGHT" ] +} + +@test "read_key maps uppercase J/K/H/L to navigation" { + run bash -c "export MOLE_BASE_LOADED=1; source '$PROJECT_ROOT/lib/core/ui.sh'; echo -n 'J' | read_key" + [ "$output" = "DOWN" ] + + run bash -c "export MOLE_BASE_LOADED=1; source '$PROJECT_ROOT/lib/core/ui.sh'; echo -n 'K' | read_key" + [ "$output" = "UP" ] +} + +@test "read_key respects MOLE_READ_KEY_FORCE_CHAR" { + run bash -c "export MOLE_BASE_LOADED=1; export MOLE_READ_KEY_FORCE_CHAR=1; source '$PROJECT_ROOT/lib/core/ui.sh'; echo -n 'j' | read_key" + [ "$output" = "CHAR:j" ] +} diff --git a/tests/debug_logging.bats b/tests/debug_logging.bats deleted file mode 100644 index 1409d4a..0000000 --- a/tests/debug_logging.bats +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env bats - -setup_file() { - PROJECT_ROOT="$(cd "${BATS_TEST_DIRNAME}/.." && pwd)" - export PROJECT_ROOT - - ORIGINAL_HOME="${HOME:-}" - export ORIGINAL_HOME - - HOME="$(mktemp -d "${BATS_TEST_DIRNAME}/tmp-debug-logging.XXXXXX")" - export HOME - - mkdir -p "$HOME" -} - -teardown_file() { - rm -rf "$HOME" - if [[ -n "${ORIGINAL_HOME:-}" ]]; then - export HOME="$ORIGINAL_HOME" - fi -} - -setup() { - export TERM="xterm-256color" - rm -rf "${HOME:?}"/* - mkdir -p "$HOME/.config/mole" -} - -@test "mo clean --debug creates debug log file" { - run env HOME="$HOME" MOLE_TEST_MODE=1 MO_DEBUG=1 "$PROJECT_ROOT/mole" clean --dry-run - [ "$status" -eq 0 ] - MOLE_OUTPUT="$output" - - DEBUG_LOG="$HOME/.config/mole/mole_debug_session.log" - [ -f "$DEBUG_LOG" ] - - run grep "Mole Debug Session" "$DEBUG_LOG" - [ "$status" -eq 0 ] - - [[ "$MOLE_OUTPUT" =~ "Debug session log saved to" ]] -} - -@test "mo clean without debug does not show debug log path" { - run env HOME="$HOME" MOLE_TEST_MODE=1 MO_DEBUG=0 "$PROJECT_ROOT/mole" clean --dry-run - [ "$status" -eq 0 ] - - [[ "$output" != *"Debug session log saved to"* ]] -} - -@test "mo clean --debug logs system info" { - run env HOME="$HOME" MOLE_TEST_MODE=1 MO_DEBUG=1 "$PROJECT_ROOT/mole" clean --dry-run - [ "$status" -eq 0 ] - - DEBUG_LOG="$HOME/.config/mole/mole_debug_session.log" - - run grep "User:" "$DEBUG_LOG" - [ "$status" -eq 0 ] - - run grep "Architecture:" "$DEBUG_LOG" - [ "$status" -eq 0 ] -} diff --git a/tests/optimize_core.bats b/tests/optimize_core.bats index db872f0..4f38f75 100644 --- a/tests/optimize_core.bats +++ b/tests/optimize_core.bats @@ -98,3 +98,28 @@ EOF [ "$status" -eq 0 ] [[ "$output" == *"LaunchServices repaired"* ]] } + +@test "opt_msg uses dry-run output" { + run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" MOLE_DRY_RUN=1 bash --noprofile --norc <<'EOF' +set -euo pipefail +source "$PROJECT_ROOT/lib/core/common.sh" +source "$PROJECT_ROOT/lib/optimize/tasks.sh" +opt_msg "dry" +EOF + + [ "$status" -eq 0 ] + [[ "$output" == *"dry"* ]] +} + +@test "run_launchctl_unload skips in dry-run" { + run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" MOLE_DRY_RUN=1 bash --noprofile --norc <<'EOF' +set -euo pipefail +source "$PROJECT_ROOT/lib/optimize/tasks.sh" +launchctl() { echo "called"; } +export -f launchctl +run_launchctl_unload "/tmp/test.plist" false +EOF + + [ "$status" -eq 0 ] + [[ "$output" != *"called"* ]] +} diff --git a/tests/optimize_helpers.bats b/tests/optimize_helpers.bats deleted file mode 100644 index 5801252..0000000 --- a/tests/optimize_helpers.bats +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env bats - -setup_file() { - PROJECT_ROOT="$(cd "${BATS_TEST_DIRNAME}/.." && pwd)" - export PROJECT_ROOT - - ORIGINAL_HOME="${HOME:-}" - export ORIGINAL_HOME - - HOME="$(mktemp -d "${BATS_TEST_DIRNAME}/tmp-optimize-helpers.XXXXXX")" - export HOME - - mkdir -p "$HOME" -} - -teardown_file() { - rm -rf "$HOME" - if [[ -n "${ORIGINAL_HOME:-}" ]]; then - export HOME="$ORIGINAL_HOME" - fi -} - -@test "opt_msg uses dry-run output" { - run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" MOLE_DRY_RUN=1 bash --noprofile --norc <<'EOF' -set -euo pipefail -source "$PROJECT_ROOT/lib/core/common.sh" -source "$PROJECT_ROOT/lib/optimize/tasks.sh" -opt_msg "dry" -EOF - - [ "$status" -eq 0 ] - [[ "$output" == *"dry"* ]] -} - -@test "run_launchctl_unload skips in dry-run" { - run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" MOLE_DRY_RUN=1 bash --noprofile --norc <<'EOF' -set -euo pipefail -source "$PROJECT_ROOT/lib/optimize/tasks.sh" -launchctl() { echo "called"; } -export -f launchctl -run_launchctl_unload "/tmp/test.plist" false -EOF - - [ "$status" -eq 0 ] - [[ "$output" != *"called"* ]] -} diff --git a/tests/project_purge_extra.bats b/tests/project_purge_extra.bats deleted file mode 100644 index e9db76a..0000000 --- a/tests/project_purge_extra.bats +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env bats - -setup_file() { - PROJECT_ROOT="$(cd "${BATS_TEST_DIRNAME}/.." && pwd)" - export PROJECT_ROOT - - ORIGINAL_HOME="${HOME:-}" - export ORIGINAL_HOME - - HOME="$(mktemp -d "${BATS_TEST_DIRNAME}/tmp-purge-extra.XXXXXX")" - export HOME - - mkdir -p "$HOME" -} - -teardown_file() { - rm -rf "$HOME" - if [[ -n "${ORIGINAL_HOME:-}" ]]; then - export HOME="$ORIGINAL_HOME" - fi -} - -@test "is_project_container detects project indicators" { - run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF' -set -euo pipefail -source "$PROJECT_ROOT/lib/clean/project.sh" -mkdir -p "$HOME/Workspace2/project" -touch "$HOME/Workspace2/project/package.json" -if is_project_container "$HOME/Workspace2" 2; then - echo "yes" -fi -EOF - - [ "$status" -eq 0 ] - [[ "$output" == *"yes"* ]] -} - -@test "discover_project_dirs includes detected containers" { - run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF' -set -euo pipefail -source "$PROJECT_ROOT/lib/clean/project.sh" -mkdir -p "$HOME/CustomProjects/app" -touch "$HOME/CustomProjects/app/go.mod" -discover_project_dirs | grep -q "$HOME/CustomProjects" -EOF - - [ "$status" -eq 0 ] -} - -@test "save_discovered_paths writes config with tilde" { - run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF' -set -euo pipefail -source "$PROJECT_ROOT/lib/clean/project.sh" -save_discovered_paths "$HOME/Projects" -grep -q "^~/" "$HOME/.config/mole/purge_paths" -EOF - - [ "$status" -eq 0 ] -} - -@test "scan_purge_targets finds artifacts via find path" { - run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" MOLE_PURGE_MIN_DEPTH=1 MOLE_PURGE_MAX_DEPTH=2 bash --noprofile --norc <<'EOF' -set -euo pipefail -PATH="/usr/bin:/bin" -source "$PROJECT_ROOT/lib/clean/project.sh" -mkdir -p "$HOME/dev/app/node_modules" -scan_purge_targets "$HOME/dev" "$HOME/results.txt" -grep -q "node_modules" "$HOME/results.txt" -EOF - - [ "$status" -eq 0 ] -} - -@test "select_purge_categories returns failure on empty input" { - run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF' -set -euo pipefail -source "$PROJECT_ROOT/lib/clean/project.sh" -if select_purge_categories; then - exit 1 -fi -EOF - - [ "$status" -eq 0 ] -} diff --git a/tests/purge.bats b/tests/purge.bats index 8c33fc2..01c2afb 100644 --- a/tests/purge.bats +++ b/tests/purge.bats @@ -204,6 +204,69 @@ setup() { [[ "$result" == "NOT_PROTECTED" ]] } +@test "is_project_container detects project indicators" { + run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF' +set -euo pipefail +source "$PROJECT_ROOT/lib/clean/project.sh" +mkdir -p "$HOME/Workspace2/project" +touch "$HOME/Workspace2/project/package.json" +if is_project_container "$HOME/Workspace2" 2; then + echo "yes" +fi +EOF + + [ "$status" -eq 0 ] + [[ "$output" == *"yes"* ]] +} + +@test "discover_project_dirs includes detected containers" { + run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF' +set -euo pipefail +source "$PROJECT_ROOT/lib/clean/project.sh" +mkdir -p "$HOME/CustomProjects/app" +touch "$HOME/CustomProjects/app/go.mod" +discover_project_dirs | grep -q "$HOME/CustomProjects" +EOF + + [ "$status" -eq 0 ] +} + +@test "save_discovered_paths writes config with tilde" { + run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF' +set -euo pipefail +source "$PROJECT_ROOT/lib/clean/project.sh" +save_discovered_paths "$HOME/Projects" +grep -q "^~/" "$HOME/.config/mole/purge_paths" +EOF + + [ "$status" -eq 0 ] +} + +@test "scan_purge_targets finds artifacts via find path" { + run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" MOLE_PURGE_MIN_DEPTH=1 MOLE_PURGE_MAX_DEPTH=2 bash --noprofile --norc <<'EOF' +set -euo pipefail +PATH="/usr/bin:/bin" +source "$PROJECT_ROOT/lib/clean/project.sh" +mkdir -p "$HOME/dev/app/node_modules" +scan_purge_targets "$HOME/dev" "$HOME/results.txt" +grep -q "node_modules" "$HOME/results.txt" +EOF + + [ "$status" -eq 0 ] +} + +@test "select_purge_categories returns failure on empty input" { + run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF' +set -euo pipefail +source "$PROJECT_ROOT/lib/clean/project.sh" +if select_purge_categories; then + exit 1 +fi +EOF + + [ "$status" -eq 0 ] +} + @test "is_protected_vendor_dir: protects Go vendor" { mkdir -p "$HOME/www/go-app/vendor" touch "$HOME/www/go-app/go.mod" diff --git a/tests/touchid.bats b/tests/touchid.bats deleted file mode 100644 index 2594006..0000000 --- a/tests/touchid.bats +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env bats - -setup_file() { - PROJECT_ROOT="$(cd "${BATS_TEST_DIRNAME}/.." && pwd)" - export PROJECT_ROOT - - ORIGINAL_HOME="${HOME:-}" - export ORIGINAL_HOME - - HOME="$(mktemp -d "${BATS_TEST_DIRNAME}/tmp-touchid.XXXXXX")" - export HOME - - mkdir -p "$HOME" -} - -teardown_file() { - rm -rf "$HOME" - if [[ -n "${ORIGINAL_HOME:-}" ]]; then - export HOME="$ORIGINAL_HOME" - fi -} - -create_fake_utils() { - local dir="$1" - mkdir -p "$dir" - - cat > "$dir/sudo" <<'SCRIPT' -#!/usr/bin/env bash -if [[ "$1" == "-n" || "$1" == "-v" ]]; then - exit 0 -fi -exec "$@" -SCRIPT - chmod +x "$dir/sudo" - - cat > "$dir/bioutil" <<'SCRIPT' -#!/usr/bin/env bash -if [[ "$1" == "-r" ]]; then - echo "Touch ID: 1" - exit 0 -fi -exit 0 -SCRIPT - chmod +x "$dir/bioutil" -} - -@test "touchid status reflects pam file contents" { - pam_file="$HOME/pam_test" - cat > "$pam_file" <<'EOF' -auth sufficient pam_opendirectory.so -EOF - - run env MOLE_PAM_SUDO_FILE="$pam_file" "$PROJECT_ROOT/bin/touchid.sh" status - [ "$status" -eq 0 ] - [[ "$output" == *"not configured"* ]] - - cat > "$pam_file" <<'EOF' -auth sufficient pam_tid.so -EOF - - run env MOLE_PAM_SUDO_FILE="$pam_file" "$PROJECT_ROOT/bin/touchid.sh" status - [ "$status" -eq 0 ] - [[ "$output" == *"enabled"* ]] -} - -@test "enable_touchid inserts pam_tid line in pam file" { - pam_file="$HOME/pam_enable" - cat > "$pam_file" <<'EOF' -auth sufficient pam_opendirectory.so -EOF - - fake_bin="$HOME/fake-bin" - create_fake_utils "$fake_bin" - - run env PATH="$fake_bin:$PATH" MOLE_PAM_SUDO_FILE="$pam_file" "$PROJECT_ROOT/bin/touchid.sh" enable - [ "$status" -eq 0 ] - grep -q "pam_tid.so" "$pam_file" - [[ -f "${pam_file}.mole-backup" ]] -} - -@test "disable_touchid removes pam_tid line" { - pam_file="$HOME/pam_disable" - cat > "$pam_file" <<'EOF' -auth sufficient pam_tid.so -auth sufficient pam_opendirectory.so -EOF - - fake_bin="$HOME/fake-bin-disable" - create_fake_utils "$fake_bin" - - run env PATH="$fake_bin:$PATH" MOLE_PAM_SUDO_FILE="$pam_file" "$PROJECT_ROOT/bin/touchid.sh" disable - [ "$status" -eq 0 ] - run grep "pam_tid.so" "$pam_file" - [ "$status" -ne 0 ] -} diff --git a/tests/ui_input.bats b/tests/ui_input.bats deleted file mode 100644 index 6633b25..0000000 --- a/tests/ui_input.bats +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env bats - -setup_file() { - PROJECT_ROOT="$(cd "${BATS_TEST_DIRNAME}/.." && pwd)" - export PROJECT_ROOT -} - -setup() { - export MOLE_BASE_LOADED=1 # mock base loaded -} - -@test "read_key maps j/k/h/l to navigation" { - source "$PROJECT_ROOT/lib/core/ui.sh" - - run bash -c "source '$PROJECT_ROOT/lib/core/ui.sh'; echo -n 'j' | read_key" - [ "$output" = "DOWN" ] - - run bash -c "source '$PROJECT_ROOT/lib/core/ui.sh'; echo -n 'k' | read_key" - [ "$output" = "UP" ] - - run bash -c "source '$PROJECT_ROOT/lib/core/ui.sh'; echo -n 'h' | read_key" - [ "$output" = "LEFT" ] - - run bash -c "source '$PROJECT_ROOT/lib/core/ui.sh'; echo -n 'l' | read_key" - [ "$output" = "RIGHT" ] -} - -@test "read_key maps uppercase J/K/H/L to navigation" { - run bash -c "source '$PROJECT_ROOT/lib/core/ui.sh'; echo -n 'J' | read_key" - [ "$output" = "DOWN" ] - - run bash -c "source '$PROJECT_ROOT/lib/core/ui.sh'; echo -n 'K' | read_key" - [ "$output" = "UP" ] -} - -@test "read_key respects MOLE_READ_KEY_FORCE_CHAR" { - run bash -c "export MOLE_READ_KEY_FORCE_CHAR=1; source '$PROJECT_ROOT/lib/core/ui.sh'; echo -n 'j' | read_key" - [ "$output" = "CHAR:j" ] -} diff --git a/tests/uninstall.bats b/tests/uninstall.bats index 4f4cf14..5e1100f 100644 --- a/tests/uninstall.bats +++ b/tests/uninstall.bats @@ -216,4 +216,25 @@ result=$(decode_file_list "$encoded_data" "TestApp") EOF [ "$status" -eq 0 ] -} \ No newline at end of file +} + +@test "remove_mole deletes manual binaries and caches" { + mkdir -p "$HOME/.local/bin" + touch "$HOME/.local/bin/mole" + touch "$HOME/.local/bin/mo" + mkdir -p "$HOME/.config/mole" "$HOME/.cache/mole" + + run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" PATH="/usr/bin:/bin" bash --noprofile --norc << 'EOF' +set -euo pipefail +start_inline_spinner() { :; } +stop_inline_spinner() { :; } +export -f start_inline_spinner stop_inline_spinner +printf '\n' | "$PROJECT_ROOT/mole" remove +EOF + + [ "$status" -eq 0 ] + [ ! -f "$HOME/.local/bin/mole" ] + [ ! -f "$HOME/.local/bin/mo" ] + [ ! -d "$HOME/.config/mole" ] + [ ! -d "$HOME/.cache/mole" ] +} diff --git a/tests/update_manager.bats b/tests/update_manager.bats index 800c0ce..a33d058 100644 --- a/tests/update_manager.bats +++ b/tests/update_manager.bats @@ -4,6 +4,9 @@ setup_file() { PROJECT_ROOT="$(cd "${BATS_TEST_DIRNAME}/.." && pwd)" export PROJECT_ROOT + CURRENT_VERSION="$(grep '^VERSION=' "$PROJECT_ROOT/mole" | head -1 | sed 's/VERSION=\"\\(.*\\)\"/\\1/')" + export CURRENT_VERSION + ORIGINAL_HOME="${HOME:-}" export ORIGINAL_HOME @@ -161,3 +164,72 @@ EOF [[ "$output" == *"MOLE_CACHE_RESET"* ]] [[ "$output" == *"All updates completed"* ]] } + +@test "update_via_homebrew reports already on latest version" { + run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF' +set -euo pipefail +MOLE_TEST_BREW_UPDATE_OUTPUT="Updated 0 formulae" +MOLE_TEST_BREW_UPGRADE_OUTPUT="Warning: mole 1.7.9 already installed" +MOLE_TEST_BREW_LIST_OUTPUT="mole 1.7.9" +start_inline_spinner() { :; } +stop_inline_spinner() { :; } +brew() { + case "$1" in + update) echo "$MOLE_TEST_BREW_UPDATE_OUTPUT";; + upgrade) echo "$MOLE_TEST_BREW_UPGRADE_OUTPUT";; + list) if [[ "$2" == "--versions" ]]; then echo "$MOLE_TEST_BREW_LIST_OUTPUT"; fi ;; + esac +} +export -f brew start_inline_spinner stop_inline_spinner +source "$PROJECT_ROOT/lib/core/common.sh" +update_via_homebrew "1.7.9" +EOF + + [ "$status" -eq 0 ] + [[ "$output" == *"Already on latest version"* ]] +} + +@test "update_mole skips download when already latest" { + run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" CURRENT_VERSION="$CURRENT_VERSION" PATH="$HOME/fake-bin:/usr/bin:/bin" TERM="dumb" bash --noprofile --norc << 'EOF' +set -euo pipefail +curl() { + local out="" + local url="" + while [[ $# -gt 0 ]]; do + case "$1" in + -o) + out="$2" + shift 2 + ;; + http*://*) + url="$1" + shift + ;; + *) + shift + ;; + esac + done + + if [[ -n "$out" ]]; then + echo "Installer executed" > "$out" + return 0 + fi + + if [[ "$url" == *"api.github.com"* ]]; then + echo "{\"tag_name\":\"$CURRENT_VERSION\"}" + else + echo "VERSION=\"$CURRENT_VERSION\"" + fi +} +export -f curl + +brew() { exit 1; } +export -f brew + +"$PROJECT_ROOT/mole" update +EOF + + [ "$status" -eq 0 ] + [[ "$output" == *"Already on latest version"* ]] +} diff --git a/tests/update_remove.bats b/tests/update_remove.bats deleted file mode 100644 index 7faded3..0000000 --- a/tests/update_remove.bats +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/env bats - -setup_file() { - PROJECT_ROOT="$(cd "${BATS_TEST_DIRNAME}/.." && pwd)" - export PROJECT_ROOT - - CURRENT_VERSION="$(grep '^VERSION=' "$PROJECT_ROOT/mole" | head -1 | sed 's/VERSION="\(.*\)"/\1/')" - export CURRENT_VERSION - - ORIGINAL_HOME="${HOME:-}" - export ORIGINAL_HOME - - HOME="$(mktemp -d "${BATS_TEST_DIRNAME}/tmp-update-home.XXXXXX")" - export HOME -} - -teardown_file() { - rm -rf "$HOME" - if [[ -n "${ORIGINAL_HOME:-}" ]]; then - export HOME="$ORIGINAL_HOME" - fi -} - -setup() { - export TERM="dumb" - rm -rf "${HOME:?}"/* - mkdir -p "$HOME" -} - -@test "update_via_homebrew reports already on latest version" { - run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF' -set -euo pipefail -MOLE_TEST_BREW_UPDATE_OUTPUT="Updated 0 formulae" -MOLE_TEST_BREW_UPGRADE_OUTPUT="Warning: mole 1.7.9 already installed" -MOLE_TEST_BREW_LIST_OUTPUT="mole 1.7.9" -start_inline_spinner() { :; } -stop_inline_spinner() { :; } -brew() { - case "$1" in - update) echo "$MOLE_TEST_BREW_UPDATE_OUTPUT";; - upgrade) echo "$MOLE_TEST_BREW_UPGRADE_OUTPUT";; - list) if [[ "$2" == "--versions" ]]; then echo "$MOLE_TEST_BREW_LIST_OUTPUT"; fi ;; - esac -} -export -f brew start_inline_spinner stop_inline_spinner -source "$PROJECT_ROOT/lib/core/common.sh" -update_via_homebrew "1.7.9" -EOF - - [ "$status" -eq 0 ] - [[ "$output" == *"Already on latest version"* ]] -} - -@test "update_mole skips download when already latest" { - run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" PATH="$HOME/fake-bin:/usr/bin:/bin" TERM="dumb" bash --noprofile --norc << 'EOF' -set -euo pipefail -mkdir -p "$HOME/fake-bin" -cat > "$HOME/fake-bin/curl" <<'SCRIPT' -#!/usr/bin/env bash -out="" -while [[ $# -gt 0 ]]; do - case "$1" in - -o) - out="$2" - shift 2 - ;; - *) - shift - ;; - esac -done -if [[ -n "$out" ]]; then - cat <<'INSTALLER' > "$out" -#!/usr/bin/env bash -echo "Installer executed" -INSTALLER -else - echo "VERSION=\"$CURRENT_VERSION\"" -fi -SCRIPT -chmod +x "$HOME/fake-bin/curl" -cat > "$HOME/fake-bin/brew" <<'SCRIPT' -#!/usr/bin/env bash -exit 1 -SCRIPT -chmod +x "$HOME/fake-bin/brew" - -"$PROJECT_ROOT/mole" update -EOF - - [ "$status" -eq 0 ] - [[ "$output" == *"Already on latest version"* ]] -} - -@test "remove_mole deletes manual binaries and caches" { - mkdir -p "$HOME/.local/bin" - touch "$HOME/.local/bin/mole" - touch "$HOME/.local/bin/mo" - mkdir -p "$HOME/.config/mole" "$HOME/.cache/mole" - - run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" PATH="/usr/bin:/bin" bash --noprofile --norc << 'EOF' -set -euo pipefail -start_inline_spinner() { :; } -stop_inline_spinner() { :; } -export -f start_inline_spinner stop_inline_spinner -printf '\n' | "$PROJECT_ROOT/mole" remove -EOF - - [ "$status" -eq 0 ] - [ ! -f "$HOME/.local/bin/mole" ] - [ ! -f "$HOME/.local/bin/mo" ] - [ ! -d "$HOME/.config/mole" ] - [ ! -d "$HOME/.cache/mole" ] -} diff --git a/tests/user_clean_core.bats b/tests/user_clean_core.bats index 6aaa5bd..18e51a7 100644 --- a/tests/user_clean_core.bats +++ b/tests/user_clean_core.bats @@ -118,3 +118,43 @@ EOF [[ "$output" == *"Empty Library folders"* ]] [[ "$output" != *"Empty Library files"* ]] } + +@test "clean_browsers calls expected cache paths" { + run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF' +set -euo pipefail +source "$PROJECT_ROOT/lib/core/common.sh" +source "$PROJECT_ROOT/lib/clean/user.sh" +safe_clean() { echo "$2"; } +clean_browsers +EOF + + [ "$status" -eq 0 ] + [[ "$output" == *"Safari cache"* ]] + [[ "$output" == *"Firefox cache"* ]] +} + +@test "clean_application_support_logs skips when no access" { + run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF' +set -euo pipefail +source "$PROJECT_ROOT/lib/core/common.sh" +source "$PROJECT_ROOT/lib/clean/user.sh" +note_activity() { :; } +clean_application_support_logs +EOF + + [ "$status" -eq 0 ] + [[ "$output" == *"Skipped: No permission"* ]] +} + +@test "clean_apple_silicon_caches exits when not M-series" { + run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" IS_M_SERIES=false bash --noprofile --norc <<'EOF' +set -euo pipefail +source "$PROJECT_ROOT/lib/core/common.sh" +source "$PROJECT_ROOT/lib/clean/user.sh" +safe_clean() { echo "$2"; } +clean_apple_silicon_caches +EOF + + [ "$status" -eq 0 ] + [[ -z "$output" ]] +} diff --git a/tests/user_clean_extra.bats b/tests/user_clean_extra.bats deleted file mode 100644 index 199a376..0000000 --- a/tests/user_clean_extra.bats +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env bats - -setup_file() { - PROJECT_ROOT="$(cd "${BATS_TEST_DIRNAME}/.." && pwd)" - export PROJECT_ROOT - - ORIGINAL_HOME="${HOME:-}" - export ORIGINAL_HOME - - HOME="$(mktemp -d "${BATS_TEST_DIRNAME}/tmp-user-clean.XXXXXX")" - export HOME - - mkdir -p "$HOME" -} - -teardown_file() { - rm -rf "$HOME" - if [[ -n "${ORIGINAL_HOME:-}" ]]; then - export HOME="$ORIGINAL_HOME" - fi -} - -@test "clean_browsers calls expected cache paths" { - run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF' -set -euo pipefail -source "$PROJECT_ROOT/lib/core/common.sh" -source "$PROJECT_ROOT/lib/clean/user.sh" -safe_clean() { echo "$2"; } -clean_browsers -EOF - - [ "$status" -eq 0 ] - [[ "$output" == *"Safari cache"* ]] - [[ "$output" == *"Firefox cache"* ]] -} - -@test "clean_application_support_logs skips when no access" { - run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF' -set -euo pipefail -source "$PROJECT_ROOT/lib/core/common.sh" -source "$PROJECT_ROOT/lib/clean/user.sh" -note_activity() { :; } -clean_application_support_logs -EOF - - [ "$status" -eq 0 ] - [[ "$output" == *"Skipped: No permission"* ]] -} - -@test "clean_apple_silicon_caches exits when not M-series" { - run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" IS_M_SERIES=false bash --noprofile --norc <<'EOF' -set -euo pipefail -source "$PROJECT_ROOT/lib/core/common.sh" -source "$PROJECT_ROOT/lib/clean/user.sh" -safe_clean() { echo "$2"; } -clean_apple_silicon_caches -EOF - - [ "$status" -eq 0 ] - [[ -z "$output" ]] -}