333 lines
7.8 KiB
Go
333 lines
7.8 KiB
Go
package rtpconn
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.stormux.org/storm/skald/diskwriter"
|
|
"git.stormux.org/storm/skald/hall"
|
|
"git.stormux.org/storm/skald/token"
|
|
"git.stormux.org/storm/skald/unbounded"
|
|
)
|
|
|
|
var tokens = []string{
|
|
`{
|
|
"token": "a",
|
|
"hall": "h",
|
|
"username": "u",
|
|
"permissions":["present"],
|
|
"expires": "2023-05-03T20:24:47.616624532+02:00"
|
|
}`,
|
|
`{
|
|
"token": "a",
|
|
"hall": "h"
|
|
}`,
|
|
`{
|
|
"token": "a",
|
|
"hall": "h",
|
|
"username":""
|
|
}`,
|
|
}
|
|
|
|
func TestParseStatefulToken(t *testing.T) {
|
|
for i, tok := range tokens {
|
|
var t1 *token.Stateful
|
|
err := json.Unmarshal([]byte(tok), &t1)
|
|
if err != nil {
|
|
t.Errorf("Unmarshal %v: %v", i, err)
|
|
continue
|
|
}
|
|
var m map[string]interface{}
|
|
err = json.Unmarshal([]byte(tok), &m)
|
|
if err != nil {
|
|
t.Errorf("Unmarshal (map) %v: %v", i, err)
|
|
continue
|
|
}
|
|
t2, err := parseStatefulToken(m)
|
|
if err != nil {
|
|
t.Errorf("parseStatefulToken %v: %v", i, err)
|
|
}
|
|
if !reflect.DeepEqual(t1, t2) {
|
|
t.Errorf("Mismatch: %v, %v", t1, t2)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHallRecording(t *testing.T) {
|
|
oldDirectory := hall.Directory
|
|
hall.Directory = t.TempDir()
|
|
defer func() {
|
|
hall.Directory = oldDirectory
|
|
}()
|
|
if err := os.WriteFile(
|
|
filepath.Join(hall.Directory, "recording-status-test.json"),
|
|
[]byte("{}"), 0o600,
|
|
); err != nil {
|
|
t.Fatalf("Write hall description: %v", err)
|
|
}
|
|
|
|
g, err := hall.Add("recording-status-test", nil)
|
|
if err != nil {
|
|
t.Fatalf("Add hall: %v", err)
|
|
}
|
|
defer hall.Delete(g.Name())
|
|
|
|
if hallRecording(g) {
|
|
t.Fatal("empty hall reported active recording")
|
|
}
|
|
|
|
disk := diskwriter.New(g)
|
|
if _, err := hall.AddClient(g.Name(), disk, hall.ClientCredentials{}); err != nil {
|
|
t.Fatalf("Add recording client: %v", err)
|
|
}
|
|
defer hall.DelClient(disk)
|
|
|
|
if !hallRecording(g) {
|
|
t.Fatal("hall did not report active recording")
|
|
}
|
|
}
|
|
|
|
func testWebClient(id string) *webClient {
|
|
return &webClient{
|
|
id: id,
|
|
actions: unbounded.New[any](),
|
|
done: make(chan struct{}),
|
|
writeCh: make(chan interface{}, 100),
|
|
writerDone: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
func makePermission(t *testing.T, name string) hall.Permissions {
|
|
t.Helper()
|
|
p, err := hall.NewPermissions(name)
|
|
if err != nil {
|
|
t.Fatalf("NewPermissions(%q): %v", name, err)
|
|
}
|
|
return p
|
|
}
|
|
|
|
func testPassword() hall.Password {
|
|
key := "pw"
|
|
return hall.Password{
|
|
Type: "plain",
|
|
Key: &key,
|
|
}
|
|
}
|
|
|
|
func addTestWebClient(t *testing.T, hallName string, username string, permission string) *webClient {
|
|
t.Helper()
|
|
c := testWebClient(username + "-id")
|
|
_, err := hall.AddClient(hallName, c, hall.ClientCredentials{
|
|
Username: &username,
|
|
Password: "pw",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("AddClient(%q): %v", username, err)
|
|
}
|
|
c.hall = hall.Get(hallName)
|
|
drainActions(t, c)
|
|
drainMessages(c)
|
|
if !member(permission, c.permissions) {
|
|
t.Fatalf("%q permissions %v, missing %q", username, c.permissions, permission)
|
|
}
|
|
return c
|
|
}
|
|
|
|
func newChalkboardTestHall(t *testing.T) string {
|
|
t.Helper()
|
|
name := "chalkboard-" + strings.NewReplacer("/", "-", " ", "-").Replace(t.Name())
|
|
oldDirectory := hall.Directory
|
|
hall.Directory = t.TempDir()
|
|
t.Cleanup(func() {
|
|
hall.Directory = oldDirectory
|
|
})
|
|
|
|
password := testPassword()
|
|
desc := &hall.Description{
|
|
Users: map[string]hall.UserDescription{
|
|
"Operator": {
|
|
Password: password,
|
|
Permissions: makePermission(t, "op"),
|
|
},
|
|
"Admin": {
|
|
Password: password,
|
|
Permissions: makePermission(t, "admin"),
|
|
},
|
|
"Participant": {
|
|
Password: password,
|
|
Permissions: makePermission(t, "present"),
|
|
},
|
|
},
|
|
}
|
|
data, err := json.Marshal(desc)
|
|
if err != nil {
|
|
t.Fatalf("Marshal hall description: %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(hall.Directory, name+".json"), data, 0o666); err != nil {
|
|
t.Fatalf("Write hall description: %v", err)
|
|
}
|
|
if _, err := hall.Add(name, nil); err != nil {
|
|
t.Fatalf("Add hall: %v", err)
|
|
}
|
|
t.Cleanup(func() {
|
|
if g := hall.Get(name); g != nil {
|
|
for _, c := range g.GetClients(nil) {
|
|
hall.DelClient(c)
|
|
}
|
|
}
|
|
hall.Delete(name)
|
|
})
|
|
return name
|
|
}
|
|
|
|
func drainActions(t *testing.T, c *webClient) {
|
|
t.Helper()
|
|
for {
|
|
select {
|
|
case <-c.actions.Ch:
|
|
for _, a := range c.actions.Get() {
|
|
if err := handleAction(c, a); err != nil {
|
|
t.Fatalf("handleAction: %v", err)
|
|
}
|
|
}
|
|
default:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func drainMessages(c *webClient) []clientMessage {
|
|
var messages []clientMessage
|
|
for {
|
|
select {
|
|
case m := <-c.writeCh:
|
|
switch m := m.(type) {
|
|
case clientMessage:
|
|
messages = append(messages, m)
|
|
case []byte:
|
|
var cm clientMessage
|
|
if err := json.Unmarshal(m, &cm); err == nil {
|
|
messages = append(messages, cm)
|
|
}
|
|
}
|
|
default:
|
|
return messages
|
|
}
|
|
}
|
|
}
|
|
|
|
func latestChalkboardMessage(messages []clientMessage) (hall.ChalkboardState, bool) {
|
|
for i := len(messages) - 1; i >= 0; i-- {
|
|
if messages[i].Type != "usermessage" || messages[i].Kind != "chalkboard" {
|
|
continue
|
|
}
|
|
b, err := json.Marshal(messages[i].Value)
|
|
if err != nil {
|
|
return hall.ChalkboardState{}, false
|
|
}
|
|
var state hall.ChalkboardState
|
|
if err := json.Unmarshal(b, &state); err != nil {
|
|
return hall.ChalkboardState{}, false
|
|
}
|
|
return state, true
|
|
}
|
|
return hall.ChalkboardState{}, false
|
|
}
|
|
|
|
func TestChalkboardEditRequiresPermission(t *testing.T) {
|
|
hallName := newChalkboardTestHall(t)
|
|
participant := addTestWebClient(t, hallName, "Participant", "present")
|
|
operator := addTestWebClient(t, hallName, "Operator", "op")
|
|
|
|
err := handleClientMessage(participant, clientMessage{
|
|
Type: "hallaction",
|
|
Kind: "chalkboard",
|
|
Value: map[string]any{
|
|
"text": "should not save",
|
|
"revision": float64(0),
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unauthorised chalkboard edit returned error: %v", err)
|
|
}
|
|
if got := participant.hall.Chalkboard().Text; got != "" {
|
|
t.Fatalf("unauthorised edit changed text to %q", got)
|
|
}
|
|
|
|
err = handleClientMessage(operator, clientMessage{
|
|
Type: "hallaction",
|
|
Kind: "chalkboard",
|
|
Value: map[string]any{
|
|
"text": "line 1\nline 2",
|
|
"revision": float64(0),
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("operator chalkboard edit: %v", err)
|
|
}
|
|
state := operator.hall.Chalkboard()
|
|
if state.Text != "line 1\nline 2" || state.Revision != 1 {
|
|
t.Fatalf("chalkboard state = %#v", state)
|
|
}
|
|
for _, c := range []*webClient{participant, operator} {
|
|
got, ok := latestChalkboardMessage(drainMessages(c))
|
|
if !ok {
|
|
t.Fatalf("%v did not receive chalkboard broadcast", c.id)
|
|
}
|
|
if got != state {
|
|
t.Fatalf("%v broadcast = %#v, want %#v", c.id, got, state)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestChalkboardGrantIsSessionPermission(t *testing.T) {
|
|
hallName := newChalkboardTestHall(t)
|
|
admin := addTestWebClient(t, hallName, "Admin", "admin")
|
|
participant := addTestWebClient(t, hallName, "Participant", "present")
|
|
|
|
err := handleClientMessage(admin, clientMessage{
|
|
Type: "useraction",
|
|
Kind: "chalkboard",
|
|
Dest: participant.id,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("admin chalkboard grant: %v", err)
|
|
}
|
|
drainActions(t, participant)
|
|
if !member("chalkboard", participant.permissions) {
|
|
t.Fatalf("grant did not add chalkboard permission: %v", participant.permissions)
|
|
}
|
|
|
|
err = handleClientMessage(participant, clientMessage{
|
|
Type: "hallaction",
|
|
Kind: "chalkboard",
|
|
Value: map[string]any{
|
|
"text": "granted edit",
|
|
"revision": float64(0),
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("granted chalkboard edit: %v", err)
|
|
}
|
|
if got := participant.hall.Chalkboard().Text; got != "granted edit" {
|
|
t.Fatalf("chalkboard text = %q", got)
|
|
}
|
|
|
|
err = handleClientMessage(admin, clientMessage{
|
|
Type: "useraction",
|
|
Kind: "unchalkboard",
|
|
Dest: participant.id,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("admin chalkboard revoke: %v", err)
|
|
}
|
|
drainActions(t, participant)
|
|
if member("chalkboard", participant.permissions) {
|
|
t.Fatalf("revoke left chalkboard permission: %v", participant.permissions)
|
|
}
|
|
}
|