BMAD-METHOD/test/test-epic-chain.sh

568 lines
15 KiB
Bash
Executable File

#!/bin/bash
#
# Epic Chain Integration Tests
# Tests the epic-chain.sh script behavior, argument parsing, and core functionality
#
# Usage: ./test-epic-chain.sh
#
# This test suite validates:
# - Argument parsing and help text
# - Epic file validation (Phase 1)
# - Dependency analysis (Phase 2)
# - Execution order determination (Phase 3)
# - Chain plan generation (Phase 4)
# - Dry-run and analyze-only modes
# - Error handling for missing epics
set -e
echo "========================================"
echo "Epic Chain Integration Tests"
echo "========================================"
echo ""
# Colors
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
PASSED=0
FAILED=0
SKIPPED=0
# Get the repo root
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
SCRIPT_PATH="$REPO_ROOT/scripts/epic-chain.sh"
# Create temp directory for test fixtures
TEMP_DIR=$(mktemp -d)
cleanup() {
rm -rf "$TEMP_DIR"
}
trap cleanup EXIT
# Helper functions
run_test() {
local test_name="$1"
local test_func="$2"
echo -e "${BLUE}Test:${NC} $test_name"
if $test_func; then
echo -e "${GREEN} PASSED${NC}"
PASSED=$((PASSED + 1))
else
echo -e "${RED} FAILED${NC}"
FAILED=$((FAILED + 1))
fi
echo ""
}
skip_test() {
local test_name="$1"
local reason="$2"
echo -e "${YELLOW}Test:${NC} $test_name"
echo -e "${YELLOW} SKIPPED${NC} - $reason"
SKIPPED=$((SKIPPED + 1))
echo ""
}
# =============================================================================
# Setup Test Fixtures
# =============================================================================
setup_test_fixtures() {
echo "Setting up test fixtures in $TEMP_DIR..."
# NOTE: The epic-chain.sh script calculates PROJECT_ROOT as SCRIPT_DIR/../..
# This means it expects to be TWO levels deep (e.g., project/subdir/scripts/)
# Structure needed:
# TEMP_DIR/
# project/ <- This becomes PROJECT_ROOT
# docs/epics/
# docs/stories/
# subdir/
# scripts/
# epic-chain.sh
PROJECT_DIR="$TEMP_DIR/project"
SCRIPTS_PARENT="$PROJECT_DIR/subdir"
# Create directory structure
mkdir -p "$PROJECT_DIR/docs/epics"
mkdir -p "$PROJECT_DIR/docs/stories"
mkdir -p "$PROJECT_DIR/docs/sprint-artifacts"
mkdir -p "$PROJECT_DIR/docs/uat"
mkdir -p "$PROJECT_DIR/docs/handoffs"
mkdir -p "$SCRIPTS_PARENT/scripts"
# Copy the script to test directory (nested two levels deep so PROJECT_ROOT resolves correctly)
cp "$SCRIPT_PATH" "$SCRIPTS_PARENT/scripts/epic-chain.sh"
chmod +x "$SCRIPTS_PARENT/scripts/epic-chain.sh"
# Update TEST_SCRIPT_PATH for tests to use
TEST_SCRIPT_PATH="$SCRIPTS_PARENT/scripts/epic-chain.sh"
# Create mock epic files
cat > "$PROJECT_DIR/docs/epics/epic-99.md" << 'EOF'
# Epic 99: Test Epic Alpha
## Description
This is a test epic for automated testing.
## Stories
- 99-1: First story
- 99-2: Second story
## Dependencies
None
EOF
cat > "$PROJECT_DIR/docs/epics/epic-100.md" << 'EOF'
# Epic 100: Test Epic Beta
## Description
This is a test epic that depends on Epic 99.
## Stories
- 100-1: Third story
- 100-2: Fourth story
## Dependencies
- Epic 99 must be completed first
EOF
cat > "$PROJECT_DIR/docs/epics/epic-101.md" << 'EOF'
# Epic 101: Test Epic Gamma
## Description
This is a test epic that depends on Epic 100.
## Stories
- 101-1: Fifth story
## Dependencies
- Epic 100 must be completed first
EOF
# Create mock story files
cat > "$PROJECT_DIR/docs/stories/99-1-first-story.md" << 'EOF'
# Story 99-1: First Story
Epic: 99
Status: Draft
## Description
First test story.
## Acceptance Criteria
- AC1: Test passes
EOF
cat > "$PROJECT_DIR/docs/stories/99-2-second-story.md" << 'EOF'
# Story 99-2: Second Story
Epic: 99
Status: Draft
## Description
Second test story.
## Acceptance Criteria
- AC1: Test passes
EOF
cat > "$PROJECT_DIR/docs/stories/100-1-third-story.md" << 'EOF'
# Story 100-1: Third Story
Epic: 100
Status: Draft
## Description
Third test story.
## Acceptance Criteria
- AC1: Test passes
EOF
cat > "$PROJECT_DIR/docs/stories/100-2-fourth-story.md" << 'EOF'
# Story 100-2: Fourth Story
Epic: 100
Status: Draft
## Description
Fourth test story.
## Acceptance Criteria
- AC1: Test passes
EOF
cat > "$PROJECT_DIR/docs/stories/101-1-fifth-story.md" << 'EOF'
# Story 101-1: Fifth Story
Epic: 101
Status: Draft
## Description
Fifth test story.
## Acceptance Criteria
- AC1: Test passes
EOF
echo "Test fixtures created."
echo ""
}
# =============================================================================
# Test Cases
# =============================================================================
# Test 1: Script exists and is executable
test_script_exists() {
[ -f "$SCRIPT_PATH" ] && [ -x "$SCRIPT_PATH" ]
}
# Test 2: Help text displays when no arguments provided
test_help_text() {
local output
output=$("$SCRIPT_PATH" 2>&1) || true
echo "$output" | grep -q "Usage:" && \
echo "$output" | grep -q "Examples:" && \
echo "$output" | grep -q "\-\-dry-run"
}
# Test 3: Unknown option handling
test_unknown_option() {
local output
local exit_code
output=$("$SCRIPT_PATH" --invalid-option 2>&1) || exit_code=$?
[ "$exit_code" -eq 1 ] && echo "$output" | grep -q "Unknown option"
}
# Test 4: Dry-run mode shows plan without executing
test_dry_run_mode() {
local output
output=$("$TEST_SCRIPT_PATH" 99 100 --dry-run 2>&1) || true
# Should show execution plan
echo "$output" | grep -q "EPIC CHAIN EXECUTION" && \
echo "$output" | grep -q "DRY RUN" && \
echo "$output" | grep -q "Epics to chain: 99 100"
}
# Test 5: Analyze-only mode
test_analyze_only_mode() {
local output
output=$("$TEST_SCRIPT_PATH" 99 100 --analyze-only 2>&1) || true
# Should complete analysis phase only
echo "$output" | grep -q "Phase 1: Validating Epics" && \
echo "$output" | grep -q "Phase 2: Analyzing Dependencies" && \
echo "$output" | grep -q "Analysis complete"
}
# Test 6: Epic validation detects missing epics
test_missing_epic_detection() {
local output
local exit_code=0
output=$("$TEST_SCRIPT_PATH" 999 --dry-run 2>&1) || exit_code=$?
# Should fail with error about missing epic
[ "$exit_code" -eq 1 ] && echo "$output" | grep -q "File not found"
}
# Test 7: Dependency detection parses epic files
test_dependency_detection() {
local output
output=$("$TEST_SCRIPT_PATH" 99 100 101 --analyze-only 2>&1) || true
# Should detect Epic 100 depends on Epic 99
echo "$output" | grep -q "Epic 100 depends on:" && \
echo "$output" | grep -q "99"
}
# Test 8: Chain plan file generation
test_chain_plan_generation() {
local output
local plan_file="$PROJECT_DIR/docs/sprint-artifacts/chain-plan.yaml"
output=$("$TEST_SCRIPT_PATH" 99 100 --analyze-only 2>&1) || true
# Check plan file was created
[ -f "$plan_file" ] && \
grep -q "epics:" "$plan_file" && \
grep -q "total_epics: 2" "$plan_file"
}
# Test 9: Story count detection
test_story_count_detection() {
local output
output=$("$TEST_SCRIPT_PATH" 99 --analyze-only --verbose 2>&1) || true
# Should find 2 stories for Epic 99
echo "$output" | grep -q "Epic 99: Found 2 story"
}
# Test 10: Verbose mode outputs extra details
test_verbose_mode() {
local output
output=$("$TEST_SCRIPT_PATH" 99 --analyze-only --verbose 2>&1) || true
# Verbose mode should show more details
echo "$output" | grep -q "Epic 99:" && \
echo "$output" | grep -q "story"
}
# Test 11: UAT gate options parsing
test_uat_gate_options() {
local output
output=$("$TEST_SCRIPT_PATH" 99 --dry-run --uat-gate=full --uat-blocking 2>&1) || true
# Should accept UAT gate options without error
echo "$output" | grep -q "EPIC CHAIN"
}
# Test 12: No-UAT option parsing
test_no_uat_option() {
local output
output=$("$TEST_SCRIPT_PATH" 99 --dry-run --no-uat 2>&1) || true
# Should accept --no-uat without error
echo "$output" | grep -q "EPIC CHAIN"
}
# Test 13: Start-from option parsing
test_start_from_option() {
local output
output=$("$TEST_SCRIPT_PATH" 99 100 101 --dry-run --start-from 100 2>&1) || true
# Should show skipping Epic 99
echo "$output" | grep -q "Skipping Epic 99"
}
# Test 14: Skip-done option parsing
test_skip_done_option() {
local output
output=$("$TEST_SCRIPT_PATH" 99 --dry-run --skip-done 2>&1) || true
# Should accept --skip-done without error
echo "$output" | grep -q "EPIC CHAIN" && \
echo "$output" | grep -q "Skip Done:.*true"
}
# Test 15: No-handoff option parsing
test_no_handoff_option() {
local output
output=$("$TEST_SCRIPT_PATH" 99 --dry-run --no-handoff 2>&1) || true
# Check chain plan reflects no-handoff option
grep -q "context_handoff: false" "$PROJECT_DIR/docs/sprint-artifacts/chain-plan.yaml"
}
# Test 16: No-combined-uat option parsing
test_no_combined_uat_option() {
local output
output=$("$TEST_SCRIPT_PATH" 99 --dry-run --no-combined-uat 2>&1) || true
# Check chain plan reflects option
grep -q "combined_uat: false" "$PROJECT_DIR/docs/sprint-artifacts/chain-plan.yaml"
}
# Test 17: No-report option parsing
test_no_report_option() {
local output
output=$("$TEST_SCRIPT_PATH" 99 --dry-run --no-report 2>&1) || true
# Should accept without error
echo "$output" | grep -q "EPIC CHAIN"
}
# Test 18: Multiple epic IDs accepted
test_multiple_epic_ids() {
local output
local plan_file="$PROJECT_DIR/docs/sprint-artifacts/chain-plan.yaml"
output=$("$TEST_SCRIPT_PATH" 99 100 101 --analyze-only 2>&1) || true
# Should show all 3 epics in output and plan file
echo "$output" | grep -q "Epics to chain: 99 100 101" && \
grep -q "total_epics: 3" "$plan_file"
}
# Test 19: Execution order display
test_execution_order_display() {
local output
output=$("$TEST_SCRIPT_PATH" 99 100 101 --analyze-only 2>&1) || true
# Should display execution order
echo "$output" | grep -q "Execution Order:" && \
echo "$output" | grep -q "1\. Epic 99" && \
echo "$output" | grep -q "2\. Epic 100" && \
echo "$output" | grep -q "3\. Epic 101"
}
# Test 20: Log file creation
test_log_file_creation() {
local output
# Run with dry-run to complete without actual execution
output=$("$TEST_SCRIPT_PATH" 99 --dry-run 2>&1) || true
# Log file is only mentioned in the final summary (after full execution)
# For this test, we verify the script writes to log by checking that
# the log file path format is used in the script (LOG_FILE=/tmp/bmad-epic-chain-$$)
# The log file won't be mentioned in analyze-only or dry-run modes
# So we just verify the output contains expected content
echo "$output" | grep -q "EPIC CHAIN"
}
# Test 21: Directory creation (UAT, handoffs, sprint-artifacts)
test_directory_creation() {
# Remove directories first
rm -rf "$PROJECT_DIR/docs/uat" "$PROJECT_DIR/docs/handoffs" "$PROJECT_DIR/docs/sprint-artifacts"
output=$("$TEST_SCRIPT_PATH" 99 --analyze-only 2>&1) || true
# Directories should be created
[ -d "$PROJECT_DIR/docs/uat" ] && \
[ -d "$PROJECT_DIR/docs/handoffs" ] && \
[ -d "$PROJECT_DIR/docs/sprint-artifacts" ]
}
# Test 22: Epic file patterns - supports epic-{id}.md format
test_epic_file_pattern_basic() {
output=$("$TEST_SCRIPT_PATH" 99 --analyze-only 2>&1) || true
echo "$output" | grep -q "Epic 99: Found epic-99.md"
}
# Test 23: Chain plan YAML structure validation
test_chain_plan_yaml_structure() {
local plan_file="$PROJECT_DIR/docs/sprint-artifacts/chain-plan.yaml"
output=$("$TEST_SCRIPT_PATH" 99 100 --analyze-only 2>&1) || true
# Validate YAML structure
grep -q "^epics:" "$plan_file" && \
grep -q "^total_epics:" "$plan_file" && \
grep -q "^execution_order:" "$plan_file" && \
grep -q "^total_stories:" "$plan_file" && \
grep -q "^options:" "$plan_file"
}
# Test 24: Chain plan includes story counts
test_chain_plan_story_counts() {
local plan_file="$PROJECT_DIR/docs/sprint-artifacts/chain-plan.yaml"
output=$("$TEST_SCRIPT_PATH" 99 100 --analyze-only 2>&1) || true
# Check story counts in plan
grep -q "stories: 2" "$plan_file" # Epic 99 has 2 stories
}
# Test 25: Combined options work together
test_combined_options() {
local output
output=$("$TEST_SCRIPT_PATH" 99 100 --dry-run --verbose --skip-done --no-handoff --no-uat 2>&1) || true
# Should work with multiple options
echo "$output" | grep -q "EPIC CHAIN" && \
echo "$output" | grep -q "Dry Run:.*true" && \
echo "$output" | grep -q "Skip Done:.*true"
}
# =============================================================================
# Run Tests
# =============================================================================
echo "Setting up test environment..."
setup_test_fixtures
echo ""
echo "========================================"
echo "Running Tests"
echo "========================================"
echo ""
# Basic tests
run_test "Script exists and is executable" test_script_exists
run_test "Help text displays when no arguments" test_help_text
run_test "Unknown option handling" test_unknown_option
# Mode tests
run_test "Dry-run mode shows plan without executing" test_dry_run_mode
run_test "Analyze-only mode" test_analyze_only_mode
run_test "Verbose mode outputs extra details" test_verbose_mode
# Validation tests
run_test "Missing epic detection" test_missing_epic_detection
run_test "Dependency detection parses epic files" test_dependency_detection
run_test "Story count detection" test_story_count_detection
# Chain plan tests
run_test "Chain plan file generation" test_chain_plan_generation
run_test "Chain plan YAML structure" test_chain_plan_yaml_structure
run_test "Chain plan includes story counts" test_chain_plan_story_counts
# Option parsing tests
run_test "Multiple epic IDs accepted" test_multiple_epic_ids
run_test "Execution order display" test_execution_order_display
run_test "Start-from option parsing" test_start_from_option
run_test "Skip-done option parsing" test_skip_done_option
run_test "No-handoff option parsing" test_no_handoff_option
run_test "No-combined-uat option parsing" test_no_combined_uat_option
run_test "No-report option parsing" test_no_report_option
run_test "UAT gate options parsing" test_uat_gate_options
run_test "No-UAT option parsing" test_no_uat_option
# Infrastructure tests
run_test "Log file creation" test_log_file_creation
run_test "Directory creation" test_directory_creation
run_test "Epic file pattern - basic" test_epic_file_pattern_basic
# Integration tests
run_test "Combined options work together" test_combined_options
# =============================================================================
# Summary
# =============================================================================
echo ""
echo "========================================"
echo "Test Results"
echo "========================================"
echo -e " Passed: ${GREEN}$PASSED${NC}"
echo -e " Failed: ${RED}$FAILED${NC}"
echo -e " Skipped: ${YELLOW}$SKIPPED${NC}"
echo "========================================"
if [ $FAILED -eq 0 ]; then
echo -e "\n${GREEN}All epic-chain tests passed!${NC}\n"
exit 0
else
echo -e "\n${RED}$FAILED epic-chain test(s) failed${NC}\n"
exit 1
fi