mirror of
https://github.com/strukturag/nextcloud-spreed-signaling
synced 2024-05-08 08:36:32 +02:00
20cc51c2fe
This allows configuring the same list of targets for all instances without having to setup the "own" address differently for each server.
1442 lines
38 KiB
Go
1442 lines
38 KiB
Go
/**
|
|
* Standalone signaling server for the Nextcloud Spreed app.
|
|
* Copyright (C) 2017 struktur AG
|
|
*
|
|
* @author Joachim Bauch <bauch@struktur.de>
|
|
*
|
|
* @license GNU AGPL version 3 or any later version
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package signaling
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/hmac"
|
|
"crypto/sha1"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/dlintw/goconf"
|
|
"github.com/gorilla/mux"
|
|
"github.com/gorilla/websocket"
|
|
)
|
|
|
|
var (
|
|
turnApiKey = "TheApiKey"
|
|
turnSecret = "TheTurnSecret"
|
|
turnServersString = "turn:1.2.3.4:9991?transport=udp,turn:1.2.3.4:9991?transport=tcp"
|
|
turnServers = strings.Split(turnServersString, ",")
|
|
)
|
|
|
|
func CreateBackendServerForTest(t *testing.T) (*goconf.ConfigFile, *BackendServer, AsyncEvents, *Hub, *mux.Router, *httptest.Server) {
|
|
return CreateBackendServerForTestFromConfig(t, nil)
|
|
}
|
|
|
|
func CreateBackendServerForTestWithTurn(t *testing.T) (*goconf.ConfigFile, *BackendServer, AsyncEvents, *Hub, *mux.Router, *httptest.Server) {
|
|
config := goconf.NewConfigFile()
|
|
config.AddOption("turn", "apikey", turnApiKey)
|
|
config.AddOption("turn", "secret", turnSecret)
|
|
config.AddOption("turn", "servers", turnServersString)
|
|
return CreateBackendServerForTestFromConfig(t, config)
|
|
}
|
|
|
|
func CreateBackendServerForTestFromConfig(t *testing.T, config *goconf.ConfigFile) (*goconf.ConfigFile, *BackendServer, AsyncEvents, *Hub, *mux.Router, *httptest.Server) {
|
|
r := mux.NewRouter()
|
|
registerBackendHandler(t, r)
|
|
|
|
server := httptest.NewServer(r)
|
|
t.Cleanup(func() {
|
|
server.Close()
|
|
})
|
|
if config == nil {
|
|
config = goconf.NewConfigFile()
|
|
}
|
|
u, err := url.Parse(server.URL)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
config.AddOption("backend", "allowed", u.Host)
|
|
if u.Scheme == "http" {
|
|
config.AddOption("backend", "allowhttp", "true")
|
|
}
|
|
config.AddOption("backend", "secret", string(testBackendSecret))
|
|
config.AddOption("sessions", "hashkey", "12345678901234567890123456789012")
|
|
config.AddOption("sessions", "blockkey", "09876543210987654321098765432109")
|
|
config.AddOption("clients", "internalsecret", string(testInternalSecret))
|
|
config.AddOption("geoip", "url", "none")
|
|
events := getAsyncEventsForTest(t)
|
|
hub, err := NewHub(config, events, nil, nil, r, "no-version")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
b, err := NewBackendServer(config, hub, "no-version")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := b.Start(r); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
go hub.Run()
|
|
|
|
t.Cleanup(func() {
|
|
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
|
defer cancel()
|
|
|
|
WaitForHub(ctx, t, hub)
|
|
})
|
|
|
|
return config, b, events, hub, r, server
|
|
}
|
|
|
|
func CreateBackendServerWithClusteringForTest(t *testing.T) (*BackendServer, *BackendServer, *Hub, *Hub, *httptest.Server, *httptest.Server) {
|
|
return CreateBackendServerWithClusteringForTestFromConfig(t, nil, nil)
|
|
}
|
|
|
|
func CreateBackendServerWithClusteringForTestFromConfig(t *testing.T, config1 *goconf.ConfigFile, config2 *goconf.ConfigFile) (*BackendServer, *BackendServer, *Hub, *Hub, *httptest.Server, *httptest.Server) {
|
|
r1 := mux.NewRouter()
|
|
registerBackendHandler(t, r1)
|
|
|
|
server1 := httptest.NewServer(r1)
|
|
t.Cleanup(func() {
|
|
server1.Close()
|
|
})
|
|
|
|
r2 := mux.NewRouter()
|
|
registerBackendHandler(t, r2)
|
|
|
|
server2 := httptest.NewServer(r2)
|
|
t.Cleanup(func() {
|
|
server2.Close()
|
|
})
|
|
|
|
nats := startLocalNatsServer(t)
|
|
grpcServer1, addr1 := NewGrpcServerForTest(t)
|
|
grpcServer2, addr2 := NewGrpcServerForTest(t)
|
|
|
|
if config1 == nil {
|
|
config1 = goconf.NewConfigFile()
|
|
}
|
|
u1, err := url.Parse(server1.URL)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
config1.AddOption("backend", "allowed", u1.Host)
|
|
if u1.Scheme == "http" {
|
|
config1.AddOption("backend", "allowhttp", "true")
|
|
}
|
|
config1.AddOption("backend", "secret", string(testBackendSecret))
|
|
config1.AddOption("sessions", "hashkey", "12345678901234567890123456789012")
|
|
config1.AddOption("sessions", "blockkey", "09876543210987654321098765432109")
|
|
config1.AddOption("clients", "internalsecret", string(testInternalSecret))
|
|
config1.AddOption("geoip", "url", "none")
|
|
|
|
events1, err := NewAsyncEvents(nats)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() {
|
|
events1.Close()
|
|
})
|
|
client1 := NewGrpcClientsForTest(t, addr2)
|
|
hub1, err := NewHub(config1, events1, grpcServer1, client1, r1, "no-version")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if config2 == nil {
|
|
config2 = goconf.NewConfigFile()
|
|
}
|
|
u2, err := url.Parse(server2.URL)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
config2.AddOption("backend", "allowed", u2.Host)
|
|
if u2.Scheme == "http" {
|
|
config2.AddOption("backend", "allowhttp", "true")
|
|
}
|
|
config2.AddOption("backend", "secret", string(testBackendSecret))
|
|
config2.AddOption("sessions", "hashkey", "12345678901234567890123456789012")
|
|
config2.AddOption("sessions", "blockkey", "09876543210987654321098765432109")
|
|
config2.AddOption("clients", "internalsecret", string(testInternalSecret))
|
|
config2.AddOption("geoip", "url", "none")
|
|
events2, err := NewAsyncEvents(nats)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() {
|
|
events2.Close()
|
|
})
|
|
client2 := NewGrpcClientsForTest(t, addr1)
|
|
hub2, err := NewHub(config2, events2, grpcServer2, client2, r2, "no-version")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
b1, err := NewBackendServer(config1, hub1, "no-version")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := b1.Start(r1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
b2, err := NewBackendServer(config2, hub2, "no-version")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := b2.Start(r2); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
go hub1.Run()
|
|
go hub2.Run()
|
|
|
|
t.Cleanup(func() {
|
|
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
|
defer cancel()
|
|
|
|
WaitForHub(ctx, t, hub1)
|
|
WaitForHub(ctx, t, hub2)
|
|
})
|
|
|
|
return b1, b2, hub1, hub2, server1, server2
|
|
}
|
|
|
|
func performBackendRequest(url string, body []byte) (*http.Response, error) {
|
|
request, err := http.NewRequest("POST", url, bytes.NewReader(body))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
request.Header.Set("Content-Type", "application/json")
|
|
rnd := newRandomString(32)
|
|
check := CalculateBackendChecksum(rnd, body, testBackendSecret)
|
|
request.Header.Set("Spreed-Signaling-Random", rnd)
|
|
request.Header.Set("Spreed-Signaling-Checksum", check)
|
|
request.Header.Set("Spreed-Signaling-Backend", url)
|
|
client := &http.Client{}
|
|
return client.Do(request)
|
|
}
|
|
|
|
func expectRoomlistEvent(ch chan *AsyncMessage, msgType string) (*EventServerMessage, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
select {
|
|
case message := <-ch:
|
|
if message.Type != "message" || message.Message == nil {
|
|
return nil, fmt.Errorf("Expected message type message, got %+v", message)
|
|
}
|
|
|
|
msg := message.Message
|
|
if msg.Type != "event" || msg.Event == nil {
|
|
return nil, fmt.Errorf("Expected message type event, got %+v", msg)
|
|
}
|
|
if msg.Event.Target != "roomlist" || msg.Event.Type != msgType {
|
|
return nil, fmt.Errorf("Expected roomlist %s event, got %+v", msgType, msg.Event)
|
|
}
|
|
return msg.Event, nil
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
}
|
|
}
|
|
|
|
func TestBackendServer_NoAuth(t *testing.T) {
|
|
_, _, _, _, _, server := CreateBackendServerForTest(t)
|
|
|
|
roomId := "the-room-id"
|
|
data := []byte{'{', '}'}
|
|
request, err := http.NewRequest("POST", server.URL+"/api/v1/room/"+roomId, bytes.NewReader(data))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
request.Header.Set("Content-Type", "application/json")
|
|
client := &http.Client{}
|
|
res, err := client.Do(request)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
defer res.Body.Close()
|
|
body, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if res.StatusCode != http.StatusForbidden {
|
|
t.Errorf("Expected error response, got %s: %s", res.Status, string(body))
|
|
}
|
|
}
|
|
|
|
func TestBackendServer_InvalidAuth(t *testing.T) {
|
|
_, _, _, _, _, server := CreateBackendServerForTest(t)
|
|
|
|
roomId := "the-room-id"
|
|
data := []byte{'{', '}'}
|
|
request, err := http.NewRequest("POST", server.URL+"/api/v1/room/"+roomId, bytes.NewReader(data))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
request.Header.Set("Content-Type", "application/json")
|
|
request.Header.Set("Spreed-Signaling-Random", "hello")
|
|
request.Header.Set("Spreed-Signaling-Checksum", "world")
|
|
client := &http.Client{}
|
|
res, err := client.Do(request)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
defer res.Body.Close()
|
|
body, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if res.StatusCode != http.StatusForbidden {
|
|
t.Errorf("Expected error response, got %s: %s", res.Status, string(body))
|
|
}
|
|
}
|
|
|
|
func TestBackendServer_OldCompatAuth(t *testing.T) {
|
|
_, _, _, _, _, server := CreateBackendServerForTest(t)
|
|
|
|
roomId := "the-room-id"
|
|
userid := "the-user-id"
|
|
roomProperties := json.RawMessage("{\"foo\":\"bar\"}")
|
|
msg := &BackendServerRoomRequest{
|
|
Type: "invite",
|
|
Invite: &BackendRoomInviteRequest{
|
|
UserIds: []string{
|
|
userid,
|
|
},
|
|
AllUserIds: []string{
|
|
userid,
|
|
},
|
|
Properties: &roomProperties,
|
|
},
|
|
}
|
|
|
|
data, err := json.Marshal(msg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
request, err := http.NewRequest("POST", server.URL+"/api/v1/room/"+roomId, bytes.NewReader(data))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
request.Header.Set("Content-Type", "application/json")
|
|
rnd := newRandomString(32)
|
|
check := CalculateBackendChecksum(rnd, data, testBackendSecret)
|
|
request.Header.Set("Spreed-Signaling-Random", rnd)
|
|
request.Header.Set("Spreed-Signaling-Checksum", check)
|
|
client := &http.Client{}
|
|
res, err := client.Do(request)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
defer res.Body.Close()
|
|
body, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if res.StatusCode != http.StatusOK {
|
|
t.Errorf("Expected success, got %s: %s", res.Status, string(body))
|
|
}
|
|
}
|
|
|
|
func TestBackendServer_InvalidBody(t *testing.T) {
|
|
_, _, _, _, _, server := CreateBackendServerForTest(t)
|
|
|
|
roomId := "the-room-id"
|
|
data := []byte{1, 2, 3, 4} // Invalid JSON
|
|
res, err := performBackendRequest(server.URL+"/api/v1/room/"+roomId, data)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer res.Body.Close()
|
|
body, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if res.StatusCode != http.StatusBadRequest {
|
|
t.Errorf("Expected error response, got %s: %s", res.Status, string(body))
|
|
}
|
|
}
|
|
|
|
func TestBackendServer_UnsupportedRequest(t *testing.T) {
|
|
_, _, _, _, _, server := CreateBackendServerForTest(t)
|
|
|
|
msg := &BackendServerRoomRequest{
|
|
Type: "lala",
|
|
}
|
|
|
|
data, err := json.Marshal(msg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
roomId := "the-room-id"
|
|
res, err := performBackendRequest(server.URL+"/api/v1/room/"+roomId, data)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer res.Body.Close()
|
|
body, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if res.StatusCode != http.StatusBadRequest {
|
|
t.Errorf("Expected error response, got %s: %s", res.Status, string(body))
|
|
}
|
|
}
|
|
|
|
func TestBackendServer_RoomInvite(t *testing.T) {
|
|
for _, backend := range eventBackendsForTest {
|
|
t.Run(backend, func(t *testing.T) {
|
|
RunTestBackendServer_RoomInvite(t)
|
|
})
|
|
}
|
|
}
|
|
|
|
type channelEventListener struct {
|
|
ch chan *AsyncMessage
|
|
}
|
|
|
|
func (l *channelEventListener) ProcessAsyncUserMessage(message *AsyncMessage) {
|
|
l.ch <- message
|
|
}
|
|
|
|
func RunTestBackendServer_RoomInvite(t *testing.T) {
|
|
_, _, events, hub, _, server := CreateBackendServerForTest(t)
|
|
|
|
u, err := url.Parse(server.URL)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
userid := "test-userid"
|
|
roomProperties := json.RawMessage("{\"foo\":\"bar\"}")
|
|
backend := hub.backend.GetBackend(u)
|
|
|
|
eventsChan := make(chan *AsyncMessage, 1)
|
|
listener := &channelEventListener{
|
|
ch: eventsChan,
|
|
}
|
|
if err := events.RegisterUserListener(userid, backend, listener); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer events.UnregisterUserListener(userid, backend, listener)
|
|
|
|
msg := &BackendServerRoomRequest{
|
|
Type: "invite",
|
|
Invite: &BackendRoomInviteRequest{
|
|
UserIds: []string{
|
|
userid,
|
|
},
|
|
AllUserIds: []string{
|
|
userid,
|
|
},
|
|
Properties: &roomProperties,
|
|
},
|
|
}
|
|
|
|
data, err := json.Marshal(msg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
roomId := "the-room-id"
|
|
res, err := performBackendRequest(server.URL+"/api/v1/room/"+roomId, data)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer res.Body.Close()
|
|
body, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if res.StatusCode != 200 {
|
|
t.Errorf("Expected successful request, got %s: %s", res.Status, string(body))
|
|
}
|
|
|
|
event, err := expectRoomlistEvent(eventsChan, "invite")
|
|
if err != nil {
|
|
t.Error(err)
|
|
} else if event.Invite == nil {
|
|
t.Errorf("Expected invite, got %+v", event)
|
|
} else if event.Invite.RoomId != roomId {
|
|
t.Errorf("Expected room %s, got %+v", roomId, event)
|
|
} else if event.Invite.Properties == nil || !bytes.Equal(*event.Invite.Properties, roomProperties) {
|
|
t.Errorf("Room properties don't match: expected %s, got %s", string(roomProperties), string(*event.Invite.Properties))
|
|
}
|
|
}
|
|
|
|
func TestBackendServer_RoomDisinvite(t *testing.T) {
|
|
for _, backend := range eventBackendsForTest {
|
|
t.Run(backend, func(t *testing.T) {
|
|
RunTestBackendServer_RoomDisinvite(t)
|
|
})
|
|
}
|
|
}
|
|
|
|
func RunTestBackendServer_RoomDisinvite(t *testing.T) {
|
|
_, _, events, hub, _, server := CreateBackendServerForTest(t)
|
|
|
|
u, err := url.Parse(server.URL)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
backend := hub.backend.GetBackend(u)
|
|
|
|
client := NewTestClient(t, server, hub)
|
|
defer client.CloseWithBye()
|
|
if err := client.SendHello(testDefaultUserId); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
|
defer cancel()
|
|
|
|
hello, err := client.RunUntilHello(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Join room by id.
|
|
roomId := "test-room"
|
|
if room, err := client.JoinRoom(ctx, roomId); err != nil {
|
|
t.Fatal(err)
|
|
} else if room.Room.RoomId != roomId {
|
|
t.Fatalf("Expected room %s, got %s", roomId, room.Room.RoomId)
|
|
}
|
|
|
|
// Ignore "join" events.
|
|
if err := client.DrainMessages(ctx); err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
roomProperties := json.RawMessage("{\"foo\":\"bar\"}")
|
|
|
|
eventsChan := make(chan *AsyncMessage, 1)
|
|
listener := &channelEventListener{
|
|
ch: eventsChan,
|
|
}
|
|
if err := events.RegisterUserListener(testDefaultUserId, backend, listener); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer events.UnregisterUserListener(testDefaultUserId, backend, listener)
|
|
|
|
msg := &BackendServerRoomRequest{
|
|
Type: "disinvite",
|
|
Disinvite: &BackendRoomDisinviteRequest{
|
|
UserIds: []string{
|
|
testDefaultUserId,
|
|
},
|
|
SessionIds: []string{
|
|
roomId + "-" + hello.Hello.SessionId,
|
|
},
|
|
AllUserIds: []string{},
|
|
Properties: &roomProperties,
|
|
},
|
|
}
|
|
|
|
data, err := json.Marshal(msg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
res, err := performBackendRequest(server.URL+"/api/v1/room/"+roomId, data)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer res.Body.Close()
|
|
body, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if res.StatusCode != 200 {
|
|
t.Errorf("Expected successful request, got %s: %s", res.Status, string(body))
|
|
}
|
|
|
|
event, err := expectRoomlistEvent(eventsChan, "disinvite")
|
|
if err != nil {
|
|
t.Error(err)
|
|
} else if event.Disinvite == nil {
|
|
t.Errorf("Expected disinvite, got %+v", event)
|
|
} else if event.Disinvite.RoomId != roomId {
|
|
t.Errorf("Expected room %s, got %+v", roomId, event)
|
|
} else if event.Disinvite.Properties != nil {
|
|
t.Errorf("Room properties should be omitted, got %s", string(*event.Disinvite.Properties))
|
|
} else if event.Disinvite.Reason != "disinvited" {
|
|
t.Errorf("Reason should be disinvited, got %s", event.Disinvite.Reason)
|
|
}
|
|
|
|
if message, err := client.RunUntilRoomlistDisinvite(ctx); err != nil {
|
|
t.Error(err)
|
|
} else if message.RoomId != roomId {
|
|
t.Errorf("Expected message for room %s, got %s", roomId, message.RoomId)
|
|
}
|
|
|
|
if message, err := client.RunUntilMessage(ctx); err != nil && !websocket.IsCloseError(err, websocket.CloseNoStatusReceived) {
|
|
t.Errorf("Received unexpected error %s", err)
|
|
} else if err == nil {
|
|
t.Errorf("Server should have closed the connection, received %+v", *message)
|
|
}
|
|
}
|
|
|
|
func TestBackendServer_RoomDisinviteDifferentRooms(t *testing.T) {
|
|
_, _, _, hub, _, server := CreateBackendServerForTest(t)
|
|
|
|
client1 := NewTestClient(t, server, hub)
|
|
defer client1.CloseWithBye()
|
|
if err := client1.SendHello(testDefaultUserId); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
client2 := NewTestClient(t, server, hub)
|
|
defer client2.CloseWithBye()
|
|
if err := client2.SendHello(testDefaultUserId); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
|
defer cancel()
|
|
|
|
hello1, err := client1.RunUntilHello(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := client2.RunUntilHello(ctx); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Join room by id.
|
|
roomId1 := "test-room1"
|
|
if _, err := client1.JoinRoom(ctx, roomId1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
roomId2 := "test-room2"
|
|
if _, err := client2.JoinRoom(ctx, roomId2); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Ignore "join" events.
|
|
if err := client1.DrainMessages(ctx); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if err := client2.DrainMessages(ctx); err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
msg := &BackendServerRoomRequest{
|
|
Type: "disinvite",
|
|
Disinvite: &BackendRoomDisinviteRequest{
|
|
UserIds: []string{
|
|
testDefaultUserId,
|
|
},
|
|
SessionIds: []string{
|
|
roomId1 + "-" + hello1.Hello.SessionId,
|
|
},
|
|
AllUserIds: []string{},
|
|
},
|
|
}
|
|
|
|
data, err := json.Marshal(msg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
res, err := performBackendRequest(server.URL+"/api/v1/room/"+roomId1, data)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer res.Body.Close()
|
|
body, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if res.StatusCode != 200 {
|
|
t.Errorf("Expected successful request, got %s: %s", res.Status, string(body))
|
|
}
|
|
|
|
if message, err := client1.RunUntilRoomlistDisinvite(ctx); err != nil {
|
|
t.Error(err)
|
|
} else if message.RoomId != roomId1 {
|
|
t.Errorf("Expected message for room %s, got %s", roomId1, message.RoomId)
|
|
}
|
|
|
|
if message, err := client1.RunUntilMessage(ctx); err != nil && !websocket.IsCloseError(err, websocket.CloseNoStatusReceived) {
|
|
t.Errorf("Received unexpected error %s", err)
|
|
} else if err == nil {
|
|
t.Errorf("Server should have closed the connection, received %+v", *message)
|
|
}
|
|
|
|
if message, err := client2.RunUntilRoomlistDisinvite(ctx); err != nil {
|
|
t.Error(err)
|
|
} else if message.RoomId != roomId1 {
|
|
t.Errorf("Expected message for room %s, got %s", roomId1, message.RoomId)
|
|
}
|
|
|
|
msg = &BackendServerRoomRequest{
|
|
Type: "update",
|
|
Update: &BackendRoomUpdateRequest{
|
|
UserIds: []string{
|
|
testDefaultUserId,
|
|
},
|
|
},
|
|
}
|
|
|
|
data, err = json.Marshal(msg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
res, err = performBackendRequest(server.URL+"/api/v1/room/"+roomId2, data)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer res.Body.Close()
|
|
body, err = io.ReadAll(res.Body)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if res.StatusCode != 200 {
|
|
t.Errorf("Expected successful request, got %s: %s", res.Status, string(body))
|
|
}
|
|
|
|
if message, err := client2.RunUntilRoomlistUpdate(ctx); err != nil {
|
|
t.Error(err)
|
|
} else if message.RoomId != roomId2 {
|
|
t.Errorf("Expected message for room %s, got %s", roomId2, message.RoomId)
|
|
}
|
|
}
|
|
|
|
func TestBackendServer_RoomUpdate(t *testing.T) {
|
|
for _, backend := range eventBackendsForTest {
|
|
t.Run(backend, func(t *testing.T) {
|
|
RunTestBackendServer_RoomUpdate(t)
|
|
})
|
|
}
|
|
}
|
|
|
|
func RunTestBackendServer_RoomUpdate(t *testing.T) {
|
|
_, _, events, hub, _, server := CreateBackendServerForTest(t)
|
|
|
|
u, err := url.Parse(server.URL)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
roomId := "the-room-id"
|
|
emptyProperties := json.RawMessage("{}")
|
|
backend := hub.backend.GetBackend(u)
|
|
if backend == nil {
|
|
t.Fatalf("Did not find backend")
|
|
}
|
|
room, err := hub.createRoom(roomId, &emptyProperties, backend)
|
|
if err != nil {
|
|
t.Fatalf("Could not create room: %s", err)
|
|
}
|
|
defer room.Close()
|
|
|
|
userid := "test-userid"
|
|
roomProperties := json.RawMessage("{\"foo\":\"bar\"}")
|
|
|
|
eventsChan := make(chan *AsyncMessage, 1)
|
|
listener := &channelEventListener{
|
|
ch: eventsChan,
|
|
}
|
|
if err := events.RegisterUserListener(userid, backend, listener); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer events.UnregisterUserListener(userid, backend, listener)
|
|
|
|
msg := &BackendServerRoomRequest{
|
|
Type: "update",
|
|
Update: &BackendRoomUpdateRequest{
|
|
UserIds: []string{
|
|
userid,
|
|
},
|
|
Properties: &roomProperties,
|
|
},
|
|
}
|
|
|
|
data, err := json.Marshal(msg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
res, err := performBackendRequest(server.URL+"/api/v1/room/"+roomId, data)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer res.Body.Close()
|
|
body, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if res.StatusCode != 200 {
|
|
t.Errorf("Expected successful request, got %s: %s", res.Status, string(body))
|
|
}
|
|
|
|
event, err := expectRoomlistEvent(eventsChan, "update")
|
|
if err != nil {
|
|
t.Error(err)
|
|
} else if event.Update == nil {
|
|
t.Errorf("Expected update, got %+v", event)
|
|
} else if event.Update.RoomId != roomId {
|
|
t.Errorf("Expected room %s, got %+v", roomId, event)
|
|
} else if event.Update.Properties == nil || !bytes.Equal(*event.Update.Properties, roomProperties) {
|
|
t.Errorf("Room properties don't match: expected %s, got %s", string(roomProperties), string(*event.Update.Properties))
|
|
}
|
|
|
|
// TODO: Use event to wait for asynchronous messages.
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
room = hub.getRoom(roomId)
|
|
if room == nil {
|
|
t.Fatalf("Room %s does not exist", roomId)
|
|
}
|
|
if string(*room.Properties()) != string(roomProperties) {
|
|
t.Errorf("Expected properties %s for room %s, got %s", string(roomProperties), room.Id(), string(*room.Properties()))
|
|
}
|
|
}
|
|
|
|
func TestBackendServer_RoomDelete(t *testing.T) {
|
|
for _, backend := range eventBackendsForTest {
|
|
t.Run(backend, func(t *testing.T) {
|
|
RunTestBackendServer_RoomDelete(t)
|
|
})
|
|
}
|
|
}
|
|
|
|
func RunTestBackendServer_RoomDelete(t *testing.T) {
|
|
_, _, events, hub, _, server := CreateBackendServerForTest(t)
|
|
|
|
u, err := url.Parse(server.URL)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
roomId := "the-room-id"
|
|
emptyProperties := json.RawMessage("{}")
|
|
backend := hub.backend.GetBackend(u)
|
|
if backend == nil {
|
|
t.Fatalf("Did not find backend")
|
|
}
|
|
if _, err := hub.createRoom(roomId, &emptyProperties, backend); err != nil {
|
|
t.Fatalf("Could not create room: %s", err)
|
|
}
|
|
|
|
userid := "test-userid"
|
|
eventsChan := make(chan *AsyncMessage, 1)
|
|
listener := &channelEventListener{
|
|
ch: eventsChan,
|
|
}
|
|
if err := events.RegisterUserListener(userid, backend, listener); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer events.UnregisterUserListener(userid, backend, listener)
|
|
|
|
msg := &BackendServerRoomRequest{
|
|
Type: "delete",
|
|
Delete: &BackendRoomDeleteRequest{
|
|
UserIds: []string{
|
|
userid,
|
|
},
|
|
},
|
|
}
|
|
|
|
data, err := json.Marshal(msg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
res, err := performBackendRequest(server.URL+"/api/v1/room/"+roomId, data)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer res.Body.Close()
|
|
body, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if res.StatusCode != 200 {
|
|
t.Errorf("Expected successful request, got %s: %s", res.Status, string(body))
|
|
}
|
|
|
|
// A deleted room is signalled as a "disinvite" event.
|
|
event, err := expectRoomlistEvent(eventsChan, "disinvite")
|
|
if err != nil {
|
|
t.Error(err)
|
|
} else if event.Disinvite == nil {
|
|
t.Errorf("Expected disinvite, got %+v", event)
|
|
} else if event.Disinvite.RoomId != roomId {
|
|
t.Errorf("Expected room %s, got %+v", roomId, event)
|
|
} else if event.Disinvite.Properties != nil {
|
|
t.Errorf("Room properties should be omitted, got %s", string(*event.Disinvite.Properties))
|
|
} else if event.Disinvite.Reason != "deleted" {
|
|
t.Errorf("Reason should be deleted, got %s", event.Disinvite.Reason)
|
|
}
|
|
|
|
// TODO: Use event to wait for asynchronous messages.
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
room := hub.getRoom(roomId)
|
|
if room != nil {
|
|
t.Errorf("Room %s should have been deleted", roomId)
|
|
}
|
|
}
|
|
|
|
func TestBackendServer_ParticipantsUpdatePermissions(t *testing.T) {
|
|
for _, subtest := range clusteredTests {
|
|
t.Run(subtest, func(t *testing.T) {
|
|
var hub1 *Hub
|
|
var hub2 *Hub
|
|
var server1 *httptest.Server
|
|
var server2 *httptest.Server
|
|
|
|
if isLocalTest(t) {
|
|
_, _, _, hub1, _, server1 = CreateBackendServerForTest(t)
|
|
|
|
hub2 = hub1
|
|
server2 = server1
|
|
} else {
|
|
_, _, hub1, hub2, server1, server2 = CreateBackendServerWithClusteringForTest(t)
|
|
}
|
|
|
|
client1 := NewTestClient(t, server1, hub1)
|
|
defer client1.CloseWithBye()
|
|
if err := client1.SendHello(testDefaultUserId + "1"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
client2 := NewTestClient(t, server2, hub2)
|
|
defer client2.CloseWithBye()
|
|
if err := client2.SendHello(testDefaultUserId + "2"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
|
defer cancel()
|
|
|
|
hello1, err := client1.RunUntilHello(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
hello2, err := client2.RunUntilHello(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
session1 := hub1.GetSessionByPublicId(hello1.Hello.SessionId)
|
|
if session1 == nil {
|
|
t.Fatalf("Session %s does not exist", hello1.Hello.SessionId)
|
|
}
|
|
session2 := hub2.GetSessionByPublicId(hello2.Hello.SessionId)
|
|
if session2 == nil {
|
|
t.Fatalf("Session %s does not exist", hello2.Hello.SessionId)
|
|
}
|
|
|
|
// Sessions have all permissions initially (fallback for old-style sessions).
|
|
assertSessionHasPermission(t, session1, PERMISSION_MAY_PUBLISH_MEDIA)
|
|
assertSessionHasPermission(t, session1, PERMISSION_MAY_PUBLISH_SCREEN)
|
|
assertSessionHasPermission(t, session2, PERMISSION_MAY_PUBLISH_MEDIA)
|
|
assertSessionHasPermission(t, session2, PERMISSION_MAY_PUBLISH_SCREEN)
|
|
|
|
// Join room by id.
|
|
roomId := "test-room"
|
|
if room, err := client1.JoinRoom(ctx, roomId); err != nil {
|
|
t.Fatal(err)
|
|
} else if room.Room.RoomId != roomId {
|
|
t.Fatalf("Expected room %s, got %s", roomId, room.Room.RoomId)
|
|
}
|
|
if room, err := client2.JoinRoom(ctx, roomId); err != nil {
|
|
t.Fatal(err)
|
|
} else if room.Room.RoomId != roomId {
|
|
t.Fatalf("Expected room %s, got %s", roomId, room.Room.RoomId)
|
|
}
|
|
|
|
// Ignore "join" events.
|
|
if err := client1.DrainMessages(ctx); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if err := client2.DrainMessages(ctx); err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
msg := &BackendServerRoomRequest{
|
|
Type: "participants",
|
|
Participants: &BackendRoomParticipantsRequest{
|
|
Changed: []map[string]interface{}{
|
|
{
|
|
"sessionId": roomId + "-" + hello1.Hello.SessionId,
|
|
"permissions": []Permission{PERMISSION_MAY_PUBLISH_MEDIA},
|
|
},
|
|
{
|
|
"sessionId": roomId + "-" + hello2.Hello.SessionId,
|
|
"permissions": []Permission{PERMISSION_MAY_PUBLISH_SCREEN},
|
|
},
|
|
},
|
|
Users: []map[string]interface{}{
|
|
{
|
|
"sessionId": roomId + "-" + hello1.Hello.SessionId,
|
|
"permissions": []Permission{PERMISSION_MAY_PUBLISH_MEDIA},
|
|
},
|
|
{
|
|
"sessionId": roomId + "-" + hello2.Hello.SessionId,
|
|
"permissions": []Permission{PERMISSION_MAY_PUBLISH_SCREEN},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
data, err := json.Marshal(msg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// The request could be sent to any of the backend servers.
|
|
res, err := performBackendRequest(server1.URL+"/api/v1/room/"+roomId, data)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer res.Body.Close()
|
|
body, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if res.StatusCode != 200 {
|
|
t.Errorf("Expected successful request, got %s: %s", res.Status, string(body))
|
|
}
|
|
|
|
// TODO: Use event to wait for asynchronous messages.
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
assertSessionHasPermission(t, session1, PERMISSION_MAY_PUBLISH_MEDIA)
|
|
assertSessionHasNotPermission(t, session1, PERMISSION_MAY_PUBLISH_SCREEN)
|
|
assertSessionHasNotPermission(t, session2, PERMISSION_MAY_PUBLISH_MEDIA)
|
|
assertSessionHasPermission(t, session2, PERMISSION_MAY_PUBLISH_SCREEN)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBackendServer_ParticipantsUpdateEmptyPermissions(t *testing.T) {
|
|
_, _, _, hub, _, server := CreateBackendServerForTest(t)
|
|
|
|
client := NewTestClient(t, server, hub)
|
|
defer client.CloseWithBye()
|
|
if err := client.SendHello(testDefaultUserId); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
|
defer cancel()
|
|
|
|
hello, err := client.RunUntilHello(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
session := hub.GetSessionByPublicId(hello.Hello.SessionId)
|
|
if session == nil {
|
|
t.Fatalf("Session %s does not exist", hello.Hello.SessionId)
|
|
}
|
|
|
|
// Sessions have all permissions initially (fallback for old-style sessions).
|
|
assertSessionHasPermission(t, session, PERMISSION_MAY_PUBLISH_MEDIA)
|
|
assertSessionHasPermission(t, session, PERMISSION_MAY_PUBLISH_SCREEN)
|
|
|
|
// Join room by id.
|
|
roomId := "test-room"
|
|
room, err := client.JoinRoom(ctx, roomId)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if room.Room.RoomId != roomId {
|
|
t.Fatalf("Expected room %s, got %s", roomId, room.Room.RoomId)
|
|
}
|
|
|
|
// Ignore "join" events.
|
|
if err := client.DrainMessages(ctx); err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
// Updating with empty permissions upgrades to non-old-style and removes
|
|
// all previously available permissions.
|
|
msg := &BackendServerRoomRequest{
|
|
Type: "participants",
|
|
Participants: &BackendRoomParticipantsRequest{
|
|
Changed: []map[string]interface{}{
|
|
{
|
|
"sessionId": roomId + "-" + hello.Hello.SessionId,
|
|
"permissions": []Permission{},
|
|
},
|
|
},
|
|
Users: []map[string]interface{}{
|
|
{
|
|
"sessionId": roomId + "-" + hello.Hello.SessionId,
|
|
"permissions": []Permission{},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
data, err := json.Marshal(msg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
res, err := performBackendRequest(server.URL+"/api/v1/room/"+roomId, data)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer res.Body.Close()
|
|
body, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if res.StatusCode != 200 {
|
|
t.Errorf("Expected successful request, got %s: %s", res.Status, string(body))
|
|
}
|
|
|
|
// TODO: Use event to wait for asynchronous messages.
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
assertSessionHasNotPermission(t, session, PERMISSION_MAY_PUBLISH_MEDIA)
|
|
assertSessionHasNotPermission(t, session, PERMISSION_MAY_PUBLISH_SCREEN)
|
|
}
|
|
|
|
func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) {
|
|
_, _, _, hub, _, server := CreateBackendServerForTest(t)
|
|
|
|
client1 := NewTestClient(t, server, hub)
|
|
defer client1.CloseWithBye()
|
|
if err := client1.SendHello(testDefaultUserId + "1"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
client2 := NewTestClient(t, server, hub)
|
|
defer client2.CloseWithBye()
|
|
if err := client2.SendHello(testDefaultUserId + "2"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
|
defer cancel()
|
|
|
|
hello1, err := client1.RunUntilHello(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
hello2, err := client2.RunUntilHello(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Join room by id.
|
|
roomId := "test-room"
|
|
if room, err := client1.JoinRoom(ctx, roomId); err != nil {
|
|
t.Fatal(err)
|
|
} else if room.Room.RoomId != roomId {
|
|
t.Fatalf("Expected room %s, got %s", roomId, room.Room.RoomId)
|
|
}
|
|
|
|
// Give message processing some time.
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
if room, err := client2.JoinRoom(ctx, roomId); err != nil {
|
|
t.Fatal(err)
|
|
} else if room.Room.RoomId != roomId {
|
|
t.Fatalf("Expected room %s, got %s", roomId, room.Room.RoomId)
|
|
}
|
|
|
|
WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2)
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
msg := &BackendServerRoomRequest{
|
|
Type: "incall",
|
|
InCall: &BackendRoomInCallRequest{
|
|
InCall: json.RawMessage("7"),
|
|
Changed: []map[string]interface{}{
|
|
{
|
|
"sessionId": roomId + "-" + hello1.Hello.SessionId,
|
|
"inCall": 7,
|
|
},
|
|
{
|
|
"sessionId": "unknown-room-session-id",
|
|
"inCall": 3,
|
|
},
|
|
},
|
|
Users: []map[string]interface{}{
|
|
{
|
|
"sessionId": roomId + "-" + hello1.Hello.SessionId,
|
|
"inCall": 7,
|
|
},
|
|
{
|
|
"sessionId": "unknown-room-session-id",
|
|
"inCall": 3,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
data, err := json.Marshal(msg)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
res, err := performBackendRequest(server.URL+"/api/v1/room/"+roomId, data)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
defer res.Body.Close()
|
|
body, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if res.StatusCode != 200 {
|
|
t.Errorf("Expected successful request, got %s: %s", res.Status, string(body))
|
|
}
|
|
}()
|
|
|
|
// Ensure the first request is being processed.
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
msg := &BackendServerRoomRequest{
|
|
Type: "incall",
|
|
InCall: &BackendRoomInCallRequest{
|
|
InCall: json.RawMessage("7"),
|
|
Changed: []map[string]interface{}{
|
|
{
|
|
"sessionId": roomId + "-" + hello1.Hello.SessionId,
|
|
"inCall": 7,
|
|
},
|
|
{
|
|
"sessionId": roomId + "-" + hello2.Hello.SessionId,
|
|
"inCall": 3,
|
|
},
|
|
},
|
|
Users: []map[string]interface{}{
|
|
{
|
|
"sessionId": roomId + "-" + hello1.Hello.SessionId,
|
|
"inCall": 7,
|
|
},
|
|
{
|
|
"sessionId": roomId + "-" + hello2.Hello.SessionId,
|
|
"inCall": 3,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
data, err := json.Marshal(msg)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
res, err := performBackendRequest(server.URL+"/api/v1/room/"+roomId, data)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
defer res.Body.Close()
|
|
body, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if res.StatusCode != 200 {
|
|
t.Errorf("Expected successful request, got %s: %s", res.Status, string(body))
|
|
}
|
|
}()
|
|
|
|
wg.Wait()
|
|
if t.Failed() {
|
|
return
|
|
}
|
|
|
|
msg1_a, err := client1.RunUntilMessage(ctx)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if in_call_1, err := checkMessageParticipantsInCall(msg1_a); err != nil {
|
|
t.Error(err)
|
|
} else if len(in_call_1.Users) != 2 {
|
|
msg1_b, err := client1.RunUntilMessage(ctx)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if in_call_2, err := checkMessageParticipantsInCall(msg1_b); err != nil {
|
|
t.Error(err)
|
|
} else if len(in_call_2.Users) != 2 {
|
|
t.Errorf("Wrong number of users received: %d, expected 2", len(in_call_2.Users))
|
|
}
|
|
}
|
|
|
|
msg2_a, err := client2.RunUntilMessage(ctx)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if in_call_1, err := checkMessageParticipantsInCall(msg2_a); err != nil {
|
|
t.Error(err)
|
|
} else if len(in_call_1.Users) != 2 {
|
|
msg2_b, err := client2.RunUntilMessage(ctx)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if in_call_2, err := checkMessageParticipantsInCall(msg2_b); err != nil {
|
|
t.Error(err)
|
|
} else if len(in_call_2.Users) != 2 {
|
|
t.Errorf("Wrong number of users received: %d, expected 2", len(in_call_2.Users))
|
|
}
|
|
}
|
|
|
|
ctx2, cancel2 := context.WithTimeout(context.Background(), time.Second+100*time.Millisecond)
|
|
defer cancel2()
|
|
|
|
if msg1_c, _ := client1.RunUntilMessage(ctx2); msg1_c != nil {
|
|
if in_call_2, err := checkMessageParticipantsInCall(msg1_c); err != nil {
|
|
t.Error(err)
|
|
} else if len(in_call_2.Users) != 2 {
|
|
t.Errorf("Wrong number of users received: %d, expected 2", len(in_call_2.Users))
|
|
}
|
|
}
|
|
|
|
ctx3, cancel3 := context.WithTimeout(context.Background(), time.Second+100*time.Millisecond)
|
|
defer cancel3()
|
|
if msg2_c, _ := client2.RunUntilMessage(ctx3); msg2_c != nil {
|
|
if in_call_2, err := checkMessageParticipantsInCall(msg2_c); err != nil {
|
|
t.Error(err)
|
|
} else if len(in_call_2.Users) != 2 {
|
|
t.Errorf("Wrong number of users received: %d, expected 2", len(in_call_2.Users))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBackendServer_RoomMessage(t *testing.T) {
|
|
_, _, _, hub, _, server := CreateBackendServerForTest(t)
|
|
|
|
client := NewTestClient(t, server, hub)
|
|
defer client.CloseWithBye()
|
|
if err := client.SendHello(testDefaultUserId + "1"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
|
defer cancel()
|
|
|
|
_, err := client.RunUntilHello(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Join room by id.
|
|
roomId := "test-room"
|
|
if room, err := client.JoinRoom(ctx, roomId); err != nil {
|
|
t.Fatal(err)
|
|
} else if room.Room.RoomId != roomId {
|
|
t.Fatalf("Expected room %s, got %s", roomId, room.Room.RoomId)
|
|
}
|
|
|
|
// Ignore "join" events.
|
|
if err := client.DrainMessages(ctx); err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
messageData := json.RawMessage("{\"foo\":\"bar\"}")
|
|
msg := &BackendServerRoomRequest{
|
|
Type: "message",
|
|
Message: &BackendRoomMessageRequest{
|
|
Data: &messageData,
|
|
},
|
|
}
|
|
|
|
data, err := json.Marshal(msg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
res, err := performBackendRequest(server.URL+"/api/v1/room/"+roomId, data)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer res.Body.Close()
|
|
body, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if res.StatusCode != 200 {
|
|
t.Errorf("Expected successful request, got %s: %s", res.Status, string(body))
|
|
}
|
|
|
|
message, err := client.RunUntilRoomMessage(ctx)
|
|
if err != nil {
|
|
t.Error(err)
|
|
} else if message.RoomId != roomId {
|
|
t.Errorf("Expected message for room %s, got %s", roomId, message.RoomId)
|
|
} else if !bytes.Equal(messageData, *message.Data) {
|
|
t.Errorf("Expected message data %s, got %s", string(messageData), string(*message.Data))
|
|
}
|
|
}
|
|
|
|
func TestBackendServer_TurnCredentials(t *testing.T) {
|
|
_, _, _, _, _, server := CreateBackendServerForTestWithTurn(t)
|
|
|
|
q := make(url.Values)
|
|
q.Set("service", "turn")
|
|
q.Set("api", turnApiKey)
|
|
request, err := http.NewRequest("GET", server.URL+"/turn/credentials?"+q.Encode(), nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
client := &http.Client{}
|
|
res, err := client.Do(request)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer res.Body.Close()
|
|
body, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if res.StatusCode != 200 {
|
|
t.Errorf("Expected successful request, got %s: %s", res.Status, string(body))
|
|
}
|
|
|
|
var cred TurnCredentials
|
|
if err := json.Unmarshal(body, &cred); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
m := hmac.New(sha1.New, []byte(turnSecret))
|
|
m.Write([]byte(cred.Username)) // nolint
|
|
password := base64.StdEncoding.EncodeToString(m.Sum(nil))
|
|
if cred.Password != password {
|
|
t.Errorf("Expected password %s, got %s", password, cred.Password)
|
|
}
|
|
if cred.TTL != int64((24 * time.Hour).Seconds()) {
|
|
t.Errorf("Expected a TTL of %d, got %d", int64((24 * time.Hour).Seconds()), cred.TTL)
|
|
}
|
|
if !reflect.DeepEqual(cred.URIs, turnServers) {
|
|
t.Errorf("Expected the list of servers as %s, got %s", turnServers, cred.URIs)
|
|
}
|
|
}
|