draftbot/draftbot_test.go

434 lines
11 KiB
Go

package draftbot_test
import (
"fmt"
"net/http"
"net/http/httptest"
"os"
"testing"
"time"
"git.fromouter.space/mcg/draft"
"git.fromouter.space/mcg/draft/mlp"
mlptestdata "git.fromouter.space/mcg/draft/mlp/testdata"
draftbot "git.fromouter.space/mcg/mlp-server-tools/draftbot"
testdata "git.fromouter.space/mcg/mlp-server-tools/draftbot/testdata"
"git.fromouter.space/mcg/cardgage/client/bot"
lobby "git.fromouter.space/mcg/cardgage/lobby/proto"
room "git.fromouter.space/mcg/cardgage/room/api"
)
const TestBotName = "test-bot"
const TestRoomName = "test-room"
type MockServer struct {
in chan room.ServerMessage
out chan room.BotMessage
t *testing.T
timeout time.Duration
roomName string
}
func makeMockServer(t *testing.T, timeout int) *MockServer {
srv := &MockServer{
in: make(chan room.ServerMessage, 99),
out: make(chan room.BotMessage, 99),
t: t,
timeout: time.Duration(timeout) * time.Second,
roomName: TestRoomName,
}
return srv
}
func (m *MockServer) Send(msg room.BotMessage) {
m.out <- msg
}
func (m *MockServer) Bind(fn bot.MessageHandler) {
for msg := range m.in {
fn(msg)
}
}
func TestDraftSession(t *testing.T) {
mock := makeMockServer(t, 5)
drafter := draftbot.NewDraftBot(mock, TestBotName)
go mock.Bind(drafter.OnMessage)
// Create a new room
mock.in <- room.ServerMessage{
RoomID: TestRoomName,
Type: room.MsgEvent,
Event: &room.Event{
Type: room.EvtNewRoom,
Room: &lobby.Room{
Id: TestRoomName,
Name: "Test draft room",
Creator: "test-owner",
},
},
}
// Create new session with a fake message from owner
mock.message("test-owner", "create", draftbot.SessionOptions{
Players: 8, // Two players, six bots
Options: draftbot.DraftOptions{
Type: draftbot.DraftSet,
Positioning: draftbot.PosEven,
Set: mlp.SetAbsoluteDiscord,
PackCount: 4,
},
})
mock.expect("session-open")
// Join session as owner
mock.message("test-owner", "join", nil)
mock.expect("player-joined-session")
// .. and as second player
mock.message("test-guest", "join", nil)
mock.expect("player-joined-session")
// Try to start the session
mock.message("test-owner", "start", nil)
// These two can happen in any order, so they get their own special check
mock.multiexpect("session-start", "draft-order")
// Pick card for each player
for packi := 0; packi < 4; packi++ {
mock.expect("draft-newpack")
for cardi := 0; cardi < 12; cardi++ {
mock.expect("draft-newpick")
// Get packs
msg1 := mock.expect("draft-availablepicks")
pack1 := msg1.Data.(draft.Pack)
msg2 := mock.expect("draft-availablepicks")
pack2 := msg2.Data.(draft.Pack)
// Pick first card in each pack
mock.message(msg1.To, "pick", pack1[0].ID)
mock.message(msg2.To, "pick", pack2[0].ID)
// Intercept picked events
mock.multiexpect("card-picked", "card-picked")
}
}
mock.expect("draft-finish")
// Close the room
mock.in <- room.ServerMessage{
RoomID: TestRoomName,
Type: room.MsgEvent,
Event: &room.Event{
Type: room.EvtRoomClosed,
},
}
}
func TestDraftSessionButEverythingGoesWrong(t *testing.T) {
mock := makeMockServer(t, 5)
drafter := draftbot.NewDraftBot(mock, TestBotName)
go mock.Bind(drafter.OnMessage)
// Create a new room
mock.in <- room.ServerMessage{
Type: room.MsgEvent,
Event: &room.Event{
Type: room.EvtNewRoom,
Room: &lobby.Room{
Id: TestRoomName,
Name: "Test draft room",
Creator: "test-owner",
},
},
}
// Try creating a new session as NOT the owner
mock.message("test-guest", "create", draftbot.SessionOptions{
Players: 8,
Options: draftbot.DraftOptions{
Type: draftbot.DraftSet,
Positioning: draftbot.PosEven,
Set: mlp.SetAbsoluteDiscord,
PackCount: 4,
},
})
mock.expect("must-be-owner")
// Try creating a session with an invalid type
mock.message("test-owner", "create", draftbot.SessionOptions{
Players: 8,
Options: draftbot.DraftOptions{
Type: "lolwhat",
},
})
mock.expect("session-create-error")
// Try creating a session with invalid data
mock.message("test-owner", "create", 42)
mock.expect("invalid-data")
// Try starting a session that doesn't exist
mock.message("test-owner", "start", nil)
mock.expect("command-unavailable")
// Try creating the session twice
mock.message("test-owner", "create", draftbot.SessionOptions{
Players: 2,
Options: draftbot.DraftOptions{
Type: draftbot.DraftSet,
Positioning: draftbot.PosEven,
Set: mlp.SetAbsoluteDiscord,
PackCount: 4,
},
})
mock.expect("session-open")
mock.message("test-owner", "create", draftbot.SessionOptions{
Players: 2,
Options: draftbot.DraftOptions{
Type: draftbot.DraftSet,
Positioning: draftbot.PosEven,
Set: mlp.SetAbsoluteDiscord,
PackCount: 4,
},
})
mock.expect("command-unavailable")
// Try to start session when no-one has joined
mock.message("test-owner", "start", nil)
mock.expect("session-start-error")
// Try to make too many players join
mock.message("a", "join", nil)
mock.expect("player-joined-session")
mock.message("b", "join", nil)
mock.expect("player-joined-session")
mock.message("c", "join", nil)
mock.expect("session-full")
// Try to make someone join a session that already started
mock.message("test-owner", "start", nil)
mock.multiexpect("session-start", "draft-order", "draft-newpack", "draft-newpick", "draft-availablepicks", "draft-availablepicks")
mock.message("c", "join", nil)
mock.expect("session-already-started")
//TODO More picking, etc shenanigans
}
func TestDraftTypes(t *testing.T) {
// Load all sets into memory
err := mlp.LoadAllSets()
if err != nil {
t.Fatalf("Could not load MLP sets needed for some drafts: %s", err.Error())
}
mock := makeMockServer(t, 5)
drafter := draftbot.NewDraftBot(mock, TestBotName)
go mock.Bind(drafter.OnMessage)
makeSession := func(roomName string, options draftbot.DraftOptions) {
// Create a new room
mock.in <- room.ServerMessage{
RoomID: roomName,
Type: room.MsgEvent,
Event: &room.Event{
Type: room.EvtNewRoom,
Room: &lobby.Room{
Id: roomName,
Name: "Test draft room",
Creator: "test-owner",
},
},
}
mock.roomName = roomName
// Create new session
mock.message("test-owner", "create", draftbot.SessionOptions{
Players: 8, // Two players, six bots
Options: options,
})
mock.expect("session-open")
}
// Make Set draft session
makeSession("set", draftbot.DraftOptions{
Type: draftbot.DraftSet,
Positioning: draftbot.PosEven,
Set: mlp.SetAbsoluteDiscord,
PackCount: 4,
})
// Make Block draft session
makeSession("block", draftbot.DraftOptions{
Type: draftbot.DraftBlock,
Positioning: draftbot.PosRandom,
Block: mlp.BlockDefenders,
})
// Make a plain Cube draft session
makeSession("cube", draftbot.DraftOptions{
Type: draftbot.DraftCube,
Positioning: draftbot.PosEven,
CubeURL: mlp.HTTPSource + "cube",
PackSize: 4,
})
// Make a I8PCube draft session
makeSession("i8pcube", draftbot.DraftOptions{
Type: draftbot.DraftI8PCube,
Positioning: draftbot.PosEven,
CubeURL: mlp.HTTPSource + "i8pcube",
MainCount: 1,
ProblemCount: 1,
})
}
func TestDraftPositioning(t *testing.T) {
mock := makeMockServer(t, 5)
drafter := draftbot.NewDraftBot(mock, TestBotName)
go mock.Bind(drafter.OnMessage)
makeSession := func(roomName string, pos string) {
// Create a new room
mock.in <- room.ServerMessage{
RoomID: roomName,
Type: room.MsgEvent,
Event: &room.Event{
Type: room.EvtNewRoom,
Room: &lobby.Room{
Id: roomName,
Name: "Test draft room",
Creator: "test-owner",
},
},
}
mock.roomName = roomName
// Create new session
mock.message("test-owner", "create", draftbot.SessionOptions{
Players: 8, // Two players, six bots
Options: draftbot.DraftOptions{
Type: draftbot.DraftSet,
Positioning: pos,
Set: mlp.SetAbsoluteDiscord,
PackCount: 4,
},
})
mock.expect("session-open")
// Make two random players join
mock.message("a", "join", nil)
mock.expect("player-joined-session")
mock.message("b", "join", nil)
mock.expect("player-joined-session")
// Start the session
mock.message("test-owner", "start", nil)
mock.multiexpect("session-start", "draft-order", "draft-newpack", "draft-newpick", "draft-availablepicks", "draft-availablepicks")
}
makeSession("even", draftbot.PosEven)
makeSession("random", draftbot.PosRandom)
}
func (m *MockServer) expect(typ string) *room.Message {
for {
select {
case msg := <-m.out:
// Skip all actions
if msg.Type != room.MsgMessage {
continue
}
// Check expected type
if msg.Message.Type != typ {
// Oh noes
m.t.Fatalf("Expected message \"%s\" but got \"%s\" (%v)", typ, msg.Message.Type, msg.Message.Data)
}
return msg.Message
case <-time.After(m.timeout):
m.t.Fatalf("Expected message \"%s\" but found nothing (timeout)!", typ)
return nil
}
}
}
func (m *MockServer) multiexpect(types ...string) {
for {
if len(types) < 1 {
return
}
select {
case msg := <-m.out:
// Skip all actions
if msg.Type != room.MsgMessage {
continue
}
m.t.Logf("-> [%s] %s", msg.Message.Type, msg.Message.Message)
// Check expected type
found := false
for i, typ := range types {
if typ == msg.Message.Type {
found = true
types[i] = types[len(types)-1]
types = types[:len(types)-1]
break
}
}
if !found {
// Oh noes
m.t.Fatalf("Expected one of %s but got \"%s\"", types, msg.Message.Type)
}
case <-time.After(m.timeout):
m.t.Fatalf("Expected one of %s but found nothing (timeout)!", types)
}
}
}
func (m *MockServer) message(from string, typ string, data interface{}) {
m.t.Logf("<- <%s> %s (%v)", from, typ, data)
m.in <- room.ServerMessage{
RoomID: m.roomName,
Type: room.MsgMessage,
Message: &room.Message{
From: from,
To: TestBotName,
Type: typ,
Data: data,
},
}
}
func TestMain(m *testing.M) {
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
switch req.URL.Path {
case "/nopenope.json":
// 404
http.Error(res, "Not found", http.StatusNotFound)
case "/broken.json":
// Broken response
fmt.Fprintf(res, "{{{{")
case "/pr.json":
fmt.Fprintf(res, mlptestdata.SetPremiere)
case "/cube":
fmt.Fprintf(res, testdata.TestGenericCubeFile)
case "/i8pcube":
fmt.Fprintf(res, testdata.TestI8PCubeFile)
default:
fmt.Fprintf(res, mlptestdata.SetFriendForever)
}
}))
mlp.HTTPSource = testServer.URL + "/"
defer testServer.Close()
os.Exit(m.Run())
}