#!/usr/bin/env bash # Test script for git-status-report using --dryrun and live checks set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" SCRIPT="$REPO_ROOT/scripts/git-status-report" PASS=0 FAIL=0 TMPDIR="" # --- Colours --- RED='\033[31m' GREEN='\033[32m' RESET='\033[0m' cleanup() { [[ -n "$TMPDIR" && -d "$TMPDIR" ]] && rm -rf "$TMPDIR" } trap cleanup EXIT setup_tmpdir() { TMPDIR=$(mktemp -d) } pass() { printf " ${GREEN}✓${RESET} %s\n" "$1" PASS=$((PASS + 1)) } fail() { printf " ${RED}✗${RESET} %s\n" "$1" [[ -n "${2:-}" ]] && printf " %s\n" "$2" FAIL=$((FAIL + 1)) } assert_contains() { local output="$1" expected="$2" label="$3" if echo "$output" | grep -qF -- "$expected"; then pass "$label" else fail "$label" "Expected to find: $expected" fi } assert_not_contains() { local output="$1" unexpected="$2" label="$3" if echo "$output" | grep -qF -- "$unexpected"; then fail "$label" "Did not expect to find: $unexpected" else pass "$label" fi } assert_exit_code() { local actual="$1" expected="$2" label="$3" if [[ "$actual" -eq "$expected" ]]; then pass "$label" else fail "$label" "Expected exit $expected, got $actual" fi } # ============================================================ echo "=== git-status-report tests ===" echo "" # --- Test: --help flag --- echo "-- help flag --" output=$(bash "$SCRIPT" --help 2>&1) || true assert_contains "$output" "Usage:" "--help shows usage" assert_contains "$output" "--dryrun" "--help mentions dryrun" # --- Test: no repos found --- echo "-- no repos found --" setup_tmpdir output=$(bash "$SCRIPT" "$TMPDIR" 2>&1) || true assert_contains "$output" "No git repositories found" "empty dir reports no repos" # --- Test: dryrun discovers repos --- echo "-- dryrun discovery --" setup_tmpdir mkdir -p "$TMPDIR/repo-a" git -C "$TMPDIR/repo-a" init -q mkdir -p "$TMPDIR/subdir/repo-b" git -C "$TMPDIR/subdir/repo-b" init -q output=$(bash "$SCRIPT" --dryrun "$TMPDIR" 2>&1) rc=$? assert_exit_code "$rc" 0 "dryrun exits 0" assert_contains "$output" "[dryrun] Would check 2 repositories:" "dryrun header with count" assert_contains "$output" "repo-a" "dryrun lists repo-a" assert_contains "$output" "repo-b" "dryrun lists repo-b" # --- Test: dryrun with -n short flag --- echo "-- dryrun short flag --" output=$(bash "$SCRIPT" -n "$TMPDIR" 2>&1) assert_contains "$output" "[dryrun]" "-n flag works as dryrun" # --- Test: clean repo --- echo "-- clean repo --" setup_tmpdir mkdir -p "$TMPDIR/clean-repo" git -C "$TMPDIR/clean-repo" init -q git -C "$TMPDIR/clean-repo" commit --allow-empty -m "init" -q output=$(bash "$SCRIPT" "$TMPDIR" 2>&1) rc=$? assert_exit_code "$rc" 0 "clean repo exits 0" assert_contains "$output" "clean and in sync" "clean repo shows clean message" # --- Test: dirty repo with uncommitted changes --- echo "-- dirty repo --" setup_tmpdir mkdir -p "$TMPDIR/dirty-repo" git -C "$TMPDIR/dirty-repo" init -q echo "hello world" > "$TMPDIR/dirty-repo/file.txt" git -C "$TMPDIR/dirty-repo" add file.txt git -C "$TMPDIR/dirty-repo" commit -m "init" -q echo "hello world modified with extra content" > "$TMPDIR/dirty-repo/file.txt" output=$(bash "$SCRIPT" "$TMPDIR" 2>&1) && rc=$? || rc=$? assert_exit_code "$rc" 1 "dirty repo exits 1" assert_contains "$output" "dirty: dirty-repo" "dirty repo header shown" assert_contains "$output" "file.txt" "changed file listed" # --- Test: untracked files --- echo "-- untracked files --" setup_tmpdir mkdir -p "$TMPDIR/untracked-repo" git -C "$TMPDIR/untracked-repo" init -q git -C "$TMPDIR/untracked-repo" commit --allow-empty -m "init" -q echo "new file content" > "$TMPDIR/untracked-repo/newfile.txt" output=$(bash "$SCRIPT" "$TMPDIR" 2>&1) && rc=$? || rc=$? assert_exit_code "$rc" 1 "untracked file makes repo dirty" assert_contains "$output" "newfile.txt" "untracked file listed" assert_contains "$output" "??" "untracked status shown" # --- Test: deleted files --- echo "-- deleted files --" setup_tmpdir mkdir -p "$TMPDIR/del-repo" git -C "$TMPDIR/del-repo" init -q echo "content to delete" > "$TMPDIR/del-repo/gone.txt" git -C "$TMPDIR/del-repo" add gone.txt git -C "$TMPDIR/del-repo" commit -m "init" -q rm "$TMPDIR/del-repo/gone.txt" output=$(bash "$SCRIPT" "$TMPDIR" 2>&1) || true assert_contains "$output" "gone.txt" "deleted file listed" # --- Test: repo with no remotes --- echo "-- no remotes --" setup_tmpdir mkdir -p "$TMPDIR/no-remote" git -C "$TMPDIR/no-remote" init -q echo "change" > "$TMPDIR/no-remote/file.txt" git -C "$TMPDIR/no-remote" add file.txt git -C "$TMPDIR/no-remote" commit -m "init" -q echo "modified" > "$TMPDIR/no-remote/file.txt" output=$(bash "$SCRIPT" "$TMPDIR" 2>&1) || true assert_contains "$output" "no remotes" "no remotes noted on dirty repo" # --- Test: ahead of remote --- echo "-- ahead of remote --" setup_tmpdir # Create a bare "remote" git init -q --bare "$TMPDIR/remote.git" mkdir -p "$TMPDIR/ahead-repo" git -C "$TMPDIR/ahead-repo" init -q git -C "$TMPDIR/ahead-repo" remote add origin "$TMPDIR/remote.git" echo "first" > "$TMPDIR/ahead-repo/file.txt" git -C "$TMPDIR/ahead-repo" add file.txt git -C "$TMPDIR/ahead-repo" commit -m "first" -q git -C "$TMPDIR/ahead-repo" push -u origin main -q 2>/dev/null || git -C "$TMPDIR/ahead-repo" push -u origin master -q 2>/dev/null # Make a local-only commit echo "second" > "$TMPDIR/ahead-repo/file.txt" git -C "$TMPDIR/ahead-repo" add file.txt git -C "$TMPDIR/ahead-repo" commit -m "second" -q output=$(bash "$SCRIPT" "$TMPDIR/ahead-repo" 2>&1) || true assert_contains "$output" "1 ahead" "ahead count shown" # --- Test: mixed clean and dirty --- echo "-- mixed repos --" setup_tmpdir mkdir -p "$TMPDIR/clean" git -C "$TMPDIR/clean" init -q git -C "$TMPDIR/clean" commit --allow-empty -m "init" -q mkdir -p "$TMPDIR/dirty" git -C "$TMPDIR/dirty" init -q echo "content" > "$TMPDIR/dirty/file.txt" git -C "$TMPDIR/dirty" add file.txt git -C "$TMPDIR/dirty" commit -m "init" -q echo "changed" > "$TMPDIR/dirty/file.txt" output=$(bash "$SCRIPT" "$TMPDIR" 2>&1) || true assert_contains "$output" "Scanned 2 repositories" "scanned count correct" assert_contains "$output" "dirty: dirty" "dirty repo listed" assert_contains "$output" "1 repositories are clean" "clean count shown" # --- Test: nested repos not double-counted --- echo "-- nested repos --" setup_tmpdir mkdir -p "$TMPDIR/outer" git -C "$TMPDIR/outer" init -q git -C "$TMPDIR/outer" commit --allow-empty -m "init" -q mkdir -p "$TMPDIR/outer/inner" git -C "$TMPDIR/outer/inner" init -q git -C "$TMPDIR/outer/inner" commit --allow-empty -m "init" -q output=$(bash "$SCRIPT" --dryrun "$TMPDIR" 2>&1) assert_contains "$output" "Would check 1 repositories:" "nested repo filtered out" assert_contains "$output" "outer" "outer repo found" assert_not_contains "$output" "inner" "inner repo excluded" # --- Test: permission denied --- echo "-- permission denied --" setup_tmpdir mkdir -p "$TMPDIR/accessible" git -C "$TMPDIR/accessible" init -q git -C "$TMPDIR/accessible" commit --allow-empty -m "init" -q mkdir -p "$TMPDIR/blocked" chmod 000 "$TMPDIR/blocked" output=$(bash "$SCRIPT" "$TMPDIR" 2>&1) rc=$? # Should still find the accessible repo assert_contains "$output" "Scanned 1 repositories" "survives permission denied" chmod 755 "$TMPDIR/blocked" # Cleanup # ============================================================ echo "" echo "Results: $PASS passed, $FAIL failed" if [[ $FAIL -gt 0 ]]; then exit 1 fi printf "${GREEN}All tests passed.${RESET}\n"