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()) }