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

feat: Add browser version cleanup tests, enhance shell completion with new tests, and introduce core command definitions.

This commit is contained in:
Tw93
2026-01-02 09:55:42 +08:00
parent 9a055ece6c
commit 88f434f01a
6 changed files with 692 additions and 77 deletions

View File

@@ -51,6 +51,7 @@ mo status # Live system health dashboard
mo purge # Clean project build artifacts
mo touchid # Configure Touch ID for sudo
mo completion # Setup shell tab completion
mo update # Update Mole
mo remove # Remove Mole from system
mo --help # Show help
@@ -70,6 +71,7 @@ mo purge --paths # Configure project scan directories
- **Safety**: Built with strict protections. See our [Security Audit](SECURITY_AUDIT.md). Preview changes with `mo clean --dry-run`.
- **Whitelist**: Manage protected paths with `mo clean --whitelist`.
- **Touch ID**: Enable Touch ID for sudo commands by running `mo touchid`.
- **Shell Completion**: Enable tab completion by running `mo completion` (auto-detect and install).
- **Navigation**: Supports standard arrow keys and Vim bindings (`h/j/k/l`).
- **Debug**: View detailed logs by appending the `--debug` flag (e.g., `mo clean --debug`).

View File

@@ -1,20 +1,178 @@
#!/bin/bash
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
source "$ROOT_DIR/lib/core/common.sh"
source "$ROOT_DIR/lib/core/commands.sh"
command_names=()
for entry in "${MOLE_COMMANDS[@]}"; do
command_names+=("${entry%%:*}")
done
command_words="${command_names[*]}"
emit_zsh_subcommands() {
for entry in "${MOLE_COMMANDS[@]}"; do
printf " '%s:%s'\n" "${entry%%:*}" "${entry#*:}"
done
}
emit_fish_completions() {
local cmd="$1"
for entry in "${MOLE_COMMANDS[@]}"; do
local name="${entry%%:*}"
local desc="${entry#*:}"
printf 'complete -c %s -n "__fish_mole_no_subcommand" -a %s -d "%s"\n' "$cmd" "$name" "$desc"
done
printf '\n'
printf 'complete -c %s -n "not __fish_mole_no_subcommand" -a bash -d "generate bash completion" -n "__fish_see_subcommand_path completion"\n' "$cmd"
printf 'complete -c %s -n "not __fish_mole_no_subcommand" -a zsh -d "generate zsh completion" -n "__fish_see_subcommand_path completion"\n' "$cmd"
printf 'complete -c %s -n "not __fish_mole_no_subcommand" -a fish -d "generate fish completion" -n "__fish_see_subcommand_path completion"\n' "$cmd"
}
# Auto-install mode when run without arguments
if [[ $# -eq 0 ]]; then
# Detect current shell
current_shell="${SHELL##*/}"
if [[ -z "$current_shell" ]]; then
current_shell="$(ps -p "$PPID" -o comm= 2>/dev/null | awk '{print $1}')"
fi
completion_name=""
if command -v mole > /dev/null 2>&1; then
completion_name="mole"
elif command -v mo > /dev/null 2>&1; then
completion_name="mo"
fi
case "$current_shell" in
bash)
config_file="${HOME}/.bashrc"
[[ -f "${HOME}/.bash_profile" ]] && config_file="${HOME}/.bash_profile"
completion_line='if output="$('"$completion_name"' completion bash 2>/dev/null)"; then eval "$output"; fi'
;;
zsh)
config_file="${HOME}/.zshrc"
completion_line='if output="$('"$completion_name"' completion zsh 2>/dev/null)"; then eval "$output"; fi'
;;
fish)
config_file="${HOME}/.config/fish/config.fish"
completion_line='set -l output ('"$completion_name"' completion fish 2>/dev/null); and echo "$output" | source'
;;
*)
log_error "Unsupported shell: $current_shell"
echo " mole completion <bash|zsh|fish>"
exit 1
;;
esac
if [[ -z "$completion_name" ]]; then
if [[ -f "$config_file" ]] && grep -Eq "(^# Mole shell completion$|(mole|mo)[[:space:]]+completion)" "$config_file" 2>/dev/null; then
original_mode=""
original_mode="$(stat -f '%Mp%Lp' "$config_file" 2>/dev/null || true)"
temp_file="$(mktemp)"
grep -Ev "(^# Mole shell completion$|(mole|mo)[[:space:]]+completion)" "$config_file" > "$temp_file" || true
mv "$temp_file" "$config_file"
if [[ -n "$original_mode" ]]; then
chmod "$original_mode" "$config_file" 2> /dev/null || true
fi
echo -e "${GREEN}${ICON_SUCCESS}${NC} Removed stale completion entries from $config_file"
echo ""
fi
log_error "mole not found in PATH - install Mole before enabling completion"
exit 1
fi
# Check if already installed and normalize to latest line
if [[ -f "$config_file" ]] && grep -Eq "(mole|mo)[[:space:]]+completion" "$config_file" 2>/dev/null; then
original_mode=""
original_mode="$(stat -f '%Mp%Lp' "$config_file" 2>/dev/null || true)"
temp_file="$(mktemp)"
grep -Ev "(^# Mole shell completion$|(mole|mo)[[:space:]]+completion)" "$config_file" > "$temp_file" || true
mv "$temp_file" "$config_file"
if [[ -n "$original_mode" ]]; then
chmod "$original_mode" "$config_file" 2> /dev/null || true
fi
echo "" >> "$config_file"
echo "# Mole shell completion" >> "$config_file"
echo "$completion_line" >> "$config_file"
echo ""
echo -e "${GREEN}${ICON_SUCCESS}${NC} Shell completion updated in $config_file"
echo ""
exit 0
fi
# Prompt user for installation
echo ""
echo -e "${GRAY}Will add to ${config_file}:${NC}"
echo " $completion_line"
echo ""
echo -ne "${PURPLE}${ICON_ARROW}${NC} Enable completion for ${GREEN}${current_shell}${NC}? ${GRAY}Enter confirm / Q cancel${NC}: "
IFS= read -r -s -n1 key || key=""
drain_pending_input
echo ""
case "$key" in
$'\e' | [Qq] | [Nn])
echo -e "${YELLOW}Cancelled${NC}"
exit 0
;;
"" | $'\n' | $'\r' | [Yy])
;;
*)
log_error "Invalid key"
exit 1
;;
esac
# Create config file if it doesn't exist
if [[ ! -f "$config_file" ]]; then
mkdir -p "$(dirname "$config_file")"
touch "$config_file"
fi
# Remove previous Mole completion lines to avoid duplicates
if [[ -f "$config_file" ]]; then
original_mode=""
original_mode="$(stat -f '%Mp%Lp' "$config_file" 2>/dev/null || true)"
temp_file="$(mktemp)"
grep -Ev "(^# Mole shell completion$|(mole|mo)[[:space:]]+completion)" "$config_file" > "$temp_file" || true
mv "$temp_file" "$config_file"
if [[ -n "$original_mode" ]]; then
chmod "$original_mode" "$config_file" 2> /dev/null || true
fi
fi
# Add completion line
echo "" >> "$config_file"
echo "# Mole shell completion" >> "$config_file"
echo "$completion_line" >> "$config_file"
echo -e "${GREEN}${ICON_SUCCESS}${NC} Completion added to $config_file"
echo ""
echo ""
echo -e "${GRAY}To activate now:${NC}"
echo -e " ${GREEN}source $config_file${NC}"
exit 0
fi
case "$1" in
bash)
cat << 'EOF'
cat << EOF
_mole_completions()
{
local cur_word prev_word
cur_word="${COMP_WORDS[COMP_CWORD]}"
prev_word="${COMP_WORDS[COMP_CWORD-1]}"
cur_word="\${COMP_WORDS[\$COMP_CWORD]}"
prev_word="\${COMP_WORDS[\$COMP_CWORD-1]}"
if [ "$COMP_CWORD" -eq 1 ]; then
COMPREPLY=( $(compgen -W "optimize clean uninstall analyze status purge touchid update remove help version completion" -- "$cur_word") )
if [ "\$COMP_CWORD" -eq 1 ]; then
COMPREPLY=( \$(compgen -W "$command_words" -- "\$cur_word") )
else
case "$prev_word" in
case "\$prev_word" in
completion)
COMPREPLY=( $(compgen -W "bash zsh fish" -- "$cur_word") )
COMPREPLY=( \$(compgen -W "bash zsh fish" -- "\$cur_word") )
;;
*)
COMPREPLY=()
@@ -23,70 +181,63 @@ _mole_completions()
fi
}
complete -F _mole_completions mole
complete -F _mole_completions mole mo
EOF
;;
zsh)
cat << 'EOF'
#compdef mole
_mole() {
local -a subcommands
subcommands=(
'optimize:Free up disk space'
'clean:Remove apps completely'
'uninstall:Check and maintain system'
'analyze:Explore disk usage'
'status:Monitor system health'
'purge:Remove old project artifacts'
'touchid:Configure Touch ID for sudo'
'update:Update to latest version'
'remove:Remove Mole from system'
'help:Show help'
'version:Show version'
'completion:Generate shell completions'
)
_describe 'subcommand' subcommands
}
_mole
EOF
printf '#compdef mole mo\n\n'
printf '_mole() {\n'
printf ' local -a subcommands\n'
printf ' subcommands=(\n'
emit_zsh_subcommands
printf ' )\n'
printf " _describe 'subcommand' subcommands\n"
printf '}\n\n'
;;
fish)
cat << 'EOF'
complete -c mole -n "__fish_mole_no_subcommand" -a optimize -d "Free up disk space"
complete -c mole -n "__fish_mole_no_subcommand" -a clean -d "Remove apps completely"
complete -c mole -n "__fish_mole_no_subcommand" -a uninstall -d "Check and maintain system"
complete -c mole -n "__fish_mole_no_subcommand" -a analyze -d "Explore disk usage"
complete -c mole -n "__fish_mole_no_subcommand" -a status -d "Monitor system health"
complete -c mole -n "__fish_mole_no_subcommand" -a purge -d "Remove old project artifacts"
complete -c mole -n "__fish_mole_no_subcommand" -a touchid -d "Configure Touch ID for sudo"
complete -c mole -n "__fish_mole_no_subcommand" -a update -d "Update to latest version"
complete -c mole -n "__fish_mole_no_subcommand" -a remove -d "Remove Mole from system"
complete -c mole -n "__fish_mole_no_subcommand" -a help -d "Show help"
complete -c mole -n "__fish_mole_no_subcommand" -a version -d "Show version"
complete -c mole -n "__fish_mole_no_subcommand" -a completion -d "Generate shell completions"
complete -c mole -n "not __fish_mole_no_subcommand" -a bash -d "generate bash completion" -n "__fish_see_subcommand_path completion"
complete -c mole -n "not __fish_mole_no_subcommand" -a zsh -d "generate zsh completion" -n "__fish_see_subcommand_path completion"
complete -c mole -n "not __fish_mole_no_subcommand" -a fish -d "generate fish completion" -n "__fish_see_subcommand_path completion"
function __fish_mole_no_subcommand
for i in (commandline -opc)
if contains -- $i optimize clean uninstall analyze status purge touchid update remove help version completion
return 1
end
end
return 0
end
function __fish_see_subcommand_path
string match -q -- "completion" (commandline -opc)[1]
end
EOF
printf '# Completions for mole\n'
emit_fish_completions mole
printf '\n# Completions for mo (alias)\n'
emit_fish_completions mo
printf '\nfunction __fish_mole_no_subcommand\n'
printf ' for i in (commandline -opc)\n'
printf ' if contains -- $i %s\n' "$command_words"
printf ' return 1\n'
printf ' end\n'
printf ' end\n'
printf ' return 0\n'
printf 'end\n\n'
printf 'function __fish_see_subcommand_path\n'
printf ' string match -q -- "completion" (commandline -opc)[1]\n'
printf 'end\n'
;;
*)
echo "Usage: mole completion [bash|zsh|fish]"
cat << 'EOF'
Usage: mole completion [bash|zsh|fish]
Setup shell tab completion for mole and mo commands.
Auto-install:
mole completion # Auto-detect shell and install
Manual install:
mole completion bash # Generate bash completion script
mole completion zsh # Generate zsh completion script
mole completion fish # Generate fish completion script
Examples:
# Auto-install (recommended)
mole completion
# Manual install - Bash
eval "$(mole completion bash)"
# Manual install - Zsh
eval "$(mole completion zsh)"
# Manual install - Fish
mole completion fish | source
EOF
exit 1
;;
esac

17
lib/core/commands.sh Normal file
View File

@@ -0,0 +1,17 @@
#!/bin/bash
# Shared command list for help text and completions.
MOLE_COMMANDS=(
"clean:Free up disk space"
"uninstall:Remove apps completely"
"optimize:Check and maintain system"
"analyze:Explore disk usage"
"status:Monitor system health"
"purge:Remove old project artifacts"
"touchid:Configure Touch ID for sudo"
"completion:Setup shell tab completion"
"update:Update to latest version"
"remove:Remove Mole from system"
"help:Show help"
"version:Show version"
)

25
mole
View File

@@ -8,6 +8,7 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/lib/core/common.sh"
source "$SCRIPT_DIR/lib/core/commands.sh"
trap cleanup_temp_files EXIT INT TERM
@@ -238,17 +239,14 @@ show_help() {
echo
printf "%s%s%s\n" "$BLUE" "COMMANDS" "$NC"
printf " %s%-28s%s %s\n" "$GREEN" "mo" "$NC" "Main menu"
printf " %s%-28s%s %s\n" "$GREEN" "mo clean" "$NC" "Free up disk space"
printf " %s%-28s%s %s\n" "$GREEN" "mo uninstall" "$NC" "Remove apps completely"
printf " %s%-28s%s %s\n" "$GREEN" "mo optimize" "$NC" "Check and maintain system"
printf " %s%-28s%s %s\n" "$GREEN" "mo analyze" "$NC" "Explore disk usage"
printf " %s%-28s%s %s\n" "$GREEN" "mo status" "$NC" "Monitor system health"
printf " %s%-28s%s %s\n" "$GREEN" "mo purge" "$NC" "Remove old project artifacts"
printf " %s%-28s%s %s\n" "$GREEN" "mo touchid" "$NC" "Configure Touch ID for sudo"
printf " %s%-28s%s %s\n" "$GREEN" "mo update" "$NC" "Update to latest version"
printf " %s%-28s%s %s\n" "$GREEN" "mo remove" "$NC" "Remove Mole from system"
printf " %s%-28s%s %s\n" "$GREEN" "mo --help" "$NC" "Show help"
printf " %s%-28s%s %s\n" "$GREEN" "mo --version" "$NC" "Show version"
for entry in "${MOLE_COMMANDS[@]}"; do
local name="${entry%%:*}"
local desc="${entry#*:}"
local display="mo $name"
[[ "$name" == "help" ]] && display="mo --help"
[[ "$name" == "version" ]] && display="mo --version"
printf " %s%-28s%s %s\n" "$GREEN" "$display" "$NC" "$desc"
done
echo
printf " %s%-28s%s %s\n" "$GREEN" "mo clean --dry-run" "$NC" "Preview cleanup"
printf " %s%-28s%s %s\n" "$GREEN" "mo clean --whitelist" "$NC" "Manage protected caches"
@@ -257,8 +255,6 @@ show_help() {
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"
echo
printf " %s%-28s%s %s\n" "$GREEN" "mo completion" "$NC" "Configure shell completion"
echo
printf "%s%s%s\n" "$BLUE" "OPTIONS" "$NC"
printf " %s%-28s%s %s\n" "$GREEN" "--debug" "$NC" "Show detailed operation logs"
echo
@@ -787,6 +783,9 @@ main() {
"touchid")
exec "$SCRIPT_DIR/bin/touchid.sh" "${args[@]:1}"
;;
"completion")
exec "$SCRIPT_DIR/bin/completion.sh" "${args[@]:1}"
;;
"update")
update_mole
exit 0

View File

@@ -0,0 +1,289 @@
#!/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-browser-cleanup.XXXXXX")"
export HOME
mkdir -p "$HOME"
}
teardown_file() {
rm -rf "$HOME"
if [[ -n "${ORIGINAL_HOME:-}" ]]; then
export HOME="$ORIGINAL_HOME"
fi
}
@test "clean_chrome_old_versions skips when Chrome is running" {
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"
# Mock pgrep to simulate Chrome running
pgrep() { return 0; }
export -f pgrep
clean_chrome_old_versions
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"Google Chrome running"* ]]
[[ "$output" == *"old versions cleanup skipped"* ]]
}
@test "clean_chrome_old_versions removes old versions but keeps current" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" DRY_RUN=true bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/clean/user.sh"
# Mock pgrep to simulate Chrome not running
pgrep() { return 1; }
export -f pgrep
# Create mock Chrome directory structure
CHROME_APP="$HOME/Applications/Google Chrome.app"
VERSIONS_DIR="$CHROME_APP/Contents/Frameworks/Google Chrome Framework.framework/Versions"
mkdir -p "$VERSIONS_DIR"/{128.0.0.0,129.0.0.0,130.0.0.0}
# Create Current symlink pointing to 130.0.0.0
ln -s "130.0.0.0" "$VERSIONS_DIR/Current"
# Mock functions
is_path_whitelisted() { return 1; }
get_path_size_kb() { echo "10240"; }
bytes_to_human() { echo "10M"; }
note_activity() { :; }
export -f is_path_whitelisted get_path_size_kb bytes_to_human note_activity
# Initialize counters
files_cleaned=0
total_size_cleaned=0
total_items=0
clean_chrome_old_versions
# Verify output mentions old versions cleanup
echo "Cleaned: $files_cleaned items"
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"Chrome old versions"* ]]
[[ "$output" == *"dry"* ]]
[[ "$output" == *"Cleaned: 2 items"* ]]
}
@test "clean_chrome_old_versions respects whitelist" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" DRY_RUN=true bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/clean/user.sh"
# Mock pgrep to simulate Chrome not running
pgrep() { return 1; }
export -f pgrep
# Create mock Chrome directory structure
CHROME_APP="$HOME/Applications/Google Chrome.app"
VERSIONS_DIR="$CHROME_APP/Contents/Frameworks/Google Chrome Framework.framework/Versions"
mkdir -p "$VERSIONS_DIR"/{128.0.0.0,129.0.0.0,130.0.0.0}
# Create Current symlink pointing to 130.0.0.0
ln -s "130.0.0.0" "$VERSIONS_DIR/Current"
# Mock is_path_whitelisted to protect version 128.0.0.0
is_path_whitelisted() {
[[ "$1" == *"128.0.0.0"* ]] && return 0
return 1
}
get_path_size_kb() { echo "10240"; }
bytes_to_human() { echo "10M"; }
note_activity() { :; }
export -f is_path_whitelisted get_path_size_kb bytes_to_human note_activity
# Initialize counters
files_cleaned=0
total_size_cleaned=0
total_items=0
clean_chrome_old_versions
# Should only clean 129.0.0.0 (not 128.0.0.0 which is whitelisted)
echo "Cleaned: $files_cleaned items"
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"Cleaned: 1 items"* ]]
}
@test "clean_chrome_old_versions DRY_RUN mode does not delete files" {
# Create test directory
CHROME_APP="$HOME/Applications/Google Chrome.app"
VERSIONS_DIR="$CHROME_APP/Contents/Frameworks/Google Chrome Framework.framework/Versions"
mkdir -p "$VERSIONS_DIR"/{128.0.0.0,130.0.0.0}
# Remove Current if it exists as a directory, then create symlink
rm -rf "$VERSIONS_DIR/Current"
ln -s "130.0.0.0" "$VERSIONS_DIR/Current"
# Create a marker file in old version
touch "$VERSIONS_DIR/128.0.0.0/marker.txt"
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" DRY_RUN=true bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/clean/user.sh"
pgrep() { return 1; }
is_path_whitelisted() { return 1; }
get_path_size_kb() { echo "10240"; }
bytes_to_human() { echo "10M"; }
note_activity() { :; }
export -f pgrep is_path_whitelisted get_path_size_kb bytes_to_human note_activity
files_cleaned=0
total_size_cleaned=0
total_items=0
clean_chrome_old_versions
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"dry"* ]]
# Verify marker file still exists (not deleted in dry run)
[ -f "$VERSIONS_DIR/128.0.0.0/marker.txt" ]
}
@test "clean_chrome_old_versions handles missing Current symlink gracefully" {
# Use a fresh temp directory for this test
TEST_HOME="$(mktemp -d "${BATS_TEST_DIRNAME}/tmp-test5.XXXXXX")"
run env HOME="$TEST_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"
pgrep() { return 1; }
is_path_whitelisted() { return 1; }
get_path_size_kb() { echo "10240"; }
bytes_to_human() { echo "10M"; }
note_activity() { :; }
export -f pgrep is_path_whitelisted get_path_size_kb bytes_to_human note_activity
# Initialize counters to prevent unbound variable errors
files_cleaned=0
total_size_cleaned=0
total_items=0
# Create Chrome app without Current symlink
CHROME_APP="$HOME/Applications/Google Chrome.app"
VERSIONS_DIR="$CHROME_APP/Contents/Frameworks/Google Chrome Framework.framework/Versions"
mkdir -p "$VERSIONS_DIR"/{128.0.0.0,129.0.0.0}
# No Current symlink created
clean_chrome_old_versions
EOF
rm -rf "$TEST_HOME"
[ "$status" -eq 0 ]
# Should exit gracefully with no output
}
@test "clean_edge_old_versions skips when Edge is running" {
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"
# Mock pgrep to simulate Edge running
pgrep() { return 0; }
export -f pgrep
clean_edge_old_versions
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"Microsoft Edge running"* ]]
[[ "$output" == *"old versions cleanup skipped"* ]]
}
@test "clean_edge_old_versions removes old versions but keeps current" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" DRY_RUN=true bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/clean/user.sh"
pgrep() { return 1; }
export -f pgrep
# Create mock Edge directory structure
EDGE_APP="$HOME/Applications/Microsoft Edge.app"
VERSIONS_DIR="$EDGE_APP/Contents/Frameworks/Microsoft Edge Framework.framework/Versions"
mkdir -p "$VERSIONS_DIR"/{120.0.0.0,121.0.0.0,122.0.0.0}
# Create Current symlink pointing to 122.0.0.0
ln -s "122.0.0.0" "$VERSIONS_DIR/Current"
is_path_whitelisted() { return 1; }
get_path_size_kb() { echo "10240"; }
bytes_to_human() { echo "10M"; }
note_activity() { :; }
export -f is_path_whitelisted get_path_size_kb bytes_to_human note_activity
files_cleaned=0
total_size_cleaned=0
total_items=0
clean_edge_old_versions
echo "Cleaned: $files_cleaned items"
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"Edge old versions"* ]]
[[ "$output" == *"dry"* ]]
[[ "$output" == *"Cleaned: 2 items"* ]]
}
@test "clean_edge_old_versions handles no old versions gracefully" {
# Use a fresh temp directory for this test
TEST_HOME="$(mktemp -d "${BATS_TEST_DIRNAME}/tmp-test8.XXXXXX")"
run env HOME="$TEST_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"
pgrep() { return 1; }
is_path_whitelisted() { return 1; }
get_path_size_kb() { echo "10240"; }
bytes_to_human() { echo "10M"; }
note_activity() { :; }
export -f pgrep is_path_whitelisted get_path_size_kb bytes_to_human note_activity
# Initialize counters
files_cleaned=0
total_size_cleaned=0
total_items=0
# Create Edge with only current version
EDGE_APP="$HOME/Applications/Microsoft Edge.app"
VERSIONS_DIR="$EDGE_APP/Contents/Frameworks/Microsoft Edge Framework.framework/Versions"
mkdir -p "$VERSIONS_DIR/122.0.0.0"
ln -s "122.0.0.0" "$VERSIONS_DIR/Current"
clean_edge_old_versions
EOF
rm -rf "$TEST_HOME"
[ "$status" -eq 0 ]
# Should exit gracefully with no cleanup output
[[ "$output" != *"Edge old versions"* ]]
}

157
tests/completion.bats Executable file
View File

@@ -0,0 +1,157 @@
#!/usr/bin/env bats
setup_file() {
PROJECT_ROOT="$(cd "${BATS_TEST_DIRNAME}/.." && pwd)"
export PROJECT_ROOT
ORIGINAL_HOME="${HOME:-}"
export ORIGINAL_HOME
ORIGINAL_PATH="${PATH:-}"
export ORIGINAL_PATH
HOME="$(mktemp -d "${BATS_TEST_DIRNAME}/tmp-completion-home.XXXXXX")"
export HOME
mkdir -p "$HOME"
PATH="$PROJECT_ROOT:$PATH"
export PATH
}
teardown_file() {
rm -rf "$HOME"
if [[ -n "${ORIGINAL_HOME:-}" ]]; then
export HOME="$ORIGINAL_HOME"
fi
if [[ -n "${ORIGINAL_PATH:-}" ]]; then
export PATH="$ORIGINAL_PATH"
fi
}
setup() {
rm -rf "$HOME/.config"
rm -rf "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.bash_profile"
mkdir -p "$HOME"
}
@test "completion script exists and is executable" {
[ -f "$PROJECT_ROOT/bin/completion.sh" ]
[ -x "$PROJECT_ROOT/bin/completion.sh" ]
}
@test "completion script has valid bash syntax" {
run bash -n "$PROJECT_ROOT/bin/completion.sh"
[ "$status" -eq 0 ]
}
@test "completion --help shows usage" {
run "$PROJECT_ROOT/bin/completion.sh" --help
[ "$status" -ne 0 ]
[[ "$output" == *"Usage: mole completion"* ]]
[[ "$output" == *"Auto-install"* ]]
}
@test "completion bash generates valid bash script" {
run "$PROJECT_ROOT/bin/completion.sh" bash
[ "$status" -eq 0 ]
[[ "$output" == *"_mole_completions"* ]]
[[ "$output" == *"complete -F _mole_completions mole mo"* ]]
}
@test "completion bash script includes all commands" {
run "$PROJECT_ROOT/bin/completion.sh" bash
[ "$status" -eq 0 ]
[[ "$output" == *"optimize"* ]]
[[ "$output" == *"clean"* ]]
[[ "$output" == *"uninstall"* ]]
[[ "$output" == *"analyze"* ]]
[[ "$output" == *"status"* ]]
[[ "$output" == *"purge"* ]]
[[ "$output" == *"touchid"* ]]
[[ "$output" == *"completion"* ]]
}
@test "completion bash script supports mo command" {
run "$PROJECT_ROOT/bin/completion.sh" bash
[ "$status" -eq 0 ]
[[ "$output" == *"complete -F _mole_completions mole mo"* ]]
}
@test "completion bash can be loaded in bash" {
run bash -c "eval \"\$(\"$PROJECT_ROOT/bin/completion.sh\" bash)\" && complete -p mole"
[ "$status" -eq 0 ]
[[ "$output" == *"_mole_completions"* ]]
}
@test "completion zsh generates valid zsh script" {
run "$PROJECT_ROOT/bin/completion.sh" zsh
[ "$status" -eq 0 ]
[[ "$output" == *"#compdef mole mo"* ]]
[[ "$output" == *"_mole()"* ]]
}
@test "completion zsh includes command descriptions" {
run "$PROJECT_ROOT/bin/completion.sh" zsh
[ "$status" -eq 0 ]
[[ "$output" == *"optimize:Free up disk space"* ]]
[[ "$output" == *"clean:Remove apps completely"* ]]
}
@test "completion fish generates valid fish script" {
run "$PROJECT_ROOT/bin/completion.sh" fish
[ "$status" -eq 0 ]
[[ "$output" == *"complete -c mole"* ]]
[[ "$output" == *"complete -c mo"* ]]
}
@test "completion fish includes both mole and mo commands" {
output="$("$PROJECT_ROOT/bin/completion.sh" fish)"
mole_count=$(echo "$output" | grep -c "complete -c mole")
mo_count=$(echo "$output" | grep -c "complete -c mo")
[ "$mole_count" -gt 0 ]
[ "$mo_count" -gt 0 ]
}
@test "completion auto-install detects zsh" {
export SHELL=/bin/zsh
# Simulate auto-install (no interaction)
run bash -c "echo 'y' | \"$PROJECT_ROOT/bin/completion.sh\""
if [[ "$output" == *"Already configured"* ]]; then
skip "Already configured from previous test"
fi
[ -f "$HOME/.zshrc" ] || skip "Auto-install didn't create .zshrc"
run grep -E "mole[[:space:]]+completion" "$HOME/.zshrc"
[ "$status" -eq 0 ]
}
@test "completion auto-install detects already installed" {
export SHELL=/bin/zsh
mkdir -p "$HOME"
echo 'eval "$(mole completion zsh)"' > "$HOME/.zshrc"
run "$PROJECT_ROOT/bin/completion.sh"
[ "$status" -eq 0 ]
[[ "$output" == *"updated"* ]]
}
@test "completion script handles invalid shell argument" {
run "$PROJECT_ROOT/bin/completion.sh" invalid-shell
[ "$status" -ne 0 ]
}
@test "completion subcommand supports bash/zsh/fish" {
run "$PROJECT_ROOT/bin/completion.sh" bash
[ "$status" -eq 0 ]
run "$PROJECT_ROOT/bin/completion.sh" zsh
[ "$status" -eq 0 ]
run "$PROJECT_ROOT/bin/completion.sh" fish
[ "$status" -eq 0 ]
}