1
0
mirror of https://github.com/tw93/Mole.git synced 2026-02-04 15:04:42 +00:00

Reconstruct clean lib code

This commit is contained in:
Tw93
2025-12-01 16:58:35 +08:00
parent 1578988ede
commit 4bd4ffc7be
43 changed files with 1105 additions and 1098 deletions

View File

@@ -24,8 +24,8 @@ teardown_file() {
}
setup() {
source "$PROJECT_ROOT/lib/common.sh"
source "$PROJECT_ROOT/lib/clean_caches.sh"
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/clean/caches.sh"
# Clean permission flag for each test
rm -f "$HOME/.cache/mole/permissions_granted"
@@ -34,7 +34,7 @@ setup() {
# Test check_tcc_permissions in non-interactive mode
@test "check_tcc_permissions skips in non-interactive mode" {
# Redirect stdin to simulate non-TTY
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/clean_caches.sh'; check_tcc_permissions" < /dev/null
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/clean/caches.sh'; check_tcc_permissions" < /dev/null
[ "$status" -eq 0 ]
# Should not create permission flag in non-interactive mode
[[ ! -f "$HOME/.cache/mole/permissions_granted" ]]
@@ -47,7 +47,7 @@ setup() {
touch "$HOME/.cache/mole/permissions_granted"
# Even in TTY mode, should skip if flag exists
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/clean_caches.sh'; [[ -t 1 ]] || true; check_tcc_permissions"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/clean/caches.sh'; [[ -t 1 ]] || true; check_tcc_permissions"
[ "$status" -eq 0 ]
}
@@ -66,13 +66,13 @@ setup() {
[[ -d "$HOME/.cache/mole" ]]
# Function should handle missing directories gracefully
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/clean_caches.sh'; check_tcc_permissions < /dev/null"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/clean/caches.sh'; check_tcc_permissions < /dev/null"
[ "$status" -eq 0 ]
}
# Test clean_service_worker_cache with non-existent path
@test "clean_service_worker_cache returns early when path doesn't exist" {
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/clean_caches.sh'; clean_service_worker_cache 'TestBrowser' '/nonexistent/path'"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/clean/caches.sh'; clean_service_worker_cache 'TestBrowser' '/nonexistent/path'"
[ "$status" -eq 0 ]
}
@@ -81,7 +81,7 @@ setup() {
local test_cache="$HOME/test_sw_cache"
mkdir -p "$test_cache"
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/clean_caches.sh'; clean_service_worker_cache 'TestBrowser' '$test_cache'"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/clean/caches.sh'; clean_service_worker_cache 'TestBrowser' '$test_cache'"
[ "$status" -eq 0 ]
rm -rf "$test_cache"
@@ -100,8 +100,8 @@ setup() {
run bash -c "
export DRY_RUN=true
export PROTECTED_SW_DOMAINS=(capcut.com photopea.com)
source '$PROJECT_ROOT/lib/common.sh'
source '$PROJECT_ROOT/lib/clean_caches.sh'
source '$PROJECT_ROOT/lib/core/common.sh'
source '$PROJECT_ROOT/lib/clean/caches.sh'
clean_service_worker_cache 'TestBrowser' '$test_cache'
"
[ "$status" -eq 0 ]
@@ -124,8 +124,8 @@ setup() {
run bash -c "
export DRY_RUN=true
source '$PROJECT_ROOT/lib/common.sh'
source '$PROJECT_ROOT/lib/clean_caches.sh'
source '$PROJECT_ROOT/lib/core/common.sh'
source '$PROJECT_ROOT/lib/clean/caches.sh'
clean_project_caches
"
[ "$status" -eq 0 ]
@@ -147,8 +147,8 @@ setup() {
# Should complete within reasonable time even with slow find
run timeout 15 bash -c "
source '$PROJECT_ROOT/lib/common.sh'
source '$PROJECT_ROOT/lib/clean_caches.sh'
source '$PROJECT_ROOT/lib/core/common.sh'
source '$PROJECT_ROOT/lib/clean/caches.sh'
clean_project_caches
"
# Either succeeds or times out gracefully (both acceptable)
@@ -168,8 +168,8 @@ setup() {
# We can't easily test this without mocking, but we can verify no crashes
run bash -c "
export DRY_RUN=true
source '$PROJECT_ROOT/lib/common.sh'
source '$PROJECT_ROOT/lib/clean_caches.sh'
source '$PROJECT_ROOT/lib/core/common.sh'
source '$PROJECT_ROOT/lib/clean/caches.sh'
clean_project_caches
"
[ "$status" -eq 0 ]

View File

@@ -46,8 +46,11 @@ setup() {
}
@test "touchid status reports current configuration" {
# Don't test actual Touch ID config (system-dependent, may trigger prompts)
# Just verify the command exists and can run
run env HOME="$HOME" "$PROJECT_ROOT/mole" touchid status
[ "$status" -eq 0 ]
# Should output either "enabled" or "not configured" message
[[ "$output" == *"Touch ID"* ]]
}

View File

@@ -30,13 +30,13 @@ teardown() {
}
@test "mo_spinner_chars returns default sequence when unset" {
result="$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/common.sh'; mo_spinner_chars")"
result="$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; mo_spinner_chars")"
[ "$result" = "|/-\\" ]
}
@test "mo_spinner_chars respects MO_SPINNER_CHARS override" {
export MO_SPINNER_CHARS="abcd"
result="$(HOME="$HOME" MO_SPINNER_CHARS="$MO_SPINNER_CHARS" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/common.sh'; mo_spinner_chars")"
result="$(HOME="$HOME" MO_SPINNER_CHARS="$MO_SPINNER_CHARS" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; mo_spinner_chars")"
[ "$result" = "abcd" ]
}
@@ -45,19 +45,19 @@ teardown() {
if [[ "$(uname -m)" == "arm64" ]]; then
expected="Apple Silicon"
fi
result="$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/common.sh'; detect_architecture")"
result="$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; detect_architecture")"
[ "$result" = "$expected" ]
}
@test "get_free_space returns a non-empty value" {
result="$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/common.sh'; get_free_space")"
result="$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; get_free_space")"
[[ -n "$result" ]]
}
@test "log_info prints message and appends to log file" {
local message="Informational message from test"
local stdout_output
stdout_output="$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/common.sh'; log_info '$message'")"
stdout_output="$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; log_info '$message'")"
[[ "$stdout_output" == *"$message"* ]]
local log_file="$HOME/.config/mole/mole.log"
@@ -69,7 +69,7 @@ teardown() {
local message="Something went wrong"
local stderr_file="$HOME/log_error_stderr.txt"
HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/common.sh'; log_error '$message' 1>/dev/null 2>'$stderr_file'"
HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; log_error '$message' 1>/dev/null 2>'$stderr_file'"
[[ -s "$stderr_file" ]]
grep -q "$message" "$stderr_file"
@@ -86,18 +86,18 @@ teardown() {
dd if=/dev/zero of="$log_file" bs=1024 count=1100 2> /dev/null
# First call should rotate
HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/common.sh'"
HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'"
[[ -f "${log_file}.old" ]]
# Verify MOLE_LOG_ROTATED was set (rotation happened)
result=$(HOME="$HOME" MOLE_LOG_ROTATED=1 bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/common.sh'; echo \$MOLE_LOG_ROTATED")
result=$(HOME="$HOME" MOLE_LOG_ROTATED=1 bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; echo \$MOLE_LOG_ROTATED")
[[ "$result" == "1" ]]
}
@test "drain_pending_input clears stdin buffer" {
# Test that drain_pending_input doesn't hang (using background job with timeout)
result=$(
(echo -e "test\ninput" | HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/common.sh'; drain_pending_input; echo done") &
(echo -e "test\ninput" | HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; drain_pending_input; echo done") &
pid=$!
sleep 2
if kill -0 "$pid" 2> /dev/null; then
@@ -114,7 +114,7 @@ teardown() {
@test "bytes_to_human converts byte counts into readable units" {
output="$(
HOME="$HOME" bash --noprofile --norc << 'EOF'
source "$PROJECT_ROOT/lib/common.sh"
source "$PROJECT_ROOT/lib/core/common.sh"
bytes_to_human 512
bytes_to_human 2048
bytes_to_human $((5 * 1024 * 1024))
@@ -135,7 +135,7 @@ EOF
@test "create_temp_file and create_temp_dir are tracked and cleaned" {
HOME="$HOME" bash --noprofile --norc << 'EOF'
source "$PROJECT_ROOT/lib/common.sh"
source "$PROJECT_ROOT/lib/core/common.sh"
create_temp_file > "$HOME/temp_file_path.txt"
create_temp_dir > "$HOME/temp_dir_path.txt"
cleanup_temp_files
@@ -151,20 +151,20 @@ EOF
@test "should_protect_data protects system and critical apps" {
# System apps should be protected
result=$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/common.sh'; should_protect_data 'com.apple.Safari' && echo 'protected' || echo 'not-protected'")
result=$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; should_protect_data 'com.apple.Safari' && echo 'protected' || echo 'not-protected'")
[ "$result" = "protected" ]
# Critical network apps should be protected
result=$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/common.sh'; should_protect_data 'com.clash.app' && echo 'protected' || echo 'not-protected'")
result=$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; should_protect_data 'com.clash.app' && echo 'protected' || echo 'not-protected'")
[ "$result" = "protected" ]
# Regular apps should not be protected
result=$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/common.sh'; should_protect_data 'com.example.RegularApp' && echo 'protected' || echo 'not-protected'")
result=$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; should_protect_data 'com.example.RegularApp' && echo 'protected' || echo 'not-protected'")
[ "$result" = "not-protected" ]
}
@test "print_summary_block formats output correctly" {
result=$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/common.sh'; print_summary_block 'success' 'Test Summary' 'Detail 1' 'Detail 2'")
result=$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; print_summary_block 'success' 'Test Summary' 'Detail 1' 'Detail 2'")
[[ "$result" == *"Test Summary"* ]]
[[ "$result" == *"Detail 1"* ]]
[[ "$result" == *"Detail 2"* ]]
@@ -173,7 +173,7 @@ EOF
@test "start_inline_spinner and stop_inline_spinner work in non-TTY" {
# Should not hang in non-interactive mode
result=$(HOME="$HOME" bash --noprofile --norc << 'EOF'
source "$PROJECT_ROOT/lib/common.sh"
source "$PROJECT_ROOT/lib/core/common.sh"
MOLE_SPINNER_PREFIX=" " start_inline_spinner "Testing..."
sleep 0.1
stop_inline_spinner

View File

@@ -32,7 +32,7 @@ setup() {
run bash --noprofile --norc -c '
set -euo pipefail
PATH="/usr/bin:/bin"
source "'"$PROJECT_ROOT"'/lib/common.sh"
source "'"$PROJECT_ROOT"'/lib/core/common.sh"
run_with_timeout 1 sleep 0.1
'
[ "$status" -eq 0 ]
@@ -42,7 +42,7 @@ setup() {
run bash --noprofile --norc -c '
set -euo pipefail
PATH="/usr/bin:/bin"
source "'"$PROJECT_ROOT"'/lib/common.sh"
source "'"$PROJECT_ROOT"'/lib/core/common.sh"
run_with_timeout 1 sleep 5
'
[ "$status" -eq 124 ]
@@ -56,8 +56,8 @@ setup() {
run env HOME="$HOME" bash --noprofile --norc << 'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/common.sh"
source "$PROJECT_ROOT/lib/optimization_tasks.sh"
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
# Mock sudo and defaults to avoid system changes
sudo() { return 0; }
defaults() { return 0; }
@@ -74,8 +74,8 @@ EOF
run env HOME="$HOME" bash --noprofile --norc << 'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/common.sh"
source "$PROJECT_ROOT/lib/optimization_tasks.sh"
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
sudo() { return 0; }
defaults() { return 0; }
export -f sudo defaults
@@ -96,8 +96,8 @@ EOF
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/common.sh"
source "$PROJECT_ROOT/lib/optimization_tasks.sh"
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
opt_saved_state_cleanup
EOF
@@ -109,8 +109,8 @@ EOF
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/common.sh"
source "$PROJECT_ROOT/lib/optimization_tasks.sh"
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
opt_saved_state_cleanup
EOF
@@ -124,8 +124,8 @@ EOF
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/common.sh"
source "$PROJECT_ROOT/lib/optimization_tasks.sh"
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
# Mock qlmanage and cleanup_path to avoid system calls
qlmanage() { return 0; }
cleanup_path() {
@@ -148,8 +148,8 @@ EOF
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/common.sh"
source "$PROJECT_ROOT/lib/optimization_tasks.sh"
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
# MOLE_MAIL_DOWNLOADS_MIN_KB is readonly, defaults to 5120 KB (~5MB)
opt_mail_downloads
EOF
@@ -170,8 +170,8 @@ EOF
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/common.sh"
source "$PROJECT_ROOT/lib/optimization_tasks.sh"
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
# MOLE_MAIL_DOWNLOADS_MIN_KB and MOLE_LOG_AGE_DAYS are readonly constants
opt_mail_downloads
EOF
@@ -182,8 +182,8 @@ EOF
@test "_opt_get_dir_size_kb returns zero for missing directory" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/common.sh"
source "$PROJECT_ROOT/lib/optimization_tasks.sh"
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
size=$(_opt_get_dir_size_kb "/nonexistent/path")
echo "$size"
EOF
@@ -198,8 +198,8 @@ EOF
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/common.sh"
source "$PROJECT_ROOT/lib/optimization_tasks.sh"
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
size=$(_opt_get_dir_size_kb "$HOME/test_size")
echo "$size"
EOF

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env bats
# Tests for safe_* functions in lib/common.sh
# Tests for safe_* functions in lib/core/common.sh
setup_file() {
PROJECT_ROOT="$(cd "${BATS_TEST_DIRNAME}/.." && pwd)"
@@ -22,7 +22,7 @@ teardown_file() {
}
setup() {
source "$PROJECT_ROOT/lib/common.sh"
source "$PROJECT_ROOT/lib/core/common.sh"
TEST_DIR="$HOME/test_safe_functions"
mkdir -p "$TEST_DIR"
}
@@ -33,39 +33,39 @@ teardown() {
# Test validate_path_for_deletion
@test "validate_path_for_deletion rejects empty path" {
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; validate_path_for_deletion ''"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; validate_path_for_deletion ''"
[ "$status" -eq 1 ]
}
@test "validate_path_for_deletion rejects relative path" {
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; validate_path_for_deletion 'relative/path'"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; validate_path_for_deletion 'relative/path'"
[ "$status" -eq 1 ]
}
@test "validate_path_for_deletion rejects path traversal" {
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; validate_path_for_deletion '/tmp/../etc'"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; validate_path_for_deletion '/tmp/../etc'"
[ "$status" -eq 1 ]
}
@test "validate_path_for_deletion rejects system directories" {
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; validate_path_for_deletion '/System'"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; validate_path_for_deletion '/System'"
[ "$status" -eq 1 ]
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; validate_path_for_deletion '/usr/bin'"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; validate_path_for_deletion '/usr/bin'"
[ "$status" -eq 1 ]
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; validate_path_for_deletion '/etc'"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; validate_path_for_deletion '/etc'"
[ "$status" -eq 1 ]
}
@test "validate_path_for_deletion accepts valid path" {
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; validate_path_for_deletion '$TEST_DIR/valid'"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; validate_path_for_deletion '$TEST_DIR/valid'"
[ "$status" -eq 0 ]
}
# Test safe_remove
@test "safe_remove validates path before deletion" {
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; safe_remove '/System/test' 2>&1"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; safe_remove '/System/test' 2>&1"
[ "$status" -eq 1 ]
}
@@ -73,7 +73,7 @@ teardown() {
local test_file="$TEST_DIR/test_file.txt"
echo "test" > "$test_file"
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; safe_remove '$test_file' true"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; safe_remove '$test_file' true"
[ "$status" -eq 0 ]
[ ! -f "$test_file" ]
}
@@ -83,19 +83,19 @@ teardown() {
mkdir -p "$test_subdir"
touch "$test_subdir/file.txt"
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; safe_remove '$test_subdir' true"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; safe_remove '$test_subdir' true"
[ "$status" -eq 0 ]
[ ! -d "$test_subdir" ]
}
@test "safe_remove handles non-existent path gracefully" {
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; safe_remove '$TEST_DIR/nonexistent' true"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; safe_remove '$TEST_DIR/nonexistent' true"
[ "$status" -eq 0 ]
}
@test "safe_remove in silent mode suppresses error output" {
# Try to remove system directory in silent mode
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; safe_remove '/System/test' true 2>&1"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; safe_remove '/System/test' true 2>&1"
[ "$status" -eq 1 ]
# Should not output error in silent mode
}
@@ -103,7 +103,7 @@ teardown() {
# Test safe_find_delete
@test "safe_find_delete validates base directory" {
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; safe_find_delete '/nonexistent' '*.tmp' 7 'f' 2>&1"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; safe_find_delete '/nonexistent' '*.tmp' 7 'f' 2>&1"
[ "$status" -eq 1 ]
}
@@ -113,7 +113,7 @@ teardown() {
mkdir -p "$real_dir"
ln -s "$real_dir" "$link_dir"
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; safe_find_delete '$link_dir' '*.tmp' 7 'f' 2>&1"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; safe_find_delete '$link_dir' '*.tmp' 7 'f' 2>&1"
[ "$status" -eq 1 ]
[[ "$output" == *"symlink"* ]]
@@ -121,7 +121,7 @@ teardown() {
}
@test "safe_find_delete validates type filter" {
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; safe_find_delete '$TEST_DIR' '*.tmp' 7 'x' 2>&1"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; safe_find_delete '$TEST_DIR' '*.tmp' 7 'x' 2>&1"
[ "$status" -eq 1 ]
[[ "$output" == *"Invalid type filter"* ]]
}
@@ -137,21 +137,21 @@ teardown() {
# Make old_file 8 days old (requires touch -t)
touch -t "$(date -v-8d '+%Y%m%d%H%M.%S' 2>/dev/null || date -d '8 days ago' '+%Y%m%d%H%M.%S')" "$old_file" 2>/dev/null || true
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; safe_find_delete '$TEST_DIR' '*.tmp' 7 'f'"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; safe_find_delete '$TEST_DIR' '*.tmp' 7 'f'"
[ "$status" -eq 0 ]
}
# Test MOLE constants are defined
@test "MOLE_* constants are defined" {
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; echo \$MOLE_TEMP_FILE_AGE_DAYS"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; echo \$MOLE_TEMP_FILE_AGE_DAYS"
[ "$status" -eq 0 ]
[ "$output" = "7" ]
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; echo \$MOLE_MAX_PARALLEL_JOBS"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; echo \$MOLE_MAX_PARALLEL_JOBS"
[ "$status" -eq 0 ]
[ "$output" = "15" ]
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; echo \$MOLE_TM_BACKUP_SAFE_HOURS"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; echo \$MOLE_TM_BACKUP_SAFE_HOURS"
[ "$status" -eq 0 ]
[ "$output" = "48" ]
}

View File

@@ -55,12 +55,16 @@ setup() {
@test "build-analyze.sh detects missing Go toolchain" {
if command -v go > /dev/null 2>&1; then
skip "Go is installed, cannot test missing toolchain"
# Go is installed, verify script doesn't error out
# (Don't actually build - too slow)
run bash -c "grep -q 'go build' '$PROJECT_ROOT/scripts/build-analyze.sh'"
[ "$status" -eq 0 ]
else
# Go is missing, verify proper error handling
run "$PROJECT_ROOT/scripts/build-analyze.sh"
[ "$status" -ne 0 ]
[[ "$output" == *"Go not installed"* ]]
fi
run "$PROJECT_ROOT/scripts/build-analyze.sh"
[ "$status" -ne 0 ]
[[ "$output" == *"Go not installed"* ]]
}
@test "build-analyze.sh has version info support" {

View File

@@ -7,8 +7,8 @@ setup_file() {
setup() {
# Source common.sh first (required by sudo_manager)
source "$PROJECT_ROOT/lib/common.sh"
source "$PROJECT_ROOT/lib/sudo_manager.sh"
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/core/sudo.sh"
}
# Test sudo session detection
@@ -33,7 +33,7 @@ setup() {
export -f sudo
# These should not crash even without real sudo
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/sudo_manager.sh'; has_sudo_session"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/core/sudo.sh'; has_sudo_session"
[ "$status" -eq 1 ] # Expected: no sudo session
}
@@ -51,7 +51,7 @@ setup() {
# Start keepalive (will run in background)
local pid
pid=$(bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/sudo_manager.sh'; _start_sudo_keepalive")
pid=$(bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/core/sudo.sh'; _start_sudo_keepalive")
# Should return a PID (number)
[[ "$pid" =~ ^[0-9]+$ ]]
@@ -63,10 +63,10 @@ setup() {
# Test _stop_sudo_keepalive
@test "_stop_sudo_keepalive handles invalid PID gracefully" {
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/sudo_manager.sh'; _stop_sudo_keepalive ''"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/core/sudo.sh'; _stop_sudo_keepalive ''"
[ "$status" -eq 0 ]
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/sudo_manager.sh'; _stop_sudo_keepalive '99999'"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/core/sudo.sh'; _stop_sudo_keepalive '99999'"
[ "$status" -eq 0 ]
}
@@ -77,12 +77,12 @@ setup() {
# Set a fake PID
export MOLE_SUDO_KEEPALIVE_PID="99999"
run bash -c "export MOLE_SUDO_KEEPALIVE_PID=99999; source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/sudo_manager.sh'; stop_sudo_session"
run bash -c "export MOLE_SUDO_KEEPALIVE_PID=99999; source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/core/sudo.sh'; stop_sudo_session"
[ "$status" -eq 0 ]
}
# Test global state management
@test "sudo manager initializes global state correctly" {
result=$(bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/sudo_manager.sh'; echo \$MOLE_SUDO_ESTABLISHED")
result=$(bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/core/sudo.sh'; echo \$MOLE_SUDO_ESTABLISHED")
[[ "$result" == "false" ]] || [[ -z "$result" ]]
}

View File

@@ -42,7 +42,7 @@ create_app_artifacts() {
result="$(
HOME="$HOME" bash --noprofile --norc << 'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/common.sh"
source "$PROJECT_ROOT/lib/core/common.sh"
find_app_files "com.example.TestApp" "TestApp"
EOF
)"
@@ -62,7 +62,7 @@ EOF
result="$(
HOME="$HOME" bash --noprofile --norc << 'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/common.sh"
source "$PROJECT_ROOT/lib/core/common.sh"
files="$(printf '%s\n%s\n' "$HOME/sized/file1" "$HOME/sized/file2")"
calculate_total_size "$files"
EOF
@@ -77,8 +77,8 @@ EOF
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/common.sh"
source "$PROJECT_ROOT/lib/uninstall_batch.sh"
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/uninstall.sh"
# Test stubs
request_sudo_access() { return 0; }
@@ -119,8 +119,8 @@ EOF
@test "decode_file_list validates base64 encoding" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/common.sh"
source "$PROJECT_ROOT/lib/uninstall_batch.sh"
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/uninstall.sh"
# Valid base64 encoded path list
valid_data=$(printf '/path/one\n/path/two' | base64)
@@ -134,8 +134,8 @@ EOF
@test "decode_file_list rejects invalid base64" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/common.sh"
source "$PROJECT_ROOT/lib/uninstall_batch.sh"
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/uninstall.sh"
# Invalid base64 - function should return empty and fail
if result=$(decode_file_list "not-valid-base64!!!" "TestApp" 2>/dev/null); then
@@ -153,8 +153,8 @@ EOF
@test "decode_file_list handles empty input" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/common.sh"
source "$PROJECT_ROOT/lib/uninstall_batch.sh"
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/uninstall.sh"
# Empty base64
empty_data=$(printf '' | base64)
@@ -169,8 +169,8 @@ EOF
@test "decode_file_list rejects non-absolute paths" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/common.sh"
source "$PROJECT_ROOT/lib/uninstall_batch.sh"
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/uninstall.sh"
# Relative path - function should reject it
bad_data=$(printf 'relative/path' | base64)

View File

@@ -22,8 +22,8 @@ teardown_file() {
}
setup() {
source "$PROJECT_ROOT/lib/common.sh"
source "$PROJECT_ROOT/lib/update_manager.sh"
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/manage/update.sh"
}
# Test brew_has_outdated function
@@ -33,7 +33,7 @@ setup() {
}
export -f brew
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/update_manager.sh'; brew_has_outdated"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/manage/update.sh'; brew_has_outdated"
[ "$status" -eq 1 ]
}
@@ -49,7 +49,7 @@ setup() {
}
export -f brew
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/update_manager.sh'; brew_has_outdated"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/manage/update.sh'; brew_has_outdated"
[ "$status" -eq 0 ]
}
@@ -64,30 +64,30 @@ setup() {
}
export -f brew
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/update_manager.sh'; brew_has_outdated cask"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/manage/update.sh'; brew_has_outdated cask"
[ "$status" -eq 0 ]
}
# Test format_brew_update_label function
@test "format_brew_update_label returns empty when no updates" {
result=$(BREW_OUTDATED_COUNT=0 bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/update_manager.sh'; format_brew_update_label")
result=$(BREW_OUTDATED_COUNT=0 bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/manage/update.sh'; format_brew_update_label")
[[ -z "$result" ]]
}
@test "format_brew_update_label formats with formula and cask counts" {
result=$(BREW_OUTDATED_COUNT=5 BREW_FORMULA_OUTDATED_COUNT=3 BREW_CASK_OUTDATED_COUNT=2 bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/update_manager.sh'; format_brew_update_label")
result=$(BREW_OUTDATED_COUNT=5 BREW_FORMULA_OUTDATED_COUNT=3 BREW_CASK_OUTDATED_COUNT=2 bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/manage/update.sh'; format_brew_update_label")
[[ "$result" =~ "3 formula" ]]
[[ "$result" =~ "2 cask" ]]
}
@test "format_brew_update_label shows total when breakdown unavailable" {
result=$(BREW_OUTDATED_COUNT=5 bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/update_manager.sh'; format_brew_update_label")
result=$(BREW_OUTDATED_COUNT=5 bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/manage/update.sh'; format_brew_update_label")
[[ "$result" =~ "5 updates" ]]
}
# Test ask_for_updates function
@test "ask_for_updates returns 1 when no updates available" {
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/update_manager.sh'; ask_for_updates < /dev/null"
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/manage/update.sh'; ask_for_updates < /dev/null"
[ "$status" -eq 1 ]
}
@@ -98,7 +98,7 @@ setup() {
export BREW_CASK_OUTDATED_COUNT=2
# Use input redirection to simulate ESC (cancel)
run bash -c "printf '\x1b' | source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/update_manager.sh'; ask_for_updates"
run bash -c "printf '\x1b' | source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/manage/update.sh'; ask_for_updates"
# Should show updates and ask for confirmation
[ "$status" -eq 1 ] # ESC cancels
}
@@ -106,21 +106,21 @@ setup() {
@test "ask_for_updates detects App Store updates" {
export APPSTORE_UPDATE_COUNT=3
run bash -c "printf '\x1b' | source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/update_manager.sh'; ask_for_updates"
run bash -c "printf '\x1b' | source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/manage/update.sh'; ask_for_updates"
[ "$status" -eq 1 ] # ESC cancels
}
@test "ask_for_updates detects macOS updates" {
export MACOS_UPDATE_AVAILABLE=true
run bash -c "printf '\x1b' | source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/update_manager.sh'; ask_for_updates"
run bash -c "printf '\x1b' | source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/manage/update.sh'; ask_for_updates"
[ "$status" -eq 1 ] # ESC cancels
}
@test "ask_for_updates detects Mole updates" {
export MOLE_UPDATE_AVAILABLE=true
run bash -c "printf '\x1b' | source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/update_manager.sh'; ask_for_updates"
run bash -c "printf '\x1b' | source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/manage/update.sh'; ask_for_updates"
[ "$status" -eq 1 ] # ESC cancels
}

View File

@@ -44,7 +44,7 @@ brew() {
esac
}
export -f brew start_inline_spinner stop_inline_spinner
source "$PROJECT_ROOT/lib/common.sh"
source "$PROJECT_ROOT/lib/core/common.sh"
update_via_homebrew "1.7.9"
EOF

View File

@@ -28,7 +28,7 @@ setup() {
@test "patterns_equivalent treats paths with tilde expansion as equal" {
local status
if HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/whitelist_manager.sh'; patterns_equivalent '~/.cache/test' \"\$HOME/.cache/test\""; then
if HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/manage/whitelist.sh'; patterns_equivalent '~/.cache/test' \"\$HOME/.cache/test\""; then
status=0
else
status=$?
@@ -38,7 +38,7 @@ setup() {
@test "patterns_equivalent distinguishes different paths" {
local status
if HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/whitelist_manager.sh'; patterns_equivalent '~/.cache/test' \"\$HOME/.cache/other\""; then
if HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/manage/whitelist.sh'; patterns_equivalent '~/.cache/test' \"\$HOME/.cache/other\""; then
status=0
else
status=$?
@@ -47,7 +47,7 @@ setup() {
}
@test "save_whitelist_patterns keeps unique entries and preserves header" {
HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/whitelist_manager.sh'; save_whitelist_patterns \"\$HOME/.cache/foo\" \"\$HOME/.cache/foo\" \"\$HOME/.cache/bar\""
HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/manage/whitelist.sh'; save_whitelist_patterns \"\$HOME/.cache/foo\" \"\$HOME/.cache/foo\" \"\$HOME/.cache/bar\""
[[ -f "$WHITELIST_PATH" ]]
@@ -64,8 +64,8 @@ setup() {
@test "load_whitelist falls back to defaults when config missing" {
rm -f "$WHITELIST_PATH"
HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/whitelist_manager.sh'; rm -f \"\$HOME/.config/mole/whitelist\"; load_whitelist; printf '%s\n' \"\${CURRENT_WHITELIST_PATTERNS[@]}\"" > "$HOME/current_whitelist.txt"
HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/whitelist_manager.sh'; printf '%s\n' \"\${DEFAULT_WHITELIST_PATTERNS[@]}\"" > "$HOME/default_whitelist.txt"
HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/manage/whitelist.sh'; rm -f \"\$HOME/.config/mole/whitelist\"; load_whitelist; printf '%s\n' \"\${CURRENT_WHITELIST_PATTERNS[@]}\"" > "$HOME/current_whitelist.txt"
HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/manage/whitelist.sh'; printf '%s\n' \"\${DEFAULT_WHITELIST_PATTERNS[@]}\"" > "$HOME/default_whitelist.txt"
current=()
while IFS= read -r line; do
@@ -83,14 +83,14 @@ setup() {
@test "is_whitelisted matches saved patterns exactly" {
local status
if HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/whitelist_manager.sh'; save_whitelist_patterns \"\$HOME/.cache/unique-pattern\"; load_whitelist; is_whitelisted \"\$HOME/.cache/unique-pattern\""; then
if HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/manage/whitelist.sh'; save_whitelist_patterns \"\$HOME/.cache/unique-pattern\"; load_whitelist; is_whitelisted \"\$HOME/.cache/unique-pattern\""; then
status=0
else
status=$?
fi
[ "$status" -eq 0 ]
if HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/whitelist_manager.sh'; save_whitelist_patterns \"\$HOME/.cache/unique-pattern\"; load_whitelist; is_whitelisted \"\$HOME/.cache/other-pattern\""; then
if HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/manage/whitelist.sh'; save_whitelist_patterns \"\$HOME/.cache/unique-pattern\"; load_whitelist; is_whitelisted \"\$HOME/.cache/other-pattern\""; then
status=0
else
status=$?