github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbtest/chat.go (about) 1 package kbtest 2 3 import ( 4 "bytes" 5 "crypto/rand" 6 "encoding/hex" 7 "encoding/json" 8 "errors" 9 "fmt" 10 "sort" 11 "strings" 12 "sync" 13 "testing" 14 "time" 15 16 "github.com/keybase/client/go/badges" 17 "github.com/keybase/client/go/chat/pager" 18 "github.com/stretchr/testify/require" 19 20 "github.com/keybase/client/go/chat/globals" 21 "github.com/keybase/client/go/chat/types" 22 "github.com/keybase/client/go/chat/utils" 23 "github.com/keybase/client/go/externalstest" 24 "github.com/keybase/client/go/libkb" 25 "github.com/keybase/client/go/protocol/chat1" 26 "github.com/keybase/client/go/protocol/gregor1" 27 "github.com/keybase/client/go/protocol/keybase1" 28 "github.com/keybase/clockwork" 29 context "golang.org/x/net/context" 30 ) 31 32 type ChatTestContext struct { 33 libkb.TestContext 34 ChatG *globals.ChatContext 35 } 36 37 func NewMetaContextForTest(c ChatTestContext) libkb.MetaContext { 38 return libkb.NewMetaContextForTest(c.TestContext) 39 } 40 41 func (c ChatTestContext) Context() *globals.Context { 42 return globals.NewContext(c.G, c.ChatG) 43 } 44 45 func (c ChatTestContext) Cleanup() { 46 if c.ChatG.PushHandler != nil { 47 <-c.ChatG.PushHandler.Stop(context.TODO()) 48 } 49 if c.ChatG.MessageDeliverer != nil { 50 <-c.ChatG.MessageDeliverer.Stop(context.TODO()) 51 } 52 if c.ChatG.ConvLoader != nil { 53 <-c.ChatG.ConvLoader.Stop(context.TODO()) 54 } 55 if c.ChatG.FetchRetrier != nil { 56 <-c.ChatG.FetchRetrier.Stop(context.TODO()) 57 } 58 if c.ChatG.EphemeralPurger != nil { 59 <-c.ChatG.EphemeralPurger.Stop(context.TODO()) 60 } 61 if c.ChatG.EphemeralTracker != nil { 62 <-c.ChatG.EphemeralTracker.Stop(context.TODO()) 63 } 64 if c.ChatG.InboxSource != nil { 65 <-c.ChatG.InboxSource.Stop(context.TODO()) 66 } 67 if c.ChatG.Indexer != nil { 68 <-c.ChatG.Indexer.Stop(context.TODO()) 69 } 70 if c.ChatG.CoinFlipManager != nil { 71 <-c.ChatG.CoinFlipManager.Stop(context.TODO()) 72 } 73 if c.ChatG.BotCommandManager != nil { 74 <-c.ChatG.BotCommandManager.Stop(context.TODO()) 75 } 76 if c.ChatG.UIInboxLoader != nil { 77 <-c.ChatG.UIInboxLoader.Stop(context.TODO()) 78 } 79 c.TestContext.Cleanup() 80 } 81 82 type ChatMockWorld struct { 83 Fc clockwork.FakeClock 84 85 T testing.TB 86 Tcs map[string]*ChatTestContext 87 TcsByID map[string]*ChatTestContext 88 Users map[string]*FakeUser 89 tlfs map[keybase1.CanonicalTlfName]chat1.TLFID 90 tlfKeys map[keybase1.CanonicalTlfName][]keybase1.CryptKey 91 92 // should always be sorted by newly updated conversation first 93 conversations []*chat1.Conversation 94 95 // each slice should always be sorted by message ID in desc, i.e. newest messages first 96 Msgs map[chat1.ConvIDStr][]*chat1.MessageBoxed 97 } 98 99 func NewChatMockWorld(t *testing.T, name string, numUsers int) (world *ChatMockWorld) { 100 world = &ChatMockWorld{ 101 T: t, 102 Fc: clockwork.NewFakeClockAt(time.Now()), 103 Tcs: make(map[string]*ChatTestContext), 104 TcsByID: make(map[string]*ChatTestContext), 105 Users: make(map[string]*FakeUser), 106 tlfs: make(map[keybase1.CanonicalTlfName]chat1.TLFID), 107 tlfKeys: make(map[keybase1.CanonicalTlfName][]keybase1.CryptKey), 108 Msgs: make(map[chat1.ConvIDStr][]*chat1.MessageBoxed), 109 } 110 for i := 0; i < numUsers; i++ { 111 kbTc := externalstest.SetupTest(t, "chat_"+name, 0) 112 tc := ChatTestContext{ 113 TestContext: kbTc, 114 ChatG: &globals.ChatContext{}, 115 } 116 tc.ChatG.Badger = badges.NewBadger(kbTc.G) 117 tc.G.SetClock(world.Fc) 118 u, err := CreateAndSignupFakeUser("chat", tc.G) 119 if err != nil { 120 require.NoError(t, err) 121 } 122 world.Users[u.Username] = u 123 world.Tcs[u.Username] = &tc 124 world.TcsByID[u.User.GetUID().String()] = &tc 125 } 126 127 world.Fc.Advance(time.Hour) 128 return world 129 } 130 131 func (w *ChatMockWorld) Cleanup() { 132 for _, tc := range w.Tcs { 133 tc.Cleanup() 134 } 135 } 136 137 func (w *ChatMockWorld) GetConversationByID(convID chat1.ConversationID) *chat1.Conversation { 138 for _, conv := range w.conversations { 139 if conv.Metadata.ConversationID.ConvIDStr() == convID.ConvIDStr() { 140 return conv 141 } 142 } 143 return nil 144 } 145 146 type ByUsername []*FakeUser 147 148 func (m ByUsername) Len() int { return len(m) } 149 func (m ByUsername) Swap(i, j int) { m[i], m[j] = m[j], m[i] } 150 func (m ByUsername) Less(i, j int) bool { 151 res := strings.Compare(m[i].Username, m[j].Username) 152 return res < 0 153 } 154 155 func (w *ChatMockWorld) GetUsers() (res []*FakeUser) { 156 for _, v := range w.Users { 157 res = append(res, v) 158 } 159 sort.Sort(ByUsername(res)) 160 return res 161 } 162 163 func mustGetRandBytesWithControlledFirstByte(n int, controlled byte) (b []byte) { 164 b = make([]byte, n) 165 if nn, err := rand.Read(b); err != nil || n != nn { 166 panic("oops") 167 } 168 b[0] = controlled // in case we have a collision? 169 return b 170 } 171 172 func mustGetRandCryptKeys(controlled byte) []keybase1.CryptKey { 173 key := keybase1.CryptKey{ 174 KeyGeneration: 1, 175 } 176 copy(key.Key[:], mustGetRandBytesWithControlledFirstByte(32, controlled)) 177 return []keybase1.CryptKey{key} 178 } 179 180 type TlfMock struct { 181 sync.Mutex 182 world *ChatMockWorld 183 } 184 185 func NewTlfMock(world *ChatMockWorld) *TlfMock { 186 return &TlfMock{world: world} 187 } 188 189 func CanonicalTlfNameForTest(tlfName string) keybase1.CanonicalTlfName { 190 // very much simplified canonicalization. 191 // TODO: implement rest when we need it 192 var names []string 193 nameMap := make(map[string]bool) 194 rawNames := strings.Split(tlfName, ",") 195 for _, rn := range rawNames { 196 if nameMap[rn] { 197 continue 198 } 199 names = append(names, rn) 200 nameMap[rn] = true 201 } 202 sort.Strings(names) 203 return keybase1.CanonicalTlfName(strings.Join(names, ",")) 204 } 205 206 func (m *TlfMock) newTLFID() chat1.TLFID { 207 suffix := byte(0x29) 208 idBytes, err := libkb.RandBytesWithSuffix(16, suffix) 209 if err != nil { 210 panic("RandBytes failed: " + err.Error()) 211 } 212 return chat1.TLFID(idBytes) 213 } 214 215 func (m *TlfMock) getTlfID(cname keybase1.CanonicalTlfName) (keybase1.TLFID, error) { 216 m.Lock() 217 defer m.Unlock() 218 tlfID, ok := m.world.tlfs[cname] 219 if !ok { 220 for _, n := range strings.Split(string(cname), ",") { 221 if m.world.Users[n] == nil { 222 return "", fmt.Errorf("user %s not found", n) 223 } 224 } 225 tlfID = m.newTLFID() 226 m.world.tlfs[cname] = tlfID 227 m.world.tlfKeys[cname] = mustGetRandCryptKeys(byte(len(m.world.tlfKeys) + 1)) 228 } 229 return keybase1.TLFID(hex.EncodeToString([]byte(tlfID))), nil 230 } 231 232 func (m *TlfMock) AllCryptKeys(ctx context.Context, tlfName string, public bool) (res types.AllCryptKeys, err error) { 233 cres, err := m.CryptKeys(ctx, tlfName) 234 if err != nil { 235 return res, err 236 } 237 res = types.NewAllCryptKeys() 238 for _, key := range cres.CryptKeys { 239 res[chat1.ConversationMembersType_KBFS] = 240 append(res[chat1.ConversationMembersType_KBFS], key) 241 res[chat1.ConversationMembersType_TEAM] = 242 append(res[chat1.ConversationMembersType_TEAM], key) 243 } 244 return res, nil 245 } 246 func (m *TlfMock) LookupName(ctx context.Context, tlfID chat1.TLFID, public bool, tlfName string) (res types.NameInfo, err error) { 247 return m.LookupID(ctx, tlfName, public) 248 } 249 250 func (m *TlfMock) TeamBotSettings(ctx context.Context, tlfName string, tlfID chat1.TLFID, 251 membersType chat1.ConversationMembersType, public bool) (map[keybase1.UserVersion]keybase1.TeamBotSettings, error) { 252 return make(map[keybase1.UserVersion]keybase1.TeamBotSettings), nil 253 } 254 255 func (m *TlfMock) LookupID(ctx context.Context, tlfName string, public bool) (res types.NameInfo, err error) { 256 var tlfID keybase1.TLFID 257 name := CanonicalTlfNameForTest(tlfName) 258 res.CanonicalName = name.String() 259 if tlfID, err = m.getTlfID(name); err != nil { 260 return res, err 261 } 262 res.ID = tlfID.ToBytes() 263 return res, nil 264 } 265 266 func (m *TlfMock) EncryptionKey(ctx context.Context, tlfName string, tlfID chat1.TLFID, 267 membersType chat1.ConversationMembersType, public bool, 268 botUID *gregor1.UID) (key types.CryptKey, ni types.NameInfo, err error) { 269 if botUID != nil { 270 return key, ni, fmt.Errorf("TeambotKeys not supported by KBFS") 271 } 272 if ni, err = m.LookupID(ctx, tlfName, public); err != nil { 273 return key, ni, err 274 } 275 if public { 276 var zero [libkb.NaclDHKeySecretSize]byte 277 return keybase1.CryptKey{ 278 KeyGeneration: 1, 279 Key: keybase1.Bytes32(zero), 280 }, ni, nil 281 } 282 allKeys, err := m.AllCryptKeys(ctx, tlfName, public) 283 if err != nil { 284 return key, ni, err 285 } 286 keys := allKeys[chat1.ConversationMembersType_KBFS] 287 return keys[len(keys)-1], ni, nil 288 } 289 290 func (m *TlfMock) DecryptionKey(ctx context.Context, tlfName string, tlfID chat1.TLFID, 291 membersType chat1.ConversationMembersType, public bool, 292 keyGeneration int, kbfsEncrypted bool, botUID *gregor1.UID) (types.CryptKey, error) { 293 if botUID != nil { 294 return nil, fmt.Errorf("TeambotKeys not supported by KBFS") 295 } 296 if public { 297 var zero [libkb.NaclDHKeySecretSize]byte 298 return keybase1.CryptKey{ 299 KeyGeneration: 1, 300 Key: keybase1.Bytes32(zero), 301 }, nil 302 } 303 allkeys, err := m.AllCryptKeys(ctx, tlfName, public) 304 if err != nil { 305 return nil, err 306 } 307 keys := allkeys[chat1.ConversationMembersType_KBFS] 308 for _, key := range keys { 309 if key.Generation() == keyGeneration { 310 return key, nil 311 } 312 } 313 return nil, errors.New("no mock key found") 314 } 315 316 func (m *TlfMock) EphemeralEncryptionKey(mctx libkb.MetaContext, tlfName string, tlfID chat1.TLFID, 317 membersType chat1.ConversationMembersType, public bool, botUID *gregor1.UID) (types.EphemeralCryptKey, error) { 318 // Returns a totally zero teamEK. That's enough to get some very simple 319 // round trip tests to pass. 320 return keybase1.TeamEphemeralKey{}, nil 321 } 322 323 func (m *TlfMock) EphemeralDecryptionKey(mctx libkb.MetaContext, tlfName string, tlfID chat1.TLFID, 324 membersType chat1.ConversationMembersType, public bool, botUID *gregor1.UID, 325 generation keybase1.EkGeneration, contentCtime *gregor1.Time) (types.EphemeralCryptKey, error) { 326 // Returns a totally zero teamEK. That's enough to get some very simple 327 // round trip tests to pass. 328 return keybase1.TeamEphemeralKey{}, nil 329 } 330 331 func (m *TlfMock) ShouldPairwiseMAC(ctx context.Context, tlfName string, tlfID chat1.TLFID, 332 membersType chat1.ConversationMembersType, public bool) (bool, []keybase1.KID, error) { 333 return false, nil, nil 334 } 335 336 func (m *TlfMock) CryptKeys(ctx context.Context, tlfName string) (res keybase1.GetTLFCryptKeysRes, err error) { 337 res.NameIDBreaks.CanonicalName = CanonicalTlfNameForTest(tlfName) 338 if res.NameIDBreaks.TlfID, err = m.getTlfID(res.NameIDBreaks.CanonicalName); err != nil { 339 return keybase1.GetTLFCryptKeysRes{}, err 340 } 341 var ok bool 342 if res.CryptKeys, ok = m.world.tlfKeys[res.NameIDBreaks.CanonicalName]; !ok { 343 err = fmt.Errorf("CryptKeys for TLF %s not found", res.NameIDBreaks.CanonicalName) 344 return res, err 345 } 346 return res, nil 347 } 348 349 func (m *TlfMock) CompleteAndCanonicalizePrivateTlfName(ctx context.Context, tlfName string) (keybase1.CanonicalTLFNameAndIDWithBreaks, error) { 350 var res keybase1.CanonicalTLFNameAndIDWithBreaks 351 res.CanonicalName = CanonicalTlfNameForTest(tlfName) 352 var err error 353 res.TlfID, err = m.getTlfID(res.CanonicalName) 354 if err != nil { 355 return keybase1.CanonicalTLFNameAndIDWithBreaks{}, err 356 } 357 return res, nil 358 } 359 360 func (m *TlfMock) PublicCanonicalTLFNameAndID(ctx context.Context, tlfName string) (keybase1.CanonicalTLFNameAndIDWithBreaks, error) { 361 res := keybase1.CanonicalTLFNameAndIDWithBreaks{ 362 CanonicalName: keybase1.CanonicalTlfName(tlfName), 363 TlfID: "abcdefg", 364 } 365 return res, nil 366 } 367 368 type ChatRemoteMockServerConnection struct { 369 mock *ChatRemoteMock 370 } 371 372 func NewChatRemoteMockServerConnection(mock *ChatRemoteMock) *ChatRemoteMockServerConnection { 373 return &ChatRemoteMockServerConnection{ 374 mock: mock, 375 } 376 } 377 378 func (m ChatRemoteMockServerConnection) GetClient() chat1.RemoteInterface { 379 return m.mock 380 } 381 382 func (m ChatRemoteMockServerConnection) Reconnect(ctx context.Context) (bool, error) { 383 return false, nil 384 } 385 386 type ChatRemoteMock struct { 387 world *ChatMockWorld 388 readMsgid map[chat1.ConvIDStr]chat1.MessageID 389 uid *gregor1.UID 390 391 CacheBodiesVersion int 392 CacheInboxVersion int 393 394 GetThreadRemoteFunc func(m *ChatRemoteMock, ctx context.Context, arg chat1.GetThreadRemoteArg) (chat1.GetThreadRemoteRes, error) 395 SyncInboxFunc func(m *ChatRemoteMock, ctx context.Context, vers chat1.InboxVers) (chat1.SyncInboxRes, error) 396 } 397 398 var _ chat1.RemoteInterface = (*ChatRemoteMock)(nil) 399 400 func NewChatRemoteMock(world *ChatMockWorld) (m *ChatRemoteMock) { 401 m = &ChatRemoteMock{ 402 world: world, 403 readMsgid: make(map[chat1.ConvIDStr]chat1.MessageID), 404 } 405 return m 406 } 407 408 func (m *ChatRemoteMock) makeReaderInfo(convID chat1.ConversationID) (ri *chat1.ConversationReaderInfo) { 409 ri = &chat1.ConversationReaderInfo{} 410 convIDStr := convID.ConvIDStr() 411 ri.ReadMsgid = m.readMsgid[convIDStr] 412 for _, m := range m.world.Msgs[convIDStr] { 413 if m.ServerHeader.MessageID > ri.MaxMsgid { 414 ri.MaxMsgid = m.ServerHeader.MessageID 415 ri.Mtime = m.ServerHeader.Ctime 416 } 417 } 418 return ri 419 } 420 421 func (m *ChatRemoteMock) SetCurrentUser(uid gregor1.UID) { 422 m.uid = &uid 423 } 424 425 func (m *ChatRemoteMock) inConversation(conv *chat1.Conversation) bool { 426 if m.uid != nil && len(conv.Metadata.ActiveList) > 0 { 427 return conv.Includes(*m.uid) 428 } 429 return true 430 } 431 432 func (m *ChatRemoteMock) RemoteNotificationSuccessful(ctx context.Context, arg chat1.RemoteNotificationSuccessfulArg) error { 433 return nil 434 } 435 436 func (m *ChatRemoteMock) GetInboxRemote(ctx context.Context, arg chat1.GetInboxRemoteArg) (res chat1.GetInboxRemoteRes, err error) { 437 // TODO: add pagination support 438 var ibfull chat1.InboxViewFull 439 for _, conv := range m.world.conversations { 440 if !m.inConversation(conv) { 441 continue 442 } 443 if arg.Query != nil { 444 if arg.Query.ConvID != nil { 445 arg.Query.ConvIDs = append(arg.Query.ConvIDs, *arg.Query.ConvID) 446 } 447 if len(arg.Query.ConvIDs) > 0 { 448 found := false 449 for _, convID := range arg.Query.ConvIDs { 450 if convID.Eq(conv.GetConvID()) { 451 found = true 452 break 453 } 454 } 455 if !found { 456 continue 457 } 458 } 459 if arg.Query.ConvID != nil && !conv.Metadata.ConversationID.Eq(*arg.Query.ConvID) { 460 continue 461 } 462 if arg.Query.TlfID != nil && !conv.Metadata.IdTriple.Tlfid.Eq(*arg.Query.TlfID) { 463 continue 464 } 465 if arg.Query.TopicType != nil && conv.Metadata.IdTriple.TopicType != *arg.Query.TopicType { 466 continue 467 } 468 if arg.Query.UnreadOnly && m.readMsgid[conv.Metadata.ConversationID.ConvIDStr()] == m.makeReaderInfo(conv.Metadata.ConversationID).MaxMsgid { 469 continue 470 } 471 if arg.Query.ReadOnly && m.readMsgid[conv.Metadata.ConversationID.ConvIDStr()] != m.makeReaderInfo(conv.Metadata.ConversationID).MaxMsgid { 472 continue 473 } 474 if arg.Query.TlfVisibility != nil && conv.Metadata.Visibility != *arg.Query.TlfVisibility { 475 continue 476 } 477 if arg.Query.After != nil && m.makeReaderInfo(conv.Metadata.ConversationID).Mtime < *arg.Query.After { 478 continue 479 } 480 if arg.Query.Before != nil && m.makeReaderInfo(conv.Metadata.ConversationID).Mtime > *arg.Query.Before { 481 continue 482 } 483 } 484 convToAppend := *conv 485 convToAppend.ReaderInfo = m.makeReaderInfo(convToAppend.Metadata.ConversationID) 486 convToAppend.Notifications = new(chat1.ConversationNotificationInfo) 487 488 ibfull.Conversations = append(ibfull.Conversations, convToAppend) 489 if arg.Pagination != nil && arg.Pagination.Num != 0 && arg.Pagination.Num == len(ibfull.Conversations) { 490 break 491 } 492 } 493 return chat1.GetInboxRemoteRes{ 494 Inbox: chat1.NewInboxViewWithFull(ibfull), 495 }, nil 496 } 497 498 func (m *ChatRemoteMock) GetPublicConversations(ctx context.Context, arg chat1.GetPublicConversationsArg) (res chat1.GetPublicConversationsRes, err error) { 499 500 for _, conv := range m.world.conversations { 501 if conv.Metadata.Visibility == keybase1.TLFVisibility_PUBLIC && 502 conv.Metadata.IdTriple.Tlfid.Eq(arg.TlfID) && 503 conv.Metadata.IdTriple.TopicType == arg.TopicType { 504 505 convToAppend := *conv 506 convToAppend.ReaderInfo = m.makeReaderInfo(convToAppend.Metadata.ConversationID) 507 res.Conversations = append(res.Conversations, convToAppend) 508 509 } 510 } 511 512 return res, nil 513 } 514 515 func (m *ChatRemoteMock) GetInboxByTLFIDRemote(ctx context.Context, tlfID chat1.TLFID) (res chat1.GetInboxByTLFIDRemoteRes, err error) { 516 for _, conv := range m.world.conversations { 517 if tlfID.Eq(conv.Metadata.IdTriple.Tlfid) { 518 convToAppend := *conv 519 convToAppend.ReaderInfo = m.makeReaderInfo(convToAppend.Metadata.ConversationID) 520 res.Convs = []chat1.Conversation{convToAppend} 521 return res, nil 522 } 523 } 524 return res, errors.New("conversation not found") 525 } 526 527 func (m *ChatRemoteMock) GetThreadRemote(ctx context.Context, arg chat1.GetThreadRemoteArg) (res chat1.GetThreadRemoteRes, err error) { 528 if m.GetThreadRemoteFunc != nil { 529 return m.GetThreadRemoteFunc(m, ctx, arg) 530 } 531 var mts map[chat1.MessageType]bool 532 if arg.Query != nil && len(arg.Query.MessageTypes) > 0 { 533 mts = make(map[chat1.MessageType]bool) 534 for _, mt := range arg.Query.MessageTypes { 535 mts[mt] = true 536 } 537 } 538 539 // TODO: add *real* pagination support 540 if arg.Pagination == nil { 541 arg.Pagination = &chat1.Pagination{Num: 10000} 542 } 543 conv, err := m.GetConversationMetadataRemote(ctx, arg.ConversationID) 544 if err != nil { 545 return res, err 546 } 547 res.MembersType = conv.Conv.GetMembersType() 548 res.Visibility = conv.Conv.Metadata.Visibility 549 msgs := m.world.Msgs[arg.ConversationID.ConvIDStr()] 550 count := 0 551 for _, msg := range msgs { 552 if arg.Query != nil { 553 if arg.Query.After != nil && msg.ServerHeader.Ctime < *arg.Query.After { 554 continue 555 } 556 if arg.Query.Before != nil && msg.ServerHeader.Ctime > *arg.Query.Before { 557 continue 558 } 559 560 } 561 res.Thread.Messages = append(res.Thread.Messages, *msg) 562 if mts != nil && mts[msg.GetMessageType()] { 563 count++ 564 } else if mts == nil { 565 count++ 566 } 567 if count >= arg.Pagination.Num { 568 break 569 } 570 } 571 if arg.Query != nil && arg.Query.MarkAsRead { 572 m.readMsgid[arg.ConversationID.ConvIDStr()] = msgs[0].ServerHeader.MessageID 573 } 574 var pmsgs []pager.Message 575 for _, m := range res.Thread.Messages { 576 pmsgs = append(pmsgs, m) 577 } 578 res.Thread.Pagination, err = pager.NewThreadPager().MakePage(pmsgs, arg.Pagination.Num, 0) 579 if err != nil { 580 return res, err 581 } 582 return res, nil 583 } 584 585 func (m *ChatRemoteMock) GetUnreadlineRemote(ctx context.Context, arg chat1.GetUnreadlineRemoteArg) (res chat1.GetUnreadlineRemoteRes, err error) { 586 return res, nil 587 } 588 589 func (m *ChatRemoteMock) GetConversationMetadataRemote(ctx context.Context, convID chat1.ConversationID) (res chat1.GetConversationMetadataRemoteRes, err error) { 590 conv := m.world.GetConversationByID(convID) 591 if conv == nil { 592 err = errors.New("conversation not found") 593 return res, err 594 } 595 res.Conv = *conv 596 return res, err 597 } 598 599 func (m *ChatRemoteMock) headerToVerifiedForTesting(h chat1.MessageClientHeader) chat1.MessageClientHeaderVerified { 600 return chat1.MessageClientHeaderVerified{ 601 Conv: h.Conv, 602 TlfName: h.TlfName, 603 TlfPublic: h.TlfPublic, 604 MessageType: h.MessageType, 605 Prev: h.Prev, 606 Sender: h.Sender, 607 SenderDevice: h.SenderDevice, 608 OutboxID: h.OutboxID, 609 OutboxInfo: h.OutboxInfo, 610 } 611 } 612 613 func (m *ChatRemoteMock) promoteWriter(ctx context.Context, sender gregor1.UID, writers []gregor1.UID) []gregor1.UID { 614 res := make([]gregor1.UID, len(writers)) 615 copy(res, writers) 616 for index, w := range writers { 617 if bytes.Equal(w.Bytes(), sender.Bytes()) { 618 res = append(res[:index], res[index+1:]...) 619 res = append([]gregor1.UID{sender}, res...) 620 return res 621 } 622 } 623 res = append([]gregor1.UID{sender}, res...) 624 return res 625 } 626 627 func (m *ChatRemoteMock) createBogusBody(typ chat1.MessageType) chat1.MessageBody { 628 return chat1.MessageBody{ 629 MessageType__: typ, 630 Text__: &chat1.MessageText{}, 631 Edit__: &chat1.MessageEdit{}, 632 Attachment__: &chat1.MessageAttachment{}, 633 Delete__: &chat1.MessageDelete{}, 634 Attachmentuploaded__: &chat1.MessageAttachmentUploaded{}, 635 Join__: &chat1.MessageJoin{}, 636 Leave__: &chat1.MessageLeave{}, 637 Headline__: &chat1.MessageHeadline{}, 638 Metadata__: &chat1.MessageConversationMetadata{}, 639 Reaction__: &chat1.MessageReaction{}, 640 } 641 } 642 643 type dummyChannelSource struct{} 644 645 var _ types.TeamChannelSource = (*dummyChannelSource)(nil) 646 647 func (d dummyChannelSource) GetLastActiveForTLF(ctx context.Context, uid gregor1.UID, tlfID chat1.TLFID, 648 topicType chat1.TopicType) (gregor1.Time, error) { 649 return 0, nil 650 } 651 652 func (d dummyChannelSource) GetLastActiveForTeams(ctx context.Context, uid gregor1.UID, 653 topicType chat1.TopicType) (res chat1.LastActiveTimeAll, err error) { 654 return res, nil 655 } 656 657 func (d dummyChannelSource) GetChannelsFull(ctx context.Context, uid gregor1.UID, tlfID chat1.TLFID, 658 topicType chat1.TopicType) ([]chat1.ConversationLocal, error) { 659 return nil, nil 660 } 661 662 func (d dummyChannelSource) GetChannelsTopicName(ctx context.Context, uid gregor1.UID, tlfID chat1.TLFID, 663 topicType chat1.TopicType) ([]chat1.ChannelNameMention, error) { 664 return nil, nil 665 } 666 667 func (d dummyChannelSource) GetChannelTopicName(ctx context.Context, uid gregor1.UID, tlfID chat1.TLFID, 668 topicType chat1.TopicType, convID chat1.ConversationID) (string, error) { 669 return "", nil 670 } 671 672 func (d dummyChannelSource) GetRecentJoins(ctx context.Context, convID chat1.ConversationID, remoteClient chat1.RemoteInterface) (int, error) { 673 return 0, nil 674 } 675 676 func (d dummyChannelSource) GetLastActiveAt(ctx context.Context, teamID keybase1.TeamID, uid gregor1.UID, remoteClient chat1.RemoteInterface) (gregor1.Time, error) { 677 return 0, nil 678 } 679 680 func (d dummyChannelSource) OnDbNuke(mctx libkb.MetaContext) error { 681 return nil 682 } 683 684 func (d dummyChannelSource) OnLogout(mctx libkb.MetaContext) error { 685 return nil 686 } 687 688 func (m *ChatRemoteMock) PostRemote(ctx context.Context, arg chat1.PostRemoteArg) (res chat1.PostRemoteRes, err error) { 689 uid := arg.MessageBoxed.ClientHeader.Sender 690 conv := m.world.GetConversationByID(arg.ConversationID) 691 ri := m.makeReaderInfo(conv.Metadata.ConversationID) 692 inserted := m.insertMsgAndSort(arg.ConversationID, arg.MessageBoxed) 693 m.world.T.Logf("PostRemote(convid:%v, msgid:%v, %v)", 694 arg.ConversationID, inserted.ServerHeader.MessageID, arg.MessageBoxed.GetMessageType()) 695 if ri.ReadMsgid == ri.MaxMsgid { 696 m.readMsgid[arg.ConversationID.ConvIDStr()] = inserted.ServerHeader.MessageID 697 } 698 conv.Metadata.ActiveList = m.promoteWriter(ctx, arg.MessageBoxed.ClientHeader.Sender, 699 conv.Metadata.ActiveList) 700 conv.MaxMsgs = m.getMaxMsgs(arg.ConversationID) 701 conv.MaxMsgSummaries = nil 702 for _, m := range conv.MaxMsgs { 703 conv.MaxMsgSummaries = append(conv.MaxMsgSummaries, m.Summary()) 704 } 705 conv.Metadata.Version++ 706 sort.Sort(convByNewlyUpdated{mock: m}) 707 res.MsgHeader = *inserted.ServerHeader 708 res.RateLimit = &chat1.RateLimit{} 709 710 // hit notify router with new message 711 if m.world.TcsByID[uid.String()].ChatG.ActivityNotifier != nil { 712 activity := chat1.NewChatActivityWithIncomingMessage(chat1.IncomingMessage{ 713 Message: utils.PresentMessageUnboxed(ctx, m.world.TcsByID[uid.String()].Context(), 714 chat1.NewMessageUnboxedWithValid(chat1.MessageUnboxedValid{ 715 ClientHeader: m.headerToVerifiedForTesting(inserted.ClientHeader), 716 ServerHeader: *inserted.ServerHeader, 717 MessageBody: m.createBogusBody(inserted.GetMessageType()), 718 }), uid, arg.ConversationID), 719 }) 720 m.world.TcsByID[uid.String()].ChatG.ActivityNotifier.Activity(context.Background(), 721 uid, conv.GetTopicType(), &activity, chat1.ChatActivitySource_REMOTE) 722 } 723 724 return 725 } 726 727 func (m *ChatRemoteMock) NewConversationRemote(ctx context.Context, arg chat1.ConversationIDTriple) (res chat1.NewConversationRemoteRes, err error) { 728 return res, errors.New("not implemented anymore") 729 } 730 731 func (m *ChatRemoteMock) NewConversationRemote2(ctx context.Context, arg chat1.NewConversationRemote2Arg) (res chat1.NewConversationRemoteRes, err error) { 732 for _, conv := range m.world.conversations { 733 if conv.Metadata.IdTriple.Tlfid.Eq(arg.IdTriple.Tlfid) && 734 conv.Metadata.IdTriple.TopicID.String() == arg.IdTriple.TopicID.String() && 735 conv.Metadata.IdTriple.TopicType == arg.IdTriple.TopicType { 736 // Identical triple 737 return res, libkb.ChatConvExistsError{ConvID: conv.Metadata.ConversationID} 738 } 739 if arg.IdTriple.TopicType == chat1.TopicType_CHAT && 740 arg.MembersType != chat1.ConversationMembersType_TEAM && 741 conv.Metadata.IdTriple.Tlfid.Eq(arg.IdTriple.Tlfid) && 742 conv.Metadata.IdTriple.TopicType == arg.IdTriple.TopicType { 743 // Existing CHAT conv 744 return res, libkb.ChatConvExistsError{ConvID: conv.Metadata.ConversationID} 745 } 746 } 747 748 res.ConvID = arg.IdTriple.ToConversationID([2]byte{0, 0}) 749 750 first := m.insertMsgAndSort(res.ConvID, arg.TLFMessage) 751 vis := keybase1.TLFVisibility_PRIVATE 752 if arg.TLFMessage.ClientHeader.TlfPublic { 753 vis = keybase1.TLFVisibility_PUBLIC 754 } 755 m.world.conversations = append(m.world.conversations, &chat1.Conversation{ 756 Metadata: chat1.ConversationMetadata{ 757 IdTriple: arg.IdTriple, 758 ConversationID: res.ConvID, 759 Visibility: vis, 760 MembersType: arg.MembersType, 761 Version: 1, 762 }, 763 MaxMsgs: []chat1.MessageBoxed{first}, 764 MaxMsgSummaries: []chat1.MessageSummary{first.Summary()}, 765 }) 766 m.readMsgid[res.ConvID.ConvIDStr()] = first.ServerHeader.MessageID 767 768 sort.Sort(convByNewlyUpdated{mock: m}) 769 return res, nil 770 } 771 772 func (m *ChatRemoteMock) GetMessagesRemote(ctx context.Context, arg chat1.GetMessagesRemoteArg) (res chat1.GetMessagesRemoteRes, err error) { 773 msgs, ok := m.world.Msgs[arg.ConversationID.ConvIDStr()] 774 if !ok { 775 return res, errors.New("conversation not found") 776 } 777 ids := make(map[chat1.MessageID]bool) 778 for _, id := range arg.MessageIDs { 779 ids[id] = true 780 } 781 for _, msg := range msgs { 782 id := msg.ServerHeader.MessageID 783 if ids[id] { 784 res.Msgs = append(res.Msgs, *msg) 785 delete(ids, id) 786 } 787 } 788 for id := range ids { 789 return res, fmt.Errorf("message %d is not found", id) 790 } 791 return res, nil 792 } 793 794 func (m *ChatRemoteMock) MarkAsRead(ctx context.Context, arg chat1.MarkAsReadArg) (res chat1.MarkAsReadRes, err error) { 795 conv := m.world.GetConversationByID(arg.ConversationID) 796 if conv == nil { 797 err = errors.New("conversation not found") 798 return res, err 799 } 800 m.readMsgid[conv.Metadata.ConversationID.ConvIDStr()] = arg.MsgID 801 return res, nil 802 } 803 804 func (m *ChatRemoteMock) SetConversationStatus(ctx context.Context, arg chat1.SetConversationStatusArg) (res chat1.SetConversationStatusRes, err error) { 805 return chat1.SetConversationStatusRes{}, errors.New("not implemented") 806 } 807 808 func (m *ChatRemoteMock) SetAppNotificationSettings(ctx context.Context, 809 arg chat1.SetAppNotificationSettingsArg) (res chat1.SetAppNotificationSettingsRes, err error) { 810 return res, errors.New("not implemented") 811 } 812 813 func (m *ChatRemoteMock) RetentionSweepConv(ctx context.Context, convID chat1.ConversationID) (res chat1.SweepRes, err error) { 814 return res, errors.New("not implemented") 815 } 816 817 func (m *ChatRemoteMock) UpgradeKBFSToImpteam(ctx context.Context, arg chat1.UpgradeKBFSToImpteamArg) error { 818 return errors.New("not implemented") 819 } 820 821 func (m *ChatRemoteMock) SetGlobalAppNotificationSettings(ctx context.Context, 822 arg chat1.GlobalAppNotificationSettings) error { 823 return errors.New("not implemented") 824 } 825 826 func (m *ChatRemoteMock) GetGlobalAppNotificationSettings(ctx context.Context) (chat1.GlobalAppNotificationSettings, error) { 827 return chat1.GlobalAppNotificationSettings{}, errors.New("not implemented") 828 } 829 830 func (m *ChatRemoteMock) TlfFinalize(ctx context.Context, arg chat1.TlfFinalizeArg) error { 831 return nil 832 } 833 834 func (m *ChatRemoteMock) TlfResolve(ctx context.Context, arg chat1.TlfResolveArg) error { 835 return nil 836 } 837 838 func (m *ChatRemoteMock) GetUnreadUpdateFull(ctx context.Context, inboxVers chat1.InboxVers) (chat1.UnreadUpdateFull, error) { 839 return chat1.UnreadUpdateFull{}, errors.New("not implemented") 840 } 841 842 func (m *ChatRemoteMock) GetInboxVersion(ctx context.Context, uid gregor1.UID) (chat1.InboxVers, error) { 843 return 1, nil 844 } 845 846 func (m *ChatRemoteMock) SyncInbox(ctx context.Context, vers chat1.InboxVers) (chat1.SyncInboxRes, error) { 847 if m.SyncInboxFunc == nil { 848 return chat1.NewSyncInboxResWithClear(), nil 849 } 850 return m.SyncInboxFunc(m, ctx, vers) 851 } 852 853 func (m *ChatRemoteMock) SyncChat(ctx context.Context, arg chat1.SyncChatArg) (chat1.SyncChatRes, error) { 854 if m.SyncInboxFunc == nil { 855 return chat1.SyncChatRes{ 856 InboxRes: chat1.NewSyncInboxResWithClear(), 857 }, nil 858 } 859 860 iboxRes, err := m.SyncInboxFunc(m, ctx, arg.Vers) 861 if err != nil { 862 return chat1.SyncChatRes{}, err 863 } 864 return chat1.SyncChatRes{ 865 InboxRes: iboxRes, 866 CacheVers: chat1.ServerCacheVers{ 867 InboxVers: m.CacheInboxVersion, 868 BodiesVers: m.CacheBodiesVersion, 869 }, 870 }, nil 871 } 872 873 func (m *ChatRemoteMock) SyncAll(ctx context.Context, arg chat1.SyncAllArg) (res chat1.SyncAllResult, err error) { 874 cres, err := m.SyncChat(ctx, chat1.SyncChatArg{ 875 Vers: arg.InboxVers, 876 SummarizeMaxMsgs: arg.SummarizeMaxMsgs, 877 }) 878 if err != nil { 879 return res, err 880 } 881 return chat1.SyncAllResult{ 882 Chat: cres, 883 }, nil 884 } 885 886 func (m *ChatRemoteMock) UpdateTypingRemote(ctx context.Context, arg chat1.UpdateTypingRemoteArg) error { 887 return nil 888 } 889 890 func (m *ChatRemoteMock) GetTLFConversations(ctx context.Context, arg chat1.GetTLFConversationsArg) (chat1.GetTLFConversationsRes, error) { 891 return chat1.GetTLFConversationsRes{}, nil 892 } 893 894 func (m *ChatRemoteMock) JoinConversation(ctx context.Context, convID chat1.ConversationID) (chat1.JoinLeaveConversationRemoteRes, error) { 895 return chat1.JoinLeaveConversationRemoteRes{}, nil 896 } 897 898 func (m *ChatRemoteMock) LeaveConversation(ctx context.Context, convID chat1.ConversationID) (chat1.JoinLeaveConversationRemoteRes, error) { 899 return chat1.JoinLeaveConversationRemoteRes{}, nil 900 } 901 902 func (m *ChatRemoteMock) PreviewConversation(ctx context.Context, convID chat1.ConversationID) (chat1.JoinLeaveConversationRemoteRes, error) { 903 return chat1.JoinLeaveConversationRemoteRes{}, nil 904 } 905 906 func (m *ChatRemoteMock) DeleteConversation(ctx context.Context, convID chat1.ConversationID) (chat1.DeleteConversationRemoteRes, error) { 907 return chat1.DeleteConversationRemoteRes{}, nil 908 } 909 910 func (m *ChatRemoteMock) RemoveFromConversation(ctx context.Context, arg chat1.RemoveFromConversationArg) (chat1.RemoveFromConversationRemoteRes, error) { 911 return chat1.RemoveFromConversationRemoteRes{}, nil 912 } 913 914 func (m *ChatRemoteMock) GetMessageBefore(ctx context.Context, arg chat1.GetMessageBeforeArg) (chat1.GetMessageBeforeRes, error) { 915 // Ignore age and get the latest message 916 var latest chat1.MessageID 917 convID := arg.ConvID.ConvIDStr() 918 for _, msg := range m.world.Msgs[convID] { 919 if msg.ServerHeader.MessageID >= latest { 920 latest = msg.ServerHeader.MessageID 921 } 922 } 923 return chat1.GetMessageBeforeRes{MsgID: latest}, nil 924 } 925 926 type convByNewlyUpdated struct { 927 mock *ChatRemoteMock 928 } 929 930 func (s convByNewlyUpdated) Len() int { return len(s.mock.world.conversations) } 931 func (s convByNewlyUpdated) Swap(i, j int) { 932 s.mock.world.conversations[i], s.mock.world.conversations[j] = s.mock.world.conversations[j], s.mock.world.conversations[i] 933 } 934 func (s convByNewlyUpdated) Less(i, j int) bool { 935 return s.mock.makeReaderInfo(s.mock.world.conversations[i].Metadata.ConversationID).Mtime > s.mock.makeReaderInfo(s.mock.world.conversations[j].Metadata.ConversationID).Mtime 936 } 937 938 type msgByMessageIDDesc struct { 939 world *ChatMockWorld 940 convID chat1.ConversationID 941 } 942 943 func (s msgByMessageIDDesc) Len() int { return len(s.world.Msgs[s.convID.ConvIDStr()]) } 944 func (s msgByMessageIDDesc) Swap(i, j int) { 945 convID := s.convID.ConvIDStr() 946 s.world.Msgs[convID][i], s.world.Msgs[convID][j] = 947 s.world.Msgs[convID][j], s.world.Msgs[convID][i] 948 } 949 func (s msgByMessageIDDesc) Less(i, j int) bool { 950 convID := s.convID.ConvIDStr() 951 return s.world.Msgs[convID][i].ServerHeader.MessageID > s.world.Msgs[convID][j].ServerHeader.MessageID 952 } 953 954 func (m *ChatRemoteMock) getMaxMsgs(convID chat1.ConversationID) (maxMsgs []chat1.MessageBoxed) { 955 finder := make(map[chat1.MessageType]*chat1.MessageBoxed) 956 convIDStr := convID.ConvIDStr() 957 for _, msg := range m.world.Msgs[convIDStr] { 958 if existing, ok := finder[msg.GetMessageType()]; !ok || existing.GetMessageID() < msg.GetMessageID() { 959 finder[msg.GetMessageType()] = msg 960 } 961 } 962 963 for _, msg := range finder { 964 maxMsgs = append(maxMsgs, *msg) 965 } 966 967 return maxMsgs 968 } 969 970 func (m *ChatRemoteMock) insertMsgAndSort(convID chat1.ConversationID, msg chat1.MessageBoxed) (inserted chat1.MessageBoxed) { 971 convIDStr := convID.ConvIDStr() 972 msg.ServerHeader = &chat1.MessageServerHeader{ 973 Ctime: gregor1.ToTime(m.world.Fc.Now()), 974 Now: gregor1.ToTime(m.world.Fc.Now()), 975 MessageID: chat1.MessageID(len(m.world.Msgs[convIDStr]) + 1), 976 } 977 m.world.Msgs[convIDStr] = append(m.world.Msgs[convIDStr], &msg) 978 sort.Sort(msgByMessageIDDesc{world: m.world, convID: convID}) 979 980 // If this message supersedes something, track it down and set supersededBy 981 if msg.ClientHeader.Supersedes > 0 { 982 for _, wmsg := range m.world.Msgs[convIDStr] { 983 if wmsg.GetMessageID() == msg.ClientHeader.Supersedes { 984 wmsg.ServerHeader.SupersededBy = msg.GetMessageID() 985 } 986 } 987 } 988 989 return msg 990 } 991 992 func (m *ChatRemoteMock) BroadcastGregorMessageToConv(ctx context.Context, 993 arg chat1.BroadcastGregorMessageToConvArg) error { 994 return nil 995 } 996 997 func (m *ChatRemoteMock) GetS3Params(context.Context, chat1.GetS3ParamsArg) (chat1.S3Params, error) { 998 return chat1.S3Params{}, errors.New("GetS3Params not mocked") 999 } 1000 1001 func (m *ChatRemoteMock) S3Sign(context.Context, chat1.S3SignArg) ([]byte, error) { 1002 return nil, errors.New("GetS3Params not mocked") 1003 } 1004 1005 func (m *ChatRemoteMock) SetConvRetention(ctx context.Context, _ chat1.SetConvRetentionArg) (res chat1.SetRetentionRes, err error) { 1006 return res, errors.New("SetConvRetention not mocked") 1007 } 1008 1009 func (m *ChatRemoteMock) SetTeamRetention(ctx context.Context, _ chat1.SetTeamRetentionArg) (res chat1.SetRetentionRes, err error) { 1010 return res, errors.New("SetTeamRetention not mocked") 1011 } 1012 1013 func (m *ChatRemoteMock) SetConvMinWriterRole(ctx context.Context, _ chat1.SetConvMinWriterRoleArg) (res chat1.SetConvMinWriterRoleRes, err error) { 1014 return res, errors.New("SetConvMinWriterRole not mocked") 1015 } 1016 1017 func (m *ChatRemoteMock) RegisterSharePost(ctx context.Context, _ chat1.RegisterSharePostArg) error { 1018 return errors.New("RegisterSharePost not mocked") 1019 } 1020 1021 func (m *ChatRemoteMock) FailSharePost(ctx context.Context, _ chat1.FailSharePostArg) error { 1022 return errors.New("FailSharePost not mocked") 1023 } 1024 1025 func (m *ChatRemoteMock) ServerNow(ctx context.Context) (res chat1.ServerNowRes, err error) { 1026 return res, errors.New("ServerNow not mocked") 1027 } 1028 1029 func (m *ChatRemoteMock) GetExternalAPIKeys(ctx context.Context, typs []chat1.ExternalAPIKeyTyp) (res []chat1.ExternalAPIKey, err error) { 1030 return res, errors.New("GetExternalAPIKeys not mocked") 1031 } 1032 1033 func (m *ChatRemoteMock) AdvertiseBotCommands(ctx context.Context, ads []chat1.RemoteBotCommandsAdvertisement) (res chat1.AdvertiseBotCommandsRes, err error) { 1034 return res, errors.New("AdvertiseBotCommands not mocked") 1035 } 1036 1037 func (m *ChatRemoteMock) ClearBotCommands(ctx context.Context, filter *chat1.RemoteClearBotCommandsFilter) (res chat1.ClearBotCommandsRes, err error) { 1038 return res, errors.New("ClearBotCommands not mocked") 1039 } 1040 1041 func (m *ChatRemoteMock) GetBotInfo(ctx context.Context, arg chat1.GetBotInfoArg) (res chat1.GetBotInfoRes, err error) { 1042 return res, errors.New("GetBotInfo not mocked") 1043 } 1044 1045 func (m *ChatRemoteMock) GetDefaultTeamChannels(ctx context.Context, teamID keybase1.TeamID) (res chat1.GetDefaultTeamChannelsRes, err error) { 1046 return res, errors.New("GetDefaultTeamChannels not mocked") 1047 } 1048 1049 func (m *ChatRemoteMock) SetDefaultTeamChannels(ctx context.Context, arg chat1.SetDefaultTeamChannelsArg) (res chat1.SetDefaultTeamChannelsRes, err error) { 1050 return res, errors.New("SetDefaultTeamChannels not mocked") 1051 } 1052 1053 func (m *ChatRemoteMock) GetRecentJoins(ctx context.Context, convID chat1.ConversationID) (res chat1.GetRecentJoinsRes, err error) { 1054 return res, errors.New("GetRecentJoins not mocked") 1055 } 1056 1057 func (m *ChatRemoteMock) GetLastActiveAt(ctx context.Context, arg chat1.GetLastActiveAtArg) (res chat1.GetLastActiveAtRes, err error) { 1058 return res, errors.New("GetLastActiveAt not mocked") 1059 } 1060 1061 func (m *ChatRemoteMock) GetResetConversations(ctx context.Context) (res chat1.GetResetConversationsRes, err error) { 1062 return res, errors.New("GetResetConversations not mocked") 1063 } 1064 1065 func (m *ChatRemoteMock) TeamIDOfConv(ctx context.Context, convID chat1.ConversationID) (res *keybase1.TeamID, err error) { 1066 return res, errors.New("TeamIDOfConv not mocked") 1067 } 1068 1069 func (m *ChatRemoteMock) RefreshParticipantsRemote(ctx context.Context, 1070 arg chat1.RefreshParticipantsRemoteArg) (res chat1.RefreshParticipantsRemoteRes, err error) { 1071 return res, errors.New("not implemented") 1072 } 1073 1074 type NonblockInboxResult struct { 1075 ConvID chat1.ConversationID 1076 Err error 1077 ConvRes *chat1.InboxUIItem 1078 InboxRes *chat1.UnverifiedInboxUIItems 1079 } 1080 1081 type NonblockThreadResult struct { 1082 Thread *chat1.UIMessages 1083 Full bool 1084 } 1085 1086 type NonblockSearchResult struct { 1087 chat1.ChatSearchHitArg 1088 } 1089 1090 type ChatUI struct { 1091 InboxCb chan NonblockInboxResult 1092 ThreadCb chan NonblockThreadResult 1093 ThreadStatusCb chan chat1.UIChatThreadStatus 1094 SearchHitCb chan chat1.ChatSearchHitArg 1095 SearchDoneCb chan chat1.ChatSearchDoneArg 1096 InboxSearchHitCb chan chat1.ChatSearchInboxHitArg 1097 InboxSearchDoneCb chan chat1.ChatSearchInboxDoneArg 1098 InboxSearchConvHitsCb chan []chat1.UIChatSearchConvHit 1099 StellarShowConfirm chan struct{} 1100 StellarDataConfirm chan chat1.UIChatPaymentSummary 1101 StellarDataError chan keybase1.Status 1102 StellarDone chan struct{} 1103 ShowManageChannels chan string 1104 GiphyResults chan chat1.GiphySearchResults 1105 GiphyWindow chan bool 1106 CoinFlipUpdates chan []chat1.UICoinFlipStatus 1107 CommandMarkdown chan *chat1.UICommandMarkdown 1108 InboxLayoutCb chan chat1.UIInboxLayout 1109 } 1110 1111 func NewChatUI() *ChatUI { 1112 return &ChatUI{ 1113 InboxCb: make(chan NonblockInboxResult, 50), 1114 ThreadCb: make(chan NonblockThreadResult, 50), 1115 ThreadStatusCb: make(chan chat1.UIChatThreadStatus, 50), 1116 SearchHitCb: make(chan chat1.ChatSearchHitArg, 50), 1117 SearchDoneCb: make(chan chat1.ChatSearchDoneArg, 50), 1118 InboxSearchHitCb: make(chan chat1.ChatSearchInboxHitArg, 50), 1119 InboxSearchDoneCb: make(chan chat1.ChatSearchInboxDoneArg, 50), 1120 InboxSearchConvHitsCb: make(chan []chat1.UIChatSearchConvHit, 50), 1121 StellarShowConfirm: make(chan struct{}, 10), 1122 StellarDataConfirm: make(chan chat1.UIChatPaymentSummary, 10), 1123 StellarDataError: make(chan keybase1.Status, 10), 1124 StellarDone: make(chan struct{}, 10), 1125 ShowManageChannels: make(chan string, 10), 1126 GiphyResults: make(chan chat1.GiphySearchResults, 10), 1127 GiphyWindow: make(chan bool, 10), 1128 CoinFlipUpdates: make(chan []chat1.UICoinFlipStatus, 100), 1129 CommandMarkdown: make(chan *chat1.UICommandMarkdown, 10), 1130 InboxLayoutCb: make(chan chat1.UIInboxLayout, 200), 1131 } 1132 } 1133 1134 func (c *ChatUI) ChatInboxConversation(ctx context.Context, arg chat1.ChatInboxConversationArg) error { 1135 var inboxItems []chat1.InboxUIItem 1136 if err := json.Unmarshal([]byte(arg.Convs), &inboxItems); err != nil { 1137 return err 1138 } 1139 for _, inboxItem := range inboxItems { 1140 c.InboxCb <- NonblockInboxResult{ 1141 ConvRes: &inboxItem, 1142 ConvID: inboxItem.GetConvID(), 1143 } 1144 } 1145 return nil 1146 } 1147 1148 func (c *ChatUI) ChatInboxLayout(ctx context.Context, layout string) error { 1149 var uilayout chat1.UIInboxLayout 1150 if err := json.Unmarshal([]byte(layout), &uilayout); err != nil { 1151 return err 1152 } 1153 c.InboxLayoutCb <- uilayout 1154 return nil 1155 } 1156 1157 func (c *ChatUI) ChatInboxFailed(ctx context.Context, arg chat1.ChatInboxFailedArg) error { 1158 c.InboxCb <- NonblockInboxResult{ 1159 Err: fmt.Errorf("%s", arg.Error.Message), 1160 } 1161 return nil 1162 } 1163 1164 func (c *ChatUI) ChatInboxUnverified(ctx context.Context, arg chat1.ChatInboxUnverifiedArg) error { 1165 var inbox chat1.UnverifiedInboxUIItems 1166 if err := json.Unmarshal([]byte(arg.Inbox), &inbox); err != nil { 1167 return err 1168 } 1169 c.InboxCb <- NonblockInboxResult{ 1170 InboxRes: &inbox, 1171 } 1172 return nil 1173 } 1174 1175 func (c *ChatUI) ChatThreadCached(ctx context.Context, arg *string) error { 1176 var thread chat1.UIMessages 1177 if arg == nil { 1178 c.ThreadCb <- NonblockThreadResult{ 1179 Thread: nil, 1180 Full: false, 1181 } 1182 } else { 1183 if err := json.Unmarshal([]byte(*arg), &thread); err != nil { 1184 return err 1185 } 1186 c.ThreadCb <- NonblockThreadResult{ 1187 Thread: &thread, 1188 Full: false, 1189 } 1190 } 1191 return nil 1192 } 1193 1194 func (c *ChatUI) ChatThreadFull(ctx context.Context, arg string) error { 1195 var thread chat1.UIMessages 1196 if err := json.Unmarshal([]byte(arg), &thread); err != nil { 1197 return err 1198 } 1199 c.ThreadCb <- NonblockThreadResult{ 1200 Thread: &thread, 1201 Full: true, 1202 } 1203 return nil 1204 } 1205 1206 func (c *ChatUI) ChatThreadStatus(ctx context.Context, status chat1.UIChatThreadStatus) error { 1207 c.ThreadStatusCb <- status 1208 return nil 1209 } 1210 1211 func (c *ChatUI) ChatConfirmChannelDelete(ctx context.Context, arg chat1.ChatConfirmChannelDeleteArg) (bool, error) { 1212 return true, nil 1213 } 1214 1215 func (c *ChatUI) ChatSearchHit(ctx context.Context, arg chat1.ChatSearchHitArg) error { 1216 c.SearchHitCb <- arg 1217 return nil 1218 } 1219 1220 func (c *ChatUI) ChatSearchDone(ctx context.Context, arg chat1.ChatSearchDoneArg) error { 1221 c.SearchDoneCb <- arg 1222 return nil 1223 } 1224 1225 func (c *ChatUI) ChatSearchInboxHit(ctx context.Context, arg chat1.ChatSearchInboxHitArg) error { 1226 c.InboxSearchHitCb <- arg 1227 return nil 1228 } 1229 1230 func (c *ChatUI) ChatSearchConvHits(ctx context.Context, hits chat1.UIChatSearchConvHits) error { 1231 c.InboxSearchConvHitsCb <- hits.Hits 1232 return nil 1233 } 1234 1235 func (c *ChatUI) ChatSearchTeamHits(ctx context.Context, hits chat1.UIChatSearchTeamHits) error { 1236 return nil 1237 } 1238 1239 func (c *ChatUI) ChatSearchBotHits(ctx context.Context, hits chat1.UIChatSearchBotHits) error { 1240 return nil 1241 } 1242 1243 func (c *ChatUI) ChatSearchInboxStart(ctx context.Context) error { 1244 return nil 1245 } 1246 1247 func (c *ChatUI) ChatSearchInboxDone(ctx context.Context, arg chat1.ChatSearchInboxDoneArg) error { 1248 c.InboxSearchDoneCb <- arg 1249 return nil 1250 } 1251 1252 func (c *ChatUI) ChatSearchIndexStatus(ctx context.Context, arg chat1.ChatSearchIndexStatusArg) error { 1253 return nil 1254 } 1255 1256 func (c *ChatUI) ChatStellarShowConfirm(ctx context.Context) error { 1257 c.StellarShowConfirm <- struct{}{} 1258 return nil 1259 } 1260 1261 func (c *ChatUI) ChatStellarDataConfirm(ctx context.Context, summary chat1.UIChatPaymentSummary) (bool, error) { 1262 c.StellarDataConfirm <- summary 1263 return true, nil 1264 } 1265 1266 func (c *ChatUI) ChatStellarDataError(ctx context.Context, err keybase1.Status) (bool, error) { 1267 c.StellarDataError <- err 1268 return false, nil 1269 } 1270 1271 func (c *ChatUI) ChatStellarDone(ctx context.Context, canceled bool) error { 1272 c.StellarDone <- struct{}{} 1273 return nil 1274 } 1275 1276 func (c *ChatUI) ChatShowManageChannels(ctx context.Context, teamname string) error { 1277 c.ShowManageChannels <- teamname 1278 return nil 1279 } 1280 1281 func (c *ChatUI) ChatGiphySearchResults(ctx context.Context, convID chat1.ConversationID, 1282 results chat1.GiphySearchResults) error { 1283 c.GiphyResults <- results 1284 return nil 1285 } 1286 1287 func (c *ChatUI) ChatGiphyToggleResultWindow(ctx context.Context, 1288 convID chat1.ConversationID, show, clearInput bool) error { 1289 c.GiphyWindow <- show 1290 return nil 1291 } 1292 1293 func (c *ChatUI) ChatCoinFlipStatus(ctx context.Context, updates []chat1.UICoinFlipStatus) error { 1294 c.CoinFlipUpdates <- updates 1295 return nil 1296 } 1297 1298 func (c *ChatUI) ChatCommandMarkdown(ctx context.Context, convID chat1.ConversationID, 1299 md *chat1.UICommandMarkdown) error { 1300 c.CommandMarkdown <- md 1301 return nil 1302 } 1303 1304 func (c *ChatUI) ChatMaybeMentionUpdate(ctx context.Context, teamName, channel string, 1305 info chat1.UIMaybeMentionInfo) error { 1306 return nil 1307 } 1308 1309 func (c *ChatUI) ChatLoadGalleryHit(ctx context.Context, msg chat1.UIMessage) error { 1310 return nil 1311 } 1312 1313 func (c *ChatUI) ChatWatchPosition(context.Context, chat1.ConversationID, chat1.UIWatchPositionPerm) (chat1.LocationWatchID, error) { 1314 return chat1.LocationWatchID(0), nil 1315 } 1316 1317 func (c *ChatUI) ChatClearWatch(context.Context, chat1.LocationWatchID) error { 1318 return nil 1319 } 1320 1321 func (c *ChatUI) ChatCommandStatus(context.Context, chat1.ConversationID, string, 1322 chat1.UICommandStatusDisplayTyp, []chat1.UICommandStatusActionTyp) error { 1323 return nil 1324 } 1325 1326 func (c *ChatUI) ChatBotCommandsUpdateStatus(context.Context, chat1.ConversationID, 1327 chat1.UIBotCommandsUpdateStatus) error { 1328 return nil 1329 } 1330 1331 func (c *ChatUI) TriggerContactSync(context.Context) error { 1332 return nil 1333 } 1334 1335 type DummyAssetDeleter struct{} 1336 1337 func NewDummyAssetDeleter() DummyAssetDeleter { 1338 return DummyAssetDeleter{} 1339 } 1340 1341 // DeleteAssets implements github.com/keybase/go/chat/storage/storage.AssetDeleter interface. 1342 func (d DummyAssetDeleter) DeleteAssets(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID, assets []chat1.Asset) { 1343 } 1344 1345 func MockSentMessages(g *libkb.GlobalContext, t libkb.TestingTB) []MockMessage { 1346 if g.ChatHelper == nil { 1347 t.Fatal("ChatHelper is nil") 1348 } 1349 mch, ok := g.ChatHelper.(*MockChatHelper) 1350 if !ok { 1351 t.Fatalf("ChatHelper isn't a mock: %T", g.ChatHelper) 1352 } 1353 return mch.SentMessages 1354 } 1355 1356 // MockMessage only supports what we're currently testing (system message for git push). 1357 type MockMessage struct { 1358 name string 1359 topicName *string 1360 membersType chat1.ConversationMembersType 1361 ident keybase1.TLFIdentifyBehavior 1362 Body chat1.MessageBody 1363 MsgType chat1.MessageType 1364 } 1365 1366 type MockChatHelper struct { 1367 SentMessages []MockMessage 1368 convs map[string]chat1.ConversationLocal 1369 } 1370 1371 var _ libkb.ChatHelper = (*MockChatHelper)(nil) 1372 1373 func NewMockChatHelper() *MockChatHelper { 1374 return &MockChatHelper{ 1375 convs: make(map[string]chat1.ConversationLocal), 1376 } 1377 } 1378 1379 func (m *MockChatHelper) SendTextByID(ctx context.Context, convID chat1.ConversationID, 1380 tlfName string, text string, vis keybase1.TLFVisibility) error { 1381 return nil 1382 } 1383 func (m *MockChatHelper) SendMsgByID(ctx context.Context, convID chat1.ConversationID, 1384 tlfName string, body chat1.MessageBody, msgType chat1.MessageType, vis keybase1.TLFVisibility) error { 1385 return nil 1386 } 1387 func (m *MockChatHelper) SendTextByIDNonblock(ctx context.Context, convID chat1.ConversationID, 1388 tlfName string, text string, outboxID *chat1.OutboxID, replyTo *chat1.MessageID) (chat1.OutboxID, error) { 1389 return chat1.OutboxID{}, nil 1390 } 1391 func (m *MockChatHelper) SendMsgByIDNonblock(ctx context.Context, convID chat1.ConversationID, 1392 tlfName string, body chat1.MessageBody, msgType chat1.MessageType, inOutboxID *chat1.OutboxID, 1393 replyTo *chat1.MessageID) (chat1.OutboxID, error) { 1394 return chat1.OutboxID{}, nil 1395 } 1396 func (m *MockChatHelper) SendTextByName(ctx context.Context, name string, topicName *string, 1397 membersType chat1.ConversationMembersType, ident keybase1.TLFIdentifyBehavior, text string) error { 1398 rb, err := libkb.RandBytes(10) 1399 if err != nil { 1400 return err 1401 } 1402 // use this to fake making channels... 1403 _, ok := m.convs[m.convKey(name, topicName)] 1404 if !ok { 1405 m.convs[m.convKey(name, topicName)] = chat1.ConversationLocal{ 1406 Info: chat1.ConversationInfoLocal{ 1407 Id: rb, 1408 TopicName: *topicName, 1409 }, 1410 } 1411 } 1412 1413 return nil 1414 } 1415 func (m *MockChatHelper) SendMsgByName(ctx context.Context, name string, topicName *string, 1416 membersType chat1.ConversationMembersType, ident keybase1.TLFIdentifyBehavior, body chat1.MessageBody, 1417 msgType chat1.MessageType) error { 1418 m.SentMessages = append(m.SentMessages, MockMessage{ 1419 name: name, 1420 topicName: topicName, 1421 membersType: membersType, 1422 ident: ident, 1423 Body: body, 1424 MsgType: msgType, 1425 }) 1426 return nil 1427 } 1428 func (m *MockChatHelper) SendTextByNameNonblock(ctx context.Context, name string, topicName *string, 1429 membersType chat1.ConversationMembersType, ident keybase1.TLFIdentifyBehavior, text string, outboxID *chat1.OutboxID) (chat1.OutboxID, error) { 1430 return chat1.OutboxID{}, nil 1431 } 1432 func (m *MockChatHelper) SendMsgByNameNonblock(ctx context.Context, name string, topicName *string, 1433 membersType chat1.ConversationMembersType, ident keybase1.TLFIdentifyBehavior, body chat1.MessageBody, 1434 msgType chat1.MessageType, outboxID *chat1.OutboxID) (chat1.OutboxID, error) { 1435 m.SentMessages = append(m.SentMessages, MockMessage{ 1436 name: name, 1437 topicName: topicName, 1438 membersType: membersType, 1439 ident: ident, 1440 Body: body, 1441 MsgType: msgType, 1442 }) 1443 return chat1.OutboxID{}, nil 1444 } 1445 1446 func (m *MockChatHelper) DeleteMsg(ctx context.Context, convID chat1.ConversationID, tlfName string, 1447 msgID chat1.MessageID) error { 1448 return nil 1449 } 1450 1451 func (m *MockChatHelper) DeleteMsgNonblock(ctx context.Context, convID chat1.ConversationID, tlfName string, 1452 msgID chat1.MessageID) error { 1453 return nil 1454 } 1455 1456 func (m *MockChatHelper) FindConversations(ctx context.Context, name string, 1457 topicName *string, topicType chat1.TopicType, 1458 membersType chat1.ConversationMembersType, vis keybase1.TLFVisibility) ([]chat1.ConversationLocal, error) { 1459 1460 conv, ok := m.convs[m.convKey(name, topicName)] 1461 if ok { 1462 return []chat1.ConversationLocal{conv}, nil 1463 } 1464 1465 return nil, nil 1466 } 1467 1468 func (m *MockChatHelper) FindConversationsByID(ctx context.Context, convIDs []chat1.ConversationID) (convs []chat1.ConversationLocal, err error) { 1469 for _, id := range convIDs { 1470 for _, v := range m.convs { 1471 if bytes.Equal(v.Info.Id, id) { 1472 convs = append(convs, v) 1473 } 1474 } 1475 } 1476 return convs, nil 1477 } 1478 1479 func (m *MockChatHelper) GetChannelTopicName(ctx context.Context, teamID keybase1.TeamID, 1480 topicType chat1.TopicType, convID chat1.ConversationID) (string, error) { 1481 for _, v := range m.convs { 1482 if v.Info.Id.Eq(convID) { 1483 return v.Info.TopicName, nil 1484 } 1485 } 1486 return "", fmt.Errorf("MockChatHelper.GetChannelTopicName conv not found %v", convID) 1487 } 1488 1489 func (m *MockChatHelper) UpgradeKBFSToImpteam(ctx context.Context, tlfName string, tlfID chat1.TLFID, 1490 public bool) error { 1491 return nil 1492 } 1493 1494 func (m *MockChatHelper) GetMessages(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID, 1495 msgIDs []chat1.MessageID, resolveSupersedes bool, reason *chat1.GetThreadReason) ([]chat1.MessageUnboxed, error) { 1496 return nil, nil 1497 } 1498 1499 func (m *MockChatHelper) GetMessage(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID, 1500 msgID chat1.MessageID, resolveSupersedes bool, reason *chat1.GetThreadReason) (chat1.MessageUnboxed, error) { 1501 return chat1.MessageUnboxed{}, nil 1502 } 1503 1504 func (m *MockChatHelper) UserReacjis(ctx context.Context, uid gregor1.UID) keybase1.UserReacjis { 1505 return keybase1.UserReacjis{} 1506 } 1507 1508 func (m *MockChatHelper) JourneycardTimeTravel(ctx context.Context, uid gregor1.UID, duration time.Duration) (int, int, error) { 1509 return 0, 0, fmt.Errorf("JourneycardTimeTravel not implemented on mock") 1510 } 1511 1512 func (m *MockChatHelper) JourneycardResetAllConvs(ctx context.Context, uid gregor1.UID) error { 1513 return fmt.Errorf("JourneycardResetAllConvs not implemented on mock") 1514 } 1515 1516 func (m *MockChatHelper) JourneycardDebugState(ctx context.Context, uid gregor1.UID, teamID keybase1.TeamID) (string, error) { 1517 return "", fmt.Errorf("JourneycardDebugState not implemented on mock") 1518 } 1519 1520 func (m *MockChatHelper) NewConversation(ctx context.Context, uid gregor1.UID, tlfName string, 1521 topicName *string, topicType chat1.TopicType, membersType chat1.ConversationMembersType, 1522 vis keybase1.TLFVisibility) (chat1.ConversationLocal, bool, error) { 1523 return chat1.ConversationLocal{}, false, nil 1524 } 1525 1526 func (m *MockChatHelper) NewConversationSkipFindExisting(ctx context.Context, uid gregor1.UID, tlfName string, 1527 topicName *string, topicType chat1.TopicType, membersType chat1.ConversationMembersType, 1528 vis keybase1.TLFVisibility) (chat1.ConversationLocal, bool, error) { 1529 return chat1.ConversationLocal{}, false, nil 1530 } 1531 1532 func (m *MockChatHelper) NewConversationWithMemberSourceConv(ctx context.Context, uid gregor1.UID, tlfName string, 1533 topicName *string, topicType chat1.TopicType, membersType chat1.ConversationMembersType, 1534 vis keybase1.TLFVisibility, retentionPolicy *chat1.RetentionPolicy, 1535 memberSourceConv *chat1.ConversationID) (chat1.ConversationLocal, bool, error) { 1536 return chat1.ConversationLocal{}, false, nil 1537 } 1538 1539 func (m *MockChatHelper) JoinConversationByID(ctx context.Context, uid gregor1.UID, 1540 convID chat1.ConversationID) error { 1541 return nil 1542 } 1543 1544 func (m *MockChatHelper) JoinConversationByName(ctx context.Context, uid gregor1.UID, tlfName, 1545 topicName string, topicType chat1.TopicType, vid keybase1.TLFVisibility) error { 1546 return nil 1547 } 1548 1549 func (m *MockChatHelper) LeaveConversation(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID) error { 1550 return nil 1551 } 1552 1553 func (m *MockChatHelper) InTeam(context.Context, gregor1.UID, keybase1.TeamID) (bool, error) { 1554 return false, nil 1555 } 1556 1557 func (m *MockChatHelper) convKey(name string, topicName *string) string { 1558 if topicName == nil { 1559 return name + ":general" 1560 } 1561 return name + ":" + *topicName 1562 } 1563 1564 func (m *MockChatHelper) BulkAddToConv(context.Context, gregor1.UID, chat1.ConversationID, []string) error { 1565 return nil 1566 } 1567 1568 type MockUIRouter struct { 1569 ui libkb.ChatUI 1570 } 1571 1572 var _ libkb.UIRouter = (*MockUIRouter)(nil) 1573 1574 func NewMockUIRouter(chatUI libkb.ChatUI) *MockUIRouter { 1575 return &MockUIRouter{ 1576 ui: chatUI, 1577 } 1578 } 1579 1580 func (f *MockUIRouter) SetUI(libkb.ConnectionID, libkb.UIKind) {} 1581 func (f *MockUIRouter) GetIdentifyUI() (libkb.IdentifyUI, error) { return nil, nil } 1582 func (f *MockUIRouter) GetIdentifyUICtx(ctx context.Context) (int, libkb.IdentifyUI, error) { 1583 return 0, nil, nil 1584 } 1585 func (f *MockUIRouter) GetSecretUI(sessionID int) (libkb.SecretUI, error) { return nil, nil } 1586 func (f *MockUIRouter) GetRekeyUI() (keybase1.RekeyUIInterface, int, error) { return nil, 0, nil } 1587 func (f *MockUIRouter) GetRekeyUINoSessionID() (keybase1.RekeyUIInterface, error) { return nil, nil } 1588 func (f *MockUIRouter) GetHomeUI() (keybase1.HomeUIInterface, error) { return nil, nil } 1589 func (f *MockUIRouter) GetIdentify3UIAdapter(libkb.MetaContext) (libkb.IdentifyUI, error) { 1590 return nil, nil 1591 } 1592 func (f *MockUIRouter) GetIdentify3UI(libkb.MetaContext) (keybase1.Identify3UiInterface, error) { 1593 return nil, nil 1594 } 1595 func (f *MockUIRouter) GetLogUI() (libkb.LogUI, error) { return nil, nil } 1596 func (f *MockUIRouter) WaitForUIType(uiKind libkb.UIKind, timeout time.Duration) bool { return false } 1597 func (f *MockUIRouter) DumpUIs() map[libkb.UIKind]libkb.ConnectionID { return nil } 1598 func (f *MockUIRouter) Shutdown() {} 1599 1600 func (f *MockUIRouter) GetChatUI() (libkb.ChatUI, error) { 1601 return f.ui, nil 1602 }