refactor to peer-centric architecture

This commit is contained in:
Jay
2026-05-13 16:51:09 -04:00
parent 49ce2eb2ac
commit a096450fc7
17 changed files with 1063 additions and 3959 deletions
+105 -320
View File
@@ -3,348 +3,133 @@ package prism
import (
"context"
"git.wisehodl.dev/jay/go-honeybee"
"git.wisehodl.dev/jay/go-roots-ws"
"github.com/stretchr/testify/assert"
"sync/atomic"
"testing"
"time"
)
func TestEmbassyPoolEvents(t *testing.T) {
func TestEmbassy_TEMPLATE(t *testing.T) {
ctx := context.Background()
eventsCh := make(chan honeybee.OutboundPoolEvent)
url := "wss://test"
connect := func(url string) error { return nil }
remove := func(url string) error { return nil }
sent := false
_ = sent
send := func(url string, data []byte) error {
sent = true
return nil
}
events := make(chan honeybee.OutboundPoolEvent)
inbox := make(chan honeybee.InboxMessage)
pool := EmbassyPlugin{
Connect: func(id string) error { return nil },
Remove: func(id string) error { return nil },
Send: func(id string, data []byte) error { return nil },
Events: eventsCh,
Connect: connect,
Remove: remove,
Send: send,
Events: events,
Inbox: inbox,
}
e := NewEmbassy(ctx, pool, nil, nil)
sub := e.Subscribe()
t.Run("added then removed", func(t *testing.T) {
err := e.Dispatch("wss://test")
assert.NoError(t, err)
Eventually(t, func() bool {
select {
default:
return false
case ev := <-sub:
return ev.Kind == EventAdded
}
}, "expected added event")
err = e.Dismiss("wss://test")
assert.NoError(t, err)
Eventually(t, func() bool {
select {
default:
return false
case ev := <-sub:
return ev.Kind == EventRemoved
}
}, "expected removed event")
})
t.Run("connected", func(t *testing.T) {
e.Dispatch("wss://test")
eventsCh <- honeybee.OutboundPoolEvent{
ID: "wss://test",
Kind: honeybee.OutboundEventConnected,
At: time.Now(),
}
Eventually(t, func() bool {
select {
default:
return false
case ev := <-sub:
return ev.Kind == EventConnected
}
}, "expected connected event")
})
t.Run("disconnected", func(t *testing.T) {
e.Dispatch("wss://test")
eventsCh <- honeybee.OutboundPoolEvent{
ID: "wss://test",
Kind: honeybee.OutboundEventDisconnected,
At: time.Now(),
}
Eventually(t, func() bool {
select {
default:
return false
case ev := <-sub:
return ev.Kind == EventDisconnected
}
}, "expected disconnected event")
})
embassy := NewEmbassy(ctx, pool, nil)
embassy.Dispatch(url)
envoy := embassy.Call(url)
assert.NotNil(t, envoy)
}
func TestEmbassyPeerRegistry(t *testing.T) {
func TestEmbassy_Dispatch(t *testing.T) {
ctx := context.Background()
eventsCh := make(chan honeybee.OutboundPoolEvent)
url := "wss://test"
connectCalled := make(chan struct{})
removeCalled := make(chan struct{})
connect := func(url string) error { close(connectCalled); return nil }
remove := func(url string) error { close(removeCalled); return nil }
sent := false
send := func(url string, data []byte) error {
sent = true
return nil
}
events := make(chan honeybee.OutboundPoolEvent)
inbox := make(chan honeybee.InboxMessage)
pool := EmbassyPlugin{
Connect: func(id string) error { return nil },
Remove: func(id string) error { return nil },
Send: func(id string, data []byte) error { return nil },
Events: eventsCh,
Connect: connect,
Remove: remove,
Send: send,
Events: events,
Inbox: inbox,
}
e := NewEmbassy(ctx, pool, nil, nil)
embassy := NewEmbassy(ctx, pool, nil)
embassy.Dispatch(url)
envoy := embassy.Call(url)
assert.NotNil(t, envoy)
// add
e.Dispatch("wss://test")
url, ok := e.HasPeer("wss://test/")
assert.Equal(t, "wss://test", url)
assert.True(t, ok)
assert.False(t, e.IsConnected("wss://test"))
// connect
eventsCh <- honeybee.OutboundPoolEvent{
ID: "wss://test",
Kind: honeybee.OutboundEventConnected,
At: time.Now(),
}
Eventually(t, func() bool {
_, exists := e.HasPeer("wss://test")
connected := e.IsConnected("wss://test")
return exists && connected
}, "expected: exists, connected")
// disconnect
eventsCh <- honeybee.OutboundPoolEvent{
ID: "wss://test",
Kind: honeybee.OutboundEventDisconnected,
At: time.Now(),
}
Eventually(t, func() bool {
_, exists := e.HasPeer("wss://test")
connected := e.IsConnected("wss://test")
return exists && !connected
}, "expected: exists, disconnected")
// remove
e.Dismiss("wss://test")
_, ok = e.HasPeer("wss://test")
_, ok := <-connectCalled
assert.False(t, ok)
assert.False(t, e.IsConnected("wss://test"))
}
func TestEmbassyPeers(t *testing.T) {
ctx := context.Background()
eventSub := envoy.SubscribeEvents()
inboxSub := envoy.SubscribeInbox([]string{"EVENT"})
pool := EmbassyPlugin{
Connect: func(id string) error { return nil },
Remove: func(id string) error { return nil },
Send: func(id string, data []byte) error { return nil },
Events: nil,
}
e := NewEmbassy(ctx, pool, nil, nil)
assert.Len(t, e.Peers(), 0)
e.Dispatch("wss://test1")
e.Dispatch("wss://test2")
assert.Len(t, e.Peers(), 2)
e.Dismiss("wss://test2")
assert.Len(t, e.Peers(), 1)
}
func TestEmbassySubFanout(t *testing.T) {
ctx := context.Background()
eventsCh := make(chan honeybee.OutboundPoolEvent)
pool := EmbassyPlugin{
Connect: func(id string) error { return nil },
Remove: func(id string) error { return nil },
Send: func(id string, data []byte) error { return nil },
Events: eventsCh,
}
e := NewEmbassy(ctx, pool, nil, nil)
sub1 := e.Subscribe()
sub2 := e.Subscribe()
e.Dispatch("wss://test")
Eventually(t, func() bool {
select {
default:
return false
case ev := <-sub1:
return ev.Kind == EventAdded
}
}, "expected added event on sub1")
Eventually(t, func() bool {
select {
default:
return false
case ev := <-sub2:
return ev.Kind == EventAdded
}
}, "expected added event on sub2")
}
func TestEmbassyClose(t *testing.T) {
ctx := context.Background()
eventsCh := make(chan honeybee.OutboundPoolEvent, 1)
pool := EmbassyPlugin{
Connect: func(id string) error { return nil },
Remove: func(id string) error { return nil },
Send: func(id string, data []byte) error { return nil },
Events: eventsCh,
}
e := NewEmbassy(ctx, pool, nil, nil)
sub1 := e.Subscribe()
sub2 := e.Subscribe()
e.Dispatch("wss://test")
e.Close()
// peer gets removed
Eventually(t, func() bool {
select {
default:
return false
case ev := <-sub1:
return ev.ID == "wss://test" && ev.Kind == EventRemoved
}
}, "expected peer removed")
Eventually(t, func() bool {
select {
default:
return false
case ev := <-sub2:
return ev.ID == "wss://test" && ev.Kind == EventRemoved
}
}, "expected peer removed")
// peer list is empty
_, ok := e.HasPeer("wss://test")
assert.False(t, ok)
assert.Len(t, e.Peers(), 0)
// subs close
Eventually(t, func() bool {
_, ok1 := <-sub1
_, ok2 := <-sub2
return !ok1 && !ok2
}, "subs should close")
}
func TestEmbassyJournals(t *testing.T) {
ctx := context.Background()
jc := NewJournalCollector()
eventsCh := make(chan honeybee.OutboundPoolEvent, 1)
pool := EmbassyPlugin{
Connect: func(id string) error { return nil },
Remove: func(id string) error { return nil },
Send: func(id string, data []byte) error { return nil },
Events: eventsCh,
}
e := NewEmbassy(ctx, pool, jc, nil)
out := jc.Out()
peer := "wss://test"
// added
e.Dispatch(peer)
Eventually(t, func() bool {
select {
case entry := <-out:
_, ok := entry.(PeerAddedJournal)
return ok
default:
return false
}
}, "expected PeerAddedJournal")
// connected
eventsCh <- honeybee.OutboundPoolEvent{
ID: peer,
Kind: honeybee.OutboundEventConnected,
At: time.Now(),
}
Eventually(t, func() bool {
select {
case entry := <-out:
e, ok := entry.(PeerConnectedJournal)
// ensure fields are correct
peerOk := e.PeerID() == "wss://test"
modOk := e.Component().Module() == "prism"
pathOk := e.Component().PathString() == "embassy"
return ok && peerOk && modOk && pathOk
default:
return false
}
}, "expected PeerConnectedJournal")
// disconnected
eventsCh <- honeybee.OutboundPoolEvent{
ID: peer,
Kind: honeybee.OutboundEventDisconnected,
At: time.Now(),
}
Eventually(t, func() bool {
select {
case entry := <-out:
_, ok := entry.(PeerDisconnectedJournal)
return ok
default:
return false
}
}, "expected PeerDisconnectedJournal")
// removed
e.Dismiss(peer)
Eventually(t, func() bool {
select {
case entry := <-out:
_, ok := entry.(PeerRemovedJournal)
return ok
default:
return false
}
}, "expected PeerRemovedJournal")
// close embassy: closes journal channel
e.Close()
// Ensure jc can close now that embassy has closed its journal channel
jcClosed := make(chan struct{})
gotEvent := atomic.Int64{}
gotInbox := atomic.Int64{}
eventDone := make(chan struct{})
inboxDone := make(chan struct{})
go func() {
jc.Close()
close(jcClosed)
for range eventSub {
gotEvent.Add(1)
}
close(eventDone)
}()
go func() {
for range inboxSub {
gotInbox.Add(1)
}
close(inboxDone)
}()
Eventually(t, func() bool {
select {
case <-jcClosed:
return true
default:
return false
}
}, "JournalCollector.Close() should return after Embassy.Close()")
events <- honeybee.OutboundPoolEvent{
ID: url, Kind: honeybee.OutboundEventConnected, At: time.Now()}
events <- honeybee.OutboundPoolEvent{
ID: "wss://other", Kind: honeybee.OutboundEventConnected, At: time.Now()}
inbox <- honeybee.InboxMessage{
ID: url,
Data: envelope.EncloseEvent([]byte("{}")),
ReceivedAt: time.Now(),
}
inbox <- honeybee.InboxMessage{
ID: "wss://other",
Data: envelope.EncloseEvent([]byte("{}")),
ReceivedAt: time.Now(),
}
Eventually(t, func() bool { return gotEvent.Load() > 0 },
"should have gotten event")
Eventually(t, func() bool { return gotInbox.Load() > 0 },
"should have gotten inbox message")
Eventually(t, func() bool { return envoy.IsConnected() },
"state should have toggled")
Never(t, func() bool { return gotEvent.Load() > 1 },
"should have only gotten one event")
Never(t, func() bool { return gotInbox.Load() > 1 },
"should have only gotten one inbox message")
envoy.Send([]byte("hello"))
assert.True(t, sent)
envoy.Dismiss()
_, ok = <-removeCalled
assert.False(t, ok)
_, ok = <-eventDone
assert.False(t, ok)
_, ok = <-inboxDone
assert.False(t, ok)
// envoy no longer in embassy
envoy = embassy.Call(url)
assert.Nil(t, envoy)
}