Files
creator-hub/internal/controlplane/api/event_channel_test.go
T
rogee 360bf96bab
douyin-release-gate / verify (push) Failing after 18m0s
feat: push Douyin events through shared WebSocket and SSE
2026-10-07 23:45:32 +08:00

180 lines
5.2 KiB
Go

package api
import (
"context"
"errors"
"git.ipao.vip/rogee/creator-hub/internal/environment"
"github.com/gorilla/websocket"
"net/http"
"net/http/httptest"
"sync/atomic"
"testing"
"time"
)
func TestEventChannelSharesConnectionAndAcknowledges(t *testing.T) {
var connections atomic.Int32
acks := make(chan struct{}, 2)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/channel" || r.Header.Get("Authorization") != "Bearer test" {
t.Errorf("unexpected channel request")
w.WriteHeader(400)
return
}
c, err := (&websocket.Upgrader{}).Upgrade(w, r, nil)
if err != nil {
t.Error(err)
return
}
defer c.Close()
connections.Add(1)
for {
var v map[string]any
if c.ReadJSON(&v) != nil {
return
}
switch v["type"] {
case "subscribe":
_ = c.WriteJSON(map[string]any{"type": "subscribed", "subscription": v["subscription"]})
_ = c.WriteJSON(map[string]any{"type": "deliveries", "subscription": v["subscription"], "deliveries": []any{map[string]any{"kind": "open", "delivery_id": v["alias"]}}})
case "ack":
acks <- struct{}{}
}
}
}))
defer server.Close()
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
target := environment.Gateway{Endpoint: server.URL, Token: "test"}
first, err := acquireEventSubscription(ctx, target, map[string]any{"alias": "a"})
if err != nil {
t.Fatal(err)
}
defer first.close()
second, err := acquireEventSubscription(ctx, target, map[string]any{"alias": "b"})
if err != nil {
t.Fatal(err)
}
defer second.close()
if connections.Load() != 1 {
t.Fatalf("connections=%d", connections.Load())
}
items, err := first.poll(ctx, nil)
if err != nil || items[0].DeliveryID != "a" {
t.Fatalf("%+v %v", items, err)
}
items, err = second.poll(ctx, nil)
if err != nil || items[0].DeliveryID != "b" {
t.Fatalf("%+v %v", items, err)
}
short, c := context.WithTimeout(ctx, 30*time.Millisecond)
defer c()
_, _ = first.poll(short, []string{"a"})
select {
case <-acks:
case <-ctx.Done():
t.Fatal("ACK not received")
}
}
func TestEventChannelRejectsHandshakeAndSubscriptionTimeout(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(503) }))
defer server.Close()
for _, endpoint := range []string{server.URL, "invalid://gateway", "http://%"} {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
_, err := acquireEventSubscription(ctx, environment.Gateway{Endpoint: endpoint}, map[string]any{"alias": "a"})
cancel()
if err == nil {
t.Fatalf("invalid connection accepted: %s", endpoint)
}
}
waiting := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c, e := (&websocket.Upgrader{}).Upgrade(w, r, nil)
if e != nil {
return
}
defer c.Close()
for {
var value map[string]any
if c.ReadJSON(&value) != nil {
return
}
}
}))
defer waiting.Close()
ctx, cancel := context.WithTimeout(context.Background(), 40*time.Millisecond)
defer cancel()
_, err := acquireEventSubscription(ctx, environment.Gateway{Endpoint: waiting.URL}, map[string]any{"alias": "a"})
if !errors.Is(err, context.DeadlineExceeded) {
t.Fatalf("subscription wait did not honor cancellation: %v", err)
}
}
func TestEventChannelReportsDisconnect(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c, e := (&websocket.Upgrader{}).Upgrade(w, r, nil)
if e != nil {
return
}
defer c.Close()
var v map[string]any
_ = c.ReadJSON(&v)
_ = c.WriteJSON(map[string]any{"type": "subscribed", "subscription": v["subscription"]})
}))
defer server.Close()
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
sub, err := acquireEventSubscription(ctx, environment.Gateway{Endpoint: server.URL}, map[string]any{"alias": "a"})
if err == nil {
defer sub.close()
_, err = sub.poll(ctx, nil)
}
if err == nil {
t.Fatal("disconnect hidden")
}
}
func TestEventChannelClosingLastAccountDoesNotRetireJoiningAccount(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c, err := (&websocket.Upgrader{}).Upgrade(w, r, nil)
if err != nil {
return
}
defer c.Close()
for {
var value map[string]any
if c.ReadJSON(&value) != nil {
return
}
if value["type"] == "subscribe" {
_ = c.WriteJSON(map[string]any{"type": "subscribed", "subscription": value["subscription"]})
}
}
}))
defer server.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
target := environment.Gateway{Endpoint: server.URL}
previous, err := acquireEventSubscription(ctx, target, map[string]any{"alias": "a"})
if err != nil {
t.Fatal(err)
}
defer func() { previous.close() }()
for i := 0; i < 50; i++ {
type result struct {
sub *eventSubscription
err error
}
joined := make(chan result, 1)
go func() {
sub, err := acquireEventSubscription(ctx, target, map[string]any{"alias": "b"})
joined <- result{sub, err}
}()
previous.close()
next := <-joined
if next.err != nil {
t.Fatalf("joining account interrupted at iteration %d: %v", i, next.err)
}
previous = next.sub
}
}