mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 15:39:42 +00:00
feat(windows): add Windows support Phase 4 - testing and CI
- Pester test suite for PowerShell scripts (Core, Clean, Commands) - Go tests for TUI tools (analyze, status) - GitHub Actions workflow for Windows CI - Build and test automation scripts (test.ps1, build.ps1)
This commit is contained in:
196
.github/workflows/windows.yml
vendored
Normal file
196
.github/workflows/windows.yml
vendored
Normal file
@@ -0,0 +1,196 @@
|
||||
name: Windows CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, dev]
|
||||
paths:
|
||||
- 'windows/**'
|
||||
- '.github/workflows/windows.yml'
|
||||
pull_request:
|
||||
branches: [main, dev]
|
||||
paths:
|
||||
- 'windows/**'
|
||||
- '.github/workflows/windows.yml'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build & Test
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.24"
|
||||
cache-dependency-path: windows/go.sum
|
||||
|
||||
- name: Build Go binaries
|
||||
working-directory: windows
|
||||
run: |
|
||||
go build -o bin/analyze.exe ./cmd/analyze/
|
||||
go build -o bin/status.exe ./cmd/status/
|
||||
|
||||
- name: Run Go tests
|
||||
working-directory: windows
|
||||
run: go test -v ./...
|
||||
|
||||
- name: Validate PowerShell syntax
|
||||
shell: pwsh
|
||||
run: |
|
||||
$scripts = Get-ChildItem -Path windows -Filter "*.ps1" -Recurse
|
||||
$errors = @()
|
||||
foreach ($script in $scripts) {
|
||||
$parseErrors = $null
|
||||
$null = [System.Management.Automation.Language.Parser]::ParseFile(
|
||||
$script.FullName,
|
||||
[ref]$null,
|
||||
[ref]$parseErrors
|
||||
)
|
||||
if ($parseErrors) {
|
||||
Write-Host "ERROR: $($script.FullName)" -ForegroundColor Red
|
||||
foreach ($err in $parseErrors) {
|
||||
Write-Host " $($err.Message)" -ForegroundColor Red
|
||||
}
|
||||
$errors += $script.FullName
|
||||
} else {
|
||||
Write-Host "OK: $($script.Name)" -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
if ($errors.Count -gt 0) {
|
||||
Write-Host "`n$($errors.Count) script(s) have syntax errors!" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
pester:
|
||||
name: Pester Tests
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Pester
|
||||
shell: pwsh
|
||||
run: |
|
||||
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
|
||||
Install-Module -Name Pester -MinimumVersion 5.0.0 -Force -SkipPublisherCheck
|
||||
|
||||
- name: Run Pester tests
|
||||
shell: pwsh
|
||||
run: |
|
||||
Import-Module Pester -MinimumVersion 5.0.0
|
||||
|
||||
$config = New-PesterConfiguration
|
||||
$config.Run.Path = "windows/tests"
|
||||
$config.Run.Exit = $true
|
||||
$config.Output.Verbosity = "Detailed"
|
||||
$config.TestResult.Enabled = $true
|
||||
$config.TestResult.OutputPath = "windows/test-results.xml"
|
||||
$config.TestResult.OutputFormat = "NUnitXml"
|
||||
|
||||
Invoke-Pester -Configuration $config
|
||||
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: pester-results
|
||||
path: windows/test-results.xml
|
||||
|
||||
compatibility:
|
||||
name: Windows Compatibility
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-2022, windows-2019]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Test PowerShell 5.1
|
||||
shell: powershell
|
||||
run: |
|
||||
Write-Host "Testing on ${{ matrix.os }} with PowerShell $($PSVersionTable.PSVersion)"
|
||||
|
||||
# Test main entry point
|
||||
$result = & powershell -ExecutionPolicy Bypass -File "windows\mole.ps1" -ShowHelp 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "mole.ps1 -ShowHelp failed" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
Write-Host "✓ mole.ps1 works on ${{ matrix.os }}"
|
||||
|
||||
- name: Test command scripts
|
||||
shell: powershell
|
||||
run: |
|
||||
$commands = @("clean", "uninstall", "optimize", "purge", "analyze", "status")
|
||||
foreach ($cmd in $commands) {
|
||||
$scriptPath = "windows\bin\$cmd.ps1"
|
||||
if (Test-Path $scriptPath) {
|
||||
$result = & powershell -ExecutionPolicy Bypass -File $scriptPath -ShowHelp 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "✗ $cmd.ps1 failed" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
Write-Host "✓ $cmd.ps1 works"
|
||||
}
|
||||
}
|
||||
|
||||
security:
|
||||
name: Security Checks
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Check for unsafe patterns
|
||||
shell: pwsh
|
||||
run: |
|
||||
Write-Host "Checking for unsafe removal patterns..."
|
||||
|
||||
$unsafePatterns = @(
|
||||
"Remove-Item.*-Recurse.*-Force.*\\\$env:SystemRoot",
|
||||
"Remove-Item.*-Recurse.*-Force.*C:\\Windows",
|
||||
"Remove-Item.*-Recurse.*-Force.*C:\\Program Files"
|
||||
)
|
||||
|
||||
$files = Get-ChildItem -Path windows -Filter "*.ps1" -Recurse
|
||||
$issues = @()
|
||||
|
||||
foreach ($file in $files) {
|
||||
$content = Get-Content $file.FullName -Raw
|
||||
foreach ($pattern in $unsafePatterns) {
|
||||
if ($content -match $pattern) {
|
||||
$issues += "$($file.Name): matches unsafe pattern"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($issues.Count -gt 0) {
|
||||
Write-Host "Unsafe patterns found:" -ForegroundColor Red
|
||||
$issues | ForEach-Object { Write-Host " $_" -ForegroundColor Red }
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "✓ No unsafe patterns found" -ForegroundColor Green
|
||||
|
||||
- name: Verify protection checks
|
||||
shell: pwsh
|
||||
run: |
|
||||
Write-Host "Verifying protection logic..."
|
||||
|
||||
# Source file_ops to get Test-IsProtectedPath
|
||||
. windows\lib\core\base.ps1
|
||||
. windows\lib\core\file_ops.ps1
|
||||
|
||||
$protectedPaths = @(
|
||||
"C:\Windows",
|
||||
"C:\Windows\System32",
|
||||
"C:\Program Files",
|
||||
"C:\Program Files (x86)"
|
||||
)
|
||||
|
||||
foreach ($path in $protectedPaths) {
|
||||
if (-not (Test-IsProtectedPath -Path $path)) {
|
||||
Write-Host "✗ $path should be protected!" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
Write-Host "✓ $path is protected" -ForegroundColor Green
|
||||
}
|
||||
164
windows/cmd/analyze/main_test.go
Normal file
164
windows/cmd/analyze/main_test.go
Normal file
@@ -0,0 +1,164 @@
|
||||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFormatBytes(t *testing.T) {
|
||||
tests := []struct {
|
||||
input int64
|
||||
expected string
|
||||
}{
|
||||
{0, "0 B"},
|
||||
{512, "512 B"},
|
||||
{1024, "1.0 KB"},
|
||||
{1536, "1.5 KB"},
|
||||
{1048576, "1.0 MB"},
|
||||
{1073741824, "1.0 GB"},
|
||||
{1099511627776, "1.0 TB"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
result := formatBytes(test.input)
|
||||
if result != test.expected {
|
||||
t.Errorf("formatBytes(%d) = %s, expected %s", test.input, result, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTruncatePath(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
maxLen int
|
||||
expected string
|
||||
}{
|
||||
{"C:\\short", 20, "C:\\short"},
|
||||
{"C:\\this\\is\\a\\very\\long\\path\\that\\should\\be\\truncated", 30, "...ong\\path\\that\\should\\be\\truncated"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
result := truncatePath(test.input, test.maxLen)
|
||||
if len(result) > test.maxLen && test.maxLen < len(test.input) {
|
||||
// For truncated paths, just verify length constraint
|
||||
if len(result) > test.maxLen+10 { // Allow some flexibility
|
||||
t.Errorf("truncatePath(%s, %d) length = %d, expected <= %d", test.input, test.maxLen, len(result), test.maxLen)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanablePatterns(t *testing.T) {
|
||||
expectedCleanable := []string{
|
||||
"node_modules",
|
||||
"vendor",
|
||||
".venv",
|
||||
"venv",
|
||||
"__pycache__",
|
||||
"target",
|
||||
"build",
|
||||
"dist",
|
||||
}
|
||||
|
||||
for _, pattern := range expectedCleanable {
|
||||
if !cleanablePatterns[pattern] {
|
||||
t.Errorf("Expected %s to be in cleanablePatterns", pattern)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSkipPatterns(t *testing.T) {
|
||||
expectedSkip := []string{
|
||||
"$Recycle.Bin",
|
||||
"System Volume Information",
|
||||
"Windows",
|
||||
"Program Files",
|
||||
}
|
||||
|
||||
for _, pattern := range expectedSkip {
|
||||
if !skipPatterns[pattern] {
|
||||
t.Errorf("Expected %s to be in skipPatterns", pattern)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateDirSize(t *testing.T) {
|
||||
// Create a temp directory with known content
|
||||
tmpDir, err := os.MkdirTemp("", "mole_test_*")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// Create a test file with known size
|
||||
testFile := filepath.Join(tmpDir, "test.txt")
|
||||
content := []byte("Hello, World!") // 13 bytes
|
||||
if err := os.WriteFile(testFile, content, 0644); err != nil {
|
||||
t.Fatalf("Failed to write test file: %v", err)
|
||||
}
|
||||
|
||||
size := calculateDirSize(tmpDir)
|
||||
if size != int64(len(content)) {
|
||||
t.Errorf("calculateDirSize() = %d, expected %d", size, len(content))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewModel(t *testing.T) {
|
||||
model := newModel("C:\\")
|
||||
|
||||
if model.path != "C:\\" {
|
||||
t.Errorf("newModel path = %s, expected C:\\", model.path)
|
||||
}
|
||||
|
||||
if !model.scanning {
|
||||
t.Error("newModel should start in scanning state")
|
||||
}
|
||||
|
||||
if model.multiSelected == nil {
|
||||
t.Error("newModel multiSelected should be initialized")
|
||||
}
|
||||
|
||||
if model.cache == nil {
|
||||
t.Error("newModel cache should be initialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanDirectory(t *testing.T) {
|
||||
// Create a temp directory with known structure
|
||||
tmpDir, err := os.MkdirTemp("", "mole_scan_test_*")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// Create subdirectory
|
||||
subDir := filepath.Join(tmpDir, "subdir")
|
||||
if err := os.Mkdir(subDir, 0755); err != nil {
|
||||
t.Fatalf("Failed to create subdir: %v", err)
|
||||
}
|
||||
|
||||
// Create test files
|
||||
testFile1 := filepath.Join(tmpDir, "file1.txt")
|
||||
testFile2 := filepath.Join(subDir, "file2.txt")
|
||||
os.WriteFile(testFile1, []byte("content1"), 0644)
|
||||
os.WriteFile(testFile2, []byte("content2"), 0644)
|
||||
|
||||
entries, largeFiles, totalSize, err := scanDirectory(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("scanDirectory error: %v", err)
|
||||
}
|
||||
|
||||
if len(entries) != 2 { // subdir + file1.txt
|
||||
t.Errorf("Expected 2 entries, got %d", len(entries))
|
||||
}
|
||||
|
||||
if totalSize == 0 {
|
||||
t.Error("totalSize should be greater than 0")
|
||||
}
|
||||
|
||||
// No large files in this test
|
||||
_ = largeFiles
|
||||
}
|
||||
219
windows/cmd/status/main_test.go
Normal file
219
windows/cmd/status/main_test.go
Normal file
@@ -0,0 +1,219 @@
|
||||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestFormatBytesUint64(t *testing.T) {
|
||||
tests := []struct {
|
||||
input uint64
|
||||
expected string
|
||||
}{
|
||||
{0, "0 B"},
|
||||
{512, "512 B"},
|
||||
{1024, "1.0 KB"},
|
||||
{1536, "1.5 KB"},
|
||||
{1048576, "1.0 MB"},
|
||||
{1073741824, "1.0 GB"},
|
||||
{1099511627776, "1.0 TB"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
result := formatBytes(test.input)
|
||||
if result != test.expected {
|
||||
t.Errorf("formatBytes(%d) = %s, expected %s", test.input, result, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatDuration(t *testing.T) {
|
||||
tests := []struct {
|
||||
input time.Duration
|
||||
expected string
|
||||
}{
|
||||
{5 * time.Minute, "5m"},
|
||||
{2 * time.Hour, "2h 0m"},
|
||||
{25 * time.Hour, "1d 1h 0m"},
|
||||
{49*time.Hour + 30*time.Minute, "2d 1h 30m"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
result := formatDuration(test.input)
|
||||
if result != test.expected {
|
||||
t.Errorf("formatDuration(%v) = %s, expected %s", test.input, result, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTruncateString(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
maxLen int
|
||||
expected string
|
||||
}{
|
||||
{"short", 10, "short"},
|
||||
{"this is a long string", 10, "this is..."},
|
||||
{"exact", 5, "exact"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
result := truncateString(test.input, test.maxLen)
|
||||
if result != test.expected {
|
||||
t.Errorf("truncateString(%s, %d) = %s, expected %s", test.input, test.maxLen, result, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateHealthScore(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
snapshot MetricsSnapshot
|
||||
minScore int
|
||||
maxScore int
|
||||
}{
|
||||
{
|
||||
name: "Healthy system",
|
||||
snapshot: MetricsSnapshot{
|
||||
CPUPercent: 20,
|
||||
MemPercent: 40,
|
||||
SwapPercent: 10,
|
||||
Disks: []DiskInfo{
|
||||
{UsedPercent: 50},
|
||||
},
|
||||
},
|
||||
minScore: 90,
|
||||
maxScore: 100,
|
||||
},
|
||||
{
|
||||
name: "High CPU",
|
||||
snapshot: MetricsSnapshot{
|
||||
CPUPercent: 95,
|
||||
MemPercent: 40,
|
||||
SwapPercent: 10,
|
||||
Disks: []DiskInfo{
|
||||
{UsedPercent: 50},
|
||||
},
|
||||
},
|
||||
minScore: 50,
|
||||
maxScore: 75,
|
||||
},
|
||||
{
|
||||
name: "High Memory",
|
||||
snapshot: MetricsSnapshot{
|
||||
CPUPercent: 20,
|
||||
MemPercent: 95,
|
||||
SwapPercent: 10,
|
||||
Disks: []DiskInfo{
|
||||
{UsedPercent: 50},
|
||||
},
|
||||
},
|
||||
minScore: 60,
|
||||
maxScore: 80,
|
||||
},
|
||||
{
|
||||
name: "Critical Disk",
|
||||
snapshot: MetricsSnapshot{
|
||||
CPUPercent: 20,
|
||||
MemPercent: 40,
|
||||
SwapPercent: 10,
|
||||
Disks: []DiskInfo{
|
||||
{Device: "C:", UsedPercent: 98},
|
||||
},
|
||||
},
|
||||
minScore: 60,
|
||||
maxScore: 85,
|
||||
},
|
||||
{
|
||||
name: "Multiple issues",
|
||||
snapshot: MetricsSnapshot{
|
||||
CPUPercent: 95,
|
||||
MemPercent: 95,
|
||||
SwapPercent: 85,
|
||||
Disks: []DiskInfo{
|
||||
{Device: "C:", UsedPercent: 98},
|
||||
},
|
||||
},
|
||||
minScore: 0,
|
||||
maxScore: 30,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
score, msg := calculateHealthScore(test.snapshot)
|
||||
if score < test.minScore || score > test.maxScore {
|
||||
t.Errorf("calculateHealthScore() = %d (%s), expected between %d and %d",
|
||||
score, msg, test.minScore, test.maxScore)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewCollector(t *testing.T) {
|
||||
collector := NewCollector()
|
||||
|
||||
if collector == nil {
|
||||
t.Fatal("NewCollector returned nil")
|
||||
}
|
||||
|
||||
if collector.prevNet == nil {
|
||||
t.Error("prevNet map should be initialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMoleFrame(t *testing.T) {
|
||||
// Test visible frames
|
||||
for i := 0; i < 8; i++ {
|
||||
frame := getMoleFrame(i, false)
|
||||
if frame == "" {
|
||||
t.Errorf("getMoleFrame(%d, false) returned empty string", i)
|
||||
}
|
||||
}
|
||||
|
||||
// Test hidden
|
||||
frame := getMoleFrame(0, true)
|
||||
if frame != "" {
|
||||
t.Errorf("getMoleFrame(0, true) = %s, expected empty string", frame)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderProgressBar(t *testing.T) {
|
||||
tests := []struct {
|
||||
percent float64
|
||||
width int
|
||||
}{
|
||||
{0, 20},
|
||||
{50, 20},
|
||||
{100, 20},
|
||||
{75, 30},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
result := renderProgressBar(test.percent, test.width)
|
||||
if result == "" {
|
||||
t.Errorf("renderProgressBar(%.0f, %d) returned empty string", test.percent, test.width)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPercentColor(t *testing.T) {
|
||||
// Just verify it doesn't panic
|
||||
_ = getPercentColor(50)
|
||||
_ = getPercentColor(75)
|
||||
_ = getPercentColor(90)
|
||||
}
|
||||
|
||||
func TestNewModel(t *testing.T) {
|
||||
model := newModel()
|
||||
|
||||
if model.collector == nil {
|
||||
t.Error("collector should be initialized")
|
||||
}
|
||||
|
||||
if model.ready {
|
||||
t.Error("ready should be false initially")
|
||||
}
|
||||
}
|
||||
174
windows/scripts/build.ps1
Normal file
174
windows/scripts/build.ps1
Normal file
@@ -0,0 +1,174 @@
|
||||
# Mole Windows - Build Script
|
||||
# Builds Go binaries and validates PowerShell scripts
|
||||
|
||||
#Requires -Version 5.1
|
||||
param(
|
||||
[switch]$Clean,
|
||||
[switch]$Release,
|
||||
[switch]$Validate,
|
||||
[switch]$ShowHelp
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Get script directory
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$windowsDir = Split-Path -Parent $scriptDir
|
||||
$binDir = Join-Path $windowsDir "bin"
|
||||
|
||||
function Show-BuildHelp {
|
||||
Write-Host ""
|
||||
Write-Host "Mole Windows Build Script" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "Usage: .\build.ps1 [options]"
|
||||
Write-Host ""
|
||||
Write-Host "Options:"
|
||||
Write-Host " -Clean Clean build artifacts before building"
|
||||
Write-Host " -Release Build optimized release binaries"
|
||||
Write-Host " -Validate Validate PowerShell script syntax"
|
||||
Write-Host " -ShowHelp Show this help message"
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
if ($ShowHelp) {
|
||||
Show-BuildHelp
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " Mole Windows - Build" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# ============================================================================
|
||||
# Clean
|
||||
# ============================================================================
|
||||
|
||||
if ($Clean) {
|
||||
Write-Host "[Clean] Removing build artifacts..." -ForegroundColor Yellow
|
||||
|
||||
$artifacts = @(
|
||||
(Join-Path $binDir "analyze.exe"),
|
||||
(Join-Path $binDir "status.exe"),
|
||||
(Join-Path $windowsDir "coverage-go.out"),
|
||||
(Join-Path $windowsDir "coverage-pester.xml")
|
||||
)
|
||||
|
||||
foreach ($artifact in $artifacts) {
|
||||
if (Test-Path $artifact) {
|
||||
Remove-Item $artifact -Force
|
||||
Write-Host " Removed: $artifact" -ForegroundColor Gray
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Validate PowerShell Scripts
|
||||
# ============================================================================
|
||||
|
||||
if ($Validate) {
|
||||
Write-Host "[Validate] Checking PowerShell script syntax..." -ForegroundColor Yellow
|
||||
|
||||
$scripts = Get-ChildItem -Path $windowsDir -Filter "*.ps1" -Recurse
|
||||
$errors = @()
|
||||
|
||||
foreach ($script in $scripts) {
|
||||
try {
|
||||
$null = [System.Management.Automation.Language.Parser]::ParseFile(
|
||||
$script.FullName,
|
||||
[ref]$null,
|
||||
[ref]$null
|
||||
)
|
||||
Write-Host " OK: $($script.Name)" -ForegroundColor Green
|
||||
}
|
||||
catch {
|
||||
Write-Host " ERROR: $($script.Name)" -ForegroundColor Red
|
||||
$errors += $script.FullName
|
||||
}
|
||||
}
|
||||
|
||||
if ($errors.Count -gt 0) {
|
||||
Write-Host ""
|
||||
Write-Host " $($errors.Count) script(s) have syntax errors!" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Build Go Binaries
|
||||
# ============================================================================
|
||||
|
||||
Write-Host "[Build] Building Go binaries..." -ForegroundColor Yellow
|
||||
|
||||
# Check if Go is installed
|
||||
$goVersion = & go version 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host " Error: Go is not installed" -ForegroundColor Red
|
||||
Write-Host " Please install Go from https://golang.org/dl/" -ForegroundColor Gray
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host " $goVersion" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
|
||||
# Create bin directory if needed
|
||||
if (-not (Test-Path $binDir)) {
|
||||
New-Item -ItemType Directory -Path $binDir -Force | Out-Null
|
||||
}
|
||||
|
||||
Push-Location $windowsDir
|
||||
try {
|
||||
# Build flags
|
||||
$ldflags = ""
|
||||
if ($Release) {
|
||||
$ldflags = "-s -w" # Strip debug info for smaller binaries
|
||||
}
|
||||
|
||||
# Build analyze
|
||||
Write-Host " Building analyze.exe..." -ForegroundColor Gray
|
||||
if ($Release) {
|
||||
& go build -ldflags "$ldflags" -o "$binDir\analyze.exe" "./cmd/analyze/"
|
||||
}
|
||||
else {
|
||||
& go build -o "$binDir\analyze.exe" "./cmd/analyze/"
|
||||
}
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host " Failed to build analyze.exe" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
$analyzeSize = (Get-Item "$binDir\analyze.exe").Length / 1MB
|
||||
Write-Host " Built: analyze.exe ($([math]::Round($analyzeSize, 2)) MB)" -ForegroundColor Green
|
||||
|
||||
# Build status
|
||||
Write-Host " Building status.exe..." -ForegroundColor Gray
|
||||
if ($Release) {
|
||||
& go build -ldflags "$ldflags" -o "$binDir\status.exe" "./cmd/status/"
|
||||
}
|
||||
else {
|
||||
& go build -o "$binDir\status.exe" "./cmd/status/"
|
||||
}
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host " Failed to build status.exe" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
$statusSize = (Get-Item "$binDir\status.exe").Length / 1MB
|
||||
Write-Host " Built: status.exe ($([math]::Round($statusSize, 2)) MB)" -ForegroundColor Green
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " Build complete!" -ForegroundColor Green
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
141
windows/scripts/test.ps1
Normal file
141
windows/scripts/test.ps1
Normal file
@@ -0,0 +1,141 @@
|
||||
# Mole Windows - Test Runner Script
|
||||
# Runs all tests (Pester for PowerShell, go test for Go)
|
||||
|
||||
#Requires -Version 5.1
|
||||
param(
|
||||
[switch]$Verbose,
|
||||
[switch]$NoPester,
|
||||
[switch]$NoGo,
|
||||
[switch]$Coverage
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$script:ExitCode = 0
|
||||
|
||||
# Get script directory
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$windowsDir = Split-Path -Parent $scriptDir
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " Mole Windows - Test Suite" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# ============================================================================
|
||||
# Pester Tests
|
||||
# ============================================================================
|
||||
|
||||
if (-not $NoPester) {
|
||||
Write-Host "[Pester] Running PowerShell tests..." -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
|
||||
# Check if Pester is installed
|
||||
$pesterModule = Get-Module -ListAvailable -Name Pester | Where-Object { $_.Version -ge "5.0.0" }
|
||||
|
||||
if (-not $pesterModule) {
|
||||
Write-Host " Installing Pester 5.x..." -ForegroundColor Gray
|
||||
Install-Module -Name Pester -MinimumVersion 5.0.0 -Force -SkipPublisherCheck -Scope CurrentUser
|
||||
}
|
||||
|
||||
Import-Module Pester -MinimumVersion 5.0.0
|
||||
|
||||
$testsDir = Join-Path $windowsDir "tests"
|
||||
|
||||
$config = New-PesterConfiguration
|
||||
$config.Run.Path = $testsDir
|
||||
$config.Run.Exit = $false
|
||||
$config.Output.Verbosity = if ($Verbose) { "Detailed" } else { "Normal" }
|
||||
|
||||
if ($Coverage) {
|
||||
$config.CodeCoverage.Enabled = $true
|
||||
$config.CodeCoverage.Path = @(
|
||||
(Join-Path $windowsDir "lib\core\*.ps1"),
|
||||
(Join-Path $windowsDir "lib\clean\*.ps1"),
|
||||
(Join-Path $windowsDir "bin\*.ps1")
|
||||
)
|
||||
$config.CodeCoverage.OutputPath = Join-Path $windowsDir "coverage-pester.xml"
|
||||
}
|
||||
|
||||
try {
|
||||
$result = Invoke-Pester -Configuration $config
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "[Pester] Results:" -ForegroundColor Yellow
|
||||
Write-Host " Passed: $($result.PassedCount)" -ForegroundColor Green
|
||||
Write-Host " Failed: $($result.FailedCount)" -ForegroundColor $(if ($result.FailedCount -gt 0) { "Red" } else { "Green" })
|
||||
Write-Host " Skipped: $($result.SkippedCount)" -ForegroundColor Gray
|
||||
|
||||
if ($result.FailedCount -gt 0) {
|
||||
$script:ExitCode = 1
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Host " Error running Pester tests: $_" -ForegroundColor Red
|
||||
$script:ExitCode = 1
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Go Tests
|
||||
# ============================================================================
|
||||
|
||||
if (-not $NoGo) {
|
||||
Write-Host "[Go] Running Go tests..." -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
|
||||
# Check if Go is installed
|
||||
$goVersion = & go version 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host " Go is not installed, skipping Go tests" -ForegroundColor Gray
|
||||
}
|
||||
else {
|
||||
Write-Host " $goVersion" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
|
||||
Push-Location $windowsDir
|
||||
try {
|
||||
$goArgs = @("test")
|
||||
if ($Verbose) {
|
||||
$goArgs += "-v"
|
||||
}
|
||||
if ($Coverage) {
|
||||
$goArgs += "-coverprofile=coverage-go.out"
|
||||
}
|
||||
$goArgs += "./..."
|
||||
|
||||
& go @goArgs
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
$script:ExitCode = 1
|
||||
}
|
||||
else {
|
||||
Write-Host ""
|
||||
Write-Host "[Go] All tests passed" -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Summary
|
||||
# ============================================================================
|
||||
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
if ($script:ExitCode -eq 0) {
|
||||
Write-Host " All tests passed!" -ForegroundColor Green
|
||||
}
|
||||
else {
|
||||
Write-Host " Some tests failed!" -ForegroundColor Red
|
||||
}
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
exit $script:ExitCode
|
||||
199
windows/tests/Clean.Tests.ps1
Normal file
199
windows/tests/Clean.Tests.ps1
Normal file
@@ -0,0 +1,199 @@
|
||||
# Mole Windows - Cleanup Module Tests
|
||||
# Pester tests for lib/clean functionality
|
||||
|
||||
BeforeAll {
|
||||
# Get the windows directory path (tests are in windows/tests/)
|
||||
$script:WindowsDir = Split-Path -Parent $PSScriptRoot
|
||||
$script:LibDir = Join-Path $script:WindowsDir "lib"
|
||||
|
||||
# Import core modules first
|
||||
. "$script:LibDir\core\base.ps1"
|
||||
. "$script:LibDir\core\log.ps1"
|
||||
. "$script:LibDir\core\ui.ps1"
|
||||
. "$script:LibDir\core\file_ops.ps1"
|
||||
|
||||
# Import cleanup modules
|
||||
. "$script:LibDir\clean\user.ps1"
|
||||
. "$script:LibDir\clean\caches.ps1"
|
||||
. "$script:LibDir\clean\dev.ps1"
|
||||
. "$script:LibDir\clean\apps.ps1"
|
||||
. "$script:LibDir\clean\system.ps1"
|
||||
|
||||
# Enable dry-run mode for all tests
|
||||
$env:MOLE_DRY_RUN = "1"
|
||||
Set-DryRunMode -Enabled $true
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
$env:MOLE_DRY_RUN = $null
|
||||
Set-DryRunMode -Enabled $false
|
||||
}
|
||||
|
||||
Describe "User Cleanup Module" {
|
||||
Context "Clear-UserTempFiles" {
|
||||
It "Should have Clear-UserTempFiles function" {
|
||||
Get-Command Clear-UserTempFiles -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It "Should run without error in dry-run mode" {
|
||||
{ Clear-UserTempFiles } | Should -Not -Throw
|
||||
}
|
||||
}
|
||||
|
||||
Context "Clear-OldDownloads" {
|
||||
It "Should have Clear-OldDownloads function" {
|
||||
Get-Command Clear-OldDownloads -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context "Clear-RecycleBin" {
|
||||
It "Should have Clear-RecycleBin function" {
|
||||
Get-Command Clear-RecycleBin -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context "Invoke-UserCleanup" {
|
||||
It "Should have main user cleanup function" {
|
||||
Get-Command Invoke-UserCleanup -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Cache Cleanup Module" {
|
||||
Context "Browser Cache Functions" {
|
||||
It "Should have Clear-BrowserCaches function" {
|
||||
Get-Command Clear-BrowserCaches -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It "Should run browser cache cleanup without error" {
|
||||
{ Clear-BrowserCaches } | Should -Not -Throw
|
||||
}
|
||||
}
|
||||
|
||||
Context "Application Cache Functions" {
|
||||
It "Should have Clear-AppCaches function" {
|
||||
Get-Command Clear-AppCaches -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context "Windows Update Cache" {
|
||||
It "Should have Clear-WindowsUpdateCache function" {
|
||||
Get-Command Clear-WindowsUpdateCache -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context "Invoke-CacheCleanup" {
|
||||
It "Should have main cache cleanup function" {
|
||||
Get-Command Invoke-CacheCleanup -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Developer Tools Cleanup Module" {
|
||||
Context "Node.js Cleanup" {
|
||||
It "Should have npm cache cleanup function" {
|
||||
Get-Command Clear-NpmCache -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context "Python Cleanup" {
|
||||
It "Should have Python cache cleanup function" {
|
||||
Get-Command Clear-PythonCaches -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context "Go Cleanup" {
|
||||
It "Should have Go cache cleanup function" {
|
||||
Get-Command Clear-GoCaches -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context "Rust Cleanup" {
|
||||
It "Should have Rust cache cleanup function" {
|
||||
Get-Command Clear-RustCaches -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context "Docker Cleanup" {
|
||||
It "Should have Docker cache cleanup function" {
|
||||
Get-Command Clear-DockerCaches -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context "Invoke-DevToolsCleanup" {
|
||||
It "Should have main dev tools cleanup function" {
|
||||
Get-Command Invoke-DevToolsCleanup -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It "Should run without error in dry-run mode" {
|
||||
{ Invoke-DevToolsCleanup } | Should -Not -Throw
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Apps Cleanup Module" {
|
||||
Context "Orphan Detection" {
|
||||
It "Should have Find-OrphanedAppData function" {
|
||||
Get-Command Find-OrphanedAppData -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It "Should have Clear-OrphanedAppData function" {
|
||||
Get-Command Clear-OrphanedAppData -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context "Specific App Cleanup" {
|
||||
It "Should have Clear-OfficeCache function" {
|
||||
Get-Command Clear-OfficeCache -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It "Should have Clear-AdobeData function" {
|
||||
Get-Command Clear-AdobeData -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context "Invoke-AppCleanup" {
|
||||
It "Should have main app cleanup function" {
|
||||
Get-Command Invoke-AppCleanup -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Describe "System Cleanup Module" {
|
||||
Context "System Temp" {
|
||||
It "Should have Clear-SystemTempFiles function" {
|
||||
Get-Command Clear-SystemTempFiles -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context "Windows Logs" {
|
||||
It "Should have Clear-WindowsLogs function" {
|
||||
Get-Command Clear-WindowsLogs -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context "Windows Update Cleanup" {
|
||||
It "Should have Clear-WindowsUpdateFiles function" {
|
||||
Get-Command Clear-WindowsUpdateFiles -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context "Memory Dumps" {
|
||||
It "Should have Clear-MemoryDumps function" {
|
||||
Get-Command Clear-MemoryDumps -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context "Admin Requirements" {
|
||||
It "Should check for admin when needed" {
|
||||
# System cleanup should handle non-admin gracefully
|
||||
{ Clear-SystemTempFiles } | Should -Not -Throw
|
||||
}
|
||||
}
|
||||
|
||||
Context "Invoke-SystemCleanup" {
|
||||
It "Should have main system cleanup function" {
|
||||
Get-Command Invoke-SystemCleanup -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
}
|
||||
140
windows/tests/Commands.Tests.ps1
Normal file
140
windows/tests/Commands.Tests.ps1
Normal file
@@ -0,0 +1,140 @@
|
||||
# Mole Windows - Command Tests
|
||||
# Pester tests for bin/ command scripts
|
||||
|
||||
BeforeAll {
|
||||
# Get the windows directory path (tests are in windows/tests/)
|
||||
$script:WindowsDir = Split-Path -Parent $PSScriptRoot
|
||||
$script:BinDir = Join-Path $script:WindowsDir "bin"
|
||||
}
|
||||
|
||||
Describe "Clean Command" {
|
||||
Context "Help Display" {
|
||||
It "Should show help without error" {
|
||||
$result = & powershell -ExecutionPolicy Bypass -File "$script:BinDir\clean.ps1" -ShowHelp 2>&1
|
||||
$result | Should -Not -BeNullOrEmpty
|
||||
$LASTEXITCODE | Should -Be 0
|
||||
}
|
||||
|
||||
It "Should mention dry-run in help" {
|
||||
$result = & powershell -ExecutionPolicy Bypass -File "$script:BinDir\clean.ps1" -ShowHelp 2>&1
|
||||
$result -join "`n" | Should -Match "DryRun"
|
||||
}
|
||||
}
|
||||
|
||||
Context "Dry Run Mode" {
|
||||
It "Should support -DryRun parameter" {
|
||||
# Just verify it starts without immediate error
|
||||
$job = Start-Job -ScriptBlock {
|
||||
param($binDir)
|
||||
& powershell -ExecutionPolicy Bypass -File "$binDir\clean.ps1" -DryRun 2>&1
|
||||
} -ArgumentList $script:BinDir
|
||||
|
||||
Start-Sleep -Seconds 3
|
||||
Stop-Job $job -ErrorAction SilentlyContinue
|
||||
Remove-Job $job -Force -ErrorAction SilentlyContinue
|
||||
|
||||
# If we got here without exception, test passes
|
||||
$true | Should -Be $true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Uninstall Command" {
|
||||
Context "Help Display" {
|
||||
It "Should show help without error" {
|
||||
$result = & powershell -ExecutionPolicy Bypass -File "$script:BinDir\uninstall.ps1" -ShowHelp 2>&1
|
||||
$result | Should -Not -BeNullOrEmpty
|
||||
$LASTEXITCODE | Should -Be 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Optimize Command" {
|
||||
Context "Help Display" {
|
||||
It "Should show help without error" {
|
||||
$result = & powershell -ExecutionPolicy Bypass -File "$script:BinDir\optimize.ps1" -ShowHelp 2>&1
|
||||
$result | Should -Not -BeNullOrEmpty
|
||||
$LASTEXITCODE | Should -Be 0
|
||||
}
|
||||
|
||||
It "Should mention optimization options in help" {
|
||||
$result = & powershell -ExecutionPolicy Bypass -File "$script:BinDir\optimize.ps1" -ShowHelp 2>&1
|
||||
$result -join "`n" | Should -Match "DryRun|Disk|DNS"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Purge Command" {
|
||||
Context "Help Display" {
|
||||
It "Should show help without error" {
|
||||
$result = & powershell -ExecutionPolicy Bypass -File "$script:BinDir\purge.ps1" -ShowHelp 2>&1
|
||||
$result | Should -Not -BeNullOrEmpty
|
||||
$LASTEXITCODE | Should -Be 0
|
||||
}
|
||||
|
||||
It "Should list artifact types in help" {
|
||||
$result = & powershell -ExecutionPolicy Bypass -File "$script:BinDir\purge.ps1" -ShowHelp 2>&1
|
||||
$result -join "`n" | Should -Match "node_modules|vendor|venv"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Analyze Command" {
|
||||
Context "Help Display" {
|
||||
It "Should show help without error" {
|
||||
$result = & powershell -ExecutionPolicy Bypass -File "$script:BinDir\analyze.ps1" -ShowHelp 2>&1
|
||||
$result | Should -Not -BeNullOrEmpty
|
||||
$LASTEXITCODE | Should -Be 0
|
||||
}
|
||||
|
||||
It "Should mention keybindings in help" {
|
||||
$result = & powershell -ExecutionPolicy Bypass -File "$script:BinDir\analyze.ps1" -ShowHelp 2>&1
|
||||
$result -join "`n" | Should -Match "Navigate|Enter|Quit"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Status Command" {
|
||||
Context "Help Display" {
|
||||
It "Should show help without error" {
|
||||
$result = & powershell -ExecutionPolicy Bypass -File "$script:BinDir\status.ps1" -ShowHelp 2>&1
|
||||
$result | Should -Not -BeNullOrEmpty
|
||||
$LASTEXITCODE | Should -Be 0
|
||||
}
|
||||
|
||||
It "Should mention system metrics in help" {
|
||||
$result = & powershell -ExecutionPolicy Bypass -File "$script:BinDir\status.ps1" -ShowHelp 2>&1
|
||||
$result -join "`n" | Should -Match "CPU|Memory|Disk|health"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Main Entry Point" {
|
||||
Context "mole.ps1" {
|
||||
BeforeAll {
|
||||
$script:MolePath = Join-Path $script:WindowsDir "mole.ps1"
|
||||
}
|
||||
|
||||
It "Should show help without error" {
|
||||
$result = & powershell -ExecutionPolicy Bypass -File $script:MolePath -ShowHelp 2>&1
|
||||
$result | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It "Should show version without error" {
|
||||
$result = & powershell -ExecutionPolicy Bypass -File $script:MolePath -Version 2>&1
|
||||
$result | Should -Not -BeNullOrEmpty
|
||||
$result -join "`n" | Should -Match "Mole|v\d+\.\d+"
|
||||
}
|
||||
|
||||
It "Should list available commands in help" {
|
||||
$result = & powershell -ExecutionPolicy Bypass -File $script:MolePath -ShowHelp 2>&1
|
||||
$helpText = $result -join "`n"
|
||||
$helpText | Should -Match "clean"
|
||||
$helpText | Should -Match "uninstall"
|
||||
$helpText | Should -Match "optimize"
|
||||
$helpText | Should -Match "purge"
|
||||
$helpText | Should -Match "analyze"
|
||||
$helpText | Should -Match "status"
|
||||
}
|
||||
}
|
||||
}
|
||||
242
windows/tests/Core.Tests.ps1
Normal file
242
windows/tests/Core.Tests.ps1
Normal file
@@ -0,0 +1,242 @@
|
||||
# Mole Windows - Core Module Tests
|
||||
# Pester tests for lib/core functionality
|
||||
|
||||
BeforeAll {
|
||||
# Get the windows directory path (tests are in windows/tests/)
|
||||
$script:WindowsDir = Split-Path -Parent $PSScriptRoot
|
||||
$script:LibDir = Join-Path $script:WindowsDir "lib"
|
||||
|
||||
# Import core modules
|
||||
. "$script:LibDir\core\base.ps1"
|
||||
. "$script:LibDir\core\log.ps1"
|
||||
. "$script:LibDir\core\ui.ps1"
|
||||
. "$script:LibDir\core\file_ops.ps1"
|
||||
}
|
||||
|
||||
Describe "Base Module" {
|
||||
Context "Color Definitions" {
|
||||
It "Should define color codes" {
|
||||
$script:Colors | Should -Not -BeNullOrEmpty
|
||||
$script:Colors.Cyan | Should -Not -BeNullOrEmpty
|
||||
$script:Colors.Green | Should -Not -BeNullOrEmpty
|
||||
$script:Colors.Red | Should -Not -BeNullOrEmpty
|
||||
$script:Colors.NC | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It "Should define icon set" {
|
||||
$script:Icons | Should -Not -BeNullOrEmpty
|
||||
$script:Icons.Success | Should -Not -BeNullOrEmpty
|
||||
$script:Icons.Error | Should -Not -BeNullOrEmpty
|
||||
$script:Icons.Warning | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context "Test-IsAdmin" {
|
||||
It "Should return a boolean" {
|
||||
$result = Test-IsAdmin
|
||||
$result | Should -BeOfType [bool]
|
||||
}
|
||||
}
|
||||
|
||||
Context "Get-WindowsVersion" {
|
||||
It "Should return version info" {
|
||||
$result = Get-WindowsVersion
|
||||
$result | Should -Not -BeNullOrEmpty
|
||||
$result.Name | Should -Not -BeNullOrEmpty
|
||||
$result.Version | Should -Not -BeNullOrEmpty
|
||||
$result.Build | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Context "Get-FreeSpace" {
|
||||
It "Should return free space string" {
|
||||
$result = Get-FreeSpace
|
||||
$result | Should -Not -BeNullOrEmpty
|
||||
# Format is like "100.00GB" or "50.5MB" (no space between number and unit)
|
||||
$result | Should -Match "\d+(\.\d+)?(B|KB|MB|GB|TB)"
|
||||
}
|
||||
|
||||
It "Should accept drive parameter" {
|
||||
$result = Get-FreeSpace -Drive "C:"
|
||||
$result | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Describe "File Operations Module" {
|
||||
BeforeAll {
|
||||
# Create temp test directory
|
||||
$script:TestDir = Join-Path $env:TEMP "mole_test_$(Get-Random)"
|
||||
New-Item -ItemType Directory -Path $script:TestDir -Force | Out-Null
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
# Cleanup test directory
|
||||
if (Test-Path $script:TestDir) {
|
||||
Remove-Item -Path $script:TestDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
Context "Format-ByteSize" {
|
||||
It "Should format bytes correctly" {
|
||||
# Actual format: no space, uses N0/N1/N2 formatting
|
||||
Format-ByteSize -Bytes 0 | Should -Be "0B"
|
||||
Format-ByteSize -Bytes 1024 | Should -Be "1KB"
|
||||
Format-ByteSize -Bytes 1048576 | Should -Be "1.0MB"
|
||||
Format-ByteSize -Bytes 1073741824 | Should -Be "1.00GB"
|
||||
}
|
||||
|
||||
It "Should handle large numbers" {
|
||||
Format-ByteSize -Bytes 1099511627776 | Should -Be "1.00TB"
|
||||
}
|
||||
}
|
||||
|
||||
Context "Get-PathSize" {
|
||||
BeforeEach {
|
||||
# Create test file
|
||||
$testFile = Join-Path $script:TestDir "testfile.txt"
|
||||
"Hello World" | Set-Content -Path $testFile
|
||||
}
|
||||
|
||||
It "Should return size for file" {
|
||||
$testFile = Join-Path $script:TestDir "testfile.txt"
|
||||
$result = Get-PathSize -Path $testFile
|
||||
$result | Should -BeGreaterThan 0
|
||||
}
|
||||
|
||||
It "Should return size for directory" {
|
||||
$result = Get-PathSize -Path $script:TestDir
|
||||
$result | Should -BeGreaterThan 0
|
||||
}
|
||||
|
||||
It "Should return 0 for non-existent path" {
|
||||
$result = Get-PathSize -Path "C:\NonExistent\Path\12345"
|
||||
$result | Should -Be 0
|
||||
}
|
||||
}
|
||||
|
||||
Context "Test-ProtectedPath" {
|
||||
It "Should protect Windows directory" {
|
||||
Test-ProtectedPath -Path "C:\Windows" | Should -Be $true
|
||||
Test-ProtectedPath -Path "C:\Windows\System32" | Should -Be $true
|
||||
}
|
||||
|
||||
It "Should protect Windows Defender paths" {
|
||||
Test-ProtectedPath -Path "C:\Program Files\Windows Defender" | Should -Be $true
|
||||
Test-ProtectedPath -Path "C:\ProgramData\Microsoft\Windows Defender" | Should -Be $true
|
||||
}
|
||||
|
||||
It "Should not protect temp directories" {
|
||||
Test-ProtectedPath -Path $env:TEMP | Should -Be $false
|
||||
}
|
||||
}
|
||||
|
||||
Context "Test-SafePath" {
|
||||
It "Should return false for protected paths" {
|
||||
Test-SafePath -Path "C:\Windows" | Should -Be $false
|
||||
Test-SafePath -Path "C:\Windows\System32" | Should -Be $false
|
||||
}
|
||||
|
||||
It "Should return true for safe paths" {
|
||||
Test-SafePath -Path $env:TEMP | Should -Be $true
|
||||
}
|
||||
|
||||
It "Should return false for empty paths" {
|
||||
# Test-SafePath has mandatory path parameter, so empty/null throws
|
||||
# But internally it should handle empty strings gracefully
|
||||
{ Test-SafePath -Path "" } | Should -Throw
|
||||
}
|
||||
}
|
||||
|
||||
Context "Remove-SafeItem" {
|
||||
BeforeEach {
|
||||
$script:TestFile = Join-Path $script:TestDir "safe_remove_test.txt"
|
||||
"Test content" | Set-Content -Path $script:TestFile
|
||||
}
|
||||
|
||||
It "Should remove file successfully" {
|
||||
$result = Remove-SafeItem -Path $script:TestFile
|
||||
$result | Should -Be $true
|
||||
Test-Path $script:TestFile | Should -Be $false
|
||||
}
|
||||
|
||||
It "Should respect DryRun mode" {
|
||||
$env:MOLE_DRY_RUN = "1"
|
||||
try {
|
||||
# Reset the module's DryRun state
|
||||
Set-DryRunMode -Enabled $true
|
||||
$result = Remove-SafeItem -Path $script:TestFile
|
||||
$result | Should -Be $true
|
||||
Test-Path $script:TestFile | Should -Be $true # File should still exist
|
||||
}
|
||||
finally {
|
||||
$env:MOLE_DRY_RUN = $null
|
||||
Set-DryRunMode -Enabled $false
|
||||
}
|
||||
}
|
||||
|
||||
It "Should not remove protected paths" {
|
||||
$result = Remove-SafeItem -Path "C:\Windows\System32"
|
||||
$result | Should -Be $false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Logging Module" {
|
||||
Context "Write-Log Functions" {
|
||||
It "Should have Write-Info function" {
|
||||
{ Write-Info "Test message" } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It "Should have Write-Success function" {
|
||||
{ Write-Success "Test message" } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It "Should have Write-Warning function" {
|
||||
# Note: The actual function is Write-Warning (conflicts with built-in)
|
||||
{ Write-Warning "Test message" } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It "Should have Write-Error function" {
|
||||
# Note: The actual function is Write-Error (conflicts with built-in)
|
||||
{ Write-Error "Test message" } | Should -Not -Throw
|
||||
}
|
||||
}
|
||||
|
||||
Context "Section Functions" {
|
||||
It "Should start and stop sections without error" {
|
||||
{ Start-Section -Title "Test Section" } | Should -Not -Throw
|
||||
{ Stop-Section } | Should -Not -Throw
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Describe "UI Module" {
|
||||
Context "Show-Banner" {
|
||||
It "Should display banner without error" {
|
||||
{ Show-Banner } | Should -Not -Throw
|
||||
}
|
||||
}
|
||||
|
||||
Context "Show-Header" {
|
||||
It "Should display header without error" {
|
||||
{ Show-Header -Title "Test Header" } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It "Should accept subtitle parameter" {
|
||||
{ Show-Header -Title "Test" -Subtitle "Subtitle" } | Should -Not -Throw
|
||||
}
|
||||
}
|
||||
|
||||
Context "Show-Summary" {
|
||||
It "Should display summary without error" {
|
||||
{ Show-Summary -SizeBytes 1024 -ItemCount 5 } | Should -Not -Throw
|
||||
}
|
||||
}
|
||||
|
||||
Context "Read-Confirmation" {
|
||||
It "Should have Read-Confirmation function" {
|
||||
Get-Command Read-Confirmation -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user