mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-17 22:16:38 +00:00
- ClaudeSymlinkManager.uninstall() now returns count for accurate reporting - install-command.ts uses return value instead of always incrementing - claude-dir-installer.ts uses getCcsHome() directly instead of string manipulation - Add TODO comments to tests explaining --install is a no-op
377 lines
13 KiB
Bash
Executable File
377 lines
13 KiB
Bash
Executable File
#!/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
|
|
# NOTE: --install is currently a no-op (under development), but we call it
|
|
# to test the install/uninstall cycle works without errors
|
|
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 ]]
|
|
"
|
|
|
|
# ============================================================================
|
|
# TEST SECTION 7: PER-PROFILE HOOK BEHAVIOR (Global settings untouched)
|
|
# ============================================================================
|
|
|
|
echo ""
|
|
echo "========================================"
|
|
echo "SECTION 7: PER-PROFILE HOOK BEHAVIOR"
|
|
echo "========================================"
|
|
|
|
test_case "Per-profile: Uninstall does NOT touch global settings.json" "Global settings unchanged" bash -c "
|
|
# Setup: Create settings.json with user hook
|
|
mkdir -p '$TEST_CLAUDE_DIR'
|
|
cat > '$TEST_CLAUDE_DIR/settings.json' << 'EOFINNER'
|
|
{
|
|
\"hooks\": {
|
|
\"PreToolUse\": [
|
|
{ \"matcher\": \"WebSearch\", \"hooks\": [{ \"command\": \"my-custom-hook.sh\" }] }
|
|
]
|
|
}
|
|
}
|
|
EOFINNER
|
|
# Uninstall
|
|
HOME='$TEST_HOME' '$CCS_PATH' --uninstall > /dev/null 2>&1
|
|
# Verify global settings unchanged
|
|
grep -q 'my-custom-hook' '$TEST_CLAUDE_DIR/settings.json'
|
|
"
|
|
|
|
test_case "Per-profile: Uninstall preserves user hooks in global settings" "User hooks in global preserved" bash -c "
|
|
# Setup: Create settings.json with CCS hook (old format - shouldn't be touched)
|
|
mkdir -p '$TEST_CLAUDE_DIR'
|
|
cat > '$TEST_CLAUDE_DIR/settings.json' << 'EOFINNER'
|
|
{
|
|
\"hooks\": {
|
|
\"PreToolUse\": [
|
|
{ \"matcher\": \"WebSearch\", \"hooks\": [{ \"command\": \"node ~/.ccs/hooks/websearch-transformer.cjs\" }] },
|
|
{ \"matcher\": \"WebSearch\", \"hooks\": [{ \"command\": \"my-custom-hook.sh\" }] }
|
|
]
|
|
}
|
|
}
|
|
EOFINNER
|
|
# Uninstall - should NOT modify global settings
|
|
HOME='$TEST_HOME' '$CCS_PATH' --uninstall > /dev/null 2>&1
|
|
# Both hooks should still be in global settings (not touched)
|
|
grep -q 'websearch-transformer' '$TEST_CLAUDE_DIR/settings.json' &&
|
|
grep -q 'my-custom-hook' '$TEST_CLAUDE_DIR/settings.json'
|
|
"
|
|
|
|
test_case "Per-profile: Migration marker cleanup on uninstall" "Marker file removed" bash -c "
|
|
# Setup: Create ~/.ccs/.hook-migrated marker
|
|
mkdir -p '$TEST_HOME/.ccs'
|
|
echo '2026-01-25T00:00:00.000Z' > '$TEST_HOME/.ccs/.hook-migrated'
|
|
# Uninstall
|
|
HOME='$TEST_HOME' '$CCS_PATH' --uninstall > /dev/null 2>&1
|
|
# Verify marker removed
|
|
[[ ! -f '$TEST_HOME/.ccs/.hook-migrated' ]]
|
|
"
|
|
|
|
test_case "Per-profile: Handles missing settings.json" "No error when settings.json missing" bash -c "
|
|
# Ensure settings.json doesn't exist
|
|
rm -f '$TEST_CLAUDE_DIR/settings.json'
|
|
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 |