github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/chat/sender_test.go (about)

     1  package chat
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strings"
     7  	"sync"
     8  	"testing"
     9  	"time"
    10  
    11  	"encoding/hex"
    12  
    13  	"github.com/keybase/client/go/chat/commands"
    14  	"github.com/keybase/client/go/chat/globals"
    15  	"github.com/keybase/client/go/chat/search"
    16  	"github.com/keybase/client/go/chat/storage"
    17  	"github.com/keybase/client/go/chat/types"
    18  	"github.com/keybase/client/go/chat/utils"
    19  	"github.com/keybase/client/go/contacts"
    20  	"github.com/keybase/client/go/ephemeral"
    21  	"github.com/keybase/client/go/kbtest"
    22  	"github.com/keybase/client/go/libkb"
    23  	"github.com/keybase/client/go/protocol/chat1"
    24  	"github.com/keybase/client/go/protocol/gregor1"
    25  	"github.com/keybase/client/go/protocol/keybase1"
    26  	"github.com/keybase/client/go/teambot"
    27  	"github.com/keybase/client/go/teams"
    28  	"github.com/stretchr/testify/require"
    29  	context "golang.org/x/net/context"
    30  )
    31  
    32  type chatListener struct {
    33  	sync.Mutex
    34  	libkb.NoopNotifyListener
    35  
    36  	// ChatActivity channels
    37  	obidsLocal     []chat1.OutboxID
    38  	obidsRemote    []chat1.OutboxID
    39  	incomingLocal  chan int
    40  	incomingRemote chan int
    41  	failing        chan []chat1.OutboxRecord
    42  	identifyUpdate chan keybase1.CanonicalTLFNameAndIDWithBreaks
    43  	inboxStale     chan struct{}
    44  	convUpdate     chan chat1.ConversationID
    45  	threadsStale   chan []chat1.ConversationStaleUpdate
    46  
    47  	bgConvLoads    chan chat1.ConversationID
    48  	typingUpdate   chan []chat1.ConvTypingUpdate
    49  	inboxSynced    chan chat1.ChatSyncResult
    50  	ephemeralPurge chan chat1.EphemeralPurgeNotifInfo
    51  }
    52  
    53  var _ libkb.NotifyListener = (*chatListener)(nil)
    54  
    55  func (n *chatListener) ChatIdentifyUpdate(update keybase1.CanonicalTLFNameAndIDWithBreaks) {
    56  	n.identifyUpdate <- update
    57  }
    58  func (n *chatListener) ChatInboxStale(uid keybase1.UID) {
    59  	select {
    60  	case n.inboxStale <- struct{}{}:
    61  	case <-time.After(5 * time.Second):
    62  		panic("timeout on the inbox stale channel")
    63  	}
    64  }
    65  func (n *chatListener) ChatConvUpdate(uid keybase1.UID, convID chat1.ConversationID) {
    66  	select {
    67  	case n.convUpdate <- convID:
    68  	case <-time.After(5 * time.Second):
    69  		panic("timeout on the threads stale channel")
    70  	}
    71  }
    72  func (n *chatListener) ChatThreadsStale(uid keybase1.UID, updates []chat1.ConversationStaleUpdate) {
    73  	select {
    74  	case n.threadsStale <- updates:
    75  	case <-time.After(5 * time.Second):
    76  		panic("timeout on the threads stale channel")
    77  	}
    78  }
    79  func (n *chatListener) ChatInboxSynced(uid keybase1.UID, topicType chat1.TopicType,
    80  	syncRes chat1.ChatSyncResult) {
    81  	switch topicType {
    82  	case chat1.TopicType_CHAT, chat1.TopicType_NONE:
    83  		select {
    84  		case n.inboxSynced <- syncRes:
    85  		case <-time.After(5 * time.Second):
    86  			panic("timeout on the threads stale channel")
    87  		}
    88  	}
    89  }
    90  func (n *chatListener) ChatTypingUpdate(updates []chat1.ConvTypingUpdate) {
    91  	select {
    92  	case n.typingUpdate <- updates:
    93  	case <-time.After(5 * time.Second):
    94  		panic("timeout on typing update")
    95  	}
    96  }
    97  
    98  func (n *chatListener) NewChatActivity(uid keybase1.UID, activity chat1.ChatActivity,
    99  	source chat1.ChatActivitySource) {
   100  	n.Lock()
   101  	defer n.Unlock()
   102  	typ, err := activity.ActivityType()
   103  	if err == nil {
   104  		switch typ {
   105  		case chat1.ChatActivityType_INCOMING_MESSAGE:
   106  			if activity.IncomingMessage().Message.IsValid() {
   107  				strOutboxID := activity.IncomingMessage().Message.Valid().OutboxID
   108  				if strOutboxID != nil {
   109  					outboxID, _ := hex.DecodeString(*strOutboxID)
   110  					switch source {
   111  					case chat1.ChatActivitySource_REMOTE:
   112  						n.obidsRemote = append(n.obidsRemote, chat1.OutboxID(outboxID))
   113  						select {
   114  						case n.incomingRemote <- len(n.obidsRemote):
   115  						case <-time.After(5 * time.Second):
   116  							panic("timeout on the incomingRemote channel")
   117  						}
   118  					case chat1.ChatActivitySource_LOCAL:
   119  						n.obidsLocal = append(n.obidsLocal, chat1.OutboxID(outboxID))
   120  						select {
   121  						case n.incomingLocal <- len(n.obidsLocal):
   122  						case <-time.After(5 * time.Second):
   123  							panic("timeout on the incomingLocal channel")
   124  						}
   125  					}
   126  				}
   127  			}
   128  		case chat1.ChatActivityType_FAILED_MESSAGE:
   129  			var rmsg []chat1.OutboxRecord
   130  			rmsg = append(rmsg, activity.FailedMessage().OutboxRecords...)
   131  			select {
   132  			case n.failing <- rmsg:
   133  			case <-time.After(5 * time.Second):
   134  				panic("timeout on the failing channel")
   135  			}
   136  		case chat1.ChatActivityType_EPHEMERAL_PURGE:
   137  			n.ephemeralPurge <- activity.EphemeralPurge()
   138  		}
   139  	}
   140  }
   141  
   142  func (n *chatListener) consumeEphemeralPurge(t *testing.T) chat1.EphemeralPurgeNotifInfo {
   143  	select {
   144  	case x := <-n.ephemeralPurge:
   145  		return x
   146  	case <-time.After(20 * time.Second):
   147  		require.Fail(t, "failed to get ephemeralPurge notification")
   148  		return chat1.EphemeralPurgeNotifInfo{}
   149  	}
   150  }
   151  
   152  func (n *chatListener) consumeConvUpdate(t *testing.T) chat1.ConversationID {
   153  	select {
   154  	case x := <-n.convUpdate:
   155  		return x
   156  	case <-time.After(20 * time.Second):
   157  		require.Fail(t, "failed to get conv update notification")
   158  		return nil
   159  	}
   160  }
   161  
   162  func newConvTriple(ctx context.Context, t *testing.T, tc *kbtest.ChatTestContext, username string) chat1.ConversationIDTriple {
   163  	return newConvTripleWithMembersType(ctx, t, tc, username, chat1.ConversationMembersType_IMPTEAMNATIVE)
   164  }
   165  
   166  func newConvTripleWithMembersType(ctx context.Context, t *testing.T, tc *kbtest.ChatTestContext,
   167  	username string, membersType chat1.ConversationMembersType) chat1.ConversationIDTriple {
   168  	nameInfo, err := CreateNameInfoSource(ctx, tc.Context(), membersType).LookupID(ctx, username, false)
   169  	require.NoError(t, err)
   170  	topicID, err := utils.NewChatTopicID()
   171  	require.NoError(t, err)
   172  	trip := chat1.ConversationIDTriple{
   173  		Tlfid:     nameInfo.ID,
   174  		TopicType: chat1.TopicType_CHAT,
   175  		TopicID:   chat1.TopicID(topicID),
   176  	}
   177  	return trip
   178  }
   179  
   180  func userTc(t *testing.T, world *kbtest.ChatMockWorld, user *kbtest.FakeUser) *kbtest.ChatTestContext {
   181  	for _, u := range world.Users {
   182  		if u.Username == user.Username {
   183  			return world.Tcs[u.Username]
   184  		}
   185  	}
   186  	require.Fail(t, "not user found")
   187  	return &kbtest.ChatTestContext{}
   188  }
   189  
   190  func NewChatMockWorld(t *testing.T, name string, numUsers int) (world *kbtest.ChatMockWorld) {
   191  	res := kbtest.NewChatMockWorld(t, name, numUsers)
   192  	for _, w := range res.Tcs {
   193  		teams.ServiceInit(w.G)
   194  		mctx := libkb.NewMetaContextTODO(w.G)
   195  		ephemeral.ServiceInit(mctx)
   196  		err := mctx.G().GetEKLib().KeygenIfNeeded(mctx)
   197  		require.NoError(t, err)
   198  		teambot.ServiceInit(mctx)
   199  		contacts.ServiceInit(w.G)
   200  	}
   201  	return res
   202  }
   203  
   204  func setupTest(t *testing.T, numUsers int) (context.Context, *kbtest.ChatMockWorld, chat1.RemoteInterface, types.Sender, types.Sender, *chatListener) {
   205  	var ri chat1.RemoteInterface
   206  	world := NewChatMockWorld(t, "chatsender", numUsers)
   207  	mock := kbtest.NewChatRemoteMock(world)
   208  	ri = mock
   209  	tlf := kbtest.NewTlfMock(world)
   210  	u := world.GetUsers()[0]
   211  	tc := world.Tcs[u.Username]
   212  	tc.G.SetService()
   213  	g := globals.NewContext(tc.G, tc.ChatG)
   214  	uid := u.User.GetUID().ToBytes()
   215  
   216  	var ctx context.Context
   217  	var serverConn types.ServerConnection
   218  	if useRemoteMock {
   219  		ctx = newTestContextWithTlfMock(tc, tlf)
   220  		serverConn = kbtest.NewChatRemoteMockServerConnection(mock)
   221  	} else {
   222  		ctx = newTestContext(tc)
   223  		nist, err := tc.G.ActiveDevice.NIST(context.TODO())
   224  		if err != nil {
   225  			t.Fatalf(err.Error())
   226  		}
   227  		sessionToken := nist.Token().String()
   228  		gh := newGregorTestConnection(tc.Context(), uid, sessionToken)
   229  		require.NoError(t, gh.Connect(ctx))
   230  		ri = gh.GetClient()
   231  		serverConn = gh
   232  	}
   233  	boxer := NewBoxer(g)
   234  	boxer.SetClock(world.Fc)
   235  	getRI := func() chat1.RemoteInterface { return ri }
   236  	baseSender := NewBlockingSender(g, boxer, getRI)
   237  	// Force a small page size here to test prev pointer calculations for
   238  	// exploding and non exploding messages
   239  	baseSender.setPrevPagination(&chat1.Pagination{Num: 2})
   240  	baseSender.SetClock(world.Fc)
   241  	sender := NewNonblockingSender(g, baseSender)
   242  	listener := chatListener{
   243  		incomingLocal:  make(chan int, 100),
   244  		incomingRemote: make(chan int, 100),
   245  		failing:        make(chan []chat1.OutboxRecord, 100),
   246  		identifyUpdate: make(chan keybase1.CanonicalTLFNameAndIDWithBreaks, 10),
   247  		inboxStale:     make(chan struct{}, 1),
   248  		convUpdate:     make(chan chat1.ConversationID, 10),
   249  		threadsStale:   make(chan []chat1.ConversationStaleUpdate, 10),
   250  		bgConvLoads:    make(chan chat1.ConversationID, 10),
   251  		typingUpdate:   make(chan []chat1.ConvTypingUpdate, 10),
   252  		inboxSynced:    make(chan chat1.ChatSyncResult, 10),
   253  		ephemeralPurge: make(chan chat1.EphemeralPurgeNotifInfo, 10),
   254  	}
   255  	chatStorage := storage.New(g, nil)
   256  	chatStorage.SetClock(world.Fc)
   257  	g.CtxFactory = NewCtxFactory(g)
   258  	g.ConvSource = NewHybridConversationSource(g, boxer, chatStorage, getRI)
   259  	chatStorage.SetAssetDeleter(g.ConvSource)
   260  	g.InboxSource = NewHybridInboxSource(g, getRI)
   261  	g.InboxSource.Start(context.TODO(), uid)
   262  	g.InboxSource.Connected(context.TODO())
   263  	g.ServerCacheVersions = storage.NewServerVersions(g)
   264  	g.NotifyRouter.AddListener(&listener)
   265  
   266  	deliverer := NewDeliverer(g, baseSender, serverConn)
   267  	deliverer.SetClock(world.Fc)
   268  	deliverer.setTestingNameInfoSource(tlf)
   269  
   270  	g.MessageDeliverer = deliverer
   271  	g.MessageDeliverer.Start(context.TODO(), uid)
   272  	g.MessageDeliverer.Connected(context.TODO())
   273  
   274  	g.FetchRetrier = NewFetchRetrier(g)
   275  	g.FetchRetrier.(*FetchRetrier).SetClock(world.Fc)
   276  	g.FetchRetrier.Connected(context.TODO())
   277  	g.FetchRetrier.Start(context.TODO(), uid)
   278  
   279  	convLoader := NewBackgroundConvLoader(g)
   280  	convLoader.loads = listener.bgConvLoads
   281  	convLoader.setTestingNameInfoSource(tlf)
   282  	g.ConvLoader = convLoader
   283  	g.ConvLoader.Start(context.TODO(), uid)
   284  
   285  	g.EphemeralTracker = NewEphemeralTracker(g)
   286  	g.EphemeralTracker.Start(context.TODO(), uid)
   287  	purger := NewBackgroundEphemeralPurger(g)
   288  	purger.SetClock(world.Fc)
   289  	g.EphemeralPurger = purger
   290  	g.EphemeralPurger.Start(context.TODO(), uid)
   291  
   292  	chatSyncer := NewSyncer(g)
   293  	chatSyncer.isConnected = true
   294  	g.Syncer = chatSyncer
   295  
   296  	g.ConnectivityMonitor = &libkb.NullConnectivityMonitor{}
   297  	pushHandler := NewPushHandler(g)
   298  	pushHandler.Start(context.TODO(), nil)
   299  	g.PushHandler = pushHandler
   300  	g.ChatHelper = NewHelper(g, getRI)
   301  	g.TeamChannelSource = NewTeamChannelSource(g)
   302  	g.ActivityNotifier = NewNotifyRouterActivityRouter(g)
   303  
   304  	searcher := search.NewRegexpSearcher(g)
   305  	// Force small pages during tests to ensure we fetch context from new pages
   306  	searcher.SetPageSize(2)
   307  	g.RegexpSearcher = searcher
   308  	indexer := search.NewIndexer(g)
   309  	ictx := globals.CtxAddIdentifyMode(context.Background(), keybase1.TLFIdentifyBehavior_CHAT_SKIP, nil)
   310  	indexer.SetPageSize(2)
   311  	indexer.SetStartSyncDelay(0)
   312  	indexer.Start(ictx, uid)
   313  	g.Indexer = indexer
   314  	g.AttachmentURLSrv = types.DummyAttachmentHTTPSrv{}
   315  	g.Unfurler = types.DummyUnfurler{}
   316  	g.AttachmentUploader = types.DummyAttachmentUploader{}
   317  	g.StellarLoader = types.DummyStellarLoader{}
   318  	g.StellarSender = types.DummyStellarSender{}
   319  	g.TeamMentionLoader = types.DummyTeamMentionLoader{}
   320  	g.JourneyCardManager = NewJourneyCardManager(g, getRI)
   321  	g.BotCommandManager = types.DummyBotCommandManager{}
   322  	g.CommandsSource = commands.NewSource(g)
   323  	g.CoinFlipManager = NewFlipManager(g, getRI)
   324  	g.CoinFlipManager.Start(context.TODO(), uid)
   325  	g.UIInboxLoader = types.DummyUIInboxLoader{}
   326  	g.UIThreadLoader = NewUIThreadLoader(g, getRI)
   327  	g.ParticipantsSource = types.DummyParticipantSource{}
   328  	g.EmojiSource = NewDevConvEmojiSource(g, getRI)
   329  
   330  	return ctx, world, ri, sender, baseSender, &listener
   331  }
   332  
   333  func TestNonblockChannel(t *testing.T) {
   334  	ctx, world, ri, sender, blockingSender, listener := setupTest(t, 1)
   335  	defer world.Cleanup()
   336  
   337  	u := world.GetUsers()[0]
   338  	uid := u.User.GetUID().ToBytes()
   339  	tc := userTc(t, world, u)
   340  	conv := newBlankConv(ctx, t, tc, uid, ri, blockingSender, u.Username)
   341  
   342  	// Send nonblock
   343  	obid, _, err := sender.Send(context.TODO(), conv.GetConvID(), chat1.MessagePlaintext{
   344  		ClientHeader: chat1.MessageClientHeader{
   345  			Conv:        conv.Metadata.IdTriple,
   346  			Sender:      u.User.GetUID().ToBytes(),
   347  			TlfName:     u.Username,
   348  			TlfPublic:   false,
   349  			MessageType: chat1.MessageType_TEXT,
   350  		},
   351  		MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{
   352  			Body: "hi",
   353  		}),
   354  	}, 0, nil, nil, nil)
   355  	require.NoError(t, err)
   356  
   357  	select {
   358  	case <-listener.incomingRemote:
   359  	case <-time.After(20 * time.Second):
   360  		require.Fail(t, "event not received")
   361  	}
   362  
   363  	require.Equal(t, 1, len(listener.obidsRemote), "wrong length")
   364  	require.Equal(t, obid, listener.obidsRemote[0], "wrong obid")
   365  }
   366  
   367  type sentRecord struct {
   368  	msgID    *chat1.MessageID
   369  	outboxID *chat1.OutboxID
   370  }
   371  
   372  func checkThread(t *testing.T, thread chat1.ThreadView, ref []sentRecord) {
   373  	require.Equal(t, len(ref), len(thread.Messages), "size not equal")
   374  	for index, msg := range thread.Messages {
   375  		rindex := len(ref) - index - 1
   376  		t.Logf("checking index: %d rindex: %d", index, rindex)
   377  		if ref[rindex].msgID != nil {
   378  			t.Logf("msgID: ref: %d actual: %d", *ref[rindex].msgID, thread.Messages[index].GetMessageID())
   379  			require.NotZero(t, msg.GetMessageID(), "missing message ID")
   380  			require.Equal(t, *ref[rindex].msgID, msg.GetMessageID(), "invalid message ID")
   381  		} else if ref[rindex].outboxID != nil {
   382  			t.Logf("obID: ref: %s actual: %s",
   383  				hex.EncodeToString(*ref[rindex].outboxID),
   384  				hex.EncodeToString(msg.Outbox().OutboxID))
   385  			require.Equal(t, *ref[rindex].outboxID, msg.Outbox().OutboxID, "invalid outbox ID")
   386  		} else {
   387  			require.Fail(t, "unknown ref type")
   388  		}
   389  		t.Logf("index %d succeeded", index)
   390  	}
   391  }
   392  
   393  func TestNonblockTimer(t *testing.T) {
   394  	ctx, world, ri, _, baseSender, listener := setupTest(t, 1)
   395  	defer world.Cleanup()
   396  
   397  	u := world.GetUsers()[0]
   398  	tc := world.Tcs[u.Username]
   399  	clock := world.Fc
   400  	trip := newConvTriple(ctx, t, tc, u.Username)
   401  	firstMessagePlaintext := chat1.MessagePlaintext{
   402  		ClientHeader: chat1.MessageClientHeader{
   403  			Conv:        trip,
   404  			TlfName:     u.Username,
   405  			TlfPublic:   false,
   406  			MessageType: chat1.MessageType_TLFNAME,
   407  		},
   408  		MessageBody: chat1.MessageBody{},
   409  	}
   410  	prepareRes, err := baseSender.Prepare(ctx, firstMessagePlaintext,
   411  		chat1.ConversationMembersType_KBFS, nil, nil)
   412  	require.NoError(t, err)
   413  	firstMessageBoxed := prepareRes.Boxed
   414  	res, err := ri.NewConversationRemote2(ctx, chat1.NewConversationRemote2Arg{
   415  		IdTriple:   trip,
   416  		TLFMessage: firstMessageBoxed,
   417  	})
   418  	require.NoError(t, err)
   419  
   420  	// Send a bunch of blocking messages
   421  	var sentRef []sentRecord
   422  	for i := 0; i < 5; i++ {
   423  		_, msgBoxed, err := baseSender.Send(ctx, res.ConvID, chat1.MessagePlaintext{
   424  			ClientHeader: chat1.MessageClientHeader{
   425  				Conv:        trip,
   426  				Sender:      u.User.GetUID().ToBytes(),
   427  				TlfName:     u.Username,
   428  				TlfPublic:   false,
   429  				MessageType: chat1.MessageType_TEXT,
   430  			},
   431  			MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{
   432  				Body: "hi",
   433  			}),
   434  		}, 0, nil, nil, nil)
   435  		require.NoError(t, err)
   436  		msgID := msgBoxed.GetMessageID()
   437  		t.Logf("generated msgID: %d", msgID)
   438  		sentRef = append(sentRef, sentRecord{msgID: &msgID})
   439  	}
   440  
   441  	outbox := storage.NewOutbox(tc.Context(), u.User.GetUID().ToBytes())
   442  	outbox.SetClock(clock)
   443  	var obids []chat1.OutboxID
   444  	msgID := *sentRef[len(sentRef)-1].msgID
   445  	for i := 0; i < 5; i++ {
   446  		obr, err := outbox.PushMessage(ctx, res.ConvID, chat1.MessagePlaintext{
   447  			ClientHeader: chat1.MessageClientHeader{
   448  				Conv:        trip,
   449  				Sender:      u.User.GetUID().ToBytes(),
   450  				TlfName:     u.Username,
   451  				TlfPublic:   false,
   452  				MessageType: chat1.MessageType_TEXT,
   453  				OutboxInfo: &chat1.OutboxInfo{
   454  					Prev: msgID,
   455  				},
   456  			},
   457  			MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{
   458  				Body: "hi",
   459  			}),
   460  		}, nil, nil, nil, keybase1.TLFIdentifyBehavior_CHAT_CLI)
   461  		obid := obr.OutboxID
   462  		t.Logf("generated obid: %s prev: %d", hex.EncodeToString(obid), msgID)
   463  		require.NoError(t, err)
   464  		obids = append(obids, obid)
   465  	}
   466  
   467  	// Make we get nothing until timer is up
   468  	select {
   469  	case <-listener.incomingRemote:
   470  		require.Fail(t, "action event received too soon")
   471  	default:
   472  	}
   473  	select {
   474  	case <-listener.failing:
   475  		require.Fail(t, "failed message")
   476  	default:
   477  	}
   478  
   479  	// Send a bunch of blocking messages
   480  	for i := 0; i < 5; i++ {
   481  		_, msgBoxed, err := baseSender.Send(ctx, res.ConvID, chat1.MessagePlaintext{
   482  			ClientHeader: chat1.MessageClientHeader{
   483  				Conv:        trip,
   484  				Sender:      u.User.GetUID().ToBytes(),
   485  				TlfName:     u.Username,
   486  				TlfPublic:   false,
   487  				MessageType: chat1.MessageType_TEXT,
   488  			},
   489  			MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{
   490  				Body: "hi",
   491  			}),
   492  		}, 0, nil, nil, nil)
   493  		require.NoError(t, err)
   494  		msgID := msgBoxed.GetMessageID()
   495  		t.Logf("generated msgID: %d", msgID)
   496  		sentRef = append(sentRef, sentRecord{msgID: &msgID})
   497  	}
   498  
   499  	// Push the outbox records to the front of the thread.
   500  	for _, o := range obids {
   501  		obid := o
   502  		sentRef = append(sentRef, sentRecord{outboxID: &obid})
   503  	}
   504  
   505  	// Check get thread, make sure it makes sense
   506  	typs := []chat1.MessageType{chat1.MessageType_TEXT}
   507  	tres, err := tc.ChatG.ConvSource.Pull(ctx, res.ConvID, u.User.GetUID().ToBytes(),
   508  		chat1.GetThreadReason_GENERAL, nil,
   509  		&chat1.GetThreadQuery{MessageTypes: typs}, nil)
   510  	tres.Messages = utils.FilterByType(tres.Messages, &chat1.GetThreadQuery{MessageTypes: typs}, true)
   511  	t.Logf("source size: %d", len(tres.Messages))
   512  	require.NoError(t, err)
   513  	checkThread(t, tres, sentRef)
   514  	clock.Advance(5 * time.Minute)
   515  
   516  	// Should get a blast of all 5
   517  
   518  	var olen int
   519  	for i := 0; i < 5; i++ {
   520  		select {
   521  		case olen = <-listener.incomingRemote:
   522  		case <-time.After(20 * time.Second):
   523  			require.Fail(t, "event not received")
   524  		}
   525  
   526  		t.Logf("OUTBOXID: %s", obids[i])
   527  		require.Equal(t, i+1, olen, "wrong length")
   528  		require.Equal(t, listener.obidsRemote[i], obids[i], "wrong obid")
   529  	}
   530  
   531  	// Make sure it is really empty
   532  	clock.Advance(5 * time.Minute)
   533  	select {
   534  	case <-listener.incomingRemote:
   535  		require.Fail(t, "action event received too soon")
   536  	default:
   537  	}
   538  }
   539  
   540  type FailingSender struct {
   541  }
   542  
   543  var _ types.Sender = (*FailingSender)(nil)
   544  
   545  func (f FailingSender) Send(ctx context.Context, convID chat1.ConversationID,
   546  	msg chat1.MessagePlaintext, clientPrev chat1.MessageID, outboxID *chat1.OutboxID,
   547  	sendOpts *chat1.SenderSendOptions, prepareOpts *chat1.SenderPrepareOptions) (chat1.OutboxID, *chat1.MessageBoxed, error) {
   548  	return chat1.OutboxID{}, nil, fmt.Errorf("I always fail!!!!")
   549  }
   550  
   551  func (f FailingSender) Prepare(ctx context.Context, msg chat1.MessagePlaintext,
   552  	membersType chat1.ConversationMembersType, conv *chat1.ConversationLocal,
   553  	opts *chat1.SenderPrepareOptions) (types.SenderPrepareResult, error) {
   554  	return types.SenderPrepareResult{}, nil
   555  }
   556  
   557  func recordCompare(t *testing.T, obids []chat1.OutboxID, obrs []chat1.OutboxRecord) {
   558  	require.Equal(t, len(obids), len(obrs), "wrong length")
   559  	for i := 0; i < len(obids); i++ {
   560  		require.Equal(t, obids[i], obrs[i].OutboxID)
   561  	}
   562  }
   563  
   564  func TestFailingSender(t *testing.T) {
   565  
   566  	ctx, world, ri, sender, _, listener := setupTest(t, 1)
   567  	defer world.Cleanup()
   568  
   569  	u := world.GetUsers()[0]
   570  	tc := userTc(t, world, u)
   571  	trip := newConvTriple(ctx, t, tc, u.Username)
   572  	res, err := ri.NewConversationRemote2(context.TODO(), chat1.NewConversationRemote2Arg{
   573  		IdTriple: trip,
   574  		TLFMessage: chat1.MessageBoxed{
   575  			ClientHeader: chat1.MessageClientHeader{
   576  				Conv:      trip,
   577  				TlfName:   u.Username,
   578  				TlfPublic: false,
   579  			},
   580  			KeyGeneration: 1,
   581  		},
   582  	})
   583  	require.NoError(t, err)
   584  
   585  	tc.ChatG.MessageDeliverer.(*Deliverer).SetSender(FailingSender{})
   586  
   587  	// Send nonblock
   588  	var obids []chat1.OutboxID
   589  	for i := 0; i < 5; i++ {
   590  		obid, _, err := sender.Send(context.TODO(), res.ConvID, chat1.MessagePlaintext{
   591  			ClientHeader: chat1.MessageClientHeader{
   592  				Conv:      trip,
   593  				Sender:    u.User.GetUID().ToBytes(),
   594  				TlfName:   u.Username,
   595  				TlfPublic: false,
   596  			},
   597  		}, 0, nil, nil, nil)
   598  		require.NoError(t, err)
   599  		obids = append(obids, obid)
   600  	}
   601  	for i := 0; i < deliverMaxAttempts; i++ {
   602  		tc.ChatG.MessageDeliverer.ForceDeliverLoop(context.TODO())
   603  	}
   604  
   605  	var recvd []chat1.OutboxRecord
   606  	for {
   607  		select {
   608  		case fid := <-listener.failing:
   609  			recvd = append(recvd, fid...)
   610  		case <-time.After(20 * time.Second):
   611  			require.Fail(t, "event not received", "len(recvd): %d", len(recvd))
   612  		}
   613  		if len(recvd) >= len(obids) {
   614  			break
   615  		}
   616  	}
   617  
   618  	require.Equal(t, len(obids), len(recvd), "invalid length")
   619  	recordCompare(t, obids, recvd)
   620  	state, err := recvd[0].State.State()
   621  	require.NoError(t, err)
   622  	require.Equal(t, chat1.OutboxStateType_ERROR, state, "wrong state type")
   623  }
   624  
   625  func TestOutboxItemExpiration(t *testing.T) {
   626  	ctx, world, ri, sender, baseSender, listener := setupTest(t, 1)
   627  	defer world.Cleanup()
   628  
   629  	u := world.GetUsers()[0]
   630  	uid := u.User.GetUID().ToBytes()
   631  	cl := world.Fc
   632  	tc := userTc(t, world, u)
   633  	conv := newBlankConv(ctx, t, tc, uid, ri, baseSender, u.Username)
   634  
   635  	tc.ChatG.MessageDeliverer.Disconnected(ctx)
   636  	tc.ChatG.MessageDeliverer.(*Deliverer).SetSender(baseSender)
   637  	obid, _, err := sender.Send(ctx, conv.GetConvID(), chat1.MessagePlaintext{
   638  		ClientHeader: chat1.MessageClientHeader{
   639  			Conv:        conv.Metadata.IdTriple,
   640  			Sender:      u.User.GetUID().ToBytes(),
   641  			TlfName:     u.Username,
   642  			TlfPublic:   false,
   643  			MessageType: chat1.MessageType_TEXT,
   644  		},
   645  		MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{
   646  			Body: "hi",
   647  		}),
   648  	}, 0, nil, nil, nil)
   649  	require.NoError(t, err)
   650  	cl.Advance(2 * time.Hour)
   651  	tc.ChatG.MessageDeliverer.Connected(ctx)
   652  	select {
   653  	case f := <-listener.failing:
   654  		require.Len(t, f, 1)
   655  		require.Equal(t, obid, f[0].OutboxID)
   656  		st, err := f[0].State.State()
   657  		require.NoError(t, err)
   658  		require.Equal(t, chat1.OutboxStateType_ERROR, st)
   659  		require.Equal(t, chat1.OutboxErrorType_EXPIRED, f[0].State.Error().Typ)
   660  	case <-time.After(20 * time.Second):
   661  		require.Fail(t, "no failing message")
   662  	}
   663  	select {
   664  	case <-listener.incomingRemote:
   665  		require.Fail(t, "no incoming message")
   666  	default:
   667  	}
   668  
   669  	outbox := storage.NewOutbox(tc.Context(), uid)
   670  	outbox.SetClock(cl)
   671  	_, err = outbox.RetryMessage(ctx, obid, nil)
   672  	require.NoError(t, err)
   673  	tc.ChatG.MessageDeliverer.ForceDeliverLoop(ctx)
   674  	select {
   675  	case i := <-listener.incomingRemote:
   676  		require.Equal(t, 1, i)
   677  	case <-time.After(20 * time.Second):
   678  		require.Fail(t, "no success")
   679  	}
   680  	select {
   681  	case <-listener.failing:
   682  		require.Fail(t, "no failing message")
   683  	default:
   684  	}
   685  }
   686  
   687  func TestDisconnectedFailure(t *testing.T) {
   688  	ctx, world, ri, sender, baseSender, listener := setupTest(t, 1)
   689  	defer world.Cleanup()
   690  
   691  	u := world.GetUsers()[0]
   692  	uid := u.User.GetUID().ToBytes()
   693  	cl := world.Fc
   694  	tc := userTc(t, world, u)
   695  	conv := newBlankConv(ctx, t, tc, uid, ri, baseSender, u.Username)
   696  
   697  	tc.ChatG.MessageDeliverer.Disconnected(ctx)
   698  	tc.ChatG.MessageDeliverer.(*Deliverer).SetSender(baseSender)
   699  
   700  	mkMsg := func() chat1.MessagePlaintext {
   701  		return chat1.MessagePlaintext{
   702  			ClientHeader: chat1.MessageClientHeader{
   703  				Conv:        conv.Metadata.IdTriple,
   704  				Sender:      uid,
   705  				TlfName:     u.Username,
   706  				TlfPublic:   false,
   707  				MessageType: chat1.MessageType_TEXT,
   708  			},
   709  			MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{
   710  				Body: "hi",
   711  			}),
   712  		}
   713  	}
   714  
   715  	// If not offline for long enough, we should be able to get a send by just reconnecting
   716  	obid, _, err := sender.Send(ctx, conv.GetConvID(), mkMsg(), 0, nil, nil, nil)
   717  	require.NoError(t, err)
   718  	cl.Advance(time.Millisecond)
   719  	select {
   720  	case <-listener.failing:
   721  		require.Fail(t, "no failed message")
   722  	default:
   723  	}
   724  	tc.ChatG.MessageDeliverer.Connected(ctx)
   725  	select {
   726  	case inc := <-listener.incomingRemote:
   727  		require.Equal(t, 1, inc)
   728  		require.Equal(t, obid, listener.obidsRemote[0])
   729  	case <-time.After(20 * time.Second):
   730  		require.Fail(t, "no incoming message")
   731  	}
   732  	listener.obidsRemote = nil
   733  
   734  	tc.ChatG.MessageDeliverer.Disconnected(ctx)
   735  	tc.ChatG.MessageDeliverer.(*Deliverer).SetSender(FailingSender{})
   736  	cl.Advance(time.Hour)
   737  
   738  	// Send nonblock
   739  	obids := []chat1.OutboxID{}
   740  	for i := 0; i < 3; i++ {
   741  		obid, _, err = sender.Send(ctx, conv.GetConvID(), mkMsg(), 0, nil, nil, nil)
   742  		require.NoError(t, err)
   743  		obids = append(obids, obid)
   744  		cl.Advance(time.Millisecond)
   745  	}
   746  
   747  	var allrecvd []chat1.OutboxRecord
   748  	var recvd []chat1.OutboxRecord
   749  	appendUnique := func(a []chat1.OutboxRecord, r []chat1.OutboxRecord) (res []chat1.OutboxRecord) {
   750  		m := make(map[string]bool)
   751  		for _, i := range a {
   752  			m[hex.EncodeToString(i.OutboxID)] = true
   753  			res = append(res, i)
   754  		}
   755  		for _, i := range r {
   756  			if !m[hex.EncodeToString(i.OutboxID)] {
   757  				res = append(res, i)
   758  			}
   759  		}
   760  		return res
   761  	}
   762  	for {
   763  		select {
   764  		case recvd = <-listener.failing:
   765  			allrecvd = appendUnique(allrecvd, recvd)
   766  			if len(allrecvd) >= len(obids) {
   767  				break
   768  			}
   769  			continue
   770  		case <-time.After(20 * time.Second):
   771  			require.Fail(t, "timeout in failing loop")
   772  		}
   773  		break
   774  	}
   775  
   776  	require.Equal(t, len(obids), len(allrecvd), "invalid length")
   777  	recordCompare(t, obids, allrecvd)
   778  
   779  	t.Logf("reconnecting and checking for successes")
   780  	<-tc.ChatG.MessageDeliverer.Stop(ctx)
   781  	<-tc.ChatG.MessageDeliverer.Stop(ctx)
   782  	tc.ChatG.MessageDeliverer.(*Deliverer).SetSender(baseSender)
   783  	outbox := storage.NewOutbox(tc.Context(), u.User.GetUID().ToBytes())
   784  	outbox.SetClock(cl)
   785  	for _, obid := range obids {
   786  		_, err = outbox.RetryMessage(ctx, obid, nil)
   787  		require.NoError(t, err)
   788  	}
   789  	tc.ChatG.MessageDeliverer.Start(ctx, u.User.GetUID().ToBytes())
   790  	tc.ChatG.MessageDeliverer.Connected(ctx)
   791  
   792  	for {
   793  		select {
   794  		case inc := <-listener.incomingRemote:
   795  			if inc >= len(obids) {
   796  				break
   797  			}
   798  			continue
   799  		case <-time.After(20 * time.Second):
   800  			require.Fail(t, "timeout in incoming loop")
   801  		}
   802  		break
   803  	}
   804  	require.Equal(t, len(obids), len(listener.obidsRemote), "wrong amount of successes")
   805  	sort.Slice(obids, func(i, j int) bool {
   806  		return j < i
   807  	})
   808  	require.Equal(t, listener.obidsRemote, obids)
   809  }
   810  
   811  // The sender is responsible for making sure that a deletion of a single
   812  // message is expanded to include all of its edits.
   813  func TestDeletionHeaders(t *testing.T) {
   814  	ctx, world, ri, _, blockingSender, _ := setupTest(t, 1)
   815  	defer world.Cleanup()
   816  
   817  	u := world.GetUsers()[0]
   818  	uid := u.User.GetUID().ToBytes()
   819  	tc := userTc(t, world, u)
   820  	conv := newBlankConv(ctx, t, tc, uid, ri, blockingSender, u.Username)
   821  	localConv := localizeConv(ctx, t, tc, uid, conv)
   822  
   823  	// Send a message and two edits.
   824  	_, firstMessageBoxed, err := blockingSender.Send(ctx, conv.GetConvID(), chat1.MessagePlaintext{
   825  		ClientHeader: chat1.MessageClientHeader{
   826  			Conv:        conv.Metadata.IdTriple,
   827  			Sender:      u.User.GetUID().ToBytes(),
   828  			TlfName:     u.Username,
   829  			MessageType: chat1.MessageType_TEXT,
   830  		},
   831  		MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{Body: "foo"}),
   832  	}, 0, nil, nil, nil)
   833  	require.NoError(t, err)
   834  	firstMessageID := firstMessageBoxed.GetMessageID()
   835  	_, editBoxed, err := blockingSender.Send(ctx, conv.GetConvID(), chat1.MessagePlaintext{
   836  		ClientHeader: chat1.MessageClientHeader{
   837  			Conv:        conv.Metadata.IdTriple,
   838  			Sender:      u.User.GetUID().ToBytes(),
   839  			TlfName:     u.Username,
   840  			MessageType: chat1.MessageType_EDIT,
   841  			Supersedes:  firstMessageID,
   842  		},
   843  		MessageBody: chat1.NewMessageBodyWithEdit(chat1.MessageEdit{MessageID: firstMessageID, Body: "bar"}),
   844  	}, 0, nil, nil, nil)
   845  	require.NoError(t, err)
   846  	editID := editBoxed.GetMessageID()
   847  	_, editBoxed2, err := blockingSender.Send(ctx, conv.GetConvID(), chat1.MessagePlaintext{
   848  		ClientHeader: chat1.MessageClientHeader{
   849  			Conv:        conv.Metadata.IdTriple,
   850  			Sender:      u.User.GetUID().ToBytes(),
   851  			TlfName:     u.Username,
   852  			MessageType: chat1.MessageType_EDIT,
   853  			Supersedes:  firstMessageID,
   854  		},
   855  		MessageBody: chat1.NewMessageBodyWithEdit(chat1.MessageEdit{MessageID: firstMessageID, Body: "baz"}),
   856  	}, 0, nil, nil, nil)
   857  	require.NoError(t, err)
   858  	editID2 := editBoxed2.GetMessageID()
   859  
   860  	// Now prepare a deletion.
   861  	deletion := chat1.MessagePlaintext{
   862  		ClientHeader: chat1.MessageClientHeader{
   863  			Conv:        conv.Metadata.IdTriple,
   864  			Sender:      u.User.GetUID().ToBytes(),
   865  			TlfName:     u.Username,
   866  			MessageType: chat1.MessageType_DELETE,
   867  			Supersedes:  firstMessageID,
   868  		},
   869  		MessageBody: chat1.NewMessageBodyWithDelete(chat1.MessageDelete{MessageIDs: []chat1.MessageID{firstMessageID}}),
   870  	}
   871  	prepareRes, err := blockingSender.Prepare(ctx, deletion,
   872  		chat1.ConversationMembersType_KBFS, &localConv, nil)
   873  	require.NoError(t, err)
   874  	preparedDeletion := prepareRes.Boxed
   875  
   876  	// Assert that the deletion gets the edit too.
   877  	deletedIDs := map[chat1.MessageID]bool{}
   878  	for _, id := range preparedDeletion.ClientHeader.Deletes {
   879  		deletedIDs[id] = true
   880  	}
   881  	if len(deletedIDs) != 3 {
   882  		t.Fatalf("expected 3 deleted IDs, found %d", len(deletedIDs))
   883  	}
   884  	if !deletedIDs[firstMessageID] {
   885  		t.Fatalf("expected message #%d to be deleted", firstMessageID)
   886  	}
   887  	if !deletedIDs[editID] {
   888  		t.Fatalf("expected message #%d to be deleted", editID)
   889  	}
   890  	if !deletedIDs[editID2] {
   891  		t.Fatalf("expected message #%d to be deleted", editID2)
   892  	}
   893  }
   894  
   895  func TestAtMentionsText(t *testing.T) {
   896  	ctx, world, ri, _, blockingSender, _ := setupTest(t, 3)
   897  	defer world.Cleanup()
   898  
   899  	u := world.GetUsers()[0]
   900  	u1 := world.GetUsers()[1]
   901  	u2 := world.GetUsers()[2]
   902  	uid := u.User.GetUID().ToBytes()
   903  	uid1 := u1.User.GetUID().ToBytes()
   904  	uid2 := u2.User.GetUID().ToBytes()
   905  	tc := userTc(t, world, u)
   906  	tlfName := u.Username + "," + u1.Username + "," + u2.Username
   907  	conv := newBlankConv(ctx, t, tc, uid, ri, blockingSender, tlfName)
   908  	localConv := localizeConv(ctx, t, tc, uid, conv)
   909  
   910  	text := fmt.Sprintf("@%s hello! From @%s. @ksjdskj", u1.Username, u2.Username)
   911  	t.Logf("text: %s", text)
   912  	prepareRes, err := blockingSender.Prepare(ctx, chat1.MessagePlaintext{
   913  		ClientHeader: chat1.MessageClientHeader{
   914  			Conv:        conv.Metadata.IdTriple,
   915  			Sender:      uid,
   916  			TlfName:     tlfName,
   917  			MessageType: chat1.MessageType_TEXT,
   918  		},
   919  		MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{
   920  			Body: text,
   921  		}),
   922  	}, chat1.ConversationMembersType_KBFS, &localConv, nil)
   923  	require.NoError(t, err)
   924  	atMentions := prepareRes.AtMentions
   925  	chanMention := prepareRes.ChannelMention
   926  	require.Equal(t, []gregor1.UID{uid1, uid2}, atMentions)
   927  	require.Equal(t, chat1.ChannelMention_NONE, chanMention)
   928  
   929  	text = "Hello @channel!"
   930  	prepareRes, err = blockingSender.Prepare(ctx, chat1.MessagePlaintext{
   931  		ClientHeader: chat1.MessageClientHeader{
   932  			Conv:        conv.Metadata.IdTriple,
   933  			Sender:      uid,
   934  			TlfName:     tlfName,
   935  			MessageType: chat1.MessageType_TEXT,
   936  		},
   937  		MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{
   938  			Body: text,
   939  		}),
   940  	}, chat1.ConversationMembersType_KBFS, &localConv, nil)
   941  	require.NoError(t, err)
   942  	atMentions = prepareRes.AtMentions
   943  	chanMention = prepareRes.ChannelMention
   944  	require.Zero(t, len(atMentions))
   945  	require.Equal(t, chat1.ChannelMention_ALL, chanMention)
   946  }
   947  
   948  func TestAtMentionsEdit(t *testing.T) {
   949  	ctx, world, ri, _, blockingSender, _ := setupTest(t, 3)
   950  	defer world.Cleanup()
   951  
   952  	u := world.GetUsers()[0]
   953  	u1 := world.GetUsers()[1]
   954  	u2 := world.GetUsers()[2]
   955  	uid := u.User.GetUID().ToBytes()
   956  	uid1 := u1.User.GetUID().ToBytes()
   957  	uid2 := u2.User.GetUID().ToBytes()
   958  	tc := userTc(t, world, u)
   959  	tlfName := u.Username + "," + u1.Username + "," + u2.Username
   960  	conv := newBlankConv(ctx, t, tc, uid, ri, blockingSender, tlfName)
   961  	localConv := localizeConv(ctx, t, tc, uid, conv)
   962  
   963  	text := fmt.Sprintf("%s hello! From %s. @ksjdskj", u1.Username, u2.Username)
   964  	t.Logf("text: %s", text)
   965  	_, firstMessageBoxed, err := blockingSender.Send(ctx, conv.GetConvID(), chat1.MessagePlaintext{
   966  		ClientHeader: chat1.MessageClientHeader{
   967  			Conv:        conv.Metadata.IdTriple,
   968  			Sender:      uid,
   969  			TlfName:     tlfName,
   970  			MessageType: chat1.MessageType_TEXT,
   971  		},
   972  		MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{
   973  			Body: text,
   974  		}),
   975  	}, 0, nil, nil, nil)
   976  	require.NoError(t, err)
   977  
   978  	// edit that message and add atMentions
   979  	text = fmt.Sprintf("@%s hello! From @%s. @ksjdskj", u1.Username, u2.Username)
   980  	firstMessageID := firstMessageBoxed.GetMessageID()
   981  	prepareRes, err := blockingSender.Prepare(ctx, chat1.MessagePlaintext{
   982  		ClientHeader: chat1.MessageClientHeader{
   983  			Conv:        conv.Metadata.IdTriple,
   984  			Sender:      u.User.GetUID().ToBytes(),
   985  			TlfName:     tlfName,
   986  			MessageType: chat1.MessageType_EDIT,
   987  			Supersedes:  firstMessageID,
   988  		},
   989  		MessageBody: chat1.NewMessageBodyWithEdit(chat1.MessageEdit{
   990  			MessageID: firstMessageID,
   991  			Body:      text,
   992  		}),
   993  	}, chat1.ConversationMembersType_KBFS, &localConv, nil)
   994  	require.NoError(t, err)
   995  	atMentions := prepareRes.AtMentions
   996  	chanMention := prepareRes.ChannelMention
   997  	require.Equal(t, []gregor1.UID{uid1, uid2}, atMentions)
   998  	require.Equal(t, chat1.ChannelMention_NONE, chanMention)
   999  
  1000  	// edit the message and add channel mention
  1001  	text = "Hello @channel!"
  1002  	prepareRes, err = blockingSender.Prepare(ctx, chat1.MessagePlaintext{
  1003  		ClientHeader: chat1.MessageClientHeader{
  1004  			Conv:        conv.Metadata.IdTriple,
  1005  			Sender:      uid,
  1006  			TlfName:     tlfName,
  1007  			MessageType: chat1.MessageType_EDIT,
  1008  			Supersedes:  firstMessageID,
  1009  		},
  1010  		MessageBody: chat1.NewMessageBodyWithEdit(chat1.MessageEdit{
  1011  			MessageID: firstMessageID,
  1012  			Body:      text,
  1013  		}),
  1014  	}, chat1.ConversationMembersType_KBFS, &localConv, nil)
  1015  	require.NoError(t, err)
  1016  	atMentions = prepareRes.AtMentions
  1017  	chanMention = prepareRes.ChannelMention
  1018  	require.Zero(t, len(atMentions))
  1019  	require.Equal(t, chat1.ChannelMention_ALL, chanMention)
  1020  }
  1021  
  1022  func TestKBFSFileEditSize(t *testing.T) {
  1023  	runWithMemberTypes(t, func(mt chat1.ConversationMembersType) {
  1024  		switch mt {
  1025  		case chat1.ConversationMembersType_IMPTEAMNATIVE, chat1.ConversationMembersType_TEAM:
  1026  		default:
  1027  			return
  1028  		}
  1029  		ctx, world, ri, _, blockingSender, _ := setupTest(t, 1)
  1030  		defer world.Cleanup()
  1031  
  1032  		u := world.GetUsers()[0]
  1033  		uid := u.User.GetUID().ToBytes()
  1034  		tlfName := u.Username
  1035  		tc := userTc(t, world, u)
  1036  		conv, created, err := NewConversation(ctx, tc.Context(), uid, tlfName, nil, chat1.TopicType_KBFSFILEEDIT,
  1037  			chat1.ConversationMembersType_IMPTEAMNATIVE, keybase1.TLFVisibility_PRIVATE, nil,
  1038  			func() chat1.RemoteInterface { return ri }, NewConvFindExistingNormal)
  1039  		require.NoError(t, err)
  1040  		require.True(t, created)
  1041  
  1042  		body := strings.Repeat("M", 100000)
  1043  		_, _, err = blockingSender.Send(ctx, conv.GetConvID(), chat1.MessagePlaintext{
  1044  			ClientHeader: chat1.MessageClientHeader{
  1045  				Sender:      uid,
  1046  				TlfName:     tlfName,
  1047  				MessageType: chat1.MessageType_TEXT,
  1048  			},
  1049  			MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{Body: body}),
  1050  		}, 0, nil, nil, nil)
  1051  		require.NoError(t, err)
  1052  	})
  1053  }
  1054  
  1055  func TestKBFSCryptKeysBit(t *testing.T) {
  1056  	runWithMemberTypes(t, func(mt chat1.ConversationMembersType) {
  1057  		ctx, world, ri, _, blockingSender, _ := setupTest(t, 1)
  1058  		defer world.Cleanup()
  1059  
  1060  		u := world.GetUsers()[0]
  1061  		uid := u.User.GetUID().ToBytes()
  1062  		tc := userTc(t, world, u)
  1063  		var name string
  1064  		switch mt {
  1065  		case chat1.ConversationMembersType_TEAM:
  1066  			name = createTeam(tc.TestContext)
  1067  		default:
  1068  			name = u.Username
  1069  		}
  1070  
  1071  		conv := newBlankConvWithMembersType(ctx, t, tc, uid, ri, blockingSender, name, mt)
  1072  		_, _, err := blockingSender.Send(ctx, conv.GetConvID(), chat1.MessagePlaintext{
  1073  			ClientHeader: chat1.MessageClientHeader{
  1074  				Conv:        conv.Metadata.IdTriple,
  1075  				Sender:      uid,
  1076  				TlfName:     name,
  1077  				MessageType: chat1.MessageType_TEXT,
  1078  			},
  1079  			MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{Body: "foo"}),
  1080  		}, 0, nil, nil, nil)
  1081  		require.NoError(t, err)
  1082  		tv, err := tc.ChatG.ConvSource.Pull(ctx, conv.GetConvID(), uid,
  1083  			chat1.GetThreadReason_GENERAL, nil,
  1084  			&chat1.GetThreadQuery{
  1085  				MessageTypes: []chat1.MessageType{chat1.MessageType_TEXT},
  1086  			}, nil)
  1087  		require.NoError(t, err)
  1088  		require.Len(t, tv.Messages, 1)
  1089  		msg := tv.Messages[0]
  1090  
  1091  		require.NotNil(t, msg.Valid().ClientHeader.KbfsCryptKeysUsed)
  1092  		switch mt {
  1093  		case chat1.ConversationMembersType_KBFS:
  1094  			require.True(t, *msg.Valid().ClientHeader.KbfsCryptKeysUsed)
  1095  		default:
  1096  			require.False(t, *msg.Valid().ClientHeader.KbfsCryptKeysUsed)
  1097  		}
  1098  	})
  1099  }
  1100  
  1101  func TestPrevPointerAddition(t *testing.T) {
  1102  	mt := chat1.ConversationMembersType_TEAM
  1103  	runWithEphemeral(t, mt, func(ephemeralLifetime *gregor1.DurationSec) {
  1104  		if ephemeralLifetime == nil {
  1105  			t.Logf("ephemeral stage: %v", ephemeralLifetime)
  1106  		} else {
  1107  			t.Logf("ephemeral stage: %v", *ephemeralLifetime)
  1108  		}
  1109  		ctx, world, ri2, _, blockingSender, _ := setupTest(t, 1)
  1110  		defer world.Cleanup()
  1111  
  1112  		ri := ri2.(*kbtest.ChatRemoteMock)
  1113  		var ephemeralMetadata *chat1.MsgEphemeralMetadata
  1114  		if ephemeralLifetime != nil {
  1115  			ephemeralMetadata = &chat1.MsgEphemeralMetadata{
  1116  				Lifetime: *ephemeralLifetime,
  1117  			}
  1118  		}
  1119  		u := world.GetUsers()[0]
  1120  		uid := u.User.GetUID().ToBytes()
  1121  		tc := userTc(t, world, u)
  1122  		conv := newBlankConv(ctx, t, tc, uid, ri, blockingSender, u.Username)
  1123  		localConv := localizeConv(ctx, t, tc, uid, conv)
  1124  
  1125  		// Send a bunch of messages on this convo
  1126  		for i := 0; i < 10; i++ {
  1127  			_, _, err := blockingSender.Send(ctx, conv.GetConvID(), chat1.MessagePlaintext{
  1128  				ClientHeader: chat1.MessageClientHeader{
  1129  					Conv:              conv.Metadata.IdTriple,
  1130  					Sender:            uid,
  1131  					TlfName:           u.Username,
  1132  					MessageType:       chat1.MessageType_TEXT,
  1133  					EphemeralMetadata: ephemeralMetadata,
  1134  				},
  1135  				MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{Body: "foo"}),
  1136  			}, 0, nil, nil, nil)
  1137  			require.NoError(t, err)
  1138  		}
  1139  
  1140  		// Hide all ephemeral messages by advancing the clock enough to hide
  1141  		// the "ash" lines.  We also mock out the server call so we can
  1142  		// simulate a chat with only long exploded ephemeral messages.
  1143  		if ephemeralLifetime != nil {
  1144  			t.Logf("expiry all ephemeral messages")
  1145  			world.Fc.Advance(ephemeralLifetime.ToDuration() + chat1.ShowExplosionLifetime)
  1146  			// Mock out pulling messages to return no messages
  1147  			blockingSender.(*BlockingSender).G().ConvSource.(*HybridConversationSource).blackoutPullForTesting = true
  1148  			// Prepare a regular message and make sure it gets prev pointers
  1149  			prepareRes, err := blockingSender.Prepare(ctx, chat1.MessagePlaintext{
  1150  				ClientHeader: chat1.MessageClientHeader{
  1151  					Conv:              conv.Metadata.IdTriple,
  1152  					Sender:            uid,
  1153  					TlfName:           u.Username,
  1154  					MessageType:       chat1.MessageType_TEXT,
  1155  					EphemeralMetadata: ephemeralMetadata,
  1156  				},
  1157  				MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{Body: "foo"}),
  1158  			}, mt, &localConv, nil)
  1159  			require.NoError(t, err)
  1160  			boxed := prepareRes.Boxed
  1161  			pendingAssetDeletes := prepareRes.PendingAssetDeletes
  1162  			require.Empty(t, pendingAssetDeletes)
  1163  			// With all of the messages filtered because they exploded and the
  1164  			// server not returning results, we give up and don't attach any
  1165  			// prevs.
  1166  			require.Empty(t, boxed.ClientHeader.Prev, "empty prev pointers")
  1167  			blockingSender.(*BlockingSender).G().ConvSource.(*HybridConversationSource).blackoutPullForTesting = false
  1168  		}
  1169  
  1170  		// Nuke the body cache
  1171  		require.NoError(t, storage.New(tc.Context(), tc.ChatG.ConvSource).ClearAll(context.TODO(), conv.GetConvID(), uid))
  1172  
  1173  		// Fetch a subset into the cache
  1174  		_, err := tc.ChatG.ConvSource.Pull(ctx, conv.GetConvID(), uid, chat1.GetThreadReason_GENERAL, nil,
  1175  			nil, &chat1.Pagination{
  1176  				Num: 2,
  1177  			})
  1178  		require.NoError(t, err)
  1179  
  1180  		// Prepare a regular message and make sure it gets prev pointers
  1181  		prepareRes, err := blockingSender.Prepare(ctx, chat1.MessagePlaintext{
  1182  			ClientHeader: chat1.MessageClientHeader{
  1183  				Conv:        conv.Metadata.IdTriple,
  1184  				Sender:      uid,
  1185  				TlfName:     u.Username,
  1186  				MessageType: chat1.MessageType_TEXT,
  1187  			},
  1188  			MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{Body: "foo"}),
  1189  		}, mt, &localConv, nil)
  1190  		require.NoError(t, err)
  1191  		boxed := prepareRes.Boxed
  1192  		pendingAssetDeletes := prepareRes.PendingAssetDeletes
  1193  		require.Empty(t, pendingAssetDeletes)
  1194  		if ephemeralLifetime == nil {
  1195  			require.NotEmpty(t, boxed.ClientHeader.Prev, "empty prev pointers")
  1196  		} else {
  1197  			// Since we only sent ephemeral messages previously, we won't have
  1198  			// any prev pointers to regular messages here.
  1199  			require.Empty(t, boxed.ClientHeader.Prev, "empty prev pointers")
  1200  		}
  1201  	})
  1202  }
  1203  
  1204  // Test a DELETE attempts to delete all associated assets.
  1205  // func TestDeletionHeaders(t *testing.T) { // <- TODO delete this line
  1206  func TestDeletionAssets(t *testing.T) {
  1207  	ctx, world, ri, _, blockingSender, _ := setupTest(t, 1)
  1208  	defer world.Cleanup()
  1209  
  1210  	u := world.GetUsers()[0]
  1211  	uid := u.User.GetUID().ToBytes()
  1212  	tc := userTc(t, world, u)
  1213  	conv := newBlankConv(ctx, t, tc, uid, ri, blockingSender, u.Username)
  1214  	localConv := localizeConv(ctx, t, tc, uid, conv)
  1215  	trip := conv.Metadata.IdTriple
  1216  
  1217  	var doomedAssets []chat1.Asset
  1218  	mkAsset := func() chat1.Asset {
  1219  		asset := chat1.Asset{
  1220  			Path: fmt.Sprintf("test-asset-%v", len(doomedAssets)),
  1221  			Size: 8,
  1222  		}
  1223  		doomedAssets = append(doomedAssets, asset)
  1224  		return asset
  1225  	}
  1226  
  1227  	// Send an attachment message and 3 MessageAttachUploaded's.
  1228  	tmp1 := mkAsset()
  1229  	_, firstMessageBoxed, err := blockingSender.Send(ctx, conv.GetConvID(), chat1.MessagePlaintext{
  1230  		ClientHeader: chat1.MessageClientHeader{
  1231  			Conv:        trip,
  1232  			Sender:      uid,
  1233  			TlfName:     u.Username,
  1234  			MessageType: chat1.MessageType_ATTACHMENT,
  1235  		},
  1236  		MessageBody: chat1.NewMessageBodyWithAttachment(chat1.MessageAttachment{
  1237  			Object: mkAsset(),
  1238  			// Use v1 and v2 assets for fuller coverage. These would never both exist in a real message.
  1239  			Preview:  &tmp1,
  1240  			Previews: []chat1.Asset{mkAsset(), mkAsset()},
  1241  		}),
  1242  	}, 0, nil, nil, nil)
  1243  	require.NoError(t, err)
  1244  	firstMessageID := firstMessageBoxed.GetMessageID()
  1245  
  1246  	editHeader := chat1.MessageClientHeader{
  1247  		Conv:        trip,
  1248  		Sender:      uid,
  1249  		TlfName:     u.Username,
  1250  		MessageType: chat1.MessageType_ATTACHMENTUPLOADED,
  1251  		Supersedes:  firstMessageID,
  1252  	}
  1253  	_, edit1Boxed, err := blockingSender.Send(ctx, conv.GetConvID(), chat1.MessagePlaintext{
  1254  		ClientHeader: editHeader,
  1255  		MessageBody: chat1.NewMessageBodyWithAttachmentuploaded(chat1.MessageAttachmentUploaded{
  1256  			MessageID: firstMessageID,
  1257  			Object:    mkAsset(),
  1258  			Previews:  []chat1.Asset{mkAsset(), mkAsset()},
  1259  		}),
  1260  	}, 0, nil, nil, nil)
  1261  	require.NoError(t, err)
  1262  	edit1ID := edit1Boxed.GetMessageID()
  1263  	_, edit2Boxed, err := blockingSender.Send(ctx, conv.GetConvID(), chat1.MessagePlaintext{
  1264  		ClientHeader: editHeader,
  1265  		MessageBody: chat1.NewMessageBodyWithAttachmentuploaded(chat1.MessageAttachmentUploaded{
  1266  			MessageID: firstMessageID,
  1267  			Object:    mkAsset(),
  1268  			Previews:  []chat1.Asset{mkAsset(), mkAsset()},
  1269  		}),
  1270  	}, 0, nil, nil, nil)
  1271  	require.NoError(t, err)
  1272  	edit2ID := edit2Boxed.GetMessageID()
  1273  	_, edit3Boxed, err := blockingSender.Send(ctx, conv.GetConvID(), chat1.MessagePlaintext{
  1274  		ClientHeader: editHeader,
  1275  		MessageBody: chat1.NewMessageBodyWithAttachmentuploaded(chat1.MessageAttachmentUploaded{
  1276  			MessageID: firstMessageID,
  1277  			Object:    chat1.Asset{},
  1278  			Previews:  nil,
  1279  		}),
  1280  	}, 0, nil, nil, nil)
  1281  	require.NoError(t, err)
  1282  	edit3ID := edit3Boxed.GetMessageID()
  1283  
  1284  	require.Equal(t, len(doomedAssets), 10, "wrong number of assets created")
  1285  
  1286  	// Now prepare a deletion.
  1287  	deletion := chat1.MessagePlaintext{
  1288  		ClientHeader: chat1.MessageClientHeader{
  1289  			Conv:        trip,
  1290  			Sender:      uid,
  1291  			TlfName:     u.Username,
  1292  			MessageType: chat1.MessageType_DELETE,
  1293  			Supersedes:  firstMessageID,
  1294  		},
  1295  		MessageBody: chat1.NewMessageBodyWithDelete(chat1.MessageDelete{MessageIDs: []chat1.MessageID{firstMessageID}}),
  1296  	}
  1297  	prepareRes, err := blockingSender.Prepare(ctx, deletion,
  1298  		chat1.ConversationMembersType_KBFS, &localConv, nil)
  1299  	require.NoError(t, err)
  1300  	preparedDeletion := prepareRes.Boxed
  1301  	pendingAssetDeletes := prepareRes.PendingAssetDeletes
  1302  
  1303  	assertAssetSetsEqual(t, pendingAssetDeletes, doomedAssets)
  1304  	require.Equal(t, len(doomedAssets), len(pendingAssetDeletes), "wrong number of assets pending deletion")
  1305  
  1306  	// Assert that the deletion gets the MessageAttachmentUploaded's too.
  1307  	deletedIDs := map[chat1.MessageID]bool{}
  1308  	for _, id := range preparedDeletion.ClientHeader.Deletes {
  1309  		deletedIDs[id] = true
  1310  	}
  1311  	if len(deletedIDs) != 4 {
  1312  		t.Fatalf("expected 4 deleted IDs, found %d", len(deletedIDs))
  1313  	}
  1314  	if !deletedIDs[firstMessageID] {
  1315  		t.Fatalf("expected message #%d to be deleted", firstMessageID)
  1316  	}
  1317  	if !deletedIDs[edit1ID] {
  1318  		t.Fatalf("expected message #%d to be deleted", edit1ID)
  1319  	}
  1320  	if !deletedIDs[edit2ID] {
  1321  		t.Fatalf("expected message #%d to be deleted", edit2ID)
  1322  	}
  1323  	if !deletedIDs[edit3ID] {
  1324  		t.Fatalf("expected message #%d to be deleted", edit3ID)
  1325  	}
  1326  }
  1327  
  1328  func assertAssetSetsEqual(t *testing.T, got []chat1.Asset, expected []chat1.Asset) {
  1329  	if !compareAssetLists(t, got, expected, false) {
  1330  		compareAssetLists(t, got, expected, true)
  1331  		t.Fatalf("asset lists not equal")
  1332  	}
  1333  }
  1334  
  1335  // compareAssetLists compares two unordered sets of assets based on Path only.
  1336  func compareAssetLists(t *testing.T, got []chat1.Asset, expected []chat1.Asset, verbose bool) bool {
  1337  	match := true
  1338  	gMap := make(map[string]chat1.Asset)
  1339  	eMap := make(map[string]chat1.Asset)
  1340  	for _, a := range got {
  1341  		gMap[a.Path] = a
  1342  	}
  1343  	for _, a := range expected {
  1344  		eMap[a.Path] = a
  1345  		if gMap[a.Path].Path != a.Path {
  1346  			match = false
  1347  			if verbose {
  1348  				t.Logf("expected: %v", a.Path)
  1349  			}
  1350  		}
  1351  	}
  1352  	for _, a := range got {
  1353  		if eMap[a.Path].Path != a.Path {
  1354  			match = false
  1355  			if verbose {
  1356  				t.Logf("got unexpected: %v", a.Path)
  1357  			}
  1358  		}
  1359  	}
  1360  	if match && len(got) != len(expected) {
  1361  		if verbose {
  1362  			t.Logf("list contains duplicates or compareAssetLists has a bug")
  1363  			for i, a := range got {
  1364  				t.Logf("[%v] %v", i, a.Path)
  1365  			}
  1366  		}
  1367  		return false
  1368  	}
  1369  
  1370  	return match
  1371  }
  1372  
  1373  func TestPairwiseMACChecker(t *testing.T) {
  1374  	runWithMemberTypes(t, func(mt chat1.ConversationMembersType) {
  1375  		// Don't run this test for kbfs
  1376  		switch mt {
  1377  		case chat1.ConversationMembersType_KBFS:
  1378  			return
  1379  		default:
  1380  		}
  1381  
  1382  		ctc := makeChatTestContext(t, "TestPairwiseMACChecker", 2)
  1383  		defer ctc.cleanup()
  1384  		users := ctc.users()
  1385  
  1386  		ephemeralMetadata := &chat1.MsgEphemeralMetadata{
  1387  			Lifetime: 100000,
  1388  		}
  1389  
  1390  		firstConv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt, users[1])
  1391  		ctx1 := ctc.as(t, users[0]).startCtx
  1392  		ctx2 := ctc.as(t, users[1]).startCtx
  1393  		ncres, err := ctc.as(t, users[0]).chatLocalHandler().NewConversationLocal(ctx1,
  1394  			chat1.NewConversationLocalArg{
  1395  				TlfName:       firstConv.TlfName,
  1396  				TopicType:     chat1.TopicType_CHAT,
  1397  				TlfVisibility: keybase1.TLFVisibility_PRIVATE,
  1398  				MembersType:   mt,
  1399  			})
  1400  		require.NoError(t, err)
  1401  		conv := ncres.Conv.Info
  1402  
  1403  		tc1 := ctc.world.Tcs[users[0].Username]
  1404  		tc2 := ctc.world.Tcs[users[1].Username]
  1405  		require.NoError(t, tc1.G.GetEKLib().KeygenIfNeeded(
  1406  			ctc.as(t, users[0]).h.G().MetaContext(ctx1)))
  1407  		require.NoError(t, tc2.G.GetEKLib().KeygenIfNeeded(
  1408  			ctc.as(t, users[1]).h.G().MetaContext(ctx2)))
  1409  		uid1 := users[0].User.GetUID()
  1410  		uid2 := users[1].User.GetUID()
  1411  		ri1 := ctc.as(t, users[0]).ri
  1412  		getRI1 := func() chat1.RemoteInterface { return ri1 }
  1413  		ri2 := ctc.as(t, users[1]).ri
  1414  		getRI2 := func() chat1.RemoteInterface { return ri2 }
  1415  		boxer1 := NewBoxer(tc1.Context())
  1416  		boxer2 := NewBoxer(tc2.Context())
  1417  		g1 := globals.NewContext(tc1.G, tc1.ChatG)
  1418  		g2 := globals.NewContext(tc2.G, tc2.ChatG)
  1419  		blockingSender1 := NewBlockingSender(g1, boxer1, getRI1)
  1420  		blockingSender2 := NewBlockingSender(g2, boxer2, getRI2)
  1421  		listener1 := newServerChatListener()
  1422  		ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener1)
  1423  
  1424  		text := "hi"
  1425  		msg := textMsgWithSender(t, text, uid1.ToBytes(), chat1.MessageBoxedVersion_V3)
  1426  		// Pairwise MACs rely on the sender's DeviceID in the header.
  1427  		deviceID1 := make([]byte, libkb.DeviceIDLen)
  1428  		err = tc1.G.ActiveDevice.DeviceID().ToBytes(deviceID1)
  1429  		require.NoError(t, err)
  1430  		msg.ClientHeader.TlfName = firstConv.TlfName
  1431  		msg.ClientHeader.SenderDevice = gregor1.DeviceID(deviceID1)
  1432  
  1433  		key := cryptKey(t)
  1434  		signKP := getSigningKeyPairForTest(t, tc1, users[0])
  1435  		encryptionKeypair, err := tc1.G.ActiveDevice.NaclEncryptionKey()
  1436  		require.NoError(t, err)
  1437  
  1438  		// Missing recipients uid2
  1439  		pairwiseMACRecipients := []keybase1.KID{encryptionKeypair.GetKID()}
  1440  
  1441  		boxed, err := boxer1.box(context.TODO(), msg, key, nil, signKP, chat1.MessageBoxedVersion_V3, pairwiseMACRecipients)
  1442  		require.NoError(t, err)
  1443  
  1444  		_, err = ri1.PostRemote(ctx1, chat1.PostRemoteArg{
  1445  			ConversationID: conv.Id, MessageBoxed: boxed,
  1446  		})
  1447  		require.Error(t, err)
  1448  		require.IsType(t, libkb.EphemeralPairwiseMACsMissingUIDsError{}, err)
  1449  		merr := err.(libkb.EphemeralPairwiseMACsMissingUIDsError)
  1450  		require.Equal(t, []keybase1.UID{uid2}, merr.UIDs)
  1451  
  1452  		// Bogus recipients, both uids are missing
  1453  		pairwiseMACRecipients = []keybase1.KID{"012141487209e42c6b39f7d9bcbda02a8e8045e4bcab10b571a5fa250ae72012bd3f0a"}
  1454  		boxed, err = boxer1.box(context.TODO(), msg, key, nil, signKP, chat1.MessageBoxedVersion_V3, pairwiseMACRecipients)
  1455  		require.NoError(t, err)
  1456  
  1457  		_, err = ri1.PostRemote(ctx1, chat1.PostRemoteArg{
  1458  			ConversationID: conv.Id,
  1459  			MessageBoxed:   boxed,
  1460  		})
  1461  		require.Error(t, err)
  1462  		require.IsType(t, libkb.EphemeralPairwiseMACsMissingUIDsError{}, err)
  1463  		merr = err.(libkb.EphemeralPairwiseMACsMissingUIDsError)
  1464  		sortUIDs := func(uids []keybase1.UID) { sort.Slice(uids, func(i, j int) bool { return uids[i] < uids[j] }) }
  1465  		expectedUIDs := []keybase1.UID{uid1, uid2}
  1466  		sortUIDs(expectedUIDs)
  1467  		sortUIDs(merr.UIDs)
  1468  		require.Equal(t, expectedUIDs, merr.UIDs)
  1469  
  1470  		// Including all devices works
  1471  		msg.ClientHeader.EphemeralMetadata = ephemeralMetadata
  1472  		_, _, err = blockingSender1.Send(ctx1, conv.Id, msg, 0, nil, nil, nil)
  1473  		require.NoError(t, err)
  1474  		select {
  1475  		case <-listener1.newMessageRemote:
  1476  		case <-time.After(20 * time.Second):
  1477  			require.Fail(t, "no new message")
  1478  		}
  1479  
  1480  		// send from user2
  1481  		text2 := "hi2"
  1482  		msg2 := textMsgWithSender(t, text2, uid2.ToBytes(), chat1.MessageBoxedVersion_V3)
  1483  		deviceID2 := make([]byte, libkb.DeviceIDLen)
  1484  		err = tc2.G.ActiveDevice.DeviceID().ToBytes(deviceID2)
  1485  		require.NoError(t, err)
  1486  		msg2.ClientHeader.TlfName = firstConv.TlfName
  1487  		msg2.ClientHeader.SenderDevice = gregor1.DeviceID(deviceID2)
  1488  		msg2.ClientHeader.EphemeralMetadata = ephemeralMetadata
  1489  		_, _, err = blockingSender2.Send(ctx2, conv.Id, msg2, 0, nil, nil, nil)
  1490  		require.NoError(t, err)
  1491  		select {
  1492  		case <-listener1.newMessageRemote:
  1493  		case <-time.After(20 * time.Second):
  1494  			require.Fail(t, "no new message")
  1495  		}
  1496  
  1497  		tv, err := tc1.Context().ConvSource.Pull(ctx1, conv.Id, uid1.ToBytes(),
  1498  			chat1.GetThreadReason_GENERAL, nil, nil, nil)
  1499  		require.NoError(t, err)
  1500  		require.Len(t, tv.Messages, 3)
  1501  		for _, msg := range tv.Messages {
  1502  			require.True(t, msg.IsValid())
  1503  		}
  1504  
  1505  		// Delete user2 and ensure user1 can still read/write to the channel
  1506  		kbtest.DeleteAccount(tc2.TestContext, users[1])
  1507  		kbtest.Logout(tc1.TestContext)
  1508  		require.NoError(t, users[0].Login(tc1.G))
  1509  
  1510  		// Nuke caches so we're forced to reload the deleted user
  1511  		_, err = tc1.G.LocalDb.Nuke()
  1512  		require.NoError(t, err)
  1513  		_, err = tc1.G.LocalChatDb.Nuke()
  1514  		require.NoError(t, err)
  1515  
  1516  		text3 := "hi3"
  1517  		msg3 := textMsgWithSender(t, text3, uid1.ToBytes(), chat1.MessageBoxedVersion_V3)
  1518  		msg3.ClientHeader.TlfName = firstConv.TlfName
  1519  		msg3.ClientHeader.SenderDevice = gregor1.DeviceID(deviceID1)
  1520  		msg3.ClientHeader.EphemeralMetadata = ephemeralMetadata
  1521  		_, _, err = blockingSender1.Send(ctx1, conv.Id, msg3, 0, nil, nil, nil)
  1522  		require.NoError(t, err)
  1523  
  1524  		_, err = tc1.G.LocalDb.Nuke()
  1525  		require.NoError(t, err)
  1526  		_, err = tc1.G.LocalChatDb.Nuke()
  1527  		require.NoError(t, err)
  1528  
  1529  		tv, err = tc1.Context().ConvSource.Pull(ctx1, conv.Id, uid1.ToBytes(),
  1530  			chat1.GetThreadReason_GENERAL, nil, nil, nil)
  1531  		require.NoError(t, err)
  1532  		require.Len(t, tv.Messages, 4)
  1533  		for _, msg := range tv.Messages {
  1534  			require.True(t, msg.IsValid())
  1535  		}
  1536  	})
  1537  }
  1538  
  1539  func TestEphemeralTopicType(t *testing.T) {
  1540  	runWithMemberTypes(t, func(mt chat1.ConversationMembersType) {
  1541  		switch mt {
  1542  		case chat1.ConversationMembersType_IMPTEAMNATIVE:
  1543  		default:
  1544  			return
  1545  		}
  1546  
  1547  		ctc := makeChatTestContext(t, "TestEphemeralTopicType", 1)
  1548  		defer ctc.cleanup()
  1549  		users := ctc.users()
  1550  
  1551  		conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_EMOJI, mt)
  1552  		ctx1 := ctc.as(t, users[0]).startCtx
  1553  
  1554  		tc1 := ctc.world.Tcs[users[0].Username]
  1555  		uid1 := users[0].User.GetUID()
  1556  		ri1 := ctc.as(t, users[0]).ri
  1557  		getRI1 := func() chat1.RemoteInterface { return ri1 }
  1558  		boxer1 := NewBoxer(tc1.Context())
  1559  		g1 := globals.NewContext(tc1.G, tc1.ChatG)
  1560  		blockingSender1 := NewBlockingSender(g1, boxer1, getRI1)
  1561  
  1562  		text := "hi"
  1563  		ephemeralMetadata := &chat1.MsgEphemeralMetadata{
  1564  			Lifetime: 100000,
  1565  		}
  1566  		msg := textMsgWithSender(t, text, uid1.ToBytes(), chat1.MessageBoxedVersion_V3)
  1567  		msg.ClientHeader.EphemeralMetadata = ephemeralMetadata
  1568  		_, _, err := blockingSender1.Send(ctx1, conv.Id, msg, 0, nil, nil, nil)
  1569  		require.Error(t, err)
  1570  	})
  1571  }
  1572  
  1573  func TestProcessDuplicateReactionMsgs(t *testing.T) {
  1574  	ctx, world, ri, _, baseSender, listener := setupTest(t, 1)
  1575  	defer world.Cleanup()
  1576  
  1577  	u := world.GetUsers()[0]
  1578  	tc := world.Tcs[u.Username]
  1579  	clock := world.Fc
  1580  	trip := newConvTriple(ctx, t, tc, u.Username)
  1581  	firstMessagePlaintext := chat1.MessagePlaintext{
  1582  		ClientHeader: chat1.MessageClientHeader{
  1583  			Conv:        trip,
  1584  			TlfName:     u.Username,
  1585  			TlfPublic:   false,
  1586  			MessageType: chat1.MessageType_TLFNAME,
  1587  		},
  1588  		MessageBody: chat1.MessageBody{},
  1589  	}
  1590  	prepareRes, err := baseSender.Prepare(ctx, firstMessagePlaintext,
  1591  		chat1.ConversationMembersType_KBFS, nil, nil)
  1592  	require.NoError(t, err)
  1593  	firstMessageBoxed := prepareRes.Boxed
  1594  	res, err := ri.NewConversationRemote2(ctx, chat1.NewConversationRemote2Arg{
  1595  		IdTriple:   trip,
  1596  		TLFMessage: firstMessageBoxed,
  1597  	})
  1598  	require.NoError(t, err)
  1599  
  1600  	// send initial text message which we will react to
  1601  	_, msgTextBoxed, err := baseSender.Send(ctx, res.ConvID, chat1.MessagePlaintext{
  1602  		ClientHeader: chat1.MessageClientHeader{
  1603  			Conv:        trip,
  1604  			Sender:      u.User.GetUID().ToBytes(),
  1605  			TlfName:     u.Username,
  1606  			TlfPublic:   false,
  1607  			MessageType: chat1.MessageType_TEXT,
  1608  		},
  1609  		MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{
  1610  			Body: "hi",
  1611  		}),
  1612  	}, 0, nil, nil, nil)
  1613  	require.NoError(t, err)
  1614  	msgTextID := msgTextBoxed.GetMessageID()
  1615  
  1616  	// Send a bunch of blocking reaction messages
  1617  	var sentRef []sentRecord
  1618  	for i := 0; i < 5; i++ {
  1619  		_, msgBoxed, err := baseSender.Send(ctx, res.ConvID, chat1.MessagePlaintext{
  1620  			ClientHeader: chat1.MessageClientHeader{
  1621  				Conv:        trip,
  1622  				Sender:      u.User.GetUID().ToBytes(),
  1623  				TlfName:     u.Username,
  1624  				TlfPublic:   false,
  1625  				Supersedes:  msgTextID,
  1626  				MessageType: chat1.MessageType_REACTION,
  1627  			},
  1628  			MessageBody: chat1.NewMessageBodyWithReaction(chat1.MessageReaction{
  1629  				Body:      ":+1:",
  1630  				MessageID: msgTextID,
  1631  			}),
  1632  		}, 0, nil, nil, nil)
  1633  		require.NoError(t, err)
  1634  		msgID := msgBoxed.GetMessageID()
  1635  		t.Logf("generated msgID: %d", msgID)
  1636  		sentRef = append(sentRef, sentRecord{msgID: &msgID})
  1637  	}
  1638  
  1639  	tres, err := tc.ChatG.ConvSource.Pull(ctx, res.ConvID, u.User.GetUID().ToBytes(),
  1640  		chat1.GetThreadReason_GENERAL, nil, nil, nil)
  1641  
  1642  	require.NoError(t, err)
  1643  	texts := utils.FilterByType(tres.Messages, &chat1.GetThreadQuery{MessageTypes: []chat1.MessageType{chat1.MessageType_TEXT}}, false)
  1644  	require.Len(t, texts, 1)
  1645  	txtMsg := texts[0]
  1646  	expectedReactionMap := chat1.ReactionMap{
  1647  		Reactions: map[string]map[string]chat1.Reaction{
  1648  			":+1:": {
  1649  				u.Username: {
  1650  					ReactionMsgID: *sentRef[len(sentRef)-1].msgID,
  1651  				},
  1652  			},
  1653  		},
  1654  	}
  1655  	// Verify the ctimes are not zero, but we don't care about the actual
  1656  	// value for the test.
  1657  	for _, reactions := range txtMsg.Valid().Reactions.Reactions {
  1658  		for k, r := range reactions {
  1659  			require.NotZero(t, r.Ctime)
  1660  			r.Ctime = 0
  1661  			reactions[k] = r
  1662  		}
  1663  	}
  1664  	require.Equal(t, expectedReactionMap, txtMsg.Valid().Reactions)
  1665  
  1666  	deletes := utils.FilterByType(tres.Messages, &chat1.GetThreadQuery{MessageTypes: []chat1.MessageType{chat1.MessageType_DELETE}}, false)
  1667  	require.Len(t, deletes, 2)
  1668  	reactions := utils.FilterByType(tres.Messages, &chat1.GetThreadQuery{MessageTypes: []chat1.MessageType{chat1.MessageType_REACTION}}, false)
  1669  	require.Len(t, reactions, 1)
  1670  
  1671  	// Add a bunch of things to the outbox. We should cancel all but one
  1672  	// ultimately deleting the reaction.
  1673  	outbox := storage.NewOutbox(tc.Context(), u.User.GetUID().ToBytes())
  1674  	outbox.SetClock(clock)
  1675  	var obids []chat1.OutboxID
  1676  	msgID := *sentRef[len(sentRef)-1].msgID
  1677  	for i := 0; i < 5; i++ {
  1678  		obr, err := outbox.PushMessage(ctx, res.ConvID, chat1.MessagePlaintext{
  1679  			ClientHeader: chat1.MessageClientHeader{
  1680  				Conv:        trip,
  1681  				Sender:      u.User.GetUID().ToBytes(),
  1682  				TlfName:     u.Username,
  1683  				TlfPublic:   false,
  1684  				Supersedes:  msgTextID,
  1685  				MessageType: chat1.MessageType_REACTION,
  1686  				OutboxInfo: &chat1.OutboxInfo{
  1687  					Prev: msgID,
  1688  				},
  1689  			},
  1690  			MessageBody: chat1.NewMessageBodyWithReaction(chat1.MessageReaction{
  1691  				Body:      ":+1:",
  1692  				MessageID: msgTextID,
  1693  			}),
  1694  		}, nil, nil, nil, keybase1.TLFIdentifyBehavior_CHAT_CLI)
  1695  		obid := obr.OutboxID
  1696  		t.Logf("generated obid: %s prev: %d", hex.EncodeToString(obid), msgID)
  1697  		require.NoError(t, err)
  1698  		obids = append(obids, obid)
  1699  	}
  1700  
  1701  	// Make we get nothing until timer is up
  1702  	select {
  1703  	case <-listener.incomingLocal:
  1704  		require.Fail(t, "action event received too soon")
  1705  	case <-listener.failing:
  1706  		require.Fail(t, "failed message")
  1707  	default:
  1708  	}
  1709  	clock.Advance(5 * time.Minute)
  1710  
  1711  	// Since we canceled all of the other outbox records we should should only
  1712  	// get one hit here.
  1713  	var olen int
  1714  	select {
  1715  	case olen = <-listener.incomingLocal:
  1716  	case <-time.After(20 * time.Second):
  1717  		require.Fail(t, "event not received")
  1718  	}
  1719  
  1720  	require.Equal(t, 1, olen, "wrong length")
  1721  	require.Equal(t, listener.obidsLocal[0], obids[0], "wrong obid")
  1722  
  1723  	// Make sure it is really empty
  1724  	clock.Advance(5 * time.Minute)
  1725  	select {
  1726  	case <-listener.incomingLocal:
  1727  		require.Fail(t, "action event received too soon")
  1728  	default:
  1729  	}
  1730  
  1731  	tres, err = tc.ChatG.ConvSource.Pull(ctx, res.ConvID, u.User.GetUID().ToBytes(),
  1732  		chat1.GetThreadReason_GENERAL, nil, nil, nil)
  1733  	require.NoError(t, err)
  1734  
  1735  	// we have the same number of messages as before since ultimately we just deleted a reaction
  1736  	texts = utils.FilterByType(tres.Messages, &chat1.GetThreadQuery{MessageTypes: []chat1.MessageType{chat1.MessageType_TEXT}}, false)
  1737  	require.Len(t, texts, 1)
  1738  	txtMsg = texts[0]
  1739  	require.Nil(t, txtMsg.Valid().Reactions.Reactions)
  1740  
  1741  	deletes = utils.FilterByType(tres.Messages, &chat1.GetThreadQuery{MessageTypes: []chat1.MessageType{chat1.MessageType_DELETE}}, false)
  1742  	require.Len(t, deletes, 3)
  1743  	reactions = utils.FilterByType(tres.Messages, &chat1.GetThreadQuery{MessageTypes: []chat1.MessageType{chat1.MessageType_REACTION}}, false)
  1744  	require.Len(t, reactions, 0)
  1745  }