148 lines
3.5 KiB
Go
148 lines
3.5 KiB
Go
// ABOUTME: Tests for GroupRole interface and role dispatch on Group
|
|
// ABOUTME: Verifies RegisterRole, event dispatch, and message routing
|
|
package sendspin
|
|
|
|
import (
|
|
"encoding/json"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// testRole is a minimal GroupRole implementation for testing.
|
|
type testRole struct {
|
|
mu sync.Mutex
|
|
role string
|
|
joined []*ServerClient
|
|
left []string
|
|
messages []json.RawMessage
|
|
}
|
|
|
|
func (r *testRole) Role() string { return r.role }
|
|
|
|
func (r *testRole) OnClientJoin(c *ServerClient) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
r.joined = append(r.joined, c)
|
|
}
|
|
|
|
func (r *testRole) OnClientLeave(id string, name string) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
r.left = append(r.left, id)
|
|
}
|
|
|
|
func (r *testRole) HandleMessage(c *ServerClient, payload json.RawMessage) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
r.messages = append(r.messages, payload)
|
|
return nil
|
|
}
|
|
|
|
func TestGroup_RegisterRole(t *testing.T) {
|
|
g := NewGroup("test")
|
|
defer g.Close()
|
|
|
|
role := &testRole{role: "controller"}
|
|
g.RegisterRole(role)
|
|
|
|
got := g.GetRole("controller")
|
|
if got != role {
|
|
t.Errorf("GetRole returned %v, want registered role", got)
|
|
}
|
|
if g.GetRole("nonexistent") != nil {
|
|
t.Error("GetRole for unregistered role should return nil")
|
|
}
|
|
}
|
|
|
|
func TestGroup_RoleDispatchOnJoinLeave(t *testing.T) {
|
|
g := NewGroup("test")
|
|
defer g.Close()
|
|
|
|
role := &testRole{role: "player"}
|
|
g.RegisterRole(role)
|
|
|
|
sc := &ServerClient{id: "c1", name: "Client 1", roles: []string{"player@v1"}}
|
|
g.addClient(sc)
|
|
|
|
// Give the dispatcher goroutine time to process.
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
role.mu.Lock()
|
|
joinedCount := len(role.joined)
|
|
var joinedID string
|
|
if joinedCount > 0 {
|
|
joinedID = role.joined[0].ID()
|
|
}
|
|
role.mu.Unlock()
|
|
|
|
if joinedCount != 1 || joinedID != "c1" {
|
|
t.Errorf("OnClientJoin: got %d calls, want 1 with id=c1", joinedCount)
|
|
}
|
|
|
|
g.removeClient(sc)
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
role.mu.Lock()
|
|
leftCount := len(role.left)
|
|
var leftID string
|
|
if leftCount > 0 {
|
|
leftID = role.left[0]
|
|
}
|
|
role.mu.Unlock()
|
|
|
|
if leftCount != 1 || leftID != "c1" {
|
|
t.Errorf("OnClientLeave: got %d calls, want 1 with id=c1", leftCount)
|
|
}
|
|
}
|
|
|
|
func TestGroup_RouteMessage(t *testing.T) {
|
|
g := NewGroup("test")
|
|
defer g.Close()
|
|
|
|
role := &testRole{role: "controller"}
|
|
g.RegisterRole(role)
|
|
|
|
sc := &ServerClient{id: "c1"}
|
|
payload := json.RawMessage(`{"command":"next"}`)
|
|
|
|
err := g.RouteMessage(sc, "controller", payload)
|
|
if err != nil {
|
|
t.Fatalf("RouteMessage: %v", err)
|
|
}
|
|
if len(role.messages) != 1 {
|
|
t.Fatalf("HandleMessage called %d times, want 1", len(role.messages))
|
|
}
|
|
}
|
|
|
|
func TestGroup_RouteMessageNoHandler(t *testing.T) {
|
|
g := NewGroup("test")
|
|
defer g.Close()
|
|
|
|
err := g.RouteMessage(&ServerClient{id: "c1"}, "nonexistent", json.RawMessage(`{}`))
|
|
if err == nil {
|
|
t.Error("RouteMessage to unregistered role should return error")
|
|
}
|
|
}
|
|
|
|
// roleWithoutMessageHandler implements GroupRole but NOT MessageHandler.
|
|
type roleWithoutMessageHandler struct {
|
|
role string
|
|
}
|
|
|
|
func (r *roleWithoutMessageHandler) Role() string { return r.role }
|
|
func (r *roleWithoutMessageHandler) OnClientJoin(c *ServerClient) {}
|
|
func (r *roleWithoutMessageHandler) OnClientLeave(id string, name string) {}
|
|
|
|
func TestGroup_RouteMessageRoleWithoutHandler(t *testing.T) {
|
|
g := NewGroup("test")
|
|
defer g.Close()
|
|
|
|
g.RegisterRole(&roleWithoutMessageHandler{role: "metadata"})
|
|
|
|
err := g.RouteMessage(&ServerClient{id: "c1"}, "metadata", json.RawMessage(`{}`))
|
|
if err == nil {
|
|
t.Error("RouteMessage to role without MessageHandler should return error")
|
|
}
|
|
}
|