mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-13 22:15:33 +00:00
refactor(tests): Phase 3 - Move native tests to new structure
- Copy Unix and Windows tests to native/ directories - Update Unix tests to source shared helpers - Remove Section 10 (npm tests) from native tests - Update final results to use shared helper functions
This commit is contained in:
1 parent
7aa01eb5f7
commit
66e93fcae1
7 files changed
+2957
No files matched your search
Executable
+280
@@ -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
|
||||
Executable
+446
@@ -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
|
||||
Executable
+310
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
Reference in new issue
Block a user