mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 17:24:45 +00:00
Merge pull request #240 from JackPhallen/feat/installers-clean
feat: Create utility to find stale app installers
This commit is contained in:
@@ -49,6 +49,7 @@ mo optimize # Refresh caches & services
|
|||||||
mo analyze # Visual disk explorer
|
mo analyze # Visual disk explorer
|
||||||
mo status # Live system health dashboard
|
mo status # Live system health dashboard
|
||||||
mo purge # Clean project build artifacts
|
mo purge # Clean project build artifacts
|
||||||
|
mo installers # Find and remove installer files
|
||||||
|
|
||||||
mo touchid # Configure Touch ID for sudo
|
mo touchid # Configure Touch ID for sudo
|
||||||
mo completion # Setup shell tab completion
|
mo completion # Setup shell tab completion
|
||||||
|
|||||||
341
bin/installers.sh
Executable file
341
bin/installers.sh
Executable file
@@ -0,0 +1,341 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Mole - Installers command
|
||||||
|
# Highlights and helps remove installer files (.dmg, .pkg, .mpkg, .iso, .xip, .zip)
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
# External variables set by menu_paginated.sh and environment
|
||||||
|
declare MOLE_SELECTION_RESULT
|
||||||
|
declare MOLE_INSTALLER_SCAN_MAX_DEPTH
|
||||||
|
|
||||||
|
export LC_ALL=C
|
||||||
|
export LANG=C
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
source "$SCRIPT_DIR/../lib/core/common.sh"
|
||||||
|
source "$SCRIPT_DIR/../lib/ui/menu_paginated.sh"
|
||||||
|
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
show_cursor
|
||||||
|
cleanup_temp_files
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
trap 'trap - EXIT; cleanup; exit 130' INT TERM
|
||||||
|
|
||||||
|
# Scan configuration
|
||||||
|
readonly INSTALLER_SCAN_MAX_DEPTH_DEFAULT=2
|
||||||
|
readonly INSTALLER_SCAN_PATHS=(
|
||||||
|
"$HOME/Downloads"
|
||||||
|
"$HOME/Desktop"
|
||||||
|
"$HOME/Documents"
|
||||||
|
"$HOME/Public"
|
||||||
|
"$HOME/Library/Downloads"
|
||||||
|
"/Users/Shared"
|
||||||
|
"/Users/Shared/Downloads" # Search one level deeper
|
||||||
|
)
|
||||||
|
readonly MAX_ZIP_ENTRIES=5
|
||||||
|
|
||||||
|
# Check for installer payloads inside ZIP (single pass, fused size and pattern check)
|
||||||
|
is_installer_zip() {
|
||||||
|
local zip="$1"
|
||||||
|
local cap="$MAX_ZIP_ENTRIES"
|
||||||
|
|
||||||
|
zipinfo -1 "$zip" >/dev/null 2>&1 || return 1
|
||||||
|
|
||||||
|
zipinfo -1 "$zip" 2>/dev/null \
|
||||||
|
| head -n $((cap + 1)) \
|
||||||
|
| awk -v cap="$cap" '
|
||||||
|
/\.(app|pkg|dmg|xip)(\/|$)/ { found=1 }
|
||||||
|
END {
|
||||||
|
if (NR > cap) exit 1
|
||||||
|
exit found ? 0 : 1
|
||||||
|
}
|
||||||
|
'
|
||||||
|
}
|
||||||
|
|
||||||
|
scan_installers_in_path() {
|
||||||
|
local path="$1"
|
||||||
|
local max_depth="${MOLE_INSTALLER_SCAN_MAX_DEPTH:-$INSTALLER_SCAN_MAX_DEPTH_DEFAULT}"
|
||||||
|
|
||||||
|
[[ -d "$path" ]] || return 0
|
||||||
|
|
||||||
|
local file
|
||||||
|
|
||||||
|
if command -v fd > /dev/null 2>&1; then
|
||||||
|
while IFS= read -r file; do
|
||||||
|
[[ -L "$file" ]] && continue # Skip symlinks explicitly
|
||||||
|
case "$file" in
|
||||||
|
*.dmg|*.pkg|*.mpkg|*.iso|*.xip)
|
||||||
|
echo "$file"
|
||||||
|
;;
|
||||||
|
*.zip)
|
||||||
|
[[ -r "$file" ]] || continue
|
||||||
|
if is_installer_zip "$file" 2>/dev/null; then
|
||||||
|
echo "$file"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done < <(
|
||||||
|
fd --no-ignore --hidden --type f --max-depth "$max_depth" \
|
||||||
|
-e dmg -e pkg -e mpkg -e iso -e xip -e zip \
|
||||||
|
. "$path" 2>/dev/null || true
|
||||||
|
)
|
||||||
|
else
|
||||||
|
while IFS= read -r file; do
|
||||||
|
[[ -L "$file" ]] && continue # Skip symlinks explicitly
|
||||||
|
case "$file" in
|
||||||
|
*.dmg|*.pkg|*.mpkg|*.iso|*.xip)
|
||||||
|
echo "$file"
|
||||||
|
;;
|
||||||
|
*.zip)
|
||||||
|
[[ -r "$file" ]] || continue
|
||||||
|
if is_installer_zip "$file" 2>/dev/null; then
|
||||||
|
echo "$file"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done < <(
|
||||||
|
find "$path" -maxdepth "$max_depth" -type f \
|
||||||
|
\( -name '*.dmg' -o -name '*.pkg' -o -name '*.mpkg' \
|
||||||
|
-o -name '*.iso' -o -name '*.xip' -o -name '*.zip' \) \
|
||||||
|
2>/dev/null || true
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
scan_all_installers() {
|
||||||
|
for path in "${INSTALLER_SCAN_PATHS[@]}"; do
|
||||||
|
scan_installers_in_path "$path"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Initialize stats
|
||||||
|
declare -i total_deleted=0
|
||||||
|
declare -i total_size_freed_kb=0
|
||||||
|
|
||||||
|
# Global arrays for installer data
|
||||||
|
declare -a INSTALLER_PATHS=()
|
||||||
|
declare -a INSTALLER_SIZES=()
|
||||||
|
declare -a DISPLAY_NAMES=()
|
||||||
|
|
||||||
|
# Collect all installers with their metadata
|
||||||
|
collect_installers() {
|
||||||
|
printf '\n'
|
||||||
|
echo -e "${BLUE}━━━ Scanning for installers ━━━${NC}"
|
||||||
|
|
||||||
|
# Clear previous results
|
||||||
|
INSTALLER_PATHS=()
|
||||||
|
INSTALLER_SIZES=()
|
||||||
|
DISPLAY_NAMES=()
|
||||||
|
|
||||||
|
# Scan all paths, deduplicate, and sort results
|
||||||
|
local -a all_files=()
|
||||||
|
local sorted_paths
|
||||||
|
sorted_paths=$(scan_all_installers | sort -u)
|
||||||
|
|
||||||
|
if [[ -z "$sorted_paths" ]]; then
|
||||||
|
echo -e " ${YELLOW}No installer files found${NC}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Read sorted results into array
|
||||||
|
while IFS= read -r file; do
|
||||||
|
[[ -z "$file" ]] && continue
|
||||||
|
all_files+=("$file")
|
||||||
|
done <<< "$sorted_paths"
|
||||||
|
|
||||||
|
# Process each installer
|
||||||
|
for file in "${all_files[@]}"; do
|
||||||
|
# Calculate file size
|
||||||
|
local file_size=0
|
||||||
|
if [[ -f "$file" ]]; then
|
||||||
|
file_size=$(get_file_size "$file")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Store installer path and size in parallel arrays
|
||||||
|
INSTALLER_PATHS+=("$file")
|
||||||
|
INSTALLER_SIZES+=("$file_size")
|
||||||
|
DISPLAY_NAMES+=("$(basename "$file")")
|
||||||
|
done
|
||||||
|
|
||||||
|
echo -e " ${GREEN}Found ${#INSTALLER_PATHS[@]} installer(s)${NC}"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Show menu for user selection
|
||||||
|
show_installer_menu() {
|
||||||
|
if [[ ${#DISPLAY_NAMES[@]} -eq 0 ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
local title="Select installers to remove"
|
||||||
|
MOLE_SELECTION_RESULT=""
|
||||||
|
paginated_multi_select "$title" "${DISPLAY_NAMES[@]}"
|
||||||
|
local selection_exit=$?
|
||||||
|
|
||||||
|
if [[ $selection_exit -ne 0 ]]; then
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Cancelled${NC}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Delete selected installers
|
||||||
|
delete_selected_installers() {
|
||||||
|
# Parse selection indices
|
||||||
|
local -a selected_indices=()
|
||||||
|
[[ -n "$MOLE_SELECTION_RESULT" ]] && IFS=',' read -ra selected_indices <<<"$MOLE_SELECTION_RESULT"
|
||||||
|
|
||||||
|
if [[ ${#selected_indices[@]} -eq 0 ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '\n'
|
||||||
|
echo -e "${BLUE}━━━ Removing installers ━━━${NC}"
|
||||||
|
|
||||||
|
# Delete each selected installer
|
||||||
|
total_deleted=0
|
||||||
|
total_size_freed_kb=0
|
||||||
|
for idx in "${selected_indices[@]}"; do
|
||||||
|
if [[ ! "$idx" =~ ^[0-9]+$ ]] || [[ $idx -ge ${#INSTALLER_PATHS[@]} ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
local file_path="${INSTALLER_PATHS[$idx]}"
|
||||||
|
local file_size="${INSTALLER_SIZES[$idx]}"
|
||||||
|
|
||||||
|
# Validate path before deletion
|
||||||
|
if ! validate_path_for_deletion "$file_path"; then
|
||||||
|
echo -e " ${RED}${ICON_FAILED}${NC} Cannot delete (invalid path): $(basename "$file_path")"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Delete the file
|
||||||
|
if safe_remove "$file_path" true; then
|
||||||
|
local human_size
|
||||||
|
human_size=$(bytes_to_human "$file_size")
|
||||||
|
total_size_freed_kb=$((total_size_freed_kb + ((file_size + 1023) / 1024)))
|
||||||
|
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Deleted: $(basename "$file_path") ${GRAY}($human_size)${NC}"
|
||||||
|
total_deleted=$((total_deleted + 1))
|
||||||
|
else
|
||||||
|
echo -e " ${RED}${ICON_FAILED}${NC} Failed to delete: $(basename "$file_path")"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Perform the installers cleanup
|
||||||
|
perform_installers() {
|
||||||
|
# Collect installers
|
||||||
|
if ! collect_installers; then
|
||||||
|
return 2 # Nothing to clean
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show menu
|
||||||
|
if ! show_installer_menu; then
|
||||||
|
return 1 # User cancelled
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Delete selected
|
||||||
|
delete_selected_installers
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
show_summary() {
|
||||||
|
echo ""
|
||||||
|
local summary_heading="Cleanup complete"
|
||||||
|
local -a summary_details=()
|
||||||
|
|
||||||
|
if [[ $total_deleted -gt 0 ]]; then
|
||||||
|
local freed_mb
|
||||||
|
freed_mb=$(echo "$total_size_freed_kb" | awk '{printf "%.2f", $1/1024}')
|
||||||
|
|
||||||
|
summary_details+=("Installers removed: $total_deleted")
|
||||||
|
summary_details+=("Space freed: ${GREEN}${freed_mb}MB${NC}")
|
||||||
|
summary_details+=("Free space now: $(get_free_space)")
|
||||||
|
else
|
||||||
|
summary_details+=("No installers were removed")
|
||||||
|
summary_details+=("Free space now: $(get_free_space)")
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_summary_block "$summary_heading" "${summary_details[@]}"
|
||||||
|
printf '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
show_help() {
|
||||||
|
echo -e "${PURPLE_BOLD}Mole Installers${NC} - Find and remove installer files"
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Usage:${NC} mo installers [options]"
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Options:${NC}"
|
||||||
|
echo " --debug Enable debug logging"
|
||||||
|
echo " --help Show this help message"
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Default Paths${NC}"
|
||||||
|
echo " - ${HOME}/Downloads"
|
||||||
|
echo " - ${HOME}/Desktop"
|
||||||
|
echo " - ${HOME}/Documents"
|
||||||
|
echo " - ${HOME}/Public"
|
||||||
|
echo " - ${HOME}/Library/Downloads"
|
||||||
|
echo " - /Users/Shared"
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
"--help")
|
||||||
|
show_help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
"--debug")
|
||||||
|
export MO_DEBUG=1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $arg"
|
||||||
|
echo "Use 'mo installers --help' for usage information"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Clear screen for better UX
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
printf '\033[2J\033[H'
|
||||||
|
fi
|
||||||
|
printf '\n'
|
||||||
|
echo -e "${PURPLE_BOLD}Find & Remove Installers${NC}"
|
||||||
|
|
||||||
|
hide_cursor
|
||||||
|
perform_installers
|
||||||
|
local exit_code=$?
|
||||||
|
show_cursor
|
||||||
|
|
||||||
|
case $exit_code in
|
||||||
|
0)
|
||||||
|
show_summary
|
||||||
|
;;
|
||||||
|
1)
|
||||||
|
printf '\n'
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
printf '\n'
|
||||||
|
echo -e "${YELLOW}No installer files found in default locations${NC}"
|
||||||
|
printf '\n'
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Only run main if not in test mode
|
||||||
|
if [[ "${MOLE_TEST_MODE:-0}" != "1" ]]; then
|
||||||
|
main "$@"
|
||||||
|
fi
|
||||||
@@ -8,6 +8,7 @@ MOLE_COMMANDS=(
|
|||||||
"analyze:Explore disk usage"
|
"analyze:Explore disk usage"
|
||||||
"status:Monitor system health"
|
"status:Monitor system health"
|
||||||
"purge:Remove old project artifacts"
|
"purge:Remove old project artifacts"
|
||||||
|
"installers:Find and remove installer files"
|
||||||
"touchid:Configure Touch ID for sudo"
|
"touchid:Configure Touch ID for sudo"
|
||||||
"completion:Setup shell tab completion"
|
"completion:Setup shell tab completion"
|
||||||
"update:Update to latest version"
|
"update:Update to latest version"
|
||||||
|
|||||||
4
mole
4
mole
@@ -222,6 +222,7 @@ show_help() {
|
|||||||
printf " %s%-28s%s %s\n" "$GREEN" "mo optimize --dry-run" "$NC" "Preview optimization"
|
printf " %s%-28s%s %s\n" "$GREEN" "mo optimize --dry-run" "$NC" "Preview optimization"
|
||||||
printf " %s%-28s%s %s\n" "$GREEN" "mo optimize --whitelist" "$NC" "Manage protected items"
|
printf " %s%-28s%s %s\n" "$GREEN" "mo optimize --whitelist" "$NC" "Manage protected items"
|
||||||
printf " %s%-28s%s %s\n" "$GREEN" "mo purge --paths" "$NC" "Configure scan directories"
|
printf " %s%-28s%s %s\n" "$GREEN" "mo purge --paths" "$NC" "Configure scan directories"
|
||||||
|
printf " %s%-28s%s %s\n" "$GREEN" "mo installers --debug" "$NC" "Find installer files"
|
||||||
echo
|
echo
|
||||||
printf "%s%s%s\n" "$BLUE" "OPTIONS" "$NC"
|
printf "%s%s%s\n" "$BLUE" "OPTIONS" "$NC"
|
||||||
printf " %s%-28s%s %s\n" "$GREEN" "--debug" "$NC" "Show detailed operation logs"
|
printf " %s%-28s%s %s\n" "$GREEN" "--debug" "$NC" "Show detailed operation logs"
|
||||||
@@ -748,6 +749,9 @@ main() {
|
|||||||
"purge")
|
"purge")
|
||||||
exec "$SCRIPT_DIR/bin/purge.sh" "${args[@]:1}"
|
exec "$SCRIPT_DIR/bin/purge.sh" "${args[@]:1}"
|
||||||
;;
|
;;
|
||||||
|
"installers")
|
||||||
|
exec "$SCRIPT_DIR/bin/installers.sh" "${args[@]:1}"
|
||||||
|
;;
|
||||||
"touchid")
|
"touchid")
|
||||||
exec "$SCRIPT_DIR/bin/touchid.sh" "${args[@]:1}"
|
exec "$SCRIPT_DIR/bin/touchid.sh" "${args[@]:1}"
|
||||||
;;
|
;;
|
||||||
|
|||||||
669
tests/installers.bats
Normal file
669
tests/installers.bats
Normal file
@@ -0,0 +1,669 @@
|
|||||||
|
#!/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-installers-home.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"
|
||||||
|
export MO_DEBUG=0
|
||||||
|
|
||||||
|
# Create standard scan directories
|
||||||
|
mkdir -p "$HOME/Downloads"
|
||||||
|
mkdir -p "$HOME/Desktop"
|
||||||
|
mkdir -p "$HOME/Documents"
|
||||||
|
mkdir -p "$HOME/Public"
|
||||||
|
mkdir -p "$HOME/Library/Downloads"
|
||||||
|
|
||||||
|
# Clear previous test files
|
||||||
|
rm -rf "${HOME:?}/Downloads"/*
|
||||||
|
rm -rf "${HOME:?}/Desktop"/*
|
||||||
|
rm -rf "${HOME:?}/Documents"/*
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test help and arguments
|
||||||
|
|
||||||
|
@test "installers.sh --help shows usage information" {
|
||||||
|
run "$PROJECT_ROOT/bin/installers.sh" --help
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"Mole Installers"* ]]
|
||||||
|
[[ "$output" == *"Usage:"* ]]
|
||||||
|
[[ "$output" == *"mo installers"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "installers.sh --help lists options and paths" {
|
||||||
|
run "$PROJECT_ROOT/bin/installers.sh" --help
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"--debug"* ]]
|
||||||
|
[[ "$output" == *"--help"* ]]
|
||||||
|
[[ "$output" == *"mo installers"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "installers.sh --help shows scan scope" {
|
||||||
|
run "$PROJECT_ROOT/bin/installers.sh" --help
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"Downloads"* ]]
|
||||||
|
[[ "$output" == *"Desktop"* ]]
|
||||||
|
[[ "$output" == *"Documents"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "installers.sh rejects unknown options" {
|
||||||
|
run "$PROJECT_ROOT/bin/installers.sh" --unknown-option
|
||||||
|
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
[[ "$output" == *"Unknown option"* ]]
|
||||||
|
[[ "$output" == *"--help"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test scan_installers_in_path function directly
|
||||||
|
# Tests are duplicated to cover both fd and find code paths
|
||||||
|
|
||||||
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
# Tests using fd (when available)
|
||||||
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
@test "scan_installers_in_path (fd): finds .dmg files" {
|
||||||
|
if ! command -v fd > /dev/null 2>&1; then
|
||||||
|
skip "fd not available on this system"
|
||||||
|
fi
|
||||||
|
|
||||||
|
touch "$HOME/Downloads/Chrome.dmg"
|
||||||
|
|
||||||
|
run bash -euo pipefail -c '
|
||||||
|
export MOLE_TEST_MODE=1
|
||||||
|
source "$1"
|
||||||
|
scan_installers_in_path "$2"
|
||||||
|
' bash "$PROJECT_ROOT/bin/installers.sh" "$HOME/Downloads"
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"Chrome.dmg"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "scan_installers_in_path (fd): finds multiple installer types" {
|
||||||
|
if ! command -v fd > /dev/null 2>&1; then
|
||||||
|
skip "fd not available on this system"
|
||||||
|
fi
|
||||||
|
|
||||||
|
touch "$HOME/Downloads/App1.dmg"
|
||||||
|
touch "$HOME/Downloads/App2.pkg"
|
||||||
|
touch "$HOME/Downloads/App3.iso"
|
||||||
|
touch "$HOME/Downloads/App.mpkg"
|
||||||
|
|
||||||
|
run bash -euo pipefail -c '
|
||||||
|
export MOLE_TEST_MODE=1
|
||||||
|
source "$1"
|
||||||
|
scan_installers_in_path "$2"
|
||||||
|
' bash "$PROJECT_ROOT/bin/installers.sh" "$HOME/Downloads"
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"App1.dmg"* ]]
|
||||||
|
[[ "$output" == *"App2.pkg"* ]]
|
||||||
|
[[ "$output" == *"App3.iso"* ]]
|
||||||
|
[[ "$output" == *"App.mpkg"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "scan_installers_in_path (fd): respects max depth" {
|
||||||
|
if ! command -v fd > /dev/null 2>&1; then
|
||||||
|
skip "fd not available on this system"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$HOME/Downloads/level1/level2/level3"
|
||||||
|
touch "$HOME/Downloads/shallow.dmg"
|
||||||
|
touch "$HOME/Downloads/level1/mid.dmg"
|
||||||
|
touch "$HOME/Downloads/level1/level2/deep.dmg"
|
||||||
|
touch "$HOME/Downloads/level1/level2/level3/too-deep.dmg"
|
||||||
|
|
||||||
|
run bash -euo pipefail -c '
|
||||||
|
export MOLE_TEST_MODE=1
|
||||||
|
source "$1"
|
||||||
|
scan_installers_in_path "$2"
|
||||||
|
' bash "$PROJECT_ROOT/bin/installers.sh" "$HOME/Downloads"
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
# Default max depth is 2
|
||||||
|
[[ "$output" == *"shallow.dmg"* ]]
|
||||||
|
[[ "$output" == *"mid.dmg"* ]]
|
||||||
|
[[ "$output" == *"deep.dmg"* ]]
|
||||||
|
[[ "$output" != *"too-deep.dmg"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "scan_installers_in_path (fd): honors MOLE_INSTALLER_SCAN_MAX_DEPTH" {
|
||||||
|
if ! command -v fd > /dev/null 2>&1; then
|
||||||
|
skip "fd not available on this system"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$HOME/Downloads/level1"
|
||||||
|
touch "$HOME/Downloads/top.dmg"
|
||||||
|
touch "$HOME/Downloads/level1/nested.dmg"
|
||||||
|
|
||||||
|
run env MOLE_INSTALLER_SCAN_MAX_DEPTH=1 bash -euo pipefail -c "
|
||||||
|
export MOLE_TEST_MODE=1
|
||||||
|
source \"\$1\"
|
||||||
|
scan_installers_in_path \"\$2\"
|
||||||
|
" bash "$PROJECT_ROOT/bin/installers.sh" "$HOME/Downloads"
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"top.dmg"* ]]
|
||||||
|
[[ "$output" != *"nested.dmg"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "scan_installers_in_path (fd): handles non-existent directory" {
|
||||||
|
if ! command -v fd > /dev/null 2>&1; then
|
||||||
|
skip "fd not available on this system"
|
||||||
|
fi
|
||||||
|
|
||||||
|
run bash -euo pipefail -c '
|
||||||
|
export MOLE_TEST_MODE=1
|
||||||
|
source "$1"
|
||||||
|
scan_installers_in_path "$2"
|
||||||
|
' bash "$PROJECT_ROOT/bin/installers.sh" "$HOME/NonExistent"
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ -z "$output" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "scan_installers_in_path (fd): ignores non-installer files" {
|
||||||
|
if ! command -v fd > /dev/null 2>&1; then
|
||||||
|
skip "fd not available on this system"
|
||||||
|
fi
|
||||||
|
|
||||||
|
touch "$HOME/Downloads/document.pdf"
|
||||||
|
touch "$HOME/Downloads/image.jpg"
|
||||||
|
touch "$HOME/Downloads/archive.tar.gz"
|
||||||
|
touch "$HOME/Downloads/Installer.dmg"
|
||||||
|
|
||||||
|
run bash -euo pipefail -c '
|
||||||
|
export MOLE_TEST_MODE=1
|
||||||
|
source "$1"
|
||||||
|
scan_installers_in_path "$2"
|
||||||
|
' bash "$PROJECT_ROOT/bin/installers.sh" "$HOME/Downloads"
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" != *"document.pdf"* ]]
|
||||||
|
[[ "$output" != *"image.jpg"* ]]
|
||||||
|
[[ "$output" != *"archive.tar.gz"* ]]
|
||||||
|
[[ "$output" == *"Installer.dmg"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "scan_installers_in_path (fd): handles filenames with spaces" {
|
||||||
|
if ! command -v fd > /dev/null 2>&1; then
|
||||||
|
skip "fd not available on this system"
|
||||||
|
fi
|
||||||
|
|
||||||
|
touch "$HOME/Downloads/My App Installer.dmg"
|
||||||
|
|
||||||
|
run bash -euo pipefail -c '
|
||||||
|
export MOLE_TEST_MODE=1
|
||||||
|
source "$1"
|
||||||
|
scan_installers_in_path "$2"
|
||||||
|
' bash "$PROJECT_ROOT/bin/installers.sh" "$HOME/Downloads"
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"My App Installer.dmg"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "scan_installers_in_path (fd): handles filenames with special characters" {
|
||||||
|
if ! command -v fd > /dev/null 2>&1; then
|
||||||
|
skip "fd not available on this system"
|
||||||
|
fi
|
||||||
|
|
||||||
|
touch "$HOME/Downloads/App-v1.2.3_beta.pkg"
|
||||||
|
|
||||||
|
run bash -euo pipefail -c '
|
||||||
|
export MOLE_TEST_MODE=1
|
||||||
|
source "$1"
|
||||||
|
scan_installers_in_path "$2"
|
||||||
|
' bash "$PROJECT_ROOT/bin/installers.sh" "$HOME/Downloads"
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"App-v1.2.3_beta.pkg"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "scan_installers_in_path (fd): returns empty for directory with no installers" {
|
||||||
|
if ! command -v fd > /dev/null 2>&1; then
|
||||||
|
skip "fd not available on this system"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create some non-installer files
|
||||||
|
touch "$HOME/Downloads/document.pdf"
|
||||||
|
touch "$HOME/Downloads/image.png"
|
||||||
|
|
||||||
|
run bash -euo pipefail -c '
|
||||||
|
export MOLE_TEST_MODE=1
|
||||||
|
source "$1"
|
||||||
|
scan_installers_in_path "$2"
|
||||||
|
' bash "$PROJECT_ROOT/bin/installers.sh" "$HOME/Downloads"
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ -z "$output" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
# Tests using find (forced fallback by hiding fd)
|
||||||
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
@test "scan_installers_in_path (fallback find): finds .dmg files" {
|
||||||
|
touch "$HOME/Downloads/Chrome.dmg"
|
||||||
|
|
||||||
|
run env PATH="/usr/bin:/bin" bash -euo pipefail -c "
|
||||||
|
export MOLE_TEST_MODE=1
|
||||||
|
source \"\$1\"
|
||||||
|
scan_installers_in_path \"\$2\"
|
||||||
|
" bash "$PROJECT_ROOT/bin/installers.sh" "$HOME/Downloads"
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"Chrome.dmg"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "scan_installers_in_path (fallback find): finds multiple installer types" {
|
||||||
|
touch "$HOME/Downloads/App1.dmg"
|
||||||
|
touch "$HOME/Downloads/App2.pkg"
|
||||||
|
touch "$HOME/Downloads/App3.iso"
|
||||||
|
touch "$HOME/Downloads/App.mpkg"
|
||||||
|
|
||||||
|
run env PATH="/usr/bin:/bin" bash -euo pipefail -c "
|
||||||
|
export MOLE_TEST_MODE=1
|
||||||
|
source \"\$1\"
|
||||||
|
scan_installers_in_path \"\$2\"
|
||||||
|
" bash "$PROJECT_ROOT/bin/installers.sh" "$HOME/Downloads"
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"App1.dmg"* ]]
|
||||||
|
[[ "$output" == *"App2.pkg"* ]]
|
||||||
|
[[ "$output" == *"App3.iso"* ]]
|
||||||
|
[[ "$output" == *"App.mpkg"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "scan_installers_in_path (fallback find): respects max depth" {
|
||||||
|
mkdir -p "$HOME/Downloads/level1/level2/level3"
|
||||||
|
touch "$HOME/Downloads/shallow.dmg"
|
||||||
|
touch "$HOME/Downloads/level1/mid.dmg"
|
||||||
|
touch "$HOME/Downloads/level1/level2/deep.dmg"
|
||||||
|
touch "$HOME/Downloads/level1/level2/level3/too-deep.dmg"
|
||||||
|
|
||||||
|
run env PATH="/usr/bin:/bin" bash -euo pipefail -c "
|
||||||
|
export MOLE_TEST_MODE=1
|
||||||
|
source \"\$1\"
|
||||||
|
scan_installers_in_path \"\$2\"
|
||||||
|
" bash "$PROJECT_ROOT/bin/installers.sh" "$HOME/Downloads"
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
# Default max depth is 2
|
||||||
|
[[ "$output" == *"shallow.dmg"* ]]
|
||||||
|
[[ "$output" == *"mid.dmg"* ]]
|
||||||
|
[[ "$output" == *"deep.dmg"* ]]
|
||||||
|
[[ "$output" != *"too-deep.dmg"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "scan_installers_in_path (fallback find): honors MOLE_INSTALLER_SCAN_MAX_DEPTH" {
|
||||||
|
mkdir -p "$HOME/Downloads/level1"
|
||||||
|
touch "$HOME/Downloads/top.dmg"
|
||||||
|
touch "$HOME/Downloads/level1/nested.dmg"
|
||||||
|
|
||||||
|
run env PATH="/usr/bin:/bin" MOLE_INSTALLER_SCAN_MAX_DEPTH=1 bash -euo pipefail -c "
|
||||||
|
export MOLE_TEST_MODE=1
|
||||||
|
source \"\$1\"
|
||||||
|
scan_installers_in_path \"\$2\"
|
||||||
|
" bash "$PROJECT_ROOT/bin/installers.sh" "$HOME/Downloads"
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"top.dmg"* ]]
|
||||||
|
[[ "$output" != *"nested.dmg"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "scan_installers_in_path (fallback find): handles non-existent directory" {
|
||||||
|
run env PATH="/usr/bin:/bin" bash -euo pipefail -c "
|
||||||
|
export MOLE_TEST_MODE=1
|
||||||
|
source \"\$1\"
|
||||||
|
scan_installers_in_path \"\$2\"
|
||||||
|
" bash "$PROJECT_ROOT/bin/installers.sh" "$HOME/NonExistent"
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ -z "$output" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "scan_installers_in_path (fallback find): ignores non-installer files" {
|
||||||
|
touch "$HOME/Downloads/document.pdf"
|
||||||
|
touch "$HOME/Downloads/image.jpg"
|
||||||
|
touch "$HOME/Downloads/archive.tar.gz"
|
||||||
|
touch "$HOME/Downloads/Installer.dmg"
|
||||||
|
|
||||||
|
run env PATH="/usr/bin:/bin" bash -euo pipefail -c "
|
||||||
|
export MOLE_TEST_MODE=1
|
||||||
|
source \"\$1\"
|
||||||
|
scan_installers_in_path \"\$2\"
|
||||||
|
" bash "$PROJECT_ROOT/bin/installers.sh" "$HOME/Downloads"
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" != *"document.pdf"* ]]
|
||||||
|
[[ "$output" != *"image.jpg"* ]]
|
||||||
|
[[ "$output" != *"archive.tar.gz"* ]]
|
||||||
|
[[ "$output" == *"Installer.dmg"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test ZIP installer detection
|
||||||
|
|
||||||
|
@test "is_installer_zip: rejects ZIP with installer content but too many entries" {
|
||||||
|
if ! command -v zipinfo > /dev/null 2>&1; then
|
||||||
|
skip "zipinfo not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create a ZIP with too many files (exceeds MAX_ZIP_ENTRIES=5)
|
||||||
|
# Include a .app file to have installer content
|
||||||
|
mkdir -p "$HOME/Downloads/large-app"
|
||||||
|
touch "$HOME/Downloads/large-app/MyApp.app"
|
||||||
|
for i in {1..9}; do
|
||||||
|
touch "$HOME/Downloads/large-app/file$i.txt"
|
||||||
|
done
|
||||||
|
(cd "$HOME/Downloads" && zip -q -r large-installer.zip large-app)
|
||||||
|
|
||||||
|
run bash -euo pipefail -c '
|
||||||
|
export MOLE_TEST_MODE=1
|
||||||
|
source "$1"
|
||||||
|
if is_installer_zip "'"$HOME/Downloads/large-installer.zip"'"; then
|
||||||
|
echo "INSTALLER"
|
||||||
|
else
|
||||||
|
echo "NOT_INSTALLER"
|
||||||
|
fi
|
||||||
|
' bash "$PROJECT_ROOT/bin/installers.sh"
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == "NOT_INSTALLER" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "is_installer_zip: detects ZIP with app content" {
|
||||||
|
if ! command -v zipinfo > /dev/null 2>&1; then
|
||||||
|
skip "zipinfo not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$HOME/Downloads/app-content"
|
||||||
|
touch "$HOME/Downloads/app-content/MyApp.app"
|
||||||
|
(cd "$HOME/Downloads" && zip -q -r app.zip app-content)
|
||||||
|
|
||||||
|
run bash -euo pipefail -c '
|
||||||
|
export MOLE_TEST_MODE=1
|
||||||
|
source "$1"
|
||||||
|
if is_installer_zip "'"$HOME/Downloads/app.zip"'"; then
|
||||||
|
echo "INSTALLER"
|
||||||
|
else
|
||||||
|
echo "NOT_INSTALLER"
|
||||||
|
fi
|
||||||
|
' bash "$PROJECT_ROOT/bin/installers.sh"
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == "INSTALLER" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "is_installer_zip: rejects ZIP with only regular files" {
|
||||||
|
if ! command -v zipinfo > /dev/null 2>&1; then
|
||||||
|
skip "zipinfo not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$HOME/Downloads/data"
|
||||||
|
touch "$HOME/Downloads/data/file1.txt"
|
||||||
|
touch "$HOME/Downloads/data/file2.pdf"
|
||||||
|
(cd "$HOME/Downloads" && zip -q -r data.zip data)
|
||||||
|
|
||||||
|
run bash -euo pipefail -c '
|
||||||
|
export MOLE_TEST_MODE=1
|
||||||
|
source "$1"
|
||||||
|
if is_installer_zip "'"$HOME/Downloads/data.zip"'"; then
|
||||||
|
echo "INSTALLER"
|
||||||
|
else
|
||||||
|
echo "NOT_INSTALLER"
|
||||||
|
fi
|
||||||
|
' bash "$PROJECT_ROOT/bin/installers.sh"
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == "NOT_INSTALLER" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Integration tests: ZIP scanning inside scan_all_installers
|
||||||
|
|
||||||
|
@test "scan_all_installers: finds installer ZIP in Downloads" {
|
||||||
|
if ! command -v zipinfo > /dev/null 2>&1; then
|
||||||
|
skip "zipinfo not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create a valid installer ZIP (contains .app)
|
||||||
|
mkdir -p "$HOME/Downloads/app-content"
|
||||||
|
touch "$HOME/Downloads/app-content/MyApp.app"
|
||||||
|
(cd "$HOME/Downloads" && zip -q -r installer.zip app-content)
|
||||||
|
|
||||||
|
run bash -euo pipefail -c '
|
||||||
|
export MOLE_TEST_MODE=1
|
||||||
|
source "$1"
|
||||||
|
scan_all_installers
|
||||||
|
' bash "$PROJECT_ROOT/bin/installers.sh"
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"installer.zip"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "scan_all_installers: ignores non-installer ZIP in Downloads" {
|
||||||
|
if ! command -v zipinfo > /dev/null 2>&1; then
|
||||||
|
skip "zipinfo not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create a non-installer ZIP (only regular files)
|
||||||
|
mkdir -p "$HOME/Downloads/data"
|
||||||
|
touch "$HOME/Downloads/data/file1.txt"
|
||||||
|
touch "$HOME/Downloads/data/file2.pdf"
|
||||||
|
(cd "$HOME/Downloads" && zip -q -r data.zip data)
|
||||||
|
|
||||||
|
run bash -euo pipefail -c '
|
||||||
|
export MOLE_TEST_MODE=1
|
||||||
|
source "$1"
|
||||||
|
scan_all_installers
|
||||||
|
' bash "$PROJECT_ROOT/bin/installers.sh"
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" != *"data.zip"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "scan_all_installers: handles missing paths gracefully" {
|
||||||
|
# Don't create all scan directories, some may not exist
|
||||||
|
# Only create Downloads, delete others if they exist
|
||||||
|
rm -rf "$HOME/Desktop"
|
||||||
|
rm -rf "$HOME/Documents"
|
||||||
|
rm -rf "$HOME/Public"
|
||||||
|
rm -rf "$HOME/Public/Downloads"
|
||||||
|
rm -rf "$HOME/Library/Downloads"
|
||||||
|
mkdir -p "$HOME/Downloads"
|
||||||
|
|
||||||
|
# Add an installer to the one directory that exists
|
||||||
|
touch "$HOME/Downloads/test.dmg"
|
||||||
|
|
||||||
|
run bash -euo pipefail -c '
|
||||||
|
export MOLE_TEST_MODE=1
|
||||||
|
source "$1"
|
||||||
|
scan_all_installers
|
||||||
|
' bash "$PROJECT_ROOT/bin/installers.sh"
|
||||||
|
|
||||||
|
# Should succeed even with missing paths
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
# Should still find the installer in the existing directory
|
||||||
|
[[ "$output" == *"test.dmg"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test edge cases
|
||||||
|
|
||||||
|
@test "scan_installers_in_path (fallback find): handles filenames with spaces" {
|
||||||
|
touch "$HOME/Downloads/My App Installer.dmg"
|
||||||
|
|
||||||
|
run env PATH="/usr/bin:/bin" bash -euo pipefail -c "
|
||||||
|
export MOLE_TEST_MODE=1
|
||||||
|
source \"\$1\"
|
||||||
|
scan_installers_in_path \"\$2\"
|
||||||
|
" bash "$PROJECT_ROOT/bin/installers.sh" "$HOME/Downloads"
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"My App Installer.dmg"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "scan_installers_in_path (fallback find): handles filenames with special characters" {
|
||||||
|
touch "$HOME/Downloads/App-v1.2.3_beta.pkg"
|
||||||
|
|
||||||
|
run env PATH="/usr/bin:/bin" bash -euo pipefail -c "
|
||||||
|
export MOLE_TEST_MODE=1
|
||||||
|
source \"\$1\"
|
||||||
|
scan_installers_in_path \"\$2\"
|
||||||
|
" bash "$PROJECT_ROOT/bin/installers.sh" "$HOME/Downloads"
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"App-v1.2.3_beta.pkg"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "scan_installers_in_path (fallback find): returns empty for directory with no installers" {
|
||||||
|
# Create some non-installer files
|
||||||
|
touch "$HOME/Downloads/document.pdf"
|
||||||
|
touch "$HOME/Downloads/image.png"
|
||||||
|
|
||||||
|
run env PATH="/usr/bin:/bin" bash -euo pipefail -c "
|
||||||
|
export MOLE_TEST_MODE=1
|
||||||
|
source \"\$1\"
|
||||||
|
scan_installers_in_path \"\$2\"
|
||||||
|
" bash "$PROJECT_ROOT/bin/installers.sh" "$HOME/Downloads"
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ -z "$output" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Failure path tests for scan_installers_in_path
|
||||||
|
|
||||||
|
@test "scan_installers_in_path: skips corrupt ZIP files" {
|
||||||
|
if ! command -v zipinfo > /dev/null 2>&1; then
|
||||||
|
skip "zipinfo not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create a corrupt ZIP file by just writing garbage data
|
||||||
|
echo "This is not a valid ZIP file" > "$HOME/Downloads/corrupt.zip"
|
||||||
|
|
||||||
|
run bash -euo pipefail -c '
|
||||||
|
export MOLE_TEST_MODE=1
|
||||||
|
source "$1"
|
||||||
|
scan_installers_in_path "$2"
|
||||||
|
' bash "$PROJECT_ROOT/bin/installers.sh" "$HOME/Downloads"
|
||||||
|
|
||||||
|
# Should succeed (return 0) and silently skip the corrupt ZIP
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
# Output should be empty since corrupt.zip is not a valid installer
|
||||||
|
[[ -z "$output" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "scan_installers_in_path: handles permission-denied files" {
|
||||||
|
if ! command -v zipinfo > /dev/null 2>&1; then
|
||||||
|
skip "zipinfo not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create a valid installer ZIP
|
||||||
|
mkdir -p "$HOME/Downloads/app-content"
|
||||||
|
touch "$HOME/Downloads/app-content/MyApp.app"
|
||||||
|
(cd "$HOME/Downloads" && zip -q -r readable.zip app-content)
|
||||||
|
|
||||||
|
# Create a readable installer ZIP alongside a permission-denied file
|
||||||
|
mkdir -p "$HOME/Downloads/restricted-app"
|
||||||
|
touch "$HOME/Downloads/restricted-app/App.app"
|
||||||
|
(cd "$HOME/Downloads" && zip -q -r restricted.zip restricted-app)
|
||||||
|
|
||||||
|
# Remove read permissions from restricted.zip
|
||||||
|
chmod 000 "$HOME/Downloads/restricted.zip"
|
||||||
|
|
||||||
|
run bash -euo pipefail -c '
|
||||||
|
export MOLE_TEST_MODE=1
|
||||||
|
source "$1"
|
||||||
|
scan_installers_in_path "$2"
|
||||||
|
' bash "$PROJECT_ROOT/bin/installers.sh" "$HOME/Downloads"
|
||||||
|
|
||||||
|
# Should succeed and find the readable.zip but skip restricted.zip
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"readable.zip"* ]]
|
||||||
|
[[ "$output" != *"restricted.zip"* ]]
|
||||||
|
|
||||||
|
# Cleanup: restore permissions for teardown
|
||||||
|
chmod 644 "$HOME/Downloads/restricted.zip"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "scan_installers_in_path: finds installer ZIP alongside corrupt ZIPs" {
|
||||||
|
if ! command -v zipinfo > /dev/null 2>&1; then
|
||||||
|
skip "zipinfo not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create a valid installer ZIP
|
||||||
|
mkdir -p "$HOME/Downloads/app-content"
|
||||||
|
touch "$HOME/Downloads/app-content/MyApp.app"
|
||||||
|
(cd "$HOME/Downloads" && zip -q -r valid-installer.zip app-content)
|
||||||
|
|
||||||
|
# Create a corrupt ZIP
|
||||||
|
echo "garbage data" > "$HOME/Downloads/corrupt.zip"
|
||||||
|
|
||||||
|
run bash -euo pipefail -c '
|
||||||
|
export MOLE_TEST_MODE=1
|
||||||
|
source "$1"
|
||||||
|
scan_installers_in_path "$2"
|
||||||
|
' bash "$PROJECT_ROOT/bin/installers.sh" "$HOME/Downloads"
|
||||||
|
|
||||||
|
# Should find the valid ZIP and silently skip the corrupt one
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"valid-installer.zip"* ]]
|
||||||
|
[[ "$output" != *"corrupt.zip"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Symlink handling tests
|
||||||
|
|
||||||
|
@test "scan_installers_in_path (fd): skips symlinks to regular files" {
|
||||||
|
if ! command -v fd > /dev/null 2>&1; then
|
||||||
|
skip "fd not available on this system"
|
||||||
|
fi
|
||||||
|
|
||||||
|
touch "$HOME/Downloads/real.dmg"
|
||||||
|
ln -s "$HOME/Downloads/real.dmg" "$HOME/Downloads/symlink.dmg"
|
||||||
|
ln -s /nonexistent "$HOME/Downloads/dangling.lnk"
|
||||||
|
|
||||||
|
run bash -euo pipefail -c '
|
||||||
|
export MOLE_TEST_MODE=1
|
||||||
|
source "$1"
|
||||||
|
scan_installers_in_path "$2"
|
||||||
|
' bash "$PROJECT_ROOT/bin/installers.sh" "$HOME/Downloads"
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"real.dmg"* ]]
|
||||||
|
[[ "$output" != *"symlink.dmg"* ]]
|
||||||
|
[[ "$output" != *"dangling.lnk"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "scan_installers_in_path (fallback find): skips symlinks to regular files" {
|
||||||
|
touch "$HOME/Downloads/real.dmg"
|
||||||
|
ln -s "$HOME/Downloads/real.dmg" "$HOME/Downloads/symlink.dmg"
|
||||||
|
ln -s /nonexistent "$HOME/Downloads/dangling.lnk"
|
||||||
|
|
||||||
|
run env PATH="/usr/bin:/bin" bash -euo pipefail -c "
|
||||||
|
export MOLE_TEST_MODE=1
|
||||||
|
source \"\$1\"
|
||||||
|
scan_installers_in_path \"\$2\"
|
||||||
|
" bash "$PROJECT_ROOT/bin/installers.sh" "$HOME/Downloads"
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"real.dmg"* ]]
|
||||||
|
[[ "$output" != *"symlink.dmg"* ]]
|
||||||
|
[[ "$output" != *"dangling.lnk"* ]]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user