package api import ( "context" "encoding/json" "errors" "net/http" "net/http/httptest" "strings" "testing" hub "git.ipao.vip/rogee/creator-hub/internal/environment" ) func TestStartUnknownPreservesGatewayFailureReason(t *testing.T) { for _, disconnected := range []bool{false, true} { t.Run(map[bool]string{false: "server failure", true: "connection lost"}[disconnected], func(t *testing.T) { created := false server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodPost { created = true if disconnected { connection, _, err := w.(http.Hijacker).Hijack() if err != nil { t.Error(err) return } _ = connection.Close() return } w.WriteHeader(http.StatusServiceUnavailable) _, _ = w.Write([]byte(`{"error":"no free Xvfb display is available"}`)) return } if created { w.WriteHeader(http.StatusServiceUnavailable) _, _ = w.Write([]byte(`{"error":"cannot reconcile runtime"}`)) return } _, _ = w.Write([]byte(`[]`)) })) defer server.Close() store := newMemoryStore() store.gateways["gw-1"] = hub.Gateway{Name: "gw-1", Endpoint: server.URL} environment := hub.EnvironmentContext{Env: hub.Env{Alias: "account-a", Gateway: "gw-1", Fingerprint: hub.Fingerprint{Seed: 1}}, AccountID: "account-a", AccountStatus: "active", BindingVersion: 1} store.bindings[environment.Alias] = environment finish := func(outcome, reason string, _ hub.EnvironmentContext) error { if outcome != "unknown" || reason != "gateway_result_unknown" { t.Errorf("unexpected result: %s/%s", outcome, reason) } return nil } err := startBrowserRuntime(context.Background(), store, nil, environment, finish) want := "no free Xvfb display is available" if disconnected { want = "EOF" } if err == nil || !strings.Contains(err.Error(), want) { t.Fatalf("original startup cause was hidden: %v", err) } }) } } func TestStartBrowserRuntimeSuccessfulAndFailedCompletion(t *testing.T) { for _, existing := range []bool{false, true} { for _, finishFails := range []bool{false, true} { t.Run(map[bool]string{false: "create", true: "reconcile"}[existing]+map[bool]string{false: " success", true: " finish error"}[finishFails], func(t *testing.T) { posts := 0 runtime := runtimeStatus{ID: "runtime-a", Alias: "account-a", State: "running", NetworkID: "net-a", BindingVersion: 1, ProxyReady: true} server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodPost { posts++ w.WriteHeader(http.StatusCreated) _ = json.NewEncoder(w).Encode(runtime) } else if existing { _ = json.NewEncoder(w).Encode([]runtimeStatus{runtime}) } else { _, _ = w.Write([]byte(`[]`)) } })) defer server.Close() store := newMemoryStore() store.gateways["gw-1"] = hub.Gateway{Name: "gw-1", Endpoint: server.URL} environment := hub.EnvironmentContext{Env: hub.Env{Alias: "account-a", Gateway: "gw-1", Fingerprint: hub.Fingerprint{Seed: 1}}, AccountID: "account-a", AccountStatus: "active", BindingVersion: 1} store.bindings[environment.Alias] = environment finishErr := errors.New("completion recording failed") finish := func(outcome, reason string, current hub.EnvironmentContext) error { want := "environment_started" if existing { want = "gateway_reconciled" } if outcome != "succeeded" || reason != want || current.RuntimeID != runtime.ID { t.Errorf("unexpected completion: %s/%s/%s", outcome, reason, current.RuntimeID) } if finishFails { return finishErr } return nil } err := startBrowserRuntime(context.Background(), store, nil, environment, finish) if finishFails && !errors.Is(err, finishErr) || !finishFails && err != nil { t.Fatalf("unexpected start result: %v", err) } if existing && posts != 0 || !existing && posts != 1 { t.Fatalf("unexpected create count: %d", posts) } }) } } } func TestGatewayRejectedPreservesServerFailureReason(t *testing.T) { err := gatewayRejected(http.StatusServiceUnavailable, []byte(`{"error":"no free Xvfb display is available"}`)) if !strings.Contains(err.Error(), "no free Xvfb display is available") { t.Fatalf("gateway failure hid its cause: %v", err) } }