dc4a5c5b5e
- tests/helpers/common.bash: hide_command hides python3/jq/rg via a per-directory-batched PATH shim (not per-file — that took minutes), making the fail-open/grep-fallback branches testable. - 20 new tests across all 7 .bats files: unknown flags, invalid --settings-scope, missing python3/jq, grep fallback when rg is unavailable, empty/multi-vault config edge cases, a vault path that stops existing or isn't a directory, and the previously-untested tool_name != Bash branch of force-ask-on-raw-git.sh. 53 -> 73 tests. - hooks.bats: deploy the hooks once in setup_file() (BATS_FILE_TMPDIR) instead of per-test (BATS_TEST_TMPDIR) -- faster, and gives kcov one stable path per hook to aggregate coverage against instead of a fragmented copy per test. - .gitignore (first in this repo) + tests/README.md Coverage section documenting the kcov invocation and why --include-pattern needs three entries, not two. Real, tool-measured coverage via kcov: 86.15% (255/296 lines) across all 10 wrapper/setup/hook scripts in both plugins, replacing an earlier manual ~68% branch-coverage estimate.
137 lines
4.6 KiB
Bash
137 lines
4.6 KiB
Bash
# Shared helpers for this repo's bats suites. Loaded with `load
|
|
# '../helpers/common'` at the top of each .bats file.
|
|
#
|
|
# The core trick used throughout: point $HOME at a fresh
|
|
# $BATS_TEST_TMPDIR/home before each test, so every script under test that
|
|
# reads/writes ~/.agent-skills/... or ~/.claude/settings.json is fully
|
|
# sandboxed and cleaned up automatically by bats after the test — no shared
|
|
# state between tests, and never touches the real machine's config.
|
|
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
|
|
# Call from a test file's setup() after exporting HOME.
|
|
sandbox_home() {
|
|
export HOME="$BATS_TEST_TMPDIR/home"
|
|
mkdir -p "$HOME"
|
|
}
|
|
|
|
# make_test_repo <dir> — git-init a throwaway repo with one commit so
|
|
# `git status`/`log`/etc have something real to operate on.
|
|
make_test_repo() {
|
|
local dir="$1"
|
|
mkdir -p "$dir"
|
|
git -C "$dir" init -q
|
|
git -C "$dir" -c user.email=test@test -c user.name=test \
|
|
commit -q --allow-empty -m "init"
|
|
}
|
|
|
|
# write_git_manager_config <config_file> <name>=<path> [<name>=<path> ...]
|
|
# Writes a git-manager config.json registering the given repos.
|
|
write_git_manager_config() {
|
|
local config_file="$1"; shift
|
|
mkdir -p "$(dirname "$config_file")"
|
|
python3 - "$config_file" "$@" << 'PYEOF'
|
|
import json, sys
|
|
config_file, pairs = sys.argv[1], sys.argv[2:]
|
|
repos = []
|
|
for p in pairs:
|
|
name, path = p.split("=", 1)
|
|
repos.append({"name": name, "path": path})
|
|
with open(config_file, "w") as f:
|
|
json.dump({"repos": repos}, f)
|
|
PYEOF
|
|
}
|
|
|
|
# make_test_vault <dir> — a small Obsidian-shaped vault: two notes with
|
|
# frontmatter tags that wikilink each other, plus .obsidian/.git noise and
|
|
# a non-markdown attachment, so exclusion behavior is exercisable.
|
|
make_test_vault() {
|
|
local dir="$1"
|
|
mkdir -p "$dir/Notes" "$dir/.obsidian" "$dir/.git"
|
|
|
|
cat > "$dir/Notes/dns-setup.md" << 'EOF'
|
|
---
|
|
tags: [networking, dns]
|
|
---
|
|
# DNS Setup
|
|
|
|
Configured pihole as the primary resolver. See [[homelab-overview]].
|
|
EOF
|
|
|
|
cat > "$dir/Notes/homelab-overview.md" << 'EOF'
|
|
---
|
|
tags: [homelab, overview]
|
|
---
|
|
# Homelab Overview
|
|
|
|
Links to [[dns-setup]] and other topics.
|
|
EOF
|
|
|
|
echo "obsidian-internal-marker" > "$dir/.obsidian/config"
|
|
echo "attachment-only-marker" > "$dir/Notes/attachment.pdf"
|
|
}
|
|
|
|
# write_vault_config <config_file> <mode> <name>=<path> [<name>=<path> ...]
|
|
write_vault_config() {
|
|
local config_file="$1"; local mode="$2"; shift 2
|
|
mkdir -p "$(dirname "$config_file")"
|
|
python3 - "$config_file" "$mode" "$@" << 'PYEOF'
|
|
import json, sys
|
|
config_file, mode, pairs = sys.argv[1], sys.argv[2], sys.argv[3:]
|
|
vaults = []
|
|
for p in pairs:
|
|
name, path = p.split("=", 1)
|
|
vaults.append({"name": name, "path": path})
|
|
with open(config_file, "w") as f:
|
|
json.dump({"vaults": vaults, "mode": mode}, f)
|
|
PYEOF
|
|
}
|
|
|
|
# hide_command <name> — makes <name> unresolvable for the rest of the test
|
|
# by building a shim directory with symlinks to every executable currently
|
|
# reachable via $PATH *except* <name>, then replacing $PATH with just that
|
|
# directory. A plain "remove one directory from $PATH" doesn't work here:
|
|
# python3/jq/rg all live in the same directory (/usr/bin) as git/sed/grep/
|
|
# realpath/etc that the scripts under test also need, so excluding that one
|
|
# directory would break everything, not just the target command. Callers
|
|
# must do any setup that itself needs the now-hidden command (e.g. writing
|
|
# a config file with write_git_manager_config/write_vault_config, both of
|
|
# which shell out to python3) *before* calling hide_command.
|
|
hide_command() {
|
|
local hidden="$1"
|
|
local shim="$BATS_TEST_TMPDIR/shim-no-$hidden"
|
|
mkdir -p "$shim"
|
|
local dir
|
|
local oldIFS="$IFS"
|
|
IFS=':'
|
|
local dirs=($PATH)
|
|
IFS="$oldIFS"
|
|
for dir in "${dirs[@]}"; do
|
|
[ -d "$dir" ] || continue
|
|
# One `ln` call per PATH directory (not per file) — forking `basename`
|
|
# and `ln` per file made this take minutes with a few thousand files
|
|
# spread across a dozen PATH dirs. `-n` skips a name already placed by
|
|
# an earlier (higher-priority) directory, matching normal PATH lookup
|
|
# order; unreadable dirs/empty globs are silenced.
|
|
ln -sn -t "$shim" "$dir"/* 2>/dev/null || true
|
|
done
|
|
rm -f "$shim/$hidden"
|
|
export PATH="$shim"
|
|
}
|
|
|
|
# json_get <file> <dotted.path> — small helper for assertions against JSON
|
|
# written by setup.sh, without needing jq as a test dependency.
|
|
json_get() {
|
|
local file="$1" path="$2"
|
|
python3 -c '
|
|
import json, sys
|
|
with open(sys.argv[1]) as f:
|
|
data = json.load(f)
|
|
for key in sys.argv[2].split("."):
|
|
if key == "":
|
|
continue
|
|
data = data[int(key)] if key.isdigit() else data[key]
|
|
print(json.dumps(data))
|
|
' "$file" "$path"
|
|
}
|