Files
geo/server/internal/shared/stream/desktop_dispatch_test.go
T
root 9fa091c150 feat(desktop): push publish tasks via AMQP topic dispatch WebSocket
Introduce a desktop.task.dispatch topic exchange with per-client routing
keys. Tenant-api publishes a task_available frame keyed by target client
ID when a publish job is created; every instance binds its own transient
queue and forwards to the matching WebSocket (/api/desktop/dispatch) it
owns. Desktop-client now prefers this push channel and only falls back
to HTTP /lease polling when the socket is down. Also drop admin-web
monitoring-plugin remnants and scope the publish modal account list to
publish platforms.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 19:46:59 +08:00

136 lines
3.5 KiB
Go

package stream
import (
"testing"
"time"
amqp "github.com/rabbitmq/amqp091-go"
)
func TestDispatchHub_PublishRoutesToLocalSubscriber(t *testing.T) {
t.Parallel()
hub := NewDesktopDispatchHub(nil, nil, []string{"publish.*"})
sub, cancel := hub.Subscribe("client-a")
defer cancel()
event := DesktopDispatchEvent{
Type: "task_available",
TaskID: "task-1",
TargetClientID: "client-a",
Kind: "publish",
Status: "queued",
}
hub.Publish(event)
select {
case got := <-sub.Outbound():
if got.TaskID != "task-1" {
t.Fatalf("unexpected task id: %s", got.TaskID)
}
case <-time.After(200 * time.Millisecond):
t.Fatal("expected to receive event within 200ms")
}
}
func TestDispatchHub_DropsEventsForUnknownClient(t *testing.T) {
t.Parallel()
hub := NewDesktopDispatchHub(nil, nil, []string{"publish.*"})
// Subscribe for a different client, publish for another one.
_, cancel := hub.Subscribe("client-b")
defer cancel()
hub.Publish(DesktopDispatchEvent{
Type: "task_available",
TargetClientID: "client-a",
})
ids := hub.ActiveClientIDs()
if len(ids) != 1 || ids[0] != "client-b" {
t.Fatalf("expected only client-b subscribed, got %v", ids)
}
}
func TestDispatchHub_CancelRemovesSubscriber(t *testing.T) {
t.Parallel()
hub := NewDesktopDispatchHub(nil, nil, []string{"publish.*"})
sub, cancel := hub.Subscribe("client-a")
cancel()
// Subsequent publish must not panic even though the subscriber is gone;
// channel is closed, reads must yield zero value with !ok.
hub.Publish(DesktopDispatchEvent{TargetClientID: "client-a"})
select {
case _, ok := <-sub.Outbound():
if ok {
t.Fatal("expected outbound channel to be closed after cancel")
}
case <-time.After(50 * time.Millisecond):
t.Fatal("expected closed channel to yield immediately")
}
if ids := hub.ActiveClientIDs(); len(ids) != 0 {
t.Fatalf("expected no subscribers after cancel, got %v", ids)
}
}
func TestDispatchHub_MultipleSubscribersSameClient(t *testing.T) {
t.Parallel()
hub := NewDesktopDispatchHub(nil, nil, []string{"publish.*"})
subA, cancelA := hub.Subscribe("client-a")
defer cancelA()
subB, cancelB := hub.Subscribe("client-a")
defer cancelB()
hub.Publish(DesktopDispatchEvent{
Type: "task_available",
TaskID: "task-1",
TargetClientID: "client-a",
})
for i, ch := range []<-chan DesktopDispatchEvent{subA.Outbound(), subB.Outbound()} {
select {
case got := <-ch:
if got.TaskID != "task-1" {
t.Fatalf("subscriber %d got unexpected task id: %s", i, got.TaskID)
}
case <-time.After(200 * time.Millisecond):
t.Fatalf("subscriber %d did not receive event", i)
}
}
}
func TestDispatchHub_HandleDeliveryFallsBackToRoutingKey(t *testing.T) {
t.Parallel()
hub := NewDesktopDispatchHub(nil, nil, []string{"publish.*"})
sub, cancel := hub.Subscribe("client-c")
defer cancel()
// Body intentionally omits target_client_id; the hub should recover it
// from the routing key "<prefix>.<client_id>".
delivery := amqp.Delivery{
RoutingKey: "publish.client-c",
Body: []byte(`{"type":"task_available","task_id":"task-9","kind":"publish","status":"queued"}`),
}
hub.handleDelivery(delivery)
select {
case got := <-sub.Outbound():
if got.TargetClientID != "client-c" {
t.Fatalf("expected fallback to set target client id, got %q", got.TargetClientID)
}
if got.TaskID != "task-9" {
t.Fatalf("unexpected task id: %s", got.TaskID)
}
case <-time.After(200 * time.Millisecond):
t.Fatal("expected fallback-routed event")
}
}