mirror of
https://github.com/tw93/Mole.git
synced 2026-03-24 16:20:08 +00:00
Add dry-run support across destructive commands (#516)
* chore: update contributors [skip ci] * Add dry-run support across destructive commands Implement dry-run for uninstall, purge, installer, touchid, completion, and remove flows.\nGuard side effects in uninstall path (launchctl, defaults writes, kill/brew actions), update help/README, and add coverage in CLI/Bats tests.\n\nValidation: ./scripts/check.sh and ./scripts/test.sh (452 tests, 0 failures, 8 skipped). * test(purge): keep dev-compatible purge coverage --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Tw93 <hitw93@gmail.com>
This commit is contained in:
491
tests/purge.bats
491
tests/purge.bats
@@ -1,35 +1,35 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
setup_file() {
|
||||
PROJECT_ROOT="$(cd "${BATS_TEST_DIRNAME}/.." && pwd)"
|
||||
export PROJECT_ROOT
|
||||
PROJECT_ROOT="$(cd "${BATS_TEST_DIRNAME}/.." && pwd)"
|
||||
export PROJECT_ROOT
|
||||
|
||||
ORIGINAL_HOME="${HOME:-}"
|
||||
export ORIGINAL_HOME
|
||||
ORIGINAL_HOME="${HOME:-}"
|
||||
export ORIGINAL_HOME
|
||||
|
||||
HOME="$(mktemp -d "${BATS_TEST_DIRNAME}/tmp-purge-home.XXXXXX")"
|
||||
export HOME
|
||||
HOME="$(mktemp -d "${BATS_TEST_DIRNAME}/tmp-purge-home.XXXXXX")"
|
||||
export HOME
|
||||
|
||||
mkdir -p "$HOME"
|
||||
mkdir -p "$HOME"
|
||||
}
|
||||
|
||||
teardown_file() {
|
||||
rm -rf "$HOME"
|
||||
if [[ -n "${ORIGINAL_HOME:-}" ]]; then
|
||||
export HOME="$ORIGINAL_HOME"
|
||||
fi
|
||||
rm -rf "$HOME"
|
||||
if [[ -n "${ORIGINAL_HOME:-}" ]]; then
|
||||
export HOME="$ORIGINAL_HOME"
|
||||
fi
|
||||
}
|
||||
|
||||
setup() {
|
||||
mkdir -p "$HOME/www"
|
||||
mkdir -p "$HOME/dev"
|
||||
mkdir -p "$HOME/.cache/mole"
|
||||
mkdir -p "$HOME/www"
|
||||
mkdir -p "$HOME/dev"
|
||||
mkdir -p "$HOME/.cache/mole"
|
||||
|
||||
rm -rf "${HOME:?}/www"/* "${HOME:?}/dev"/*
|
||||
rm -rf "${HOME:?}/www"/* "${HOME:?}/dev"/*
|
||||
}
|
||||
|
||||
@test "is_safe_project_artifact: rejects shallow paths (protection against accidents)" {
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
if is_safe_project_artifact '$HOME/www/node_modules' '$HOME/www'; then
|
||||
echo 'UNSAFE'
|
||||
@@ -37,11 +37,11 @@ setup() {
|
||||
echo 'SAFE'
|
||||
fi
|
||||
")
|
||||
[[ "$result" == "SAFE" ]]
|
||||
[[ "$result" == "SAFE" ]]
|
||||
}
|
||||
|
||||
@test "is_safe_project_artifact: allows proper project artifacts" {
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
if is_safe_project_artifact '$HOME/www/myproject/node_modules' '$HOME/www'; then
|
||||
echo 'ALLOWED'
|
||||
@@ -49,11 +49,11 @@ setup() {
|
||||
echo 'BLOCKED'
|
||||
fi
|
||||
")
|
||||
[[ "$result" == "ALLOWED" ]]
|
||||
[[ "$result" == "ALLOWED" ]]
|
||||
}
|
||||
|
||||
@test "is_safe_project_artifact: rejects non-absolute paths" {
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
if is_safe_project_artifact 'relative/path/node_modules' '$HOME/www'; then
|
||||
echo 'UNSAFE'
|
||||
@@ -61,11 +61,11 @@ setup() {
|
||||
echo 'SAFE'
|
||||
fi
|
||||
")
|
||||
[[ "$result" == "SAFE" ]]
|
||||
[[ "$result" == "SAFE" ]]
|
||||
}
|
||||
|
||||
@test "is_safe_project_artifact: validates depth calculation" {
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
if is_safe_project_artifact '$HOME/www/project/subdir/node_modules' '$HOME/www'; then
|
||||
echo 'ALLOWED'
|
||||
@@ -73,14 +73,14 @@ setup() {
|
||||
echo 'BLOCKED'
|
||||
fi
|
||||
")
|
||||
[[ "$result" == "ALLOWED" ]]
|
||||
[[ "$result" == "ALLOWED" ]]
|
||||
}
|
||||
|
||||
@test "is_safe_project_artifact: allows direct child when search path is project root" {
|
||||
mkdir -p "$HOME/single-project/node_modules"
|
||||
touch "$HOME/single-project/package.json"
|
||||
mkdir -p "$HOME/single-project/node_modules"
|
||||
touch "$HOME/single-project/package.json"
|
||||
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
if is_safe_project_artifact '$HOME/single-project/node_modules' '$HOME/single-project'; then
|
||||
echo 'ALLOWED'
|
||||
@@ -89,15 +89,15 @@ setup() {
|
||||
fi
|
||||
")
|
||||
|
||||
[[ "$result" == "ALLOWED" ]]
|
||||
[[ "$result" == "ALLOWED" ]]
|
||||
}
|
||||
|
||||
@test "is_safe_project_artifact: accepts physical path under symlinked search root" {
|
||||
mkdir -p "$HOME/www/real/proj/node_modules"
|
||||
touch "$HOME/www/real/proj/package.json"
|
||||
ln -s "$HOME/www/real" "$HOME/www/link"
|
||||
mkdir -p "$HOME/www/real/proj/node_modules"
|
||||
touch "$HOME/www/real/proj/package.json"
|
||||
ln -s "$HOME/www/real" "$HOME/www/link"
|
||||
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
if is_safe_project_artifact '$HOME/www/real/proj/node_modules' '$HOME/www/link/proj'; then
|
||||
echo 'ALLOWED'
|
||||
@@ -106,43 +106,43 @@ setup() {
|
||||
fi
|
||||
")
|
||||
|
||||
[[ "$result" == "ALLOWED" ]]
|
||||
[[ "$result" == "ALLOWED" ]]
|
||||
}
|
||||
|
||||
@test "filter_nested_artifacts: removes nested node_modules" {
|
||||
mkdir -p "$HOME/www/project/node_modules/package/node_modules"
|
||||
mkdir -p "$HOME/www/project/node_modules/package/node_modules"
|
||||
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
printf '%s\n' '$HOME/www/project/node_modules' '$HOME/www/project/node_modules/package/node_modules' | \
|
||||
filter_nested_artifacts | wc -l | tr -d ' '
|
||||
")
|
||||
|
||||
[[ "$result" == "1" ]]
|
||||
[[ "$result" == "1" ]]
|
||||
}
|
||||
|
||||
@test "filter_nested_artifacts: keeps independent artifacts" {
|
||||
mkdir -p "$HOME/www/project1/node_modules"
|
||||
mkdir -p "$HOME/www/project2/target"
|
||||
mkdir -p "$HOME/www/project1/node_modules"
|
||||
mkdir -p "$HOME/www/project2/target"
|
||||
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
printf '%s\n' '$HOME/www/project1/node_modules' '$HOME/www/project2/target' | \
|
||||
filter_nested_artifacts | wc -l | tr -d ' '
|
||||
")
|
||||
|
||||
[[ "$result" == "2" ]]
|
||||
[[ "$result" == "2" ]]
|
||||
}
|
||||
|
||||
@test "filter_nested_artifacts: removes Xcode build subdirectories (Mac projects)" {
|
||||
# Simulate Mac Xcode project with nested .build directories:
|
||||
# ~/www/testapp/build
|
||||
# ~/www/testapp/build/Framework.build
|
||||
# ~/www/testapp/build/Package.build
|
||||
mkdir -p "$HOME/www/testapp/build/Framework.build"
|
||||
mkdir -p "$HOME/www/testapp/build/Package.build"
|
||||
# Simulate Mac Xcode project with nested .build directories:
|
||||
# ~/www/testapp/build
|
||||
# ~/www/testapp/build/Framework.build
|
||||
# ~/www/testapp/build/Package.build
|
||||
mkdir -p "$HOME/www/testapp/build/Framework.build"
|
||||
mkdir -p "$HOME/www/testapp/build/Package.build"
|
||||
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
printf '%s\n' \
|
||||
'$HOME/www/testapp/build' \
|
||||
@@ -150,20 +150,20 @@ setup() {
|
||||
'$HOME/www/testapp/build/Package.build' | \
|
||||
filter_nested_artifacts | wc -l | tr -d ' '
|
||||
")
|
||||
|
||||
# Should only keep the top-level 'build' directory, filtering out nested .build dirs
|
||||
[[ "$result" == "1" ]]
|
||||
|
||||
# Should only keep the top-level 'build' directory, filtering out nested .build dirs
|
||||
[[ "$result" == "1" ]]
|
||||
}
|
||||
|
||||
# Vendor protection unit tests
|
||||
@test "is_rails_project_root: detects valid Rails project" {
|
||||
mkdir -p "$HOME/www/test-rails/config"
|
||||
mkdir -p "$HOME/www/test-rails/bin"
|
||||
touch "$HOME/www/test-rails/config/application.rb"
|
||||
touch "$HOME/www/test-rails/Gemfile"
|
||||
touch "$HOME/www/test-rails/bin/rails"
|
||||
mkdir -p "$HOME/www/test-rails/config"
|
||||
mkdir -p "$HOME/www/test-rails/bin"
|
||||
touch "$HOME/www/test-rails/config/application.rb"
|
||||
touch "$HOME/www/test-rails/Gemfile"
|
||||
touch "$HOME/www/test-rails/bin/rails"
|
||||
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
if is_rails_project_root '$HOME/www/test-rails'; then
|
||||
echo 'YES'
|
||||
@@ -172,14 +172,14 @@ setup() {
|
||||
fi
|
||||
")
|
||||
|
||||
[[ "$result" == "YES" ]]
|
||||
[[ "$result" == "YES" ]]
|
||||
}
|
||||
|
||||
@test "is_rails_project_root: rejects non-Rails directory" {
|
||||
mkdir -p "$HOME/www/not-rails"
|
||||
touch "$HOME/www/not-rails/package.json"
|
||||
mkdir -p "$HOME/www/not-rails"
|
||||
touch "$HOME/www/not-rails/package.json"
|
||||
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
if is_rails_project_root '$HOME/www/not-rails'; then
|
||||
echo 'YES'
|
||||
@@ -188,14 +188,14 @@ setup() {
|
||||
fi
|
||||
")
|
||||
|
||||
[[ "$result" == "NO" ]]
|
||||
[[ "$result" == "NO" ]]
|
||||
}
|
||||
|
||||
@test "is_go_project_root: detects valid Go project" {
|
||||
mkdir -p "$HOME/www/test-go"
|
||||
touch "$HOME/www/test-go/go.mod"
|
||||
mkdir -p "$HOME/www/test-go"
|
||||
touch "$HOME/www/test-go/go.mod"
|
||||
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
if is_go_project_root '$HOME/www/test-go'; then
|
||||
echo 'YES'
|
||||
@@ -204,14 +204,14 @@ setup() {
|
||||
fi
|
||||
")
|
||||
|
||||
[[ "$result" == "YES" ]]
|
||||
[[ "$result" == "YES" ]]
|
||||
}
|
||||
|
||||
@test "is_php_project_root: detects valid PHP Composer project" {
|
||||
mkdir -p "$HOME/www/test-php"
|
||||
touch "$HOME/www/test-php/composer.json"
|
||||
mkdir -p "$HOME/www/test-php"
|
||||
touch "$HOME/www/test-php/composer.json"
|
||||
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
if is_php_project_root '$HOME/www/test-php'; then
|
||||
echo 'YES'
|
||||
@@ -220,17 +220,17 @@ setup() {
|
||||
fi
|
||||
")
|
||||
|
||||
[[ "$result" == "YES" ]]
|
||||
[[ "$result" == "YES" ]]
|
||||
}
|
||||
|
||||
@test "is_protected_vendor_dir: protects Rails vendor" {
|
||||
mkdir -p "$HOME/www/rails-app/vendor"
|
||||
mkdir -p "$HOME/www/rails-app/config"
|
||||
touch "$HOME/www/rails-app/config/application.rb"
|
||||
touch "$HOME/www/rails-app/Gemfile"
|
||||
touch "$HOME/www/rails-app/config/environment.rb"
|
||||
mkdir -p "$HOME/www/rails-app/vendor"
|
||||
mkdir -p "$HOME/www/rails-app/config"
|
||||
touch "$HOME/www/rails-app/config/application.rb"
|
||||
touch "$HOME/www/rails-app/Gemfile"
|
||||
touch "$HOME/www/rails-app/config/environment.rb"
|
||||
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
if is_protected_vendor_dir '$HOME/www/rails-app/vendor'; then
|
||||
echo 'PROTECTED'
|
||||
@@ -239,14 +239,14 @@ setup() {
|
||||
fi
|
||||
")
|
||||
|
||||
[[ "$result" == "PROTECTED" ]]
|
||||
[[ "$result" == "PROTECTED" ]]
|
||||
}
|
||||
|
||||
@test "is_protected_vendor_dir: does not protect PHP vendor" {
|
||||
mkdir -p "$HOME/www/php-app/vendor"
|
||||
touch "$HOME/www/php-app/composer.json"
|
||||
mkdir -p "$HOME/www/php-app/vendor"
|
||||
touch "$HOME/www/php-app/composer.json"
|
||||
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
if is_protected_vendor_dir '$HOME/www/php-app/vendor'; then
|
||||
echo 'PROTECTED'
|
||||
@@ -255,11 +255,11 @@ setup() {
|
||||
fi
|
||||
")
|
||||
|
||||
[[ "$result" == "NOT_PROTECTED" ]]
|
||||
[[ "$result" == "NOT_PROTECTED" ]]
|
||||
}
|
||||
|
||||
@test "is_project_container detects project indicators" {
|
||||
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
|
||||
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
|
||||
set -euo pipefail
|
||||
source "$PROJECT_ROOT/lib/clean/project.sh"
|
||||
mkdir -p "$HOME/Workspace2/project"
|
||||
@@ -269,12 +269,12 @@ if is_project_container "$HOME/Workspace2" 2; then
|
||||
fi
|
||||
EOF
|
||||
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"yes"* ]]
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"yes"* ]]
|
||||
}
|
||||
|
||||
@test "discover_project_dirs includes detected containers" {
|
||||
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
|
||||
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
|
||||
set -euo pipefail
|
||||
source "$PROJECT_ROOT/lib/clean/project.sh"
|
||||
mkdir -p "$HOME/CustomProjects/app"
|
||||
@@ -282,22 +282,22 @@ touch "$HOME/CustomProjects/app/go.mod"
|
||||
discover_project_dirs | grep -q "$HOME/CustomProjects"
|
||||
EOF
|
||||
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "save_discovered_paths writes config with tilde" {
|
||||
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
|
||||
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
|
||||
set -euo pipefail
|
||||
source "$PROJECT_ROOT/lib/clean/project.sh"
|
||||
save_discovered_paths "$HOME/Projects"
|
||||
grep -q "^~/" "$HOME/.config/mole/purge_paths"
|
||||
EOF
|
||||
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "select_purge_categories returns failure on empty input" {
|
||||
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
|
||||
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
|
||||
set -euo pipefail
|
||||
source "$PROJECT_ROOT/lib/clean/project.sh"
|
||||
if select_purge_categories; then
|
||||
@@ -305,7 +305,7 @@ if select_purge_categories; then
|
||||
fi
|
||||
EOF
|
||||
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "select_purge_categories restores caller EXIT/INT/TERM traps" {
|
||||
@@ -369,10 +369,10 @@ EOF
|
||||
}
|
||||
|
||||
@test "is_protected_vendor_dir: protects Go vendor" {
|
||||
mkdir -p "$HOME/www/go-app/vendor"
|
||||
touch "$HOME/www/go-app/go.mod"
|
||||
mkdir -p "$HOME/www/go-app/vendor"
|
||||
touch "$HOME/www/go-app/go.mod"
|
||||
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
if is_protected_vendor_dir '$HOME/www/go-app/vendor'; then
|
||||
echo 'PROTECTED'
|
||||
@@ -381,13 +381,13 @@ EOF
|
||||
fi
|
||||
")
|
||||
|
||||
[[ "$result" == "PROTECTED" ]]
|
||||
[[ "$result" == "PROTECTED" ]]
|
||||
}
|
||||
|
||||
@test "is_protected_vendor_dir: protects unknown vendor (conservative)" {
|
||||
mkdir -p "$HOME/www/unknown-app/vendor"
|
||||
mkdir -p "$HOME/www/unknown-app/vendor"
|
||||
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
if is_protected_vendor_dir '$HOME/www/unknown-app/vendor'; then
|
||||
echo 'PROTECTED'
|
||||
@@ -396,14 +396,14 @@ EOF
|
||||
fi
|
||||
")
|
||||
|
||||
[[ "$result" == "PROTECTED" ]]
|
||||
[[ "$result" == "PROTECTED" ]]
|
||||
}
|
||||
|
||||
@test "is_protected_purge_artifact: handles vendor directories correctly" {
|
||||
mkdir -p "$HOME/www/php-app/vendor"
|
||||
touch "$HOME/www/php-app/composer.json"
|
||||
mkdir -p "$HOME/www/php-app/vendor"
|
||||
touch "$HOME/www/php-app/composer.json"
|
||||
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
if is_protected_purge_artifact '$HOME/www/php-app/vendor'; then
|
||||
echo 'PROTECTED'
|
||||
@@ -412,14 +412,14 @@ EOF
|
||||
fi
|
||||
")
|
||||
|
||||
# PHP vendor should not be protected
|
||||
[[ "$result" == "NOT_PROTECTED" ]]
|
||||
# PHP vendor should not be protected
|
||||
[[ "$result" == "NOT_PROTECTED" ]]
|
||||
}
|
||||
|
||||
@test "is_protected_purge_artifact: returns false for non-vendor artifacts" {
|
||||
mkdir -p "$HOME/www/app/node_modules"
|
||||
mkdir -p "$HOME/www/app/node_modules"
|
||||
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
if is_protected_purge_artifact '$HOME/www/app/node_modules'; then
|
||||
echo 'PROTECTED'
|
||||
@@ -428,23 +428,23 @@ EOF
|
||||
fi
|
||||
")
|
||||
|
||||
# node_modules is not in the protected list
|
||||
[[ "$result" == "NOT_PROTECTED" ]]
|
||||
# node_modules is not in the protected list
|
||||
[[ "$result" == "NOT_PROTECTED" ]]
|
||||
}
|
||||
|
||||
# Integration tests
|
||||
@test "scan_purge_targets: skips Rails vendor directory" {
|
||||
mkdir -p "$HOME/www/rails-app/vendor/javascript"
|
||||
mkdir -p "$HOME/www/rails-app/config"
|
||||
touch "$HOME/www/rails-app/config/application.rb"
|
||||
touch "$HOME/www/rails-app/Gemfile"
|
||||
mkdir -p "$HOME/www/rails-app/bin"
|
||||
touch "$HOME/www/rails-app/bin/rails"
|
||||
mkdir -p "$HOME/www/rails-app/vendor/javascript"
|
||||
mkdir -p "$HOME/www/rails-app/config"
|
||||
touch "$HOME/www/rails-app/config/application.rb"
|
||||
touch "$HOME/www/rails-app/Gemfile"
|
||||
mkdir -p "$HOME/www/rails-app/bin"
|
||||
touch "$HOME/www/rails-app/bin/rails"
|
||||
|
||||
local scan_output
|
||||
scan_output="$(mktemp)"
|
||||
local scan_output
|
||||
scan_output="$(mktemp)"
|
||||
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
scan_purge_targets '$HOME/www' '$scan_output'
|
||||
if grep -q '$HOME/www/rails-app/vendor' '$scan_output'; then
|
||||
@@ -454,19 +454,19 @@ EOF
|
||||
fi
|
||||
")
|
||||
|
||||
rm -f "$scan_output"
|
||||
rm -f "$scan_output"
|
||||
|
||||
[[ "$result" == "SKIPPED" ]]
|
||||
[[ "$result" == "SKIPPED" ]]
|
||||
}
|
||||
|
||||
@test "scan_purge_targets: cleans PHP Composer vendor directory" {
|
||||
mkdir -p "$HOME/www/php-app/vendor"
|
||||
touch "$HOME/www/php-app/composer.json"
|
||||
mkdir -p "$HOME/www/php-app/vendor"
|
||||
touch "$HOME/www/php-app/composer.json"
|
||||
|
||||
local scan_output
|
||||
scan_output="$(mktemp)"
|
||||
local scan_output
|
||||
scan_output="$(mktemp)"
|
||||
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
scan_purge_targets '$HOME/www' '$scan_output'
|
||||
if grep -q '$HOME/www/php-app/vendor' '$scan_output'; then
|
||||
@@ -476,20 +476,20 @@ EOF
|
||||
fi
|
||||
")
|
||||
|
||||
rm -f "$scan_output"
|
||||
rm -f "$scan_output"
|
||||
|
||||
[[ "$result" == "FOUND" ]]
|
||||
[[ "$result" == "FOUND" ]]
|
||||
}
|
||||
|
||||
@test "scan_purge_targets: skips Go vendor directory" {
|
||||
mkdir -p "$HOME/www/go-app/vendor"
|
||||
touch "$HOME/www/go-app/go.mod"
|
||||
touch "$HOME/www/go-app/go.sum"
|
||||
mkdir -p "$HOME/www/go-app/vendor"
|
||||
touch "$HOME/www/go-app/go.mod"
|
||||
touch "$HOME/www/go-app/go.sum"
|
||||
|
||||
local scan_output
|
||||
scan_output="$(mktemp)"
|
||||
local scan_output
|
||||
scan_output="$(mktemp)"
|
||||
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
scan_purge_targets '$HOME/www' '$scan_output'
|
||||
if grep -q '$HOME/www/go-app/vendor' '$scan_output'; then
|
||||
@@ -499,19 +499,19 @@ EOF
|
||||
fi
|
||||
")
|
||||
|
||||
rm -f "$scan_output"
|
||||
rm -f "$scan_output"
|
||||
|
||||
[[ "$result" == "SKIPPED" ]]
|
||||
[[ "$result" == "SKIPPED" ]]
|
||||
}
|
||||
|
||||
@test "scan_purge_targets: skips unknown vendor directory" {
|
||||
# Create a vendor directory without any project file
|
||||
mkdir -p "$HOME/www/unknown-app/vendor"
|
||||
# Create a vendor directory without any project file
|
||||
mkdir -p "$HOME/www/unknown-app/vendor"
|
||||
|
||||
local scan_output
|
||||
scan_output="$(mktemp)"
|
||||
local scan_output
|
||||
scan_output="$(mktemp)"
|
||||
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
scan_purge_targets '$HOME/www' '$scan_output'
|
||||
if grep -q '$HOME/www/unknown-app/vendor' '$scan_output'; then
|
||||
@@ -521,20 +521,20 @@ EOF
|
||||
fi
|
||||
")
|
||||
|
||||
rm -f "$scan_output"
|
||||
rm -f "$scan_output"
|
||||
|
||||
# Unknown vendor should be protected (conservative approach)
|
||||
[[ "$result" == "SKIPPED" ]]
|
||||
# Unknown vendor should be protected (conservative approach)
|
||||
[[ "$result" == "SKIPPED" ]]
|
||||
}
|
||||
|
||||
@test "scan_purge_targets: finds direct-child artifacts in project root with find mode" {
|
||||
mkdir -p "$HOME/single-project/node_modules"
|
||||
touch "$HOME/single-project/package.json"
|
||||
mkdir -p "$HOME/single-project/node_modules"
|
||||
touch "$HOME/single-project/package.json"
|
||||
|
||||
local scan_output
|
||||
scan_output="$(mktemp)"
|
||||
local scan_output
|
||||
scan_output="$(mktemp)"
|
||||
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
MO_USE_FIND=1 scan_purge_targets '$HOME/single-project' '$scan_output'
|
||||
if grep -q '$HOME/single-project/node_modules' '$scan_output'; then
|
||||
@@ -544,19 +544,19 @@ EOF
|
||||
fi
|
||||
")
|
||||
|
||||
rm -f "$scan_output"
|
||||
rm -f "$scan_output"
|
||||
|
||||
[[ "$result" == "FOUND" ]]
|
||||
[[ "$result" == "FOUND" ]]
|
||||
}
|
||||
|
||||
@test "scan_purge_targets: supports trailing slash search path in find mode" {
|
||||
mkdir -p "$HOME/single-project/node_modules"
|
||||
touch "$HOME/single-project/package.json"
|
||||
mkdir -p "$HOME/single-project/node_modules"
|
||||
touch "$HOME/single-project/package.json"
|
||||
|
||||
local scan_output
|
||||
scan_output="$(mktemp)"
|
||||
local scan_output
|
||||
scan_output="$(mktemp)"
|
||||
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
MO_USE_FIND=1 scan_purge_targets '$HOME/single-project/' '$scan_output'
|
||||
if grep -q '$HOME/single-project/node_modules' '$scan_output'; then
|
||||
@@ -566,16 +566,16 @@ EOF
|
||||
fi
|
||||
")
|
||||
|
||||
rm -f "$scan_output"
|
||||
rm -f "$scan_output"
|
||||
|
||||
[[ "$result" == "FOUND" ]]
|
||||
[[ "$result" == "FOUND" ]]
|
||||
}
|
||||
|
||||
@test "is_recently_modified: detects recent projects" {
|
||||
mkdir -p "$HOME/www/project/node_modules"
|
||||
touch "$HOME/www/project/package.json"
|
||||
mkdir -p "$HOME/www/project/node_modules"
|
||||
touch "$HOME/www/project/package.json"
|
||||
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/core/common.sh'
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
if is_recently_modified '$HOME/www/project/node_modules'; then
|
||||
@@ -584,66 +584,66 @@ EOF
|
||||
echo 'OLD'
|
||||
fi
|
||||
")
|
||||
[[ "$result" == "RECENT" ]]
|
||||
[[ "$result" == "RECENT" ]]
|
||||
}
|
||||
|
||||
@test "is_recently_modified: marks old projects correctly" {
|
||||
mkdir -p "$HOME/www/old-project/node_modules"
|
||||
mkdir -p "$HOME/www/old-project"
|
||||
mkdir -p "$HOME/www/old-project/node_modules"
|
||||
mkdir -p "$HOME/www/old-project"
|
||||
|
||||
bash -c "
|
||||
bash -c "
|
||||
source '$PROJECT_ROOT/lib/core/common.sh'
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
is_recently_modified '$HOME/www/old-project/node_modules' || true
|
||||
"
|
||||
local exit_code=$?
|
||||
[ "$exit_code" -eq 0 ] || [ "$exit_code" -eq 1 ]
|
||||
local exit_code=$?
|
||||
[ "$exit_code" -eq 0 ] || [ "$exit_code" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "purge targets are configured correctly" {
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
echo \"\${PURGE_TARGETS[@]}\"
|
||||
")
|
||||
[[ "$result" == *"node_modules"* ]]
|
||||
[[ "$result" == *"target"* ]]
|
||||
[[ "$result" == *"node_modules"* ]]
|
||||
[[ "$result" == *"target"* ]]
|
||||
}
|
||||
|
||||
@test "get_dir_size_kb: calculates directory size" {
|
||||
mkdir -p "$HOME/www/test-project/node_modules"
|
||||
dd if=/dev/zero of="$HOME/www/test-project/node_modules/file.bin" bs=1024 count=1024 2>/dev/null
|
||||
mkdir -p "$HOME/www/test-project/node_modules"
|
||||
dd if=/dev/zero of="$HOME/www/test-project/node_modules/file.bin" bs=1024 count=1024 2>/dev/null
|
||||
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
get_dir_size_kb '$HOME/www/test-project/node_modules'
|
||||
")
|
||||
|
||||
[[ "$result" -ge 1000 ]] && [[ "$result" -le 1100 ]]
|
||||
[[ "$result" -ge 1000 ]] && [[ "$result" -le 1100 ]]
|
||||
}
|
||||
|
||||
@test "get_dir_size_kb: handles non-existent paths gracefully" {
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
get_dir_size_kb '$HOME/www/non-existent'
|
||||
")
|
||||
[[ "$result" == "0" ]]
|
||||
[[ "$result" == "0" ]]
|
||||
}
|
||||
|
||||
@test "get_dir_size_kb: returns TIMEOUT when size calculation hangs" {
|
||||
mkdir -p "$HOME/www/stuck-project/node_modules"
|
||||
mkdir -p "$HOME/www/stuck-project/node_modules"
|
||||
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/core/common.sh'
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
run_with_timeout() { return 124; }
|
||||
get_dir_size_kb '$HOME/www/stuck-project/node_modules'
|
||||
")
|
||||
|
||||
[[ "$result" == "TIMEOUT" ]]
|
||||
[[ "$result" == "TIMEOUT" ]]
|
||||
}
|
||||
|
||||
@test "clean_project_artifacts: restores caller INT/TERM traps" {
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
set -euo pipefail
|
||||
export HOME='$HOME'
|
||||
source '$PROJECT_ROOT/lib/core/common.sh'
|
||||
@@ -669,92 +669,108 @@ EOF
|
||||
fi
|
||||
")
|
||||
|
||||
[[ "$result" == *"PASS"* ]]
|
||||
[[ "$result" == *"PASS"* ]]
|
||||
}
|
||||
|
||||
@test "clean_project_artifacts: handles empty directory gracefully" {
|
||||
run bash -c "
|
||||
run bash -c "
|
||||
export HOME='$HOME'
|
||||
source '$PROJECT_ROOT/lib/core/common.sh'
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
clean_project_artifacts
|
||||
" < /dev/null
|
||||
" </dev/null
|
||||
|
||||
[[ "$status" -eq 0 ]] || [[ "$status" -eq 2 ]]
|
||||
[[ "$status" -eq 0 ]] || [[ "$status" -eq 2 ]]
|
||||
}
|
||||
|
||||
@test "clean_project_artifacts: scans and finds artifacts" {
|
||||
if ! command -v gtimeout >/dev/null 2>&1 && ! command -v timeout >/dev/null 2>&1; then
|
||||
skip "gtimeout/timeout not available"
|
||||
fi
|
||||
if ! command -v gtimeout >/dev/null 2>&1 && ! command -v timeout >/dev/null 2>&1; then
|
||||
skip "gtimeout/timeout not available"
|
||||
fi
|
||||
|
||||
mkdir -p "$HOME/www/test-project/node_modules/package1"
|
||||
echo "test data" > "$HOME/www/test-project/node_modules/package1/index.js"
|
||||
mkdir -p "$HOME/www/test-project/node_modules/package1"
|
||||
echo "test data" >"$HOME/www/test-project/node_modules/package1/index.js"
|
||||
|
||||
mkdir -p "$HOME/www/test-project"
|
||||
mkdir -p "$HOME/www/test-project"
|
||||
|
||||
timeout_cmd="timeout"
|
||||
command -v timeout >/dev/null 2>&1 || timeout_cmd="gtimeout"
|
||||
timeout_cmd="timeout"
|
||||
command -v timeout >/dev/null 2>&1 || timeout_cmd="gtimeout"
|
||||
|
||||
run bash -c "
|
||||
run bash -c "
|
||||
export HOME='$HOME'
|
||||
$timeout_cmd 5 '$PROJECT_ROOT/bin/purge.sh' 2>&1 < /dev/null || true
|
||||
"
|
||||
|
||||
[[ "$output" =~ "Scanning" ]] ||
|
||||
[[ "$output" =~ "Purge complete" ]] ||
|
||||
[[ "$output" =~ "No old" ]] ||
|
||||
[[ "$output" =~ "Great" ]]
|
||||
[[ "$output" =~ "Scanning" ]] ||
|
||||
[[ "$output" =~ "Purge complete" ]] ||
|
||||
[[ "$output" =~ "No old" ]] ||
|
||||
[[ "$output" =~ "Great" ]]
|
||||
}
|
||||
|
||||
@test "mo purge: command exists and is executable" {
|
||||
[ -x "$PROJECT_ROOT/mole" ]
|
||||
[ -f "$PROJECT_ROOT/bin/purge.sh" ]
|
||||
[ -x "$PROJECT_ROOT/mole" ]
|
||||
[ -f "$PROJECT_ROOT/bin/purge.sh" ]
|
||||
}
|
||||
|
||||
@test "mo purge: shows in help text" {
|
||||
run env HOME="$HOME" "$PROJECT_ROOT/mole" --help
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"mo purge"* ]]
|
||||
run env HOME="$HOME" "$PROJECT_ROOT/mole" --help
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"mo purge"* ]]
|
||||
}
|
||||
|
||||
@test "mo purge: accepts --debug flag" {
|
||||
if ! command -v gtimeout >/dev/null 2>&1 && ! command -v timeout >/dev/null 2>&1; then
|
||||
skip "gtimeout/timeout not available"
|
||||
fi
|
||||
if ! command -v gtimeout >/dev/null 2>&1 && ! command -v timeout >/dev/null 2>&1; then
|
||||
skip "gtimeout/timeout not available"
|
||||
fi
|
||||
|
||||
timeout_cmd="timeout"
|
||||
command -v timeout >/dev/null 2>&1 || timeout_cmd="gtimeout"
|
||||
timeout_cmd="timeout"
|
||||
command -v timeout >/dev/null 2>&1 || timeout_cmd="gtimeout"
|
||||
|
||||
run bash -c "
|
||||
run bash -c "
|
||||
export HOME='$HOME'
|
||||
$timeout_cmd 2 '$PROJECT_ROOT/mole' purge --debug < /dev/null 2>&1 || true
|
||||
"
|
||||
true
|
||||
true
|
||||
}
|
||||
|
||||
@test "mo purge: accepts --dry-run flag" {
|
||||
if ! command -v gtimeout >/dev/null 2>&1 && ! command -v timeout >/dev/null 2>&1; then
|
||||
skip "gtimeout/timeout not available"
|
||||
fi
|
||||
|
||||
timeout_cmd="timeout"
|
||||
command -v timeout >/dev/null 2>&1 || timeout_cmd="gtimeout"
|
||||
|
||||
run bash -c "
|
||||
export HOME='$HOME'
|
||||
$timeout_cmd 2 '$PROJECT_ROOT/mole' purge --dry-run < /dev/null 2>&1 || true
|
||||
"
|
||||
|
||||
[[ "$output" == *"DRY RUN MODE"* ]] || [[ "$output" == *"Dry run complete"* ]]
|
||||
}
|
||||
|
||||
@test "mo purge: creates cache directory for stats" {
|
||||
if ! command -v gtimeout >/dev/null 2>&1 && ! command -v timeout >/dev/null 2>&1; then
|
||||
skip "gtimeout/timeout not available"
|
||||
fi
|
||||
if ! command -v gtimeout >/dev/null 2>&1 && ! command -v timeout >/dev/null 2>&1; then
|
||||
skip "gtimeout/timeout not available"
|
||||
fi
|
||||
|
||||
timeout_cmd="timeout"
|
||||
command -v timeout >/dev/null 2>&1 || timeout_cmd="gtimeout"
|
||||
timeout_cmd="timeout"
|
||||
command -v timeout >/dev/null 2>&1 || timeout_cmd="gtimeout"
|
||||
|
||||
bash -c "
|
||||
bash -c "
|
||||
export HOME='$HOME'
|
||||
$timeout_cmd 2 '$PROJECT_ROOT/mole' purge < /dev/null 2>&1 || true
|
||||
"
|
||||
|
||||
[ -d "$HOME/.cache/mole" ] || [ -d "${XDG_CACHE_HOME:-$HOME/.cache}/mole" ]
|
||||
[ -d "$HOME/.cache/mole" ] || [ -d "${XDG_CACHE_HOME:-$HOME/.cache}/mole" ]
|
||||
}
|
||||
|
||||
# .NET bin directory detection tests
|
||||
@test "is_dotnet_bin_dir: finds .NET context in parent directory with Debug dir" {
|
||||
mkdir -p "$HOME/www/dotnet-app/bin/Debug"
|
||||
touch "$HOME/www/dotnet-app/MyProject.csproj"
|
||||
mkdir -p "$HOME/www/dotnet-app/bin/Debug"
|
||||
touch "$HOME/www/dotnet-app/MyProject.csproj"
|
||||
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
if is_dotnet_bin_dir '$HOME/www/dotnet-app/bin'; then
|
||||
echo 'FOUND'
|
||||
@@ -763,14 +779,14 @@ EOF
|
||||
fi
|
||||
")
|
||||
|
||||
[[ "$result" == "FOUND" ]]
|
||||
[[ "$result" == "FOUND" ]]
|
||||
}
|
||||
|
||||
@test "is_dotnet_bin_dir: requires .csproj AND Debug/Release" {
|
||||
mkdir -p "$HOME/www/dotnet-app/bin"
|
||||
touch "$HOME/www/dotnet-app/MyProject.csproj"
|
||||
mkdir -p "$HOME/www/dotnet-app/bin"
|
||||
touch "$HOME/www/dotnet-app/MyProject.csproj"
|
||||
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
if is_dotnet_bin_dir '$HOME/www/dotnet-app/bin'; then
|
||||
echo 'FOUND'
|
||||
@@ -779,15 +795,15 @@ EOF
|
||||
fi
|
||||
")
|
||||
|
||||
# Should not find it because Debug/Release directories don't exist
|
||||
[[ "$result" == "NOT_FOUND" ]]
|
||||
# Should not find it because Debug/Release directories don't exist
|
||||
[[ "$result" == "NOT_FOUND" ]]
|
||||
}
|
||||
|
||||
@test "is_dotnet_bin_dir: rejects non-bin directories" {
|
||||
mkdir -p "$HOME/www/dotnet-app/obj"
|
||||
touch "$HOME/www/dotnet-app/MyProject.csproj"
|
||||
mkdir -p "$HOME/www/dotnet-app/obj"
|
||||
touch "$HOME/www/dotnet-app/MyProject.csproj"
|
||||
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
if is_dotnet_bin_dir '$HOME/www/dotnet-app/obj'; then
|
||||
echo 'FOUND'
|
||||
@@ -795,19 +811,18 @@ EOF
|
||||
echo 'NOT_FOUND'
|
||||
fi
|
||||
")
|
||||
[[ "$result" == "NOT_FOUND" ]]
|
||||
[[ "$result" == "NOT_FOUND" ]]
|
||||
}
|
||||
|
||||
|
||||
# Integration test for bin scanning
|
||||
@test "scan_purge_targets: includes .NET bin directories with Debug/Release" {
|
||||
mkdir -p "$HOME/www/dotnet-app/bin/Debug"
|
||||
touch "$HOME/www/dotnet-app/MyProject.csproj"
|
||||
mkdir -p "$HOME/www/dotnet-app/bin/Debug"
|
||||
touch "$HOME/www/dotnet-app/MyProject.csproj"
|
||||
|
||||
local scan_output
|
||||
scan_output="$(mktemp)"
|
||||
local scan_output
|
||||
scan_output="$(mktemp)"
|
||||
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
scan_purge_targets '$HOME/www' '$scan_output'
|
||||
if grep -q '$HOME/www/dotnet-app/bin' '$scan_output'; then
|
||||
@@ -817,19 +832,19 @@ EOF
|
||||
fi
|
||||
")
|
||||
|
||||
rm -f "$scan_output"
|
||||
rm -f "$scan_output"
|
||||
|
||||
[[ "$result" == "FOUND" ]]
|
||||
[[ "$result" == "FOUND" ]]
|
||||
}
|
||||
|
||||
@test "scan_purge_targets: skips generic bin directories (non-.NET)" {
|
||||
mkdir -p "$HOME/www/ruby-app/bin"
|
||||
touch "$HOME/www/ruby-app/Gemfile"
|
||||
mkdir -p "$HOME/www/ruby-app/bin"
|
||||
touch "$HOME/www/ruby-app/Gemfile"
|
||||
|
||||
local scan_output
|
||||
scan_output="$(mktemp)"
|
||||
local scan_output
|
||||
scan_output="$(mktemp)"
|
||||
|
||||
result=$(bash -c "
|
||||
result=$(bash -c "
|
||||
source '$PROJECT_ROOT/lib/clean/project.sh'
|
||||
scan_purge_targets '$HOME/www' '$scan_output'
|
||||
if grep -q '$HOME/www/ruby-app/bin' '$scan_output'; then
|
||||
@@ -839,6 +854,6 @@ EOF
|
||||
fi
|
||||
")
|
||||
|
||||
rm -f "$scan_output"
|
||||
[[ "$result" == "SKIPPED" ]]
|
||||
rm -f "$scan_output"
|
||||
[[ "$result" == "SKIPPED" ]]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user