Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
POST /api/v1/recon/scan probes fleet ports from the server host, crawls owned HTTP targets, maps findings to spread lanes, and records optional oath ledger rows.
453 lines
19 KiB
PowerShell
453 lines
19 KiB
PowerShell
# 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
|
|
}
|
|
|
|
# Reclaim :18989 from a prior crashed E2E run so health checks hit this phase''s server.
|
|
$e2ePort = 18989
|
|
Get-NetTCPConnection -LocalPort $e2ePort -ErrorAction SilentlyContinue |
|
|
Select-Object -ExpandProperty OwningProcess -Unique |
|
|
ForEach-Object {
|
|
if ($_ -and $_ -ne 0) {
|
|
Stop-Process -Id $_ -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
Start-Sleep -Milliseconds 500
|
|
$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
|