init
This commit is contained in:
398
cmd/internal/server/hub_test.go
Normal file
398
cmd/internal/server/hub_test.go
Normal file
@@ -0,0 +1,398 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"omnisocketgo/cmd/internal/latencylog"
|
||||
"omnisocketgo/cmd/internal/protocol"
|
||||
"omnisocketgo/cmd/internal/transport"
|
||||
)
|
||||
|
||||
type recordingLogger struct {
|
||||
mu sync.Mutex
|
||||
events []latencylog.Event
|
||||
}
|
||||
|
||||
func (l *recordingLogger) LogEvent(event latencylog.Event) error {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
l.events = append(l.events, event)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *recordingLogger) Events() []latencylog.Event {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
return append([]latencylog.Event(nil), l.events...)
|
||||
}
|
||||
|
||||
func TestServeConnRegistersPeer(t *testing.T) {
|
||||
hub := NewHub()
|
||||
client, done := startHubConn(t, hub)
|
||||
|
||||
if err := client.Send(protocol.Message{
|
||||
Type: protocol.MessageTypeRegister,
|
||||
From: "peer-a",
|
||||
To: protocol.ServerPeerID,
|
||||
}); err != nil {
|
||||
t.Fatalf("Send(register) error = %v", err)
|
||||
}
|
||||
|
||||
waitFor(t, func() bool { return hub.HasPeer("peer-a") }, "peer-a to be registered")
|
||||
|
||||
if err := client.Close(); err != nil {
|
||||
t.Fatalf("client.Close() error = %v", err)
|
||||
}
|
||||
|
||||
err := <-done
|
||||
if err == nil || !strings.Contains(err.Error(), "receive loop read") {
|
||||
t.Fatalf("ServeConn() error = %v, want read-loop shutdown error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeConnRejectsDuplicatePeerID(t *testing.T) {
|
||||
hub := NewHub()
|
||||
|
||||
first, firstDone := startHubConn(t, hub)
|
||||
registerPeer(t, first, "peer-a")
|
||||
waitFor(t, func() bool { return hub.HasPeer("peer-a") }, "peer-a to be registered")
|
||||
|
||||
second, secondDone := startHubConn(t, hub)
|
||||
registerPeer(t, second, "peer-a")
|
||||
|
||||
got, err := second.Receive()
|
||||
if err != nil {
|
||||
t.Fatalf("second.Receive() error = %v", err)
|
||||
}
|
||||
if got.Type != protocol.MessageTypeError {
|
||||
t.Fatalf("got message type %s, want %s", got.Type, protocol.MessageTypeError)
|
||||
}
|
||||
if string(got.Body) != "duplicate peer id: peer-a" {
|
||||
t.Fatalf("error body = %q, want duplicate peer message", got.Body)
|
||||
}
|
||||
|
||||
waitFor(t, func() bool { return hub.HasPeer("peer-a") }, "original peer-a to remain registered")
|
||||
|
||||
if err := first.Close(); err != nil {
|
||||
t.Fatalf("first.Close() error = %v", err)
|
||||
}
|
||||
|
||||
if err := <-secondDone; err == nil || !strings.Contains(err.Error(), "duplicate peer id") {
|
||||
t.Fatalf("second ServeConn() error = %v, want duplicate peer id error", err)
|
||||
}
|
||||
if err := <-firstDone; err == nil || !strings.Contains(err.Error(), "receive loop read") {
|
||||
t.Fatalf("first ServeConn() error = %v, want read-loop shutdown error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeConnForwardsMessages(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
msg protocol.Message
|
||||
}{
|
||||
{
|
||||
name: "text",
|
||||
msg: protocol.Message{
|
||||
Type: protocol.MessageTypeText,
|
||||
ID: 1,
|
||||
From: "spoofed",
|
||||
To: "peer-b",
|
||||
Body: []byte("hello"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "file",
|
||||
msg: protocol.Message{
|
||||
Type: protocol.MessageTypeFile,
|
||||
ID: 2,
|
||||
From: "spoofed",
|
||||
To: "peer-b",
|
||||
FileName: "payload.bin",
|
||||
Body: []byte{0x01, 0x02, 0x03},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
hub := NewHub()
|
||||
|
||||
sender, senderDone := startHubConn(t, hub)
|
||||
receiver, receiverDone := startHubConn(t, hub)
|
||||
registerPeer(t, sender, "peer-a")
|
||||
registerPeer(t, receiver, "peer-b")
|
||||
waitFor(t, func() bool { return hub.HasPeer("peer-a") && hub.HasPeer("peer-b") }, "both peers to be registered")
|
||||
|
||||
if err := sender.Send(tt.msg); err != nil {
|
||||
t.Fatalf("sender.Send() error = %v", err)
|
||||
}
|
||||
|
||||
got, err := receiver.Receive()
|
||||
if err != nil {
|
||||
t.Fatalf("receiver.Receive() error = %v", err)
|
||||
}
|
||||
|
||||
want := tt.msg
|
||||
want.From = "peer-a"
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("forwarded message mismatch: got %+v want %+v", got, want)
|
||||
}
|
||||
|
||||
_ = sender.Close()
|
||||
_ = receiver.Close()
|
||||
<-senderDone
|
||||
<-receiverDone
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeConnReturnsErrorForUnknownTarget(t *testing.T) {
|
||||
hub := NewHub()
|
||||
|
||||
client, done := startHubConn(t, hub)
|
||||
registerPeer(t, client, "peer-a")
|
||||
waitFor(t, func() bool { return hub.HasPeer("peer-a") }, "peer-a to be registered")
|
||||
|
||||
if err := client.Send(protocol.Message{
|
||||
Type: protocol.MessageTypeText,
|
||||
ID: 1,
|
||||
From: "peer-a",
|
||||
To: "missing-peer",
|
||||
Body: []byte("hello"),
|
||||
}); err != nil {
|
||||
t.Fatalf("Send(text) error = %v", err)
|
||||
}
|
||||
|
||||
got, err := client.Receive()
|
||||
if err != nil {
|
||||
t.Fatalf("Receive() error = %v", err)
|
||||
}
|
||||
if got.Type != protocol.MessageTypeError {
|
||||
t.Fatalf("got message type %s, want %s", got.Type, protocol.MessageTypeError)
|
||||
}
|
||||
if string(got.Body) != "unknown target: missing-peer" {
|
||||
t.Fatalf("error body = %q, want unknown target message", got.Body)
|
||||
}
|
||||
if !hub.HasPeer("peer-a") {
|
||||
t.Fatal("peer-a should remain registered after unknown target error")
|
||||
}
|
||||
|
||||
_ = client.Close()
|
||||
<-done
|
||||
}
|
||||
|
||||
func TestServeConnRejectsRegisterAfterRegistration(t *testing.T) {
|
||||
hub := NewHub()
|
||||
|
||||
client, done := startHubConn(t, hub)
|
||||
registerPeer(t, client, "peer-a")
|
||||
waitFor(t, func() bool { return hub.HasPeer("peer-a") }, "peer-a to be registered")
|
||||
|
||||
if err := client.Send(protocol.Message{
|
||||
Type: protocol.MessageTypeRegister,
|
||||
From: "peer-a",
|
||||
To: protocol.ServerPeerID,
|
||||
}); err != nil {
|
||||
t.Fatalf("Send(register again) error = %v", err)
|
||||
}
|
||||
|
||||
got, err := client.Receive()
|
||||
if err != nil {
|
||||
t.Fatalf("Receive() error = %v", err)
|
||||
}
|
||||
if got.Type != protocol.MessageTypeError {
|
||||
t.Fatalf("got message type %s, want %s", got.Type, protocol.MessageTypeError)
|
||||
}
|
||||
if string(got.Body) != "registered peers can only send text or file messages" {
|
||||
t.Fatalf("error body = %q, want registered-peer protocol error", got.Body)
|
||||
}
|
||||
|
||||
if err := <-done; err == nil || !strings.Contains(err.Error(), "unexpected message type from peer peer-a: register") {
|
||||
t.Fatalf("ServeConn() error = %v, want unexpected register message error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeConnUnregistersPeerOnClose(t *testing.T) {
|
||||
hub := NewHub()
|
||||
|
||||
client, done := startHubConn(t, hub)
|
||||
registerPeer(t, client, "peer-a")
|
||||
waitFor(t, func() bool { return hub.HasPeer("peer-a") }, "peer-a to be registered")
|
||||
|
||||
if err := client.Close(); err != nil {
|
||||
t.Fatalf("client.Close() error = %v", err)
|
||||
}
|
||||
<-done
|
||||
|
||||
waitFor(t, func() bool { return !hub.HasPeer("peer-a") }, "peer-a to be unregistered")
|
||||
}
|
||||
|
||||
func TestServeConnDoesNotEmitEndpointLatencyEventsOnForward(t *testing.T) {
|
||||
logger := &recordingLogger{}
|
||||
hub := NewHub(WithLogger(logger))
|
||||
|
||||
sender, senderDone := startHubConn(t, hub)
|
||||
receiver, receiverDone := startHubConn(t, hub)
|
||||
registerPeer(t, sender, "peer-a")
|
||||
registerPeer(t, receiver, "peer-b")
|
||||
waitFor(t, func() bool { return hub.HasPeer("peer-a") && hub.HasPeer("peer-b") }, "both peers to be registered")
|
||||
|
||||
msg := protocol.Message{
|
||||
Type: protocol.MessageTypeText,
|
||||
ID: 11,
|
||||
From: "spoofed",
|
||||
To: "peer-b",
|
||||
Body: []byte("hello"),
|
||||
}
|
||||
if err := sender.Send(msg); err != nil {
|
||||
t.Fatalf("sender.Send() error = %v", err)
|
||||
}
|
||||
|
||||
got, err := receiver.Receive()
|
||||
if err != nil {
|
||||
t.Fatalf("receiver.Receive() error = %v", err)
|
||||
}
|
||||
msg.From = "peer-a"
|
||||
if !reflect.DeepEqual(got, msg) {
|
||||
t.Fatalf("forwarded message mismatch: got %+v want %+v", got, msg)
|
||||
}
|
||||
|
||||
events := logger.Events()
|
||||
if len(events) != 0 {
|
||||
t.Fatalf("event count = %d, want 0 because server is a black-box relay", len(events))
|
||||
}
|
||||
|
||||
_ = sender.Close()
|
||||
_ = receiver.Close()
|
||||
<-senderDone
|
||||
<-receiverDone
|
||||
}
|
||||
|
||||
func TestServeConnDoesNotLogLatencyEventsForUnknownTarget(t *testing.T) {
|
||||
logger := &recordingLogger{}
|
||||
hub := NewHub(WithLogger(logger))
|
||||
|
||||
client, done := startHubConn(t, hub)
|
||||
registerPeer(t, client, "peer-a")
|
||||
waitFor(t, func() bool { return hub.HasPeer("peer-a") }, "peer-a to be registered")
|
||||
|
||||
if err := client.Send(protocol.Message{
|
||||
Type: protocol.MessageTypeText,
|
||||
ID: 15,
|
||||
From: "peer-a",
|
||||
To: "missing-peer",
|
||||
Body: []byte("hello"),
|
||||
}); err != nil {
|
||||
t.Fatalf("Send(text) error = %v", err)
|
||||
}
|
||||
|
||||
got, err := client.Receive()
|
||||
if err != nil {
|
||||
t.Fatalf("Receive() error = %v", err)
|
||||
}
|
||||
if got.Type != protocol.MessageTypeError {
|
||||
t.Fatalf("got message type %s, want %s", got.Type, protocol.MessageTypeError)
|
||||
}
|
||||
if events := logger.Events(); len(events) != 0 {
|
||||
t.Fatalf("event count = %d, want 0 for unknown target path", len(events))
|
||||
}
|
||||
|
||||
_ = client.Close()
|
||||
<-done
|
||||
}
|
||||
|
||||
func TestServeConnDoesNotLogLatencyEventsForDuplicateRegister(t *testing.T) {
|
||||
logger := &recordingLogger{}
|
||||
hub := NewHub(WithLogger(logger))
|
||||
|
||||
first, firstDone := startHubConn(t, hub)
|
||||
registerPeer(t, first, "peer-a")
|
||||
waitFor(t, func() bool { return hub.HasPeer("peer-a") }, "peer-a to be registered")
|
||||
|
||||
second, secondDone := startHubConn(t, hub)
|
||||
registerPeer(t, second, "peer-a")
|
||||
|
||||
got, err := second.Receive()
|
||||
if err != nil {
|
||||
t.Fatalf("second.Receive() error = %v", err)
|
||||
}
|
||||
if got.Type != protocol.MessageTypeError {
|
||||
t.Fatalf("got type %s, want %s", got.Type, protocol.MessageTypeError)
|
||||
}
|
||||
if events := logger.Events(); len(events) != 0 {
|
||||
t.Fatalf("event count = %d, want 0 for duplicate register path", len(events))
|
||||
}
|
||||
|
||||
_ = first.Close()
|
||||
<-secondDone
|
||||
<-firstDone
|
||||
}
|
||||
|
||||
func startHubConn(t *testing.T, hub *Hub) (*transport.TCPConn, <-chan error) {
|
||||
t.Helper()
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("net.Listen() error = %v", err)
|
||||
}
|
||||
done := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
serverSide, acceptErr := listener.Accept()
|
||||
if acceptErr != nil {
|
||||
done <- acceptErr
|
||||
return
|
||||
}
|
||||
done <- hub.ServeConn(serverSide)
|
||||
}()
|
||||
|
||||
clientSide, err := net.Dial("tcp", listener.Addr().String())
|
||||
if err != nil {
|
||||
_ = listener.Close()
|
||||
t.Fatalf("net.Dial() error = %v", err)
|
||||
}
|
||||
if err := listener.Close(); err != nil {
|
||||
t.Fatalf("listener.Close() error = %v", err)
|
||||
}
|
||||
|
||||
conn, err := transport.NewTCPConn(clientSide)
|
||||
if err != nil {
|
||||
_ = clientSide.Close()
|
||||
t.Fatalf("transport.NewTCPConn() error = %v", err)
|
||||
}
|
||||
|
||||
return conn, done
|
||||
}
|
||||
|
||||
func registerPeer(t *testing.T, conn *transport.TCPConn, peerID string) {
|
||||
t.Helper()
|
||||
|
||||
if err := conn.Send(protocol.Message{
|
||||
Type: protocol.MessageTypeRegister,
|
||||
From: peerID,
|
||||
To: protocol.ServerPeerID,
|
||||
}); err != nil {
|
||||
t.Fatalf("Send(register %s) error = %v", peerID, err)
|
||||
}
|
||||
}
|
||||
|
||||
func waitFor(t *testing.T, condition func() bool, description string) {
|
||||
t.Helper()
|
||||
|
||||
deadline := time.Now().Add(500 * time.Millisecond)
|
||||
for time.Now().Before(deadline) {
|
||||
if condition() {
|
||||
return
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
t.Fatalf("timed out waiting for %s", description)
|
||||
}
|
||||
Reference in New Issue
Block a user