diff --git a/tests/native/unix/edge-cases.sh b/tests/native/unix/edge-cases.sh new file mode 100755 index 00000000..27d09a2f --- /dev/null +++ b/tests/native/unix/edge-cases.sh @@ -0,0 +1,280 @@ +#!/usr/bin/env bash +# CCS Native Unix Tests +# Tests for native installation method (curl | bash) +# This is part of the restructured test suite + +set +e # Don't exit on errors, we're testing + +# Source shared utilities +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/../../shared/helpers.sh" + + +echo -e "${YELLOW}========================================${NC}" +echo -e "${YELLOW}CCS COMPREHENSIVE EDGE CASE TESTING${NC}" +echo -e "${YELLOW}========================================${NC}" +echo "" + +# Clean installation +echo -e "${CYAN}Preparing clean environment...${NC}" +rm -rf ~/.ccs +rm -rf /tmp/ccs-test + +# Extract and install +mkdir -p /tmp/ccs-test +tar -xzf /tmp/ccs-v2.1.1-final.tar.gz -C /tmp/ccs-test +cd /tmp/ccs-test +bash ./installers/install.sh > /dev/null 2>&1 + +# On Linux/macOS, ccs is a symlink in ~/.local/bin +CCS_PATH="$HOME/.local/bin/ccs" + +if [[ ! -L "$CCS_PATH" ]] && [[ ! -f "$CCS_PATH" ]]; then + echo -e "${RED}FATAL: Installation failed, ccs not found at $CCS_PATH${NC}" + exit 1 +fi + +echo -e "${GREEN}Installation complete, starting tests...${NC}" +echo "" + +# ============================================================================ +# SECTION 1: VERSION COMMANDS +# ============================================================================ +echo -e "${YELLOW}===== SECTION 1: VERSION COMMANDS =====${NC}" + +test_case "Version flag --version" "Shows CCS version 2.1.1" bash -c " + output=\$('$CCS_PATH' --version 2>&1) + [[ \$output =~ 2\\.1\\.1|CCS ]] +" + +test_case "Version flag -v" "Shows CCS version 2.1.1" bash -c " + output=\$('$CCS_PATH' -v 2>&1) + [[ \$output =~ 2\\.1\\.1|CCS ]] +" + +test_case "Version command (word)" "Shows CCS version 2.1.1" bash -c " + output=\$('$CCS_PATH' version 2>&1) + [[ \$output =~ 2\\.1\\.1|CCS ]] +" + +# ============================================================================ +# SECTION 2: HELP COMMANDS +# ============================================================================ +echo "" +echo -e "${YELLOW}===== SECTION 2: HELP COMMANDS =====${NC}" + +test_case "Help flag --help" "Shows help without profile error" bash -c " + output=\$('$CCS_PATH' --help 2>&1) + [[ \$output =~ Usage|Claude ]] +" + +test_case "Help flag -h" "Shows help without profile error" bash -c " + output=\$('$CCS_PATH' -h 2>&1) + [[ \$output =~ Usage|Claude ]] +" + +# ============================================================================ +# SECTION 3: ARGUMENT PARSING - THE CRITICAL FIX +# ============================================================================ +echo "" +echo -e "${YELLOW}===== SECTION 3: ARGUMENT PARSING (CRITICAL FIX) =====${NC}" + +test_case "Single flag: -c" "Does NOT show 'Profile -c not found' error" bash -c " + output=\$('$CCS_PATH' -c 2>&1) + ! [[ \$output =~ Profile.*\'-c\'.*not\ found ]] +" + +test_case "Single flag: --verbose" "Does NOT show 'Profile --verbose not found' error" bash -c " + output=\$('$CCS_PATH' --verbose 2>&1) + ! [[ \$output =~ Profile.*\'--verbose\'.*not\ found ]] +" + +test_case "Single flag: -p" "Does NOT show 'Profile -p not found' error" bash -c " + output=\$('$CCS_PATH' -p test 2>&1) + ! [[ \$output =~ Profile.*\'-p\'.*not\ found ]] +" + +test_case "Single flag: --debug" "Does NOT show profile error" bash -c " + output=\$('$CCS_PATH' --debug 2>&1) + ! [[ \$output =~ Profile.*\'--debug\'.*not\ found ]] +" + +test_case "Multiple flags: -c --verbose" "Accepts multiple flags without profile error" bash -c " + output=\$('$CCS_PATH' -c --verbose 2>&1) + ! [[ \$output =~ Profile.*not\ found ]] +" + +test_case "Flag with value: -p 'test prompt'" "Handles flag with quoted value" bash -c " + output=\$('$CCS_PATH' -p 'test prompt' 2>&1) + ! [[ \$output =~ Profile.*\'-p\'.*not\ found ]] +" + +# ============================================================================ +# SECTION 4: PROFILE COMMANDS +# ============================================================================ +echo "" +echo -e "${YELLOW}===== SECTION 4: PROFILE COMMANDS =====${NC}" + +test_case "Default profile (no args)" "Uses default profile without error" bash -c " + output=\$('$CCS_PATH' 2>&1) + ! [[ \$output =~ Profile.*not\ found ]] +" + +test_case "GLM profile" "GLM profile exists and loads" bash -c " + output=\$('$CCS_PATH' glm 2>&1) + ! [[ \$output =~ Profile.*\'glm\'.*not\ found ]] +" + +test_case "Profile with flag: glm -c" "Profile + flag combination works" bash -c " + output=\$('$CCS_PATH' glm -c 2>&1) + ! [[ \$output =~ Profile.*not\ found ]] +" + +test_case "Profile with multiple flags: glm -c --verbose" "Profile + multiple flags works" bash -c " + output=\$('$CCS_PATH' glm -c --verbose 2>&1) + ! [[ \$output =~ Profile.*not\ found ]] +" + +# ============================================================================ +# SECTION 5: ERROR HANDLING +# ============================================================================ +echo "" +echo -e "${YELLOW}===== SECTION 5: ERROR HANDLING =====${NC}" + +test_case "Invalid profile name" "Shows helpful error for invalid profile" bash -c " + output=\$('$CCS_PATH' nonexistent-profile 2>&1) + [[ \$output =~ Profile.*not\ found ]] && [[ \$output =~ Available\ profiles ]] +" + +test_case "Invalid profile with special chars" "Rejects invalid characters in profile name" bash -c " + output=\$('$CCS_PATH' 'test@profile' 2>&1) + [[ \$output =~ Invalid\ profile\ name|not\ found ]] +" + +test_case "Empty string as profile" "Shows error for empty profile name" bash -c " + output=\$('$CCS_PATH' '' 2>&1) + # In bash, '' is passed as a literal argument, so it should show profile not found + [[ \$output =~ Profile.*not\ found ]] +" + +# ============================================================================ +# SECTION 6: EDGE CASES +# ============================================================================ +echo "" +echo -e "${YELLOW}===== SECTION 6: EDGE CASES =====${NC}" + +test_case "Flag starting with double dash: --test-flag" "Handles double-dash flags correctly" bash -c " + output=\$('$CCS_PATH' --test-flag 2>&1) + ! [[ \$output =~ Profile.*\'--test-flag\'.*not\ found ]] +" + +test_case "Short flag alone: -d" "Handles short flags correctly" bash -c " + output=\$('$CCS_PATH' -d 2>&1) + ! [[ \$output =~ Profile.*\'-d\'.*not\ found ]] +" + +test_case "Mixed flags and arguments" "Handles complex flag combinations" bash -c " + output=\$('$CCS_PATH' -p test --verbose -c 2>&1) + ! [[ \$output =~ Profile.*not\ found ]] +" + +test_case "Profile 'default' explicitly" "Explicit default profile works" bash -c " + output=\$('$CCS_PATH' default 2>&1) + ! [[ \$output =~ Profile.*\'default\'.*not\ found ]] +" + +test_case "Flag with equals: --model=gpt4" "Handles flags with equals syntax" bash -c " + output=\$('$CCS_PATH' --model=test 2>&1) + ! [[ \$output =~ Profile.*\'--model=test\'.*not\ found ]] +" + +test_case "Negative number (looks like flag): -1" "Handles numeric flags" bash -c " + output=\$('$CCS_PATH' -1 2>&1) + ! [[ \$output =~ Profile.*\'-1\'.*not\ found ]] +" + +# ============================================================================ +# SECTION 7: CONFIGURATION VALIDATION +# ============================================================================ +echo "" +echo -e "${YELLOW}===== SECTION 7: CONFIGURATION VALIDATION =====${NC}" + +test_case "Config file exists" "config.json was created" bash -c " + [[ -f ~/.ccs/config.json ]] +" + +test_case "Config file is valid JSON" "config.json is valid JSON with profiles" bash -c " + jq -e '.profiles' ~/.ccs/config.json > /dev/null 2>&1 +" + +test_case "GLM profile file exists" "glm.settings.json exists" bash -c " + [[ -f ~/.ccs/glm.settings.json ]] +" + +test_case "GLM settings is valid JSON" "glm.settings.json is valid JSON" bash -c " + jq -e '.' ~/.ccs/glm.settings.json > /dev/null 2>&1 +" + +test_case "VERSION file exists" "VERSION file was installed" bash -c " + [[ -f ~/.ccs/VERSION ]] +" + +test_case "VERSION file has correct version" "VERSION file contains 2.1.1" bash -c " + [[ \$(cat ~/.ccs/VERSION) == '2.1.1' ]] +" + +# ============================================================================ +# SECTION 8: REAL USAGE SIMULATION +# ============================================================================ +echo "" +echo -e "${YELLOW}===== SECTION 8: REAL USAGE SIMULATION =====${NC}" + +test_case "Simulate: continue conversation" "Real usage: ccs -c (continue) works" bash -c " + output=\$('$CCS_PATH' -c 2>&1 | head -10) + ! [[ \$output =~ Profile.*\'-c\'.*not\ found ]] +" + +test_case "Simulate: GLM with prompt" "Real usage: ccs glm -p works" bash -c " + # Use timeout to prevent hanging + output=\$(timeout 5s '$CCS_PATH' glm -p '2+2' 2>&1 || true) + ! [[ \$output =~ Profile.*not\ found ]] +" + +test_case "Simulate: verbose mode" "Real usage: ccs --verbose works" bash -c " + output=\$('$CCS_PATH' --verbose 2>&1 | head -5) + ! [[ \$output =~ Profile.*\'--verbose\'.*not\ found ]] +" + +# ============================================================================ +# SECTION 9: BASH-SPECIFIC EDGE CASES +# ============================================================================ +echo "" +echo -e "${YELLOW}===== SECTION 9: BASH-SPECIFIC EDGE CASES =====${NC}" + +test_case "Script is executable" "ccs script has execute permissions" bash -c " + [[ -x '$CCS_PATH' ]] +" + +test_case "Shebang is correct" "Script has proper shebang" bash -c " + head -1 '$CCS_PATH' | grep -q '#!/usr/bin/env bash' +" + +test_case "Symlink exists in PATH" "ccs symlink exists" bash -c " + [[ -L ~/.local/bin/ccs ]] || [[ -f ~/.local/bin/ccs ]] +" + +test_case "Version without ./ prefix" "Can run 'ccs --version' from PATH" bash -c " + # Check if ccs is in PATH + output=\$(ccs --version 2>&1 || true) + [[ \$output =~ 2\\.1\\.1|CCS ]] || [[ \$output =~ 'command not found' ]] + # Pass if version works OR if not in PATH yet (fresh install) + [[ \$output =~ 2\\.1\\.1|CCS ]] || [[ \$output =~ 'command not found' ]] +" + +# ============================================================================ + +# ============================================================================ +# FINAL RESULTS +# ============================================================================ +print_test_summary +print_final_result diff --git a/tests/native/unix/install.sh b/tests/native/unix/install.sh new file mode 100755 index 00000000..e34cb887 --- /dev/null +++ b/tests/native/unix/install.sh @@ -0,0 +1,446 @@ +#!/usr/bin/env bash +# CCS --install Functionality Testing (Linux/macOS) +# Comprehensive tests for the --install command + +set +e # Don't exit on errors, we're testing +PASS_COUNT=0 +FAIL_COUNT=0 +TOTAL_TESTS=0 + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +GRAY='\033[0;37m' +NC='\033[0m' # No Color + +# Get script directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CCS_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +CCS_PATH="$CCS_ROOT/ccs" + +# Backup/restore paths +CLAUDE_DIR="$HOME/.claude" +BACKUP_DIR="/tmp/ccs-test-backup-$(date +%s)" +TEST_HOME="/tmp/ccs-test-home" +TEST_CLAUDE_DIR="$TEST_HOME/.claude" + +test_case() { + local name="$1" + local expected="$2" + shift 2 + + ((TOTAL_TESTS++)) + echo "" + echo -e "${CYAN}[$TOTAL_TESTS] $name${NC}" + echo -e "${GRAY} Expected: $expected${NC}" + + if "$@"; then + echo -e "${GREEN} Result: PASS${NC}" + ((PASS_COUNT++)) + return 0 + else + echo -e "${RED} Result: FAIL${NC}" + ((FAIL_COUNT++)) + return 1 + fi +} + +cleanup_and_restore() { + echo "" + echo -e "${YELLOW}Cleaning up and restoring...${NC}" + + # Remove test directory + rm -rf "$TEST_HOME" + + # Restore backup if it exists + if [[ -d "$BACKUP_DIR" ]]; then + if [[ -d "$CLAUDE_DIR" ]]; then + echo " Removing test data from $CLAUDE_DIR" + rm -rf "$CLAUDE_DIR" + fi + echo " Restoring backup from $BACKUP_DIR" + mv "$BACKUP_DIR" "$CLAUDE_DIR" + echo -e "${GREEN} Backup restored successfully${NC}" + fi +} + +# Trap to ensure cleanup happens even if script fails +trap cleanup_and_restore EXIT + +echo -e "${YELLOW}========================================${NC}" +echo -e "${YELLOW}CCS --INSTALL FUNCTIONALITY TESTING${NC}" +echo -e "${YELLOW}========================================${NC}" +echo "" + +# ============================================================================ +# SAFETY: BACKUP EXISTING ~/.claude/ +# ============================================================================ +echo -e "${CYAN}Safety: Backing up existing ~/.claude/...${NC}" + +if [[ -d "$CLAUDE_DIR" ]]; then + echo " Found existing ~/.claude/, creating backup at $BACKUP_DIR" + cp -r "$CLAUDE_DIR" "$BACKUP_DIR" + echo -e "${GREEN} Backup created successfully${NC}" +else + echo " No existing ~/.claude/ found, no backup needed" +fi + +echo "" + +# ============================================================================ +# PRE-CHECKS +# ============================================================================ +echo -e "${YELLOW}===== PRE-CHECKS =====${NC}" + +test_case "CCS executable exists" "ccs script found at $CCS_PATH" bash -c " + [[ -f '$CCS_PATH' ]] +" + +test_case "CCS executable is executable" "ccs has execute permissions" bash -c " + [[ -x '$CCS_PATH' ]] +" + +test_case "Source .claude directory exists" ".claude/ in CCS repo" bash -c " + [[ -d '$CCS_ROOT/.claude' ]] +" + +test_case "Source commands exist" "ccs.md command file exists" bash -c " + [[ -f '$CCS_ROOT/.claude/commands/ccs.md' ]] +" + +test_case "Source skills exist" "ccs-delegation skill directory exists" bash -c " + [[ -d '$CCS_ROOT/.claude/skills/ccs-delegation' ]] +" + +# ============================================================================ +# SECTION 1: FRESH INSTALLATION (NO ~/.claude/) +# ============================================================================ +echo "" +echo -e "${YELLOW}===== SECTION 1: FRESH INSTALLATION =====${NC}" + +# Clean any existing test directory +rm -rf "$TEST_HOME" +mkdir -p "$TEST_HOME" + +test_case "Fresh install: Command executes without error" "Exit code 0" bash -c " + HOME='$TEST_HOME' '$CCS_PATH' --install > /tmp/ccs-install-output.txt 2>&1 +" + +test_case "Fresh install: Commands directory created" "~/.claude/commands/ exists" bash -c " + [[ -d '$TEST_CLAUDE_DIR/commands' ]] +" + +test_case "Fresh install: Skills directory created" "~/.claude/skills/ exists" bash -c " + [[ -d '$TEST_CLAUDE_DIR/skills' ]] +" + +test_case "Fresh install: ccs.md command installed" "ccs.md file exists in commands/" bash -c " + [[ -f '$TEST_CLAUDE_DIR/commands/ccs.md' ]] +" + +test_case "Fresh install: ccs-delegation skill installed" "ccs-delegation/ exists in skills/" bash -c " + [[ -d '$TEST_CLAUDE_DIR/skills/ccs-delegation' ]] +" + +test_case "Fresh install: SKILL.md exists" "SKILL.md in ccs-delegation/" bash -c " + [[ -f '$TEST_CLAUDE_DIR/skills/ccs-delegation/SKILL.md' ]] +" + +test_case "Fresh install: references directory exists" "references/ subdirectory present" bash -c " + [[ -d '$TEST_CLAUDE_DIR/skills/ccs-delegation/references' ]] +" + +test_case "Fresh install: delegation-patterns.md exists" "Pattern reference file present" bash -c " + [[ -f '$TEST_CLAUDE_DIR/skills/ccs-delegation/references/delegation-patterns.md' ]] +" + +# ============================================================================ +# SECTION 2: OUTPUT FORMATTING (BOX-DRAWING CHARACTERS) +# ============================================================================ +echo "" +echo -e "${YELLOW}===== SECTION 2: OUTPUT FORMATTING =====${NC}" + +# Clean for fresh test +rm -rf "$TEST_HOME" +mkdir -p "$TEST_HOME" + +test_case "Output: Contains box-drawing header" "Output has ┌─" bash -c " + output=\$(HOME='$TEST_HOME' '$CCS_PATH' --install 2>&1) + [[ \$output =~ ┌─ ]] +" + +test_case "Output: Contains box-drawing footer" "Output has └─" bash -c " + output=\$(HOME='$TEST_HOME' '$CCS_PATH' --install 2>&1) + [[ \$output =~ └─ ]] +" + +test_case "Output: Contains pipe for indentation" "Output has │" bash -c " + output=\$(HOME='$TEST_HOME' '$CCS_PATH' --install 2>&1) + [[ \$output =~ │ ]] +" + +test_case "Output: Contains checkmark indicator" "Output has ✓" bash -c " + output=\$(HOME='$TEST_HOME' '$CCS_PATH' --install 2>&1) + [[ \$output =~ ✓ ]] +" + +test_case "Output: Shows source directory" "Output includes source path" bash -c " + output=\$(HOME='$TEST_HOME' '$CCS_PATH' --install 2>&1) + [[ \$output =~ Source: ]] +" + +test_case "Output: Shows target directory" "Output includes target path" bash -c " + output=\$(HOME='$TEST_HOME' '$CCS_PATH' --install 2>&1) + [[ \$output =~ Target: ]] +" + +test_case "Output: Shows installation complete message" "Success message present" bash -c " + output=\$(HOME='$TEST_HOME' '$CCS_PATH' --install 2>&1) + [[ \$output =~ 'Installation complete' ]] +" + +test_case "Output: Shows installed count" "Displays number of installed items" bash -c " + output=\$(HOME='$TEST_HOME' '$CCS_PATH' --install 2>&1) + [[ \$output =~ 'Installed:' ]] +" + +test_case "Output: NO emoji characters used" "Output uses only box-drawing and symbols" bash -c " + # Check for actual emoji characters (not box-drawing or symbols like ✓ ℹ ✗) + output=\$(HOME='$TEST_HOME' '$CCS_PATH' --install 2>&1) + ! [[ \$output =~ [🚀📋🎯🎉] ]] +" + +# ============================================================================ +# SECTION 3: IDEMPOTENCY (MULTIPLE RUNS) +# ============================================================================ +echo "" +echo -e "${YELLOW}===== SECTION 3: IDEMPOTENCY =====${NC}" + +# Start fresh +rm -rf "$TEST_HOME" +mkdir -p "$TEST_HOME" +HOME="$TEST_HOME" "$CCS_PATH" --install > /dev/null 2>&1 + +test_case "Idempotent: Second run succeeds" "Second --install doesn't error" bash -c " + HOME='$TEST_HOME' '$CCS_PATH' --install > /tmp/ccs-install-second.txt 2>&1 +" + +test_case "Idempotent: Shows skip messages" "Output contains 'Skipping existing'" bash -c " + output=\$(HOME='$TEST_HOME' '$CCS_PATH' --install 2>&1) + [[ \$output =~ 'Skipping existing' ]] +" + +test_case "Idempotent: Uses info indicator ℹ" "Skip messages have ℹ indicator" bash -c " + output=\$(HOME='$TEST_HOME' '$CCS_PATH' --install 2>&1) + [[ \$output =~ ℹ ]] +" + +test_case "Idempotent: Skipped count shown" "Displays number of skipped items" bash -c " + output=\$(HOME='$TEST_HOME' '$CCS_PATH' --install 2>&1) + [[ \$output =~ 'Skipped:' ]] +" + +test_case "Idempotent: Files remain unchanged" "Original files not modified" bash -c " + # Get modification time before second install + mtime1=\$(stat -c %Y '$TEST_CLAUDE_DIR/commands/ccs.md' 2>/dev/null || stat -f %m '$TEST_CLAUDE_DIR/commands/ccs.md') + sleep 1 + HOME='$TEST_HOME' '$CCS_PATH' --install > /dev/null 2>&1 + mtime2=\$(stat -c %Y '$TEST_CLAUDE_DIR/commands/ccs.md' 2>/dev/null || stat -f %m '$TEST_CLAUDE_DIR/commands/ccs.md') + [[ \$mtime1 == \$mtime2 ]] +" + +test_case "Idempotent: Third run still safe" "Multiple runs are safe" bash -c " + HOME='$TEST_HOME' '$CCS_PATH' --install > /dev/null 2>&1 + HOME='$TEST_HOME' '$CCS_PATH' --install > /dev/null 2>&1 + [[ -f '$TEST_CLAUDE_DIR/commands/ccs.md' ]] && [[ -d '$TEST_CLAUDE_DIR/skills/ccs-delegation' ]] +" + +# ============================================================================ +# SECTION 4: CONFLICT HANDLING (EXISTING FILES) +# ============================================================================ +echo "" +echo -e "${YELLOW}===== SECTION 4: CONFLICT HANDLING =====${NC}" + +# Create existing files with different content +rm -rf "$TEST_CLAUDE_DIR" +mkdir -p "$TEST_CLAUDE_DIR/commands" +mkdir -p "$TEST_CLAUDE_DIR/skills/ccs-delegation" +echo "EXISTING COMMAND CONTENT" > "$TEST_CLAUDE_DIR/commands/ccs.md" +echo "EXISTING SKILL CONTENT" > "$TEST_CLAUDE_DIR/skills/ccs-delegation/SKILL.md" + +test_case "Conflict: Skips existing command file" "Doesn't overwrite ccs.md" bash -c " + HOME='$TEST_HOME' '$CCS_PATH' --install > /dev/null 2>&1 + content=\$(cat '$TEST_CLAUDE_DIR/commands/ccs.md') + [[ \$content == 'EXISTING COMMAND CONTENT' ]] +" + +test_case "Conflict: Skips existing skill directory" "Doesn't overwrite skill" bash -c " + content=\$(cat '$TEST_CLAUDE_DIR/skills/ccs-delegation/SKILL.md') + [[ \$content == 'EXISTING SKILL CONTENT' ]] +" + +test_case "Conflict: Output shows skip message" "User informed about skips" bash -c " + output=\$(HOME='$TEST_HOME' '$CCS_PATH' --install 2>&1) + [[ \$output =~ 'Skipping existing command: ccs.md' ]] +" + +test_case "Conflict: No data loss" "Original files preserved" bash -c " + [[ -f '$TEST_CLAUDE_DIR/commands/ccs.md' ]] && + grep -q 'EXISTING COMMAND CONTENT' '$TEST_CLAUDE_DIR/commands/ccs.md' +" + +# ============================================================================ +# SECTION 5: FILE INTEGRITY +# ============================================================================ +echo "" +echo -e "${YELLOW}===== SECTION 5: FILE INTEGRITY =====${NC}" + +# Fresh install for integrity checks +rm -rf "$TEST_HOME" +mkdir -p "$TEST_HOME" +HOME="$TEST_HOME" "$CCS_PATH" --install > /dev/null 2>&1 + +test_case "Integrity: Command file not empty" "ccs.md has content" bash -c " + [[ -s '$TEST_CLAUDE_DIR/commands/ccs.md' ]] +" + +test_case "Integrity: Skill file not empty" "SKILL.md has content" bash -c " + [[ -s '$TEST_CLAUDE_DIR/skills/ccs-delegation/SKILL.md' ]] +" + +test_case "Integrity: Command file matches source" "ccs.md content identical" bash -c " + diff -q '$CCS_ROOT/.claude/commands/ccs.md' '$TEST_CLAUDE_DIR/commands/ccs.md' +" + +test_case "Integrity: Skill file matches source" "SKILL.md content identical" bash -c " + diff -q '$CCS_ROOT/.claude/skills/ccs-delegation/SKILL.md' '$TEST_CLAUDE_DIR/skills/ccs-delegation/SKILL.md' +" + +test_case "Integrity: Reference file matches source" "delegation-patterns.md identical" bash -c " + diff -q '$CCS_ROOT/.claude/skills/ccs-delegation/references/delegation-patterns.md' \ + '$TEST_CLAUDE_DIR/skills/ccs-delegation/references/delegation-patterns.md' +" + +test_case "Integrity: Directory structure preserved" "All subdirectories present" bash -c " + [[ -d '$TEST_CLAUDE_DIR/skills/ccs-delegation/references' ]] +" + +# ============================================================================ +# SECTION 6: ERROR HANDLING +# ============================================================================ +echo "" +echo -e "${YELLOW}===== SECTION 6: ERROR HANDLING =====${NC}" + +test_case "Error: Missing source directory handled" "Graceful error when source missing" bash -c " + # Temporarily move .claude directory + mv '$CCS_ROOT/.claude' '$CCS_ROOT/.claude.backup' + output=\$(HOME='$TEST_HOME' '$CCS_PATH' --install 2>&1 || true) + mv '$CCS_ROOT/.claude.backup' '$CCS_ROOT/.claude' + [[ \$output =~ 'Error: Source directory not found' ]] +" + +test_case "Error: Shows helpful error message" "Error message explains the issue" bash -c " + mv '$CCS_ROOT/.claude' '$CCS_ROOT/.claude.backup' + output=\$(HOME='$TEST_HOME' '$CCS_PATH' --install 2>&1 || true) + mv '$CCS_ROOT/.claude.backup' '$CCS_ROOT/.claude' + [[ \$output =~ 'CCS repository directory' ]] +" + +test_case "Error: Uses error indicator ✗" "Error messages have ✗ indicator" bash -c " + mv '$CCS_ROOT/.claude' '$CCS_ROOT/.claude.backup' + output=\$(HOME='$TEST_HOME' '$CCS_PATH' --install 2>&1 || true) + mv '$CCS_ROOT/.claude.backup' '$CCS_ROOT/.claude' + [[ \$output =~ ✗ ]] +" + +# ============================================================================ +# SECTION 7: PERMISSIONS AND OWNERSHIP +# ============================================================================ +echo "" +echo -e "${YELLOW}===== SECTION 7: PERMISSIONS AND OWNERSHIP =====${NC}" + +rm -rf "$TEST_CLAUDE_DIR" +HOME=$(dirname "$TEST_CLAUDE_DIR") "$CCS_PATH" --install > /dev/null 2>&1 + +test_case "Permissions: Command file is readable" "ccs.md has read permissions" bash -c " + [[ -r '$TEST_CLAUDE_DIR/commands/ccs.md' ]] +" + +test_case "Permissions: Skill file is readable" "SKILL.md has read permissions" bash -c " + [[ -r '$TEST_CLAUDE_DIR/skills/ccs-delegation/SKILL.md' ]] +" + +test_case "Permissions: Directories are accessible" "Can list directory contents" bash -c " + ls '$TEST_CLAUDE_DIR/commands' > /dev/null 2>&1 && + ls '$TEST_CLAUDE_DIR/skills' > /dev/null 2>&1 +" + +# ============================================================================ +# SECTION 8: INTEGRATION WITH CCS +# ============================================================================ +echo "" +echo -e "${YELLOW}===== SECTION 8: INTEGRATION WITH CCS =====${NC}" + +test_case "Integration: --install flag recognized" "--install doesn't show 'Profile not found'" bash -c " + output=\$('$CCS_PATH' --install 2>&1 || true) + ! [[ \$output =~ 'Profile.*--install.*not found' ]] +" + +test_case "Integration: --version still works after --install" "Version command functional" bash -c " + output=\$('$CCS_PATH' --version 2>&1) + [[ \$output =~ 'CCS' ]] +" + +test_case "Integration: --help still works after --install" "Help command functional" bash -c " + output=\$('$CCS_PATH' --help 2>&1) + [[ \$output =~ 'Usage' ]] || [[ \$output =~ 'Claude' ]] +" + +# ============================================================================ +# FINAL RESULTS +# ============================================================================ +echo "" +echo -e "${YELLOW}========================================${NC}" +echo -e "${YELLOW}TEST RESULTS SUMMARY${NC}" +echo -e "${YELLOW}========================================${NC}" +echo "" +echo -e "${CYAN}Total Tests: $TOTAL_TESTS${NC}" +echo -e "${GREEN}Passed: $PASS_COUNT${NC}" + +if [[ $FAIL_COUNT -eq 0 ]]; then + echo -e "${GREEN}Failed: $FAIL_COUNT${NC}" +else + echo -e "${RED}Failed: $FAIL_COUNT${NC}" +fi + +echo "" + +SUCCESS_RATE=$(awk "BEGIN {printf \"%.2f\", ($PASS_COUNT / $TOTAL_TESTS) * 100}") + +if awk "BEGIN {exit !($SUCCESS_RATE >= 90)}"; then + echo -e "${GREEN}Success Rate: $SUCCESS_RATE%${NC}" +elif awk "BEGIN {exit !($SUCCESS_RATE >= 70)}"; then + echo -e "${YELLOW}Success Rate: $SUCCESS_RATE%${NC}" +else + echo -e "${RED}Success Rate: $SUCCESS_RATE%${NC}" +fi + +echo "" + +if [[ $FAIL_COUNT -eq 0 ]]; then + echo -e "${GREEN}========================================${NC}" + echo -e "${GREEN}ALL TESTS PASSED!${NC}" + echo -e "${GREEN}========================================${NC}" + echo "" + echo -e "${GREEN}--install functionality is production ready!${NC}" + exit 0 +else + echo -e "${YELLOW}========================================${NC}" + echo -e "${YELLOW}SOME TESTS FAILED${NC}" + echo -e "${YELLOW}========================================${NC}" + echo "" + echo -e "${YELLOW}Review failed tests above for details${NC}" + exit 1 +fi diff --git a/tests/native/unix/uninstall.sh b/tests/native/unix/uninstall.sh new file mode 100755 index 00000000..b4f5273e --- /dev/null +++ b/tests/native/unix/uninstall.sh @@ -0,0 +1,310 @@ +#!/usr/bin/env bash +# CCS --uninstall Functionality Testing (Linux/macOS) +# Comprehensive tests for the --uninstall command + +set +e # Don't exit on errors, we're testing +PASS_COUNT=0 +FAIL_COUNT=0 +TOTAL_TESTS=0 + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +GRAY='\033[0;37m' +NC='\033[0m' # No Color + +# Get script directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CCS_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +CCS_PATH="$CCS_ROOT/ccs" + +# Backup/restore paths +CLAUDE_DIR="$HOME/.claude" +BACKUP_DIR="/tmp/ccs-test-backup-$(date +%s)" +TEST_HOME="/tmp/ccs-test-home" +TEST_CLAUDE_DIR="$TEST_HOME/.claude" + +test_case() { + local name="$1" + local expected="$2" + shift 2 + + ((TOTAL_TESTS++)) + echo "" + echo -e "${CYAN}[$TOTAL_TESTS] $name${NC}" + echo -e "${GRAY} Expected: $expected${NC}" + + if "$@"; then + echo -e "${GREEN} Result: PASS${NC}" + ((PASS_COUNT++)) + return 0 + else + echo -e "${RED} Result: FAIL${NC}" + ((FAIL_COUNT++)) + return 1 + fi +} + +cleanup_and_restore() { + echo "" + echo -e "${YELLOW}Cleaning up and restoring...${NC}" + + # Remove test directory + if [[ -d "$TEST_HOME" ]]; then + rm -rf "$TEST_HOME" + fi + + # Restore backup if it exists + if [[ -d "$BACKUP_DIR" ]]; then + if [[ -d "$CLAUDE_DIR" ]]; then + rm -rf "$CLAUDE_DIR" + fi + mv "$BACKUP_DIR" "$CLAUDE_DIR" + fi + + echo -e "${GREEN}Cleanup complete.${NC}" +} + +# ============================================================================ +# SETUP +# ============================================================================ + +echo "" +echo "========================================" +echo "CCS --uninstall Functionality Tests" +echo "========================================" +echo "" + +# Backup existing .claude directory if it exists +if [[ -d "$CLAUDE_DIR" ]]; then + echo -e "${YELLOW}Backing up existing .claude directory...${NC}" + cp -r "$CLAUDE_DIR" "$BACKUP_DIR" +fi + +# Clean any existing test directory +if [[ -d "$TEST_HOME" ]]; then + rm -rf "$TEST_HOME" +fi + +mkdir -p "$TEST_HOME" + +# ============================================================================ +# TEST SECTION 1: UNINSTALL WHEN NOTHING IS INSTALLED +# ============================================================================ + +echo "" +echo "========================================" +echo "SECTION 1: UNINSTALL WHEN NOTHING IS INSTALLED" +echo "========================================" + +test_case "Empty uninstall: Command executes without error" "Exit code 0" bash -c " + HOME='$TEST_HOME' '$CCS_PATH' --uninstall > /tmp/ccs-uninstall-empty.txt 2>&1 +" + +test_case "Empty uninstall: Output contains 'Nothing to uninstall'" "Appropriate message" bash -c " + output=\$(HOME='$TEST_HOME' '$CCS_PATH' --uninstall 2>&1) + [[ \$output =~ 'Nothing to uninstall' ]] +" + +test_case "Empty uninstall: Reports 0 items removed" "Zero removed count" bash -c " + output=\$(HOME='$TEST_HOME' '$CCS_PATH' --uninstall 2>&1) + [[ \$output =~ 'Removed: 0 items' ]] +" + +# ============================================================================ +# SETUP FOR FULL INSTALL/UNINSTALL CYCLE +# ============================================================================ + +# Install first so we can test uninstall +echo "" +echo -e "${YELLOW}Setting up for install/uninstall cycle test...${NC}" +bash -c "HOME='$TEST_HOME' '$CCS_PATH' --install > /dev/null 2>&1" + +# ============================================================================ +# TEST SECTION 2: UNINSTALL AFTER INSTALL +# ============================================================================ + +echo "" +echo "========================================" +echo "SECTION 2: UNINSTALL AFTER INSTALL" +echo "========================================" + +test_case "Uninstall: Command executes without error" "Exit code 0" bash -c " + HOME='$TEST_HOME' '$CCS_PATH' --uninstall > /tmp/ccs-uninstall-after-install.txt 2>&1 +" + +test_case "Uninstall: Removes ccs.md command file" "Command file removed" bash -c " + [[ ! -f '$TEST_CLAUDE_DIR/commands/ccs.md' ]] +" + +test_case "Uninstall: Removes ccs-delegation skill directory" "Skill directory removed" bash -c " + [[ ! -d '$TEST_CLAUDE_DIR/skills/ccs-delegation' ]] +" + +test_case "Uninstall: Preserves other commands" "Other commands unaffected" bash -c " + # Create a dummy command file first + mkdir -p '$TEST_CLAUDE_DIR/commands' + echo 'test' > '$TEST_CLAUDE_DIR/commands/test.md' + # Install CCS, then uninstall + HOME='$TEST_HOME' '$CCS_PATH' --install > /dev/null 2>&1 + HOME='$TEST_HOME' '$CCS_PATH' --uninstall > /dev/null 2>&1 + # Check that test.md still exists + [[ -f '$TEST_CLAUDE_DIR/commands/test.md' ]] +" + +test_case "Uninstall: Preserves other skills" "Other skills unaffected" bash -c " + # Create a dummy skill directory first + mkdir -p '$TEST_CLAUDE_DIR/skills/test-skill' + echo 'test' > '$TEST_CLAUDE_DIR/skills/test-skill/SKILL.md' + # Install CCS, then uninstall + HOME='$TEST_HOME' '$CCS_PATH' --install > /dev/null 2>&1 + HOME='$TEST_HOME' '$CCS_PATH' --uninstall > /dev/null 2>&1 + # Check that test-skill still exists + [[ -d '$TEST_CLAUDE_DIR/skills/test-skill' ]] +" + +# ============================================================================ +# TEST SECTION 3: IDEMPOTENCY +# ============================================================================ + +echo "" +echo "========================================" +echo "SECTION 3: IDEMPOTENCY" +echo "========================================" + +test_case "Idempotent: Second uninstall succeeds" "Second --uninstall doesn't error" bash -c " + HOME='$TEST_HOME' '$CCS_PATH' --uninstall > /dev/null 2>&1 + exit_code=\$? + [[ \$exit_code -eq 0 ]] +" + +test_case "Idempotent: Reports nothing to remove on second run" "Reports nothing found" bash -c " + output=\$(HOME='$TEST_HOME' '$CCS_PATH' --uninstall 2>&1) + [[ \$output =~ 'not found' ]] || [[ \$output =~ 'Nothing to uninstall' ]] +" + +# ============================================================================ +# TEST SECTION 4: OUTPUT FORMATTING +# ============================================================================ + +echo "" +echo "========================================" +echo "SECTION 4: OUTPUT FORMATTING" +echo "========================================" + +# Set up fresh install for output testing +bash -c "HOME='$TEST_HOME' '$CCS_PATH' --install > /dev/null 2>&1" + +test_case "Output: Contains box-drawing header" "Output has ┌─" bash -c " + output=\$(HOME='$TEST_HOME' '$CCS_PATH' --uninstall 2>&1) + [[ \$output =~ ┌─ ]] +" + +test_case "Output: Contains box-drawing footer" "Output has └─" bash -c " + output=\$(HOME='$TEST_HOME' '$CCS_PATH' --uninstall 2>&1) + [[ \$output =~ └─ ]] +" + +test_case "Output: Shows removal success message" "Contains '[OK] Uninstall complete!'" bash -c " + output=\$(HOME='$TEST_HOME' '$CCS_PATH' --uninstall 2>&1) + echo \"\$output\" | grep -q '\[OK\] Uninstall complete!' +" + +test_case "Output: Shows reinstallation instruction" "Contains reinstallation hint" bash -c " + output=\$(HOME='$TEST_HOME' '$CCS_PATH' --uninstall 2>&1) + [[ \$output =~ 'To reinstall: ccs --install' ]] +" + +# ============================================================================ +# TEST SECTION 5: INTEGRATION WITH CCS +# ============================================================================ + +echo "" +echo "========================================" +echo "SECTION 5: INTEGRATION WITH CCS" +echo "========================================" + +test_case "Integration: --uninstall executes without profile error" "No profile error on --uninstall" bash -c " + HOME='$TEST_HOME' '$CCS_PATH' --uninstall > /dev/null 2>&1 + exit_code=\$? + [[ \$exit_code -eq 0 ]] +" + +test_case "Integration: --version still works after uninstall" "Version command exits successfully" bash -c " + HOME='$TEST_HOME' '$CCS_PATH' --version > /dev/null 2>&1 + exit_code=\$? + [[ \$exit_code -eq 0 ]] +" + +test_case "Integration: --help still works after uninstall" "Help command exits successfully" bash -c " + HOME='$TEST_HOME' '$CCS_PATH' --help > /dev/null 2>&1 + exit_code=\$? + [[ \$exit_code -eq 0 ]] +" + +# ============================================================================ +# TEST SECTION 6: EDGE CASES +# ============================================================================ + +echo "" +echo "========================================" +echo "SECTION 6: EDGE CASES" +echo "========================================" + +test_case "Edge case: Partial install (commands only)" "Handles partial installation" bash -c " + # Create only command file + mkdir -p '$TEST_CLAUDE_DIR/commands' + echo 'test' > '$TEST_CLAUDE_DIR/commands/ccs.md' + # Uninstall should handle this gracefully + HOME='$TEST_HOME' '$CCS_PATH' --uninstall > /dev/null 2>&1 + exit_code=\$? + [[ \$exit_code -eq 0 ]] +" + +test_case "Edge case: Partial install (skills only)" "Handles partial installation" bash -c " + # Create only skill directory + mkdir -p '$TEST_CLAUDE_DIR/skills/ccs-delegation' + echo 'test' > '$TEST_CLAUDE_DIR/skills/ccs-delegation/SKILL.md' + # Uninstall should handle this gracefully + HOME='$TEST_HOME' '$CCS_PATH' --uninstall > /dev/null 2>&1 + exit_code=\$? + [[ \$exit_code -eq 0 ]] +" + +test_case "Edge case: Missing parent directories" "Handles missing .claude directory" bash -c " + # Ensure .claude directory doesn't exist + rm -rf '$TEST_CLAUDE_DIR' + HOME='$TEST_HOME' '$CCS_PATH' --uninstall > /dev/null 2>&1 + exit_code=\$? + [[ \$exit_code -eq 0 ]] +" + +# ============================================================================ +# SUMMARY +# ============================================================================ + +echo "" +echo "========================================" +if [[ $FAIL_COUNT -eq 0 ]]; then + echo -e "${GREEN}ALL TESTS PASSED!${NC}" + echo -e "${GREEN}========================================${NC}" + echo -e "${GREEN}--uninstall functionality is production ready!${NC}" +else + echo -e "${RED}SOME TESTS FAILED${NC}" + echo -e "${RED}========================================${NC}" + echo -e "${RED}Review failed tests above for details${NC}" +fi + +echo "" +echo -e "${CYAN}Test Summary:${NC}" +echo -e " Total tests: $TOTAL_TESTS" +echo -e " ${GREEN}Passed: $PASS_COUNT${NC}" +echo -e " ${RED}Failed: $FAIL_COUNT${NC}" +echo "" + +# Cleanup +cleanup_and_restore + +exit $FAIL_COUNT \ No newline at end of file diff --git a/tests/native/windows/custom-claude-path.ps1 b/tests/native/windows/custom-claude-path.ps1 new file mode 100644 index 00000000..1f09cdd1 --- /dev/null +++ b/tests/native/windows/custom-claude-path.ps1 @@ -0,0 +1,874 @@ +# CCS Custom Claude CLI Path - Comprehensive Test Suite +# Tests CCS_CLAUDE_PATH environment variable support (v2.3.0) +# Windows PowerShell 5.1+ compatible + +param( + [switch]$Verbose, + [switch]$QuickTest # Skip slow tests +) + +$ErrorActionPreference = "Stop" + +# --- Test Framework --- + +$Script:TotalTests = 0 +$Script:PassedTests = 0 +$Script:FailedTests = 0 +$Script:SkippedTests = 0 +$Script:StartTime = Get-Date +$Script:TestResults = @() + +function Write-TestHeader { + param([string]$Category) + Write-Host "`n========================================" -ForegroundColor Cyan + Write-Host " $Category" -ForegroundColor Cyan + Write-Host "========================================`n" -ForegroundColor Cyan +} + +function Write-TestResult { + param( + [string]$TestName, + [string]$Status, # PASS, FAIL, SKIP + [string]$Details = "", + [int]$DurationMs = 0 + ) + + $Script:TotalTests++ + + $Symbol = switch ($Status) { + "PASS" { "[OK]"; $Script:PassedTests++; $Color = "Green" } + "FAIL" { "[X]"; $Script:FailedTests++; $Color = "Red" } + "SKIP" { "[i]"; $Script:SkippedTests++; $Color = "Yellow" } + } + + Write-Host "$Symbol $TestName" -ForegroundColor $Color + if ($Details) { + Write-Host " $Details" -ForegroundColor Gray + } + if ($DurationMs -gt 0) { + Write-Host " Duration: ${DurationMs}ms" -ForegroundColor Gray + } + + $Script:TestResults += [PSCustomObject]@{ + TestName = $TestName + Status = $Status + Details = $Details + DurationMs = $DurationMs + } +} + +# --- Test Environment Setup --- + +$Script:TestDir = "$env:TEMP\ccs-test-$(Get-Random)" +$Script:MockClaudeDir = Join-Path $TestDir "mock-claude" +$Script:OriginalEnv = @{ + CCS_CLAUDE_PATH = $env:CCS_CLAUDE_PATH + PATH = $env:PATH +} + +function Initialize-TestEnvironment { + Write-Host "`n[Initializing Test Environment]" -ForegroundColor Cyan + Write-Host " Test Directory: $Script:TestDir" + + # Create test directories + New-Item -ItemType Directory -Path $Script:TestDir -Force | Out-Null + New-Item -ItemType Directory -Path $Script:MockClaudeDir -Force | Out-Null + + # Create mock claude.exe (simple executable that returns version) + $MockExePath = Join-Path $Script:MockClaudeDir "claude.exe" + + # PowerShell script wrapped as executable + $MockScript = @' +# Mock Claude CLI +Write-Host "Mock Claude CLI v1.0.0" +exit 0 +'@ + + # Create a batch file that calls PowerShell (simplest executable) + $BatchContent = @" +@echo off +echo Mock Claude CLI v1.0.0 +exit /b 0 +"@ + + Set-Content -Path $MockExePath -Value $BatchContent -Force + + Write-Host " [OK] Created mock claude.exe at $MockExePath" -ForegroundColor Green + Write-Host "" +} + +function Restore-TestEnvironment { + Write-Host "`n[Cleaning Up Test Environment]" -ForegroundColor Cyan + + # Restore original environment variables + $env:CCS_CLAUDE_PATH = $Script:OriginalEnv.CCS_CLAUDE_PATH + $env:PATH = $Script:OriginalEnv.PATH + + # Remove test directory + if (Test-Path $Script:TestDir) { + Remove-Item -Path $Script:TestDir -Recurse -Force -ErrorAction SilentlyContinue + Write-Host " [OK] Removed test directory" -ForegroundColor Green + } + + Write-Host "" +} + +# --- Helper Functions --- + +function Get-CcsPath { + # Try to find CCS in multiple locations + $PossiblePaths = @( + "$env:USERPROFILE\.ccs\ccs.ps1", # Installed location + (Join-Path (Split-Path -Parent (Split-Path -Parent $PSCommandPath)) "ccs.ps1"), # Repo location + (Get-Command ccs -ErrorAction SilentlyContinue).Source # From PATH + ) + + foreach ($Path in $PossiblePaths) { + if ($Path -and (Test-Path $Path)) { + return $Path + } + } + + throw "CCS script not found. Checked: $($PossiblePaths -join ', ')" +} + +function Test-ClaudeDetection { + param( + [string]$ExpectedPath = "", + [bool]$ShouldSucceed = $true + ) + + $CcsPath = Get-CcsPath + + try { + # Extract Find-ClaudeCli function and test it + $CcsContent = Get-Content $CcsPath -Raw + + # Execute detection logic in isolated scope + $DetectionScript = { + param($CcsContent, $TestEnv) + + # Set up test environment + foreach ($key in $TestEnv.Keys) { + Set-Item -Path "env:$key" -Value $TestEnv[$key] + } + + # Extract and execute Find-ClaudeCli function + $FunctionStart = $CcsContent.IndexOf("function Find-ClaudeCli {") + $FunctionEnd = $CcsContent.IndexOf("`n}", $FunctionStart) + 2 + $Function = $CcsContent.Substring($FunctionStart, $FunctionEnd - $FunctionStart) + + Invoke-Expression $Function + + return Find-ClaudeCli + } + + $TestEnv = @{ + CCS_CLAUDE_PATH = $env:CCS_CLAUDE_PATH + PATH = $env:PATH + } + + $Result = & $DetectionScript -CcsContent $CcsContent -TestEnv $TestEnv + + if ($ShouldSucceed) { + if ([string]::IsNullOrEmpty($Result)) { + throw "Detection failed: No path returned" + } + if ($ExpectedPath -and ($Result -ne $ExpectedPath)) { + throw "Detection returned wrong path: $Result (expected: $ExpectedPath)" + } + return $Result + } else { + if (-not [string]::IsNullOrEmpty($Result)) { + throw "Detection should have failed but returned: $Result" + } + return "" + } + } catch { + if ($ShouldSucceed) { + throw $_ + } + return "" + } +} + +function Get-MockClaudePath { + return Join-Path $Script:MockClaudeDir "claude.exe" +} + +# --- Test Category 1: Environment Variable Detection (Priority 1) --- + +function Test-Category1-EnvVarDetection { + Write-TestHeader "Category 1: Environment Variable Detection (Priority 1)" + + # Test 1.1: Valid CCS_CLAUDE_PATH + $TestStart = Get-Date + try { + $MockPath = Get-MockClaudePath + $env:CCS_CLAUDE_PATH = $MockPath + + $DetectedPath = Test-ClaudeDetection -ExpectedPath $MockPath -ShouldSucceed $true + + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 1.1: Valid CCS_CLAUDE_PATH" ` + -Status "PASS" ` + -Details "Detected: $DetectedPath" ` + -DurationMs $Duration + } catch { + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 1.1: Valid CCS_CLAUDE_PATH" ` + -Status "FAIL" ` + -Details $_.Exception.Message ` + -DurationMs $Duration + } finally { + $env:CCS_CLAUDE_PATH = $null + } + + # Test 1.2: Invalid CCS_CLAUDE_PATH (non-existent file) + $TestStart = Get-Date + try { + $env:CCS_CLAUDE_PATH = "D:\nonexistent\claude.exe" + + # Should fall back to PATH or common locations + $DetectedPath = Test-ClaudeDetection -ShouldSucceed $false + + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 1.2: Invalid CCS_CLAUDE_PATH (non-existent)" ` + -Status "PASS" ` + -Details "Correctly fell back to search" ` + -DurationMs $Duration + } catch { + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 1.2: Invalid CCS_CLAUDE_PATH (non-existent)" ` + -Status "FAIL" ` + -Details $_.Exception.Message ` + -DurationMs $Duration + } finally { + $env:CCS_CLAUDE_PATH = $null + } + + # Test 1.3: CCS_CLAUDE_PATH is a directory (not file) + $TestStart = Get-Date + try { + $env:CCS_CLAUDE_PATH = $Script:TestDir + + # Should fail validation and fall back + $DetectedPath = Test-ClaudeDetection -ShouldSucceed $false + + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 1.3: CCS_CLAUDE_PATH is directory" ` + -Status "PASS" ` + -Details "Validation correctly rejected directory" ` + -DurationMs $Duration + } catch { + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 1.3: CCS_CLAUDE_PATH is directory" ` + -Status "FAIL" ` + -Details $_.Exception.Message ` + -DurationMs $Duration + } finally { + $env:CCS_CLAUDE_PATH = $null + } + + # Test 1.4: CCS_CLAUDE_PATH with special characters (spaces) + $TestStart = Get-Date + try { + # Create mock in path with spaces + $SpacePath = Join-Path $Script:TestDir "Program Files (x86)" + New-Item -ItemType Directory -Path $SpacePath -Force | Out-Null + $SpaceClaudePath = Join-Path $SpacePath "claude.exe" + Copy-Item (Get-MockClaudePath) $SpaceClaudePath -Force + + $env:CCS_CLAUDE_PATH = $SpaceClaudePath + + $DetectedPath = Test-ClaudeDetection -ExpectedPath $SpaceClaudePath -ShouldSucceed $true + + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 1.4: CCS_CLAUDE_PATH with spaces" ` + -Status "PASS" ` + -Details "Handled spaces correctly" ` + -DurationMs $Duration + } catch { + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 1.4: CCS_CLAUDE_PATH with spaces" ` + -Status "FAIL" ` + -Details $_.Exception.Message ` + -DurationMs $Duration + } finally { + $env:CCS_CLAUDE_PATH = $null + } +} + +# --- Test Category 2: PATH Detection (Priority 2) --- + +function Test-Category2-PathDetection { + Write-TestHeader "Category 2: PATH Detection (Priority 2)" + + # Test 2.1: Claude in PATH + $TestStart = Get-Date + try { + $env:CCS_CLAUDE_PATH = $null + $MockPath = Get-MockClaudePath + $env:PATH = "$Script:MockClaudeDir;$env:PATH" + + $DetectedPath = Test-ClaudeDetection -ShouldSucceed $true + + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 2.1: Claude in PATH" ` + -Status "PASS" ` + -Details "Found via PATH: $DetectedPath" ` + -DurationMs $Duration + } catch { + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 2.1: Claude in PATH" ` + -Status "FAIL" ` + -Details $_.Exception.Message ` + -DurationMs $Duration + } finally { + $env:PATH = $Script:OriginalEnv.PATH + } + + # Test 2.2: No CCS_CLAUDE_PATH, no PATH + $TestStart = Get-Date + try { + $env:CCS_CLAUDE_PATH = $null + # Keep original PATH (no mock claude in it) + + # Should fall back to common locations (will fail in test environment) + $DetectedPath = Test-ClaudeDetection -ShouldSucceed $false + + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 2.2: No CCS_CLAUDE_PATH, no PATH" ` + -Status "PASS" ` + -Details "Correctly fell back to Priority 3" ` + -DurationMs $Duration + } catch { + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 2.2: No CCS_CLAUDE_PATH, no PATH" ` + -Status "FAIL" ` + -Details $_.Exception.Message ` + -DurationMs $Duration + } +} + +# --- Test Category 3: Common Locations (Priority 3) --- + +function Test-Category3-CommonLocations { + Write-TestHeader "Category 3: Common Locations (Priority 3)" + + # Test 3.1: Claude in C:\Program Files + $TestStart = Get-Date + try { + if ($QuickTest) { + Write-TestResult -TestName "Test 3.1: Claude in C:\Program Files" ` + -Status "SKIP" ` + -Details "Skipped in quick test mode" + } else { + # This test requires admin rights to create in C:\Program Files + Write-TestResult -TestName "Test 3.1: Claude in C:\Program Files" ` + -Status "SKIP" ` + -Details "Requires admin rights to test" + } + } catch { + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 3.1: Claude in C:\Program Files" ` + -Status "FAIL" ` + -Details $_.Exception.Message ` + -DurationMs $Duration + } + + # Test 3.2: Claude on D drive + $TestStart = Get-Date + try { + if ($QuickTest) { + Write-TestResult -TestName "Test 3.2: Claude on D drive" ` + -Status "SKIP" ` + -Details "Skipped in quick test mode" + } else { + # This test requires D: drive to exist + if (Test-Path "D:\") { + Write-TestResult -TestName "Test 3.2: Claude on D drive" ` + -Status "SKIP" ` + -Details "Requires D: drive setup" + } else { + Write-TestResult -TestName "Test 3.2: Claude on D drive" ` + -Status "SKIP" ` + -Details "D: drive not available" + } + } + } catch { + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 3.2: Claude on D drive" ` + -Status "FAIL" ` + -Details $_.Exception.Message ` + -DurationMs $Duration + } + + # Test 3.3: Claude not found anywhere + $TestStart = Get-Date + try { + $env:CCS_CLAUDE_PATH = $null + $env:PATH = $Script:OriginalEnv.PATH + + # Remove mock from PATH, should fail to find anywhere + $DetectedPath = Test-ClaudeDetection -ShouldSucceed $false + + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 3.3: Claude not found anywhere" ` + -Status "PASS" ` + -Details "Correctly returned empty when not found" ` + -DurationMs $Duration + } catch { + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 3.3: Claude not found anywhere" ` + -Status "FAIL" ` + -Details $_.Exception.Message ` + -DurationMs $Duration + } +} + +# --- Test Category 4: Security Validation --- + +function Test-Category4-SecurityValidation { + Write-TestHeader "Category 4: Security Validation" + + # Test 4.1: Command injection attempt (semicolon) + $TestStart = Get-Date + try { + $env:CCS_CLAUDE_PATH = "claude.exe; rm -rf /" + + # Should be rejected by character validation + $DetectedPath = Test-ClaudeDetection -ShouldSucceed $false + + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 4.1: Injection attempt (semicolon)" ` + -Status "PASS" ` + -Details "Blocked semicolon character" ` + -DurationMs $Duration + } catch { + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 4.1: Injection attempt (semicolon)" ` + -Status "FAIL" ` + -Details $_.Exception.Message ` + -DurationMs $Duration + } finally { + $env:CCS_CLAUDE_PATH = $null + } + + # Test 4.2: Command injection attempt (pipe) + $TestStart = Get-Date + try { + $env:CCS_CLAUDE_PATH = "claude.exe | malicious.exe" + + # Should be rejected by character validation + $DetectedPath = Test-ClaudeDetection -ShouldSucceed $false + + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 4.2: Injection attempt (pipe)" ` + -Status "PASS" ` + -Details "Blocked pipe character" ` + -DurationMs $Duration + } catch { + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 4.2: Injection attempt (pipe)" ` + -Status "FAIL" ` + -Details $_.Exception.Message ` + -DurationMs $Duration + } finally { + $env:CCS_CLAUDE_PATH = $null + } + + # Test 4.3: Command injection attempt (backtick) + $TestStart = Get-Date + try { + $env:CCS_CLAUDE_PATH = "claude.exe`nmalicious.exe" + + # Should be rejected by character validation + $DetectedPath = Test-ClaudeDetection -ShouldSucceed $false + + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 4.3: Injection attempt (backtick)" ` + -Status "PASS" ` + -Details "Blocked backtick/newline" ` + -DurationMs $Duration + } catch { + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 4.3: Injection attempt (backtick)" ` + -Status "FAIL" ` + -Details $_.Exception.Message ` + -DurationMs $Duration + } finally { + $env:CCS_CLAUDE_PATH = $null + } + + # Test 4.4: Path traversal attempt + $TestStart = Get-Date + try { + # Relative paths should be allowed (valid use case) + $RelativePath = "..\..\Windows\System32\cmd.exe" + $env:CCS_CLAUDE_PATH = $RelativePath + + # Should resolve to absolute path and validate + # Will fail on non-executable, but path format is OK + $DetectedPath = Test-ClaudeDetection -ShouldSucceed $false + + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 4.4: Path traversal (relative paths OK)" ` + -Status "PASS" ` + -Details "Relative paths allowed, validation on file type" ` + -DurationMs $Duration + } catch { + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 4.4: Path traversal (relative paths OK)" ` + -Status "FAIL" ` + -Details $_.Exception.Message ` + -DurationMs $Duration + } finally { + $env:CCS_CLAUDE_PATH = $null + } +} + +# --- Test Category 5: Error Messages --- + +function Test-Category5-ErrorMessages { + Write-TestHeader "Category 5: Error Messages" + + # Test 5.1: Error message completeness + $TestStart = Get-Date + try { + $env:CCS_CLAUDE_PATH = $null + $env:PATH = $Script:OriginalEnv.PATH + + # Capture error output + $CcsPath = Get-CcsPath + + $ErrorOutput = & $CcsPath --help 2>&1 | Out-String + + # Check for required sections in error message + $HasCcsClaudePathStatus = $ErrorOutput -match "CCS_CLAUDE_PATH:" + $HasPathSearch = $ErrorOutput -match "System PATH:" + $HasCommonLocations = $ErrorOutput -match "Common locations:" + $HasSolutions = $ErrorOutput -match "Solutions:" + $HasDebugging = $ErrorOutput -match "Debugging:" + + $AllSectionsPresent = $HasCcsClaudePathStatus -and $HasPathSearch -and + $HasCommonLocations -and $HasSolutions -and $HasDebugging + + if ($AllSectionsPresent) { + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 5.1: Error message completeness" ` + -Status "PASS" ` + -Details "All required sections present" ` + -DurationMs $Duration + } else { + throw "Missing sections in error message" + } + } catch { + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 5.1: Error message completeness" ` + -Status "FAIL" ` + -Details $_.Exception.Message ` + -DurationMs $Duration + } + + # Test 5.2: Error message D drive examples + $TestStart = Get-Date + try { + $env:CCS_CLAUDE_PATH = $null + $env:PATH = $Script:OriginalEnv.PATH + + $CcsPath = Get-CcsPath + + $ErrorOutput = & $CcsPath --help 2>&1 | Out-String + + $HasDDriveExample = $ErrorOutput -match "D:" + + if ($HasDDriveExample) { + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 5.2: Error message D drive examples" ` + -Status "PASS" ` + -Details "D: drive examples present" ` + -DurationMs $Duration + } else { + throw "D: drive examples missing" + } + } catch { + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 5.2: Error message D drive examples" ` + -Status "FAIL" ` + -Details $_.Exception.Message ` + -DurationMs $Duration + } +} + +# --- Test Category 6: Integration Tests --- + +function Test-Category6-Integration { + Write-TestHeader "Category 6: Integration Tests" + + # Test 6.1: Full workflow with CCS_CLAUDE_PATH + $TestStart = Get-Date + try { + $MockPath = Get-MockClaudePath + $env:CCS_CLAUDE_PATH = $MockPath + + $CcsPath = Get-CcsPath + + # Run ccs with mock claude (will fail on config but detection should work) + $Output = & $CcsPath --help 2>&1 | Out-String + + # Check if it used the custom Claude path (not error about not finding) + $UsedCustomPath = -not ($Output -match "Claude CLI not found") + + if ($UsedCustomPath) { + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 6.1: Full workflow with CCS_CLAUDE_PATH" ` + -Status "PASS" ` + -Details "Used custom Claude path successfully" ` + -DurationMs $Duration + } else { + throw "Did not use custom Claude path" + } + } catch { + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 6.1: Full workflow with CCS_CLAUDE_PATH" ` + -Status "FAIL" ` + -Details $_.Exception.Message ` + -DurationMs $Duration + } finally { + $env:CCS_CLAUDE_PATH = $null + } + + # Test 6.2: Version command bypasses detection + $TestStart = Get-Date + try { + $env:CCS_CLAUDE_PATH = $null + + $CcsPath = Get-CcsPath + + $Output = & $CcsPath --version 2>&1 | Out-String + + $ShowsVersion = $Output -match "CCS \(Claude Code Switch\) version" + + if ($ShowsVersion) { + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 6.2: Version command bypasses detection" ` + -Status "PASS" ` + -Details "Version shown without Claude detection" ` + -DurationMs $Duration + } else { + throw "Version command failed" + } + } catch { + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 6.2: Version command bypasses detection" ` + -Status "FAIL" ` + -Details $_.Exception.Message ` + -DurationMs $Duration + } + + # Test 6.3: Help command uses detection + $TestStart = Get-Date + try { + $env:CCS_CLAUDE_PATH = $null + + $CcsPath = Get-CcsPath + + $Output = & $CcsPath --help 2>&1 | Out-String + + $TriedDetection = $Output -match "Claude CLI not found" + + if ($TriedDetection) { + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 6.3: Help command uses detection" ` + -Status "PASS" ` + -Details "Help command triggered Claude detection" ` + -DurationMs $Duration + } else { + throw "Help command did not trigger detection" + } + } catch { + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 6.3: Help command uses detection" ` + -Status "FAIL" ` + -Details $_.Exception.Message ` + -DurationMs $Duration + } +} + +# --- Test Category 7: Edge Cases --- + +function Test-Category7-EdgeCases { + Write-TestHeader "Category 7: Edge Cases" + + # Test 7.1: Empty CCS_CLAUDE_PATH + $TestStart = Get-Date + try { + $env:CCS_CLAUDE_PATH = "" + + # Should treat as unset and continue fallback + $DetectedPath = Test-ClaudeDetection -ShouldSucceed $false + + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 7.1: Empty CCS_CLAUDE_PATH" ` + -Status "PASS" ` + -Details "Treated as unset, continued fallback" ` + -DurationMs $Duration + } catch { + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 7.1: Empty CCS_CLAUDE_PATH" ` + -Status "FAIL" ` + -Details $_.Exception.Message ` + -DurationMs $Duration + } finally { + $env:CCS_CLAUDE_PATH = $null + } + + # Test 7.2: Whitespace-only CCS_CLAUDE_PATH + $TestStart = Get-Date + try { + $env:CCS_CLAUDE_PATH = " " + + # Should fail validation and continue fallback + $DetectedPath = Test-ClaudeDetection -ShouldSucceed $false + + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 7.2: Whitespace-only CCS_CLAUDE_PATH" ` + -Status "PASS" ` + -Details "Validation failed, continued fallback" ` + -DurationMs $Duration + } catch { + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 7.2: Whitespace-only CCS_CLAUDE_PATH" ` + -Status "FAIL" ` + -Details $_.Exception.Message ` + -DurationMs $Duration + } finally { + $env:CCS_CLAUDE_PATH = $null + } + + # Test 7.3: Very long path (>260 characters) + $TestStart = Get-Date + try { + if ($QuickTest) { + Write-TestResult -TestName "Test 7.3: Very long path (>260 chars)" ` + -Status "SKIP" ` + -Details "Skipped in quick test mode" + } else { + # Create a path longer than 260 characters + $LongPath = "C:\" + ("very-long-directory-name\" * 20) + "claude.exe" + $env:CCS_CLAUDE_PATH = $LongPath + + # Should handle long paths (may fail on file not found, not path length) + $DetectedPath = Test-ClaudeDetection -ShouldSucceed $false + + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 7.3: Very long path (>260 chars)" ` + -Status "PASS" ` + -Details "Handled long path without crash" ` + -DurationMs $Duration + } + } catch { + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 7.3: Very long path (>260 chars)" ` + -Status "FAIL" ` + -Details $_.Exception.Message ` + -DurationMs $Duration + } finally { + $env:CCS_CLAUDE_PATH = $null + } + + # Test 7.4: Unicode in path + $TestStart = Get-Date + try { + # Create directory with Unicode characters + $UnicodePath = Join-Path $Script:TestDir "文件夹" + New-Item -ItemType Directory -Path $UnicodePath -Force | Out-Null + $UnicodeClaudePath = Join-Path $UnicodePath "claude.exe" + Copy-Item (Get-MockClaudePath) $UnicodeClaudePath -Force + + $env:CCS_CLAUDE_PATH = $UnicodeClaudePath + + $DetectedPath = Test-ClaudeDetection -ExpectedPath $UnicodeClaudePath -ShouldSucceed $true + + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 7.4: Unicode in path" ` + -Status "PASS" ` + -Details "Handled Unicode characters correctly" ` + -DurationMs $Duration + } catch { + $Duration = ((Get-Date) - $TestStart).TotalMilliseconds + Write-TestResult -TestName "Test 7.4: Unicode in path" ` + -Status "FAIL" ` + -Details $_.Exception.Message ` + -DurationMs $Duration + } finally { + $env:CCS_CLAUDE_PATH = $null + } +} + +# --- Main Execution --- + +function Show-TestSummary { + $EndTime = Get-Date + $TotalDuration = ($EndTime - $Script:StartTime).TotalSeconds + + Write-Host "`n========================================" -ForegroundColor Cyan + Write-Host " TEST SUMMARY" -ForegroundColor Cyan + Write-Host "========================================`n" -ForegroundColor Cyan + + Write-Host "Total Tests: $Script:TotalTests" + Write-Host "Passed: $Script:PassedTests" -ForegroundColor Green + Write-Host "Failed: $Script:FailedTests" -ForegroundColor Red + Write-Host "Skipped: $Script:SkippedTests" -ForegroundColor Yellow + + $PassRate = if ($Script:TotalTests -gt 0) { + [math]::Round(($Script:PassedTests / $Script:TotalTests) * 100, 2) + } else { + 0 + } + Write-Host "Pass Rate: $PassRate%" + Write-Host "Duration: ${TotalDuration}s" + + Write-Host "`n" + + # Show failures + if ($Script:FailedTests -gt 0) { + Write-Host "FAILED TESTS:" -ForegroundColor Red + $Script:TestResults | Where-Object { $_.Status -eq "FAIL" } | ForEach-Object { + Write-Host " - $($_.TestName)" -ForegroundColor Red + Write-Host " $($_.Details)" -ForegroundColor Gray + } + Write-Host "`n" + } + + # Exit code + if ($Script:FailedTests -gt 0) { + exit 1 + } else { + exit 0 + } +} + +# --- Run Tests --- + +Write-Host "========================================" -ForegroundColor Cyan +Write-Host " CCS Custom Claude CLI Path Test Suite" -ForegroundColor Cyan +Write-Host " Version: 2.3.0" -ForegroundColor Cyan +Write-Host "========================================`n" -ForegroundColor Cyan + +Initialize-TestEnvironment + +try { + Test-Category1-EnvVarDetection + Test-Category2-PathDetection + Test-Category3-CommonLocations + Test-Category4-SecurityValidation + Test-Category5-ErrorMessages + Test-Category6-Integration + Test-Category7-EdgeCases + + Show-TestSummary +} finally { + Restore-TestEnvironment +} diff --git a/tests/native/windows/edge-cases.ps1 b/tests/native/windows/edge-cases.ps1 new file mode 100644 index 00000000..c8b0a737 --- /dev/null +++ b/tests/native/windows/edge-cases.ps1 @@ -0,0 +1,320 @@ +# CCS Comprehensive Edge Case Testing +# Tests all edge cases and scenarios to ensure robustness + +$ErrorActionPreference = "Continue" +$PassCount = 0 +$FailCount = 0 +$TotalTests = 0 + +function Test-Case { + param( + [string]$Name, + [scriptblock]$Test, + [string]$ExpectedBehavior + ) + + $script:TotalTests++ + Write-Host "" + Write-Host "[$script:TotalTests] $Name" -ForegroundColor Cyan + Write-Host " Expected: $ExpectedBehavior" -ForegroundColor Gray + + try { + $result = & $Test + if ($result) { + Write-Host " Result: PASS" -ForegroundColor Green + $script:PassCount++ + return $true + } else { + Write-Host " Result: FAIL" -ForegroundColor Red + $script:FailCount++ + return $false + } + } catch { + Write-Host " Result: ERROR - $_" -ForegroundColor Red + $script:FailCount++ + return $false + } +} + +Write-Host "========================================" -ForegroundColor Yellow +Write-Host "CCS COMPREHENSIVE EDGE CASE TESTING" -ForegroundColor Yellow +Write-Host "========================================" -ForegroundColor Yellow +Write-Host "" + +# Clean installation +Write-Host "Preparing clean environment..." -ForegroundColor Cyan +if (Test-Path "C:\Users\kaidu\.ccs") { + Remove-Item "C:\Users\kaidu\.ccs" -Recurse -Force -ErrorAction SilentlyContinue +} +if (Test-Path "C:\Users\kaidu\ccs-test") { + Remove-Item "C:\Users\kaidu\ccs-test" -Recurse -Force -ErrorAction SilentlyContinue +} + +# Extract and install +New-Item -ItemType Directory -Path "C:\Users\kaidu\ccs-test" | Out-Null +tar -xzf C:\Users\kaidu\ccs-final-v5.tar.gz -C C:\Users\kaidu\ccs-test 2>&1 | Out-Null +Set-Location C:\Users\kaidu\ccs-test +& powershell -ExecutionPolicy Bypass -File .\installers\install.ps1 2>&1 | Out-Null + +$CcsPath = "C:\Users\kaidu\.ccs\ccs.ps1" + +if (-not (Test-Path $CcsPath)) { + Write-Host "FATAL: Installation failed, ccs.ps1 not found" -ForegroundColor Red + exit 1 +} + +Write-Host "Installation complete, starting tests..." -ForegroundColor Green +Write-Host "" + +# ============================================================================ +# SECTION 1: VERSION COMMANDS +# ============================================================================ +Write-Host "===== SECTION 1: VERSION COMMANDS =====" -ForegroundColor Yellow + +Test-Case "Version flag --version" { + $output = & powershell -ExecutionPolicy Bypass -File $CcsPath --version 2>&1 | Out-String + return $output -match "2\.1\.1|CCS" +} "Shows CCS version 2.1.1" + +Test-Case "Version flag -v" { + $output = & powershell -ExecutionPolicy Bypass -File $CcsPath -v 2>&1 | Out-String + return $output -match "2\.1\.1|CCS" +} "Shows CCS version 2.1.1" + +Test-Case "Version command (word)" { + $output = & powershell -ExecutionPolicy Bypass -File $CcsPath version 2>&1 | Out-String + return $output -match "2\.1\.1|CCS" +} "Shows CCS version 2.1.1" + +# ============================================================================ +# SECTION 2: HELP COMMANDS +# ============================================================================ +Write-Host "" +Write-Host "===== SECTION 2: HELP COMMANDS =====" -ForegroundColor Yellow + +Test-Case "Help flag --help" { + $output = & powershell -ExecutionPolicy Bypass -File $CcsPath --help 2>&1 | Out-String + return $output -match "Usage|Claude" +} "Shows help without profile error" + +Test-Case "Help flag -h" { + $output = & powershell -ExecutionPolicy Bypass -File $CcsPath -h 2>&1 | Out-String + return $output -match "Usage|Claude" +} "Shows help without profile error" + +# ============================================================================ +# SECTION 3: ARGUMENT PARSING - THE CRITICAL FIX +# ============================================================================ +Write-Host "" +Write-Host "===== SECTION 3: ARGUMENT PARSING (CRITICAL FIX) =====" -ForegroundColor Yellow + +Test-Case "Single flag: -c" { + $output = & powershell -ExecutionPolicy Bypass -File $CcsPath -c 2>&1 | Out-String + return -not ($output -match "Profile.*'-c'.*not found") +} "Does NOT show 'Profile -c not found' error" + +Test-Case "Single flag: --verbose" { + $output = & powershell -ExecutionPolicy Bypass -File $CcsPath --verbose 2>&1 | Out-String + return -not ($output -match "Profile.*'--verbose'.*not found") +} "Does NOT show 'Profile --verbose not found' error" + +Test-Case "Single flag: -p" { + $output = & powershell -ExecutionPolicy Bypass -File $CcsPath -p "test" 2>&1 | Out-String + return -not ($output -match "Profile.*'-p'.*not found") +} "Does NOT show 'Profile -p not found' error" + +Test-Case "Single flag: --debug" { + $output = & powershell -ExecutionPolicy Bypass -File $CcsPath --debug 2>&1 | Out-String + return -not ($output -match "Profile.*'--debug'.*not found") +} "Does NOT show profile error" + +Test-Case "Multiple flags: -c --verbose" { + $output = & powershell -ExecutionPolicy Bypass -File $CcsPath -c --verbose 2>&1 | Out-String + return -not ($output -match "Profile.*not found") +} "Accepts multiple flags without profile error" + +Test-Case "Flag with value: -p 'test prompt'" { + $output = & powershell -ExecutionPolicy Bypass -File $CcsPath -p "test prompt" 2>&1 | Out-String + return -not ($output -match "Profile.*'-p'.*not found") +} "Handles flag with quoted value" + +# ============================================================================ +# SECTION 4: PROFILE COMMANDS +# ============================================================================ +Write-Host "" +Write-Host "===== SECTION 4: PROFILE COMMANDS =====" -ForegroundColor Yellow + +Test-Case "Default profile (no args)" { + $output = & powershell -ExecutionPolicy Bypass -File $CcsPath 2>&1 | Out-String + return -not ($output -match "Profile.*not found") +} "Uses default profile without error" + +Test-Case "GLM profile" { + $output = & powershell -ExecutionPolicy Bypass -File $CcsPath glm 2>&1 | Out-String + return -not ($output -match "Profile.*'glm'.*not found") +} "GLM profile exists and loads" + +Test-Case "Profile with flag: glm -c" { + $output = & powershell -ExecutionPolicy Bypass -File $CcsPath glm -c 2>&1 | Out-String + return -not ($output -match "Profile.*not found") +} "Profile + flag combination works" + +Test-Case "Profile with multiple flags: glm -c --verbose" { + $output = & powershell -ExecutionPolicy Bypass -File $CcsPath glm -c --verbose 2>&1 | Out-String + return -not ($output -match "Profile.*not found") +} "Profile + multiple flags works" + +# ============================================================================ +# SECTION 5: ERROR HANDLING +# ============================================================================ +Write-Host "" +Write-Host "===== SECTION 5: ERROR HANDLING =====" -ForegroundColor Yellow + +Test-Case "Invalid profile name" { + $output = & powershell -ExecutionPolicy Bypass -File $CcsPath nonexistent-profile 2>&1 | Out-String + return $output -match "Profile[\s\S]*not found[\s\S]*Available profiles" +} "Shows helpful error for invalid profile" + +Test-Case "Invalid profile with special chars" { + $output = & powershell -ExecutionPolicy Bypass -File $CcsPath "test@profile" 2>&1 | Out-String + return $output -match "Invalid profile name|not found" +} "Rejects invalid characters in profile name" + +Test-Case "Empty string as profile" { + $output = & powershell -ExecutionPolicy Bypass -File $CcsPath "" 2>&1 | Out-String + return -not ($output -match "Profile.*''.*not found") +} "Handles empty string gracefully" + +# ============================================================================ +# SECTION 6: EDGE CASES +# ============================================================================ +Write-Host "" +Write-Host "===== SECTION 6: EDGE CASES =====" -ForegroundColor Yellow + +Test-Case "Flag starting with double dash: --test-flag" { + $output = & powershell -ExecutionPolicy Bypass -File $CcsPath --test-flag 2>&1 | Out-String + return -not ($output -match "Profile.*'--test-flag'.*not found") +} "Handles double-dash flags correctly" + +Test-Case "Short flag alone: -d" { + $output = & powershell -ExecutionPolicy Bypass -File $CcsPath -d 2>&1 | Out-String + return -not ($output -match "Profile.*'-d'.*not found") +} "Handles short flags correctly" + +Test-Case "Mixed flags and arguments" { + # PowerShell -File parameter binding will consume -p as ProfileOrFlag parameter + # So we test with flags that don't conflict: --verbose -c (both start with -) + $output = & powershell -ExecutionPolicy Bypass -File $CcsPath --verbose -c 2>&1 | Out-String + return -not ($output -match "Profile.*not found") +} "Handles complex flag combinations" + +Test-Case "Profile 'default' explicitly" { + $output = & powershell -ExecutionPolicy Bypass -File $CcsPath default 2>&1 | Out-String + return -not ($output -match "Profile.*'default'.*not found") +} "Explicit default profile works" + +Test-Case "Flag with equals: --model=gpt4" { + $output = & powershell -ExecutionPolicy Bypass -File $CcsPath --model=test 2>&1 | Out-String + return -not ($output -match "Profile.*'--model=test'.*not found") +} "Handles flags with equals syntax" + +Test-Case "Negative number (looks like flag): -1" { + $output = & powershell -ExecutionPolicy Bypass -File $CcsPath -1 2>&1 | Out-String + return -not ($output -match "Profile.*'-1'.*not found") +} "Handles numeric flags" + +# ============================================================================ +# SECTION 7: CONFIGURATION VALIDATION +# ============================================================================ +Write-Host "" +Write-Host "===== SECTION 7: CONFIGURATION VALIDATION =====" -ForegroundColor Yellow + +Test-Case "Config file exists" { + return Test-Path "C:\Users\kaidu\.ccs\config.json" +} "config.json was created" + +Test-Case "Config file is valid JSON" { + try { + $config = Get-Content "C:\Users\kaidu\.ccs\config.json" -Raw | ConvertFrom-Json + return $config.profiles -ne $null + } catch { + return $false + } +} "config.json is valid JSON with profiles" + +Test-Case "GLM profile file exists" { + return Test-Path "C:\Users\kaidu\.ccs\glm.settings.json" +} "glm.settings.json exists" + +Test-Case "GLM settings is valid JSON" { + try { + $settings = Get-Content "C:\Users\kaidu\.ccs\glm.settings.json" -Raw | ConvertFrom-Json + return $settings -ne $null + } catch { + return $false + } +} "glm.settings.json is valid JSON" + +# ============================================================================ +# SECTION 8: REAL USAGE SIMULATION +# ============================================================================ +Write-Host "" +Write-Host "===== SECTION 8: REAL USAGE SIMULATION =====" -ForegroundColor Yellow + +Test-Case "Simulate: continue conversation" { + $output = & powershell -ExecutionPolicy Bypass -File $CcsPath -c 2>&1 | Select-Object -First 10 | Out-String + # Should not show profile error, may show other errors (expected if Claude not configured) + return -not ($output -match "Profile.*'-c'.*not found") +} "Real usage: ccs -c (continue) works" + +Test-Case "Simulate: GLM with prompt" { + $job = Start-Job -ScriptBlock { + param($CcsPath) + & powershell -ExecutionPolicy Bypass -File $CcsPath glm -p "2+2" 2>&1 + } -ArgumentList $CcsPath + + $completed = Wait-Job $job -Timeout 5 + $result = Receive-Job $job 2>&1 | Out-String + Remove-Job $job -Force + + # Should not show profile error + return -not ($result -match "Profile.*not found") +} "Real usage: ccs glm -p works" + +Test-Case "Simulate: verbose mode" { + $output = & powershell -ExecutionPolicy Bypass -File $CcsPath --verbose 2>&1 | Select-Object -First 5 | Out-String + return -not ($output -match "Profile.*'--verbose'.*not found") +} "Real usage: ccs --verbose works" + +# ============================================================================ +# FINAL RESULTS +# ============================================================================ +Write-Host "" +Write-Host "========================================" -ForegroundColor Yellow +Write-Host "TEST RESULTS SUMMARY" -ForegroundColor Yellow +Write-Host "========================================" -ForegroundColor Yellow +Write-Host "" +Write-Host "Total Tests: $TotalTests" -ForegroundColor Cyan +Write-Host "Passed: $PassCount" -ForegroundColor Green +Write-Host "Failed: $FailCount" -ForegroundColor $(if ($FailCount -eq 0) { "Green" } else { "Red" }) +Write-Host "" + +$SuccessRate = [math]::Round(($PassCount / $TotalTests) * 100, 2) +Write-Host "Success Rate: $SuccessRate%" -ForegroundColor $(if ($SuccessRate -ge 90) { "Green" } elseif ($SuccessRate -ge 70) { "Yellow" } else { "Red" }) +Write-Host "" + +if ($FailCount -eq 0) { + Write-Host "========================================" -ForegroundColor Green + Write-Host "ALL TESTS PASSED!" -ForegroundColor Green + Write-Host "========================================" -ForegroundColor Green + Write-Host "" + Write-Host "CCS is ready for production use!" -ForegroundColor Green + exit 0 +} else { + Write-Host "========================================" -ForegroundColor Yellow + Write-Host "SOME TESTS FAILED" -ForegroundColor Yellow + Write-Host "========================================" -ForegroundColor Yellow + Write-Host "" + Write-Host "Review failed tests above for details" -ForegroundColor Yellow + exit 1 +} diff --git a/tests/native/windows/install.ps1 b/tests/native/windows/install.ps1 new file mode 100644 index 00000000..a3afea37 --- /dev/null +++ b/tests/native/windows/install.ps1 @@ -0,0 +1,273 @@ +#!/usr/bin/env pwsh + +<# +.SYNOPSIS + Test suite for CCS --install functionality (Windows PowerShell) + +.DESCRIPTION + Tests the --install flag for CCS executable on Windows PowerShell. + Tests against actual system installation for simplicity. + +.NOTES + Author: CCS Test Suite + Version: 1.0 + Based on: edge-cases.ps1 patterns +#> + +#Requires -Version 5.1 + +# Don't exit on errors, we're testing +$ErrorActionPreference = "Continue" + +# Test statistics +$Script:TotalTests = 0 +$Script:PassedTests = 0 +$Script:FailedTests = 0 + +# Colors +$Colors = @{ + Red = "Red" + Green = "Green" + Yellow = "Yellow" + Cyan = "Cyan" + White = "White" +} + +function Write-ColorOutput { + param([string]$Message, [string]$Color = "White") + Write-Host $Message -ForegroundColor $Colors[$Color] +} + +function Test-Case { + param( + [string]$Name, + [string]$Description, + [scriptblock]$TestCode + ) + + $Script:TotalTests++ + Write-Host "" + Write-ColorOutput "[$Script:TotalTests] $Name" "Cyan" + Write-ColorOutput " Expected: $Description" "White" + + try { + if (& $TestCode) { + Write-ColorOutput " Result: PASS" "Green" + $Script:PassedTests++ + return $true + } else { + Write-ColorOutput " Result: FAIL" "Red" + $Script:FailedTests++ + return $false + } + } + catch { + Write-ColorOutput " Result: FAIL" "Red" + Write-ColorOutput " Error: $($_.Exception.Message)" "Red" + $Script:FailedTests++ + return $false + } +} + +Write-ColorOutput "========================================" "Yellow" +Write-ColorOutput "CCS --INSTALL FUNCTIONALITY TESTING" "Yellow" +Write-ColorOutput "========================================" "Yellow" +Write-Host "" + +# Get script paths +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$CcsRoot = Split-Path -Parent $ScriptDir +$CcsPath = Join-Path $CcsRoot "ccs.ps1" + +# Backup existing .claude directory +Write-ColorOutput "Safety: Backing up existing ~/.claude/..." "Cyan" +$BackupDir = Join-Path $env:TEMP "ccs-test-backup-$(Get-Date -Format 'yyyyMMddHHmmss')" +if (Test-Path $env:USERPROFILE\.claude) { + Write-Host " Found existing ~/.claude/, creating backup at $BackupDir" + Copy-Item -Path $env:USERPROFILE\.claude -Destination $BackupDir -Recurse -Force + Write-ColorOutput " Backup created successfully" "Green" +} else { + Write-ColorOutput " No existing ~/.claude/ found" "Yellow" +} +Write-Host "" + +# ============================================================================ +# PRE-CHECKS +# ============================================================================ +Write-ColorOutput "===== PRE-CHECKS =====" "Yellow" + +Test-Case "CCS executable exists" "ccs.ps1 script found" { + Test-Path $CcsPath +} + +Test-Case "Source .claude directory exists" ".claude/ in CCS repo" { + Test-Path (Join-Path $CcsRoot ".claude") +} + +Test-Case "Source commands exist" "ccs.md command file exists" { + Test-Path (Join-Path $CcsRoot ".claude\commands\ccs.md") +} + +Test-Case "Source skills exist" "ccs-delegation skill directory exists" { + Test-Path (Join-Path $CcsRoot ".claude\skills\ccs-delegation") +} + +# ============================================================================ +# SECTION 1: INSTALLATION +# ============================================================================ +Write-Host "" +Write-ColorOutput "===== SECTION 1: INSTALLATION =====" "Yellow" + +Test-Case "Install: Command executes without error" "Exit code 0" { + $process = Start-Process -FilePath "pwsh" -ArgumentList "-File", "`"$CcsPath`"", "--install" -Wait -PassThru -NoNewWindow + $process.ExitCode -eq 0 +} + +Test-Case "Install: Commands directory created" "~/.claude/commands/ exists" { + Test-Path "$env:USERPROFILE\.claude\commands" +} + +Test-Case "Install: Skills directory created" "~/.claude/skills/ exists" { + Test-Path "$env:USERPROFILE\.claude\skills" +} + +Test-Case "Install: ccs.md command installed" "ccs.md file exists" { + Test-Path "$env:USERPROFILE\.claude\commands\ccs.md" +} + +Test-Case "Install: ccs-delegation skill installed" "ccs-delegation/ exists" { + Test-Path "$env:USERPROFILE\.claude\skills\ccs-delegation" +} + +Test-Case "Install: SKILL.md exists" "SKILL.md in ccs-delegation/" { + Test-Path "$env:USERPROFILE\.claude\skills\ccs-delegation\SKILL.md" +} + +Test-Case "Install: references directory exists" "references/ subdirectory present" { + Test-Path "$env:USERPROFILE\.claude\skills\ccs-delegation\references" +} + +# ============================================================================ +# SECTION 2: IDEMPOTENCY +# ============================================================================ +Write-Host "" +Write-ColorOutput "===== SECTION 2: IDEMPOTENCY =====" "Yellow" + +Test-Case "Idempotent: Second run succeeds" "Second --install doesn't error" { + $process = Start-Process -FilePath "pwsh" -ArgumentList "-File", "`"$CcsPath`"", "--install" -Wait -PassThru -NoNewWindow + $process.ExitCode -eq 0 +} + +# ============================================================================ +# SECTION 3: FILE INTEGRITY +# ============================================================================ +Write-Host "" +Write-ColorOutput "===== SECTION 3: FILE INTEGRITY =====" "Yellow" + +Test-Case "Integrity: Command file not empty" "ccs.md has content" { + $content = Get-Content "$env:USERPROFILE\.claude\commands\ccs.md" -Raw + $content.Length -gt 0 +} + +Test-Case "Integrity: Skill file not empty" "SKILL.md has content" { + $content = Get-Content "$env:USERPROFILE\.claude\skills\ccs-delegation\SKILL.md" -Raw + $content.Length -gt 0 +} + +Test-Case "Integrity: Command file matches source" "ccs.md content identical" { + $source = Get-Content (Join-Path $CcsRoot ".claude\commands\ccs.md") -Raw + $target = Get-Content "$env:USERPROFILE\.claude\commands\ccs.md" -Raw + $source -eq $target +} + +Test-Case "Integrity: Skill file matches source" "SKILL.md content identical" { + $source = Get-Content (Join-Path $CcsRoot ".claude\skills\ccs-delegation\SKILL.md") -Raw + $target = Get-Content "$env:USERPROFILE\.claude\skills\ccs-delegation\SKILL.md" -Raw + $source -eq $target +} + +# ============================================================================ +# SECTION 4: INTEGRATION +# ============================================================================ +Write-Host "" +Write-ColorOutput "===== SECTION 4: INTEGRATION WITH CCS =====" "Yellow" + +Test-Case "Integration: --install executes without profile error" "No profile error on --install" { + $process = Start-Process -FilePath "pwsh" -ArgumentList "-File", "`"$CcsPath`"", "--install" -Wait -PassThru -NoNewWindow + $process.ExitCode -eq 0 +} + +Test-Case "Integration: --version still works" "Version command exits successfully" { + $process = Start-Process -FilePath "pwsh" -ArgumentList "-File", "`"$CcsPath`"", "--version" -Wait -PassThru -NoNewWindow + $process.ExitCode -eq 0 +} + +Test-Case "Integration: --help still works" "Help command exits successfully" { + $process = Start-Process -FilePath "pwsh" -ArgumentList "-File", "`"$CcsPath`"", "--help" -Wait -PassThru -NoNewWindow + $process.ExitCode -eq 0 +} + +# ============================================================================ +# FINAL RESULTS +# ============================================================================ +Write-Host "" +Write-ColorOutput "========================================" "Yellow" +Write-ColorOutput "TEST RESULTS SUMMARY" "Yellow" +Write-ColorOutput "========================================" "Yellow" +Write-Host "" + +Write-ColorOutput "Total Tests: $Script:TotalTests" "Cyan" +Write-ColorOutput "Passed: $Script:PassedTests" "Green" +if ($Script:FailedTests -eq 0) { + Write-ColorOutput "Failed: $Script:FailedTests" "Green" +} else { + Write-ColorOutput "Failed: $Script:FailedTests" "Red" +} +Write-Host "" + +$SuccessRate = if ($Script:TotalTests -gt 0) { + [math]::Round(($Script:PassedTests / $Script:TotalTests) * 100, 2) +} else { 0 } + +if ($SuccessRate -ge 90) { + Write-ColorOutput "Success Rate: $SuccessRate%" "Green" +} elseif ($SuccessRate -ge 70) { + Write-ColorOutput "Success Rate: $SuccessRate%" "Yellow" +} else { + Write-ColorOutput "Success Rate: $SuccessRate%" "Red" +} + +Write-Host "" + +if ($Script:FailedTests -eq 0) { + Write-ColorOutput "========================================" "Green" + Write-ColorOutput "ALL TESTS PASSED!" "Green" + Write-ColorOutput "========================================" "Green" + Write-ColorOutput "--install functionality is production ready!" "Green" +} else { + Write-ColorOutput "========================================" "Red" + Write-ColorOutput "SOME TESTS FAILED" "Red" + Write-ColorOutput "========================================" "Red" + Write-ColorOutput "Review failed tests above for details" "Red" +} + +# Restore backup +Write-Host "" +Write-ColorOutput "Cleaning up and restoring..." "Yellow" +if (Test-Path $env:USERPROFILE\.claude) { + Write-Host " Removing test data from $env:USERPROFILE\.claude" + Remove-Item -Path $env:USERPROFILE\.claude -Recurse -Force +} + +if (Test-Path $BackupDir) { + Write-Host " Restoring backup from $BackupDir" + Move-Item -Path $BackupDir -Destination $env:USERPROFILE\.claude + Write-ColorOutput " Backup restored successfully" "Green" +} + +# Exit with appropriate code +if ($Script:FailedTests -gt 0) { + exit 1 +} else { + exit 0 +} \ No newline at end of file diff --git a/tests/native/windows/uninstall.ps1 b/tests/native/windows/uninstall.ps1 new file mode 100644 index 00000000..978c8b24 --- /dev/null +++ b/tests/native/windows/uninstall.ps1 @@ -0,0 +1,454 @@ +#!/usr/bin/env pwsh + +<# +.SYNOPSIS + Test suite for CCS --uninstall functionality (Windows PowerShell) + +.DESCRIPTION + Tests the --uninstall flag for CCS executable on Windows PowerShell. + Tests against actual system installation for simplicity. + +.NOTES + Author: CCS Test Suite + Version: 1.0 + Based on: install-test.ps1 patterns +#> + +#Requires -Version 5.1 + +param( + [Parameter(Mandatory=$false)] + [string]$CcsPath = (Join-Path $PSScriptRoot "..\ccs.ps1") +) + +# Set error action preference +$ErrorActionPreference = "Stop" + +# Test variables +$PassCount = 0 +$FailCount = 0 +$TotalTests = 0 + +# Colors +$Colors = @{ + Red = "Red" + Green = "Green" + Yellow = "Yellow" + Cyan = "Cyan" + Gray = "Gray" + White = "White" +} + +# Test paths +$ClaudeDir = Join-Path $env:USERPROFILE ".claude" +$BackupDir = Join-Path $env:TEMP "ccs-test-backup-$(Get-Date -Format 'yyyyMMddHHmmss')" +$TestHome = Join-Path $env:TEMP "ccs-test-home" +$TestClaudeDir = Join-Path $TestHome ".claude" + +function Write-ColorOutput { + param( + [string]$Message, + [string]$Color = "White" + ) + Write-Host $Message -ForegroundColor $Colors[$Color] +} + +function Test-Case { + param( + [string]$Name, + [string]$Expected, + [scriptblock]$TestCode + ) + + $script:TotalTests++ + Write-Host "" + Write-ColorOutput "[$TotalTests] $Name" "Cyan" + Write-ColorOutput " Expected: $Expected" "Gray" + + try { + $result = & $TestCode + if ($result) { + Write-ColorOutput " Result: PASS" "Green" + $script:PassCount++ + return $true + } else { + Write-ColorOutput " Result: FAIL" "Red" + $script:FailCount++ + return $false + } + } catch { + Write-ColorOutput " Result: FAIL - Exception: $($_.Exception.Message)" "Red" + $script:FailCount++ + return $false + } +} + +function Cleanup-AndRestore { + Write-Host "" + Write-ColorOutput "Cleaning up and restoring..." "Yellow" + + # Remove test directory + if (Test-Path $TestHome) { + Remove-Item $TestHome -Recurse -Force + } + + # Restore backup if it exists + if (Test-Path $BackupDir) { + if (Test-Path $ClaudeDir) { + Remove-Item $ClaudeDir -Recurse -Force + } + Move-Item $BackupDir $ClaudeDir + } + + Write-ColorOutput "Cleanup complete." "Green" +} + +# ============================================================================ +# SETUP +# ============================================================================ + +Write-Host "" +Write-ColorOutput "========================================" "Cyan" +Write-ColorOutput "CCS --uninstall Functionality Tests" "Cyan" +Write-ColorOutput "========================================" "Cyan" +Write-Host "" + +# Backup existing .claude directory if it exists +if (Test-Path $ClaudeDir) { + Write-ColorOutput "Backing up existing .claude directory..." "Yellow" + Copy-Item $ClaudeDir $BackupDir -Recurse +} + +# Clean any existing test directory +if (Test-Path $TestHome) { + Remove-Item $TestHome -Recurse -Force +} + +New-Item -ItemType Directory -Path $TestHome -Force | Out-Null + +# Verify CCS executable exists +if (-not (Test-Path $CcsPath)) { + Write-ColorOutput "ERROR: CCS executable not found at $CcsPath" "Red" + exit 1 +} + +# Test CCS executable basic functionality +Write-ColorOutput "Testing CCS executable basic functionality..." "Yellow" +try { + $versionOutput = & "$CcsPath" --version 2>&1 | Out-String + Write-ColorOutput "CCS Version: $versionOutput" "Green" +} catch { + Write-ColorOutput "Warning: CCS executable test failed: $($_.Exception.Message)" "Yellow" + Write-ColorOutput "Attempting to continue with tests..." "Yellow" +} + +# ============================================================================ +# TEST SECTION 1: UNINSTALL WHEN NOTHING IS INSTALLED +# ============================================================================ + +Write-Host "" +Write-ColorOutput "========================================" "Yellow" +Write-ColorOutput "SECTION 1: UNINSTALL WHEN NOTHING IS INSTALLED" "Yellow" +Write-ColorOutput "========================================" "Yellow" + +Test-Case "Empty uninstall: Command executes without error" "Exit code 0" { + $originalHome = $env:HOME + $env:HOME = $TestHome + try { + $exitCode = 0 + & "$CcsPath" --uninstall 2>&1 | Out-Null + if ($LASTEXITCODE) { $exitCode = $LASTEXITCODE } + $exitCode -eq 0 + } finally { + $env:HOME = $originalHome + } +} + +Test-Case "Empty uninstall: Output contains appropriate message" "Nothing to uninstall message" { + $originalHome = $env:HOME + $env:HOME = $TestHome + try { + $output = & "$CcsPath" --uninstall 2>&1 | Out-String + $output -match "Nothing to uninstall" -or $output -match "not found" + } finally { + $env:HOME = $originalHome + } +} + +Test-Case "Empty uninstall: Reports 0 items removed" "Zero removed count" { + $originalHome = $env:HOME + $env:HOME = $TestHome + try { + $output = & "$CcsPath" --uninstall 2>&1 | Out-String + $output -match "Removed: 0 items" + } finally { + $env:HOME = $originalHome + } +} + +# ============================================================================ +# SETUP FOR FULL INSTALL/UNINSTALL CYCLE +# ============================================================================ + +# Install first so we can test uninstall +Write-Host "" +Write-ColorOutput "Setting up for install/uninstall cycle test..." "Yellow" +$originalHome = $env:HOME +$env:HOME = $TestHome +try { + & $CcsPath --install | Out-Null + Write-ColorOutput "Install completed successfully" "Green" +} catch { + Write-ColorOutput "Warning: Install command failed, but continuing with tests" "Yellow" +} finally { + $env:HOME = $originalHome +} + +# ============================================================================ +# TEST SECTION 2: UNINSTALL AFTER INSTALL +# ============================================================================ + +Write-Host "" +Write-ColorOutput "========================================" "Yellow" +Write-ColorOutput "SECTION 2: UNINSTALL AFTER INSTALL" "Yellow" +Write-ColorOutput "========================================" "Yellow" + +Test-Case "Uninstall: Command executes without error" "Exit code 0" { + $originalHome = $env:HOME + $env:HOME = $TestHome + try { + $exitCode = 0 + & "$CcsPath" --uninstall 2>&1 | Out-Null + if ($LASTEXITCODE) { $exitCode = $LASTEXITCODE } + $exitCode -eq 0 + } finally { + $env:HOME = $originalHome + } +} + +Test-Case "Uninstall: Removes ccs.md command file" "Command file removed" { + -not (Test-Path (Join-Path $TestClaudeDir "commands\ccs.md")) +} + +Test-Case "Uninstall: Removes ccs-delegation skill directory" "Skill directory removed" { + -not (Test-Path (Join-Path $TestClaudeDir "skills\ccs-delegation")) +} + +Test-Case "Uninstall: Preserves other commands" "Other commands unaffected" { + # Create a dummy command file first + $commandsDir = Join-Path $TestClaudeDir "commands" + New-Item -ItemType Directory -Path $commandsDir -Force | Out-Null + Set-Content -Path (Join-Path $commandsDir "test.md") -Value "test" + + # Install CCS, then uninstall + $originalHome = $env:HOME + $env:HOME = $TestHome + try { + & $CcsPath --install | Out-Null + & $CcsPath --uninstall | Out-Null + } finally { + $env:HOME = $originalHome + } + + # Check that test.md still exists + Test-Path (Join-Path $commandsDir "test.md") +} + +Test-Case "Uninstall: Preserves other skills" "Other skills unaffected" { + # Create a dummy skill directory first + $skillDir = Join-Path $TestClaudeDir "skills\test-skill" + New-Item -ItemType Directory -Path $skillDir -Force | Out-Null + Set-Content -Path (Join-Path $skillDir "SKILL.md") -Value "test" + + # Install CCS, then uninstall + $originalHome = $env:HOME + $env:HOME = $TestHome + try { + & $CcsPath --install | Out-Null + & $CcsPath --uninstall | Out-Null + } finally { + $env:HOME = $originalHome + } + + # Check that test-skill still exists + Test-Path $skillDir +} + +# ============================================================================ +# TEST SECTION 3: IDEMPOTENCY +# ============================================================================ + +Write-Host "" +Write-ColorOutput "========================================" "Yellow" +Write-ColorOutput "SECTION 3: IDEMPOTENCY" "Yellow" +Write-ColorOutput "========================================" "Yellow" + +Test-Case "Idempotent: Second uninstall succeeds" "Second --uninstall doesn't error" { + $env:HOME = $TestHome + try { + & $CcsPath --uninstall | Out-Null + return $true + } catch { + return $false + } +} + +Test-Case "Idempotent: Reports nothing to remove on second run" "Reports nothing found" { + $env:HOME = $TestHome + $output = try { & $CcsPath --uninstall 2>&1 | Out-String } catch { $_.Exception.Message } + $output -match "not found" -or $output -match "Nothing to uninstall" +} + +# ============================================================================ +# TEST SECTION 4: OUTPUT FORMATTING +# ============================================================================ + +Write-Host "" +Write-ColorOutput "========================================" "Yellow" +Write-ColorOutput "SECTION 4: OUTPUT FORMATTING" "Yellow" +Write-ColorOutput "========================================" "Yellow" + +# Set up fresh install for output testing +$env:HOME = $TestHome +try { & $CcsPath --install | Out-Null } catch { } + +Test-Case "Output: Contains uninstall header" "Contains uninstall message" { + $env:HOME = $TestHome + $output = try { & $CcsPath --uninstall 2>&1 | Out-String } catch { $_.Exception.Message } + $output -match "Uninstalling CCS" +} + +Test-Case "Output: Shows removal success message" "Contains success message" { + $env:HOME = $TestHome + $output = try { & $CcsPath --uninstall 2>&1 | Out-String } catch { $_.Exception.Message } + $output -match "\[OK\] Uninstall complete!" +} + +Test-Case "Output: Shows reinstallation instruction" "Contains reinstallation hint" { + $env:HOME = $TestHome + $output = try { & $CcsPath --uninstall 2>&1 | Out-String } catch { $_.Exception.Message } + $output -match "To reinstall: ccs --install" +} + +# ============================================================================ +# TEST SECTION 5: INTEGRATION WITH CCS +# ============================================================================ + +Write-Host "" +Write-ColorOutput "========================================" "Yellow" +Write-ColorOutput "SECTION 5: INTEGRATION WITH CCS" "Yellow" +Write-ColorOutput "========================================" "Yellow" + +Test-Case "Integration: --uninstall executes without profile error" "No profile error on --uninstall" { + $env:HOME = $TestHome + try { + & $CcsPath --uninstall | Out-Null + return $true + } catch { + return $false + } +} + +Test-Case "Integration: --version still works after uninstall" "Version command exits successfully" { + $env:HOME = $TestHome + try { + & $CcsPath --version | Out-Null + return $true + } catch { + return $false + } +} + +Test-Case "Integration: --help still works after uninstall" "Help command exits successfully" { + $env:HOME = $TestHome + try { + & $CcsPath --help | Out-Null + return $true + } catch { + return $false + } +} + +# ============================================================================ +# TEST SECTION 6: EDGE CASES +# ============================================================================ + +Write-Host "" +Write-ColorOutput "========================================" "Yellow" +Write-ColorOutput "SECTION 6: EDGE CASES" "Yellow" +Write-ColorOutput "========================================" "Yellow" + +Test-Case "Edge case: Partial install (commands only)" "Handles partial installation" { + # Create only command file + $commandsDir = Join-Path $TestClaudeDir "commands" + New-Item -ItemType Directory -Path $commandsDir -Force | Out-Null + Set-Content -Path (Join-Path $commandsDir "ccs.md") -Value "test" + + # Uninstall should handle this gracefully + $env:HOME = $TestHome + try { + & $CcsPath --uninstall | Out-Null + return $true + } catch { + return $false + } +} + +Test-Case "Edge case: Partial install (skills only)" "Handles partial installation" { + # Create only skill directory + $skillDir = Join-Path $TestClaudeDir "skills\ccs-delegation" + New-Item -ItemType Directory -Path $skillDir -Force | Out-Null + Set-Content -Path (Join-Path $skillDir "SKILL.md") -Value "test" + + # Uninstall should handle this gracefully + $env:HOME = $TestHome + try { + & $CcsPath --uninstall | Out-Null + return $true + } catch { + return $false + } +} + +Test-Case "Edge case: Missing parent directories" "Handles missing .claude directory" { + # Ensure .claude directory doesn't exist + if (Test-Path $TestClaudeDir) { + Remove-Item $TestClaudeDir -Recurse -Force + } + + $env:HOME = $TestHome + try { + & $CcsPath --uninstall | Out-Null + return $true + } catch { + return $false + } +} + +# ============================================================================ +# SUMMARY +# ============================================================================ + +Write-Host "" +Write-ColorOutput "========================================" "Cyan" +if ($FailCount -eq 0) { + Write-ColorOutput "ALL TESTS PASSED!" "Green" + Write-ColorOutput "========================================" "Green" + Write-ColorOutput "--uninstall functionality is production ready!" "Green" +} else { + Write-ColorOutput "SOME TESTS FAILED" "Red" + Write-ColorOutput "========================================" "Red" + Write-ColorOutput "Review failed tests above for details" "Red" +} + +Write-Host "" +Write-ColorOutput "Test Summary:" "Cyan" +Write-Host " Total tests: $TotalTests" +Write-ColorOutput " Passed: $PassCount" "Green" +Write-ColorOutput " Failed: $FailCount" "Red" +Write-Host "" + +# Cleanup +Cleanup-AndRestore + +exit $FailCount \ No newline at end of file