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 }