对接
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
package live
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/olahol/melody"
|
||||
"github.com/pion/webrtc/v4"
|
||||
|
||||
bizlive "kra/internal/biz/live"
|
||||
platformws "kra/pkg/websocket"
|
||||
)
|
||||
|
||||
type liveHubStub struct {
|
||||
mu sync.Mutex
|
||||
messages map[*melody.Session][][]byte
|
||||
}
|
||||
|
||||
func (h *liveHubStub) Enabled() bool { return true }
|
||||
func (h *liveHubStub) Broadcast([]byte) error { return nil }
|
||||
func (h *liveHubStub) BroadcastBinary([]byte) error { return nil }
|
||||
func (h *liveHubStub) Sessions() ([]*platformws.Session, error) { return nil, nil }
|
||||
func (h *liveHubStub) Len() int { return 0 }
|
||||
func (h *liveHubStub) SendBinary(*platformws.Session, []byte) error { return nil }
|
||||
func (h *liveHubStub) OnMessage(func(*platformws.Session, []byte)) {}
|
||||
func (h *liveHubStub) OnBinaryMessage(func(*platformws.Session, []byte)) {}
|
||||
func (h *liveHubStub) OnConnect(func(*platformws.Session)) {}
|
||||
func (h *liveHubStub) OnDisconnect(func(*platformws.Session)) {}
|
||||
func (h *liveHubStub) Send(session *platformws.Session, message []byte) error {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
if h.messages == nil {
|
||||
h.messages = make(map[*melody.Session][][]byte)
|
||||
}
|
||||
h.messages[session] = append(h.messages[session], append([]byte(nil), message...))
|
||||
return nil
|
||||
}
|
||||
|
||||
type liveCatalogStub struct {
|
||||
bizlive.CatalogRepo
|
||||
room *bizlive.Room
|
||||
err error
|
||||
}
|
||||
|
||||
func (s *liveCatalogStub) GetRoom(context.Context, uint) (*bizlive.Room, error) {
|
||||
if s.err != nil {
|
||||
return nil, s.err
|
||||
}
|
||||
copy := *s.room
|
||||
return ©, nil
|
||||
}
|
||||
|
||||
func (s *liveCatalogStub) GetCategory(context.Context, uint) (*bizlive.Category, error) {
|
||||
return &bizlive.Category{ID: s.room.CategoryID, Enabled: true}, nil
|
||||
}
|
||||
|
||||
type liveStreamStub struct {
|
||||
bizlive.StreamRepo
|
||||
mu sync.Mutex
|
||||
streams map[string]*bizlive.Stream
|
||||
users map[uint]struct{}
|
||||
registerCalls int
|
||||
}
|
||||
|
||||
func (s *liveStreamStub) Get(_ context.Context, streamID string) (*bizlive.Stream, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
stream := s.streams[streamID]
|
||||
if stream == nil {
|
||||
return &bizlive.Stream{StreamID: streamID}, nil
|
||||
}
|
||||
copy := *stream
|
||||
return ©, nil
|
||||
}
|
||||
|
||||
func (s *liveStreamStub) List(context.Context) ([]*bizlive.Stream, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
items := make([]*bizlive.Stream, 0, len(s.streams))
|
||||
for _, stream := range s.streams {
|
||||
if stream.PublisherOnline {
|
||||
copy := *stream
|
||||
items = append(items, ©)
|
||||
}
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
func (s *liveStreamStub) RegisterSubscriber(_ context.Context, streamID string, userID uint) (*bizlive.Stream, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.registerCalls++
|
||||
if s.users == nil {
|
||||
s.users = make(map[uint]struct{})
|
||||
}
|
||||
if _, exists := s.users[userID]; exists {
|
||||
return nil, bizlive.ErrSubscriberExists
|
||||
}
|
||||
s.users[userID] = struct{}{}
|
||||
return &bizlive.Stream{StreamID: streamID, SubscriberCount: len(s.users)}, nil
|
||||
}
|
||||
|
||||
func newAnonymousTestManager(catalog bizlive.CatalogRepo, streams bizlive.StreamRepo) *Manager {
|
||||
streamUC := bizlive.NewStreamUsecase(streams)
|
||||
return &Manager{
|
||||
hub: &liveHubStub{},
|
||||
streams: streamUC,
|
||||
roomCatalog: bizlive.NewRoomUsecase(catalog, streamUC),
|
||||
rooms: make(map[string]*room),
|
||||
peers: make(map[*melody.Session]*peer),
|
||||
observers: make(map[*melody.Session]struct{}),
|
||||
stop: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnonymousSubscribersReceiveDistinctRuntimeIdentities(t *testing.T) {
|
||||
streams := &liveStreamStub{streams: map[string]*bizlive.Stream{"room-7": {StreamID: "room-7", PublisherOnline: true}}}
|
||||
manager := newAnonymousTestManager(&liveCatalogStub{room: &bizlive.Room{ID: 7, CategoryID: 1, Enabled: true}}, streams)
|
||||
first := &melody.Session{Keys: map[string]any{}}
|
||||
second := &melody.Session{Keys: map[string]any{}}
|
||||
|
||||
manager.join(first, joinPayload{Role: "subscriber", StreamID: "room-7"})
|
||||
manager.join(second, joinPayload{Role: "subscriber", StreamID: "room-7"})
|
||||
|
||||
p1, p2 := manager.peer(first), manager.peer(second)
|
||||
if p1 == nil || p2 == nil {
|
||||
t.Fatalf("anonymous subscribers did not join: first=%+v second=%+v", p1, p2)
|
||||
}
|
||||
if p1.authenticated || p2.authenticated || p1.userID == 0 || p2.userID == 0 || p1.userID == p2.userID {
|
||||
t.Fatalf("anonymous identities are not unique: first=%d second=%d", p1.userID, p2.userID)
|
||||
}
|
||||
if len(streams.users) != 2 {
|
||||
t.Fatalf("subscriber count = %d, want 2", len(streams.users))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubscriberCannotCreateUnknownCatalogRoom(t *testing.T) {
|
||||
manager := newAnonymousTestManager(&liveCatalogStub{err: bizlive.ErrRoomNotFound}, &liveStreamStub{})
|
||||
session := &melody.Session{Keys: map[string]any{}}
|
||||
|
||||
manager.join(session, joinPayload{Role: "subscriber", StreamID: "room-999999"})
|
||||
|
||||
if manager.peer(session) != nil || len(manager.rooms) != 0 {
|
||||
t.Fatalf("unknown room created runtime state: peers=%d rooms=%d", len(manager.peers), len(manager.rooms))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubscriberCannotCreateOfflineRuntimeState(t *testing.T) {
|
||||
streams := &liveStreamStub{}
|
||||
manager := newAnonymousTestManager(&liveCatalogStub{room: &bizlive.Room{ID: 7, CategoryID: 1, Enabled: true}}, streams)
|
||||
session := &melody.Session{Keys: map[string]any{}}
|
||||
|
||||
manager.join(session, joinPayload{Role: "subscriber", StreamID: "room-7"})
|
||||
|
||||
if manager.peer(session) != nil || len(manager.rooms) != 0 || len(streams.users) != 0 || streams.registerCalls != 0 {
|
||||
t.Fatalf("offline room created runtime state: peers=%d rooms=%d users=%d registrations=%d", len(manager.peers), len(manager.rooms), len(streams.users), streams.registerCalls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnonymousPublisherIsRejected(t *testing.T) {
|
||||
manager := newAnonymousTestManager(&liveCatalogStub{room: &bizlive.Room{ID: 7, Enabled: true}}, &liveStreamStub{})
|
||||
session := &melody.Session{Keys: map[string]any{}}
|
||||
|
||||
manager.join(session, joinPayload{Role: "publisher", StreamID: "room-7"})
|
||||
|
||||
if manager.peer(session) != nil {
|
||||
t.Fatal("anonymous publisher joined")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClosedSupersededPeerConnectionDoesNotRemovePeer(t *testing.T) {
|
||||
oldPC, err := webrtc.NewPeerConnection(webrtc.Configuration{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer oldPC.Close()
|
||||
newPC, err := webrtc.NewPeerConnection(webrtc.Configuration{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer newPC.Close()
|
||||
|
||||
p := &peer{pc: oldPC}
|
||||
replaced, installed := p.replacePeerConnection(newPC)
|
||||
if !installed || replaced != oldPC {
|
||||
t.Fatalf("replaced peer connection = %p, want %p", replaced, oldPC)
|
||||
}
|
||||
manager := &Manager{}
|
||||
manager.handleICEConnectionStateChange(p, oldPC, webrtc.ICEConnectionStateClosed)
|
||||
|
||||
cleanupAvailable := false
|
||||
p.cleanup.Do(func() { cleanupAvailable = true })
|
||||
if !cleanupAvailable {
|
||||
t.Fatal("superseded peer connection consumed peer cleanup")
|
||||
}
|
||||
if !p.hasPeerConnection(newPC) {
|
||||
t.Fatal("replacement peer connection is no longer active")
|
||||
}
|
||||
}
|
||||
|
||||
var _ platformws.Hub = (*liveHubStub)(nil)
|
||||
Reference in New Issue
Block a user