Initial addition of ms-ai-architect plugin to the open-source marketplace. Private content excluded: orchestrator/ (Linear tooling), docs/utredning/ (client investigation), generated test reports and PDF export script. skill-gen tooling moved from orchestrator/ to scripts/skill-gen/. Security scan: WARNING (risk 20/100) — no secrets, no injection found. False positive fixed: added gitleaks:allow to Python variable reference in output-validation-grounding-verification.md line 109. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
132 lines
4.1 KiB
Bash
132 lines
4.1 KiB
Bash
#!/bin/bash
|
|
# test-hooks.sh — Unit tests for ms-ai-architect hook scripts
|
|
# Usage: bash tests/test-hooks.sh
|
|
|
|
set -euo pipefail
|
|
|
|
PLUGIN_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
SCRIPTS_DIR="$PLUGIN_ROOT/hooks/scripts"
|
|
PASS=0
|
|
FAIL=0
|
|
|
|
pass() { echo -e "\033[0;32m ✓ $1\033[0m"; PASS=$((PASS + 1)); }
|
|
fail() { echo -e "\033[0;31m ✗ $1\033[0m"; FAIL=$((FAIL + 1)); }
|
|
|
|
echo "=== Hook Script Tests ==="
|
|
echo ""
|
|
|
|
# -------------------------------------------------------
|
|
# pre-edit-secrets.mjs
|
|
# -------------------------------------------------------
|
|
echo "--- pre-edit-secrets.mjs ---"
|
|
|
|
# Test 1: clean content passes
|
|
echo '{"tool_input":{"content":"Hello world","file_path":"readme.md"}}' \
|
|
| node "$SCRIPTS_DIR/pre-edit-secrets.mjs" 2>/dev/null \
|
|
&& pass "Clean content: exit 0" \
|
|
|| fail "Clean content: expected exit 0"
|
|
|
|
# Test 2: Azure Storage key blocked (constructed at runtime to avoid hook self-trigger)
|
|
AZURE_KEY_PAYLOAD=$(printf '{"tool_input":{"content":"%s","file_path":"config.ts"}}' \
|
|
"DefaultEndpointsProtocol=https;AccountName=test;AccountKey=$(printf 'a%.0s' {1..44})=")
|
|
echo "$AZURE_KEY_PAYLOAD" \
|
|
| node "$SCRIPTS_DIR/pre-edit-secrets.mjs" 2>/dev/null \
|
|
&& fail "Azure Storage key: expected exit 2 (blocked)" \
|
|
|| pass "Azure Storage key: blocked correctly"
|
|
|
|
# Test 3: GitHub token blocked (constructed at runtime)
|
|
GH_TOKEN="ghp_$(printf 'a%.0s' {1..40})"
|
|
printf '{"tool_input":{"content":"token: %s","file_path":"ci.yml"}}' "$GH_TOKEN" \
|
|
| node "$SCRIPTS_DIR/pre-edit-secrets.mjs" 2>/dev/null \
|
|
&& fail "GitHub token: expected exit 2 (blocked)" \
|
|
|| pass "GitHub token: blocked correctly"
|
|
|
|
# Test 4: empty content passes
|
|
echo '{"tool_input":{"content":"","file_path":"empty.md"}}' \
|
|
| node "$SCRIPTS_DIR/pre-edit-secrets.mjs" 2>/dev/null \
|
|
&& pass "Empty content: exit 0" \
|
|
|| fail "Empty content: expected exit 0"
|
|
|
|
# Test 5: test files skipped (constructed at runtime)
|
|
printf '{"tool_input":{"content":"api_key = \\"%s\\"","file_path":"auth.test.ts"}}' \
|
|
"$(printf 'x%.0s' {1..25})" \
|
|
| node "$SCRIPTS_DIR/pre-edit-secrets.mjs" 2>/dev/null \
|
|
&& pass "Test file: skipped (exit 0)" \
|
|
|| fail "Test file: should be skipped"
|
|
|
|
echo ""
|
|
|
|
# -------------------------------------------------------
|
|
# session-start-context.mjs
|
|
# -------------------------------------------------------
|
|
echo "--- session-start-context.mjs ---"
|
|
|
|
OUTPUT=$(CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" node "$SCRIPTS_DIR/session-start-context.mjs" 2>/dev/null)
|
|
EXIT_CODE=$?
|
|
|
|
if [ $EXIT_CODE -eq 0 ]; then
|
|
pass "Runs without error (exit 0)"
|
|
else
|
|
fail "Expected exit 0, got $EXIT_CODE"
|
|
fi
|
|
|
|
if echo "$OUTPUT" | grep -q "Architect:"; then
|
|
pass "Output contains 'Architect:' prefix"
|
|
else
|
|
fail "Output missing 'Architect:' prefix"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# -------------------------------------------------------
|
|
# stop-assessment-reminder.mjs
|
|
# -------------------------------------------------------
|
|
echo "--- stop-assessment-reminder.mjs ---"
|
|
|
|
# Test from a temp dir without .work/
|
|
TMPDIR_TEST=$(mktemp -d)
|
|
OUTPUT=$(cd "$TMPDIR_TEST" && node "$SCRIPTS_DIR/stop-assessment-reminder.mjs" 2>/dev/null)
|
|
EXIT_CODE=$?
|
|
|
|
if [ $EXIT_CODE -eq 0 ]; then
|
|
pass "No .work/: exit 0"
|
|
else
|
|
fail "No .work/: expected exit 0, got $EXIT_CODE"
|
|
fi
|
|
|
|
if [ "$OUTPUT" = "{}" ]; then
|
|
pass "No .work/: returns {}"
|
|
else
|
|
fail "No .work/: expected {}, got: $OUTPUT"
|
|
fi
|
|
|
|
# Test with a fresh .work/ session
|
|
mkdir -p "$TMPDIR_TEST/.work/test-session"
|
|
touch "$TMPDIR_TEST/.work/test-session/state.json"
|
|
OUTPUT=$(cd "$TMPDIR_TEST" && node "$SCRIPTS_DIR/stop-assessment-reminder.mjs" 2>/dev/null)
|
|
|
|
if echo "$OUTPUT" | grep -q "systemMessage"; then
|
|
pass "Fresh .work/: returns systemMessage"
|
|
else
|
|
fail "Fresh .work/: expected systemMessage in output"
|
|
fi
|
|
|
|
if echo "$OUTPUT" | grep -q "architect:adr"; then
|
|
pass "Fresh .work/: suggests /architect:adr"
|
|
else
|
|
fail "Fresh .work/: missing /architect:adr suggestion"
|
|
fi
|
|
|
|
rm -rf "$TMPDIR_TEST"
|
|
|
|
echo ""
|
|
|
|
# -------------------------------------------------------
|
|
# Summary
|
|
# -------------------------------------------------------
|
|
TOTAL=$((PASS + FAIL))
|
|
echo "=== Results: $PASS/$TOTAL passed, $FAIL failed ==="
|
|
|
|
if [ "$FAIL" -gt 0 ]; then
|
|
exit 1
|
|
fi
|