Files
AetherForge/server/internal/builder/handler_http_test.go
AetherForge ea6f54ad03 Expand test coverage across server, agent, and web; fix bugs found during audit.
Adds hundreds of unit/integration/e2e tests, fixes WS bcrypt auth, config merge, fleet analytics, agent schedule/log tail, and documents stale PROBLEMS items. Updates PROBLEMS.md, README, and test scripts; ignores local spread-kits and coverage dirs.
2026-05-31 01:13:58 -07:00

215 lines
5.5 KiB
Go

package builder
import (
"context"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
)
func TestCancelBuild(t *testing.T) {
h := &Handler{}
if h.CancelBuild("missing") {
t.Fatal("unknown token should return false")
}
ctx, cancel := context.WithCancel(context.Background())
h.registerCancel("tok-1", cancel)
if !h.CancelBuild("tok-1") {
t.Fatal("expected cancel success")
}
select {
case <-ctx.Done():
default:
t.Fatal("context should be cancelled")
}
}
func TestUnregisterCancelEmptyToken(t *testing.T) {
h := &Handler{activeCancels: map[string]context.CancelFunc{"x": func() {}}}
h.unregisterCancel("")
if _, ok := h.activeCancels["x"]; !ok {
t.Fatal("empty token unregister should be no-op")
}
}
func TestServeHTTPMethodNotAllowed(t *testing.T) {
h := &Handler{}
req := httptest.NewRequest(http.MethodGet, "/api/v1/builder", nil)
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != http.StatusMethodNotAllowed {
t.Fatalf("status: %d", rec.Code)
}
}
func TestServeHTTPInvalidJSON(t *testing.T) {
h := &Handler{}
req := httptest.NewRequest(http.MethodPost, "/api/v1/builder", strings.NewReader("{bad"))
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("status: %d body: %s", rec.Code, rec.Body.String())
}
}
func TestServeHTTPMissingWallet(t *testing.T) {
h := &Handler{}
body := `{"worker_name":"pc","server_url":"http://127.0.0.1:8989"}`
req := httptest.NewRequest(http.MethodPost, "/api/v1/builder", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("status: %d", rec.Code)
}
if !strings.Contains(rec.Body.String(), "wallet") {
t.Fatalf("body: %s", rec.Body.String())
}
}
func TestServeEstimateMethodNotAllowed(t *testing.T) {
h := &Handler{}
req := httptest.NewRequest(http.MethodGet, "/api/v1/builder/estimate", nil)
rec := httptest.NewRecorder()
h.ServeEstimate(rec, req)
if rec.Code != http.StatusMethodNotAllowed {
t.Fatalf("status: %d", rec.Code)
}
}
func TestServeEstimateRequiresMultipart(t *testing.T) {
h := &Handler{}
req := httptest.NewRequest(http.MethodPost, "/api/v1/builder/estimate", strings.NewReader(`{}`))
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
h.ServeEstimate(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("status: %d", rec.Code)
}
if !strings.Contains(rec.Body.String(), "multipart") {
t.Fatalf("body: %s", rec.Body.String())
}
}
func TestNormalizeRequestStrictWallet(t *testing.T) {
h := &Handler{policy: BuildPolicy{StrictWalletValidation: true}}
req := &BuildRequest{
WorkerName: "pc",
ServerURL: "http://127.0.0.1:8989",
Wallet: "not-a-wallet",
}
err := h.normalizeRequest(req)
if err == nil || !strings.Contains(err.Error(), "wallet") {
t.Fatalf("expected wallet error, got %v", err)
}
}
func TestNormalizeRequestCustomInstallBase(t *testing.T) {
h := &Handler{}
req := &BuildRequest{
WorkerName: "pc",
ServerURL: "http://127.0.0.1:8989",
Wallet: "48abc",
InstallBase: "custom",
}
if err := h.normalizeRequest(req); err == nil {
t.Fatal("expected install_custom_base error")
}
}
func TestNormalizeRequestInvalidOutputDir(t *testing.T) {
h := &Handler{}
req := &BuildRequest{
WorkerName: "pc",
ServerURL: "http://127.0.0.1:8989",
Wallet: "48abc",
OutputDir: "../escape",
}
if err := h.normalizeRequest(req); err == nil {
t.Fatal("expected output_dir error")
}
}
func TestNormalizeRequestSpreadKit(t *testing.T) {
h := &Handler{}
req := &BuildRequest{
WorkerName: "pc",
ServerURL: "http://127.0.0.1:8989",
Wallet: "48abc",
SpreadKit: true,
FusionEnabled: true,
}
if err := h.normalizeRequest(req); err != nil {
t.Fatal(err)
}
if req.FusionEnabled {
t.Fatal("spread kit should disable fusion")
}
if req.TargetOS != "universal" {
t.Fatalf("target os: %q", req.TargetOS)
}
if !req.Persistence || !req.AutoStart {
t.Fatal("spread kit should force persistence")
}
}
func TestNormalizeRequestAIDefaults(t *testing.T) {
h := &Handler{}
req := &BuildRequest{
WorkerName: "pc",
ServerURL: "http://127.0.0.1:8989",
Wallet: "48abc",
AIEnabled: true,
}
if err := h.normalizeRequest(req); err != nil {
t.Fatal(err)
}
if req.AIOllamaEndpoint == "" || req.AIModel == "" {
t.Fatal("AI defaults should be set")
}
}
func TestNormalizeRequestThreadPercentCap(t *testing.T) {
h := &Handler{}
req := &BuildRequest{
WorkerName: "pc",
ServerURL: "http://127.0.0.1:8989",
Wallet: "48abc",
ThreadPercent: 150,
}
if err := h.normalizeRequest(req); err != nil {
t.Fatal(err)
}
if req.ThreadPercent != 100 {
t.Fatalf("capped at 100, got %d", req.ThreadPercent)
}
}
func TestExportBuildArtifactsInvalidDir(t *testing.T) {
h := &Handler{projectRoot: t.TempDir(), dataDir: t.TempDir()}
_, _, err := h.exportBuildArtifacts("a", "b.exe", "c", "d.ps1", "..")
if err == nil {
t.Fatal("expected invalid output_dir error")
}
}
func TestPublishRootExecutableNoProjectRoot(t *testing.T) {
dir := t.TempDir()
src := filepath.Join(dir, "out.exe")
if err := os.WriteFile(src, []byte("bin"), 0644); err != nil {
t.Fatal(err)
}
h := &Handler{projectRoot: "."}
got, err := h.publishRootExecutable(src, "out.exe")
if err != nil {
t.Fatal(err)
}
if got != src {
t.Fatalf("expected source path %q, got %q", src, got)
}
}