mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 06:16:37 +00:00
- 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
875 lines
29 KiB
PowerShell
875 lines
29 KiB
PowerShell
# 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
|
|
}
|