skills/.specify/scripts/bash/tests/test-agent-update.sh
dan 955b6905cc test: add agent file update tests and fix section ordering bug
33 tests covering:
- Basic functionality (tech/change entries)
- Missing section handling
- Timestamp updates
- Idempotency (no duplicates)
- Change entry limits (max 2 kept)
- Database entries
- Tech entry placement
- Edge cases (empty, NEEDS CLARIFICATION, EOF)
- format_technology_stack function

Bug fixed: Recent Changes section was skipped when preceded by
Active Technologies section. The ## header was caught by generic
"close tech section on any ##" logic before reaching Recent Changes
handling. Reordered conditions to check for Recent Changes first.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 23:12:01 -08:00

512 lines
13 KiB
Bash
Executable file

#!/usr/bin/env bash
# Tests for update_existing_agent_file function
# Run: bash .specify/scripts/bash/tests/test-agent-update.sh
set -uo pipefail
# Note: not using -e so we can continue after failed assertions
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PASSED=0
FAILED=0
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color
# Test fixtures directory
FIXTURES_DIR="$SCRIPT_DIR/fixtures/agent-update"
mkdir -p "$FIXTURES_DIR"
# Mock logging functions (silence in tests)
log_info() { :; }
log_error() { echo "ERROR: $1" >&2; }
log_warning() { :; }
log_success() { :; }
# Extract format_technology_stack function
eval "$(sed -n '/^format_technology_stack()/,/^}/p' "$SCRIPT_DIR/../update-agent-context.sh")"
# Extract update_existing_agent_file function - use more specific pattern
# The function ends with a single } on its own line before the next function
eval "$(awk '/^update_existing_agent_file\(\)/,/^}$/ {print; if (/^}$/) exit}' "$SCRIPT_DIR/../update-agent-context.sh")"
# Test helper: compare files
assert_file_eq() {
local description="$1"
local expected_file="$2"
local actual_file="$3"
if diff -q "$expected_file" "$actual_file" > /dev/null 2>&1; then
echo -e "${GREEN}PASS${NC}: $description"
((PASSED++))
else
echo -e "${RED}FAIL${NC}: $description"
echo " Expected:"
cat "$expected_file" | sed 's/^/ /'
echo " Actual:"
cat "$actual_file" | sed 's/^/ /'
echo " Diff:"
diff "$expected_file" "$actual_file" | sed 's/^/ /' || true
((FAILED++))
fi
}
# Test helper: check file contains pattern
assert_contains() {
local description="$1"
local pattern="$2"
local file="$3"
if grep -q "$pattern" "$file" 2>/dev/null; then
echo -e "${GREEN}PASS${NC}: $description"
((PASSED++))
else
echo -e "${RED}FAIL${NC}: $description"
echo " Pattern not found: '$pattern'"
echo " File contents:"
cat "$file" | sed 's/^/ /'
((FAILED++))
fi
}
# Test helper: check file does NOT contain pattern
assert_not_contains() {
local description="$1"
local pattern="$2"
local file="$3"
if ! grep -q "$pattern" "$file" 2>/dev/null; then
echo -e "${GREEN}PASS${NC}: $description"
((PASSED++))
else
echo -e "${RED}FAIL${NC}: $description"
echo " Pattern found but should not be: '$pattern'"
echo " File contents:"
cat "$file" | sed 's/^/ /'
((FAILED++))
fi
}
# Test helper: count occurrences
assert_count() {
local description="$1"
local expected="$2"
local pattern="$3"
local file="$4"
local actual
actual=$(grep -c "$pattern" "$file" 2>/dev/null || echo "0")
if [[ "$actual" == "$expected" ]]; then
echo -e "${GREEN}PASS${NC}: $description"
((PASSED++))
else
echo -e "${RED}FAIL${NC}: $description"
echo " Expected count: $expected"
echo " Actual count: $actual"
echo " Pattern: '$pattern'"
((FAILED++))
fi
}
# Setup test file
setup_test_file() {
local content="$1"
local test_file
test_file=$(mktemp)
echo -e "$content" > "$test_file"
echo "$test_file"
}
# Cleanup
cleanup_test_file() {
rm -f "$1"
}
echo "=== Agent File Update Tests ==="
echo ""
# --- Basic Functionality ---
echo "## Basic Functionality"
# Test: Normal update with both sections
NEW_LANG="Python 3.11"
NEW_FRAMEWORK="FastAPI"
NEW_DB=""
CURRENT_BRANCH="feature-auth"
test_file=$(setup_test_file "# Project
## Active Technologies
- JavaScript + React (main)
## Recent Changes
- old-feature: Added logging
## Other Section
Some content")
update_existing_agent_file "$test_file" "2024-01-15"
assert_contains "Adds new tech entry" "Python 3.11 + FastAPI (feature-auth)" "$test_file"
assert_contains "Adds new change entry" "feature-auth: Added Python 3.11 + FastAPI" "$test_file"
assert_contains "Preserves existing tech entry" "JavaScript + React (main)" "$test_file"
assert_contains "Preserves other sections" "## Other Section" "$test_file"
cleanup_test_file "$test_file"
# --- Missing Sections ---
echo ""
echo "## Missing Sections"
# Test: Missing Active Technologies section
NEW_LANG="Rust"
NEW_FRAMEWORK=""
NEW_DB=""
CURRENT_BRANCH="add-rust"
test_file=$(setup_test_file "# Project
## Some Other Section
Content here
## Recent Changes
- old: stuff")
update_existing_agent_file "$test_file" "2024-01-15"
assert_contains "Creates Active Technologies section" "## Active Technologies" "$test_file"
assert_contains "Adds tech to new section" "Rust (add-rust)" "$test_file"
cleanup_test_file "$test_file"
# Test: Missing Recent Changes section
NEW_LANG="Go"
NEW_FRAMEWORK=""
NEW_DB=""
CURRENT_BRANCH="add-go"
test_file=$(setup_test_file "# Project
## Active Technologies
- Python (main)")
update_existing_agent_file "$test_file" "2024-01-15"
assert_contains "Creates Recent Changes section" "## Recent Changes" "$test_file"
assert_contains "Adds change to new section" "add-go: Added Go" "$test_file"
cleanup_test_file "$test_file"
# Test: Both sections missing
NEW_LANG="TypeScript"
NEW_FRAMEWORK="Express"
NEW_DB=""
CURRENT_BRANCH="add-ts"
test_file=$(setup_test_file "# Project
Just some content here.")
update_existing_agent_file "$test_file" "2024-01-15"
assert_contains "Creates Active Technologies when missing" "## Active Technologies" "$test_file"
assert_contains "Creates Recent Changes when missing" "## Recent Changes" "$test_file"
assert_contains "Adds tech entry" "TypeScript + Express (add-ts)" "$test_file"
assert_contains "Adds change entry" "add-ts: Added TypeScript + Express" "$test_file"
cleanup_test_file "$test_file"
# --- Timestamp Updates ---
echo ""
echo "## Timestamp Updates"
NEW_LANG=""
NEW_FRAMEWORK=""
NEW_DB=""
CURRENT_BRANCH="update-test"
test_file=$(setup_test_file "# Project
**Last updated**: 2023-06-01
## Active Technologies
- Python (main)")
update_existing_agent_file "$test_file" "2024-01-15"
assert_contains "Updates timestamp" "2024-01-15" "$test_file"
assert_not_contains "Old timestamp removed" "2023-06-01" "$test_file"
cleanup_test_file "$test_file"
# --- Idempotency ---
echo ""
echo "## Idempotency"
# Test: Don't add duplicate tech entries
NEW_LANG="Python"
NEW_FRAMEWORK="Django"
NEW_DB=""
CURRENT_BRANCH="feature-x"
test_file=$(setup_test_file "# Project
## Active Technologies
- Python + Django (feature-x)
## Recent Changes
- feature-x: Added Python + Django")
update_existing_agent_file "$test_file" "2024-01-15"
# Check tech section specifically - should only have one tech entry line
assert_count "No duplicate tech entry in Active Technologies" "1" "^- Python + Django (feature-x)$" "$test_file"
cleanup_test_file "$test_file"
# --- Change Entry Limits ---
echo ""
echo "## Change Entry Limits (max 2 kept)"
NEW_LANG="Rust"
NEW_FRAMEWORK=""
NEW_DB=""
CURRENT_BRANCH="new-feature"
test_file=$(setup_test_file "# Project
## Recent Changes
- old-1: First old change
- old-2: Second old change
- old-3: Third old change
- old-4: Fourth old change
## Other Section")
update_existing_agent_file "$test_file" "2024-01-15"
assert_contains "New change added first" "new-feature: Added Rust" "$test_file"
assert_contains "Keeps first old change" "old-1: First old change" "$test_file"
assert_contains "Keeps second old change" "old-2: Second old change" "$test_file"
assert_not_contains "Removes third old change" "old-3:" "$test_file"
assert_not_contains "Removes fourth old change" "old-4:" "$test_file"
cleanup_test_file "$test_file"
# --- Database Entries ---
echo ""
echo "## Database Entries"
NEW_LANG=""
NEW_FRAMEWORK=""
NEW_DB="PostgreSQL"
CURRENT_BRANCH="add-db"
test_file=$(setup_test_file "# Project
## Active Technologies
- Python (main)
## Recent Changes
- old: stuff")
update_existing_agent_file "$test_file" "2024-01-15"
assert_contains "Adds database entry to tech section" "PostgreSQL (add-db)" "$test_file"
assert_contains "Adds database to change entry" "add-db: Added PostgreSQL" "$test_file"
cleanup_test_file "$test_file"
# Test: DB with N/A value not added
NEW_LANG=""
NEW_FRAMEWORK=""
NEW_DB="N/A"
CURRENT_BRANCH="no-db"
test_file=$(setup_test_file "# Project
## Active Technologies
- Python (main)")
update_existing_agent_file "$test_file" "2024-01-15"
assert_not_contains "N/A database not added" "N/A (no-db)" "$test_file"
cleanup_test_file "$test_file"
# --- Tech Entry Placement ---
echo ""
echo "## Tech Entry Placement"
# Test: Tech entries added before next section header
NEW_LANG="Java"
NEW_FRAMEWORK=""
NEW_DB=""
CURRENT_BRANCH="add-java"
test_file=$(setup_test_file "## Active Technologies
- Python (main)
## Next Section
content")
update_existing_agent_file "$test_file" "2024-01-15"
# The Java entry should appear between Python and Next Section
assert_contains "Tech entry added to section" "Java (add-java)" "$test_file"
# Verify order: Java should come after Python line
if grep -n "Python" "$test_file" | head -1 | cut -d: -f1 > /tmp/python_line && \
grep -n "Java" "$test_file" | head -1 | cut -d: -f1 > /tmp/java_line; then
python_line=$(cat /tmp/python_line)
java_line=$(cat /tmp/java_line)
if [[ "$java_line" -gt "$python_line" ]]; then
echo -e "${GREEN}PASS${NC}: Tech entry appears after existing entries"
((PASSED++))
else
echo -e "${RED}FAIL${NC}: Tech entry appears after existing entries"
echo " Java at line $java_line, Python at line $python_line"
((FAILED++))
fi
rm -f /tmp/python_line /tmp/java_line
fi
cleanup_test_file "$test_file"
# Test: Tech entries added before blank line in section
NEW_LANG="Ruby"
NEW_FRAMEWORK=""
NEW_DB=""
CURRENT_BRANCH="add-ruby"
test_file=$(setup_test_file "## Active Technologies
- Python (main)
## Other Section")
update_existing_agent_file "$test_file" "2024-01-15"
assert_contains "Tech entry added before blank line" "Ruby (add-ruby)" "$test_file"
cleanup_test_file "$test_file"
# --- Edge Cases ---
echo ""
echo "## Edge Cases"
# Test: Empty NEW_LANG and NEW_FRAMEWORK (no tech to add)
NEW_LANG=""
NEW_FRAMEWORK=""
NEW_DB=""
CURRENT_BRANCH="no-tech"
test_file=$(setup_test_file "# Project
## Active Technologies
- Python (main)
## Recent Changes
- old: change")
update_existing_agent_file "$test_file" "2024-01-15"
assert_not_contains "No empty tech entry added" "( no-tech)" "$test_file"
cleanup_test_file "$test_file"
# Test: NEEDS CLARIFICATION values not added
NEW_LANG="NEEDS CLARIFICATION"
NEW_FRAMEWORK=""
NEW_DB="NEEDS CLARIFICATION"
CURRENT_BRANCH="unclear"
test_file=$(setup_test_file "# Project
## Active Technologies
- Python (main)")
update_existing_agent_file "$test_file" "2024-01-15"
assert_not_contains "NEEDS CLARIFICATION language not added" "NEEDS CLARIFICATION" "$test_file"
cleanup_test_file "$test_file"
# Test: File ending with tech section (no next header)
NEW_LANG="Scala"
NEW_FRAMEWORK=""
NEW_DB=""
CURRENT_BRANCH="add-scala"
test_file=$(setup_test_file "# Project
## Active Technologies
- Python (main)")
update_existing_agent_file "$test_file" "2024-01-15"
assert_contains "Tech added to section at EOF" "Scala (add-scala)" "$test_file"
cleanup_test_file "$test_file"
# --- Format Technology Stack ---
echo ""
echo "## format_technology_stack Function"
result=$(format_technology_stack "Python" "Flask")
if [[ "$result" == "Python + Flask" ]]; then
echo -e "${GREEN}PASS${NC}: format_technology_stack with lang and framework"
((PASSED++))
else
echo -e "${RED}FAIL${NC}: format_technology_stack with lang and framework"
echo " Expected: 'Python + Flask'"
echo " Actual: '$result'"
((FAILED++))
fi
result=$(format_technology_stack "Python" "")
if [[ "$result" == "Python" ]]; then
echo -e "${GREEN}PASS${NC}: format_technology_stack with lang only"
((PASSED++))
else
echo -e "${RED}FAIL${NC}: format_technology_stack with lang only"
echo " Expected: 'Python'"
echo " Actual: '$result'"
((FAILED++))
fi
result=$(format_technology_stack "" "")
if [[ "$result" == "" ]]; then
echo -e "${GREEN}PASS${NC}: format_technology_stack with nothing"
((PASSED++))
else
echo -e "${RED}FAIL${NC}: format_technology_stack with nothing"
echo " Expected: ''"
echo " Actual: '$result'"
((FAILED++))
fi
result=$(format_technology_stack "NEEDS CLARIFICATION" "Flask")
if [[ "$result" == "Flask" ]]; then
echo -e "${GREEN}PASS${NC}: format_technology_stack filters NEEDS CLARIFICATION"
((PASSED++))
else
echo -e "${RED}FAIL${NC}: format_technology_stack filters NEEDS CLARIFICATION"
echo " Expected: 'Flask'"
echo " Actual: '$result'"
((FAILED++))
fi
# --- Summary ---
echo ""
echo "=== Summary ==="
echo -e "Passed: ${GREEN}$PASSED${NC}"
echo -e "Failed: ${RED}$FAILED${NC}"
# Cleanup fixtures
rm -rf "$FIXTURES_DIR"
if [[ $FAILED -gt 0 ]]; then
exit 1
fi