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) } }