refactor to peer-centric architecture
This commit is contained in:
+105
-320
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user