/
githubmirror
/
client
Обзор
Документация
Войти
/
githubmirror
/
client
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
go/kbtest/chat.go
1 663 строки
53 KB
zoom-ua
add validateTeamGitChatConv (#29295)
09 июн 2026, 19:55
Не верифицирован
09 июн 2026, 19:55
e658b4a
Код
Авторство
О чём код?
package kbtest import ( "bytes" "context" "crypto/rand" "encoding/hex" "encoding/json" "errors" "fmt" "sort" "strings" "sync" "testing" "time" "github.com/keybase/client/go/badges" "github.com/keybase/client/go/chat/pager" "github.com/stretchr/testify/require" "github.com/keybase/client/go/chat/globals" "github.com/keybase/client/go/chat/types" "github.com/keybase/client/go/chat/utils" "github.com/keybase/client/go/externalstest" "github.com/keybase/client/go/libkb" "github.com/keybase/client/go/protocol/chat1" "github.com/keybase/client/go/protocol/gregor1" "github.com/keybase/client/go/protocol/keybase1" "github.com/keybase/clockwork" ) type ChatTestContext struct { libkb.TestContext ChatG *globals.ChatContext // gregor connection for tests (if using real gregord) GregorConn gregorTestConnectionCloser } func NewMetaContextForTest(c ChatTestContext) libkb.MetaContext { return libkb.NewMetaContextForTest(c.TestContext) } func (c ChatTestContext) Context() *globals.Context { return globals.NewContext(c.G, c.ChatG) } func (c ChatTestContext) Cleanup() { if c.GregorConn != nil { c.GregorConn.Close() } if c.ChatG.PushHandler != nil { <-c.ChatG.PushHandler.Stop(context.TODO()) } if c.ChatG.MessageDeliverer != nil { <-c.ChatG.MessageDeliverer.Stop(context.TODO()) } if c.ChatG.ConvLoader != nil { <-c.ChatG.ConvLoader.Stop(context.TODO()) } if c.ChatG.FetchRetrier != nil { <-c.ChatG.FetchRetrier.Stop(context.TODO()) } if c.ChatG.EphemeralPurger != nil { <-c.ChatG.EphemeralPurger.Stop(context.TODO()) } if c.ChatG.EphemeralTracker != nil { <-c.ChatG.EphemeralTracker.Stop(context.TODO()) } if c.ChatG.InboxSource != nil { <-c.ChatG.InboxSource.Stop(context.TODO()) } if c.ChatG.Indexer != nil { <-c.ChatG.Indexer.Stop(context.TODO()) } if c.ChatG.CoinFlipManager != nil { <-c.ChatG.CoinFlipManager.Stop(context.TODO()) } if c.ChatG.BotCommandManager != nil { <-c.ChatG.BotCommandManager.Stop(context.TODO()) } if c.ChatG.UIInboxLoader != nil { <-c.ChatG.UIInboxLoader.Stop(context.TODO()) } c.TestContext.Cleanup() } type gregorTestConnectionCloser interface { Close() } type ChatMockWorld struct { Fc clockwork.FakeClock T testing.TB Tcs map[string]*ChatTestContext TcsByID map[string]*ChatTestContext Users map[string]*FakeUser tlfs map[keybase1.CanonicalTlfName]chat1.TLFID tlfKeys map[keybase1.CanonicalTlfName][]keybase1.CryptKey // should always be sorted by newly updated conversation first conversations []*chat1.Conversation // each slice should always be sorted by message ID in desc, i.e. newest messages first Msgs map[chat1.ConvIDStr][]*chat1.MessageBoxed } func NewChatMockWorld(t *testing.T, name string, numUsers int) (world *ChatMockWorld) { world = &ChatMockWorld{ T: t, Fc: clockwork.NewFakeClockAt(time.Now()), Tcs: make(map[string]*ChatTestContext), TcsByID: make(map[string]*ChatTestContext), Users: make(map[string]*FakeUser), tlfs: make(map[keybase1.CanonicalTlfName]chat1.TLFID), tlfKeys: make(map[keybase1.CanonicalTlfName][]keybase1.CryptKey), Msgs: make(map[chat1.ConvIDStr][]*chat1.MessageBoxed), } for range numUsers { kbTc := externalstest.SetupTest(t, "chat_"+name, 0) tc := ChatTestContext{ TestContext: kbTc, ChatG: &globals.ChatContext{}, } tc.ChatG.Badger = badges.NewBadger(kbTc.G) tc.G.SetClock(world.Fc) u, err := CreateAndSignupFakeUser("chat", tc.G) if err != nil { require.NoError(t, err) } world.Users[u.Username] = u world.Tcs[u.Username] = &tc world.TcsByID[u.User.GetUID().String()] = &tc } world.Fc.Advance(time.Hour) return world } func (w *ChatMockWorld) Cleanup() { for _, tc := range w.Tcs { tc.Cleanup() } } func (w *ChatMockWorld) GetConversationByID(convID chat1.ConversationID) *chat1.Conversation { for _, conv := range w.conversations { if conv.Metadata.ConversationID.ConvIDStr() == convID.ConvIDStr() { return conv } } return nil } type ByUsername []*FakeUser func (m ByUsername) Len() int { return len(m) } func (m ByUsername) Swap(i, j int) { m[i], m[j] = m[j], m[i] } func (m ByUsername) Less(i, j int) bool { res := strings.Compare(m[i].Username, m[j].Username) return res < 0 } func (w *ChatMockWorld) GetUsers() (res []*FakeUser) { for _, v := range w.Users { res = append(res, v) } sort.Sort(ByUsername(res)) return res } func mustGetRandBytesWithControlledFirstByte(n int, controlled byte) (b []byte) { b = make([]byte, n) if nn, err := rand.Read(b); err != nil || n != nn { panic("oops") } b[0] = controlled // in case we have a collision? return b } func mustGetRandCryptKeys(controlled byte) []keybase1.CryptKey { key := keybase1.CryptKey{ KeyGeneration: 1, } copy(key.Key[:], mustGetRandBytesWithControlledFirstByte(32, controlled)) return []keybase1.CryptKey{key} } type TlfMock struct { sync.Mutex world *ChatMockWorld } func NewTlfMock(world *ChatMockWorld) *TlfMock { return &TlfMock{world: world} } func CanonicalTlfNameForTest(tlfName string) keybase1.CanonicalTlfName { // very much simplified canonicalization. // TODO: implement rest when we need it var names []string nameMap := make(map[string]bool) rawNames := strings.SplitSeq(tlfName, ",") for rn := range rawNames { if nameMap[rn] { continue } names = append(names, rn) nameMap[rn] = true } sort.Strings(names) return keybase1.CanonicalTlfName(strings.Join(names, ",")) } func (m *TlfMock) newTLFID() chat1.TLFID { suffix := byte(0x29) idBytes, err := libkb.RandBytesWithSuffix(16, suffix) if err != nil { panic("RandBytes failed: " + err.Error()) } return chat1.TLFID(idBytes) } func (m *TlfMock) getTlfID(cname keybase1.CanonicalTlfName) (keybase1.TLFID, error) { m.Lock() defer m.Unlock() tlfID, ok := m.world.tlfs[cname] if !ok { for n := range strings.SplitSeq(string(cname), ",") { if m.world.Users[n] == nil { return "", fmt.Errorf("user %s not found", n) } } tlfID = m.newTLFID() m.world.tlfs[cname] = tlfID m.world.tlfKeys[cname] = mustGetRandCryptKeys(byte(len(m.world.tlfKeys) + 1)) } return keybase1.TLFID(hex.EncodeToString([]byte(tlfID))), nil } func (m *TlfMock) AllCryptKeys(ctx context.Context, tlfName string, public bool) (res types.AllCryptKeys, err error) { cres, err := m.CryptKeys(ctx, tlfName) if err != nil { return res, err } res = types.NewAllCryptKeys() for _, key := range cres.CryptKeys { res[chat1.ConversationMembersType_KBFS] = append(res[chat1.ConversationMembersType_KBFS], key) res[chat1.ConversationMembersType_TEAM] = append(res[chat1.ConversationMembersType_TEAM], key) } return res, nil } func (m *TlfMock) LookupName(ctx context.Context, tlfID chat1.TLFID, public bool, tlfName string) (res types.NameInfo, err error) { return m.LookupID(ctx, tlfName, public) } func (m *TlfMock) TeamBotSettings(ctx context.Context, tlfName string, tlfID chat1.TLFID, membersType chat1.ConversationMembersType, public bool, ) (map[keybase1.UserVersion]keybase1.TeamBotSettings, error) { return make(map[keybase1.UserVersion]keybase1.TeamBotSettings), nil } func (m *TlfMock) LookupID(ctx context.Context, tlfName string, public bool) (res types.NameInfo, err error) { var tlfID keybase1.TLFID name := CanonicalTlfNameForTest(tlfName) res.CanonicalName = name.String() if tlfID, err = m.getTlfID(name); err != nil { return res, err } res.ID = tlfID.ToBytes() return res, nil } func (m *TlfMock) EncryptionKey(ctx context.Context, tlfName string, tlfID chat1.TLFID, membersType chat1.ConversationMembersType, public bool, botUID *gregor1.UID, ) (key types.CryptKey, ni types.NameInfo, err error) { if botUID != nil { return key, ni, fmt.Errorf("TeambotKeys not supported by KBFS") } if ni, err = m.LookupID(ctx, tlfName, public); err != nil { return key, ni, err } if public { var zero [libkb.NaclDHKeySecretSize]byte return keybase1.CryptKey{ KeyGeneration: 1, Key: keybase1.Bytes32(zero), }, ni, nil } allKeys, err := m.AllCryptKeys(ctx, tlfName, public) if err != nil { return key, ni, err } keys := allKeys[chat1.ConversationMembersType_KBFS] return keys[len(keys)-1], ni, nil } func (m *TlfMock) DecryptionKey(ctx context.Context, tlfName string, tlfID chat1.TLFID, membersType chat1.ConversationMembersType, public bool, keyGeneration int, kbfsEncrypted bool, botUID *gregor1.UID, ) (types.CryptKey, error) { if botUID != nil { return nil, fmt.Errorf("TeambotKeys not supported by KBFS") } if public { var zero [libkb.NaclDHKeySecretSize]byte return keybase1.CryptKey{ KeyGeneration: 1, Key: keybase1.Bytes32(zero), }, nil } allkeys, err := m.AllCryptKeys(ctx, tlfName, public) if err != nil { return nil, err } keys := allkeys[chat1.ConversationMembersType_KBFS] for _, key := range keys { if key.Generation() == keyGeneration { return key, nil } } return nil, errors.New("no mock key found") } func (m *TlfMock) EphemeralEncryptionKey(mctx libkb.MetaContext, tlfName string, tlfID chat1.TLFID, membersType chat1.ConversationMembersType, public bool, botUID *gregor1.UID, ) (types.EphemeralCryptKey, error) { // Returns a totally zero teamEK. That's enough to get some very simple // round trip tests to pass. return keybase1.TeamEphemeralKey{}, nil } func (m *TlfMock) EphemeralDecryptionKey(mctx libkb.MetaContext, tlfName string, tlfID chat1.TLFID, membersType chat1.ConversationMembersType, public bool, botUID *gregor1.UID, generation keybase1.EkGeneration, contentCtime *gregor1.Time, ) (types.EphemeralCryptKey, error) { // Returns a totally zero teamEK. That's enough to get some very simple // round trip tests to pass. return keybase1.TeamEphemeralKey{}, nil } func (m *TlfMock) ShouldPairwiseMAC(ctx context.Context, tlfName string, tlfID chat1.TLFID, membersType chat1.ConversationMembersType, public bool, ) (bool, []keybase1.KID, error) { return false, nil, nil } func (m *TlfMock) CryptKeys(ctx context.Context, tlfName string) (res keybase1.GetTLFCryptKeysRes, err error) { res.NameIDBreaks.CanonicalName = CanonicalTlfNameForTest(tlfName) if res.NameIDBreaks.TlfID, err = m.getTlfID(res.NameIDBreaks.CanonicalName); err != nil { return keybase1.GetTLFCryptKeysRes{}, err } var ok bool if res.CryptKeys, ok = m.world.tlfKeys[res.NameIDBreaks.CanonicalName]; !ok { err = fmt.Errorf("CryptKeys for TLF %s not found", res.NameIDBreaks.CanonicalName) return res, err } return res, nil } func (m *TlfMock) CompleteAndCanonicalizePrivateTlfName(ctx context.Context, tlfName string) (keybase1.CanonicalTLFNameAndIDWithBreaks, error) { var res keybase1.CanonicalTLFNameAndIDWithBreaks res.CanonicalName = CanonicalTlfNameForTest(tlfName) var err error res.TlfID, err = m.getTlfID(res.CanonicalName) if err != nil { return keybase1.CanonicalTLFNameAndIDWithBreaks{}, err } return res, nil } func (m *TlfMock) PublicCanonicalTLFNameAndID(ctx context.Context, tlfName string) (keybase1.CanonicalTLFNameAndIDWithBreaks, error) { res := keybase1.CanonicalTLFNameAndIDWithBreaks{ CanonicalName: keybase1.CanonicalTlfName(tlfName), TlfID: "abcdefg", } return res, nil } type ChatRemoteMockServerConnection struct { mock *ChatRemoteMock } func NewChatRemoteMockServerConnection(mock *ChatRemoteMock) *ChatRemoteMockServerConnection { return &ChatRemoteMockServerConnection{ mock: mock, } } func (m ChatRemoteMockServerConnection) GetClient() chat1.RemoteInterface { return m.mock } func (m ChatRemoteMockServerConnection) Reconnect(ctx context.Context) (bool, error) { return false, nil } type ChatRemoteMock struct { world *ChatMockWorld readMsgid map[chat1.ConvIDStr]chat1.MessageID uid *gregor1.UID CacheBodiesVersion int CacheInboxVersion int GetThreadRemoteFunc func(m *ChatRemoteMock, ctx context.Context, arg chat1.GetThreadRemoteArg) (chat1.GetThreadRemoteRes, error) SyncInboxFunc func(m *ChatRemoteMock, ctx context.Context, vers chat1.InboxVers) (chat1.SyncInboxRes, error) } var _ chat1.RemoteInterface = (*ChatRemoteMock)(nil) func NewChatRemoteMock(world *ChatMockWorld) (m *ChatRemoteMock) { m = &ChatRemoteMock{ world: world, readMsgid: make(map[chat1.ConvIDStr]chat1.MessageID), } return m } func (m *ChatRemoteMock) makeReaderInfo(convID chat1.ConversationID) (ri *chat1.ConversationReaderInfo) { ri = &chat1.ConversationReaderInfo{} convIDStr := convID.ConvIDStr() ri.ReadMsgid = m.readMsgid[convIDStr] for _, m := range m.world.Msgs[convIDStr] { if m.ServerHeader.MessageID > ri.MaxMsgid { ri.MaxMsgid = m.ServerHeader.MessageID ri.Mtime = m.ServerHeader.Ctime } } return ri } func (m *ChatRemoteMock) SetCurrentUser(uid gregor1.UID) { m.uid = &uid } func (m *ChatRemoteMock) inConversation(conv *chat1.Conversation) bool { if m.uid != nil && len(conv.Metadata.ActiveList) > 0 { return conv.Includes(*m.uid) } return true } func (m *ChatRemoteMock) RemoteNotificationSuccessful(ctx context.Context, arg chat1.RemoteNotificationSuccessfulArg) error { return nil } func (m *ChatRemoteMock) GetInboxRemote(ctx context.Context, arg chat1.GetInboxRemoteArg) (res chat1.GetInboxRemoteRes, err error) { // TODO: add pagination support var ibfull chat1.InboxViewFull for _, conv := range m.world.conversations { if !m.inConversation(conv) { continue } if arg.Query != nil { if arg.Query.ConvID != nil { arg.Query.ConvIDs = append(arg.Query.ConvIDs, *arg.Query.ConvID) } if len(arg.Query.ConvIDs) > 0 { found := false for _, convID := range arg.Query.ConvIDs { if convID.Eq(conv.GetConvID()) { found = true break } } if !found { continue } } if arg.Query.ConvID != nil && !conv.Metadata.ConversationID.Eq(*arg.Query.ConvID) { continue } if arg.Query.TlfID != nil && !conv.Metadata.IdTriple.Tlfid.Eq(*arg.Query.TlfID) { continue } if arg.Query.TopicType != nil && conv.Metadata.IdTriple.TopicType != *arg.Query.TopicType { continue } if arg.Query.UnreadOnly && m.readMsgid[conv.Metadata.ConversationID.ConvIDStr()] == m.makeReaderInfo(conv.Metadata.ConversationID).MaxMsgid { continue } if arg.Query.ReadOnly && m.readMsgid[conv.Metadata.ConversationID.ConvIDStr()] != m.makeReaderInfo(conv.Metadata.ConversationID).MaxMsgid { continue } if arg.Query.TlfVisibility != nil && conv.Metadata.Visibility != *arg.Query.TlfVisibility { continue } if arg.Query.After != nil && m.makeReaderInfo(conv.Metadata.ConversationID).Mtime < *arg.Query.After { continue } if arg.Query.Before != nil && m.makeReaderInfo(conv.Metadata.ConversationID).Mtime > *arg.Query.Before { continue } } convToAppend := *conv convToAppend.ReaderInfo = m.makeReaderInfo(convToAppend.Metadata.ConversationID) convToAppend.Notifications = new(chat1.ConversationNotificationInfo) ibfull.Conversations = append(ibfull.Conversations, convToAppend) if arg.Pagination != nil && arg.Pagination.Num != 0 && arg.Pagination.Num == len(ibfull.Conversations) { break } } return chat1.GetInboxRemoteRes{ Inbox: chat1.NewInboxViewWithFull(ibfull), }, nil } func (m *ChatRemoteMock) GetPublicConversations(ctx context.Context, arg chat1.GetPublicConversationsArg) (res chat1.GetPublicConversationsRes, err error) { for _, conv := range m.world.conversations { if conv.Metadata.Visibility == keybase1.TLFVisibility_PUBLIC && conv.Metadata.IdTriple.Tlfid.Eq(arg.TlfID) && conv.Metadata.IdTriple.TopicType == arg.TopicType { convToAppend := *conv convToAppend.ReaderInfo = m.makeReaderInfo(convToAppend.Metadata.ConversationID) res.Conversations = append(res.Conversations, convToAppend) } } return res, nil } func (m *ChatRemoteMock) GetInboxByTLFIDRemote(ctx context.Context, tlfID chat1.TLFID) (res chat1.GetInboxByTLFIDRemoteRes, err error) { for _, conv := range m.world.conversations { if tlfID.Eq(conv.Metadata.IdTriple.Tlfid) { convToAppend := *conv convToAppend.ReaderInfo = m.makeReaderInfo(convToAppend.Metadata.ConversationID) res.Convs = []chat1.Conversation{convToAppend} return res, nil } } return res, errors.New("conversation not found") } func (m *ChatRemoteMock) GetThreadRemote(ctx context.Context, arg chat1.GetThreadRemoteArg) (res chat1.GetThreadRemoteRes, err error) { if m.GetThreadRemoteFunc != nil { return m.GetThreadRemoteFunc(m, ctx, arg) } var mts map[chat1.MessageType]bool if arg.Query != nil && len(arg.Query.MessageTypes) > 0 { mts = make(map[chat1.MessageType]bool) for _, mt := range arg.Query.MessageTypes { mts[mt] = true } } // TODO: add *real* pagination support if arg.Pagination == nil { arg.Pagination = &chat1.Pagination{Num: 10000} } conv, err := m.GetConversationMetadataRemote(ctx, arg.ConversationID) if err != nil { return res, err } res.MembersType = conv.Conv.GetMembersType() res.Visibility = conv.Conv.Metadata.Visibility msgs := m.world.Msgs[arg.ConversationID.ConvIDStr()] count := 0 for _, msg := range msgs { if arg.Query != nil { if arg.Query.After != nil && msg.ServerHeader.Ctime < *arg.Query.After { continue } if arg.Query.Before != nil && msg.ServerHeader.Ctime > *arg.Query.Before { continue } } res.Thread.Messages = append(res.Thread.Messages, *msg) if mts != nil && mts[msg.GetMessageType()] { count++ } else if mts == nil { count++ } if count >= arg.Pagination.Num { break } } if arg.Query != nil && arg.Query.MarkAsRead { m.readMsgid[arg.ConversationID.ConvIDStr()] = msgs[0].ServerHeader.MessageID } var pmsgs []pager.Message for _, m := range res.Thread.Messages { pmsgs = append(pmsgs, m) } res.Thread.Pagination, err = pager.NewThreadPager().MakePage(pmsgs, arg.Pagination.Num, 0) if err != nil { return res, err } return res, nil } func (m *ChatRemoteMock) GetUnreadlineRemote(ctx context.Context, arg chat1.GetUnreadlineRemoteArg) (res chat1.GetUnreadlineRemoteRes, err error) { return res, nil } func (m *ChatRemoteMock) GetConversationMetadataRemote(ctx context.Context, convID chat1.ConversationID) (res chat1.GetConversationMetadataRemoteRes, err error) { conv := m.world.GetConversationByID(convID) if conv == nil { err = errors.New("conversation not found") return res, err } res.Conv = *conv return res, err } func (m *ChatRemoteMock) headerToVerifiedForTesting(h chat1.MessageClientHeader) chat1.MessageClientHeaderVerified { return chat1.MessageClientHeaderVerified{ Conv: h.Conv, TlfName: h.TlfName, TlfPublic: h.TlfPublic, MessageType: h.MessageType, Prev: h.Prev, Sender: h.Sender, SenderDevice: h.SenderDevice, OutboxID: h.OutboxID, OutboxInfo: h.OutboxInfo, } } func (m *ChatRemoteMock) promoteWriter(ctx context.Context, sender gregor1.UID, writers []gregor1.UID) []gregor1.UID { res := make([]gregor1.UID, len(writers)) copy(res, writers) for index, w := range writers { if bytes.Equal(w.Bytes(), sender.Bytes()) { res = append(res[:index], res[index+1:]...) res = append([]gregor1.UID{sender}, res...) return res } } res = append([]gregor1.UID{sender}, res...) return res } func (m *ChatRemoteMock) createBogusBody(typ chat1.MessageType) chat1.MessageBody { return chat1.MessageBody{ MessageType__: typ, Text__: &chat1.MessageText{}, Edit__: &chat1.MessageEdit{}, Attachment__: &chat1.MessageAttachment{}, Delete__: &chat1.MessageDelete{}, Attachmentuploaded__: &chat1.MessageAttachmentUploaded{}, Join__: &chat1.MessageJoin{}, Leave__: &chat1.MessageLeave{}, Headline__: &chat1.MessageHeadline{}, Metadata__: &chat1.MessageConversationMetadata{}, Reaction__: &chat1.MessageReaction{}, } } type dummyChannelSource struct{} var _ types.TeamChannelSource = (*dummyChannelSource)(nil) func (d dummyChannelSource) GetLastActiveForTLF(ctx context.Context, uid gregor1.UID, tlfID chat1.TLFID, topicType chat1.TopicType, ) (gregor1.Time, error) { return 0, nil } func (d dummyChannelSource) GetLastActiveForTeams(ctx context.Context, uid gregor1.UID, topicType chat1.TopicType, ) (res chat1.LastActiveTimeAll, err error) { return res, nil } func (d dummyChannelSource) GetChannelsFull(ctx context.Context, uid gregor1.UID, tlfID chat1.TLFID, topicType chat1.TopicType, ) ([]chat1.ConversationLocal, error) { return nil, nil } func (d dummyChannelSource) GetChannelsTopicName(ctx context.Context, uid gregor1.UID, tlfID chat1.TLFID, topicType chat1.TopicType, ) ([]chat1.ChannelNameMention, error) { return nil, nil } func (d dummyChannelSource) GetChannelTopicName(ctx context.Context, uid gregor1.UID, tlfID chat1.TLFID, topicType chat1.TopicType, convID chat1.ConversationID, ) (string, error) { return "", nil } func (d dummyChannelSource) GetRecentJoins(ctx context.Context, convID chat1.ConversationID, remoteClient chat1.RemoteInterface) (int, error) { return 0, nil } func (d dummyChannelSource) GetLastActiveAt(ctx context.Context, teamID keybase1.TeamID, uid gregor1.UID, remoteClient chat1.RemoteInterface) (gregor1.Time, error) { return 0, nil } func (d dummyChannelSource) OnDbNuke(mctx libkb.MetaContext) error { return nil } func (d dummyChannelSource) OnLogout(mctx libkb.MetaContext) error { return nil } func (m *ChatRemoteMock) PostRemote(ctx context.Context, arg chat1.PostRemoteArg) (res chat1.PostRemoteRes, err error) { uid := arg.MessageBoxed.ClientHeader.Sender conv := m.world.GetConversationByID(arg.ConversationID) ri := m.makeReaderInfo(conv.Metadata.ConversationID) inserted := m.insertMsgAndSort(arg.ConversationID, arg.MessageBoxed) m.world.T.Logf("PostRemote(convid:%v, msgid:%v, %v)", arg.ConversationID, inserted.ServerHeader.MessageID, arg.MessageBoxed.GetMessageType()) if ri.ReadMsgid == ri.MaxMsgid { m.readMsgid[arg.ConversationID.ConvIDStr()] = inserted.ServerHeader.MessageID } conv.Metadata.ActiveList = m.promoteWriter(ctx, arg.MessageBoxed.ClientHeader.Sender, conv.Metadata.ActiveList) conv.MaxMsgs = m.getMaxMsgs(arg.ConversationID) conv.MaxMsgSummaries = nil for _, m := range conv.MaxMsgs { conv.MaxMsgSummaries = append(conv.MaxMsgSummaries, m.Summary()) } conv.Metadata.Version++ sort.Sort(convByNewlyUpdated{mock: m}) res.MsgHeader = *inserted.ServerHeader res.RateLimit = &chat1.RateLimit{} // hit notify router with new message if m.world.TcsByID[uid.String()].ChatG.ActivityNotifier != nil { activity := chat1.NewChatActivityWithIncomingMessage(chat1.IncomingMessage{ Message: utils.PresentMessageUnboxed(ctx, m.world.TcsByID[uid.String()].Context(), chat1.NewMessageUnboxedWithValid(chat1.MessageUnboxedValid{ ClientHeader: m.headerToVerifiedForTesting(inserted.ClientHeader), ServerHeader: *inserted.ServerHeader, MessageBody: m.createBogusBody(inserted.GetMessageType()), }), uid, arg.ConversationID), }) m.world.TcsByID[uid.String()].ChatG.ActivityNotifier.Activity(context.Background(), uid, conv.GetTopicType(), &activity, chat1.ChatActivitySource_REMOTE) } return } func (m *ChatRemoteMock) NewConversationRemote(ctx context.Context, arg chat1.ConversationIDTriple) (res chat1.NewConversationRemoteRes, err error) { return res, errors.New("not implemented anymore") } func (m *ChatRemoteMock) NewConversationRemote2(ctx context.Context, arg chat1.NewConversationRemote2Arg) (res chat1.NewConversationRemoteRes, err error) { for _, conv := range m.world.conversations { if conv.Metadata.IdTriple.Tlfid.Eq(arg.IdTriple.Tlfid) && conv.Metadata.IdTriple.TopicID.String() == arg.IdTriple.TopicID.String() && conv.Metadata.IdTriple.TopicType == arg.IdTriple.TopicType { // Identical triple return res, libkb.ChatConvExistsError{ConvID: conv.Metadata.ConversationID} } if arg.IdTriple.TopicType == chat1.TopicType_CHAT && arg.MembersType != chat1.ConversationMembersType_TEAM && conv.Metadata.IdTriple.Tlfid.Eq(arg.IdTriple.Tlfid) && conv.Metadata.IdTriple.TopicType == arg.IdTriple.TopicType { // Existing CHAT conv return res, libkb.ChatConvExistsError{ConvID: conv.Metadata.ConversationID} } } res.ConvID = arg.IdTriple.ToConversationID([2]byte{0, 0}) first := m.insertMsgAndSort(res.ConvID, arg.TLFMessage) vis := keybase1.TLFVisibility_PRIVATE if arg.TLFMessage.ClientHeader.TlfPublic { vis = keybase1.TLFVisibility_PUBLIC } m.world.conversations = append(m.world.conversations, &chat1.Conversation{ Metadata: chat1.ConversationMetadata{ IdTriple: arg.IdTriple, ConversationID: res.ConvID, Visibility: vis, MembersType: arg.MembersType, Version: 1, }, MaxMsgs: []chat1.MessageBoxed{first}, MaxMsgSummaries: []chat1.MessageSummary{first.Summary()}, }) m.readMsgid[res.ConvID.ConvIDStr()] = first.ServerHeader.MessageID sort.Sort(convByNewlyUpdated{mock: m}) return res, nil } func (m *ChatRemoteMock) GetMessagesRemote(ctx context.Context, arg chat1.GetMessagesRemoteArg) (res chat1.GetMessagesRemoteRes, err error) { msgs, ok := m.world.Msgs[arg.ConversationID.ConvIDStr()] if !ok { return res, errors.New("conversation not found") } ids := make(map[chat1.MessageID]bool) for _, id := range arg.MessageIDs { ids[id] = true } for _, msg := range msgs { id := msg.ServerHeader.MessageID if ids[id] { res.Msgs = append(res.Msgs, *msg) delete(ids, id) } } for id := range ids { return res, fmt.Errorf("message %d is not found", id) } return res, nil } func (m *ChatRemoteMock) MarkAsRead(ctx context.Context, arg chat1.MarkAsReadArg) (res chat1.MarkAsReadRes, err error) { conv := m.world.GetConversationByID(arg.ConversationID) if conv == nil { err = errors.New("conversation not found") return res, err } m.readMsgid[conv.Metadata.ConversationID.ConvIDStr()] = arg.MsgID return res, nil } func (m *ChatRemoteMock) SetConversationStatus(ctx context.Context, arg chat1.SetConversationStatusArg) (res chat1.SetConversationStatusRes, err error) { return chat1.SetConversationStatusRes{}, errors.New("not implemented") } func (m *ChatRemoteMock) SetAppNotificationSettings(ctx context.Context, arg chat1.SetAppNotificationSettingsArg, ) (res chat1.SetAppNotificationSettingsRes, err error) { return res, errors.New("not implemented") } func (m *ChatRemoteMock) RetentionSweepConv(ctx context.Context, convID chat1.ConversationID) (res chat1.SweepRes, err error) { return res, errors.New("not implemented") } func (m *ChatRemoteMock) UpgradeKBFSToImpteam(ctx context.Context, arg chat1.UpgradeKBFSToImpteamArg) error { return errors.New("not implemented") } func (m *ChatRemoteMock) SetGlobalAppNotificationSettings(ctx context.Context, arg chat1.GlobalAppNotificationSettings, ) error { return errors.New("not implemented") } func (m *ChatRemoteMock) GetGlobalAppNotificationSettings(ctx context.Context) (chat1.GlobalAppNotificationSettings, error) { return chat1.GlobalAppNotificationSettings{}, errors.New("not implemented") } func (m *ChatRemoteMock) TlfFinalize(ctx context.Context, arg chat1.TlfFinalizeArg) error { return nil } func (m *ChatRemoteMock) TlfResolve(ctx context.Context, arg chat1.TlfResolveArg) error { return nil } func (m *ChatRemoteMock) GetUnreadUpdateFull(ctx context.Context, inboxVers chat1.InboxVers) (chat1.UnreadUpdateFull, error) { return chat1.UnreadUpdateFull{}, errors.New("not implemented") } func (m *ChatRemoteMock) GetInboxVersion(ctx context.Context, uid gregor1.UID) (chat1.InboxVers, error) { return 1, nil } func (m *ChatRemoteMock) SyncInbox(ctx context.Context, vers chat1.InboxVers) (chat1.SyncInboxRes, error) { if m.SyncInboxFunc == nil { return chat1.NewSyncInboxResWithClear(), nil } return m.SyncInboxFunc(m, ctx, vers) } func (m *ChatRemoteMock) SyncChat(ctx context.Context, arg chat1.SyncChatArg) (chat1.SyncChatRes, error) { if m.SyncInboxFunc == nil { return chat1.SyncChatRes{ InboxRes: chat1.NewSyncInboxResWithClear(), }, nil } iboxRes, err := m.SyncInboxFunc(m, ctx, arg.Vers) if err != nil { return chat1.SyncChatRes{}, err } return chat1.SyncChatRes{ InboxRes: iboxRes, CacheVers: chat1.ServerCacheVers{ InboxVers: m.CacheInboxVersion, BodiesVers: m.CacheBodiesVersion, }, }, nil } func (m *ChatRemoteMock) SyncAll(ctx context.Context, arg chat1.SyncAllArg) (res chat1.SyncAllResult, err error) { cres, err := m.SyncChat(ctx, chat1.SyncChatArg{ Vers: arg.InboxVers, SummarizeMaxMsgs: arg.SummarizeMaxMsgs, }) if err != nil { return res, err } return chat1.SyncAllResult{ Chat: cres, }, nil } func (m *ChatRemoteMock) UpdateTypingRemote(ctx context.Context, arg chat1.UpdateTypingRemoteArg) error { return nil } func (m *ChatRemoteMock) GetTLFConversations(ctx context.Context, arg chat1.GetTLFConversationsArg) (chat1.GetTLFConversationsRes, error) { return chat1.GetTLFConversationsRes{}, nil } func (m *ChatRemoteMock) JoinConversation(ctx context.Context, convID chat1.ConversationID) (chat1.JoinLeaveConversationRemoteRes, error) { return chat1.JoinLeaveConversationRemoteRes{}, nil } func (m *ChatRemoteMock) LeaveConversation(ctx context.Context, convID chat1.ConversationID) (chat1.JoinLeaveConversationRemoteRes, error) { return chat1.JoinLeaveConversationRemoteRes{}, nil } func (m *ChatRemoteMock) PreviewConversation(ctx context.Context, convID chat1.ConversationID) (chat1.JoinLeaveConversationRemoteRes, error) { return chat1.JoinLeaveConversationRemoteRes{}, nil } func (m *ChatRemoteMock) DeleteConversation(ctx context.Context, convID chat1.ConversationID) (chat1.DeleteConversationRemoteRes, error) { return chat1.DeleteConversationRemoteRes{}, nil } func (m *ChatRemoteMock) RemoveFromConversation(ctx context.Context, arg chat1.RemoveFromConversationArg) (chat1.RemoveFromConversationRemoteRes, error) { return chat1.RemoveFromConversationRemoteRes{}, nil } func (m *ChatRemoteMock) GetMessageBefore(ctx context.Context, arg chat1.GetMessageBeforeArg) (chat1.GetMessageBeforeRes, error) { // Ignore age and get the latest message var latest chat1.MessageID convID := arg.ConvID.ConvIDStr() for _, msg := range m.world.Msgs[convID] { if msg.ServerHeader.MessageID >= latest { latest = msg.ServerHeader.MessageID } } return chat1.GetMessageBeforeRes{MsgID: latest}, nil } type convByNewlyUpdated struct { mock *ChatRemoteMock } func (s convByNewlyUpdated) Len() int { return len(s.mock.world.conversations) } func (s convByNewlyUpdated) Swap(i, j int) { s.mock.world.conversations[i], s.mock.world.conversations[j] = s.mock.world.conversations[j], s.mock.world.conversations[i] } func (s convByNewlyUpdated) Less(i, j int) bool { return s.mock.makeReaderInfo(s.mock.world.conversations[i].Metadata.ConversationID).Mtime > s.mock.makeReaderInfo(s.mock.world.conversations[j].Metadata.ConversationID).Mtime } type msgByMessageIDDesc struct { world *ChatMockWorld convID chat1.ConversationID } func (s msgByMessageIDDesc) Len() int { return len(s.world.Msgs[s.convID.ConvIDStr()]) } func (s msgByMessageIDDesc) Swap(i, j int) { convID := s.convID.ConvIDStr() s.world.Msgs[convID][i], s.world.Msgs[convID][j] = s.world.Msgs[convID][j], s.world.Msgs[convID][i] } func (s msgByMessageIDDesc) Less(i, j int) bool { convID := s.convID.ConvIDStr() return s.world.Msgs[convID][i].ServerHeader.MessageID > s.world.Msgs[convID][j].ServerHeader.MessageID } func (m *ChatRemoteMock) getMaxMsgs(convID chat1.ConversationID) (maxMsgs []chat1.MessageBoxed) { finder := make(map[chat1.MessageType]*chat1.MessageBoxed) convIDStr := convID.ConvIDStr() for _, msg := range m.world.Msgs[convIDStr] { if existing, ok := finder[msg.GetMessageType()]; !ok || existing.GetMessageID() < msg.GetMessageID() { finder[msg.GetMessageType()] = msg } } for _, msg := range finder { maxMsgs = append(maxMsgs, *msg) } return maxMsgs } func (m *ChatRemoteMock) insertMsgAndSort(convID chat1.ConversationID, msg chat1.MessageBoxed) (inserted chat1.MessageBoxed) { convIDStr := convID.ConvIDStr() msg.ServerHeader = &chat1.MessageServerHeader{ Ctime: gregor1.ToTime(m.world.Fc.Now()), Now: gregor1.ToTime(m.world.Fc.Now()), MessageID: chat1.MessageID(len(m.world.Msgs[convIDStr]) + 1), //nolint:gosec // G115: Mock test data generating sequential MessageIDs, safe to convert } m.world.Msgs[convIDStr] = append(m.world.Msgs[convIDStr], &msg) sort.Sort(msgByMessageIDDesc{world: m.world, convID: convID}) // If this message supersedes something, track it down and set supersededBy if msg.ClientHeader.Supersedes > 0 { for _, wmsg := range m.world.Msgs[convIDStr] { if wmsg.GetMessageID() == msg.ClientHeader.Supersedes { wmsg.ServerHeader.SupersededBy = msg.GetMessageID() } } } return msg } func (m *ChatRemoteMock) BroadcastGregorMessageToConv(ctx context.Context, arg chat1.BroadcastGregorMessageToConvArg, ) error { return nil } func (m *ChatRemoteMock) GetS3Params(context.Context, chat1.GetS3ParamsArg) (chat1.S3Params, error) { return chat1.S3Params{}, errors.New("GetS3Params not mocked") } func (m *ChatRemoteMock) S3Sign(context.Context, chat1.S3SignArg) ([]byte, error) { return nil, errors.New("GetS3Params not mocked") } func (m *ChatRemoteMock) SetConvRetention(ctx context.Context, _ chat1.SetConvRetentionArg) (res chat1.SetRetentionRes, err error) { return res, errors.New("SetConvRetention not mocked") } func (m *ChatRemoteMock) SetTeamRetention(ctx context.Context, _ chat1.SetTeamRetentionArg) (res chat1.SetRetentionRes, err error) { return res, errors.New("SetTeamRetention not mocked") } func (m *ChatRemoteMock) SetConvMinWriterRole(ctx context.Context, _ chat1.SetConvMinWriterRoleArg) (res chat1.SetConvMinWriterRoleRes, err error) { return res, errors.New("SetConvMinWriterRole not mocked") } func (m *ChatRemoteMock) RegisterSharePost(ctx context.Context, _ chat1.RegisterSharePostArg) error { return errors.New("RegisterSharePost not mocked") } func (m *ChatRemoteMock) FailSharePost(ctx context.Context, _ chat1.FailSharePostArg) error { return errors.New("FailSharePost not mocked") } func (m *ChatRemoteMock) ServerNow(ctx context.Context) (res chat1.ServerNowRes, err error) { return res, errors.New("ServerNow not mocked") } func (m *ChatRemoteMock) GetExternalAPIKeys(ctx context.Context, typs []chat1.ExternalAPIKeyTyp) (res []chat1.ExternalAPIKey, err error) { return res, errors.New("GetExternalAPIKeys not mocked") } func (m *ChatRemoteMock) AdvertiseBotCommands(ctx context.Context, ads []chat1.RemoteBotCommandsAdvertisement) (res chat1.AdvertiseBotCommandsRes, err error) { return res, errors.New("AdvertiseBotCommands not mocked") } func (m *ChatRemoteMock) ClearBotCommands(ctx context.Context, filter *chat1.RemoteClearBotCommandsFilter) (res chat1.ClearBotCommandsRes, err error) { return res, errors.New("ClearBotCommands not mocked") } func (m *ChatRemoteMock) GetBotInfo(ctx context.Context, arg chat1.GetBotInfoArg) (res chat1.GetBotInfoRes, err error) { return res, errors.New("GetBotInfo not mocked") } func (m *ChatRemoteMock) GetDefaultTeamChannels(ctx context.Context, teamID keybase1.TeamID) (res chat1.GetDefaultTeamChannelsRes, err error) { return res, errors.New("GetDefaultTeamChannels not mocked") } func (m *ChatRemoteMock) SetDefaultTeamChannels(ctx context.Context, arg chat1.SetDefaultTeamChannelsArg) (res chat1.SetDefaultTeamChannelsRes, err error) { return res, errors.New("SetDefaultTeamChannels not mocked") } func (m *ChatRemoteMock) GetRecentJoins(ctx context.Context, convID chat1.ConversationID) (res chat1.GetRecentJoinsRes, err error) { return res, errors.New("GetRecentJoins not mocked") } func (m *ChatRemoteMock) GetLastActiveAt(ctx context.Context, arg chat1.GetLastActiveAtArg) (res chat1.GetLastActiveAtRes, err error) { return res, errors.New("GetLastActiveAt not mocked") } func (m *ChatRemoteMock) GetResetConversations(ctx context.Context) (res chat1.GetResetConversationsRes, err error) { return res, errors.New("GetResetConversations not mocked") } func (m *ChatRemoteMock) TeamIDOfConv(ctx context.Context, convID chat1.ConversationID) (res *keybase1.TeamID, err error) { return res, errors.New("TeamIDOfConv not mocked") } func (m *ChatRemoteMock) ValidateTeamGitChatConv(ctx context.Context, arg chat1.ValidateTeamGitChatConvArg) (res chat1.ValidateTeamGitChatConvRes, err error) { return res, errors.New("ValidateTeamGitChatConv not mocked") } func (m *ChatRemoteMock) RefreshParticipantsRemote(ctx context.Context, arg chat1.RefreshParticipantsRemoteArg, ) (res chat1.RefreshParticipantsRemoteRes, err error) { return res, errors.New("not implemented") } type NonblockInboxResult struct { ConvID chat1.ConversationID Err error ConvRes *chat1.InboxUIItem InboxRes *chat1.UnverifiedInboxUIItems } type NonblockThreadResult struct { Thread *chat1.UIMessages Full bool } type NonblockSearchResult struct { chat1.ChatSearchHitArg } type ChatUI struct { InboxCb chan NonblockInboxResult ThreadCb chan NonblockThreadResult ThreadStatusCb chan chat1.UIChatThreadStatus SearchHitCb chan chat1.ChatSearchHitArg SearchDoneCb chan chat1.ChatSearchDoneArg InboxSearchHitCb chan chat1.ChatSearchInboxHitArg InboxSearchDoneCb chan chat1.ChatSearchInboxDoneArg InboxSearchConvHitsCb chan []chat1.UIChatSearchConvHit StellarShowConfirm chan struct{} StellarDataConfirm chan chat1.UIChatPaymentSummary StellarDataError chan keybase1.Status StellarDone chan struct{} ShowManageChannels chan string GiphyResults chan chat1.GiphySearchResults GiphyWindow chan bool CoinFlipUpdates chan []chat1.UICoinFlipStatus CommandMarkdown chan *chat1.UICommandMarkdown InboxLayoutCb chan chat1.UIInboxLayout } func NewChatUI() *ChatUI { return &ChatUI{ InboxCb: make(chan NonblockInboxResult, 50), ThreadCb: make(chan NonblockThreadResult, 50), ThreadStatusCb: make(chan chat1.UIChatThreadStatus, 50), SearchHitCb: make(chan chat1.ChatSearchHitArg, 50), SearchDoneCb: make(chan chat1.ChatSearchDoneArg, 50), InboxSearchHitCb: make(chan chat1.ChatSearchInboxHitArg, 50), InboxSearchDoneCb: make(chan chat1.ChatSearchInboxDoneArg, 50), InboxSearchConvHitsCb: make(chan []chat1.UIChatSearchConvHit, 50), StellarShowConfirm: make(chan struct{}, 10), StellarDataConfirm: make(chan chat1.UIChatPaymentSummary, 10), StellarDataError: make(chan keybase1.Status, 10), StellarDone: make(chan struct{}, 10), ShowManageChannels: make(chan string, 10), GiphyResults: make(chan chat1.GiphySearchResults, 10), GiphyWindow: make(chan bool, 10), CoinFlipUpdates: make(chan []chat1.UICoinFlipStatus, 100), CommandMarkdown: make(chan *chat1.UICommandMarkdown, 10), InboxLayoutCb: make(chan chat1.UIInboxLayout, 200), } } func (c *ChatUI) ChatInboxConversation(ctx context.Context, arg chat1.ChatInboxConversationArg) error { var inboxItems []chat1.InboxUIItem if err := json.Unmarshal([]byte(arg.Convs), &inboxItems); err != nil { return err } for _, inboxItem := range inboxItems { c.InboxCb <- NonblockInboxResult{ ConvRes: &inboxItem, ConvID: inboxItem.GetConvID(), } } return nil } func (c *ChatUI) ChatInboxLayout(ctx context.Context, layout string) error { var uilayout chat1.UIInboxLayout if err := json.Unmarshal([]byte(layout), &uilayout); err != nil { return err } c.InboxLayoutCb <- uilayout return nil } func (c *ChatUI) ChatInboxFailed(ctx context.Context, arg chat1.ChatInboxFailedArg) error { c.InboxCb <- NonblockInboxResult{ Err: fmt.Errorf("%s", arg.Error.Message), } return nil } func (c *ChatUI) ChatInboxUnverified(ctx context.Context, arg chat1.ChatInboxUnverifiedArg) error { var inbox chat1.UnverifiedInboxUIItems if err := json.Unmarshal([]byte(arg.Inbox), &inbox); err != nil { return err } c.InboxCb <- NonblockInboxResult{ InboxRes: &inbox, } return nil } func (c *ChatUI) ChatThreadCached(ctx context.Context, arg *string) error { var thread chat1.UIMessages if arg == nil { c.ThreadCb <- NonblockThreadResult{ Thread: nil, Full: false, } } else { if err := json.Unmarshal([]byte(*arg), &thread); err != nil { return err } c.ThreadCb <- NonblockThreadResult{ Thread: &thread, Full: false, } } return nil } func (c *ChatUI) ChatThreadFull(ctx context.Context, arg string) error { var thread chat1.UIMessages if err := json.Unmarshal([]byte(arg), &thread); err != nil { return err } c.ThreadCb <- NonblockThreadResult{ Thread: &thread, Full: true, } return nil } func (c *ChatUI) ChatThreadStatus(ctx context.Context, status chat1.UIChatThreadStatus) error { c.ThreadStatusCb <- status return nil } func (c *ChatUI) ChatConfirmChannelDelete(ctx context.Context, arg chat1.ChatConfirmChannelDeleteArg) (bool, error) { return true, nil } func (c *ChatUI) ChatSearchHit(ctx context.Context, arg chat1.ChatSearchHitArg) error { c.SearchHitCb <- arg return nil } func (c *ChatUI) ChatSearchDone(ctx context.Context, arg chat1.ChatSearchDoneArg) error { c.SearchDoneCb <- arg return nil } func (c *ChatUI) ChatSearchInboxHit(ctx context.Context, arg chat1.ChatSearchInboxHitArg) error { c.InboxSearchHitCb <- arg return nil } func (c *ChatUI) ChatSearchConvHits(ctx context.Context, hits chat1.UIChatSearchConvHits) error { c.InboxSearchConvHitsCb <- hits.Hits return nil } func (c *ChatUI) ChatSearchTeamHits(ctx context.Context, hits chat1.UIChatSearchTeamHits) error { return nil } func (c *ChatUI) ChatSearchBotHits(ctx context.Context, hits chat1.UIChatSearchBotHits) error { return nil } func (c *ChatUI) ChatSearchInboxStart(ctx context.Context) error { return nil } func (c *ChatUI) ChatSearchInboxDone(ctx context.Context, arg chat1.ChatSearchInboxDoneArg) error { c.InboxSearchDoneCb <- arg return nil } func (c *ChatUI) ChatSearchIndexStatus(ctx context.Context, arg chat1.ChatSearchIndexStatusArg) error { return nil } func (c *ChatUI) ChatStellarShowConfirm(ctx context.Context) error { c.StellarShowConfirm <- struct{}{} return nil } func (c *ChatUI) ChatStellarDataConfirm(ctx context.Context, summary chat1.UIChatPaymentSummary) (bool, error) { c.StellarDataConfirm <- summary return true, nil } func (c *ChatUI) ChatStellarDataError(ctx context.Context, err keybase1.Status) (bool, error) { c.StellarDataError <- err return false, nil } func (c *ChatUI) ChatStellarDone(ctx context.Context, canceled bool) error { c.StellarDone <- struct{}{} return nil } func (c *ChatUI) ChatShowManageChannels(ctx context.Context, teamname string) error { c.ShowManageChannels <- teamname return nil } func (c *ChatUI) ChatGiphySearchResults(ctx context.Context, convID chat1.ConversationID, results chat1.GiphySearchResults, ) error { c.GiphyResults <- results return nil } func (c *ChatUI) ChatGiphyToggleResultWindow(ctx context.Context, convID chat1.ConversationID, show, clearInput bool, ) error { c.GiphyWindow <- show return nil } func (c *ChatUI) ChatCoinFlipStatus(ctx context.Context, updates []chat1.UICoinFlipStatus) error { c.CoinFlipUpdates <- updates return nil } func (c *ChatUI) ChatCommandMarkdown(ctx context.Context, convID chat1.ConversationID, md *chat1.UICommandMarkdown, ) error { c.CommandMarkdown <- md return nil } func (c *ChatUI) ChatMaybeMentionUpdate(ctx context.Context, teamName, channel string, info chat1.UIMaybeMentionInfo, ) error { return nil } func (c *ChatUI) ChatLoadGalleryHit(ctx context.Context, msg chat1.UIMessage) error { return nil } func (c *ChatUI) ChatWatchPosition(context.Context, chat1.ConversationID, chat1.UIWatchPositionPerm) (chat1.LocationWatchID, error) { return chat1.LocationWatchID(0), nil } func (c *ChatUI) ChatClearWatch(context.Context, chat1.LocationWatchID) error { return nil } func (c *ChatUI) ChatCommandStatus(context.Context, chat1.ConversationID, string, chat1.UICommandStatusDisplayTyp, []chat1.UICommandStatusActionTyp, ) error { return nil } func (c *ChatUI) ChatBotCommandsUpdateStatus(context.Context, chat1.ConversationID, chat1.UIBotCommandsUpdateStatus, ) error { return nil } func (c *ChatUI) TriggerContactSync(context.Context) error { return nil } type DummyAssetDeleter struct{} func NewDummyAssetDeleter() DummyAssetDeleter { return DummyAssetDeleter{} } // DeleteAssets implements github.com/keybase/go/chat/storage/storage.AssetDeleter interface. func (d DummyAssetDeleter) DeleteAssets(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID, assets []chat1.Asset) { } func MockSentMessages(g *libkb.GlobalContext, t libkb.TestingTB) []MockMessage { if g.ChatHelper == nil { t.Fatal("ChatHelper is nil") } mch, ok := g.ChatHelper.(*MockChatHelper) if !ok { t.Fatalf("ChatHelper isn't a mock: %T", g.ChatHelper) } return mch.SentMessages } // MockMessage only supports what we're currently testing (system message for git push). type MockMessage struct { name string topicName *string membersType chat1.ConversationMembersType ident keybase1.TLFIdentifyBehavior Body chat1.MessageBody MsgType chat1.MessageType } type MockChatHelper struct { SentMessages []MockMessage convs map[string]chat1.ConversationLocal } var _ libkb.ChatHelper = (*MockChatHelper)(nil) func NewMockChatHelper() *MockChatHelper { return &MockChatHelper{ convs: make(map[string]chat1.ConversationLocal), } } func (m *MockChatHelper) SendTextByID(ctx context.Context, convID chat1.ConversationID, tlfName string, text string, vis keybase1.TLFVisibility, ) error { return nil } func (m *MockChatHelper) SendMsgByID(ctx context.Context, convID chat1.ConversationID, tlfName string, body chat1.MessageBody, msgType chat1.MessageType, vis keybase1.TLFVisibility, ) error { return nil } func (m *MockChatHelper) SendTextByIDNonblock(ctx context.Context, convID chat1.ConversationID, tlfName string, text string, outboxID *chat1.OutboxID, replyTo *chat1.MessageID, ) (chat1.OutboxID, error) { return chat1.OutboxID{}, nil } func (m *MockChatHelper) SendMsgByIDNonblock(ctx context.Context, convID chat1.ConversationID, tlfName string, body chat1.MessageBody, msgType chat1.MessageType, inOutboxID *chat1.OutboxID, replyTo *chat1.MessageID, ) (chat1.OutboxID, error) { return chat1.OutboxID{}, nil } func (m *MockChatHelper) SendTextByName(ctx context.Context, name string, topicName *string, membersType chat1.ConversationMembersType, ident keybase1.TLFIdentifyBehavior, text string, ) error { rb, err := libkb.RandBytes(10) if err != nil { return err } // use this to fake making channels... _, ok := m.convs[m.convKey(name, topicName)] if !ok { m.convs[m.convKey(name, topicName)] = chat1.ConversationLocal{ Info: chat1.ConversationInfoLocal{ Id: rb, TopicName: *topicName, }, } } return nil } func (m *MockChatHelper) SendMsgByName(ctx context.Context, name string, topicName *string, membersType chat1.ConversationMembersType, ident keybase1.TLFIdentifyBehavior, body chat1.MessageBody, msgType chat1.MessageType, ) error { m.SentMessages = append(m.SentMessages, MockMessage{ name: name, topicName: topicName, membersType: membersType, ident: ident, Body: body, MsgType: msgType, }) return nil } func (m *MockChatHelper) SendTextByNameNonblock(ctx context.Context, name string, topicName *string, membersType chat1.ConversationMembersType, ident keybase1.TLFIdentifyBehavior, text string, outboxID *chat1.OutboxID, ) (chat1.OutboxID, error) { return chat1.OutboxID{}, nil } func (m *MockChatHelper) SendMsgByNameNonblock(ctx context.Context, name string, topicName *string, membersType chat1.ConversationMembersType, ident keybase1.TLFIdentifyBehavior, body chat1.MessageBody, msgType chat1.MessageType, outboxID *chat1.OutboxID, ) (chat1.OutboxID, error) { m.SentMessages = append(m.SentMessages, MockMessage{ name: name, topicName: topicName, membersType: membersType, ident: ident, Body: body, MsgType: msgType, }) return chat1.OutboxID{}, nil } func (m *MockChatHelper) DeleteMsg(ctx context.Context, convID chat1.ConversationID, tlfName string, msgID chat1.MessageID, ) error { return nil } func (m *MockChatHelper) DeleteMsgNonblock(ctx context.Context, convID chat1.ConversationID, tlfName string, msgID chat1.MessageID, ) error { return nil } func (m *MockChatHelper) FindConversations(ctx context.Context, name string, topicName *string, topicType chat1.TopicType, membersType chat1.ConversationMembersType, vis keybase1.TLFVisibility, ) ([]chat1.ConversationLocal, error) { conv, ok := m.convs[m.convKey(name, topicName)] if ok { return []chat1.ConversationLocal{conv}, nil } return nil, nil } func (m *MockChatHelper) FindConversationsByID(ctx context.Context, convIDs []chat1.ConversationID) (convs []chat1.ConversationLocal, err error) { for _, id := range convIDs { for _, v := range m.convs { if bytes.Equal(v.Info.Id, id) { convs = append(convs, v) } } } return convs, nil } func (m *MockChatHelper) GetChannelTopicName(ctx context.Context, teamID keybase1.TeamID, topicType chat1.TopicType, convID chat1.ConversationID, ) (string, error) { for _, v := range m.convs { if v.Info.Id.Eq(convID) { return v.Info.TopicName, nil } } return "", fmt.Errorf("MockChatHelper.GetChannelTopicName conv not found %v", convID) } func (m *MockChatHelper) UpgradeKBFSToImpteam(ctx context.Context, tlfName string, tlfID chat1.TLFID, public bool, ) error { return nil } func (m *MockChatHelper) GetMessages(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID, msgIDs []chat1.MessageID, resolveSupersedes bool, reason *chat1.GetThreadReason, ) ([]chat1.MessageUnboxed, error) { return nil, nil } func (m *MockChatHelper) GetMessage(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID, msgID chat1.MessageID, resolveSupersedes bool, reason *chat1.GetThreadReason, ) (chat1.MessageUnboxed, error) { return chat1.MessageUnboxed{}, nil } func (m *MockChatHelper) UserReacjis(ctx context.Context, uid gregor1.UID) keybase1.UserReacjis { return keybase1.UserReacjis{} } func (m *MockChatHelper) JourneycardTimeTravel(ctx context.Context, uid gregor1.UID, duration time.Duration) (int, int, error) { return 0, 0, fmt.Errorf("JourneycardTimeTravel not implemented on mock") } func (m *MockChatHelper) JourneycardResetAllConvs(ctx context.Context, uid gregor1.UID) error { return fmt.Errorf("JourneycardResetAllConvs not implemented on mock") } func (m *MockChatHelper) JourneycardDebugState(ctx context.Context, uid gregor1.UID, teamID keybase1.TeamID) (string, error) { return "", fmt.Errorf("JourneycardDebugState not implemented on mock") } func (m *MockChatHelper) NewConversation(ctx context.Context, uid gregor1.UID, tlfName string, topicName *string, topicType chat1.TopicType, membersType chat1.ConversationMembersType, vis keybase1.TLFVisibility, ) (chat1.ConversationLocal, bool, error) { return chat1.ConversationLocal{}, false, nil } func (m *MockChatHelper) NewConversationSkipFindExisting(ctx context.Context, uid gregor1.UID, tlfName string, topicName *string, topicType chat1.TopicType, membersType chat1.ConversationMembersType, vis keybase1.TLFVisibility, ) (chat1.ConversationLocal, bool, error) { return chat1.ConversationLocal{}, false, nil } func (m *MockChatHelper) NewConversationWithMemberSourceConv(ctx context.Context, uid gregor1.UID, tlfName string, topicName *string, topicType chat1.TopicType, membersType chat1.ConversationMembersType, vis keybase1.TLFVisibility, retentionPolicy *chat1.RetentionPolicy, memberSourceConv *chat1.ConversationID, ) (chat1.ConversationLocal, bool, error) { return chat1.ConversationLocal{}, false, nil } func (m *MockChatHelper) JoinConversationByID(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID, ) error { return nil } func (m *MockChatHelper) JoinConversationByName(ctx context.Context, uid gregor1.UID, tlfName, topicName string, topicType chat1.TopicType, vid keybase1.TLFVisibility, ) error { return nil } func (m *MockChatHelper) LeaveConversation(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID) error { return nil } func (m *MockChatHelper) InTeam(context.Context, gregor1.UID, keybase1.TeamID) (bool, error) { return false, nil } func (m *MockChatHelper) convKey(name string, topicName *string) string { if topicName == nil { return name + ":general" } return name + ":" + *topicName } func (m *MockChatHelper) BulkAddToConv(context.Context, gregor1.UID, chat1.ConversationID, []string) error { return nil } type MockUIRouter struct { ui libkb.ChatUI } var _ libkb.UIRouter = (*MockUIRouter)(nil) func NewMockUIRouter(chatUI libkb.ChatUI) *MockUIRouter { return &MockUIRouter{ ui: chatUI, } } func (f *MockUIRouter) SetUI(libkb.ConnectionID, libkb.UIKind) {} func (f *MockUIRouter) GetIdentifyUI() (libkb.IdentifyUI, error) { return nil, nil } func (f *MockUIRouter) GetIdentifyUICtx(ctx context.Context) (int, libkb.IdentifyUI, error) { return 0, nil, nil } func (f *MockUIRouter) GetSecretUI(sessionID int) (libkb.SecretUI, error) { return nil, nil } func (f *MockUIRouter) GetRekeyUI() (keybase1.RekeyUIInterface, int, error) { return nil, 0, nil } func (f *MockUIRouter) GetRekeyUINoSessionID() (keybase1.RekeyUIInterface, error) { return nil, nil } func (f *MockUIRouter) GetHomeUI() (keybase1.HomeUIInterface, error) { return nil, nil } func (f *MockUIRouter) GetIdentify3UIAdapter(libkb.MetaContext) (libkb.IdentifyUI, error) { return nil, nil } func (f *MockUIRouter) GetIdentify3UI(libkb.MetaContext) (keybase1.Identify3UiInterface, error) { return nil, nil } func (f *MockUIRouter) GetLogUI() (libkb.LogUI, error) { return nil, nil } func (f *MockUIRouter) WaitForUIType(uiKind libkb.UIKind, timeout time.Duration) bool { return false } func (f *MockUIRouter) DumpUIs() map[libkb.UIKind]libkb.ConnectionID { return nil } func (f *MockUIRouter) Shutdown() {} func (f *MockUIRouter) GetChatUI() (libkb.ChatUI, error) { return f.ui, nil }