mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 18:16:08 +00:00
- postuninstall.js: add file logging for debugging on error - profile-hook-injector.ts: use 'wx' flag for atomic marker creation - profile-hook-injector.ts: include parse error message in debug log - install-command.ts: use actual counts for consistent semantics - Windows tests: align Section 7 with per-profile hook architecture
550 lines
17 KiB
PowerShell
550 lines
17 KiB
PowerShell
#!/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
|
|
# NOTE: --install is currently a no-op (under development), but we call it
|
|
# to test the install/uninstall cycle works without errors
|
|
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
|
|
}
|
|
}
|
|
|
|
# ============================================================================
|
|
# TEST SECTION 7: PER-PROFILE HOOK BEHAVIOR (Global settings untouched)
|
|
# ============================================================================
|
|
|
|
Write-Host ""
|
|
Write-ColorOutput "========================================" "Yellow"
|
|
Write-ColorOutput "SECTION 7: PER-PROFILE HOOK BEHAVIOR" "Yellow"
|
|
Write-ColorOutput "========================================" "Yellow"
|
|
|
|
Test-Case "Per-profile: Uninstall does NOT touch global settings.json" "Global settings unchanged" {
|
|
# Setup: Create settings.json with user hook
|
|
New-Item -ItemType Directory -Path $TestClaudeDir -Force | Out-Null
|
|
$settingsPath = Join-Path $TestClaudeDir "settings.json"
|
|
$settingsContent = @'
|
|
{
|
|
"hooks": {
|
|
"PreToolUse": [
|
|
{ "matcher": "WebSearch", "hooks": [{ "command": "my-custom-hook.sh" }] }
|
|
]
|
|
}
|
|
}
|
|
'@
|
|
Set-Content -Path $settingsPath -Value $settingsContent
|
|
|
|
# Uninstall
|
|
$env:HOME = $TestHome
|
|
try {
|
|
& $CcsPath --uninstall | Out-Null
|
|
} catch { }
|
|
|
|
# Verify global settings unchanged
|
|
$content = Get-Content -Path $settingsPath -Raw
|
|
$content -match "my-custom-hook"
|
|
}
|
|
|
|
Test-Case "Per-profile: Uninstall preserves CCS hooks in global settings (not touched)" "Both hooks preserved" {
|
|
# Setup: Create settings.json with CCS hook (old format - shouldn't be touched)
|
|
New-Item -ItemType Directory -Path $TestClaudeDir -Force | Out-Null
|
|
$settingsPath = Join-Path $TestClaudeDir "settings.json"
|
|
$settingsContent = @'
|
|
{
|
|
"hooks": {
|
|
"PreToolUse": [
|
|
{ "matcher": "WebSearch", "hooks": [{ "command": "node ~/.ccs/hooks/websearch-transformer.cjs" }] },
|
|
{ "matcher": "WebSearch", "hooks": [{ "command": "my-custom-hook.sh" }] }
|
|
]
|
|
}
|
|
}
|
|
'@
|
|
Set-Content -Path $settingsPath -Value $settingsContent
|
|
|
|
# Uninstall - should NOT modify global settings
|
|
$env:HOME = $TestHome
|
|
try {
|
|
& $CcsPath --uninstall | Out-Null
|
|
} catch { }
|
|
|
|
# Both hooks should still be in global settings (not touched)
|
|
$content = Get-Content -Path $settingsPath -Raw
|
|
($content -match "websearch-transformer") -and ($content -match "my-custom-hook")
|
|
}
|
|
|
|
Test-Case "Per-profile: Migration marker cleanup on uninstall" "Marker file removed" {
|
|
# Setup: Create ~/.ccs/.hook-migrated marker
|
|
$ccsDir = Join-Path $TestHome ".ccs"
|
|
New-Item -ItemType Directory -Path $ccsDir -Force | Out-Null
|
|
Set-Content -Path (Join-Path $ccsDir ".hook-migrated") -Value "2026-01-25T00:00:00.000Z"
|
|
|
|
# Uninstall
|
|
$env:HOME = $TestHome
|
|
try {
|
|
& $CcsPath --uninstall | Out-Null
|
|
} catch { }
|
|
|
|
# Verify marker removed
|
|
-not (Test-Path (Join-Path $ccsDir ".hook-migrated"))
|
|
}
|
|
|
|
Test-Case "Per-profile: Handles missing settings.json" "No error when settings.json missing" {
|
|
# Ensure settings.json doesn't exist
|
|
$settingsPath = Join-Path $TestClaudeDir "settings.json"
|
|
if (Test-Path $settingsPath) {
|
|
Remove-Item $settingsPath -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 |