diff --git a/tests/run.sh b/tests/run.sh index 8ff543d..d7b8e0a 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -33,15 +33,38 @@ run_test() { echo -e "${CYAN}Running crabcode tests${NC}" echo "" +# ============================================================================= +# Setup: Isolate tests from user's real config +# ============================================================================= + +CRABCODE="$PROJECT_DIR/src/crabcode" +REAL_CONFIG_DIR="$HOME/.crabcode" +BACKUP_CONFIG_DIR="" + +# Temporarily move the user's real config out of the way +if [ -d "$REAL_CONFIG_DIR" ]; then + BACKUP_CONFIG_DIR=$(mktemp -d) + mv "$REAL_CONFIG_DIR" "$BACKUP_CONFIG_DIR/crabcode-backup" +fi + +# Restore user config on exit (even on failure) +cleanup_test_env() { + # Remove any test config we created + rm -rf "$REAL_CONFIG_DIR" 2>/dev/null || true + # Restore original config + if [ -n "$BACKUP_CONFIG_DIR" ] && [ -d "$BACKUP_CONFIG_DIR/crabcode-backup" ]; then + mv "$BACKUP_CONFIG_DIR/crabcode-backup" "$REAL_CONFIG_DIR" + rm -rf "$BACKUP_CONFIG_DIR" + fi +} +trap cleanup_test_env EXIT + # ============================================================================= # Unit Tests # ============================================================================= echo -e "${YELLOW}Unit Tests${NC}" -# Source the script for testing functions (without running main) -CRABCODE="$PROJECT_DIR/src/crabcode" - # Test: Script exists and is executable run_test "Script exists" "[ -f '$CRABCODE' ]" run_test "Script is executable" "[ -x '$CRABCODE' ] || chmod +x '$CRABCODE'" @@ -55,8 +78,8 @@ run_test "Version command" "'$CRABCODE' --version | grep -q 'crabcode'" # Test: Cheat command works run_test "Cheat command" "'$CRABCODE' cheat | grep -q 'CHEAT SHEET'" -# Test: Config command works without config -run_test "Config without config file" "'$CRABCODE' config 2>&1 | grep -qE '(No config|not found)'" +# Test: Config command works without config (no ~/.crabcode at all) +run_test "Config without config file" "'$CRABCODE' config 2>&1 | grep -qE '(No config|not found|No projects)'" # Test: Doctor command works run_test "Doctor command" "'$CRABCODE' doctor | grep -q 'Doctor'" @@ -100,11 +123,11 @@ EOF if command -v yq &>/dev/null; then run_test "yq installed" "true" - run_test "Parse session_name" "[ \"$(yq -r '.session_name' '$TEST_CONFIG')\" = 'testcrab' ]" - run_test "Parse workspace_base" "[ \"$(yq -r '.workspace_base' '$TEST_CONFIG')\" = '/tmp/test-workspaces' ]" - run_test "Parse workspaces.count" "[ \"$(yq -r '.workspaces.count' '$TEST_CONFIG')\" = '3' ]" - run_test "Parse ports.api_base" "[ \"$(yq -r '.ports.api_base' '$TEST_CONFIG')\" = '4000' ]" - run_test "Parse pane command" "[ \"$(yq -r '.layout.panes[1].command' '$TEST_CONFIG')\" = 'echo \"server\"' ]" + run_test "Parse session_name" "[ \"\$(yq -r '.session_name' '$TEST_CONFIG')\" = 'testcrab' ]" + run_test "Parse workspace_base" "[ \"\$(yq -r '.workspace_base' '$TEST_CONFIG')\" = '/tmp/test-workspaces' ]" + run_test "Parse workspaces.count" "[ \"\$(yq -r '.workspaces.count' '$TEST_CONFIG')\" = '3' ]" + run_test "Parse ports.api_base" "[ \"\$(yq -r '.ports.api_base' '$TEST_CONFIG')\" = '4000' ]" + run_test "Parse pane command" "[ \"\$(yq -r '.layout.panes[1].command' '$TEST_CONFIG')\" = 'echo \"server\"' ]" else echo -e " ${YELLOW}Skipping config parsing tests (yq not installed)${NC}" fi @@ -120,11 +143,53 @@ echo "" echo -e "${YELLOW}Command Parsing Tests${NC}" # Test various command patterns (these should fail gracefully without config) -run_test "Invalid number arg" "'$CRABCODE' abc 2>&1 | grep -qE '(Invalid|Error)'" +run_test "Invalid number arg" "'$CRABCODE' abc 2>&1 | grep -qE '(Invalid|Error|Unknown)'" -# Only test subcommand parsing if yq is installed (otherwise script exits early) +# ============================================================================= +# Tests requiring a minimal project config +# ============================================================================= + +# Create a minimal project config so ticket/ws commands pass config validation if command -v yq &>/dev/null; then - run_test "Unknown subcommand" "'$CRABCODE' 1 foobar 2>&1 | grep -qE '(Unknown|mean)'" + TEST_MAIN_REPO=$(mktemp -d) + git init "$TEST_MAIN_REPO" &>/dev/null + (cd "$TEST_MAIN_REPO" && git commit --allow-empty -m "init" &>/dev/null) + TEST_WS_BASE=$(mktemp -d) + + mkdir -p "$HOME/.crabcode/projects" + cat > "$HOME/.crabcode/projects/test.yaml" << PROJEOF +session_name: test +workspace_base: $TEST_WS_BASE +main_repo: $TEST_MAIN_REPO + +workspaces: + prefix: ws + branch_pattern: workspace-{N} + +layout: + panes: + - name: terminal + command: "" + - name: main + command: "" +PROJEOF + + # Set test as default project + cat > "$HOME/.crabcode/config.yaml" << GLOBALEOF +default_project: test +GLOBALEOF +fi + +# ============================================================================= +# Command Parsing Tests (require project config) +# ============================================================================= + +echo "" +echo -e "${YELLOW}Subcommand Parsing Tests${NC}" + +if command -v yq &>/dev/null; then + # "crab 1 foobar" should either say Unknown or Did you mean + run_test "Unknown subcommand" "'$CRABCODE' ws 1 foobar 2>&1 | grep -qE '(Unknown|mean)'" else echo -e " ${YELLOW}Skipping subcommand test (yq not installed)${NC}" fi @@ -136,26 +201,165 @@ fi echo "" echo -e "${YELLOW}Ticket Command Tests${NC}" -# Test: ticket with no args shows usage -run_test "Ticket no args shows usage" "'$CRABCODE' ticket 2>&1 | grep -qE 'Usage.*crab ticket'" +if command -v yq &>/dev/null; then + # Test: ticket with no args shows usage + run_test "Ticket no args shows usage" "'$CRABCODE' ticket 2>&1 | grep -qE 'Usage.*crab ticket'" -# Test: ticket with invalid identifier is rejected -run_test "Ticket rejects semicolon" "'$CRABCODE' ticket 'foo;bar' 2>&1 | grep -q 'Invalid ticket identifier'" -run_test "Ticket rejects spaces" "'$CRABCODE' ticket 'foo bar' 2>&1 | grep -q 'Invalid ticket identifier'" -run_test "Ticket rejects shell chars" "'$CRABCODE' ticket 'ENG\$(whoami)' 2>&1 | grep -q 'Invalid ticket identifier'" -run_test "Ticket rejects braces" "'$CRABCODE' ticket '{identifier}' 2>&1 | grep -q 'Invalid ticket identifier'" + # Test: ticket with invalid identifier is rejected + run_test "Ticket rejects semicolon" "'$CRABCODE' ticket 'foo;bar' 2>&1 | grep -q 'Invalid ticket identifier'" + run_test "Ticket rejects spaces" "'$CRABCODE' ticket 'foo bar' 2>&1 | grep -q 'Invalid ticket identifier'" + run_test "Ticket rejects shell chars" "'$CRABCODE' ticket 'ENG\$(whoami)' 2>&1 | grep -q 'Invalid ticket identifier'" + run_test "Ticket rejects braces" "'$CRABCODE' ticket '{identifier}' 2>&1 | grep -q 'Invalid ticket identifier'" -# Test: valid identifiers pass validation (will fail later at tmux/config, not at validation) -run_test "Ticket accepts ENG-123" "'$CRABCODE' ticket ENG-123 2>&1 | grep -vq 'Invalid ticket identifier'" -run_test "Ticket accepts PROJ_42" "'$CRABCODE' ticket PROJ_42 2>&1 | grep -vq 'Invalid ticket identifier'" + # Test: valid identifiers pass validation (will fail later at tmux, not at validation) + run_test "Ticket accepts ENG-123" "'$CRABCODE' ticket ENG-123 2>&1 | grep -vq 'Invalid ticket identifier'" + run_test "Ticket accepts PROJ_42" "'$CRABCODE' ticket PROJ_42 2>&1 | grep -vq 'Invalid ticket identifier'" -# Test: ws N ticket validation -if command -v yq &>/dev/null; then + # Test: ws N ticket validation run_test "ws ticket no id shows error" "'$CRABCODE' ws 1 ticket 2>&1 | grep -qE 'Ticket identifier required'" run_test "ws ticket rejects bad id" "'$CRABCODE' ws 1 ticket 'bad!id' 2>&1 | grep -q 'Invalid ticket identifier'" run_test "ws ticket accepts valid id" "'$CRABCODE' ws 1 ticket ENG-123 2>&1 | grep -vq 'Invalid ticket identifier'" else - echo -e " ${YELLOW}Skipping ws ticket tests (yq not installed)${NC}" + echo -e " ${YELLOW}Skipping ticket tests (yq not installed)${NC}" +fi + +# ============================================================================= +# Alias Command Tests +# ============================================================================= + +echo "" +echo -e "${YELLOW}Alias Command Tests${NC}" + +if command -v yq &>/dev/null; then + # Ensure clean alias state + if [ -f "$HOME/.crabcode/config.yaml" ]; then + yq -i 'del(.aliases)' "$HOME/.crabcode/config.yaml" 2>/dev/null || true + fi + + # Test: list aliases when none configured + run_test "Alias list (empty)" "'$CRABCODE' alias 2>&1 | grep -q 'No aliases configured'" + + # Test: set requires a name + run_test "Alias set no args" "'$CRABCODE' alias set 2>&1 | grep -q 'Usage'" + + # Test: set requires a command value + run_test "Alias set no value" "'$CRABCODE' alias set myalias 2>&1 | grep -q 'Usage'" + + # Test: set rejects invalid alias names + run_test "Alias set rejects spaces" "'$CRABCODE' alias set 'bad name' cmd 2>&1 | grep -q 'Invalid alias name'" + run_test "Alias set rejects special chars" "'$CRABCODE' alias set 'bad!name' cmd 2>&1 | grep -q 'Invalid alias name'" + + # Test: set accepts valid names + run_test "Alias set single word" "'$CRABCODE' alias set testalias1 restart 2>&1 | grep -q 'Alias set'" + run_test "Alias set with hyphen" "'$CRABCODE' alias set test-alias2 cleanup 2>&1 | grep -q 'Alias set'" + run_test "Alias set with underscore" "'$CRABCODE' alias set test_alias3 'ws new' 2>&1 | grep -q 'Alias set'" + + # Test: list shows created aliases + run_test "Alias list shows alias" "'$CRABCODE' alias 2>&1 | grep -q 'testalias1'" + run_test "Alias list shows value" "'$CRABCODE' alias 2>&1 | grep -q 'restart'" + + # Test: aliases are persisted in global config + run_test "Alias persisted in config" "yq -r '.aliases.testalias1' '$HOME/.crabcode/config.yaml' | grep -q 'restart'" + + # Test: overwrite existing alias + run_test "Alias overwrite" "'$CRABCODE' alias set testalias1 cleanup 2>&1 | grep -q 'Alias set'" + run_test "Alias overwrite persisted" "yq -r '.aliases.testalias1' '$HOME/.crabcode/config.yaml' | grep -q 'cleanup'" + + # Test: remove alias + run_test "Alias rm" "'$CRABCODE' alias rm testalias1 2>&1 | grep -q 'Removed alias'" + + # Test: remove nonexistent alias + run_test "Alias rm nonexistent" "'$CRABCODE' alias rm nonexistent 2>&1 | grep -q 'not found'" + + # Test: rm requires a name + run_test "Alias rm no args" "'$CRABCODE' alias rm 2>&1 | grep -q 'Usage'" + + # Test: unknown subcommand + run_test "Alias unknown subcommand" "'$CRABCODE' alias foobar 2>&1 | grep -q 'Unknown alias subcommand'" + + # Cleanup test aliases + "$CRABCODE" alias rm test-alias2 2>/dev/null || true + "$CRABCODE" alias rm test_alias3 2>/dev/null || true +else + echo -e " ${YELLOW}Skipping alias tests (yq not installed)${NC}" +fi + +# ============================================================================= +# Alias Resolution Tests +# ============================================================================= + +echo "" +echo -e "${YELLOW}Alias Resolution Tests${NC}" + +if command -v yq &>/dev/null; then + # Set an alias that maps to a known command + "$CRABCODE" alias set testver '--version' 2>/dev/null + + # Test: alias resolves to the target command + run_test "Alias resolves to target" "'$CRABCODE' testver 2>&1 | grep -q 'crabcode'" + + # Set a multi-word alias + "$CRABCODE" alias set testhelp '--help' 2>/dev/null + + run_test "Multi-word alias resolves" "'$CRABCODE' testhelp 2>&1 | grep -q 'crab'" + + # Cleanup + "$CRABCODE" alias rm testver 2>/dev/null || true + "$CRABCODE" alias rm testhelp 2>/dev/null || true +else + echo -e " ${YELLOW}Skipping alias resolution tests (yq not installed)${NC}" +fi + +# ============================================================================= +# Msg Command Tests +# ============================================================================= + +echo "" +echo -e "${YELLOW}Msg Command Tests${NC}" + +# Test: msg help +run_test "Msg help" "'$CRABCODE' msg help 2>&1 | grep -qE 'P2P Messaging|msg'" +run_test "Msg no args shows help" "'$CRABCODE' msg 2>&1 | grep -qE 'P2P Messaging|msg'" + +# Test: msg status without relay +run_test "Msg status shows info" "'$CRABCODE' msg status 2>&1 | grep -qE 'Message Status|Name|Relay'" + +# Test: msg say without args shows current state +run_test "Msg say shows state" "'$CRABCODE' msg say 2>&1 | grep -qE 'Text-to-speech'" + +# Test: msg say on/off toggles +run_test "Msg say on" "'$CRABCODE' msg say on 2>&1 | grep -q 'enabled'" +run_test "Msg say off" "'$CRABCODE' msg say off 2>&1 | grep -q 'disabled'" + +# Test: msg say shows updated state after toggle +run_test "Msg say reflects off state" "'$CRABCODE' msg say 2>&1 | grep -q 'off'" + +# Reset to default +"$CRABCODE" msg say on 2>/dev/null || true + +# Test: msg unknown subcommand +run_test "Msg unknown subcommand" "'$CRABCODE' msg foobar 2>&1 | grep -q 'Unknown msg command'" + +# Test: msg read without relay (should not crash) +run_test "Msg read graceful without relay" "'$CRABCODE' msg read 2>&1; true" + +# Test: msg history without relay (should not crash) +run_test "Msg history graceful without relay" "'$CRABCODE' msg history 2>&1; true" + +# Test: msg start requires python3 +if ! command -v python3 &>/dev/null; then + run_test "Msg start without python3" "'$CRABCODE' msg start 2>&1 | grep -q 'Python3'" +fi + +# ============================================================================= +# Cleanup test repos +# ============================================================================= + +if [ -n "${TEST_MAIN_REPO:-}" ]; then + rm -rf "$TEST_MAIN_REPO" 2>/dev/null || true +fi +if [ -n "${TEST_WS_BASE:-}" ]; then + rm -rf "$TEST_WS_BASE" 2>/dev/null || true fi # =============================================================================