/
githubmirror
/
client
Обзор
Документация
Войти
/
githubmirror
/
client
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
go/chat/flipmanager_test.go
561 строка
21 KB
chrisnojima-zoom
Version 670 - clean2 (#29122)
08 июн 2026, 19:31
Не верифицирован
08 июн 2026, 19:31
1943197
Код
Авторство
О чём код?
package chat import ( "context" "fmt" "strings" "testing" "time" "github.com/keybase/client/go/chat/flip" "github.com/keybase/client/go/chat/globals" "github.com/keybase/client/go/chat/types" "github.com/keybase/client/go/externalstest" "github.com/keybase/client/go/kbtest" "github.com/keybase/client/go/protocol/chat1" "github.com/keybase/client/go/protocol/gregor1" "github.com/keybase/clockwork" "github.com/stretchr/testify/require" ) func consumeFlipToResult(t *testing.T, ui *kbtest.ChatUI, listener *serverChatListener, gameID chat1.FlipGameIDStr, numUsers int, ) string { timeout := 20 * time.Second consumeNewMsgRemote(t, listener, chat1.MessageType_FLIP) // host msg for { select { case updates := <-ui.CoinFlipUpdates: require.Equal(t, 1, len(updates)) t.Logf("update: %v gameID: %s", updates[0].Phase, updates[0].GameID) if updates[0].Phase == chat1.UICoinFlipPhase_COMPLETE { if updates[0].GameID != gameID { // it is possible for a game to produce more than one complete update // so if we get one for a different game, just skip it t.Logf("skipping complete: looking: %s found: %s", gameID, updates[0].GameID) continue } require.Equal(t, numUsers, len(updates[0].Participants)) return updates[0].ResultText } case <-time.After(timeout): require.Fail(t, "no complete") } } } func assertNoFlip(t *testing.T, ui *kbtest.ChatUI) { select { case <-ui.CoinFlipUpdates: require.Fail(t, "unexpected coinflip update") default: } } func TestFlipManagerStartFlip(t *testing.T) { t.Skip() runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { runWithEphemeral(t, mt, func(ephemeralLifetime *gregor1.DurationSec) { ctc := makeChatTestContext(t, "FlipManagerStartFlip", 3) defer ctc.cleanup() users := ctc.users() numUsers := 3 flip.DefaultCommitmentWindowMsec = 2000 var ui0, ui1, ui2 *kbtest.ChatUI ui0 = kbtest.NewChatUI() ui1 = kbtest.NewChatUI() ui2 = kbtest.NewChatUI() ctc.as(t, users[0]).h.mockChatUI = ui0 ctc.as(t, users[1]).h.mockChatUI = ui1 ctc.as(t, users[2]).h.mockChatUI = ui2 ctc.world.Tcs[users[0].Username].G.UIRouter = kbtest.NewMockUIRouter(ui0) ctc.world.Tcs[users[1].Username].G.UIRouter = kbtest.NewMockUIRouter(ui1) ctc.world.Tcs[users[2].Username].G.UIRouter = kbtest.NewMockUIRouter(ui2) listener0 := newServerChatListener() listener1 := newServerChatListener() listener2 := newServerChatListener() ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener0) ctc.as(t, users[1]).h.G().NotifyRouter.AddListener(listener1) ctc.as(t, users[2]).h.G().NotifyRouter.AddListener(listener2) t.Logf("uid0: %s", users[0].GetUID()) t.Logf("uid1: %s", users[1].GetUID()) t.Logf("uid2: %s", users[2].GetUID()) conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt, ctc.as(t, users[1]).user(), ctc.as(t, users[2]).user()) consumeNewConversation(t, listener0, conv.Id) consumeNewConversation(t, listener1, conv.Id) consumeNewConversation(t, listener2, conv.Id) var policy *chat1.RetentionPolicy if ephemeralLifetime != nil { p := chat1.NewRetentionPolicyWithEphemeral(chat1.RpEphemeral{Age: *ephemeralLifetime}) policy = &p mustSetConvRetentionLocal(t, ctc, users[0], conv.Id, p) consumeNewMsgRemote(t, listener0, chat1.MessageType_SYSTEM) consumeNewMsgRemote(t, listener1, chat1.MessageType_SYSTEM) consumeNewMsgRemote(t, listener2, chat1.MessageType_SYSTEM) } var flipMsgs []chat1.UIMessage expectedDevConvs := 0 // bool expectedDevConvs++ mustPostLocalForTest(t, ctc, users[0], conv, chat1.NewMessageBodyWithText(chat1.MessageText{ Body: "/flip", })) flipMsg := consumeNewMsgRemote(t, listener0, chat1.MessageType_FLIP) flipMsgs = append(flipMsgs, flipMsg) require.True(t, flipMsg.IsValid()) require.NotNil(t, flipMsg.Valid().FlipGameID) gameID := *flipMsg.Valid().FlipGameID consumeNewMsgRemote(t, listener1, chat1.MessageType_FLIP) consumeNewMsgRemote(t, listener2, chat1.MessageType_FLIP) res0 := consumeFlipToResult(t, ui0, listener0, gameID, numUsers) t.Logf("res0 (coin): %s", res0) require.True(t, res0 == "HEADS" || res0 == "TAILS") res1 := consumeFlipToResult(t, ui1, listener1, gameID, numUsers) require.Equal(t, res0, res1) res2 := consumeFlipToResult(t, ui2, listener2, gameID, numUsers) require.Equal(t, res0, res2) // limit expectedDevConvs++ mustPostLocalForTest(t, ctc, users[0], conv, chat1.NewMessageBodyWithText(chat1.MessageText{ Body: "/flip 10", })) flipMsg = consumeNewMsgRemote(t, listener0, chat1.MessageType_FLIP) flipMsgs = append(flipMsgs, flipMsg) require.True(t, flipMsg.IsValid()) require.NotNil(t, flipMsg.Valid().FlipGameID) gameID = *flipMsg.Valid().FlipGameID consumeNewMsgRemote(t, listener1, chat1.MessageType_FLIP) consumeNewMsgRemote(t, listener2, chat1.MessageType_FLIP) res0 = consumeFlipToResult(t, ui0, listener0, gameID, numUsers) found := false t.Logf("res0 (limit): %s", res0) for i := 1; i <= 10; i++ { if res0 == fmt.Sprintf("%d", i) { found = true break } } require.True(t, found) res1 = consumeFlipToResult(t, ui1, listener1, gameID, numUsers) require.Equal(t, res0, res1) res2 = consumeFlipToResult(t, ui2, listener2, gameID, numUsers) require.Equal(t, res0, res2) // range expectedDevConvs++ mustPostLocalForTest(t, ctc, users[0], conv, chat1.NewMessageBodyWithText(chat1.MessageText{ Body: "/flip 10..15", })) flipMsg = consumeNewMsgRemote(t, listener0, chat1.MessageType_FLIP) flipMsgs = append(flipMsgs, flipMsg) require.True(t, flipMsg.IsValid()) require.NotNil(t, flipMsg.Valid().FlipGameID) gameID = *flipMsg.Valid().FlipGameID consumeNewMsgRemote(t, listener1, chat1.MessageType_FLIP) consumeNewMsgRemote(t, listener2, chat1.MessageType_FLIP) res0 = consumeFlipToResult(t, ui0, listener0, gameID, numUsers) t.Logf("res0 (range): %s", res0) found = false for i := 10; i <= 15; i++ { if res0 == fmt.Sprintf("%d", i) { found = true break } } require.True(t, found) res1 = consumeFlipToResult(t, ui1, listener1, gameID, numUsers) require.Equal(t, res0, res1) res2 = consumeFlipToResult(t, ui2, listener2, gameID, numUsers) require.Equal(t, res0, res2) // shuffle ref := []string{"mike", "karen", "lisa", "sara", "anna"} refMap := make(map[string]bool) for _, r := range ref { refMap[r] = true } expectedDevConvs++ mustPostLocalForTest(t, ctc, users[0], conv, chat1.NewMessageBodyWithText(chat1.MessageText{ Body: fmt.Sprintf("/flip %s", strings.Join(ref, ",")), })) flipMsg = consumeNewMsgRemote(t, listener0, chat1.MessageType_FLIP) flipMsgs = append(flipMsgs, flipMsg) require.True(t, flipMsg.IsValid()) require.NotNil(t, flipMsg.Valid().FlipGameID) gameID = *flipMsg.Valid().FlipGameID consumeNewMsgRemote(t, listener1, chat1.MessageType_FLIP) consumeNewMsgRemote(t, listener2, chat1.MessageType_FLIP) res0 = consumeFlipToResult(t, ui0, listener0, gameID, numUsers) t.Logf("res0 (shuffle): %s", res0) toks := strings.SplitSeq(res0, ",") for t := range toks { delete(refMap, strings.Trim(t, " ")) } require.Zero(t, len(refMap)) require.True(t, found) res1 = consumeFlipToResult(t, ui1, listener1, gameID, numUsers) require.Equal(t, res0, res1) res2 = consumeFlipToResult(t, ui2, listener2, gameID, numUsers) require.Equal(t, res0, res2) uid := users[0].User.GetUID().ToBytes() ttype := chat1.TopicType_DEV ctx := ctc.as(t, users[0]).startCtx ibox, _, err := ctc.as(t, users[0]).h.G().InboxSource.Read(ctx, uid, types.ConversationLocalizerBlocking, types.InboxSourceDataSourceAll, nil, &chat1.GetInboxLocalQuery{ TopicType: &ttype, }) require.NoError(t, err) numConvs := 0 for _, conv := range ibox.Convs { if strings.HasPrefix(conv.Info.TopicName, gameIDTopicNamePrefix) { numConvs++ require.Equal(t, policy, conv.ConvRetention) } } require.Equal(t, expectedDevConvs, numConvs) for _, flipMsg := range flipMsgs { mustDeleteMsg(ctx, t, ctc, users[0], conv, flipMsg.GetMessageID()) consumeNewMsgRemote(t, listener1, chat1.MessageType_DELETE) consumeNewMsgRemote(t, listener2, chat1.MessageType_DELETE) } ibox, _, err = ctc.as(t, users[0]).h.G().InboxSource.Read(ctx, uid, types.ConversationLocalizerBlocking, types.InboxSourceDataSourceAll, nil, &chat1.GetInboxLocalQuery{ TopicType: &ttype, }) require.NoError(t, err) require.Zero(t, len(ibox.Convs)) }) }) } func TestFlipManagerChannelFlip(t *testing.T) { // ensure only members of a channel are included in the flip runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { switch mt { case chat1.ConversationMembersType_TEAM: default: return } ctc := makeChatTestContext(t, "FlipManagerChannelFlip", 3) defer ctc.cleanup() users := ctc.users() flip.DefaultCommitmentWindowMsec = 500 var ui0, ui1, ui2 *kbtest.ChatUI ui0 = kbtest.NewChatUI() ui1 = kbtest.NewChatUI() ui2 = kbtest.NewChatUI() ctc.as(t, users[0]).h.mockChatUI = ui0 ctc.as(t, users[1]).h.mockChatUI = ui1 ctc.as(t, users[2]).h.mockChatUI = ui2 ctc.world.Tcs[users[0].Username].G.UIRouter = kbtest.NewMockUIRouter(ui0) ctc.world.Tcs[users[1].Username].G.UIRouter = kbtest.NewMockUIRouter(ui1) ctc.world.Tcs[users[2].Username].G.UIRouter = kbtest.NewMockUIRouter(ui2) listener0 := newServerChatListener() listener1 := newServerChatListener() listener2 := newServerChatListener() ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener0) ctc.as(t, users[1]).h.G().NotifyRouter.AddListener(listener1) ctc.as(t, users[2]).h.G().NotifyRouter.AddListener(listener2) conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt, ctc.as(t, users[1]).user(), ctc.as(t, users[2]).user()) consumeNewConversation(t, listener0, conv.Id) consumeNewConversation(t, listener1, conv.Id) consumeNewConversation(t, listener2, conv.Id) topicName := "channel-1" channel := mustCreateChannelForTest(t, ctc, users[0], chat1.TopicType_CHAT, &topicName, mt, ctc.as(t, users[1]).user(), ctc.as(t, users[2]).user()) consumeNewMsgRemote(t, listener0, chat1.MessageType_JOIN) consumeNewMsgRemote(t, listener0, chat1.MessageType_SYSTEM) consumeNewMsgRemote(t, listener0, chat1.MessageType_SYSTEM) consumeNewMsgRemote(t, listener1, chat1.MessageType_SYSTEM) consumeNewMsgRemote(t, listener1, chat1.MessageType_SYSTEM) consumeNewMsgRemote(t, listener2, chat1.MessageType_SYSTEM) consumeNewMsgRemote(t, listener2, chat1.MessageType_SYSTEM) mustJoinConversationByID(t, ctc, users[1], channel.Id) consumeNewMsgRemote(t, listener0, chat1.MessageType_JOIN) consumeNewMsgRemote(t, listener1, chat1.MessageType_JOIN) mustJoinConversationByID(t, ctc, users[2], channel.Id) _, err := ctc.as(t, users[2]).chatLocalHandler().LeaveConversationLocal( ctc.as(t, users[0]).startCtx, channel.Id) require.NoError(t, err) consumeNewMsgRemote(t, listener0, chat1.MessageType_JOIN) consumeNewMsgRemote(t, listener1, chat1.MessageType_JOIN) consumeNewMsgRemote(t, listener2, chat1.MessageType_JOIN) consumeNewMsgRemote(t, listener0, chat1.MessageType_LEAVE) consumeNewMsgRemote(t, listener1, chat1.MessageType_LEAVE) mustPostLocalForTest(t, ctc, users[0], channel, chat1.NewMessageBodyWithText(chat1.MessageText{ Body: "/flip", })) flipMsg := consumeNewMsgRemote(t, listener0, chat1.MessageType_FLIP) require.True(t, flipMsg.IsValid()) require.NotNil(t, flipMsg.Valid().FlipGameID) gameID := *flipMsg.Valid().FlipGameID consumeNewMsgRemote(t, listener1, chat1.MessageType_FLIP) res0 := consumeFlipToResult(t, ui0, listener0, gameID, 2) require.True(t, res0 == "HEADS" || res0 == "TAILS") res1 := consumeFlipToResult(t, ui1, listener1, gameID, 2) require.Equal(t, res0, res1) assertNoFlip(t, ui2) }) } func TestFlipManagerParseEdges(t *testing.T) { tc := externalstest.SetupTest(t, "flip", 0) defer tc.Cleanup() g := globals.NewContext(tc.G, &globals.ChatContext{}) fm := NewFlipManager(g, nil) testCase := func(text string, ftyp flip.FlipType, refMetadata flipTextMetadata) { start, metadata := fm.startFromText(text, nil) ft, err := start.Params.T() require.NoError(t, err) require.Equal(t, ftyp, ft) require.Equal(t, refMetadata, metadata) } deck := "2♠️,3♠️,4♠️,5♠️,6♠️,7♠️,8♠️,9♠️,10♠️,J♠️,Q♠️,K♠️,A♠️,2♣️,3♣️,4♣️,5♣️,6♣️,7♣️,8♣️,9♣️,10♣️,J♣️,Q♣️,K♣️,A♣️,2♦️,3♦️,4♦️,5♦️,6♦️,7♦️,8♦️,9♦️,10♦️,J♦️,Q♦️,K♦️,A♦️,2♥️,3♥️,4♥️,5♥️,6♥️,7♥️,8♥️,9♥️,10♥️,J♥️,Q♥️,K♥️,A♥️" cards := strings.Split(deck, ",") testCase("/flip 10", flip.FlipType_BIG, flipTextMetadata{LowerBound: "1"}) testCase("/flip 0", flip.FlipType_SHUFFLE, flipTextMetadata{ShuffleItems: []string{"0"}}) testCase("/flip -1", flip.FlipType_SHUFFLE, flipTextMetadata{ShuffleItems: []string{"-1"}}) testCase("/flip 1..5", flip.FlipType_BIG, flipTextMetadata{LowerBound: "1"}) testCase("/flip -20..20", flip.FlipType_BIG, flipTextMetadata{LowerBound: "-20"}) testCase("/flip -20..20,mike", flip.FlipType_SHUFFLE, flipTextMetadata{ShuffleItems: []string{"-20..20", "mike"}}) testCase("/flip 1..1", flip.FlipType_BIG, flipTextMetadata{LowerBound: "1"}) testCase("/flip 1..0", flip.FlipType_SHUFFLE, flipTextMetadata{ShuffleItems: []string{"1..0"}}) testCase("/flip mike, karen, jim", flip.FlipType_SHUFFLE, flipTextMetadata{ShuffleItems: []string{"mike", "karen", "jim"}}) testCase("/flip mike, jim bob j , jim", flip.FlipType_SHUFFLE, flipTextMetadata{ShuffleItems: []string{"mike", "jim bob j", "jim"}}) testCase("/flip 10...20", flip.FlipType_SHUFFLE, flipTextMetadata{ShuffleItems: []string{"10...20"}}) testCase("/flip 1,0", flip.FlipType_SHUFFLE, flipTextMetadata{ShuffleItems: []string{"1", "0"}}) testCase("/flip 1,0", flip.FlipType_SHUFFLE, flipTextMetadata{ShuffleItems: []string{"1", "0"}}) testCase("/flip cards", flip.FlipType_SHUFFLE, flipTextMetadata{ ShuffleItems: cards, DeckShuffle: true, }) testCase("/flip cards 5 mikem, joshblum, chris", flip.FlipType_SHUFFLE, flipTextMetadata{ ShuffleItems: cards, DeckShuffle: false, HandCardCount: 5, HandTargets: []string{"mikem", "joshblum", "chris"}, }) testCase("/flip cards 5 mike maxim, lisa maxim, anna ", flip.FlipType_SHUFFLE, flipTextMetadata{ ShuffleItems: cards, DeckShuffle: false, HandCardCount: 5, HandTargets: []string{"mike maxim", "lisa maxim", "anna"}, }) testCase("/flip cards 5 mikem, , , joshblum, chris", flip.FlipType_SHUFFLE, flipTextMetadata{ ShuffleItems: cards, DeckShuffle: false, HandCardCount: 5, HandTargets: []string{"mikem", "joshblum", "chris"}, }) testCase("/flip cards 5", flip.FlipType_SHUFFLE, flipTextMetadata{ ShuffleItems: cards, DeckShuffle: true, }) testCase("/flip cards -5 chris mike", flip.FlipType_SHUFFLE, flipTextMetadata{ ShuffleItems: cards, DeckShuffle: true, }) } func TestFlipManagerLoadFlip(t *testing.T) { t.Skip() runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { ctc := makeChatTestContext(t, "FlipManager", 2) defer ctc.cleanup() users := ctc.users() ui0 := kbtest.NewChatUI() ui1 := kbtest.NewChatUI() ctc.as(t, users[0]).h.mockChatUI = ui0 ctc.as(t, users[1]).h.mockChatUI = ui1 ctc.world.Tcs[users[0].Username].G.UIRouter = kbtest.NewMockUIRouter(ui0) ctc.world.Tcs[users[1].Username].G.UIRouter = kbtest.NewMockUIRouter(ui1) ctx := ctc.as(t, users[0]).startCtx tc := ctc.world.Tcs[users[0].Username] uid := users[0].User.GetUID().ToBytes() listener0 := newServerChatListener() listener1 := newServerChatListener() ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener0) ctc.as(t, users[1]).h.G().NotifyRouter.AddListener(listener1) flip.DefaultCommitmentWindowMsec = 500 timeout := 20 * time.Second ctc.world.Tcs[users[0].Username].ChatG.Syncer.(*Syncer).isConnected = true conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt, ctc.as(t, users[1]).user()) mustPostLocalForTest(t, ctc, users[0], conv, chat1.NewMessageBodyWithText(chat1.MessageText{ Body: "/flip", })) flipMsg := consumeNewMsgRemote(t, listener0, chat1.MessageType_FLIP) require.True(t, flipMsg.IsValid()) require.NotNil(t, flipMsg.Valid().FlipGameID) strGameID := *flipMsg.Valid().FlipGameID consumeNewMsgRemote(t, listener1, chat1.MessageType_FLIP) res := consumeFlipToResult(t, ui0, listener0, strGameID, 2) require.True(t, res == "HEADS" || res == "TAILS") res1 := consumeFlipToResult(t, ui1, listener1, strGameID, 2) require.Equal(t, res, res1) hostMsg, err := tc.Context().ConvSource.GetMessage(ctx, conv.Id, uid, 2, nil, nil, true) require.NoError(t, err) require.True(t, hostMsg.IsValid()) body := hostMsg.Valid().MessageBody require.True(t, body.IsType(chat1.MessageType_FLIP)) gameID := body.Flip().GameID testLoadFlip := func() { tc.Context().CoinFlipManager.LoadFlip(ctx, uid, conv.Id, hostMsg.GetMessageID(), body.Flip().FlipConvID, gameID) select { case updates := <-ui0.CoinFlipUpdates: require.Equal(t, 1, len(updates)) require.Equal(t, chat1.UICoinFlipPhase_COMPLETE, updates[0].Phase) require.Equal(t, conv.Id.ConvIDStr(), updates[0].ConvID) require.Equal(t, res, updates[0].ResultText) case <-time.After(timeout): require.Fail(t, "no updates") } } testLoadFlip() err = tc.Context().ConvSource.Clear(ctx, conv.Id, uid, nil) require.NoError(t, err) tc.Context().CoinFlipManager.(*FlipManager).clearGameCache() testLoadFlip() }) } func TestFlipManagerRateLimit(t *testing.T) { t.Skip() ctc := makeChatTestContext(t, "TestFlipManagerRateLimit", 2) defer ctc.cleanup() users := ctc.users() useRemoteMock = false defer func() { useRemoteMock = true }() ui0 := kbtest.NewChatUI() ui1 := kbtest.NewChatUI() ctc.as(t, users[0]).h.mockChatUI = ui0 ctc.as(t, users[1]).h.mockChatUI = ui1 ctc.world.Tcs[users[0].Username].G.UIRouter = kbtest.NewMockUIRouter(ui0) ctc.world.Tcs[users[1].Username].G.UIRouter = kbtest.NewMockUIRouter(ui1) listener0 := newServerChatListener() listener1 := newServerChatListener() ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener0) ctc.as(t, users[1]).h.G().NotifyRouter.AddListener(listener1) flip.DefaultCommitmentWindowMsec = 500 tc := ctc.world.Tcs[users[0].Username] tc1 := ctc.world.Tcs[users[1].Username] clock := clockwork.NewFakeClock() flipmgr := tc.Context().CoinFlipManager.(*FlipManager) flipmgr1 := tc1.Context().CoinFlipManager.(*FlipManager) flipmgr.clock = clock flipmgr1.clock = clock flipmgr.testingServerClock = clock flipmgr1.testingServerClock = clock flipmgr.maxConvParticipations = 1 <-flipmgr.Stop(context.TODO()) <-flipmgr1.Stop(context.TODO()) flipmgr.Start(context.TODO(), gregor1.UID(users[0].GetUID().ToBytes())) flipmgr1.Start(context.TODO(), gregor1.UID(users[1].GetUID().ToBytes())) simRealClock := func(stopCh chan struct{}) { t := time.NewTicker(100 * time.Millisecond) for { select { case <-t.C: clock.Advance(100 * time.Millisecond) case <-stopCh: return } } } t.Logf("uid0: %s", users[0].GetUID()) t.Logf("uid1: %s", users[1].GetUID()) conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, chat1.ConversationMembersType_IMPTEAMNATIVE, ctc.as(t, users[1]).user()) mustPostLocalForTest(t, ctc, users[0], conv, chat1.NewMessageBodyWithText(chat1.MessageText{ Body: "/flip", })) flipMsg := consumeNewMsgRemote(t, listener0, chat1.MessageType_FLIP) require.True(t, flipMsg.IsValid()) require.NotNil(t, flipMsg.Valid().FlipGameID) gameID := *flipMsg.Valid().FlipGameID t.Logf("gameID: %s", gameID) consumeNewMsgRemote(t, listener1, chat1.MessageType_FLIP) stopCh := make(chan struct{}) go simRealClock(stopCh) res := consumeFlipToResult(t, ui0, listener0, gameID, 2) require.True(t, res == "HEADS" || res == "TAILS") res1 := consumeFlipToResult(t, ui1, listener1, gameID, 2) require.Equal(t, res, res1) close(stopCh) clock.Advance(time.Minute) mustPostLocalForTest(t, ctc, users[1], conv, chat1.NewMessageBodyWithText(chat1.MessageText{ Body: "/flip", })) select { case <-ui0.CoinFlipUpdates: require.Fail(t, "no update for 0") default: } flipMsg = consumeNewMsgRemote(t, listener0, chat1.MessageType_FLIP) require.True(t, flipMsg.IsValid()) require.NotNil(t, flipMsg.Valid().FlipGameID) gameID = *flipMsg.Valid().FlipGameID t.Logf("gameID: %s", gameID) stopCh = make(chan struct{}) go simRealClock(stopCh) consumeNewMsgRemote(t, listener0, chat1.MessageType_FLIP) // get host msg res = consumeFlipToResult(t, ui1, listener1, gameID, 1) require.True(t, res == "HEADS" || res == "TAILS") close(stopCh) clock.Advance(10 * time.Minute) mustPostLocalForTest(t, ctc, users[1], conv, chat1.NewMessageBodyWithText(chat1.MessageText{ Body: "/flip", })) flipMsg = consumeNewMsgRemote(t, listener0, chat1.MessageType_FLIP) require.True(t, flipMsg.IsValid()) require.NotNil(t, flipMsg.Valid().FlipGameID) gameID = *flipMsg.Valid().FlipGameID t.Logf("gameID: %s", gameID) consumeNewMsgRemote(t, listener1, chat1.MessageType_FLIP) stopCh = make(chan struct{}) go simRealClock(stopCh) res = consumeFlipToResult(t, ui0, listener0, gameID, 2) require.True(t, res == "HEADS" || res == "TAILS") res1 = consumeFlipToResult(t, ui1, listener1, gameID, 2) require.Equal(t, res, res1) close(stopCh) }