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  }