Files
silo/internal/api/servermode_test.go
Forbes e7da3ee94d feat(sse): per-connection filtering with user and workstation context
- Extend sseClient with userID, workstationID, and item filter set
- Update Subscribe() to accept userID and workstationID params
- Add WatchItem/UnwatchItem/IsWatchingItem methods on sseClient
- Add PublishToItem, PublishToWorkstation, PublishToUser targeted delivery
- Targeted events get IDs but skip history ring buffer (real-time only)
- Update HandleEvents to pass auth user ID and workstation_id query param
- Touch workstation last_seen on SSE connect
- Existing Publish() broadcast unchanged; all current callers unaffected
- Add 5 new tests for targeted delivery and item watch lifecycle

Closes #162
2026-03-01 10:04:01 -06:00

119 lines
2.9 KiB
Go

package api
import (
"testing"
"time"
"github.com/rs/zerolog"
)
func TestServerStateModeNormal(t *testing.T) {
b := NewBroker(zerolog.Nop())
ss := NewServerState(zerolog.Nop(), nil, b)
if ss.Mode() != ModeNormal {
t.Fatalf("expected normal, got %s", ss.Mode())
}
if ss.IsReadOnly() {
t.Fatal("expected not read-only")
}
}
func TestServerStateModeReadOnly(t *testing.T) {
b := NewBroker(zerolog.Nop())
ss := NewServerState(zerolog.Nop(), nil, b)
ss.SetReadOnly(true)
if ss.Mode() != ModeReadOnly {
t.Fatalf("expected read-only, got %s", ss.Mode())
}
if !ss.IsReadOnly() {
t.Fatal("expected read-only")
}
ss.SetReadOnly(false)
if ss.Mode() != ModeNormal {
t.Fatalf("expected normal after clearing read-only, got %s", ss.Mode())
}
}
func TestServerStateModeDegraded(t *testing.T) {
b := NewBroker(zerolog.Nop())
// Pass a non-nil storage placeholder to simulate configured storage.
// We manipulate storageOK directly to test degraded mode.
ss := NewServerState(zerolog.Nop(), nil, b)
// Simulate storage configured but unhealthy by setting fields directly.
ss.mu.Lock()
// We need a non-nil storage pointer to trigger degraded mode check.
// Since we can't easily create a fake storage, we test the mode() logic
// by checking that without storage, mode stays normal.
ss.mu.Unlock()
// Without storage configured, mode should be normal even if storageOK is false
ss.mu.Lock()
ss.storageOK = false
ss.mu.Unlock()
if ss.Mode() != ModeNormal {
t.Fatalf("expected normal (no storage configured), got %s", ss.Mode())
}
}
func TestServerStateToggleReadOnly(t *testing.T) {
b := NewBroker(zerolog.Nop())
ss := NewServerState(zerolog.Nop(), nil, b)
ss.ToggleReadOnly()
if !ss.IsReadOnly() {
t.Fatal("expected read-only after toggle")
}
ss.ToggleReadOnly()
if ss.IsReadOnly() {
t.Fatal("expected not read-only after second toggle")
}
}
func TestServerStateBroadcastsOnTransition(t *testing.T) {
b := NewBroker(zerolog.Nop())
c := b.Subscribe("", "")
defer b.Unsubscribe(c)
ss := NewServerState(zerolog.Nop(), nil, b)
ss.SetReadOnly(true)
select {
case ev := <-c.ch:
if ev.Type != "server.state" {
t.Fatalf("expected server.state event, got %s", ev.Type)
}
case <-time.After(time.Second):
t.Fatal("timed out waiting for server.state event")
}
// Setting to same value should not broadcast
ss.SetReadOnly(true)
select {
case ev := <-c.ch:
t.Fatalf("unexpected event on no-op SetReadOnly: %+v", ev)
case <-time.After(50 * time.Millisecond):
// expected — no event
}
}
func TestServerStateReadOnlyOverridesDegraded(t *testing.T) {
b := NewBroker(zerolog.Nop())
ss := NewServerState(zerolog.Nop(), nil, b)
// Both read-only and storage down: should show read-only
ss.mu.Lock()
ss.readOnly = true
ss.storageOK = false
ss.mu.Unlock()
if ss.Mode() != ModeReadOnly {
t.Fatalf("expected read-only to override degraded, got %s", ss.Mode())
}
}