mirror of
https://github.com/openlibrecommunity/olcrtc.git
synced 2026-06-08 13:24:44 +00:00
test: port provider unit tests and fix API regressions from master
- Ported unit tests for WB Stream, SaluteJazz, and Telemost API clients - Ported engine helper tests for SaluteJazz and Goolom - Fixed missing WB Stream WebSocket URL update (wss://rtc-el-01.wb.ru) - Corrected SaluteJazz header casing (X-Jazz-AuthType) - Fixed linting errors (goconst, gosec, lll) in test files
This commit is contained in:
@@ -17,12 +17,12 @@ import (
|
||||
|
||||
const (
|
||||
authTypeAnonymous = "ANONYMOUS"
|
||||
headerAuthType = "X-Jazz-Authtype"
|
||||
headerAuthType = "X-Jazz-AuthType"
|
||||
headerContentType = "Content-Type"
|
||||
contentTypeJSON = "application/json"
|
||||
)
|
||||
|
||||
var apiBase = "https://bk.salutejazz.ru" //nolint:gochecknoglobals // overridable base URL for tests
|
||||
var apiBase = "https://bk.salutejazz.ru" //nolint:gochecknoglobals // package-level state intentional
|
||||
|
||||
// roomInfo contains connection details for a SaluteJazz room.
|
||||
type roomInfo struct {
|
||||
|
||||
143
internal/auth/salutejazz/api_test.go
Normal file
143
internal/auth/salutejazz/api_test.go
Normal file
@@ -0,0 +1,143 @@
|
||||
package salutejazz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/openlibrecommunity/olcrtc/internal/auth"
|
||||
)
|
||||
|
||||
func withJazzAPIServer(t *testing.T, h http.Handler) {
|
||||
t.Helper()
|
||||
old := apiBase
|
||||
srv := httptest.NewServer(h)
|
||||
t.Cleanup(func() {
|
||||
apiBase = old
|
||||
srv.Close()
|
||||
})
|
||||
apiBase = srv.URL
|
||||
}
|
||||
|
||||
func TestCreateMeetingAndPreconnect(t *testing.T) {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("POST /room/create-meeting", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get("X-Jazz-Authtype") != authTypeAnonymous {
|
||||
t.Fatalf("missing auth header: %v", r.Header)
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(createResponse{RoomID: "room-1", Password: "pass"}) //nolint:gosec
|
||||
})
|
||||
mux.HandleFunc("POST /room/room-1/preconnect", func(w http.ResponseWriter, _ *http.Request) {
|
||||
_ = json.NewEncoder(w).Encode(map[string]string{connectorURLKey: testConnector})
|
||||
})
|
||||
|
||||
withJazzAPIServer(t, mux)
|
||||
|
||||
headers := map[string]string{
|
||||
headerAuthType: authTypeAnonymous,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
created, err := createMeeting(context.Background(), headers)
|
||||
if err != nil {
|
||||
t.Fatalf("createMeeting() error = %v", err)
|
||||
}
|
||||
if created.RoomID != "room-1" || created.Password != "pass" {
|
||||
t.Fatalf("createMeeting() = %+v", created)
|
||||
}
|
||||
|
||||
connector, err := preconnect(context.Background(), "room-1", "pass", headers)
|
||||
if err != nil {
|
||||
t.Fatalf("preconnect() error = %v", err)
|
||||
}
|
||||
if connector != testConnector {
|
||||
t.Fatalf("preconnect() = %q", connector)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
testRoomID = "new-room"
|
||||
testPassword = "new-pass"
|
||||
testConnector = "wss://connector"
|
||||
connectorURLKey = "connectorUrl"
|
||||
)
|
||||
|
||||
func TestCreateRoomAndJoinRoom(t *testing.T) {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("POST /room/create-meeting", func(w http.ResponseWriter, _ *http.Request) {
|
||||
_ = json.NewEncoder(w).Encode(createResponse{RoomID: testRoomID, Password: testPassword}) //nolint:gosec
|
||||
})
|
||||
mux.HandleFunc("POST /room/{id}/preconnect", func(w http.ResponseWriter, _ *http.Request) {
|
||||
_ = json.NewEncoder(w).Encode(map[string]string{connectorURLKey: testConnector})
|
||||
})
|
||||
|
||||
withJazzAPIServer(t, mux)
|
||||
|
||||
room, err := createRoom(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("createRoom() error = %v", err)
|
||||
}
|
||||
if room.RoomID != testRoomID || room.Password != testPassword ||
|
||||
room.ConnectorURL != testConnector {
|
||||
t.Fatalf("createRoom() = %+v", room)
|
||||
}
|
||||
|
||||
room, err = joinRoom(context.Background(), "existing", "secret")
|
||||
if err != nil {
|
||||
t.Fatalf("joinRoom() error = %v", err)
|
||||
}
|
||||
if room.RoomID != "existing" || room.Password != "secret" || room.ConnectorURL != testConnector {
|
||||
t.Fatalf("joinRoom() = %+v", room)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJazzAPIErrors(t *testing.T) {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/room/create-meeting", func(w http.ResponseWriter, _ *http.Request) {
|
||||
http.Error(w, "bad", http.StatusTeapot)
|
||||
})
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) {
|
||||
http.Error(w, "bad", http.StatusInternalServerError)
|
||||
})
|
||||
|
||||
withJazzAPIServer(t, mux)
|
||||
|
||||
if _, err := createMeeting(context.Background(), nil); !errors.Is(err, errCreateRoomFailed) {
|
||||
t.Fatalf("createMeeting() error = %v, want %v", err, errCreateRoomFailed)
|
||||
}
|
||||
if _, err := preconnect(context.Background(), "room", "pass", nil); !errors.Is(err, errPreconnectFailed) {
|
||||
t.Fatalf("preconnect() error = %v, want %v", err, errPreconnectFailed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJazzIssue(t *testing.T) {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("POST /room/create-meeting", func(w http.ResponseWriter, _ *http.Request) {
|
||||
_ = json.NewEncoder(w).Encode(createResponse{RoomID: testRoomID, Password: testPassword}) //nolint:gosec
|
||||
})
|
||||
mux.HandleFunc("POST /room/{id}/preconnect", func(w http.ResponseWriter, _ *http.Request) {
|
||||
_ = json.NewEncoder(w).Encode(map[string]string{connectorURLKey: testConnector})
|
||||
})
|
||||
|
||||
withJazzAPIServer(t, mux)
|
||||
|
||||
p := Provider{}
|
||||
creds, err := p.Issue(context.Background(), auth.Config{
|
||||
RoomURL: "any",
|
||||
Name: "peer",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Issue() error = %v", err)
|
||||
}
|
||||
if creds.URL != testConnector {
|
||||
t.Fatalf("creds.URL = %q", creds.URL)
|
||||
}
|
||||
if creds.Token != testRoomID {
|
||||
t.Fatalf("creds.Token = %q", creds.Token)
|
||||
}
|
||||
if creds.Extra["password"] != testPassword {
|
||||
t.Fatalf("creds.Extra[password] = %q", creds.Extra["password"])
|
||||
}
|
||||
}
|
||||
65
internal/auth/telemost/api_test.go
Normal file
65
internal/auth/telemost/api_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package telemost
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func withTelemostAPIServer(t *testing.T, h http.Handler) {
|
||||
t.Helper()
|
||||
old := apiBase
|
||||
srv := httptest.NewServer(h)
|
||||
t.Cleanup(func() {
|
||||
apiBase = old
|
||||
srv.Close()
|
||||
})
|
||||
apiBase = srv.URL
|
||||
}
|
||||
|
||||
func TestGetConnectionInfo(t *testing.T) {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("GET /conferences/{id...}", func(w http.ResponseWriter, r *http.Request) {
|
||||
if !strings.HasPrefix(r.URL.Path, "/conferences/room/id/connection") {
|
||||
t.Fatalf("path = %q", r.URL.Path)
|
||||
}
|
||||
if r.URL.Query().Get("display_name") != "peer" {
|
||||
t.Fatalf("display_name query = %q", r.URL.Query().Get("display_name"))
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(ConnectionInfo{
|
||||
RoomID: "room",
|
||||
PeerID: "peer-id",
|
||||
Credentials: "creds",
|
||||
})
|
||||
})
|
||||
|
||||
withTelemostAPIServer(t, mux)
|
||||
|
||||
info, err := GetConnectionInfo(context.Background(), "room/id", "peer")
|
||||
if err != nil {
|
||||
t.Fatalf("GetConnectionInfo() error = %v", err)
|
||||
}
|
||||
if info.RoomID != "room" || info.PeerID != "peer-id" || info.Credentials != "creds" {
|
||||
t.Fatalf("GetConnectionInfo() = %+v", info)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetConnectionInfoErrors(t *testing.T) {
|
||||
withTelemostAPIServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
http.Error(w, "bad", http.StatusForbidden)
|
||||
}))
|
||||
if _, err := GetConnectionInfo(context.Background(), "room", "peer"); !errors.Is(err, ErrAPI) {
|
||||
t.Fatalf("GetConnectionInfo() error = %v, want %v", err, ErrAPI)
|
||||
}
|
||||
|
||||
withTelemostAPIServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
_, _ = w.Write([]byte("{"))
|
||||
}))
|
||||
if _, err := GetConnectionInfo(context.Background(), "room", "peer"); err == nil {
|
||||
t.Fatal("GetConnectionInfo() unexpectedly accepted bad json")
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,9 @@ import (
|
||||
"github.com/openlibrecommunity/olcrtc/internal/protect"
|
||||
)
|
||||
|
||||
const wsURL = "wss://wbstream01-el.wb.ru:7880"
|
||||
const wsURL = "wss://rtc-el-01.wb.ru"
|
||||
|
||||
var apiBase = "https://stream.wb.ru" //nolint:gochecknoglobals // overridable base URL for tests
|
||||
var apiBase = "https://stream.wb.ru" //nolint:gochecknoglobals // package-level state intentional
|
||||
|
||||
var (
|
||||
errGuestRegister = errors.New("guest register failed")
|
||||
|
||||
135
internal/auth/wbstream/api_test.go
Normal file
135
internal/auth/wbstream/api_test.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package wbstream
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/openlibrecommunity/olcrtc/internal/auth"
|
||||
)
|
||||
|
||||
const (
|
||||
testAccessToken = "access"
|
||||
testRoomID = "room"
|
||||
testToken = "token"
|
||||
testPeerName = "peer"
|
||||
)
|
||||
|
||||
func withWBAPIServer(t *testing.T, h http.Handler) {
|
||||
t.Helper()
|
||||
old := apiBase
|
||||
srv := httptest.NewServer(h)
|
||||
t.Cleanup(func() {
|
||||
apiBase = old
|
||||
srv.Close()
|
||||
})
|
||||
apiBase = srv.URL
|
||||
}
|
||||
|
||||
func TestWBStreamAPIHappyPath(t *testing.T) {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("POST /auth/api/v1/auth/user/guest-register", func(w http.ResponseWriter, _ *http.Request) {
|
||||
_ = json.NewEncoder(w).Encode(guestRegisterResponse{AccessToken: testAccessToken}) //nolint:gosec
|
||||
})
|
||||
mux.HandleFunc("POST /api-room/api/v2/room", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get("Authorization") != "Bearer "+testAccessToken {
|
||||
t.Fatalf("room auth = %q", r.Header.Get("Authorization"))
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
_ = json.NewEncoder(w).Encode(createRoomResponse{RoomID: testRoomID})
|
||||
})
|
||||
mux.HandleFunc("POST /api-room/api/v1/room/"+testRoomID+"/join", func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
mux.HandleFunc("GET /api-room-manager/v2/room/"+testRoomID+"/connection-details",
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Query().Get("displayName") != testPeerName {
|
||||
t.Fatalf("displayName query = %q", r.URL.Query().Get("displayName"))
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(tokenResponse{RoomToken: testToken})
|
||||
})
|
||||
|
||||
withWBAPIServer(t, mux)
|
||||
|
||||
access, err := registerGuest(context.Background(), testPeerName)
|
||||
if err != nil {
|
||||
t.Fatalf("registerGuest() error = %v", err)
|
||||
}
|
||||
if access != testAccessToken {
|
||||
t.Fatalf("registerGuest() = %q", access)
|
||||
}
|
||||
|
||||
room, err := createRoom(context.Background(), access)
|
||||
if err != nil {
|
||||
t.Fatalf("createRoom() error = %v", err)
|
||||
}
|
||||
if room != testRoomID {
|
||||
t.Fatalf("createRoom() = %q", room)
|
||||
}
|
||||
|
||||
if err := joinRoom(context.Background(), access, room); err != nil {
|
||||
t.Fatalf("joinRoom() error = %v", err)
|
||||
}
|
||||
token, err := getToken(context.Background(), access, room, testPeerName)
|
||||
if err != nil {
|
||||
t.Fatalf("getToken() error = %v", err)
|
||||
}
|
||||
if token != testToken {
|
||||
t.Fatalf("getToken() = %q", token)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWBStreamAPIErrors(t *testing.T) {
|
||||
withWBAPIServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
http.Error(w, "bad", http.StatusBadGateway)
|
||||
}))
|
||||
|
||||
if _, err := registerGuest(context.Background(), testPeerName); !errors.Is(err, errGuestRegister) {
|
||||
t.Fatalf("registerGuest() error = %v, want %v", err, errGuestRegister)
|
||||
}
|
||||
if _, err := createRoom(context.Background(), testAccessToken); !errors.Is(err, errCreateRoom) {
|
||||
t.Fatalf("createRoom() error = %v, want %v", err, errCreateRoom)
|
||||
}
|
||||
if err := joinRoom(context.Background(), testAccessToken, testRoomID); !errors.Is(err, errJoinRoom) {
|
||||
t.Fatalf("joinRoom() error = %v, want %v", err, errJoinRoom)
|
||||
}
|
||||
if _, err := getToken(context.Background(), testAccessToken, testRoomID, testPeerName); !errors.Is(err, errGetToken) {
|
||||
t.Fatalf("getToken() error = %v, want %v", err, errGetToken)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWBStreamIssue(t *testing.T) {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("POST /auth/api/v1/auth/user/guest-register", func(w http.ResponseWriter, _ *http.Request) {
|
||||
_ = json.NewEncoder(w).Encode(guestRegisterResponse{AccessToken: testAccessToken}) //nolint:gosec
|
||||
})
|
||||
mux.HandleFunc("POST /api-room/api/v2/room", func(w http.ResponseWriter, _ *http.Request) {
|
||||
_ = json.NewEncoder(w).Encode(createRoomResponse{RoomID: "created"})
|
||||
})
|
||||
mux.HandleFunc("POST /api-room/api/v1/room/{id}/join", func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
mux.HandleFunc("GET /api-room-manager/v2/room/{id}/connection-details", func(w http.ResponseWriter, _ *http.Request) {
|
||||
_ = json.NewEncoder(w).Encode(tokenResponse{RoomToken: testToken})
|
||||
})
|
||||
|
||||
withWBAPIServer(t, mux)
|
||||
|
||||
p := Provider{}
|
||||
creds, err := p.Issue(context.Background(), auth.Config{
|
||||
RoomURL: "any",
|
||||
Name: testPeerName,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Issue() error = %v", err)
|
||||
}
|
||||
if creds.Token != testToken {
|
||||
t.Fatalf("creds.Token = %q", creds.Token)
|
||||
}
|
||||
if creds.Extra["roomID"] != "created" {
|
||||
t.Fatalf("creds.Extra[roomID] = %q", creds.Extra["roomID"])
|
||||
}
|
||||
}
|
||||
85
internal/engine/goolom/session_helpers_test.go
Normal file
85
internal/engine/goolom/session_helpers_test.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package goolom
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
//nolint:cyclop // table-driven test naturally has many branches
|
||||
func TestSessionReconnectAndEndedHelpers(t *testing.T) {
|
||||
s := &Session{
|
||||
reconnectCh: make(chan struct{}, 2),
|
||||
closeCh: make(chan struct{}),
|
||||
keepAliveCh: make(chan struct{}),
|
||||
sessionCloseCh: make(chan struct{}),
|
||||
telemetryCh: make(chan struct{}, 1),
|
||||
}
|
||||
|
||||
keepAliveCh, sessionCloseCh := s.resetSession()
|
||||
if keepAliveCh == nil || sessionCloseCh == nil || keepAliveCh != s.keepAliveCh || sessionCloseCh != s.sessionCloseCh {
|
||||
t.Fatal("resetSession() did not replace session channels")
|
||||
}
|
||||
|
||||
s.subscriberReady.Store(true)
|
||||
s.publisherReady.Store(true)
|
||||
s.resetMediaState()
|
||||
if s.subscriberReady.Load() || s.publisherReady.Load() || s.subscriberConn == nil || s.publisherConn == nil {
|
||||
t.Fatal("resetMediaState() did not reset readiness")
|
||||
}
|
||||
|
||||
s.queueReconnect()
|
||||
select {
|
||||
case <-s.reconnectCh:
|
||||
default:
|
||||
t.Fatal("queueReconnect() did not enqueue")
|
||||
}
|
||||
|
||||
s.SetShouldReconnect(func() bool { return false })
|
||||
s.queueReconnect()
|
||||
select {
|
||||
case <-s.reconnectCh:
|
||||
t.Fatal("queueReconnect() enqueued despite policy=false")
|
||||
default:
|
||||
}
|
||||
|
||||
s.reconnectCh <- struct{}{}
|
||||
s.reconnectCh <- struct{}{}
|
||||
s.drainReconnectQueue()
|
||||
select {
|
||||
case <-s.reconnectCh:
|
||||
t.Fatal("drainReconnectQueue() left queued item")
|
||||
default:
|
||||
}
|
||||
|
||||
s.telemetryActive.Store(true)
|
||||
s.stopTelemetry()
|
||||
select {
|
||||
case <-s.telemetryCh:
|
||||
default:
|
||||
t.Fatal("stopTelemetry() did not signal active telemetry")
|
||||
}
|
||||
|
||||
ended := ""
|
||||
s.SetEndedCallback(func(reason string) { ended = reason })
|
||||
s.signalEnded("done")
|
||||
if !s.closed.Load() || ended != "done" {
|
||||
t.Fatalf("signalEnded() closed=%v reason=%q", s.closed.Load(), ended)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWaitForAckTimeoutAndClose(t *testing.T) {
|
||||
s := &Session{
|
||||
closeCh: make(chan struct{}),
|
||||
ackWaiters: make(map[string]chan struct{}),
|
||||
}
|
||||
ch := s.registerAckWaiter("timeout")
|
||||
if s.waitForAck("timeout", ch, time.Millisecond) {
|
||||
t.Fatal("waitForAck(timeout) = true")
|
||||
}
|
||||
|
||||
ch = s.registerAckWaiter("closed")
|
||||
close(s.closeCh)
|
||||
if s.waitForAck("closed", ch, time.Second) {
|
||||
t.Fatal("waitForAck(closeCh) = true")
|
||||
}
|
||||
}
|
||||
70
internal/engine/salutejazz/datapacket_test.go
Normal file
70
internal/engine/salutejazz/datapacket_test.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package salutejazz
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDataPacketRoundTrip(t *testing.T) {
|
||||
payload := []byte("hello jazz")
|
||||
raw := EncodeDataPacket(payload)
|
||||
|
||||
got, ok := DecodeDataPacket(raw)
|
||||
if !ok {
|
||||
t.Fatal("DecodeDataPacket() ok = false")
|
||||
}
|
||||
if !bytes.Equal(got, payload) {
|
||||
t.Fatalf("DecodeDataPacket() = %q, want %q", got, payload)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeDataPacketRejectsMalformedPackets(t *testing.T) {
|
||||
tests := [][]byte{
|
||||
nil,
|
||||
{0xff},
|
||||
encodeField(1, 0, encodeVarint(0)),
|
||||
{byte(2<<3 | 2), 10, 1},
|
||||
{byte(3<<3 | 7), 0},
|
||||
}
|
||||
|
||||
for _, raw := range tests {
|
||||
if payload, ok := DecodeDataPacket(raw); ok {
|
||||
t.Fatalf("DecodeDataPacket(%v) = (%q, true), want false", raw, payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFieldsSkipsSupportedNonTargetWireTypes(t *testing.T) {
|
||||
data := encodeField(1, 0, encodeVarint(150))
|
||||
data = append(data, encodeField(3, 1, []byte("12345678"))...)
|
||||
data = append(data, encodeField(4, 5, []byte("1234"))...)
|
||||
data = append(data, encodeField(2, 2, []byte("target"))...)
|
||||
|
||||
got, ok := parseFields(data, 2)
|
||||
if !ok || string(got) != "target" {
|
||||
t.Fatalf("parseFields() = (%q, %v), want target", got, ok)
|
||||
}
|
||||
}
|
||||
|
||||
func TestByteReader(t *testing.T) {
|
||||
r := &byteReader{data: []byte{1, 2, 3}}
|
||||
b, err := r.ReadByte()
|
||||
if err != nil || b != 1 {
|
||||
t.Fatalf("ReadByte() = (%d, %v), want (1, nil)", b, err)
|
||||
}
|
||||
|
||||
buf := make([]byte, 4)
|
||||
n, err := r.Read(buf)
|
||||
if err != nil || n != 2 || !bytes.Equal(buf[:n], []byte{2, 3}) {
|
||||
t.Fatalf("Read() = (%d, %v, %v), want two bytes", n, err, buf[:n])
|
||||
}
|
||||
|
||||
if _, err := r.ReadByte(); !errors.Is(err, io.EOF) {
|
||||
t.Fatalf("ReadByte() error = %v, want EOF", err)
|
||||
}
|
||||
if n, err := r.Read(buf); !errors.Is(err, io.EOF) || n != 0 {
|
||||
t.Fatalf("Read() = (%d, %v), want (0, EOF)", n, err)
|
||||
}
|
||||
}
|
||||
112
internal/engine/salutejazz/session_helpers_test.go
Normal file
112
internal/engine/salutejazz/session_helpers_test.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package salutejazz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/pion/webrtc/v4"
|
||||
)
|
||||
|
||||
//nolint:cyclop // table-driven test naturally has many branches
|
||||
func TestSessionStateHelpers(t *testing.T) {
|
||||
s := &Session{
|
||||
reconnectCh: make(chan struct{}, 1),
|
||||
closeCh: make(chan struct{}),
|
||||
sessionCloseCh: make(chan struct{}),
|
||||
sendQueue: make(chan []byte, 1),
|
||||
subscriberConn: make(chan struct{}),
|
||||
publisherConn: make(chan struct{}),
|
||||
}
|
||||
|
||||
s.resetMediaState()
|
||||
if s.subscriberReady.Load() || s.publisherReady.Load() || s.subscriberConn == nil || s.publisherConn == nil {
|
||||
t.Fatal("resetMediaState() did not reset readiness")
|
||||
}
|
||||
if s.hasLocalVideoTracks() {
|
||||
t.Fatal("hasLocalVideoTracks() = true without tracks")
|
||||
}
|
||||
if err := s.AddVideoTrack(nil); err != nil {
|
||||
t.Fatalf("AddVideoTrack(nil) error = %v", err)
|
||||
}
|
||||
if !s.hasLocalVideoTracks() {
|
||||
t.Fatal("hasLocalVideoTracks() = false after AddVideoTrack")
|
||||
}
|
||||
|
||||
s.SetVideoTrackHandler(func(*webrtc.TrackRemote, *webrtc.RTPReceiver) {})
|
||||
if s.videoTrackHandler() == nil {
|
||||
t.Fatal("videoTrackHandler() = nil")
|
||||
}
|
||||
|
||||
cfg := defaultWebRTCConfig()
|
||||
if cfg.SDPSemantics != webrtc.SDPSemanticsUnifiedPlan || cfg.BundlePolicy != webrtc.BundlePolicyMaxBundle {
|
||||
t.Fatalf("defaultWebRTCConfig() = %+v", cfg)
|
||||
}
|
||||
if s.buildAPI() == nil {
|
||||
t.Fatal("buildAPI() returned nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionCallbacksQueueReconnectAndClose(t *testing.T) {
|
||||
s := &Session{
|
||||
reconnectCh: make(chan struct{}, 1),
|
||||
closeCh: make(chan struct{}),
|
||||
sessionCloseCh: make(chan struct{}),
|
||||
sendQueue: make(chan []byte, 1),
|
||||
}
|
||||
|
||||
s.SetReconnectCallback(func(*webrtc.DataChannel) {})
|
||||
s.SetShouldReconnect(func() bool { return true })
|
||||
s.SetEndedCallback(func(string) {})
|
||||
if s.onReconnect == nil || s.shouldReconnect == nil || s.onEnded == nil {
|
||||
t.Fatal("callbacks were not stored")
|
||||
}
|
||||
|
||||
s.queueReconnect()
|
||||
select {
|
||||
case <-s.reconnectCh:
|
||||
default:
|
||||
t.Fatal("queueReconnect() did not enqueue")
|
||||
}
|
||||
|
||||
s.SetShouldReconnect(func() bool { return false })
|
||||
s.queueReconnect()
|
||||
select {
|
||||
case <-s.reconnectCh:
|
||||
t.Fatal("queueReconnect() enqueued despite policy=false")
|
||||
default:
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
s.WatchConnection(context.Background())
|
||||
close(done)
|
||||
}()
|
||||
if err := s.Close(); err != nil {
|
||||
t.Fatalf("Close() error = %v", err)
|
||||
}
|
||||
<-done
|
||||
if err := s.Send([]byte("closed")); !errors.Is(err, ErrDataChannelNotReady) {
|
||||
t.Fatalf("Send() error = %v, want datachannel not ready", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionCanSendVideoOnlyModes(t *testing.T) {
|
||||
s := &Session{sendQueue: make(chan []byte, 1)}
|
||||
s.subscriberReady.Store(true)
|
||||
if !s.CanSend() {
|
||||
t.Fatal("CanSend() = false for subscriber-ready session without local video")
|
||||
}
|
||||
_ = s.AddVideoTrack(nil)
|
||||
if s.CanSend() {
|
||||
t.Fatal("CanSend() = true with local video but publisher not ready")
|
||||
}
|
||||
s.publisherReady.Store(true)
|
||||
if !s.CanSend() {
|
||||
t.Fatal("CanSend() = false with subscriber and publisher ready")
|
||||
}
|
||||
s.closed.Store(true)
|
||||
if s.CanSend() {
|
||||
t.Fatal("CanSend() = true for closed session")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user