ws: synthesize job.finished from update watcher so browser stream wakes up

This commit is contained in:
2026-05-07 20:32:48 +01:00
parent 001575ae9c
commit 6ef58a707e
3 changed files with 108 additions and 6 deletions
+73 -4
View File
@@ -8,6 +8,7 @@ import (
"github.com/oklog/ulid/v2"
"gitea.dcglab.co.uk/steve/restic-manager/internal/api"
"gitea.dcglab.co.uk/steve/restic-manager/internal/store"
)
@@ -50,7 +51,7 @@ func TestUpdateWatcherOnHelloSuccess(t *testing.T) {
jobID := seedJob(t, st, hostID)
a := &fakeAlerts{}
w := NewUpdateWatcher(st, a)
w := NewUpdateWatcher(st, a, nil)
w.Track(jobID, hostID)
w.OnHello(context.Background(), hostID, "v2", "v2")
@@ -83,7 +84,7 @@ func TestUpdateWatcherTimeout(t *testing.T) {
jobID := seedJob(t, st, hostID)
a := &fakeAlerts{}
w := NewUpdateWatcher(st, a)
w := NewUpdateWatcher(st, a, nil)
w.Track(jobID, hostID)
time.Sleep(80 * time.Millisecond)
@@ -113,7 +114,7 @@ func TestUpdateWatcherMismatchedVersionNoOp(t *testing.T) {
jobID := seedJob(t, st, hostID)
a := &fakeAlerts{}
w := NewUpdateWatcher(st, a)
w := NewUpdateWatcher(st, a, nil)
w.Track(jobID, hostID)
w.OnHello(context.Background(), hostID, "v1", "v2")
@@ -140,7 +141,7 @@ func TestUpdateWatcherHelloAfterTimeoutIsNoOp(t *testing.T) {
jobID := seedJob(t, st, hostID)
a := &fakeAlerts{}
w := NewUpdateWatcher(st, a)
w := NewUpdateWatcher(st, a, nil)
w.Track(jobID, hostID)
time.Sleep(80 * time.Millisecond)
@@ -159,3 +160,71 @@ func TestUpdateWatcherHelloAfterTimeoutIsNoOp(t *testing.T) {
t.Fatalf("late hello triggered ResolveUpdateFailed: %v", a.resolved)
}
}
func TestUpdateWatcherOnHelloBroadcastsJobFinished(t *testing.T) {
st := openWSTestStore(t)
hostID := ulid.Make().String()
seedHostWS(t, st, hostID)
jobID := seedJob(t, st, hostID)
hub := NewJobHub()
sub := hub.Register(jobID)
defer sub.unregister()
w := NewUpdateWatcher(st, &fakeAlerts{}, hub)
w.Track(jobID, hostID)
w.OnHello(context.Background(), hostID, "v2", "v2")
select {
case env := <-sub.ch:
if env.Type != api.MsgJobFinished {
t.Fatalf("envelope type: got %q want %q", env.Type, api.MsgJobFinished)
}
var p api.JobFinishedPayload
if err := env.UnmarshalPayload(&p); err != nil {
t.Fatalf("unmarshal payload: %v", err)
}
if p.JobID != jobID || p.Status != api.JobSucceeded {
t.Fatalf("payload: got %+v", p)
}
case <-time.After(time.Second):
t.Fatal("expected synthetic job.finished broadcast, got nothing")
}
}
func TestUpdateWatcherTimeoutBroadcastsJobFinished(t *testing.T) {
prev := updateTimeout
updateTimeout = 50 * time.Millisecond
t.Cleanup(func() { updateTimeout = prev })
st := openWSTestStore(t)
hostID := ulid.Make().String()
seedHostWS(t, st, hostID)
jobID := seedJob(t, st, hostID)
hub := NewJobHub()
sub := hub.Register(jobID)
defer sub.unregister()
w := NewUpdateWatcher(st, &fakeAlerts{}, hub)
w.Track(jobID, hostID)
time.Sleep(80 * time.Millisecond)
w.sweep(context.Background(), time.Now())
select {
case env := <-sub.ch:
if env.Type != api.MsgJobFinished {
t.Fatalf("envelope type: got %q want %q", env.Type, api.MsgJobFinished)
}
var p api.JobFinishedPayload
if err := env.UnmarshalPayload(&p); err != nil {
t.Fatalf("unmarshal payload: %v", err)
}
if p.JobID != jobID || p.Status != api.JobFailed {
t.Fatalf("payload: got %+v", p)
}
case <-time.After(time.Second):
t.Fatal("expected synthetic job.finished broadcast, got nothing")
}
}