Add bats test suite for git-manager and obsidian-vault-kb

Isolated per-test $HOME sandboxing via tests/helpers/common.bash.
Covers both plugins' wrapper scripts, their setup.sh, and
git-manager's two PreToolUse hooks (53 tests). Verified the suite
catches regressions by temporarily reintroducing the recently-fixed
vault_search.sh -g/-- ordering bug and confirming it fails.
This commit is contained in:
Henner M. Kruse
2026-08-04 19:34:51 +00:00
parent 20cdf74b58
commit 2d25e69632
9 changed files with 674 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
#!/usr/bin/env bats
# Tests for vendor/claude-code/plugins/obsidian-vault-kb/scripts/setup.sh.
load '../helpers/common'
setup() {
sandbox_home
SETUP="$REPO_ROOT/vendor/claude-code/plugins/obsidian-vault-kb/scripts/setup.sh"
SETTINGS_FILE="$HOME/.claude/settings.json"
CONFIG_FILE="$HOME/.agent-skills/obsidian-vault-kb/config.json"
VAULT="$BATS_TEST_TMPDIR/vault"
make_test_vault "$VAULT"
}
@test "writes the three Bash rules and one Read rule" {
run "$SETUP" --settings-scope user --mode append --vault v="$VAULT"
[ "$status" -eq 0 ]
allow=$(json_get "$SETTINGS_FILE" "permissions.allow")
[[ "$allow" == *'Bash(~/.agent-skills/obsidian-vault-kb/bin/vault_index.sh:*)'* ]]
[[ "$allow" == *'Bash(~/.agent-skills/obsidian-vault-kb/bin/vault_search.sh:*)'* ]]
[[ "$allow" == *'Bash(~/.agent-skills/obsidian-vault-kb/bin/vault_backlinks.sh:*)'* ]]
[[ "$allow" == *'Read(~/.agent-skills/obsidian-vault-kb/config.json)'* ]]
}
@test "--mode outside the fixed set errors" {
run "$SETUP" --settings-scope user --mode not-a-real-mode --vault v="$VAULT"
[ "$status" -eq 1 ]
[[ "$output" == *"--mode must be read-only, append, or maintain"* ]]
}
@test "at least one --vault is required" {
run "$SETUP" --settings-scope user --mode append
[ "$status" -eq 1 ]
[[ "$output" == *"at least one --vault"* ]]
}
@test "a --vault pointing at a nonexistent directory errors before writing config" {
run "$SETUP" --settings-scope user --mode append --vault v="$BATS_TEST_TMPDIR/nope"
[ "$status" -ne 0 ]
[ ! -f "$CONFIG_FILE" ]
}
@test "registers the vault path and mode in config.json" {
run "$SETUP" --settings-scope user --mode append --vault v="$VAULT"
[ "$status" -eq 0 ]
mode=$(json_get "$CONFIG_FILE" "mode")
[ "$mode" = '"append"' ]
path=$(json_get "$CONFIG_FILE" "vaults.0.path")
[ "$path" = "\"$VAULT\"" ]
}
@test "registering the same vault name twice updates the path instead of duplicating" {
VAULT2="$BATS_TEST_TMPDIR/vault2"
make_test_vault "$VAULT2"
"$SETUP" --settings-scope user --mode append --vault v="$VAULT" > /dev/null
run "$SETUP" --settings-scope user --mode maintain --vault v="$VAULT2"
[ "$status" -eq 0 ]
count=$(python3 -c "import json; print(len(json.load(open('$CONFIG_FILE'))['vaults']))")
[ "$count" -eq 1 ]
path=$(json_get "$CONFIG_FILE" "vaults.0.path")
[ "$path" = "\"$VAULT2\"" ]
mode=$(json_get "$CONFIG_FILE" "mode")
[ "$mode" = '"maintain"' ]
}
@@ -0,0 +1,52 @@
#!/usr/bin/env bats
# Tests for skills/obsidian-vault-kb/scripts/vault_backlinks.sh.
load '../helpers/common'
setup() {
sandbox_home
SCRIPT="$REPO_ROOT/skills/obsidian-vault-kb/scripts/vault_backlinks.sh"
CONFIG_FILE="$HOME/.agent-skills/obsidian-vault-kb/config.json"
VAULT="$BATS_TEST_TMPDIR/vault"
make_test_vault "$VAULT"
write_vault_config "$CONFIG_FILE" "append" "v=$VAULT"
}
@test "finds a plain [[note]] wikilink" {
run "$SCRIPT" homelab-overview
[ "$status" -eq 0 ]
[[ "$output" == *"dns-setup.md"* ]]
}
@test "finds a [[note|alias]] wikilink" {
cat > "$VAULT/Notes/router.md" << 'EOF'
See [[dns-setup|the DNS note]] for details.
EOF
run "$SCRIPT" dns-setup
[ "$status" -eq 0 ]
[[ "$output" == *"router.md"* ]]
[[ "$output" == *"homelab-overview.md"* ]]
}
@test "a note name with regex metacharacters is handled safely" {
cat > "$VAULT/Notes/weird.md" << 'EOF'
Related: [[weird.name[test]]]
EOF
run "$SCRIPT" 'weird.name[test]'
[ "$status" -eq 0 ]
[[ "$output" == *"weird.md"* ]]
}
@test "excludes backlinks that only exist inside a non-markdown file" {
echo '[[dns-setup]]' > "$VAULT/Notes/attachment2.pdf"
run "$SCRIPT" dns-setup
[ "$status" -eq 0 ]
[[ "$output" == *"homelab-overview.md"* ]]
[[ "$output" != *"attachment2.pdf"* ]]
}
@test "an unlinked note reports no backlinks found" {
run "$SCRIPT" nope-not-a-note
[ "$status" -eq 0 ]
[[ "$output" == *"no backlinks found"* ]]
}
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env bats
# Tests for skills/obsidian-vault-kb/scripts/vault_index.sh.
load '../helpers/common'
setup() {
sandbox_home
SCRIPT="$REPO_ROOT/skills/obsidian-vault-kb/scripts/vault_index.sh"
CONFIG_FILE="$HOME/.agent-skills/obsidian-vault-kb/config.json"
VAULT="$BATS_TEST_TMPDIR/vault"
make_test_vault "$VAULT"
write_vault_config "$CONFIG_FILE" "append" "v=$VAULT"
}
@test "folder structure excludes .obsidian and .git" {
run "$SCRIPT" v
[ "$status" -eq 0 ]
[[ "$output" == *"/Notes"* ]]
[[ "$output" != *".obsidian"* ]]
[[ "$output" != *"/.git"* ]]
}
@test "note count per top-level folder counts only .md files" {
run "$SCRIPT" v
[ "$status" -eq 0 ]
[[ "$output" == *"Notes: 2"* ]]
}
@test "frontmatter tags are extracted and deduplicated" {
run "$SCRIPT" v
[ "$status" -eq 0 ]
for tag in dns homelab networking overview; do
[[ "$output" == *"$tag"* ]]
done
}
@test "a note matching *overview* is flagged as a possible MOC file" {
run "$SCRIPT" v
[ "$status" -eq 0 ]
[[ "$output" == *"homelab-overview.md"* ]]
}
@test "unknown vault name errors" {
run "$SCRIPT" not-a-real-vault
[ "$status" -eq 1 ]
[[ "$output" == *"no vault named"* ]]
}
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env bats
# Tests for skills/obsidian-vault-kb/scripts/vault_search.sh.
#
# The "non-markdown file not matched" test is a direct regression guard for
# the bug where `-g` glob filters were placed after `--`, so ripgrep treated
# them as literal positional paths instead of options and the *.md filter
# silently never applied (confirmed by matching content inside a .pdf).
load '../helpers/common'
setup() {
sandbox_home
SCRIPT="$REPO_ROOT/skills/obsidian-vault-kb/scripts/vault_search.sh"
CONFIG_FILE="$HOME/.agent-skills/obsidian-vault-kb/config.json"
VAULT="$BATS_TEST_TMPDIR/vault"
make_test_vault "$VAULT"
write_vault_config "$CONFIG_FILE" "append" "v=$VAULT"
}
@test "finds a match inside a markdown note" {
run "$SCRIPT" pihole
[ "$status" -eq 0 ]
[[ "$output" == *"dns-setup.md"* ]]
}
@test "does not match content that exists only inside a non-markdown file" {
run "$SCRIPT" attachment-only-marker
[[ "$output" == *"(no matches)"* ]]
[[ "$output" != *"attachment.pdf"* ]]
}
@test "does not match content that exists only inside .obsidian" {
run "$SCRIPT" obsidian-internal-marker
[[ "$output" == *"(no matches)"* ]]
}
@test "no spurious ripgrep argument errors on stderr/stdout" {
run "$SCRIPT" pihole
[[ "$output" != *"No such file or directory"* ]]
}
@test "a subfolder argument narrows the search root" {
run "$SCRIPT" pihole v Notes
[ "$status" -eq 0 ]
[[ "$output" == *"== Search root: $VAULT/Notes =="* ]]
[[ "$output" == *"dns-setup.md"* ]]
}
@test "a subfolder containing '..' is rejected" {
run "$SCRIPT" pihole v ../../etc
[ "$status" -eq 1 ]
[[ "$output" == *"no '..' or absolute paths"* ]]
}
@test "an absolute-path subfolder is rejected" {
run "$SCRIPT" pihole v /etc
[ "$status" -eq 1 ]
[[ "$output" == *"no '..' or absolute paths"* ]]
}
@test "a subfolder symlink escaping the vault is rejected" {
ln -s /etc "$VAULT/Notes/escape-link"
run "$SCRIPT" pihole v Notes/escape-link
[ "$status" -eq 1 ]
[[ "$output" == *"outside the vault"* ]]
}