# AetherForge - full test suite runner param( [switch]$SkipE2E, [switch]$SkipBuild, [switch]$Verbose, [switch]$ReconOnly, [switch]$P2, [switch]$S3Swarm, [switch]$CloudMap, [switch]$Fargate, [switch]$PolicyFanout, [switch]$SSM, [switch]$Seer, [switch]$Oath, [switch]$Contingency, [switch]$CloudVenue ) $ErrorActionPreference = "Stop" $Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path) if (-not (Test-Path (Join-Path $Root "server\go.mod"))) { $Root = Split-Path -Parent $MyInvocation.MyCommand.Path if (-not (Test-Path (Join-Path $Root "server\go.mod"))) { $Root = (Get-Location).Path } } $Results = @() $Failed = 0 function Write-Phase([string]$Name) { Write-Host "" Write-Host "==============================================================" -ForegroundColor Cyan Write-Host " $Name" -ForegroundColor Cyan Write-Host "==============================================================" -ForegroundColor Cyan } function Invoke-Npm([string[]]$NpmArgs) { $prevEAP = $ErrorActionPreference $ErrorActionPreference = 'Continue' try { $out = & npm @NpmArgs 2>&1 | ForEach-Object { "$_" } $text = ($out -join "`n").Trim() if ($text) { Write-Host $text } if (-not $LASTEXITCODE -or $LASTEXITCODE -eq 0) { $global:LASTEXITCODE = 0; return } throw "npm $($NpmArgs -join ' ') failed (exit $LASTEXITCODE)" } finally { $ErrorActionPreference = $prevEAP } } function Invoke-GoTest([string]$Package) { $prevEAP = $ErrorActionPreference $ErrorActionPreference = 'Continue' try { $out = go test $Package -count=1 2>&1 | ForEach-Object { "$_" } $text = ($out -join "`n").Trim() if ($text) { Write-Host $text } if (-not $LASTEXITCODE -or $LASTEXITCODE -eq 0) { $global:LASTEXITCODE = 0; return } # Windows AV occasionally locks *.test.exe after an otherwise passing run. if ($text -match 'unlinkat.*\.test\.exe') { Write-Host " >> (ignored test binary cleanup flake on Windows for $Package)" -ForegroundColor Yellow return } throw "go test $Package failed (exit $LASTEXITCODE)" } finally { $ErrorActionPreference = $prevEAP } } function Invoke-Phase([string]$Name, [scriptblock]$Block) { Write-Phase $Name try { & $Block if ($LASTEXITCODE -and $LASTEXITCODE -ne 0) { throw "exit code $LASTEXITCODE" } $script:Results += [pscustomobject]@{ Phase = $Name; Status = "PASS" } Write-Host " >> PASS" -ForegroundColor Green } catch { $script:Failed++ $msg = $_.Exception.Message $script:Results += [pscustomobject]@{ Phase = $Name; Status = "FAIL"; Detail = $msg } Write-Host " >> FAIL: $msg" -ForegroundColor Red if ($Verbose) { Write-Host $_ } } } Write-Host "" Write-Host " AetherForge Full Test Suite" -ForegroundColor Yellow Write-Host " Root: $Root" function Exit-FeatureRun([string]$Label) { Write-Host "" if ($Failed -gt 0) { Write-Host " $Label run FAILED ($Failed phase(s))" -ForegroundColor Red exit 1 } Write-Host " $Label run complete" -ForegroundColor Green exit 0 } if ($S3Swarm) { Invoke-Phase "S3Swarm (RS erasure + CloudFront magnets)" { Push-Location (Join-Path $Root "server") go test ./internal/erasure/... -run S3Swarm -count=1 go test ./internal/api/... -run "S3Swarm|ErasurePlan|SwarmMagnet|spread_s3" -count=1 Pop-Location Push-Location (Join-Path $Root "agent") go test ./deploy/... -run Erasure -count=1 Pop-Location Push-Location (Join-Path $Root "server\web") if (-not (Test-Path "node_modules")) { npm install --silent } npm run test -- --run src/components/Forge/AwsErasureSwarmPanel.test.tsx Pop-Location } Exit-FeatureRun "S3Swarm" } if ($CloudMap) { Invoke-Phase "CloudMap (service registry + seeder discovery)" { Push-Location (Join-Path $Root "agent") go test ./deploy/... -run CloudMap -count=1 go test ./deploy/... -run "CloudMap|EC2Instance" -count=1 Pop-Location Push-Location (Join-Path $Root "server") go test ./internal/api/... -run CloudMap -count=1 Pop-Location Push-Location (Join-Path $Root "server\web") if (-not (Test-Path "node_modules")) { npm install --silent } npm run test -- --run src/help/cloudSpreadMethods.test.ts Pop-Location } Exit-FeatureRun "CloudMap" } if ($Fargate) { Invoke-Phase "Fargate (burst seeder export + BGP hints)" { Push-Location (Join-Path $Root "server") go test ./internal/fargate/... ./internal/api/... -run Fargate -count=1 go test ./internal/spreadrouter/... -run Fargate -count=1 Pop-Location Push-Location (Join-Path $Root "server\web") if (-not (Test-Path "node_modules")) { npm install --silent } npm run test -- --run src/help/cloudSpreadMethods.test.ts Pop-Location } Exit-FeatureRun "Fargate" } if ($PolicyFanout) { Invoke-Phase "PolicyFanout (policy snapshot + EventBridge ZIP)" { Push-Location (Join-Path $Root "server") go test ./internal/api/... -run PolicyFanout -count=1 go test ./internal/api/... -run PolicySnapshot -count=1 Pop-Location Push-Location (Join-Path $Root "server\web") if (-not (Test-Path "node_modules")) { npm install --silent } npm run test -- --run src/help/cloudSpreadMethods.test.ts Pop-Location } Exit-FeatureRun "PolicyFanout" } if ($SSM) { Invoke-Phase "SSM (Run Command document export + cloud kits)" { Push-Location (Join-Path $Root "server") go test ./internal/api/... -run "SSMSpread|CloudTemplate|CloudConnection" -count=1 Pop-Location Push-Location (Join-Path $Root "agent") go test ./deploy/... -run SSMDocument -count=1 Pop-Location Push-Location (Join-Path $Root "server\web") if (-not (Test-Path "node_modules")) { npm install --silent } npm run test -- --run src/help/cloudSpreadMethods.test.ts src/components/Spread/CloudSpreadPanel.test.tsx Pop-Location } Exit-FeatureRun "SSM" } if ($Seer) { Invoke-Phase "Seer (reasoning stream + LLM notes)" { Push-Location (Join-Path $Root "server") go test ./internal/api/... ./internal/ai/... -run Seer -count=1 go test ./internal/epidemiology/... -run Seer -count=1 Pop-Location Push-Location (Join-Path $Root "server\web") if (-not (Test-Path "node_modules")) { npm install --silent } npm run test -- --run src/pages/SeerPage.test.tsx src/help/seerEvents.test.ts src/help/subnetAutopsy.test.ts Pop-Location } Exit-FeatureRun "Seer" } if ($Oath) { Invoke-Phase "Oath (operator accountability ledger)" { Push-Location (Join-Path $Root "server") go test ./internal/db/... -run OathLedger -count=1 go test ./internal/api/... -run OathLedger -count=1 Pop-Location Push-Location (Join-Path $Root "server\web") if (-not (Test-Path "node_modules")) { npm install --silent } npm run test -- --run src/pages/OathLedgerPage.test.tsx Pop-Location } Exit-FeatureRun "Oath" } if ($Contingency) { Invoke-Phase "Contingency (onion miner tree + persona ghosts)" { Push-Location (Join-Path $Root "agent") go test ./miner/... ./client/... -run Contingency -count=1 Pop-Location Push-Location (Join-Path $Root "server") go test ./internal/api/... ./internal/ai/... ./internal/mining/... -run Contingency -count=1 Pop-Location Push-Location (Join-Path $Root "server\web") if (-not (Test-Path "node_modules")) { npm install --silent } npm run test -- --run src/help/miningSelfSurgery.test.ts Pop-Location } Exit-FeatureRun "Contingency" } if ($CloudVenue) { Invoke-Phase "CloudVenue (EC2 IMDS biomes + persona packs)" { Push-Location (Join-Path $Root "server") go test ./internal/api/... ./internal/ai/... -run CloudVenue -count=1 Pop-Location Push-Location (Join-Path $Root "agent") go test ./deploy/... -run "CloudMap|EC2Instance" -count=1 Pop-Location Push-Location (Join-Path $Root "server\web") if (-not (Test-Path "node_modules")) { npm install --silent } npm run test -- --run src/help/cloudVenueBiomeWeather.test.ts src/help/scoutBiomeWeather.test.ts Pop-Location } Exit-FeatureRun "CloudVenue" } if ($P2) { Invoke-Phase "P2 focused (mining, spread, path forge, WS/beacon)" { Push-Location (Join-Path $Root "server") go test ./internal/api/... ./internal/builder/... -run "SpreadLane|WSBeacon|PathForge|Download|SpreadCred|PostDeploy" -count=1 Pop-Location Push-Location (Join-Path $Root "agent") go test ./client/... -run "MiningChain|WSBeacon|PathTracer" -count=1 go test ./deploy/... -run "WinRM|Spread|Staging|LinuxLOTL|BITS|HiddenRun|DeployPlan" -count=1 go test ./miner/... -run "Fallback|TripleOnion|Container|Runtime" -count=1 Pop-Location Push-Location (Join-Path $Root "server\web") if (-not (Test-Path "node_modules")) { npm install --silent } npm run test -- --run src/pages/BuilderPage.test.tsx src/help/spreadProfiles.test.ts src/help/lotlOnionTiers.test.ts Pop-Location } Write-Host "" Write-Host " P2-only run complete (run full suite phase 8 for Playwright E2E)" -ForegroundColor Green exit 0 } if ($ReconOnly) { Invoke-Phase "Fleet recon (focused)" { Push-Location (Join-Path $Root "server") go test ./internal/api/... -run "PathTracer|SpreadCred|MergeService|DeployPlan" -count=1 go test ./internal/db/... -run CredEdge -count=1 go test . -run "OrderDeploymentCred|LoadDeploymentCred" -count=1 Pop-Location Push-Location (Join-Path $Root "agent") go test ./vulnprobe/... ./miner/... -run "TripleOnion|Correlate|VulnProbe|Risk" -count=1 go test ./deploy/... -run "NetworkHints|ServiceDiscovery|CredSpread|Discover" -count=1 go test ./client/... -run "Vuln|SpreadCred|ChainOrder" -count=1 Pop-Location Push-Location (Join-Path $Root "server\web") if (-not (Test-Path "node_modules")) { npm install --silent } npm run test -- --run src/help/reconRisk.test.ts src/components/Fleet/ReconBadges.test.tsx src/components/Fleet/CrucibleExpandedOps.test.tsx Pop-Location } Write-Host "" Write-Host " Recon-only run complete" -ForegroundColor Green exit 0 } Invoke-Phase "1/8 Go server tests" { Push-Location (Join-Path $Root "server") go test ./... -count=1 Pop-Location } Invoke-Phase "2/8 Go agent tests" { Push-Location (Join-Path $Root "agent") # Package-by-package avoids Windows unlinkat flakes on deploy.test.exe after `go test ./...`. $pkgs = @(go list ./... 2>$null) foreach ($pkg in $pkgs) { Invoke-GoTest $pkg } Pop-Location } # LOTL/tiered mining quick-run (subset of phase 2): # cd agent && go test ./miner/... ./client/... ./deploy/... -run "Lotl|LOTL|Tier|Staging|Fallback|Mining" -count=1 # Server LOTL relay (subset of phase 1): # cd server && go test ./internal/api/... ./internal/builder/... ./internal/models/... -run "Lotl|LOTL|Tier|StatsBatch|Mining" -count=1 # Frontend LOTL (subset of phase 4): # cd server/web && npm test -- --run src/help/lotlOnionTiers.test.ts src/components/Fleet/LotlTierBadge.test.tsx src/help/applyStatsUpdate.test.ts src/context/WebSocketProvider.test.tsx Invoke-Phase "3/8 Fusion module tests" { Push-Location (Join-Path $Root "fusion") go test . -count=1 go build . Pop-Location } Invoke-Phase "4/8 Frontend unit tests (Vitest)" { Push-Location (Join-Path $Root "server\web") if (-not (Test-Path "node_modules")) { Invoke-Npm @("install", "--silent") } Invoke-Npm @("test") Pop-Location } if (-not $SkipBuild) { Invoke-Phase "5/8 Frontend production build" { Push-Location (Join-Path $Root "server\web") Invoke-Npm @("run", "build") Pop-Location } Invoke-Phase "6/8 Server binary compile" { Push-Location (Join-Path $Root "server") go build -o (Join-Path $Root "bin\miner-server.exe") . Pop-Location } Invoke-Phase "7/8 Agent binary compile" { Push-Location (Join-Path $Root "agent") go build -o (Join-Path $Root "bin\install-worker.exe") . Pop-Location } Invoke-Phase "7b Agent cross-compile (linux/darwin)" { Push-Location (Join-Path $Root "agent") $env:CGO_ENABLED = "0" $env:GOOS = "linux"; $env:GOARCH = "amd64" go build -o (Join-Path $Root "bin\install-worker-linux-amd64") -ldflags "-s -w" . $env:GOOS = "linux"; $env:GOARCH = "arm64" go build -o (Join-Path $Root "bin\install-worker-linux-arm64") -ldflags "-s -w" . $env:GOOS = "darwin"; $env:GOARCH = "arm64" go build -o (Join-Path $Root "bin\install-worker-darwin-arm64") -ldflags "-s -w" . $env:GOOS = "darwin"; $env:GOARCH = "amd64" go build -o (Join-Path $Root "bin\install-worker-darwin-amd64") -ldflags "-s -w" . Remove-Item Env:GOOS, Env:GOARCH -ErrorAction SilentlyContinue Pop-Location } } else { Write-Phase "5-7/8 Build phases (skipped)" } if (-not $SkipE2E) { Invoke-Phase "8/8 E2E smoke (Playwright)" { $DataDir = Join-Path $env:TEMP ("aether-e2e-" + [guid]::NewGuid().ToString("n")) New-Item -ItemType Directory -Force -Path $DataDir | Out-Null $E2EUser = if ($env:AETHERFORGE_E2E_USER) { $env:AETHERFORGE_E2E_USER } else { "testuser" } $E2EPass = if ($env:AETHERFORGE_E2E_PASS) { $env:AETHERFORGE_E2E_PASS } else { "testpass" } $usersObj = @{ $E2EUser = $E2EPass } [System.IO.File]::WriteAllText((Join-Path $DataDir "users.json"), ($usersObj | ConvertTo-Json -Compress)) $env:AETHERFORGE_E2E_USER = $E2EUser $env:AETHERFORGE_E2E_PASS = $E2EPass $WebRoot = Join-Path $Root "server\webroot" # Vite outDir is server/webroot; sync to legacy dist so findWebRoot fallbacks stay fresh. $DistDir = Join-Path $Root "server\web\dist" if (Test-Path $WebRoot) { New-Item -ItemType Directory -Force -Path $DistDir | Out-Null Copy-Item (Join-Path $WebRoot "*") $DistDir -Recurse -Force -ErrorAction SilentlyContinue } elseif (Test-Path (Join-Path $Root "server\web\dist")) { Copy-Item (Join-Path $Root "server\web\dist\*") $WebRoot -Recurse -Force -ErrorAction SilentlyContinue } $ServerExe = Join-Path $Root "bin\miner-server.exe" if (-not (Test-Path $ServerExe)) { Push-Location (Join-Path $Root "server") go build -o $ServerExe . Pop-Location } $env:AETHERFORGE_E2E = "1" $proc = Start-Process -FilePath $ServerExe -ArgumentList "-port","18989","-data",$DataDir -WorkingDirectory $Root -PassThru -WindowStyle Hidden try { $ready = $false for ($i = 0; $i -lt 30; $i++) { try { $r = Invoke-RestMethod "http://127.0.0.1:18989/api/v1/health" -TimeoutSec 2 if ($r.status -eq "ok") { $ready = $true; break } } catch {} Start-Sleep -Seconds 1 } if (-not $ready) { throw "E2E server did not become healthy on :18989" } $fleetSecret = "" $basic = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("${E2EUser}:${E2EPass}")) $cfgHeaders = @{ Authorization = "Basic $basic"; "X-AetherForge-Client" = "dashboard" } for ($j = 0; $j -lt 30; $j++) { try { $cfg = Invoke-RestMethod "http://127.0.0.1:18989/api/v1/config" -Headers $cfgHeaders -TimeoutSec 3 if ($cfg.server -and $cfg.server.fleet_secret) { $fleetSecret = [string]$cfg.server.fleet_secret break } } catch {} $cfgPath = Join-Path $DataDir "config.json" if (Test-Path $cfgPath) { $cfgRaw = Get-Content $cfgPath -Raw if ($cfgRaw -match '"fleet_secret"\s*:\s*"([^"]+)"') { $fleetSecret = $Matches[1] break } } Start-Sleep -Seconds 1 } if (-not $fleetSecret) { throw "E2E server missing server.fleet_secret (config API + config.json)" } Remove-Item Env:AETHERFORGE_FLEET_SECRET -ErrorAction SilentlyContinue Remove-Item Env:AETHERFORGE_URL -ErrorAction SilentlyContinue Push-Location (Join-Path $Root "server\web") $env:AETHERFORGE_URL = "http://127.0.0.1:18989" $env:AETHERFORGE_FLEET_SECRET = $fleetSecret npx playwright install chromium 2>$null | Out-Null $prevEAP = $ErrorActionPreference $ErrorActionPreference = 'Continue' try { & npx playwright test --config playwright.config.ts 2>&1 | ForEach-Object { "$_" } | Write-Host if ($LASTEXITCODE -and $LASTEXITCODE -ne 0) { throw "playwright test failed (exit $LASTEXITCODE)" } } finally { $ErrorActionPreference = $prevEAP } Pop-Location } finally { if ($proc -and -not $proc.HasExited) { Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue } Remove-Item $DataDir -Recurse -Force -ErrorAction SilentlyContinue } } } else { Write-Phase "8/8 E2E (skipped)" } Write-Host "" Write-Host "==============================================================" -ForegroundColor Cyan Write-Host " SUMMARY" -ForegroundColor Cyan Write-Host "==============================================================" -ForegroundColor Cyan $Results | Format-Table -AutoSize if ($Failed -gt 0) { Write-Host " $Failed phase(s) FAILED" -ForegroundColor Red exit 1 } Write-Host " All phases PASSED" -ForegroundColor Green exit 0