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>
512 lines
13 KiB
Bash
Executable file
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
|