package api import ( "context" "errors" "net/http" "net/http/httptest" "testing" "time" "git.ipao.vip/rogee/creator-hub/internal/creator" hub "git.ipao.vip/rogee/creator-hub/internal/environment" ) func TestCreatorEventFromGatewayNoticeValidatesAndNormalizesInput(t *testing.T) { input, err := creatorEventFromGatewayNotice("account-1", creatorGatewayEventNotice{ EventKey: "1", EventType: "dm", InteractorUID: "123", MessageText: " hello ", PlatformEventAt: "2026-09-17T08:00:00+08:00", GatewayReceivedAt: "2026-09-17T00:00:01Z", }) if err != nil { t.Fatal(err) } if input.MessageType != creator.MessageTypeText || input.MessageText != "hello" || input.PlatformEventAt == nil || input.GatewayReceivedAt == nil { t.Fatalf("normalized event = %#v", input) } if !input.PlatformEventAt.Equal(time.Date(2026, 9, 17, 0, 0, 0, 0, time.UTC)) { t.Fatalf("platform event time = %v", input.PlatformEventAt) } invalid := []creatorGatewayEventNotice{ {EventKey: "", EventType: "comment"}, {EventKey: "1", EventType: "unknown"}, {EventKey: "1", EventType: "comment", InteractorUID: "0"}, {EventKey: "1", EventType: "comment", CommentID: "bad"}, {EventKey: "1", EventType: "comment", MessageType: "bad"}, {EventKey: "1", EventType: "comment", PlatformEventAt: "bad"}, {EventKey: "1", EventType: "comment", GatewayReceivedAt: "bad"}, } for index, notice := range invalid { if _, err := creatorEventFromGatewayNotice("account-1", notice); err == nil { t.Errorf("invalid notice %d returned no error", index) } } if _, err := creatorEventFromGatewayNotice("", creatorGatewayEventNotice{EventKey: "1", EventType: "comment"}); !errors.Is(err, creator.ErrInvalid) { t.Fatalf("empty account error = %v", err) } } func TestCreatorEventListenerControlPathsRemainVisible(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) cancel() if waitCreatorEventBackoff(ctx, time.Millisecond) { t.Fatal("canceled listener backoff was reported as ready") } if !waitCreatorEventBackoff(context.Background(), 0) { t.Fatal("zero listener backoff was not ready") } server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodDelete { w.WriteHeader(http.StatusNoContent) return } w.WriteHeader(http.StatusBadGateway) })) stopCreatorEventListener("account-1", hub.Gateway{Endpoint: server.URL}, "/v1/events", map[string]any{}) server.Close() server = httptest.NewServer(http.NotFoundHandler()) stopCreatorEventListener("account-1", hub.Gateway{Endpoint: server.URL}, "/v1/events", map[string]any{}) server.Close() binding := creatorEventBinding{accountID: "account-1", env: testRunnableEnvironment()} runCreatorEventListener(context.Background(), nil, binding, nil, nil) manager := &creatorEventListenerManager{items: map[string]creatorEventListenerHandle{}} if err := manager.reconcile(context.Background(), nil, nil, nil, nil, nil); !errors.Is(err, creator.ErrUnavailable) { t.Fatalf("nil listener dependencies error = %v", err) } listenerCtx, listenerCancel := context.WithCancel(context.Background()) listenerCancel() RunCreatorEventListeners(listenerCtx, nil, nil, nil, nil, nil) } func TestHandleCreatorGatewayEventClassifiesNonNoticeEventsWithoutStore(t *testing.T) { binding := creatorEventBinding{accountID: "account-1"} for _, kind := range []string{"error", "reconnected", "open", "baseline", "close", "unknown"} { handleCreatorGatewayEvent(context.Background(), nil, binding, creatorGatewayEvent{Kind: kind}, nil, nil) } handleCreatorGatewayEvent(context.Background(), nil, binding, creatorGatewayEvent{Kind: "notice"}, nil, nil) handleCreatorGatewayEvent(context.Background(), nil, binding, creatorGatewayEvent{ Kind: "notice", Notice: &creatorGatewayEventNotice{EventKey: "1", EventType: "comment", InteractorUID: "123"}, }, nil, nil) binding.env = hub.EnvironmentContext{Env: hub.Env{Alias: "account-1"}, RuntimeID: "runtime-1", RuntimeNetworkID: "network-1", BindingVersion: 1} if got := creatorListenerGeneration(binding.env); got != "runtime-1:network-1:1" { t.Fatalf("listener generation = %q", got) } }