1
0
mirror of https://github.com/jhillyerd/inbucket.git synced 2026-10-29 13:42:30 +00:00

refactor(rest): deduplicate WebSocket monitor controllers (#622)

The black-box monitor tests showed socketv1_controller.go and
socketv2_controller.go were near-identical: same constants (identical
values), upgrader, read/write pumps, Close, and handler bodies, differing
only in the event payload shape and v1 dropping deletions.

Extract the shared machinery into socket_controller.go:

- msgListener[T] generic listener with toStored/toDeleted payload
  transforms; a nil toDeleted preserves the socketv1 behavior of ignoring
  deletions
- socketReadPump / socketWritePump free functions (WriteJSON is
  payload-type agnostic)
- serveMonitor shared handler body (upgrade, gauge tracking, listener
  registration, pump wiring)

The version files shrink to their listener constructors and the four
exported handlers, whose signatures are unchanged.  v1 payloads are now
shaped at enqueue time rather than write time; the JSON on the wire is
identical, pinned by the commit-1 tests, which pass unmodified.

Adds direct unit tests for the extracted listener logic: watches()
filtering, Receive/Delete filtering + payload shaping, nil-toDeleted
deletion dropping, and sequential Close idempotency.
This commit is contained in:
James Hillyerd
2026-09-17 18:43:34 -07:00
committed by GitHub
parent 9aaad6b950
commit 47239c08eb
4 changed files with 329 additions and 392 deletions
+71
View File
@@ -337,3 +337,74 @@ func TestMonitorUpgradeFailedV2(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, 400, w.Code)
}
// assertNoMoreEvents fails the test if any further events are queued on the channel.
func assertNoMoreEvents[T any](t *testing.T, c chan T) {
t.Helper()
select {
case ev := <-c:
t.Fatalf("unexpected event: %v", ev)
default:
}
}
func TestMsgListenerWatches(t *testing.T) {
testCases := []struct {
name string
mailbox string // listener watch; "" watches all mailboxes
event string // mailbox of the incoming event
want bool
}{
{name: "all watcher matches any mailbox", mailbox: "", event: "anything", want: true},
{name: "watched mailbox matches", mailbox: "watched", event: "watched", want: true},
{name: "other mailbox filtered", mailbox: "watched", event: "other", want: false},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ml := &msgListener[string]{mailbox: tc.mailbox}
assert.Equal(t, tc.want, ml.watches(tc.event))
})
}
}
func TestMsgListenerReceiveFiltersAndShapes(t *testing.T) {
hub := startMsgHub(t)
ml := newMsgListener(hub, "watched",
func(msg event.MessageMetadata) string { return "stored:" + msg.Mailbox + "/" + msg.ID },
func(mailbox, id string) string { return "deleted:" + mailbox + "/" + id })
require.NoError(t, ml.Receive(testMetadata("other", "0001")))
require.NoError(t, ml.Receive(testMetadata("watched", "0002")))
require.NoError(t, ml.Delete("other", "0003"))
require.NoError(t, ml.Delete("watched", "0004"))
// Events for other mailboxes are filtered; payloads are shaped by the transforms.
assert.Equal(t, "stored:watched/0002", <-ml.c)
assert.Equal(t, "deleted:watched/0004", <-ml.c)
assertNoMoreEvents(t, ml.c)
}
func TestMsgListenerNilDeletedDropsDeletes(t *testing.T) {
hub := startMsgHub(t)
ml := newMsgListener(hub, "watched",
func(msg event.MessageMetadata) string { return "stored:" + msg.ID },
nil) // socketv1 API ignores deletions
require.NoError(t, ml.Receive(testMetadata("watched", "0001")))
require.NoError(t, ml.Delete("watched", "0001"))
assert.Equal(t, "stored:0001", <-ml.c)
assertNoMoreEvents(t, ml.c)
}
func TestMsgListenerCloseIdempotent(t *testing.T) {
hub := startMsgHub(t)
ml := newMsgListener(hub, "",
func(msg event.MessageMetadata) string { return "stored" }, nil)
ml.Close()
ml.Close() // second close must not double-close the event channel
_, ok := <-ml.c
assert.False(t, ok, "event channel should be closed")
}