This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/server/cluster/registry_test.go
Jeffrey Smith 36d6158940
All checks were successful
CI/CD / detect-changes (push) Successful in 3s
CI/CD / test-frontend (push) Successful in 6s
CI/CD / test-go-pg (push) Successful in 2m42s
CI/CD / test-sqlite (push) Successful in 2m48s
CI/CD / build-and-deploy (push) Successful in 1m5s
Feat v0.6.4 health metrics (#39)
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com>
Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
2026-03-31 14:05:49 +00:00

153 lines
4.0 KiB
Go

package cluster
import (
"context"
"encoding/json"
"testing"
"time"
"switchboard-core/store"
)
// mockClusterStore implements store.ClusterStore for unit tests.
type mockClusterStore struct {
nodes []store.ClusterNode
heartbeatRows int64
heartbeatErr error
sweepCalled bool
registerCalled bool
lastStats json.RawMessage
}
func (m *mockClusterStore) Register(_ context.Context, nodeID, endpoint string) error {
m.registerCalled = true
return nil
}
func (m *mockClusterStore) Heartbeat(_ context.Context, nodeID string, stats json.RawMessage) (int64, error) {
m.lastStats = stats
return m.heartbeatRows, m.heartbeatErr
}
func (m *mockClusterStore) SweepStale(_ context.Context, _ time.Duration) (int64, error) {
m.sweepCalled = true
return 0, nil
}
func (m *mockClusterStore) ListNodes(_ context.Context) ([]store.ClusterNode, error) {
return m.nodes, nil
}
func (m *mockClusterStore) Deregister(_ context.Context, _ string) error {
return nil
}
// mockHub implements ConnCounter.
type mockHub struct{ count int }
func (h *mockHub) ConnCount() int { return h.count }
func TestCollectStats(t *testing.T) {
hub := &mockHub{count: 5}
reg := NewRegistry("test-node", "http://localhost:8080", nil, hub, RegistryConfig{
HeartbeatInterval: 10 * time.Second,
StaleThreshold: 30 * time.Second,
})
data := reg.collectStats()
var stats map[string]any
if err := json.Unmarshal(data, &stats); err != nil {
t.Fatalf("collectStats returned invalid JSON: %v", err)
}
// Verify expected keys
expectedKeys := []string{"goroutines", "heap_alloc", "heap_sys", "gc_cycles", "gc_pause_ns", "uptime_sec", "ws_clients"}
for _, key := range expectedKeys {
if _, ok := stats[key]; !ok {
t.Errorf("missing expected stats key: %s", key)
}
}
// ws_clients should reflect hub count
if ws, ok := stats["ws_clients"].(float64); !ok || int(ws) != 5 {
t.Errorf("ws_clients = %v, want 5", stats["ws_clients"])
}
}
func TestCollectStatsFattened(t *testing.T) {
hub := &mockHub{count: 3}
reg := NewRegistry("test-node", "", nil, hub, RegistryConfig{
HeartbeatInterval: 10 * time.Second,
StaleThreshold: 30 * time.Second,
})
reg.SetSandboxStats(func() (uint64, uint64, float64) { return 100, 5, 12.3 })
reg.SetTriggerFireCount(func() int64 { return 42 })
reg.SetExtensionCount(func() int { return 8 })
data := reg.collectStats()
var stats map[string]any
if err := json.Unmarshal(data, &stats); err != nil {
t.Fatalf("collectStats returned invalid JSON: %v", err)
}
// New fattened keys
fatKeys := []string{
"stack_in_use", "gc_cpu_pct",
"extensions_loaded", "starlark_exec_total", "starlark_errors_total",
"starlark_avg_duration_ms", "trigger_fires_total",
}
for _, key := range fatKeys {
if _, ok := stats[key]; !ok {
t.Errorf("missing fattened stats key: %s", key)
}
}
if v := stats["extensions_loaded"].(float64); int(v) != 8 {
t.Errorf("extensions_loaded = %v, want 8", v)
}
if v := stats["starlark_exec_total"].(float64); int(v) != 100 {
t.Errorf("starlark_exec_total = %v, want 100", v)
}
if v := stats["trigger_fires_total"].(float64); int(v) != 42 {
t.Errorf("trigger_fires_total = %v, want 42", v)
}
}
func TestRegistryStartStop(t *testing.T) {
ms := &mockClusterStore{heartbeatRows: 1}
hub := &mockHub{count: 0}
reg := NewRegistry("test-node", "", ms, hub, RegistryConfig{
HeartbeatInterval: 50 * time.Millisecond,
StaleThreshold: 150 * time.Millisecond,
})
if err := reg.Start(); err != nil {
t.Fatalf("Start() error: %v", err)
}
if !ms.registerCalled {
t.Error("Register was not called on Start")
}
// Let at least one tick fire
time.Sleep(100 * time.Millisecond)
reg.Stop()
if !ms.sweepCalled {
t.Error("SweepStale was never called during tick")
}
if ms.lastStats == nil {
t.Error("Heartbeat was never called with stats")
}
}
func TestNodeID(t *testing.T) {
reg := NewRegistry("my-node-123", "", nil, &mockHub{}, RegistryConfig{})
if got := reg.NodeID(); got != "my-node-123" {
t.Errorf("NodeID() = %q, want %q", got, "my-node-123")
}
}