github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/chat/sync_test.go (about) 1 package chat 2 3 import ( 4 "testing" 5 "time" 6 7 "github.com/keybase/client/go/chat/globals" 8 "github.com/keybase/client/go/chat/storage" 9 "github.com/keybase/client/go/chat/types" 10 "github.com/keybase/client/go/chat/utils" 11 "github.com/keybase/client/go/kbtest" 12 "github.com/keybase/client/go/libkb" 13 "github.com/keybase/client/go/protocol/chat1" 14 "github.com/keybase/client/go/protocol/gregor1" 15 "github.com/keybase/client/go/protocol/keybase1" 16 "github.com/stretchr/testify/require" 17 "golang.org/x/net/context" 18 ) 19 20 func newBlankConv(ctx context.Context, t *testing.T, tc *kbtest.ChatTestContext, 21 uid gregor1.UID, ri chat1.RemoteInterface, sender types.Sender, tlfName string) chat1.Conversation { 22 return newBlankConvWithMembersType(ctx, t, tc, uid, ri, sender, tlfName, 23 chat1.ConversationMembersType_IMPTEAMUPGRADE) 24 } 25 26 func localizeConv(ctx context.Context, t *testing.T, tc *kbtest.ChatTestContext, 27 uid gregor1.UID, conv chat1.Conversation) chat1.ConversationLocal { 28 rc := utils.RemoteConv(conv) 29 locals, _, err := tc.Context().InboxSource.Localize(ctx, uid, []types.RemoteConversation{rc}, 30 types.ConversationLocalizerBlocking) 31 require.NoError(t, err) 32 require.Equal(t, 1, len(locals)) 33 return locals[0] 34 } 35 36 func newBlankConvWithMembersType(ctx context.Context, t *testing.T, tc *kbtest.ChatTestContext, 37 uid gregor1.UID, ri chat1.RemoteInterface, sender types.Sender, tlfName string, 38 membersType chat1.ConversationMembersType) chat1.Conversation { 39 res, created, err := NewConversation(ctx, tc.Context(), uid, tlfName, nil, chat1.TopicType_CHAT, membersType, 40 keybase1.TLFVisibility_PRIVATE, nil, func() chat1.RemoteInterface { return ri }, 41 NewConvFindExistingNormal) 42 require.NoError(t, err) 43 require.True(t, created) 44 convID := res.GetConvID() 45 ires, err := ri.GetInboxRemote(ctx, chat1.GetInboxRemoteArg{ 46 Query: &chat1.GetInboxQuery{ 47 ConvID: &convID, 48 }, 49 }) 50 require.NoError(t, err) 51 return ires.Inbox.Full().Conversations[0] 52 } 53 54 func newConv(ctx context.Context, t *testing.T, tc *kbtest.ChatTestContext, uid gregor1.UID, 55 ri chat1.RemoteInterface, sender types.Sender, tlfName string) (chat1.ConversationLocal, chat1.Conversation) { 56 conv := newBlankConv(ctx, t, tc, uid, ri, sender, tlfName) 57 _, _, err := sender.Send(ctx, conv.GetConvID(), chat1.MessagePlaintext{ 58 ClientHeader: chat1.MessageClientHeader{ 59 Conv: conv.Metadata.IdTriple, 60 Sender: uid, 61 TlfName: tlfName, 62 MessageType: chat1.MessageType_TEXT, 63 }, 64 MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{Body: "foo"}), 65 }, 0, nil, nil, nil) 66 require.NoError(t, err) 67 convID := conv.GetConvID() 68 ib, _, err := tc.Context().InboxSource.Read(ctx, uid, types.ConversationLocalizerBlocking, 69 types.InboxSourceDataSourceAll, nil, &chat1.GetInboxLocalQuery{ 70 ConvIDs: []chat1.ConversationID{convID}, 71 }) 72 require.NoError(t, err) 73 require.Equal(t, 1, len(ib.Convs)) 74 require.Equal(t, 1, len(ib.ConvsUnverified)) 75 return ib.Convs[0], ib.ConvsUnverified[0].Conv 76 } 77 78 func doSync(t *testing.T, syncer types.Syncer, ri chat1.RemoteInterface, uid gregor1.UID) { 79 res, err := ri.SyncAll(context.TODO(), chat1.SyncAllArg{ 80 Uid: uid, 81 }) 82 require.NoError(t, err) 83 require.NoError(t, syncer.Sync(context.TODO(), ri, uid, &res.Chat)) 84 } 85 86 func TestSyncerConnected(t *testing.T) { 87 ctx, world, ri2, _, sender, list := setupTest(t, 3) 88 defer world.Cleanup() 89 90 ri := ri2.(*kbtest.ChatRemoteMock) 91 u := world.GetUsers()[0] 92 u1 := world.GetUsers()[1] 93 u2 := world.GetUsers()[2] 94 uid := u.User.GetUID().ToBytes() 95 tc := world.Tcs[u.Username] 96 syncer := NewSyncer(tc.Context()) 97 syncer.isConnected = true 98 ibox := storage.NewInbox(tc.Context()) 99 store := storage.New(tc.Context(), tc.ChatG.ConvSource) 100 101 var convs []chat1.Conversation 102 convs = append(convs, newBlankConv(ctx, t, tc, uid, ri, sender, u.Username+","+u1.Username)) 103 convs = append(convs, newBlankConv(ctx, t, tc, uid, ri, sender, u.Username+","+u2.Username)) 104 convs = append(convs, newBlankConv(ctx, t, tc, uid, ri, sender, u.Username+","+u2.Username+","+u1.Username)) 105 for index, conv := range convs { 106 t.Logf("index: %d conv: %s", index, conv.GetConvID()) 107 } 108 // background loader will pick up all the convs from the creates above 109 convMap := make(map[chat1.ConvIDStr]bool) 110 for _, c := range convs { 111 convMap[c.GetConvID().ConvIDStr()] = true 112 } 113 for i := 0; i < len(convs); i++ { 114 select { 115 case convID := <-list.bgConvLoads: 116 delete(convMap, convID.ConvIDStr()) 117 case <-time.After(20 * time.Second): 118 require.Fail(t, "no background conv loaded") 119 } 120 } 121 require.Zero(t, len(convMap)) 122 123 t.Logf("test current") 124 ri.SyncInboxFunc = func(m *kbtest.ChatRemoteMock, ctx context.Context, vers chat1.InboxVers) (chat1.SyncInboxRes, error) { 125 return chat1.NewSyncInboxResWithCurrent(), nil 126 } 127 doSync(t, syncer, ri, uid) 128 select { 129 case sres := <-list.inboxSynced: 130 typ, err := sres.SyncType() 131 require.NoError(t, err) 132 require.Equal(t, chat1.SyncInboxResType_CURRENT, typ) 133 case <-time.After(20 * time.Second): 134 require.Fail(t, "no inbox sync received") 135 } 136 137 t.Logf("test clear") 138 ri.SyncInboxFunc = func(m *kbtest.ChatRemoteMock, ctx context.Context, vers chat1.InboxVers) (chat1.SyncInboxRes, error) { 139 return chat1.NewSyncInboxResWithClear(), nil 140 } 141 doSync(t, syncer, ri, uid) 142 select { 143 case sres := <-list.inboxSynced: 144 typ, err := sres.SyncType() 145 require.NoError(t, err) 146 require.Equal(t, chat1.SyncInboxResType_CLEAR, typ) 147 case <-time.After(20 * time.Second): 148 require.Fail(t, "no inbox synced received") 149 } 150 _, _, err := ibox.ReadAll(ctx, uid, true) 151 require.Error(t, err) 152 require.IsType(t, storage.MissError{}, err) 153 154 t.Logf("test incremental") 155 mconv := convs[1] 156 _, cerr := tc.ChatG.ConvSource.Pull(ctx, mconv.GetConvID(), uid, chat1.GetThreadReason_GENERAL, nil, nil, 157 nil) 158 require.NoError(t, cerr) 159 _, _, serr := tc.ChatG.InboxSource.Read(ctx, uid, types.ConversationLocalizerBlocking, 160 types.InboxSourceDataSourceAll, nil, nil) 161 require.NoError(t, serr) 162 _, iconvs, err := ibox.ReadAll(ctx, uid, true) 163 require.NoError(t, err) 164 require.Equal(t, len(convs), len(iconvs)) 165 166 ri.SyncInboxFunc = func(m *kbtest.ChatRemoteMock, ctx context.Context, vers chat1.InboxVers) (chat1.SyncInboxRes, error) { 167 mconv.Metadata.Status = chat1.ConversationStatus_MUTED 168 return chat1.NewSyncInboxResWithIncremental(chat1.SyncIncrementalRes{ 169 Vers: 100, 170 Convs: []chat1.Conversation{mconv}, 171 }), nil 172 } 173 doSync(t, syncer, ri, uid) 174 select { 175 case sres := <-list.inboxSynced: 176 typ, err := sres.SyncType() 177 require.NoError(t, err) 178 require.Equal(t, chat1.SyncInboxResType_INCREMENTAL, typ) 179 updates := sres.Incremental().Items 180 require.Equal(t, 1, len(updates)) 181 require.Equal(t, convs[1].GetConvID().ConvIDStr(), updates[0].Conv.ConvID) 182 require.True(t, updates[0].ShouldUnbox) 183 case <-time.After(20 * time.Second): 184 require.Fail(t, "no threads stale received") 185 } 186 select { 187 case cid := <-list.bgConvLoads: 188 require.Equal(t, convs[1].GetConvID(), cid) 189 case <-time.After(20 * time.Second): 190 require.Fail(t, "no background conv loaded") 191 } 192 vers, iconvs, err := ibox.ReadAll(context.TODO(), uid, true) 193 require.NoError(t, err) 194 require.Equal(t, len(convs), len(iconvs)) 195 for _, ic := range iconvs { 196 if ic.GetConvID().Eq(mconv.GetConvID()) { 197 require.Equal(t, chat1.ConversationStatus_MUTED, ic.Conv.Metadata.Status) 198 } 199 } 200 require.Equal(t, chat1.ConversationStatus_UNFILED, convs[1].Metadata.Status) 201 require.Equal(t, chat1.InboxVers(100), vers) 202 thread, cerr := store.Fetch(context.TODO(), mconv, uid, nil, nil, nil) 203 require.NoError(t, cerr) 204 require.Equal(t, 1, len(thread.Thread.Messages)) 205 206 t.Logf("test server version") 207 srvVers, err := ibox.ServerVersion(context.TODO(), uid) 208 require.NoError(t, err) 209 require.Zero(t, srvVers) 210 ri.SyncInboxFunc = func(m *kbtest.ChatRemoteMock, ctx context.Context, vers chat1.InboxVers) (chat1.SyncInboxRes, error) { 211 return chat1.NewSyncInboxResWithCurrent(), nil 212 } 213 ri.CacheInboxVersion = 5 214 ri.CacheBodiesVersion = 5 215 doSync(t, syncer, ri, uid) 216 select { 217 case sres := <-list.inboxSynced: 218 typ, err := sres.SyncType() 219 require.NoError(t, err) 220 require.Equal(t, chat1.SyncInboxResType_CLEAR, typ) 221 case <-time.After(20 * time.Second): 222 require.Fail(t, "no inbox stale received") 223 } 224 _, _, err = ibox.ReadAll(ctx, uid, true) 225 require.Error(t, err) 226 require.IsType(t, storage.MissError{}, err) 227 _, cerr = store.Fetch(ctx, mconv, uid, nil, nil, nil) 228 require.Error(t, cerr) 229 require.IsType(t, storage.MissError{}, cerr) 230 _, _, serr = tc.Context().InboxSource.Read(ctx, uid, types.ConversationLocalizerBlocking, 231 types.InboxSourceDataSourceAll, nil, nil) 232 require.NoError(t, serr) 233 _, iconvs, err = ibox.ReadAll(ctx, uid, true) 234 require.NoError(t, err) 235 require.Equal(t, len(convs), len(iconvs)) 236 srvVers, err = ibox.ServerVersion(context.TODO(), uid) 237 require.NoError(t, err) 238 require.Equal(t, 5, srvVers) 239 240 // Make sure we didn't get any stales 241 select { 242 case <-list.threadsStale: 243 require.Fail(t, "no thread stales") 244 default: 245 } 246 select { 247 case <-list.inboxStale: 248 require.Fail(t, "no inbox stales") 249 default: 250 } 251 } 252 253 func TestSyncerNeverJoined(t *testing.T) { 254 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 255 switch mt { 256 case chat1.ConversationMembersType_TEAM: 257 default: 258 return 259 } 260 261 ctc := makeChatTestContext(t, "SyncerNeverJoined", 2) 262 defer ctc.cleanup() 263 users := ctc.users() 264 265 ctc1 := ctc.as(t, users[0]) 266 ctc2 := ctc.as(t, users[1]) 267 ctx1 := ctc1.startCtx 268 uid1 := gregor1.UID(users[0].GetUID().ToBytes()) 269 uid2 := gregor1.UID(users[1].GetUID().ToBytes()) 270 g1 := ctc1.h.G() 271 g2 := ctc2.h.G() 272 273 syncer1 := NewSyncer(g1) 274 syncer1.isConnected = true 275 syncer2 := NewSyncer(g2) 276 syncer2.isConnected = true 277 278 listener1 := newServerChatListener() 279 g1.NotifyRouter.AddListener(listener1) 280 listener2 := newServerChatListener() 281 g2.NotifyRouter.AddListener(listener2) 282 t.Logf("u0: %s, u1: %s", users[0].GetUID(), users[1].GetUID()) 283 284 conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt, 285 ctc.as(t, users[1]).user()) 286 convID := conv.Id 287 288 // create a channel, ensure user1 gets an inbox bump but does not see 289 // the conversation since it's in the NEVER_JOINED state. 290 topicName := "chan1" 291 channel, err := ctc1.chatLocalHandler().NewConversationLocal(ctx1, 292 chat1.NewConversationLocalArg{ 293 TlfName: conv.TlfName, 294 TopicName: &topicName, 295 TopicType: chat1.TopicType_CHAT, 296 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 297 MembersType: chat1.ConversationMembersType_TEAM, 298 }) 299 require.NoError(t, err) 300 chanID := channel.Conv.GetConvID() 301 t.Logf("conv: %s chan: %s", conv.Id, chanID) 302 303 consumeNewMsgRemote(t, listener1, chat1.MessageType_JOIN) 304 consumeTeamType(t, listener1) 305 consumeTeamType(t, listener2) 306 consumeNewMsgRemote(t, listener1, chat1.MessageType_SYSTEM) 307 consumeNewMsgRemote(t, listener2, chat1.MessageType_SYSTEM) 308 309 doAuthedSync := func(ctx context.Context, g *globals.Context, syncer types.Syncer, ri chat1.RemoteInterface, uid gregor1.UID) { 310 nist, err := g.ExternalG().ActiveDevice.NIST(context.TODO()) 311 require.NoError(t, err) 312 sessionToken := gregor1.SessionToken(nist.Token().String()) 313 res, err := ri.SyncAll(ctx, chat1.SyncAllArg{ 314 Uid: uid, 315 Session: sessionToken, 316 }) 317 require.NoError(t, err) 318 require.NoError(t, syncer.Sync(context.TODO(), ri, uid, &res.Chat)) 319 } 320 321 ctx := context.TODO() 322 doAuthedSync(ctx, g1, syncer1, ctc1.ri, uid1) 323 select { 324 case sres := <-listener1.inboxSynced: 325 typ, err := sres.SyncType() 326 require.NoError(t, err) 327 require.Equal(t, chat1.SyncInboxResType_INCREMENTAL, typ) 328 require.Len(t, sres.Incremental().Items, 2) 329 var foundConv, foundChan bool 330 for _, item := range sres.Incremental().Items { 331 if convID.ConvIDStr() == item.Conv.ConvID { 332 foundConv = true 333 } else if chanID.ConvIDStr() == item.Conv.ConvID { 334 foundChan = true 335 } 336 require.Equal(t, chat1.ConversationMemberStatus_ACTIVE, item.Conv.MemberStatus) 337 } 338 require.True(t, foundConv) 339 require.True(t, foundChan) 340 case <-time.After(20 * time.Second): 341 require.Fail(t, "no inbox synced received") 342 } 343 344 // simulate an old client that doesn't understand NEVER_JOINED 345 userAgent := libkb.UserAgent 346 libkb.UserAgent = "old:ua:2.12.1" 347 ctx = globals.ChatCtx(context.TODO(), g1, keybase1.TLFIdentifyBehavior_CHAT_GUI, nil, nil) 348 libkb.UserAgent = userAgent // reset user agent for future tests. 349 doAuthedSync(ctx, g2, syncer2, ctc2.ri, uid2) 350 select { 351 case sres := <-listener2.inboxSynced: 352 typ, err := sres.SyncType() 353 require.NoError(t, err) 354 require.Equal(t, chat1.SyncInboxResType_INCREMENTAL, typ) 355 require.Len(t, sres.Incremental().Items, 1) 356 require.Equal(t, convID.ConvIDStr(), sres.Incremental().Items[0].Conv.ConvID) 357 require.Equal(t, chat1.ConversationMemberStatus_ACTIVE, sres.Incremental().Items[0].Conv.MemberStatus) 358 case <-time.After(20 * time.Second): 359 require.Fail(t, "no inbox synced received") 360 } 361 362 ctx = context.TODO() 363 doAuthedSync(ctx, g2, syncer2, ctc2.ri, uid2) 364 select { 365 case sres := <-listener2.inboxSynced: 366 typ, err := sres.SyncType() 367 require.NoError(t, err) 368 require.Equal(t, chat1.SyncInboxResType_INCREMENTAL, typ) 369 require.Len(t, sres.Incremental().Items, 2) 370 case <-time.After(20 * time.Second): 371 require.Fail(t, "no inbox synced received") 372 } 373 }) 374 } 375 376 func TestSyncerMembersTypeChanged(t *testing.T) { 377 ctx, world, ri2, _, sender, list := setupTest(t, 1) 378 defer world.Cleanup() 379 380 ri := ri2.(*kbtest.ChatRemoteMock) 381 u := world.GetUsers()[0] 382 tc := world.Tcs[u.Username] 383 syncer := NewSyncer(tc.Context()) 384 syncer.isConnected = true 385 uid := gregor1.UID(u.User.GetUID().ToBytes()) 386 387 conv := newBlankConvWithMembersType(ctx, t, tc, uid, ri, sender, u.Username, chat1.ConversationMembersType_KBFS) 388 t.Logf("convID: %s", conv.GetConvID()) 389 convID := conv.GetConvID() 390 391 _, msg, err := sender.Send(ctx, convID, chat1.MessagePlaintext{ 392 ClientHeader: chat1.MessageClientHeader{ 393 Conv: conv.Metadata.IdTriple, 394 Sender: uid, 395 TlfName: u.Username, 396 TlfPublic: false, 397 MessageType: chat1.MessageType_TEXT, 398 }, 399 MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{ 400 Body: "hi", 401 }), 402 }, 0, nil, nil, nil) 403 require.NoError(t, err) 404 s := storage.New(tc.Context(), tc.ChatG.ConvSource) 405 storedMsgs, err := s.FetchMessages(ctx, convID, uid, []chat1.MessageID{msg.GetMessageID()}) 406 require.NoError(t, err) 407 require.Len(t, storedMsgs, 1) 408 require.NotNil(t, storedMsgs[0]) 409 require.Equal(t, msg.GetMessageID(), storedMsgs[0].GetMessageID()) 410 411 ri.SyncInboxFunc = func(m *kbtest.ChatRemoteMock, ctx context.Context, vers chat1.InboxVers) (chat1.SyncInboxRes, error) { 412 conv.Metadata.MembersType = chat1.ConversationMembersType_IMPTEAMUPGRADE 413 return chat1.NewSyncInboxResWithIncremental(chat1.SyncIncrementalRes{ 414 Vers: 100, 415 Convs: []chat1.Conversation{conv}, 416 }), nil 417 } 418 doSync(t, syncer, ri, uid) 419 select { 420 case sres := <-list.inboxSynced: 421 typ, err := sres.SyncType() 422 require.NoError(t, err) 423 require.Equal(t, chat1.SyncInboxResType_INCREMENTAL, typ) 424 require.Equal(t, convID.ConvIDStr(), sres.Incremental().Items[0].Conv.ConvID) 425 require.Equal(t, chat1.ConversationMembersType_IMPTEAMUPGRADE, 426 sres.Incremental().Items[0].Conv.MembersType) 427 require.True(t, sres.Incremental().Items[0].ShouldUnbox) 428 storedMsgs, err = s.FetchMessages(ctx, convID, uid, []chat1.MessageID{msg.GetMessageID()}) 429 require.NoError(t, err) 430 require.Len(t, storedMsgs, 1) 431 require.Nil(t, storedMsgs[0]) 432 case <-time.After(20 * time.Second): 433 require.Fail(t, "no inbox synced received") 434 } 435 436 } 437 438 func TestSyncerAppState(t *testing.T) { 439 ctx, world, ri2, _, sender, list := setupTest(t, 1) 440 defer world.Cleanup() 441 442 ri := ri2.(*kbtest.ChatRemoteMock) 443 u := world.GetUsers()[0] 444 uid := u.User.GetUID().ToBytes() 445 tc := world.Tcs[u.Username] 446 syncer := NewSyncer(tc.Context()) 447 syncer.isConnected = true 448 449 _, conv := newConv(ctx, t, tc, uid, ri, sender, u.Username) 450 t.Logf("test incremental") 451 tc.G.MobileAppState.Update(keybase1.MobileAppState_BACKGROUND) 452 syncer.SendChatStaleNotifications(context.TODO(), uid, []chat1.ConversationStaleUpdate{ 453 { 454 ConvID: conv.GetConvID(), 455 UpdateType: chat1.StaleUpdateType_NEWACTIVITY, 456 }, 457 }, true) 458 select { 459 case <-list.threadsStale: 460 require.Fail(t, "no stale messages in bkg mode") 461 default: 462 } 463 464 tc.G.MobileAppState.Update(keybase1.MobileAppState_FOREGROUND) 465 select { 466 case updates := <-list.threadsStale: 467 require.Equal(t, 1, len(updates)) 468 require.Equal(t, chat1.StaleUpdateType_NEWACTIVITY, updates[0].UpdateType) 469 case <-time.After(20 * time.Second): 470 require.Fail(t, "no stale messages") 471 } 472 473 tc.G.MobileAppState.Update(keybase1.MobileAppState_BACKGROUND) 474 syncer.SendChatStaleNotifications(context.TODO(), uid, nil, true) 475 select { 476 case <-list.inboxStale: 477 require.Fail(t, "no stale messages in bkg mode") 478 default: 479 } 480 481 tc.G.MobileAppState.Update(keybase1.MobileAppState_FOREGROUND) 482 select { 483 case <-list.inboxStale: 484 case <-time.After(20 * time.Second): 485 require.Fail(t, "no inbox stale message") 486 } 487 } 488 489 // Test that we miss an Expunge and then get it in an incremental sync, 490 // the messages get deleted. 491 func TestSyncerRetentionExpunge(t *testing.T) { 492 ctx, world, ri2, _, sender, list := setupTest(t, 2) 493 defer world.Cleanup() 494 495 ri := ri2.(*kbtest.ChatRemoteMock) 496 u := world.GetUsers()[0] 497 u1 := world.GetUsers()[1] 498 uid := u.User.GetUID().ToBytes() 499 tc := world.Tcs[u.Username] 500 syncer := NewSyncer(tc.Context()) 501 syncer.isConnected = true 502 ibox := storage.NewInbox(tc.Context()) 503 store := storage.New(tc.Context(), tc.ChatG.ConvSource) 504 505 tlfName := u.Username + "," + u1.Username 506 mconv := newBlankConv(ctx, t, tc, uid, ri, sender, tlfName) 507 select { 508 case cid := <-list.bgConvLoads: 509 require.Equal(t, mconv.GetConvID(), cid) 510 case <-time.After(20 * time.Second): 511 require.Fail(t, "no background conv loaded") 512 } 513 514 t.Logf("test incremental") 515 _, _, err := sender.Send(ctx, mconv.GetConvID(), chat1.MessagePlaintext{ 516 ClientHeader: chat1.MessageClientHeader{ 517 Conv: mconv.Metadata.IdTriple, 518 Sender: uid, 519 TlfName: tlfName, 520 MessageType: chat1.MessageType_TEXT, 521 }, 522 MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{ 523 Body: "hi", 524 }), 525 }, 0, nil, nil, nil) 526 require.NoError(t, err) 527 tv, cerr := tc.ChatG.ConvSource.Pull(ctx, mconv.GetConvID(), uid, chat1.GetThreadReason_GENERAL, nil, 528 nil, nil) 529 require.NoError(t, cerr) 530 require.Equal(t, 2, len(tv.Messages)) 531 _, _, serr := tc.ChatG.InboxSource.Read(ctx, uid, types.ConversationLocalizerBlocking, 532 types.InboxSourceDataSourceAll, nil, nil) 533 require.NoError(t, serr) 534 select { 535 case cid := <-list.bgConvLoads: 536 require.Equal(t, mconv.GetConvID(), cid) 537 case <-time.After(20 * time.Second): 538 require.Fail(t, "no background conv loaded") 539 } 540 _, iconvs, err := ibox.ReadAll(ctx, uid, true) 541 require.NoError(t, err) 542 require.Len(t, iconvs, 1) 543 require.Equal(t, chat1.MessageID(2), iconvs[0].Conv.ReaderInfo.MaxMsgid) 544 mconv = iconvs[0].Conv 545 546 time.Sleep(400 * time.Millisecond) 547 select { 548 case <-list.bgConvLoads: 549 require.Fail(t, "no loads here") 550 default: 551 } 552 ri.SyncInboxFunc = func(m *kbtest.ChatRemoteMock, ctx context.Context, vers chat1.InboxVers) (chat1.SyncInboxRes, error) { 553 mconv.Expunge = chat1.Expunge{Upto: 12} 554 return chat1.NewSyncInboxResWithIncremental(chat1.SyncIncrementalRes{ 555 Vers: 100, 556 Convs: []chat1.Conversation{mconv}, 557 }), nil 558 } 559 doSync(t, syncer, ri, uid) 560 select { 561 case sres := <-list.inboxSynced: 562 typ, err := sres.SyncType() 563 require.NoError(t, err) 564 require.Equal(t, chat1.SyncInboxResType_INCREMENTAL, typ) 565 updates := sres.Incremental().Items 566 require.Equal(t, 1, len(updates)) 567 require.Equal(t, mconv.GetConvID().ConvIDStr(), updates[0].Conv.ConvID) 568 case <-time.After(20 * time.Second): 569 require.Fail(t, "no threads stale received") 570 } 571 select { 572 case cid := <-list.bgConvLoads: 573 require.Equal(t, mconv.GetConvID(), cid) 574 case <-time.After(20 * time.Second): 575 require.Fail(t, "no background conv loaded") 576 } 577 _, iconvs, err = ibox.ReadAll(context.TODO(), uid, true) 578 require.NoError(t, err) 579 require.Len(t, iconvs, 1) 580 require.Equal(t, chat1.Expunge{Upto: 12}, iconvs[0].Conv.Expunge) 581 thread, cerr := store.Fetch(context.TODO(), mconv, uid, nil, nil, nil) 582 require.NoError(t, cerr) 583 require.True(t, len(thread.Thread.Messages) > 1) 584 for i, m := range thread.Thread.Messages { 585 t.Logf("message %v", i) 586 require.True(t, m.IsValid()) 587 require.True(t, m.Valid().MessageBody.IsNil(), "remaining messages should have no body") 588 } 589 } 590 591 func TestSyncerTeamFilter(t *testing.T) { 592 ctx, world, ri2, _, sender, list := setupTest(t, 2) 593 defer world.Cleanup() 594 595 ri := ri2.(*kbtest.ChatRemoteMock) 596 u := world.GetUsers()[0] 597 u2 := world.GetUsers()[0] 598 uid := u.User.GetUID().ToBytes() 599 tc := world.Tcs[u.Username] 600 syncer := NewSyncer(tc.Context()) 601 syncer.isConnected = true 602 ibox := storage.NewInbox(tc.Context()) 603 604 _, iconv := newConv(ctx, t, tc, uid, ri, sender, u.Username) 605 tconv := newBlankConvWithMembersType(ctx, t, tc, uid, ri, sender, u.Username+","+u2.Username, 606 chat1.ConversationMembersType_TEAM) 607 608 _, _, err := tc.ChatG.InboxSource.Read(ctx, uid, types.ConversationLocalizerBlocking, 609 types.InboxSourceDataSourceAll, nil, nil) 610 require.NoError(t, err) 611 _, iconvs, err := ibox.ReadAll(ctx, uid, true) 612 require.NoError(t, err) 613 require.Len(t, iconvs, 2) 614 require.NoError(t, ibox.TeamTypeChanged(ctx, uid, 1, tconv.GetConvID(), chat1.TeamType_COMPLEX, nil)) 615 tconv.Metadata.TeamType = chat1.TeamType_COMPLEX 616 617 t.Logf("dont sync shallow team change") 618 syncConvs := []chat1.Conversation{iconv, tconv} 619 ri.SyncInboxFunc = func(m *kbtest.ChatRemoteMock, ctx context.Context, vers chat1.InboxVers) (chat1.SyncInboxRes, error) { 620 return chat1.NewSyncInboxResWithIncremental(chat1.SyncIncrementalRes{ 621 Vers: 100, 622 Convs: syncConvs, 623 }), nil 624 } 625 doSync(t, syncer, ri, uid) 626 select { 627 case res := <-list.inboxSynced: 628 typ, err := res.SyncType() 629 require.NoError(t, err) 630 require.Equal(t, chat1.SyncInboxResType_INCREMENTAL, typ) 631 require.Equal(t, 2, len(res.Incremental().Items)) 632 items := res.Incremental().Items 633 if items[0].Conv.ConvID == iconv.GetConvID().ConvIDStr() { 634 require.True(t, items[0].ShouldUnbox) 635 require.False(t, items[1].ShouldUnbox) 636 require.Equal(t, tconv.GetConvID().ConvIDStr(), items[1].Conv.ConvID) 637 } else if items[0].Conv.ConvID == tconv.GetConvID().ConvIDStr() { 638 require.False(t, items[0].ShouldUnbox) 639 require.True(t, items[1].ShouldUnbox) 640 require.Equal(t, iconv.GetConvID().ConvIDStr(), items[1].Conv.ConvID) 641 } else { 642 require.Fail(t, "unknown conv") 643 } 644 case <-time.After(20 * time.Second): 645 require.Fail(t, "no sync") 646 } 647 648 t.Logf("sync it if metadata changed") 649 for index, msg := range tconv.MaxMsgSummaries { 650 if msg.GetMessageType() == chat1.MessageType_METADATA { 651 tconv.MaxMsgSummaries[index] = chat1.MessageSummary{ 652 MsgID: 10, 653 MessageType: chat1.MessageType_METADATA, 654 } 655 } 656 } 657 syncConvs = []chat1.Conversation{iconv, tconv} 658 doSync(t, syncer, ri, uid) 659 select { 660 case res := <-list.inboxSynced: 661 typ, err := res.SyncType() 662 require.NoError(t, err) 663 require.Equal(t, chat1.SyncInboxResType_INCREMENTAL, typ) 664 require.Equal(t, 2, len(res.Incremental().Items)) 665 case <-time.After(20 * time.Second): 666 require.Fail(t, "no sync") 667 } 668 } 669 670 func TestSyncerBackgroundLoader(t *testing.T) { 671 ctx, world, ri2, _, sender, list := setupTest(t, 2) 672 defer world.Cleanup() 673 674 ri := ri2.(*kbtest.ChatRemoteMock) 675 u := world.GetUsers()[0] 676 uid := u.User.GetUID().ToBytes() 677 tc := world.Tcs[u.Username] 678 syncer := NewSyncer(tc.Context()) 679 syncer.isConnected = true 680 hcs := tc.Context().ConvSource.(*HybridConversationSource) 681 682 conv := newBlankConv(ctx, t, tc, uid, ri, sender, u.Username) 683 select { 684 case <-list.bgConvLoads: 685 case <-time.After(20 * time.Second): 686 require.Fail(t, "no conv load on sync") 687 } 688 ri.SyncInboxFunc = func(m *kbtest.ChatRemoteMock, ctx context.Context, vers chat1.InboxVers) (chat1.SyncInboxRes, error) { 689 return chat1.NewSyncInboxResWithIncremental(chat1.SyncIncrementalRes{ 690 Vers: 100, 691 Convs: []chat1.Conversation{conv}, 692 }), nil 693 } 694 doSync(t, syncer, ri, uid) 695 select { 696 case <-list.bgConvLoads: 697 case <-time.After(20 * time.Second): 698 require.Fail(t, "no conv load on sync") 699 } 700 time.Sleep(400 * time.Millisecond) 701 select { 702 case <-list.bgConvLoads: 703 require.Fail(t, "no conv load here") 704 default: 705 } 706 707 _, txtMsg, err := sender.Send(ctx, conv.GetConvID(), chat1.MessagePlaintext{ 708 ClientHeader: chat1.MessageClientHeader{ 709 Conv: conv.Metadata.IdTriple, 710 Sender: u.User.GetUID().ToBytes(), 711 TlfName: u.Username, 712 MessageType: chat1.MessageType_TEXT, 713 }, 714 MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{ 715 Body: "MIKE!!!!", 716 }), 717 }, 0, nil, nil, nil) 718 require.NoError(t, err) 719 _, delMsg, err := sender.Send(ctx, conv.GetConvID(), chat1.MessagePlaintext{ 720 ClientHeader: chat1.MessageClientHeader{ 721 Conv: conv.Metadata.IdTriple, 722 Sender: u.User.GetUID().ToBytes(), 723 TlfName: u.Username, 724 MessageType: chat1.MessageType_DELETE, 725 Supersedes: txtMsg.GetMessageID(), 726 }, 727 MessageBody: chat1.NewMessageBodyWithDelete(chat1.MessageDelete{ 728 MessageIDs: []chat1.MessageID{txtMsg.GetMessageID()}, 729 }), 730 }, 0, nil, nil, nil) 731 require.NoError(t, err) 732 require.NotNil(t, delMsg) 733 require.NoError(t, hcs.storage.ClearAll(context.TODO(), conv.GetConvID(), uid)) 734 ri.SyncInboxFunc = func(m *kbtest.ChatRemoteMock, ctx context.Context, vers chat1.InboxVers) (chat1.SyncInboxRes, error) { 735 conv.MaxMsgs = append(conv.MaxMsgs, *delMsg) 736 conv.MaxMsgSummaries = append(conv.MaxMsgSummaries, delMsg.Summary()) 737 return chat1.NewSyncInboxResWithIncremental(chat1.SyncIncrementalRes{ 738 Vers: 200, 739 Convs: []chat1.Conversation{conv}, 740 }), nil 741 } 742 doSync(t, syncer, ri, uid) 743 select { 744 case <-list.bgConvLoads: 745 case <-time.After(2 * time.Second): 746 require.Fail(t, "no conv load on sync") 747 } 748 time.Sleep(400 * time.Millisecond) 749 select { 750 case <-list.bgConvLoads: 751 require.Fail(t, "no conv load here") 752 default: 753 } 754 } 755 756 func TestSyncerBackgroundLoaderRemoved(t *testing.T) { 757 ctx, world, ri2, _, sender, list := setupTest(t, 2) 758 defer world.Cleanup() 759 760 ri := ri2.(*kbtest.ChatRemoteMock) 761 u := world.GetUsers()[0] 762 uid := u.User.GetUID().ToBytes() 763 tc := world.Tcs[u.Username] 764 syncer := NewSyncer(tc.Context()) 765 syncer.isConnected = true 766 767 conv := newBlankConv(ctx, t, tc, uid, ri, sender, u.Username) 768 select { 769 case <-list.bgConvLoads: 770 case <-time.After(20 * time.Second): 771 require.Fail(t, "no conv load on sync") 772 } 773 ri.SyncInboxFunc = func(m *kbtest.ChatRemoteMock, ctx context.Context, vers chat1.InboxVers) (chat1.SyncInboxRes, error) { 774 sconv := conv.DeepCopy() 775 sconv.ReaderInfo.Status = chat1.ConversationMemberStatus_REMOVED 776 return chat1.NewSyncInboxResWithIncremental(chat1.SyncIncrementalRes{ 777 Vers: 100, 778 Convs: []chat1.Conversation{sconv}, 779 }), nil 780 } 781 doSync(t, syncer, ri, uid) 782 time.Sleep(400 * time.Millisecond) 783 select { 784 case <-list.bgConvLoads: 785 require.Fail(t, "no sync should happen") 786 default: 787 } 788 ri.SyncInboxFunc = func(m *kbtest.ChatRemoteMock, ctx context.Context, vers chat1.InboxVers) (chat1.SyncInboxRes, error) { 789 sconv := conv.DeepCopy() 790 sconv.Metadata.Existence = chat1.ConversationExistence_ARCHIVED 791 return chat1.NewSyncInboxResWithIncremental(chat1.SyncIncrementalRes{ 792 Vers: 100, 793 Convs: []chat1.Conversation{sconv}, 794 }), nil 795 } 796 doSync(t, syncer, ri, uid) 797 time.Sleep(400 * time.Millisecond) 798 select { 799 case <-list.bgConvLoads: 800 require.Fail(t, "no sync should happen") 801 default: 802 } 803 } 804 805 func TestSyncerSortAndLimit(t *testing.T) { 806 useRemoteMock = false 807 defer func() { useRemoteMock = true }() 808 ctc := makeChatTestContext(t, "TestSyncerLimit", 2) 809 defer ctc.cleanup() 810 811 timeout := 3 * time.Second 812 users := ctc.users() 813 ctx := ctc.as(t, users[0]).startCtx 814 tc := ctc.world.Tcs[users[0].Username] 815 uid := gregor1.UID(users[0].GetUID().ToBytes()) 816 impConvLocal := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 817 chat1.ConversationMembersType_IMPTEAMNATIVE) 818 smallConvLocal := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 819 chat1.ConversationMembersType_TEAM) 820 bigConvLocal := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 821 chat1.ConversationMembersType_TEAM, users[1]) 822 topicName := "MIKE" 823 _, err := ctc.as(t, users[0]).chatLocalHandler().NewConversationLocal(ctx, 824 chat1.NewConversationLocalArg{ 825 TlfName: bigConvLocal.TlfName, 826 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 827 TopicType: chat1.TopicType_CHAT, 828 MembersType: chat1.ConversationMembersType_TEAM, 829 TopicName: &topicName, 830 }) 831 require.NoError(t, err) 832 impConv, err := utils.GetUnverifiedConv(ctx, tc.Context(), uid, impConvLocal.Id, 833 types.InboxSourceDataSourceAll) 834 require.NoError(t, err) 835 smallConv, err := utils.GetUnverifiedConv(ctx, tc.Context(), uid, smallConvLocal.Id, 836 types.InboxSourceDataSourceAll) 837 require.NoError(t, err) 838 bigConv, err := utils.GetUnverifiedConv(ctx, tc.Context(), uid, bigConvLocal.Id, 839 types.InboxSourceDataSourceAll) 840 require.NoError(t, err) 841 t.Logf("impconv: %s", impConv.GetConvID()) 842 t.Logf("smallconv: %s", smallConv.GetConvID()) 843 t.Logf("bigconv: %s", bigConv.GetConvID()) 844 845 bgLoads := make(chan chat1.ConversationID, 10) 846 tc.Context().ConvLoader.(*BackgroundConvLoader).loadWait = 0 847 tc.Context().ConvLoader.(*BackgroundConvLoader).loads = bgLoads 848 tc.Context().ConvLoader.Start(ctx, uid) 849 tc.Context().Syncer.(*Syncer).isConnected = true 850 tc.Context().Syncer.(*Syncer).maxLimitedConvLoads = 2 851 syncRes := chat1.SyncChatRes{ 852 InboxRes: chat1.NewSyncInboxResWithIncremental(chat1.SyncIncrementalRes{ 853 Vers: 10, 854 Convs: []chat1.Conversation{bigConv.Conv, smallConv.Conv, impConv.Conv}, 855 }), 856 } 857 require.NoError(t, tc.Context().Syncer.Sync(ctx, ctc.as(t, users[0]).ri, uid, &syncRes)) 858 select { 859 case convID := <-bgLoads: 860 require.Equal(t, smallConv.GetConvID(), convID) 861 case <-time.After(timeout): 862 require.Fail(t, "no bkg load") 863 } 864 select { 865 case convID := <-bgLoads: 866 require.Equal(t, impConv.GetConvID(), convID) 867 case <-time.After(timeout): 868 require.Fail(t, "no bkg load") 869 } 870 time.Sleep(200 * time.Millisecond) 871 select { 872 case <-bgLoads: 873 default: 874 require.Fail(t, "no bkg load expected") 875 } 876 } 877 878 func TestSyncerStorageClear(t *testing.T) { 879 ctx, world, ri2, _, sender, list := setupTest(t, 2) 880 defer world.Cleanup() 881 882 ri := ri2.(*kbtest.ChatRemoteMock) 883 u := world.GetUsers()[0] 884 uid := u.User.GetUID().ToBytes() 885 tc := world.Tcs[u.Username] 886 syncer := NewSyncer(tc.Context()) 887 syncer.isConnected = true 888 889 conv := newBlankConv(ctx, t, tc, uid, ri, sender, u.Username) 890 select { 891 case <-list.bgConvLoads: 892 case <-time.After(20 * time.Second): 893 require.Fail(t, "no conv load on sync") 894 } 895 tv, err := tc.Context().ConvSource.PullLocalOnly(ctx, conv.GetConvID(), uid, chat1.GetThreadReason_GENERAL, nil, nil, 0) 896 require.NoError(t, err) 897 require.Equal(t, 1, len(tv.Messages)) 898 899 ri.SyncInboxFunc = func(m *kbtest.ChatRemoteMock, ctx context.Context, vers chat1.InboxVers) (chat1.SyncInboxRes, error) { 900 sconv := conv.DeepCopy() 901 sconv.ReaderInfo.Status = chat1.ConversationMemberStatus_REMOVED 902 return chat1.NewSyncInboxResWithIncremental(chat1.SyncIncrementalRes{ 903 Vers: 100, 904 Convs: []chat1.Conversation{sconv}, 905 }), nil 906 } 907 doSync(t, syncer, ri, uid) 908 time.Sleep(400 * time.Millisecond) 909 910 _, err = tc.Context().ConvSource.PullLocalOnly(ctx, conv.GetConvID(), uid, chat1.GetThreadReason_GENERAL, nil, nil, 0) 911 require.Error(t, err) 912 require.IsType(t, storage.MissError{}, err) 913 }