Files
multica/server/internal/daemon/wakeup_test.go
Bohan Jiang 286ecf04b1 feat(daemon): add WebSocket heartbeat with HTTP fallback
Adds daemon WebSocket heartbeat acknowledgements while preserving HTTP heartbeat fallback and HTTP task claim/result paths. Keeps old daemon compatibility and task wakeup behavior intact.
2026-04-29 17:17:55 +08:00

78 lines
2.1 KiB
Go

package daemon
import (
"log/slog"
"testing"
"time"
)
func TestTaskWakeupURL(t *testing.T) {
tests := []struct {
name string
baseURL string
runtimeIDs []string
want string
}{
{
name: "http base",
baseURL: "http://localhost:8080",
runtimeIDs: []string{"runtime-b", "runtime-a"},
want: "ws://localhost:8080/api/daemon/ws?runtime_ids=runtime-a%2Cruntime-b",
},
{
name: "https base",
baseURL: "https://api.example.com",
runtimeIDs: []string{"runtime-1"},
want: "wss://api.example.com/api/daemon/ws?runtime_ids=runtime-1",
},
{
name: "base path",
baseURL: "https://api.example.com/multica",
runtimeIDs: []string{"runtime-1"},
want: "wss://api.example.com/multica/api/daemon/ws?runtime_ids=runtime-1",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := taskWakeupURL(tt.baseURL, tt.runtimeIDs)
if err != nil {
t.Fatalf("taskWakeupURL: %v", err)
}
if got != tt.want {
t.Fatalf("taskWakeupURL() = %q, want %q", got, tt.want)
}
})
}
}
// TestWSHeartbeatFreshnessSuppressesHTTP pins the WS-vs-HTTP coordination:
// once a runtime acked over WS within the freshness window the HTTP
// heartbeat loop must skip it to avoid duplicate DB writes.
func TestWSHeartbeatFreshnessSuppressesHTTP(t *testing.T) {
d := New(Config{HeartbeatInterval: 15 * time.Second}, slog.Default())
if d.wsHeartbeatRecentlyAcked("runtime-1") {
t.Fatalf("expected unrecorded runtime to be stale")
}
d.recordWSHeartbeatAck("runtime-1")
if !d.wsHeartbeatRecentlyAcked("runtime-1") {
t.Fatalf("expected just-acked runtime to be fresh")
}
// Force the entry past the freshness window.
d.wsHBMu.Lock()
d.wsHBLastAck["runtime-1"] = time.Now().Add(-d.wsHeartbeatFreshness() - time.Second)
d.wsHBMu.Unlock()
if d.wsHeartbeatRecentlyAcked("runtime-1") {
t.Fatalf("expected aged runtime to be stale (HTTP heartbeat must resume)")
}
d.recordWSHeartbeatAck("runtime-2")
d.clearWSHeartbeatAcks()
if d.wsHeartbeatRecentlyAcked("runtime-2") {
t.Fatalf("expected clearWSHeartbeatAcks to drop all entries")
}
}