Files
uopi/internal/broker/broker_test.go
T
2026-04-24 15:46:04 +02:00

157 lines
3.4 KiB
Go

package broker_test
import (
"context"
"log/slog"
"testing"
"time"
"github.com/uopi/uopi/internal/broker"
"github.com/uopi/uopi/internal/datasource/stub"
)
func newBroker(t *testing.T) (*broker.Broker, context.CancelFunc) {
t.Helper()
ctx, cancel := context.WithCancel(context.Background())
log := slog.Default()
b := broker.New(ctx, log)
ds := stub.New()
if err := ds.Connect(ctx); err != nil {
t.Fatal(err)
}
b.Register(ds)
return b, cancel
}
func recv(t *testing.T, ch <-chan broker.Update, timeout time.Duration) broker.Update {
t.Helper()
select {
case u := <-ch:
return u
case <-time.After(timeout):
t.Fatalf("timed out waiting for update after %s", timeout)
return broker.Update{}
}
}
func TestFanOut(t *testing.T) {
b, cancel := newBroker(t)
defer cancel()
ref := broker.SignalRef{DS: "stub", Name: "sine_1hz"}
ch1 := make(chan broker.Update, 10)
ch2 := make(chan broker.Update, 10)
unsub1, err := b.Subscribe(ref, ch1)
if err != nil {
t.Fatal(err)
}
unsub2, err := b.Subscribe(ref, ch2)
if err != nil {
t.Fatal(err)
}
defer unsub2()
// Both channels must receive an update
recv(t, ch1, 2*time.Second)
recv(t, ch2, 2*time.Second)
// Only one upstream subscription should exist
if n := b.ActiveSubscriptions(); n != 1 {
t.Errorf("expected 1 active subscription, got %d", n)
}
// After unsubscribing ch1, ch2 should still receive updates
unsub1()
// drain any buffered updates
for len(ch2) > 0 {
<-ch2
}
recv(t, ch2, 2*time.Second)
}
func TestUnsubscribeLastClientTearsDownUpstream(t *testing.T) {
b, cancel := newBroker(t)
defer cancel()
ref := broker.SignalRef{DS: "stub", Name: "ramp_10s"}
ch := make(chan broker.Update, 10)
unsub, err := b.Subscribe(ref, ch)
if err != nil {
t.Fatal(err)
}
recv(t, ch, 2*time.Second)
if n := b.ActiveSubscriptions(); n != 1 {
t.Errorf("expected 1 active subscription before unsub, got %d", n)
}
unsub()
// Give the broker a moment to process the teardown
time.Sleep(50 * time.Millisecond)
if n := b.ActiveSubscriptions(); n != 0 {
t.Errorf("expected 0 active subscriptions after unsub, got %d", n)
}
}
func TestUnknownDataSource(t *testing.T) {
b, cancel := newBroker(t)
defer cancel()
ref := broker.SignalRef{DS: "nonexistent", Name: "signal"}
ch := make(chan broker.Update, 1)
_, err := b.Subscribe(ref, ch)
if err == nil {
t.Fatal("expected error for unknown data source, got nil")
}
}
func TestUnknownSignal(t *testing.T) {
b, cancel := newBroker(t)
defer cancel()
ref := broker.SignalRef{DS: "stub", Name: "does_not_exist"}
ch := make(chan broker.Update, 1)
_, err := b.Subscribe(ref, ch)
if err == nil {
t.Fatal("expected error for unknown signal, got nil")
}
}
func TestMultipleSignals(t *testing.T) {
b, cancel := newBroker(t)
defer cancel()
signals := []string{"sine_1hz", "sine_01hz", "ramp_10s", "toggle_1hz"}
channels := make([]chan broker.Update, len(signals))
unsubs := make([]func(), len(signals))
for i, name := range signals {
ch := make(chan broker.Update, 10)
channels[i] = ch
unsub, err := b.Subscribe(broker.SignalRef{DS: "stub", Name: name}, ch)
if err != nil {
t.Fatalf("subscribe %s: %v", name, err)
}
unsubs[i] = unsub
}
defer func() {
for _, u := range unsubs {
u()
}
}()
if n := b.ActiveSubscriptions(); n != len(signals) {
t.Errorf("expected %d active subscriptions, got %d", len(signals), n)
}
for i, ch := range channels {
recv(t, ch, 2*time.Second)
_ = i
}
}