Files

224 lines
6.9 KiB
Go

package rtpconn
import (
"regexp"
"strings"
"testing"
"git.stormux.org/storm/skald/hall"
)
func TestParseDiceExpression(t *testing.T) {
tests := []struct {
expression string
count int64
sides int64
modifier int64
}{
{"2d4", 2, 4, 0},
{"10d8 + 5", 10, 8, 5},
{"3 d 2+5", 3, 2, 5},
{"2 D 20 - 1", 2, 20, -1},
{" 1d6 ", 1, 6, 0},
}
for _, test := range tests {
t.Run(test.expression, func(t *testing.T) {
got, err := parseDiceExpression(test.expression)
if err != nil {
t.Fatalf("parseDiceExpression(%q): %v", test.expression, err)
}
if got.Count != test.count || got.Sides != test.sides || got.Modifier != test.modifier {
t.Fatalf("parseDiceExpression(%q) = %#v", test.expression, got)
}
})
}
}
func TestParseDiceExpressionRejectsInvalidAndOversizedRolls(t *testing.T) {
tests := []string{
"", "d20", "2d", "2d1", "0d6", "101d6", "2d1000001",
"2d6 + 1000001", "2 0d6", "2d6 trailing",
}
for _, expression := range tests {
t.Run(expression, func(t *testing.T) {
if _, err := parseDiceExpression(expression); err == nil {
t.Fatalf("parseDiceExpression(%q) unexpectedly succeeded", expression)
}
})
}
}
func TestFormatDiceAnnouncement(t *testing.T) {
tests := []struct {
name string
roll diceRoll
expected string
}{
{
"two dice",
diceRoll{Count: 2, Sides: 4, Results: []int64{1, 2}, Total: 3},
"Storm rolls 2 4-sided dice: 1 and 2, for a total of 3.",
},
{
"many dice with modifier",
diceRoll{Count: 10, Sides: 10, Modifier: 5, Results: []int64{2, 3, 4, 3, 4, 1, 2, 1, 2, 3}, Total: 30},
"Storm rolls 10 10-sided dice: 2, 3, 4, 3, 4, and 5 more, plus 5, for a total of 30.",
},
{
"one die with negative modifier",
diceRoll{Count: 1, Sides: 20, Modifier: -1, Results: []int64{17}, Total: 16},
"Storm rolls 1 20-sided die: 17, minus 1, for a total of 16.",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if got := formatDiceAnnouncement("Storm", test.roll); got != test.expected {
t.Fatalf("formatDiceAnnouncement() = %q, want %q", got, test.expected)
}
})
}
}
func TestGenerateDiceRollStaysWithinBounds(t *testing.T) {
roll, err := generateDiceRoll(diceRoll{Count: 100, Sides: 6, Modifier: -50})
if err != nil {
t.Fatalf("generateDiceRoll: %v", err)
}
if len(roll.Results) != 100 {
t.Fatalf("generated %d results, want 100", len(roll.Results))
}
wantTotal := int64(-50)
for _, result := range roll.Results {
if result < 1 || result > 6 {
t.Fatalf("generated result %d outside 1..6", result)
}
wantTotal += result
}
if roll.Total != wantTotal {
t.Fatalf("total = %d, want %d", roll.Total, wantTotal)
}
}
func TestDiceRollIsServerGeneratedAndStoredInHistory(t *testing.T) {
hallName := newChalkboardTestHall(t)
roller := addTestWebClient(t, hallName, "Participant", "message")
observer := addTestWebClient(t, hallName, "Operator", "op")
if err := handleClientMessage(roller, clientMessage{
Type: "hallaction",
Kind: "roll",
Value: "2d4 + 5",
}); err != nil {
t.Fatalf("handleClientMessage(roll): %v", err)
}
for name, messages := range map[string][]clientMessage{
"roller": drainMessages(roller),
"observer": drainMessages(observer),
} {
if len(messages) != 1 {
t.Fatalf("%s received %d messages, want 1: %#v", name, len(messages), messages)
}
message := messages[0]
if message.Type != "chat" || message.Kind != "dice" || !message.Privileged {
t.Fatalf("%s received non-authoritative dice message: %#v", name, message)
}
if message.Source != "" || message.Username == nil || *message.Username != "Dice" {
t.Fatalf("%s received dice message with unexpected identity: %#v", name, message)
}
text, ok := message.Value.(string)
if !ok || !regexp.MustCompile(`^Participant rolls 2 4-sided dice: [1-4] and [1-4], plus 5, for a total of (7|8|9|10|11|12|13)\.$`).MatchString(text) {
t.Fatalf("%s received malformed dice announcement %q", name, text)
}
}
history := roller.hall.GetChatHistory()
if len(history) != 1 || history[0].Kind != "dice" {
t.Fatalf("dice history = %#v", history)
}
newcomer := testWebClient("admin-id")
adminName := "Admin"
if _, err := hall.AddClient(hallName, newcomer, hall.ClientCredentials{
Username: &adminName,
Password: "pw",
}); err != nil {
t.Fatalf("AddClient(Admin): %v", err)
}
newcomer.hall = hall.Get(hallName)
drainActions(t, newcomer)
var historicalDice *clientMessage
for _, message := range drainMessages(newcomer) {
if message.Type == "chathistory" && message.Kind == "dice" {
message := message
historicalDice = &message
break
}
}
if historicalDice == nil || !historicalDice.Privileged {
t.Fatalf("new participant did not receive authoritative dice history: %#v", historicalDice)
}
}
func TestDiceRollRequiresMessagePermission(t *testing.T) {
hallName := newChalkboardTestHall(t)
roller := addTestWebClient(t, hallName, "Participant", "message")
roller.permissions = remove("message", roller.permissions)
if err := handleClientMessage(roller, clientMessage{
Type: "hallaction", Kind: "roll", Value: "1d6",
}); err != nil {
t.Fatalf("handleClientMessage(roll): %v", err)
}
if got := roller.hall.GetChatHistory(); len(got) != 0 {
t.Fatalf("unauthorised roll entered history: %#v", got)
}
messages := drainMessages(roller)
if len(messages) != 1 || messages[0].Kind != "error" ||
!strings.Contains(messages[0].Value.(string), "not authorised") {
t.Fatalf("unauthorised roll response = %#v", messages)
}
}
func TestClientsCannotSubmitServerGeneratedMessages(t *testing.T) {
hallName := newChalkboardTestHall(t)
roller := addTestWebClient(t, hallName, "Participant", "message")
for _, messageKind := range []string{"dice", "eightball", "coin"} {
for _, messageType := range []string{"chat", "usermessage"} {
drainMessages(roller)
if err := handleClientMessage(roller, clientMessage{
Type: messageType, Kind: messageKind, Value: "whatever result I wanted",
}); err != nil {
t.Fatalf("handleClientMessage(%s, %s): %v", messageType, messageKind, err)
}
messages := drainMessages(roller)
if len(messages) != 1 || messages[0].Kind != "error" {
t.Fatalf("reserved %s/%s response = %#v", messageType, messageKind, messages)
}
}
}
if got := roller.hall.GetChatHistory(); len(got) != 0 {
t.Fatalf("forged dice message entered history: %#v", got)
}
}
func TestChatIdentityComesFromConnection(t *testing.T) {
hallName := newChalkboardTestHall(t)
sender := addTestWebClient(t, hallName, "Participant", "message")
observer := addTestWebClient(t, hallName, "Operator", "op")
if err := handleClientMessage(sender, clientMessage{
Type: "chat", Value: "hello",
}); err != nil {
t.Fatalf("handleClientMessage(chat): %v", err)
}
messages := drainMessages(observer)
if len(messages) != 1 || messages[0].Source != sender.id ||
messages[0].Username == nil || *messages[0].Username != "Participant" {
t.Fatalf("chat did not use connection identity: %#v", messages)
}
}