github.com/keybase/client/go@v0.0.0-20240520164431-4f512a4c85a3/chat/server_test.go (about) 1 // Copyright 2016 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 package chat 5 6 import ( 7 "bytes" 8 "crypto/rand" 9 "encoding/binary" 10 "encoding/hex" 11 "encoding/json" 12 "errors" 13 "fmt" 14 "net/http" 15 "os" 16 "sort" 17 "strconv" 18 "strings" 19 "testing" 20 "time" 21 22 "github.com/davecgh/go-spew/spew" 23 "github.com/keybase/client/go/chat/bots" 24 "github.com/keybase/client/go/kbhttp/manager" 25 26 "golang.org/x/net/context" 27 28 "encoding/base64" 29 30 "github.com/keybase/client/go/chat/commands" 31 "github.com/keybase/client/go/chat/globals" 32 "github.com/keybase/client/go/chat/msgchecker" 33 "github.com/keybase/client/go/chat/search" 34 "github.com/keybase/client/go/chat/storage" 35 "github.com/keybase/client/go/chat/types" 36 "github.com/keybase/client/go/chat/utils" 37 "github.com/keybase/client/go/chat/wallet" 38 "github.com/keybase/client/go/gregor" 39 grutils "github.com/keybase/client/go/gregor/utils" 40 "github.com/keybase/client/go/kbtest" 41 "github.com/keybase/client/go/libkb" 42 "github.com/keybase/client/go/logger" 43 "github.com/keybase/client/go/protocol/chat1" 44 "github.com/keybase/client/go/protocol/gregor1" 45 "github.com/keybase/client/go/protocol/keybase1" 46 "github.com/keybase/client/go/protocol/stellar1" 47 "github.com/keybase/client/go/teams" 48 "github.com/keybase/clockwork" 49 "github.com/keybase/go-codec/codec" 50 "github.com/keybase/go-framed-msgpack-rpc/rpc" 51 "github.com/stretchr/testify/require" 52 ) 53 54 type gregorTestConnection struct { 55 globals.Contextified 56 utils.DebugLabeler 57 58 cli rpc.GenericClient 59 uid gregor1.UID 60 sessionToken string 61 } 62 63 var _ rpc.ConnectionHandler = (*gregorTestConnection)(nil) 64 65 func newGregorTestConnection(g *globals.Context, uid gregor1.UID, sessionToken string) *gregorTestConnection { 66 return &gregorTestConnection{ 67 Contextified: globals.NewContextified(g), 68 DebugLabeler: utils.NewDebugLabeler(g.ExternalG(), "gregorTestConnection", false), 69 uid: uid, 70 sessionToken: sessionToken, 71 } 72 } 73 74 func (g *gregorTestConnection) Connect(ctx context.Context) (err error) { 75 defer g.Trace(ctx, &err, "Connect")() 76 uri, err := rpc.ParseFMPURI(g.G().Env.GetGregorURI()) 77 if err != nil { 78 return err 79 } 80 opts := rpc.ConnectionOpts{ 81 TagsFunc: logger.LogTagsFromContextRPC, 82 WrapErrorFunc: libkb.MakeWrapError(g.G().ExternalG()), 83 } 84 trans := rpc.NewConnectionTransport(uri, libkb.NewRPCLogFactory(g.G().ExternalG()), 85 g.G().ExternalG().RemoteNetworkInstrumenterStorage, 86 libkb.MakeWrapError(g.G().ExternalG()), rpc.DefaultMaxFrameLength) 87 conn := rpc.NewConnectionWithTransport(g, trans, 88 libkb.NewContextifiedErrorUnwrapper(g.G().ExternalG()), 89 logger.LogOutputWithDepthAdder{Logger: g.G().Log}, opts) 90 g.cli = conn.GetClient() 91 return nil 92 } 93 94 func (g *gregorTestConnection) GetClient() chat1.RemoteInterface { 95 return chat1.RemoteClient{Cli: g.cli} 96 } 97 98 func (g *gregorTestConnection) Reconnect(ctx context.Context) (bool, error) { 99 return false, nil 100 } 101 102 func (g *gregorTestConnection) OnConnect(ctx context.Context, conn *rpc.Connection, 103 cli rpc.GenericClient, srv *rpc.Server) error { 104 g.Debug(ctx, "logged in: authenticating") 105 ac := gregor1.AuthClient{Cli: cli} 106 auth, err := ac.AuthenticateSessionToken(ctx, gregor1.SessionToken(g.sessionToken)) 107 if err != nil { 108 g.Debug(ctx, "auth error: %s", err) 109 return err 110 } 111 if !auth.Uid.Eq(g.uid) { 112 return fmt.Errorf("wrong uid authed: auth: %s uid: %s", auth.Uid, g.uid) 113 } 114 115 return srv.Register(gregor1.OutgoingProtocol(g)) 116 } 117 118 func (g *gregorTestConnection) BroadcastMessage(ctx context.Context, m gregor1.Message) error { 119 if obm := m.ToOutOfBandMessage(); obm != nil { 120 _, err := g.G().PushHandler.HandleOobm(ctx, obm) 121 return err 122 } 123 if ibm := m.ToInBandMessage(); ibm != nil { 124 if creation := ibm.ToStateUpdateMessage().Creation(); creation != nil { 125 switch creation.Category().String() { 126 case "team.sbs": 127 var msg keybase1.TeamSBSMsg 128 if err := json.Unmarshal(creation.Body().Bytes(), &msg); err != nil { 129 g.G().Log.CDebugf(ctx, "error unmarshaling team.sbs item: %s", err) 130 return err 131 } 132 err := teams.HandleSBSRequest(ctx, g.G().ExternalG(), msg) 133 if err != nil { 134 return err 135 } 136 case "team.change": 137 var msg []keybase1.TeamChangeRow 138 if err := json.Unmarshal(creation.Body().Bytes(), &msg); err != nil { 139 g.G().Log.CDebugf(ctx, "error unmarshaling team.change items: %s", err) 140 return err 141 } 142 err := teams.HandleChangeNotification(ctx, g.G().ExternalG(), msg, keybase1.TeamChangeSet{}) 143 if err != nil { 144 return err 145 } 146 } 147 } 148 } 149 return nil 150 } 151 152 func (g *gregorTestConnection) State(ctx context.Context) (gregor.State, error) { 153 return gregor1.IncomingClient{Cli: g.cli}.State(ctx, gregor1.StateArg{ 154 Uid: g.uid, 155 }) 156 } 157 158 func (g *gregorTestConnection) UpdateCategory(ctx context.Context, cat string, body []byte, 159 dtime gregor1.TimeOrOffset) (gregor1.MsgID, error) { 160 msg, err := grutils.TemplateMessage(g.uid) 161 if err != nil { 162 return nil, err 163 } 164 msgID := msg.Ibm_.StateUpdate_.Md_.MsgID_ 165 msg.Ibm_.StateUpdate_.Creation_ = &gregor1.Item{ 166 Category_: gregor1.Category(cat), 167 Body_: gregor1.Body(body), 168 Dtime_: dtime, 169 } 170 msg.Ibm_.StateUpdate_.Dismissal_ = &gregor1.Dismissal{ 171 Ranges_: []gregor1.MsgRange{ 172 { 173 Category_: gregor1.Category(cat), 174 SkipMsgIDs_: []gregor1.MsgID{msgID}, 175 }}, 176 } 177 return msgID, gregor1.IncomingClient{Cli: g.cli}.ConsumeMessage(ctx, msg) 178 } 179 180 func (g *gregorTestConnection) DismissItem(ctx context.Context, cli gregor1.IncomingInterface, id gregor.MsgID) error { 181 msg, err := grutils.FormMessageForDismissItem(ctx, g.uid, id) 182 if err != nil { 183 return err 184 } 185 return gregor1.IncomingClient{Cli: g.cli}.ConsumeMessage(ctx, msg.(gregor1.Message)) 186 } 187 188 func (g *gregorTestConnection) DismissCategory(ctx context.Context, cat gregor1.Category) error { 189 msg, err := grutils.FormMessageForDismissCategory(ctx, g.uid, cat) 190 if err != nil { 191 return err 192 } 193 return gregor1.IncomingClient{Cli: g.cli}.ConsumeMessage(ctx, msg.(gregor1.Message)) 194 } 195 196 func (g *gregorTestConnection) InjectItem(ctx context.Context, cat string, body []byte, 197 dtime gregor1.TimeOrOffset) (gregor1.MsgID, error) { 198 msg, err := grutils.FormMessageForInjectItem(ctx, g.uid, cat, body, dtime) 199 if err != nil { 200 return nil, err 201 } 202 retMsgID := gregor1.MsgID(msg.ToInBandMessage().Metadata().MsgID().Bytes()) 203 return retMsgID, gregor1.IncomingClient{Cli: g.cli}.ConsumeMessage(ctx, msg.(gregor1.Message)) 204 } 205 206 func (g *gregorTestConnection) LocalDismissItem(ctx context.Context, id gregor.MsgID) error { 207 return nil 208 } 209 210 func (g *gregorTestConnection) OnConnectError(err error, reconnectThrottleDuration time.Duration) { 211 } 212 213 func (g *gregorTestConnection) OnDoCommandError(err error, nextTime time.Duration) { 214 } 215 216 func (g *gregorTestConnection) OnDisconnected(ctx context.Context, status rpc.DisconnectStatus) { 217 } 218 219 func (g *gregorTestConnection) ShouldRetry(name string, err error) bool { 220 return false 221 } 222 223 func (g *gregorTestConnection) ShouldRetryOnConnect(err error) bool { 224 return false 225 } 226 227 func (g *gregorTestConnection) HandlerName() string { 228 return "gregorTestConnection" 229 } 230 231 func newTestContext(tc *kbtest.ChatTestContext) context.Context { 232 if tc.ChatG.CtxFactory == nil { 233 g := globals.NewContext(tc.G, tc.ChatG) 234 g.CtxFactory = NewCtxFactory(g) 235 } 236 return globals.ChatCtx(context.Background(), tc.Context(), keybase1.TLFIdentifyBehavior_CHAT_CLI, 237 nil, NewCachingIdentifyNotifier(tc.Context())) 238 } 239 240 func newTestContextWithTlfMock(tc *kbtest.ChatTestContext, tlfMock types.NameInfoSource) context.Context { 241 ctx := newTestContext(tc) 242 return globals.CtxAddOverrideNameInfoSource(ctx, tlfMock) 243 } 244 245 type testUISource struct { 246 } 247 248 func (t testUISource) GetChatUI(sessionID int) libkb.ChatUI { 249 return nil 250 } 251 252 func (t testUISource) GetStreamUICli() *keybase1.StreamUiClient { 253 return &keybase1.StreamUiClient{Cli: nil} 254 } 255 256 // Create a team with me as the owner 257 func createTeam(tc libkb.TestContext) string { 258 b, err := libkb.RandBytes(4) 259 require.NoError(tc.T, err) 260 261 name := fmt.Sprintf("TeAm%v", hex.EncodeToString(b)) 262 _, err = teams.CreateRootTeam(context.TODO(), tc.G, name, keybase1.TeamSettings{}) 263 require.NoError(tc.T, err) 264 return name 265 } 266 267 // Create a team with me as the owner and writers as writers. 268 // Writers must not include me. 269 func createTeamWithWriters(tc libkb.TestContext, writers []*kbtest.FakeUser) string { 270 name := createTeam(tc) 271 for _, u := range writers { 272 err := teams.SetRoleWriter(context.TODO(), tc.G, name, u.Username) 273 require.NoError(tc.T, err, "team set role") 274 } 275 return name 276 } 277 278 type byUsername []*kbtest.FakeUser 279 280 func (b byUsername) Len() int { return len(b) } 281 func (b byUsername) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 282 func (b byUsername) Less(i, j int) bool { 283 return strings.Compare(b[i].Username, b[j].Username) < 0 284 } 285 286 func teamKey(users []*kbtest.FakeUser) (res string) { 287 ucopy := make([]*kbtest.FakeUser, len(users)) 288 copy(ucopy, users) 289 sort.Sort(byUsername(ucopy)) 290 for _, u := range ucopy { 291 res += u.Username 292 } 293 return res 294 } 295 296 var useRemoteMock = true 297 298 func runWithMemberTypes(t *testing.T, f func(membersType chat1.ConversationMembersType)) { 299 useRemoteMock = false 300 defer func() { useRemoteMock = true }() 301 t.Logf("Team Stage Begin") 302 start := time.Now() 303 f(chat1.ConversationMembersType_TEAM) 304 t.Logf("Team Stage End: %v", time.Since(start)) 305 306 t.Logf("Implicit Team Stage Begin") 307 os.Setenv("KEYBASE_FEATURES", "admin") 308 defer os.Setenv("KEYBASE_FEATURES", "") 309 start = time.Now() 310 f(chat1.ConversationMembersType_IMPTEAMNATIVE) 311 t.Logf("Implicit Team Stage End: %v", time.Since(start)) 312 } 313 314 func runWithEphemeral(t *testing.T, mt chat1.ConversationMembersType, f func(ephemeralLifetime *gregor1.DurationSec)) { 315 switch mt { 316 case chat1.ConversationMembersType_TEAM, chat1.ConversationMembersType_IMPTEAMUPGRADE, chat1.ConversationMembersType_IMPTEAMNATIVE: 317 f(nil) 318 lifetime := gregor1.DurationSec(24 * 60 * 60 * 6) 319 f(&lifetime) 320 default: 321 f(nil) 322 } 323 } 324 325 func runWithRetentionPolicyTypes(t *testing.T, f func(policy chat1.RetentionPolicy, ephemeralLifetime *gregor1.DurationSec)) { 326 age := gregor1.DurationSec(1) 327 t.Logf("using EXPIRE retention policy") 328 f(chat1.NewRetentionPolicyWithExpire(chat1.RpExpire{Age: age}), nil) 329 t.Logf("using EPHEMERAL retention policy") 330 f(chat1.NewRetentionPolicyWithEphemeral(chat1.RpEphemeral{Age: age}), &age) 331 } 332 333 type chatTestUserContext struct { 334 startCtx context.Context 335 u *kbtest.FakeUser 336 h *Server 337 ri chat1.RemoteInterface 338 m libkb.MetaContext 339 } 340 341 func (tuc *chatTestUserContext) user() *kbtest.FakeUser { 342 return tuc.u 343 } 344 345 func (tuc *chatTestUserContext) chatLocalHandler() chat1.LocalInterface { 346 return tuc.h 347 } 348 349 type chatTestContext struct { 350 world *kbtest.ChatMockWorld 351 352 userContextCache map[string]*chatTestUserContext 353 teamCache map[string]string 354 } 355 356 func makeChatTestContext(t *testing.T, name string, numUsers int) *chatTestContext { 357 ctc := &chatTestContext{} 358 ctc.world = NewChatMockWorld(t, name, numUsers) 359 ctc.userContextCache = make(map[string]*chatTestUserContext) 360 ctc.teamCache = make(map[string]string) 361 return ctc 362 } 363 364 func (c *chatTestContext) advanceFakeClock(d time.Duration) { 365 c.world.Fc.Advance(d) 366 } 367 368 func (c *chatTestContext) as(t *testing.T, user *kbtest.FakeUser) *chatTestUserContext { 369 var ctx context.Context 370 require.NotNil(t, user) 371 372 if tuc, ok := c.userContextCache[user.Username]; ok { 373 return tuc 374 } 375 376 tc, ok := c.world.Tcs[user.Username] 377 require.True(t, ok) 378 g := globals.NewContext(tc.G, tc.ChatG) 379 h := NewServer(g, nil, testUISource{}) 380 uid := gregor1.UID(user.User.GetUID().ToBytes()) 381 382 var tlf *kbtest.TlfMock 383 var ri chat1.RemoteInterface 384 var serverConn types.ServerConnection 385 if useRemoteMock { 386 mockRemote := kbtest.NewChatRemoteMock(c.world) 387 mockRemote.SetCurrentUser(user.User.GetUID().ToBytes()) 388 tlf = kbtest.NewTlfMock(c.world) 389 ri = mockRemote 390 serverConn = kbtest.NewChatRemoteMockServerConnection(mockRemote) 391 ctx = newTestContextWithTlfMock(tc, tlf) 392 } else { 393 ctx = newTestContext(tc) 394 nist, err := tc.G.ActiveDevice.NIST(context.TODO()) 395 require.NoError(t, err) 396 sessionToken := nist.Token().String() 397 gh := newGregorTestConnection(tc.Context(), uid, sessionToken) 398 g.GregorState = gh 399 require.NoError(t, gh.Connect(ctx)) 400 ri = gh.GetClient() 401 serverConn = gh 402 } 403 404 h.boxer = NewBoxer(g) 405 406 chatStorage := storage.New(g, nil) 407 chatStorage.SetClock(c.world.Fc) 408 g.CtxFactory = NewCtxFactory(g) 409 g.ConvSource = NewHybridConversationSource(g, h.boxer, chatStorage, 410 func() chat1.RemoteInterface { return ri }) 411 chatStorage.SetAssetDeleter(g.ConvSource) 412 g.InboxSource = NewHybridInboxSource(g, 413 func() chat1.RemoteInterface { return ri }) 414 g.InboxSource.Start(context.TODO(), uid) 415 g.InboxSource.Connected(context.TODO()) 416 g.ServerCacheVersions = storage.NewServerVersions(g) 417 chatSyncer := NewSyncer(g) 418 g.Syncer = chatSyncer 419 g.ConnectivityMonitor = &libkb.NullConnectivityMonitor{} 420 searcher := search.NewRegexpSearcher(g) 421 // Force small pages during tests to ensure we fetch context from new pages 422 searcher.SetPageSize(2) 423 g.RegexpSearcher = searcher 424 indexer := search.NewIndexer(g) 425 ictx := globals.CtxAddIdentifyMode(context.Background(), keybase1.TLFIdentifyBehavior_CHAT_SKIP, nil) 426 indexer.SetPageSize(2) 427 indexer.SetStartSyncDelay(0) 428 indexer.Start(ictx, uid) 429 g.Indexer = indexer 430 431 h.setTestRemoteClient(ri) 432 433 tc.G.SetService() 434 baseSender := NewBlockingSender(g, h.boxer, func() chat1.RemoteInterface { return ri }) 435 deliverer := NewDeliverer(g, baseSender, serverConn) 436 deliverer.SetClock(c.world.Fc) 437 if useRemoteMock { 438 deliverer.setTestingNameInfoSource(tlf) 439 } 440 g.MessageDeliverer = deliverer 441 g.MessageDeliverer.Start(context.TODO(), uid) 442 g.MessageDeliverer.Connected(context.TODO()) 443 444 retrier := NewFetchRetrier(g) 445 retrier.SetClock(c.world.Fc) 446 g.FetchRetrier = retrier 447 g.FetchRetrier.Connected(context.TODO()) 448 g.FetchRetrier.Start(context.TODO(), uid) 449 450 g.ConvLoader = NewBackgroundConvLoader(g) 451 g.EphemeralPurger = types.DummyEphemeralPurger{} 452 g.CommandsSource = commands.NewSource(g) 453 454 pushHandler := NewPushHandler(g) 455 pushHandler.Start(context.TODO(), nil) 456 g.PushHandler = pushHandler 457 g.TeamChannelSource = NewTeamChannelSource(g) 458 g.AttachmentURLSrv = types.DummyAttachmentHTTPSrv{} 459 g.ActivityNotifier = NewNotifyRouterActivityRouter(g) 460 g.AttachmentUploader = types.DummyAttachmentUploader{} 461 g.Unfurler = types.DummyUnfurler{} 462 g.StellarLoader = types.DummyStellarLoader{} 463 g.StellarSender = types.DummyStellarSender{} 464 g.TeamMentionLoader = types.DummyTeamMentionLoader{} 465 g.CoinFlipManager = NewFlipManager(g, func() chat1.RemoteInterface { return ri }) 466 g.CoinFlipManager.Start(context.TODO(), uid) 467 g.JourneyCardManager = NewJourneyCardManager(g, func() chat1.RemoteInterface { return ri }) 468 g.BotCommandManager = bots.NewCachingBotCommandManager(g, func() chat1.RemoteInterface { return ri }, 469 CreateNameInfoSource) 470 g.BotCommandManager.Start(context.TODO(), uid) 471 g.UIInboxLoader = types.DummyUIInboxLoader{} 472 g.UIThreadLoader = NewUIThreadLoader(g, func() chat1.RemoteInterface { return ri }) 473 g.ParticipantsSource = NewCachingParticipantSource(g, func() chat1.RemoteInterface { return ri }) 474 g.EmojiSource = NewDevConvEmojiSource(g, func() chat1.RemoteInterface { return ri }) 475 g.EphemeralTracker = NewEphemeralTracker(g) 476 g.EphemeralTracker.Start(context.TODO(), uid) 477 478 tc.G.ChatHelper = NewHelper(g, func() chat1.RemoteInterface { return ri }) 479 480 tuc := &chatTestUserContext{ 481 h: h, 482 u: user, 483 startCtx: ctx, 484 ri: ri, 485 m: libkb.NewMetaContext(ctx, tc.G), 486 } 487 c.userContextCache[user.Username] = tuc 488 return tuc 489 } 490 491 func (c *chatTestContext) cleanup() { 492 c.world.Cleanup() 493 } 494 495 func (c *chatTestContext) users() (users []*kbtest.FakeUser) { 496 for _, u := range c.world.Users { 497 users = append(users, u) 498 } 499 return users 500 } 501 502 func mustCreatePublicConversationForTest(t *testing.T, ctc *chatTestContext, creator *kbtest.FakeUser, 503 topicType chat1.TopicType, membersType chat1.ConversationMembersType, others ...*kbtest.FakeUser) (created chat1.ConversationInfoLocal) { 504 created = mustCreateConversationForTestNoAdvanceClock(t, ctc, creator, topicType, 505 nil, keybase1.TLFVisibility_PUBLIC, membersType, others...) 506 ctc.advanceFakeClock(time.Second) 507 return created 508 } 509 510 func mustCreateConversationForTest(t *testing.T, ctc *chatTestContext, creator *kbtest.FakeUser, 511 topicType chat1.TopicType, membersType chat1.ConversationMembersType, others ...*kbtest.FakeUser) (created chat1.ConversationInfoLocal) { 512 created = mustCreateConversationForTestNoAdvanceClock(t, ctc, creator, topicType, 513 nil, keybase1.TLFVisibility_PRIVATE, membersType, others...) 514 ctc.advanceFakeClock(time.Second) 515 return created 516 } 517 518 func mustCreateChannelForTest(t *testing.T, ctc *chatTestContext, creator *kbtest.FakeUser, 519 topicType chat1.TopicType, topicName *string, membersType chat1.ConversationMembersType, 520 others ...*kbtest.FakeUser) (created chat1.ConversationInfoLocal) { 521 created = mustCreateConversationForTestNoAdvanceClock(t, ctc, creator, topicType, 522 topicName, keybase1.TLFVisibility_PRIVATE, membersType, others...) 523 ctc.advanceFakeClock(time.Second) 524 return created 525 } 526 527 func mustCreateConversationForTestNoAdvanceClock(t *testing.T, ctc *chatTestContext, 528 creator *kbtest.FakeUser, topicType chat1.TopicType, topicName *string, visibility keybase1.TLFVisibility, 529 membersType chat1.ConversationMembersType, others ...*kbtest.FakeUser) (created chat1.ConversationInfoLocal) { 530 var err error 531 532 t.Logf("mustCreateConversationForTestNoAdvanceClock") 533 t.Logf("creator: %v", creator.Username) 534 for _, o := range others { 535 t.Logf("other: %v", o.Username) 536 } 537 538 // Create conversation name based on list of users 539 var name string 540 switch membersType { 541 case chat1.ConversationMembersType_KBFS, chat1.ConversationMembersType_IMPTEAMNATIVE, 542 chat1.ConversationMembersType_IMPTEAMUPGRADE: 543 var memberStr []string 544 for _, other := range others { 545 memberStr = append(memberStr, other.Username) 546 } 547 memberStr = append(memberStr, creator.Username) 548 name = strings.Join(memberStr, ",") 549 case chat1.ConversationMembersType_TEAM: 550 tc := ctc.world.Tcs[creator.Username] 551 users := others 552 users = append(users, creator) 553 key := teamKey(users) 554 if tn, ok := ctc.teamCache[key]; !ok { 555 name = createTeamWithWriters(tc.TestContext, others) 556 ctc.teamCache[key] = name 557 } else { 558 name = tn 559 } 560 default: 561 t.Fatalf("unhandled membersType: %v", membersType) 562 } 563 564 tc := ctc.as(t, creator) 565 ncres, err := tc.chatLocalHandler().NewConversationLocal(tc.startCtx, 566 chat1.NewConversationLocalArg{ 567 TlfName: name, 568 TopicType: topicType, 569 TopicName: topicName, 570 TlfVisibility: visibility, 571 MembersType: membersType, 572 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 573 }) 574 require.NoError(t, err) 575 576 // Set initial active list 577 conv := ctc.world.GetConversationByID(ncres.Conv.GetConvID()) 578 if conv != nil { 579 conv.Metadata.ActiveList = append(conv.Metadata.ActiveList, creator.GetUID().ToBytes()) 580 for _, o := range others { 581 conv.Metadata.ActiveList = append(conv.Metadata.ActiveList, o.GetUID().ToBytes()) 582 } 583 } 584 585 return ncres.Conv.Info 586 } 587 588 func postLocalEphemeralForTest(t *testing.T, ctc *chatTestContext, asUser *kbtest.FakeUser, conv chat1.ConversationInfoLocal, msg chat1.MessageBody, ephemeralLifetime *gregor1.DurationSec) (chat1.PostLocalRes, error) { 589 defer ctc.advanceFakeClock(time.Second) 590 mt, err := msg.MessageType() 591 require.NoError(t, err) 592 tc := ctc.as(t, asUser) 593 var ephemeralMetadata *chat1.MsgEphemeralMetadata 594 if ephemeralLifetime != nil { 595 ephemeralMetadata = &chat1.MsgEphemeralMetadata{ 596 Lifetime: *ephemeralLifetime, 597 } 598 } 599 return tc.chatLocalHandler().PostLocal(tc.startCtx, chat1.PostLocalArg{ 600 ConversationID: conv.Id, 601 Msg: chat1.MessagePlaintext{ 602 ClientHeader: chat1.MessageClientHeader{ 603 Conv: conv.Triple, 604 MessageType: mt, 605 TlfName: conv.TlfName, 606 EphemeralMetadata: ephemeralMetadata, 607 }, 608 MessageBody: msg, 609 }, 610 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 611 }) 612 } 613 614 func mustPostLocalEphemeralForTest(t *testing.T, ctc *chatTestContext, 615 asUser *kbtest.FakeUser, conv chat1.ConversationInfoLocal, msg chat1.MessageBody, ephemeralLifetime *gregor1.DurationSec) chat1.MessageID { 616 res, err := postLocalEphemeralForTest(t, ctc, asUser, conv, msg, ephemeralLifetime) 617 require.NoError(t, err) 618 ctc.advanceFakeClock(time.Second) 619 return res.MessageID 620 } 621 622 func postLocalForTestNoAdvanceClock(t *testing.T, ctc *chatTestContext, asUser *kbtest.FakeUser, conv chat1.ConversationInfoLocal, msg chat1.MessageBody) (chat1.PostLocalRes, error) { 623 mt, err := msg.MessageType() 624 require.NoError(t, err) 625 tc := ctc.as(t, asUser) 626 return tc.chatLocalHandler().PostLocal(tc.startCtx, chat1.PostLocalArg{ 627 ConversationID: conv.Id, 628 Msg: chat1.MessagePlaintext{ 629 ClientHeader: chat1.MessageClientHeader{ 630 Conv: conv.Triple, 631 MessageType: mt, 632 TlfName: conv.TlfName, 633 }, 634 MessageBody: msg, 635 }, 636 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 637 }) 638 } 639 640 func postLocalForTest(t *testing.T, ctc *chatTestContext, 641 asUser *kbtest.FakeUser, conv chat1.ConversationInfoLocal, msg chat1.MessageBody) (chat1.PostLocalRes, error) { 642 defer ctc.advanceFakeClock(time.Second) 643 return postLocalForTestNoAdvanceClock(t, ctc, asUser, conv, msg) 644 } 645 646 func mustPostLocalForTestNoAdvanceClock(t *testing.T, ctc *chatTestContext, 647 asUser *kbtest.FakeUser, conv chat1.ConversationInfoLocal, msg chat1.MessageBody) chat1.MessageID { 648 x, err := postLocalForTestNoAdvanceClock(t, ctc, asUser, conv, msg) 649 require.NoError(t, err) 650 return x.MessageID 651 } 652 653 func mustPostLocalForTest(t *testing.T, ctc *chatTestContext, 654 asUser *kbtest.FakeUser, conv chat1.ConversationInfoLocal, msg chat1.MessageBody) chat1.MessageID { 655 msgID := mustPostLocalForTestNoAdvanceClock(t, ctc, asUser, conv, msg) 656 ctc.advanceFakeClock(time.Second) 657 return msgID 658 } 659 660 func mustSetConvRetentionLocal(t *testing.T, ctc *chatTestContext, asUser *kbtest.FakeUser, 661 convID chat1.ConversationID, policy chat1.RetentionPolicy) { 662 tc := ctc.as(t, asUser) 663 err := tc.chatLocalHandler().SetConvRetentionLocal(tc.startCtx, chat1.SetConvRetentionLocalArg{ 664 ConvID: convID, 665 Policy: policy, 666 }) 667 require.NoError(t, err) 668 } 669 670 func mustSetTeamRetentionLocal(t *testing.T, ctc *chatTestContext, asUser *kbtest.FakeUser, 671 teamID keybase1.TeamID, policy chat1.RetentionPolicy) { 672 tc := ctc.as(t, asUser) 673 err := tc.chatLocalHandler().SetTeamRetentionLocal(tc.startCtx, chat1.SetTeamRetentionLocalArg{ 674 TeamID: teamID, 675 Policy: policy, 676 }) 677 require.NoError(t, err) 678 } 679 680 func mustSetConvRetention(t *testing.T, ctc *chatTestContext, asUser *kbtest.FakeUser, 681 convID chat1.ConversationID, policy chat1.RetentionPolicy, sweepChannel uint64) { 682 tc := ctc.as(t, asUser) 683 // Use the remote version instead of the local version in order to have access to sweepChannel. 684 _, err := tc.ri.SetConvRetention(tc.startCtx, chat1.SetConvRetentionArg{ 685 ConvID: convID, 686 Policy: policy, 687 SweepChannel: sweepChannel, 688 }) 689 require.NoError(t, err) 690 } 691 692 func mustSetTeamRetention(t *testing.T, ctc *chatTestContext, asUser *kbtest.FakeUser, 693 teamID keybase1.TeamID, policy chat1.RetentionPolicy, sweepChannel uint64) { 694 tc := ctc.as(t, asUser) 695 // Use the remote version instead of the local version in order to have access to sweepChannel. 696 _, err := tc.ri.SetTeamRetention(tc.startCtx, chat1.SetTeamRetentionArg{ 697 TeamID: teamID, 698 Policy: policy, 699 SweepChannel: sweepChannel, 700 }) 701 require.NoError(t, err) 702 } 703 704 func mustJoinConversationByID(t *testing.T, ctc *chatTestContext, asUser *kbtest.FakeUser, convID chat1.ConversationID) { 705 tc := ctc.as(t, asUser) 706 _, err := tc.chatLocalHandler().JoinConversationByIDLocal(tc.startCtx, convID) 707 require.NoError(t, err) 708 } 709 710 // Make chatsweeperd run a particular conversation until messages are deleted. 711 // The RPC does not need to run as a particular user, that's just an easy way to get a remote client. 712 // Consumes expunge notifications from `listener` and returns the latest one. 713 // `upto` is optional (0 means any) 714 func sweepPollForDeletion(t *testing.T, ctc *chatTestContext, asUser *kbtest.FakeUser, listener *serverChatListener, convID chat1.ConversationID, uptoWant chat1.MessageID) chat1.ExpungeInfo { 715 t.Logf("sweepPollForDeletion(convID: %v, uptoWant: %v", convID, uptoWant) 716 tc := ctc.as(t, asUser) 717 maxTime := 5 * time.Second 718 afterCh := time.After(maxTime) 719 var foundTaskCount int 720 var upto chat1.MessageID 721 for i := 0; ; i++ { 722 ctx := globals.ChatCtx(context.Background(), tc.h.G(), keybase1.TLFIdentifyBehavior_CLI, nil, nil) 723 trace, _ := globals.CtxTrace(ctx) 724 t.Logf("+ RetentionSweepConv(%v) (uptoWant %v) [chat-trace=%v]", convID.String(), uptoWant, trace) 725 res, err := tc.ri.RetentionSweepConv(ctx, convID) 726 t.Logf("- RetentionSweepConv res: %+v", res) 727 require.NoError(t, err) 728 if res.FoundTask { 729 foundTaskCount++ 730 } 731 if res.DeletedMessages { 732 var expungeInfo chat1.ExpungeInfo 733 for { 734 expungeInfo = consumeExpunge(t, listener) 735 if expungeInfo.Expunge == res.Expunge { 736 break 737 } 738 t.Logf("sweepPollForDeletion %+v != %+v, trying consumeExpunge again", 739 expungeInfo.Expunge, res.Expunge) 740 } 741 require.Equal(t, convID, expungeInfo.ConvID, "accidentally consumed expunge info for other conv") 742 upto = res.Expunge.Upto 743 if upto >= uptoWant { 744 return expungeInfo 745 } 746 t.Logf("sweepPollForDeletion ignoring expungeInfo: %+v (uptoWant:%v)", expungeInfo.Expunge, uptoWant) 747 } 748 time.Sleep(10 * time.Millisecond) 749 select { 750 case <-afterCh: 751 require.FailNow(t, fmt.Sprintf("no messages deleted after %v runs, %v hit, upto %v, %v", 752 i, foundTaskCount, upto, maxTime)) 753 default: 754 } 755 } 756 } 757 758 // Sweep a conv and assert that no deletion occurred 759 func sweepNoDeletion(t *testing.T, ctc *chatTestContext, asUser *kbtest.FakeUser, convID chat1.ConversationID) { 760 t.Logf("sweepNoDeletion(convID: %v)", convID) 761 tc := ctc.as(t, asUser) 762 res, err := tc.ri.RetentionSweepConv(tc.startCtx, convID) 763 require.NoError(t, err) 764 require.False(t, res.DeletedMessages, "messages deleted") 765 } 766 767 func TestChatSrvNewConversationLocal(t *testing.T) { 768 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 769 ctc := makeChatTestContext(t, "NewConversationLocal", 2) 770 defer ctc.cleanup() 771 users := ctc.users() 772 created := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt, 773 ctc.as(t, users[1]).user()) 774 tc := ctc.world.Tcs[users[0].Username] 775 ctx := ctc.as(t, users[0]).startCtx 776 uid := users[0].User.GetUID().ToBytes() 777 conv, err := utils.GetUnverifiedConv(ctx, tc.Context(), uid, created.Id, 778 types.InboxSourceDataSourceRemoteOnly) 779 require.NoError(t, err) 780 require.NotZero(t, len(conv.Conv.MaxMsgSummaries)) 781 switch mt { 782 case chat1.ConversationMembersType_KBFS, chat1.ConversationMembersType_IMPTEAMNATIVE: 783 refName := string(kbtest.CanonicalTlfNameForTest( 784 ctc.as(t, users[0]).user().Username + "," + ctc.as(t, users[1]).user().Username), 785 ) 786 require.Equal(t, refName, conv.Conv.MaxMsgSummaries[0].TlfName) 787 case chat1.ConversationMembersType_TEAM: 788 teamName := ctc.teamCache[teamKey(ctc.users())] 789 require.Equal(t, strings.ToLower(teamName), conv.Conv.MaxMsgSummaries[0].TlfName) 790 } 791 }) 792 } 793 794 func TestChatSrvNewChatConversationLocalTwice(t *testing.T) { 795 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 796 ctc := makeChatTestContext(t, "NewConversationLocalTwice", 2) 797 defer ctc.cleanup() 798 users := ctc.users() 799 800 c1 := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 801 mt, ctc.as(t, users[1]).user()) 802 c2 := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 803 mt, ctc.as(t, users[1]).user()) 804 805 t.Logf("c1: %v c2: %v", c1, c2) 806 require.True(t, c2.Id.Eq(c1.Id)) 807 }) 808 } 809 810 func TestChatNewDevConversationLocalTwice(t *testing.T) { 811 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 812 ctc := makeChatTestContext(t, "NewDevConversationLocalTwice", 2) 813 defer ctc.cleanup() 814 users := ctc.users() 815 816 mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_DEV, 817 mt, ctc.as(t, users[1]).user()) 818 mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_DEV, 819 mt, ctc.as(t, users[1]).user()) 820 }) 821 } 822 823 func TestChatSrvNewConversationMultiTeam(t *testing.T) { 824 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 825 ctc := makeChatTestContext(t, "NewConversationLocalTeams", 2) 826 defer ctc.cleanup() 827 users := ctc.users() 828 829 conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 830 mt, ctc.as(t, users[1]).user()) 831 832 tc := ctc.as(t, users[0]) 833 topicName := "MIKETIME" 834 arg := chat1.NewConversationLocalArg{ 835 TlfName: conv.TlfName, 836 TopicName: &topicName, 837 TopicType: chat1.TopicType_CHAT, 838 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 839 MembersType: mt, 840 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 841 } 842 ncres, err := tc.chatLocalHandler().NewConversationLocal(tc.startCtx, arg) 843 require.NoError(t, err) 844 switch mt { 845 case chat1.ConversationMembersType_TEAM: 846 require.Equal(t, topicName, ncres.Conv.Info.TopicName) 847 require.NotEqual(t, conv.Id, ncres.Conv.GetConvID()) 848 default: 849 require.Equal(t, conv.Id, ncres.Conv.GetConvID()) 850 } 851 // recreate as the second user 852 tc2 := ctc.as(t, users[1]) 853 ncres2, err := tc2.chatLocalHandler().NewConversationLocal(tc2.startCtx, arg) 854 require.NoError(t, err) 855 switch mt { 856 case chat1.ConversationMembersType_TEAM: 857 require.Equal(t, topicName, ncres.Conv.Info.TopicName) 858 require.Equal(t, ncres.Conv.GetConvID(), ncres2.Conv.GetConvID()) 859 default: 860 require.Equal(t, conv.Id, ncres.Conv.GetConvID()) 861 } 862 863 // Try some invalid names 864 topicName = "#mike" 865 _, err = tc.chatLocalHandler().NewConversationLocal(tc.startCtx, arg) 866 require.Error(t, err) 867 topicName = "/mike" 868 _, err = tc.chatLocalHandler().NewConversationLocal(tc.startCtx, arg) 869 require.Error(t, err) 870 topicName = "mi.ke" 871 _, err = tc.chatLocalHandler().NewConversationLocal(tc.startCtx, arg) 872 require.Error(t, err) 873 arg.TopicName = nil 874 ncres, err = tc.chatLocalHandler().NewConversationLocal(tc.startCtx, arg) 875 require.NoError(t, err) 876 switch mt { 877 case chat1.ConversationMembersType_TEAM: 878 require.Equal(t, globals.DefaultTeamTopic, ncres.Conv.Info.TopicName) 879 default: 880 } 881 arg.TopicName = &topicName 882 topicName = "dskjdskdjskdjskdjskdjskdjskdjskjdskjdskdskdjksdjks" 883 _, err = tc.chatLocalHandler().NewConversationLocal(tc.startCtx, arg) 884 require.Error(t, err) 885 }) 886 } 887 888 func TestChatSrvGetInboxAndUnboxLocal(t *testing.T) { 889 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 890 ctc := makeChatTestContext(t, "ResolveConversationLocal", 2) 891 defer ctc.cleanup() 892 users := ctc.users() 893 894 ctx := ctc.as(t, users[0]).startCtx 895 created := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 896 mt, ctc.as(t, users[1]).user()) 897 898 gilres, err := ctc.as(t, users[0]).chatLocalHandler().GetInboxAndUnboxLocal(ctx, chat1.GetInboxAndUnboxLocalArg{ 899 Query: &chat1.GetInboxLocalQuery{ 900 ConvIDs: []chat1.ConversationID{created.Id}, 901 }, 902 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 903 }) 904 if err != nil { 905 t.Fatalf("GetInboxAndUnboxLocal error: %v", err) 906 } 907 conversations := gilres.Conversations 908 if len(conversations) != 1 { 909 t.Fatalf("unexpected response from GetInboxAndUnboxLocal. expected 1 items, got %d\n", len(conversations)) 910 } 911 912 tc := ctc.world.Tcs[users[0].Username] 913 uid := users[0].User.GetUID().ToBytes() 914 915 conv, err := utils.GetUnverifiedConv(ctx, tc.Context(), uid, created.Id, 916 types.InboxSourceDataSourceRemoteOnly) 917 require.NoError(t, err) 918 if conversations[0].Info.TlfName != conv.Conv.MaxMsgSummaries[0].TlfName { 919 t.Fatalf("unexpected TlfName in response from GetInboxAndUnboxLocal. %s != %s (mt = %v)", conversations[0].Info.TlfName, conv.Conv.MaxMsgSummaries[0].TlfName, mt) 920 } 921 if !conversations[0].Info.Id.Eq(created.Id) { 922 t.Fatalf("unexpected Id in response from GetInboxAndUnboxLocal. %s != %s\n", conversations[0].Info.Id, created.Id) 923 } 924 if conversations[0].Info.Triple.TopicType != chat1.TopicType_CHAT { 925 t.Fatalf("unexpected topicType in response from GetInboxAndUnboxLocal. %s != %s\n", conversations[0].Info.Triple.TopicType, chat1.TopicType_CHAT) 926 } 927 }) 928 } 929 func TestChatSrvGetInboxNonblockLocalMetadata(t *testing.T) { 930 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 931 ctc := makeChatTestContext(t, "GetInboxNonblockLocalLocalMetadata", 6) 932 defer ctc.cleanup() 933 users := ctc.users() 934 935 numconvs := 5 936 ui := kbtest.NewChatUI() 937 ctc.as(t, users[0]).h.mockChatUI = ui 938 tc := ctc.world.Tcs[users[0].Username] 939 ctx := ctc.as(t, users[0]).startCtx 940 uid := gregor1.UID(users[0].GetUID().ToBytes()) 941 tc.G.UIRouter = kbtest.NewMockUIRouter(ui) 942 tc.ChatG.UIInboxLoader = NewUIInboxLoader(tc.Context()) 943 tc.ChatG.UIInboxLoader.Start(ctx, uid) 944 defer func() { <-tc.ChatG.UIInboxLoader.Stop(ctx) }() 945 946 var firstConv chat1.ConversationInfoLocal 947 switch mt { 948 case chat1.ConversationMembersType_TEAM: 949 firstConv = mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt, users[1:]...) 950 default: 951 } 952 953 // Create a bunch of blank convos 954 oldUILoader := tc.ChatG.UIInboxLoader 955 tc.ChatG.UIInboxLoader = types.DummyUIInboxLoader{} 956 convs := make(map[chat1.ConvIDStr]bool) 957 for i := 0; i < numconvs; i++ { 958 var created chat1.ConversationInfoLocal 959 switch mt { 960 case chat1.ConversationMembersType_TEAM: 961 topicName := fmt.Sprintf("%d", i+1) 962 ncres, err := ctc.as(t, users[0]).chatLocalHandler().NewConversationLocal(ctx, 963 chat1.NewConversationLocalArg{ 964 TlfName: firstConv.TlfName, 965 TopicName: &topicName, 966 TopicType: chat1.TopicType_CHAT, 967 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 968 MembersType: chat1.ConversationMembersType_TEAM, 969 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 970 }) 971 require.NoError(t, err) 972 created = ncres.Conv.Info 973 default: 974 created = mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 975 mt, ctc.as(t, users[i+1]).user()) 976 } 977 t.Logf("created: %s", created.Id) 978 convs[created.Id.ConvIDStr()] = true 979 980 mustPostLocalForTest(t, ctc, users[i+1], created, 981 chat1.NewMessageBodyWithText(chat1.MessageText{ 982 Body: fmt.Sprintf("%d", i+1), 983 })) 984 time.Sleep(100 * time.Millisecond) 985 } 986 987 tc.ChatG.UIInboxLoader = oldUILoader 988 _, err := ctc.as(t, users[0]).chatLocalHandler().GetInboxNonblockLocal(ctx, 989 chat1.GetInboxNonblockLocalArg{ 990 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 991 }, 992 ) 993 require.NoError(t, err) 994 995 // Account for initial team convo 996 switch mt { 997 case chat1.ConversationMembersType_TEAM: 998 numconvs++ 999 default: 1000 } 1001 1002 select { 1003 case ibox := <-ui.InboxCb: 1004 require.NotNil(t, ibox.InboxRes, "nil inbox") 1005 require.Equal(t, numconvs, len(ibox.InboxRes.Items)) 1006 for _, conv := range ibox.InboxRes.Items { 1007 require.Nil(t, conv.LocalMetadata) 1008 } 1009 case <-time.After(20 * time.Second): 1010 require.Fail(t, "no inbox received") 1011 } 1012 // Get all convos 1013 for i := 0; i < numconvs; i++ { 1014 select { 1015 case conv := <-ui.InboxCb: 1016 require.NotNil(t, conv.ConvRes, "no conv") 1017 delete(convs, conv.ConvID.ConvIDStr()) 1018 case <-time.After(20 * time.Second): 1019 require.Fail(t, "no conv received") 1020 } 1021 } 1022 require.Equal(t, 0, len(convs), "didn't get all convs") 1023 1024 _, err = ctc.as(t, users[0]).chatLocalHandler().GetInboxNonblockLocal(ctx, 1025 chat1.GetInboxNonblockLocalArg{ 1026 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 1027 }, 1028 ) 1029 require.NoError(t, err) 1030 1031 select { 1032 case ibox := <-ui.InboxCb: 1033 require.NotNil(t, ibox.InboxRes, "nil inbox") 1034 require.Equal(t, numconvs, len(ibox.InboxRes.Items)) 1035 sort.Slice(ibox.InboxRes.Items, func(i, j int) bool { 1036 return ibox.InboxRes.Items[i].Time.After(ibox.InboxRes.Items[j].Time) 1037 }) 1038 for index, conv := range ibox.InboxRes.Items { 1039 snippet := "<nil>" 1040 if conv.LocalMetadata != nil { 1041 snippet = conv.LocalMetadata.Snippet 1042 } 1043 t.Logf("metadata snippet: index: %d snippet: %s time: %v", index, snippet, 1044 conv.Time) 1045 } 1046 index := 0 1047 for _, conv := range ibox.InboxRes.Items { 1048 require.NotNil(t, conv.LocalMetadata) 1049 switch mt { 1050 case chat1.ConversationMembersType_TEAM: 1051 if conv.ConvID == firstConv.Id.ConvIDStr() { 1052 continue 1053 } 1054 if strings.Contains(conv.LocalMetadata.Snippet, "created a new channel") { 1055 continue 1056 } 1057 require.Equal(t, fmt.Sprintf("%d", numconvs-index-1), conv.LocalMetadata.ChannelName) 1058 require.Equal(t, 1059 fmt.Sprintf("%s: %d", users[numconvs-index-1].Username, numconvs-index-1), 1060 conv.LocalMetadata.Snippet) 1061 require.Zero(t, len(conv.LocalMetadata.WriterNames)) 1062 default: 1063 require.Equal(t, fmt.Sprintf("%d", numconvs-index), conv.LocalMetadata.Snippet) 1064 require.Equal(t, 2, len(conv.LocalMetadata.WriterNames)) 1065 } 1066 index++ 1067 } 1068 case <-time.After(20 * time.Second): 1069 require.Fail(t, "no inbox received") 1070 } 1071 // Get all convos 1072 for i := 0; i < numconvs; i++ { 1073 select { 1074 case conv := <-ui.InboxCb: 1075 require.NotNil(t, conv.ConvRes, "no conv") 1076 delete(convs, conv.ConvID.ConvIDStr()) 1077 case <-time.After(20 * time.Second): 1078 require.Fail(t, "no conv received") 1079 } 1080 } 1081 require.Equal(t, 0, len(convs), "didnt get all convs") 1082 }) 1083 } 1084 1085 func TestChatSrvGetInboxNonblock(t *testing.T) { 1086 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 1087 ctc := makeChatTestContext(t, "GetInboxNonblockLocal", 6) 1088 defer ctc.cleanup() 1089 users := ctc.users() 1090 1091 numconvs := 5 1092 ui := kbtest.NewChatUI() 1093 ctc.as(t, users[0]).h.mockChatUI = ui 1094 tc := ctc.world.Tcs[users[0].Username] 1095 tc.G.UIRouter = kbtest.NewMockUIRouter(ui) 1096 ctx := ctc.as(t, users[0]).startCtx 1097 uid := gregor1.UID(users[0].GetUID().ToBytes()) 1098 tc.G.UIRouter = kbtest.NewMockUIRouter(ui) 1099 tc.ChatG.UIInboxLoader = NewUIInboxLoader(tc.Context()) 1100 tc.ChatG.UIInboxLoader.Start(ctx, uid) 1101 defer func() { <-tc.ChatG.UIInboxLoader.Stop(ctx) }() 1102 1103 // Create a bunch of blank convos 1104 convs := make(map[chat1.ConvIDStr]bool) 1105 for i := 0; i < numconvs; i++ { 1106 created := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 1107 mt, ctc.as(t, users[i+1]).user()) 1108 convs[created.Id.ConvIDStr()] = true 1109 } 1110 1111 t.Logf("blank convos test") 1112 // Get inbox (should be blank) 1113 _, err := ctc.as(t, users[0]).chatLocalHandler().GetInboxNonblockLocal(ctx, 1114 chat1.GetInboxNonblockLocalArg{ 1115 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 1116 }, 1117 ) 1118 require.NoError(t, err) 1119 select { 1120 case ibox := <-ui.InboxCb: 1121 require.NotNil(t, ibox.InboxRes, "nil inbox") 1122 switch mt { 1123 case chat1.ConversationMembersType_TEAM: 1124 require.Equal(t, numconvs, len(ibox.InboxRes.Items)) 1125 default: 1126 require.Zero(t, len(ibox.InboxRes.Items), "wrong size inbox") 1127 } 1128 case <-time.After(20 * time.Second): 1129 require.Fail(t, "no inbox received") 1130 } 1131 // Get all convos 1132 for i := 0; i < numconvs; i++ { 1133 select { 1134 case conv := <-ui.InboxCb: 1135 require.NotNil(t, conv.ConvRes, "no conv") 1136 delete(convs, conv.ConvID.ConvIDStr()) 1137 case <-time.After(20 * time.Second): 1138 require.Fail(t, "no conv received") 1139 } 1140 } 1141 require.Equal(t, 0, len(convs), "didnt get all convs") 1142 1143 // Send a bunch of messages 1144 t.Logf("messages in convos test") 1145 convs = make(map[chat1.ConvIDStr]bool) 1146 for i := 0; i < numconvs; i++ { 1147 conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 1148 mt, ctc.as(t, users[i+1]).user()) 1149 convs[conv.Id.ConvIDStr()] = true 1150 1151 _, err := ctc.as(t, users[0]).chatLocalHandler().PostLocal(ctx, chat1.PostLocalArg{ 1152 ConversationID: conv.Id, 1153 Msg: chat1.MessagePlaintext{ 1154 ClientHeader: chat1.MessageClientHeader{ 1155 Conv: conv.Triple, 1156 MessageType: chat1.MessageType_TEXT, 1157 TlfName: conv.TlfName, 1158 }, 1159 MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{ 1160 Body: "HI", 1161 }), 1162 }, 1163 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 1164 }) 1165 require.NoError(t, err) 1166 } 1167 1168 // Get inbox (should be blank) 1169 _, err = ctc.as(t, users[0]).chatLocalHandler().GetInboxNonblockLocal(ctx, 1170 chat1.GetInboxNonblockLocalArg{ 1171 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 1172 }, 1173 ) 1174 require.NoError(t, err) 1175 select { 1176 case ibox := <-ui.InboxCb: 1177 require.NotNil(t, ibox.InboxRes, "nil inbox") 1178 require.Equal(t, len(convs), len(ibox.InboxRes.Items), "wrong size inbox") 1179 case <-time.After(20 * time.Second): 1180 require.Fail(t, "no inbox received") 1181 } 1182 // Get all convos 1183 for i := 0; i < numconvs; i++ { 1184 select { 1185 case conv := <-ui.InboxCb: 1186 require.NotNil(t, conv.ConvRes, "no conv") 1187 delete(convs, conv.ConvID.ConvIDStr()) 1188 case <-time.After(20 * time.Second): 1189 require.Fail(t, "no conv received") 1190 } 1191 } 1192 require.Equal(t, 0, len(convs), "didnt get all convs") 1193 1194 // Make sure there is nothing left 1195 select { 1196 case <-ui.InboxCb: 1197 require.Fail(t, "should have drained channel") 1198 default: 1199 } 1200 }) 1201 } 1202 1203 func TestChatSrvGetInboxAndUnboxLocalTlfName(t *testing.T) { 1204 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 1205 ctc := makeChatTestContext(t, "ResolveConversationLocal", 2) 1206 defer ctc.cleanup() 1207 users := ctc.users() 1208 1209 created := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 1210 mt, ctc.as(t, users[1]).user()) 1211 1212 var name string 1213 switch mt { 1214 case chat1.ConversationMembersType_KBFS, chat1.ConversationMembersType_IMPTEAMNATIVE, 1215 chat1.ConversationMembersType_IMPTEAMUPGRADE: 1216 name = ctc.as(t, users[1]).user().Username + "," + ctc.as(t, users[0]).user().Username // not canonical 1217 case chat1.ConversationMembersType_TEAM: 1218 name = ctc.teamCache[teamKey(ctc.users())] 1219 } 1220 1221 visibility := keybase1.TLFVisibility_PRIVATE 1222 ctx := ctc.as(t, users[0]).startCtx 1223 gilres, err := ctc.as(t, users[0]).chatLocalHandler().GetInboxAndUnboxLocal(ctx, chat1.GetInboxAndUnboxLocalArg{ 1224 Query: &chat1.GetInboxLocalQuery{ 1225 Name: &chat1.NameQuery{ 1226 Name: name, 1227 MembersType: mt, 1228 }, 1229 TlfVisibility: &visibility, 1230 }, 1231 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 1232 }) 1233 require.NoError(t, err) 1234 conversations := gilres.Conversations 1235 require.Equal(t, 1, len(conversations)) 1236 tc := ctc.world.Tcs[users[0].Username] 1237 uid := users[0].User.GetUID().ToBytes() 1238 conv, err := utils.GetUnverifiedConv(ctx, tc.Context(), uid, created.Id, 1239 types.InboxSourceDataSourceRemoteOnly) 1240 require.NoError(t, err) 1241 require.Equal(t, conversations[0].Info.TlfName, conv.Conv.MaxMsgSummaries[0].TlfName) 1242 require.Equal(t, conversations[0].Info.Id, created.Id) 1243 require.Equal(t, chat1.TopicType_CHAT, conversations[0].Info.Triple.TopicType) 1244 }) 1245 } 1246 1247 func TestChatSrvPostLocal(t *testing.T) { 1248 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 1249 ctc := makeChatTestContext(t, "PostLocal", 2) 1250 defer ctc.cleanup() 1251 users := ctc.users() 1252 1253 created := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 1254 mt, ctc.as(t, users[1]).user()) 1255 1256 // un-canonicalize TLF name 1257 t.Logf("TLF name: %s", created.TlfName) 1258 parts := strings.Split(created.TlfName, ",") 1259 sort.Sort(sort.Reverse(sort.StringSlice(parts))) 1260 created.TlfName = strings.Join(parts, ",") 1261 1262 mustPostLocalForTest(t, ctc, users[0], created, chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hello!"})) 1263 1264 // we just posted this message, so should be the first one. 1265 uid := users[0].User.GetUID().ToBytes() 1266 tc := ctc.world.Tcs[users[0].Username] 1267 ctx := ctc.as(t, users[0]).startCtx 1268 tv, err := tc.Context().ConvSource.Pull(ctx, created.Id, uid, chat1.GetThreadReason_GENERAL, nil, 1269 nil, nil) 1270 require.NoError(t, err) 1271 t.Logf("nmsg: %v", len(tv.Messages)) 1272 require.NotZero(t, len(tv.Messages)) 1273 msg := tv.Messages[0] 1274 1275 if mt == chat1.ConversationMembersType_KBFS { 1276 require.NotEqual(t, created.TlfName, msg.Valid().ClientHeader.TlfName) 1277 } 1278 require.NotZero(t, len(msg.Valid().ClientHeader.Sender.Bytes())) 1279 require.NotZero(t, len(msg.Valid().ClientHeader.SenderDevice.Bytes())) 1280 1281 t.Logf("try headline specific RPC interface") 1282 res, err := ctc.as(t, users[0]).chatLocalHandler().PostHeadline(ctx, chat1.PostHeadlineArg{ 1283 ConversationID: created.Id, 1284 TlfName: created.TlfName, 1285 TlfPublic: false, 1286 Headline: "HI", 1287 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 1288 }) 1289 require.NoError(t, err) 1290 t.Logf("headline -> msgid:%v", res.MessageID) 1291 tv, err = tc.Context().ConvSource.Pull(ctx, created.Id, uid, chat1.GetThreadReason_GENERAL, nil, 1292 nil, nil) 1293 require.NoError(t, err) 1294 t.Logf("nmsg: %v", len(tv.Messages)) 1295 require.NotZero(t, len(tv.Messages)) 1296 msg = tv.Messages[0] 1297 require.Equal(t, chat1.MessageType_HEADLINE, msg.GetMessageType()) 1298 1299 t.Logf("try delete-history RPC interface") 1300 _, err = ctc.as(t, users[0]).chatLocalHandler().PostDeleteHistoryByAge(ctx, chat1.PostDeleteHistoryByAgeArg{ 1301 ConversationID: created.Id, 1302 TlfName: created.TlfName, 1303 TlfPublic: false, 1304 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 1305 Age: 0, 1306 }) 1307 require.NoError(t, err) 1308 tv, err = tc.Context().ConvSource.Pull(ctx, created.Id, uid, chat1.GetThreadReason_GENERAL, nil, 1309 nil, nil) 1310 require.NoError(t, err) 1311 t.Logf("nmsg: %v", len(tv.Messages)) 1312 // Teams don't use the remote mock. So PostDeleteHistoryByAge won't 1313 // have gotten a good answer from GetMessageBefore. 1314 if useRemoteMock { 1315 t.Logf("check that the deletable messages are gone") 1316 for _, m := range tv.Messages { 1317 require.False(t, chat1.IsDeletableByDeleteHistory(m.GetMessageType()), 1318 "deletable message found: %v %v", m.GetMessageID(), m.GetMessageType()) 1319 } 1320 } 1321 }) 1322 } 1323 1324 func TestChatSrvPostLocalAtMention(t *testing.T) { 1325 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 1326 ctc := makeChatTestContext(t, "PostLocal", 2) 1327 defer ctc.cleanup() 1328 users := ctc.users() 1329 1330 switch mt { 1331 case chat1.ConversationMembersType_KBFS, chat1.ConversationMembersType_IMPTEAMNATIVE, 1332 chat1.ConversationMembersType_IMPTEAMUPGRADE: 1333 return 1334 } 1335 1336 listener := newServerChatListener() 1337 ctc.as(t, users[1]).h.G().NotifyRouter.AddListener(listener) 1338 ctx := ctc.as(t, users[0]).startCtx 1339 1340 created := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 1341 mt, ctc.as(t, users[1]).user()) 1342 1343 text := fmt.Sprintf("@%s", users[1].Username) 1344 mustPostLocalForTest(t, ctc, users[0], created, 1345 chat1.NewMessageBodyWithText(chat1.MessageText{Body: text})) 1346 1347 select { 1348 case info := <-listener.newMessageRemote: 1349 require.True(t, info.Message.IsValid()) 1350 require.Equal(t, chat1.MessageType_TEXT, info.Message.GetMessageType()) 1351 require.Equal(t, 1, len(info.Message.Valid().AtMentions)) 1352 require.Equal(t, users[1].Username, info.Message.Valid().AtMentions[0]) 1353 require.True(t, info.DisplayDesktopNotification) 1354 require.NotEqual(t, "", info.DesktopNotificationSnippet) 1355 case <-time.After(20 * time.Second): 1356 require.Fail(t, "no new message") 1357 } 1358 1359 // Test that edits work 1360 postRes, err := ctc.as(t, users[0]).chatLocalHandler().PostLocal(ctx, chat1.PostLocalArg{ 1361 ConversationID: created.Id, 1362 Msg: chat1.MessagePlaintext{ 1363 ClientHeader: chat1.MessageClientHeader{ 1364 Conv: created.Triple, 1365 MessageType: chat1.MessageType_TEXT, 1366 TlfName: created.TlfName, 1367 }, 1368 MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{ 1369 Body: "HI", 1370 }), 1371 }, 1372 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 1373 }) 1374 require.NoError(t, err) 1375 consumeNewMsgRemote(t, listener, chat1.MessageType_TEXT) 1376 _, err = ctc.as(t, users[0]).chatLocalHandler().PostLocal(ctx, chat1.PostLocalArg{ 1377 ConversationID: created.Id, 1378 Msg: chat1.MessagePlaintext{ 1379 ClientHeader: chat1.MessageClientHeader{ 1380 Conv: created.Triple, 1381 MessageType: chat1.MessageType_EDIT, 1382 TlfName: created.TlfName, 1383 Supersedes: postRes.MessageID, 1384 }, 1385 MessageBody: chat1.NewMessageBodyWithEdit(chat1.MessageEdit{ 1386 MessageID: postRes.MessageID, 1387 Body: fmt.Sprintf("@%s", users[1].Username), 1388 }), 1389 }, 1390 }) 1391 require.NoError(t, err) 1392 select { 1393 case info := <-listener.newMessageRemote: 1394 require.True(t, info.Message.IsValid()) 1395 require.Equal(t, chat1.MessageType_EDIT, info.Message.GetMessageType()) 1396 require.Equal(t, 1, len(info.Message.Valid().AtMentions)) 1397 require.Equal(t, users[1].Username, info.Message.Valid().AtMentions[0]) 1398 case <-time.After(20 * time.Second): 1399 require.Fail(t, "no new message") 1400 } 1401 threadRes, err := ctc.as(t, users[1]).chatLocalHandler().GetThreadLocal(ctx, chat1.GetThreadLocalArg{ 1402 ConversationID: created.Id, 1403 Query: &chat1.GetThreadQuery{ 1404 MessageTypes: []chat1.MessageType{chat1.MessageType_TEXT}, 1405 }, 1406 }) 1407 filterOutJourneycards(&threadRes.Thread) 1408 require.NoError(t, err) 1409 require.Equal(t, 2, len(threadRes.Thread.Messages)) 1410 require.True(t, threadRes.Thread.Messages[0].IsValid()) 1411 require.Equal(t, 1, len(threadRes.Thread.Messages[0].Valid().AtMentionUsernames)) 1412 require.Equal(t, users[1].Username, threadRes.Thread.Messages[0].Valid().AtMentionUsernames[0]) 1413 1414 // Make sure @channel works 1415 mustPostLocalForTest(t, ctc, users[0], created, 1416 chat1.NewMessageBodyWithText(chat1.MessageText{Body: "@channel"})) 1417 select { 1418 case info := <-listener.newMessageRemote: 1419 require.True(t, info.Message.IsValid()) 1420 require.Equal(t, chat1.MessageType_TEXT, info.Message.GetMessageType()) 1421 require.Zero(t, len(info.Message.Valid().AtMentions)) 1422 require.Equal(t, chat1.ChannelMention_ALL, info.Message.Valid().ChannelMention) 1423 require.True(t, info.DisplayDesktopNotification) 1424 require.NotEqual(t, "", info.DesktopNotificationSnippet) 1425 case <-time.After(20 * time.Second): 1426 require.Fail(t, "no new message") 1427 } 1428 1429 // Test that system messages do the right thing 1430 subBody := chat1.NewMessageSystemWithAddedtoteam(chat1.MessageSystemAddedToTeam{ 1431 Addee: users[1].Username, 1432 }) 1433 mustPostLocalForTest(t, ctc, users[0], created, chat1.NewMessageBodyWithSystem(subBody)) 1434 select { 1435 case info := <-listener.newMessageRemote: 1436 require.True(t, info.Message.IsValid()) 1437 require.Equal(t, chat1.MessageType_SYSTEM, info.Message.GetMessageType()) 1438 require.Equal(t, 1, len(info.Message.Valid().AtMentions)) 1439 require.Equal(t, users[1].Username, info.Message.Valid().AtMentions[0]) 1440 require.Equal(t, chat1.ChannelMention_NONE, info.Message.Valid().ChannelMention) 1441 require.True(t, info.DisplayDesktopNotification) 1442 require.NotEqual(t, "", info.DesktopNotificationSnippet) 1443 case <-time.After(20 * time.Second): 1444 require.Fail(t, "no new message") 1445 } 1446 1447 // Test that flip messages do the right thing 1448 mustPostLocalForTest(t, ctc, users[0], created, chat1.NewMessageBodyWithFlip(chat1.MessageFlip{ 1449 Text: fmt.Sprintf("/flip @%s", users[1].Username), 1450 })) 1451 select { 1452 case info := <-listener.newMessageRemote: 1453 require.True(t, info.Message.IsValid()) 1454 require.Equal(t, chat1.MessageType_FLIP, info.Message.GetMessageType()) 1455 require.Equal(t, 1, len(info.Message.Valid().AtMentions)) 1456 require.Equal(t, users[1].Username, info.Message.Valid().AtMentions[0]) 1457 require.Equal(t, chat1.ChannelMention_NONE, info.Message.Valid().ChannelMention) 1458 require.True(t, info.DisplayDesktopNotification) 1459 require.NotEqual(t, "", info.DesktopNotificationSnippet) 1460 case <-time.After(20 * time.Second): 1461 require.Fail(t, "no new message") 1462 } 1463 }) 1464 } 1465 1466 func TestChatSrvPostLocalLengthLimit(t *testing.T) { 1467 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 1468 ctc := makeChatTestContext(t, "PostLocal", 2) 1469 defer ctc.cleanup() 1470 users := ctc.users() 1471 1472 var created chat1.ConversationInfoLocal 1473 var dev chat1.ConversationInfoLocal 1474 switch mt { 1475 case chat1.ConversationMembersType_TEAM: 1476 firstConv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 1477 mt, ctc.as(t, users[1]).user()) 1478 topicName := "MIKE" 1479 ncres, err := ctc.as(t, users[0]).chatLocalHandler().NewConversationLocal(context.TODO(), 1480 chat1.NewConversationLocalArg{ 1481 TlfName: firstConv.TlfName, 1482 TopicName: &topicName, 1483 TopicType: chat1.TopicType_CHAT, 1484 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 1485 MembersType: chat1.ConversationMembersType_TEAM, 1486 }) 1487 require.NoError(t, err) 1488 created = ncres.Conv.Info 1489 default: 1490 created = mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 1491 mt, ctc.as(t, users[1]).user()) 1492 } 1493 dev = mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_DEV, 1494 mt, ctc.as(t, users[1]).user()) 1495 1496 // text msg 1497 maxTextBody := strings.Repeat(".", msgchecker.TextMessageMaxLength) 1498 _, err := postLocalForTest(t, ctc, users[0], created, chat1.NewMessageBodyWithText(chat1.MessageText{Body: maxTextBody})) 1499 require.NoError(t, err) 1500 _, err = postLocalForTest(t, ctc, users[0], created, chat1.NewMessageBodyWithText(chat1.MessageText{Body: maxTextBody + "!"})) 1501 require.Error(t, err) 1502 1503 // dev text 1504 maxDevTextBody := strings.Repeat(".", msgchecker.DevTextMessageMaxLength) 1505 _, err = postLocalForTest(t, ctc, users[0], dev, 1506 chat1.NewMessageBodyWithText(chat1.MessageText{Body: maxDevTextBody})) 1507 require.NoError(t, err) 1508 _, err = postLocalForTest(t, ctc, users[0], dev, 1509 chat1.NewMessageBodyWithText(chat1.MessageText{Body: maxDevTextBody + "!"})) 1510 require.Error(t, err) 1511 1512 // headline 1513 maxHeadlineBody := strings.Repeat(".", msgchecker.HeadlineMaxLength) 1514 _, err = postLocalForTest(t, ctc, users[0], created, chat1.NewMessageBodyWithHeadline(chat1.MessageHeadline{Headline: maxHeadlineBody})) 1515 require.NoError(t, err) 1516 _, err = postLocalForTest(t, ctc, users[0], created, chat1.NewMessageBodyWithHeadline(chat1.MessageHeadline{Headline: maxHeadlineBody + "!"})) 1517 require.Error(t, err) 1518 1519 // topic 1520 maxTopicBody := strings.Repeat("a", msgchecker.TopicMaxLength) 1521 _, err = postLocalForTest(t, ctc, users[0], created, chat1.NewMessageBodyWithMetadata(chat1.MessageConversationMetadata{ConversationTitle: maxTopicBody})) 1522 require.NoError(t, err) 1523 _, err = postLocalForTest(t, ctc, users[0], created, chat1.NewMessageBodyWithMetadata(chat1.MessageConversationMetadata{ConversationTitle: maxTopicBody + "a"})) 1524 require.Error(t, err) 1525 _, err = postLocalForTest(t, ctc, users[0], created, chat1.NewMessageBodyWithMetadata(chat1.MessageConversationMetadata{ConversationTitle: "#mike"})) 1526 require.Error(t, err) 1527 _, err = postLocalForTest(t, ctc, users[0], created, chat1.NewMessageBodyWithMetadata(chat1.MessageConversationMetadata{ConversationTitle: "mii.ke"})) 1528 require.Error(t, err) 1529 1530 // request payment 1531 maxPaymentNote := strings.Repeat(".", msgchecker.RequestPaymentTextMaxLength) 1532 _, err = postLocalForTest(t, ctc, users[0], created, chat1.NewMessageBodyWithRequestpayment( 1533 chat1.MessageRequestPayment{ 1534 RequestID: stellar1.KeybaseRequestID("dummy id"), 1535 Note: maxPaymentNote, 1536 })) 1537 require.NoError(t, err) 1538 _, err = postLocalForTest(t, ctc, users[0], created, chat1.NewMessageBodyWithRequestpayment( 1539 chat1.MessageRequestPayment{ 1540 RequestID: stellar1.KeybaseRequestID("dummy id"), 1541 Note: maxPaymentNote + "!", 1542 })) 1543 require.Error(t, err) 1544 }) 1545 } 1546 1547 func TestChatSrvGetThreadLocal(t *testing.T) { 1548 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 1549 ctc := makeChatTestContext(t, "GetThreadLocal", 2) 1550 defer ctc.cleanup() 1551 users := ctc.users() 1552 1553 created := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 1554 mt, ctc.as(t, users[1]).user()) 1555 msgID1, err := postLocalForTest(t, ctc, users[0], created, 1556 chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hello!"})) 1557 require.NoError(t, err) 1558 t.Logf("msgID1: %d", msgID1.MessageID) 1559 1560 ctx := ctc.as(t, users[0]).startCtx 1561 tvres, err := ctc.as(t, users[0]).chatLocalHandler().GetThreadLocal(ctx, chat1.GetThreadLocalArg{ 1562 ConversationID: created.Id, 1563 Query: &chat1.GetThreadQuery{ 1564 MessageTypes: []chat1.MessageType{chat1.MessageType_TEXT}, 1565 }, 1566 }) 1567 require.NoError(t, err) 1568 1569 tv := tvres.Thread 1570 expectedMessages := 1 1571 require.Len(t, tv.Messages, expectedMessages, 1572 "unexpected response from GetThreadLocal . number of messages") 1573 require.Equal(t, "hello!", tv.Messages[0].Valid().MessageBody.Text().Body) 1574 1575 // Test message ID control 1576 plres, err := postLocalForTest(t, ctc, users[0], created, 1577 chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hello!"})) 1578 require.NoError(t, err) 1579 t.Logf("msgID2: %d", plres.MessageID) 1580 msgID3, err := postLocalForTest(t, ctc, users[0], created, 1581 chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hello!"})) 1582 require.NoError(t, err) 1583 t.Logf("msgID3: %d", msgID3.MessageID) 1584 tvres, err = ctc.as(t, users[0]).chatLocalHandler().GetThreadLocal(ctx, chat1.GetThreadLocalArg{ 1585 ConversationID: created.Id, 1586 Query: &chat1.GetThreadQuery{ 1587 MessageTypes: []chat1.MessageType{chat1.MessageType_TEXT}, 1588 MessageIDControl: &chat1.MessageIDControl{ 1589 Pivot: &plres.MessageID, 1590 Mode: chat1.MessageIDControlMode_NEWERMESSAGES, 1591 Num: 1, 1592 }, 1593 }, 1594 }) 1595 require.NoError(t, err) 1596 require.Equal(t, 1, len(tvres.Thread.Messages)) 1597 require.Equal(t, msgID3.MessageID, tvres.Thread.Messages[0].GetMessageID()) 1598 tvres, err = ctc.as(t, users[0]).chatLocalHandler().GetThreadLocal(ctx, chat1.GetThreadLocalArg{ 1599 ConversationID: created.Id, 1600 Query: &chat1.GetThreadQuery{ 1601 MessageTypes: []chat1.MessageType{chat1.MessageType_TEXT}, 1602 MessageIDControl: &chat1.MessageIDControl{ 1603 Pivot: &plres.MessageID, 1604 Mode: chat1.MessageIDControlMode_OLDERMESSAGES, 1605 Num: 1, 1606 }, 1607 }, 1608 }) 1609 require.NoError(t, err) 1610 require.Equal(t, 1, len(tvres.Thread.Messages)) 1611 require.Equal(t, msgID1.MessageID, tvres.Thread.Messages[0].GetMessageID()) 1612 1613 tvres, err = ctc.as(t, users[0]).chatLocalHandler().GetThreadLocal(ctx, chat1.GetThreadLocalArg{ 1614 ConversationID: created.Id, 1615 Query: &chat1.GetThreadQuery{ 1616 MessageTypes: []chat1.MessageType{chat1.MessageType_TEXT}, 1617 MessageIDControl: &chat1.MessageIDControl{ 1618 Num: 2, 1619 }, 1620 }, 1621 }) 1622 require.NoError(t, err) 1623 require.Equal(t, 2, len(tvres.Thread.Messages)) 1624 require.Equal(t, msgID3.MessageID, tvres.Thread.Messages[0].GetMessageID()) 1625 require.Equal(t, plres.MessageID, tvres.Thread.Messages[1].GetMessageID()) 1626 }) 1627 } 1628 1629 func TestChatSrvGetThreadLocalMarkAsRead(t *testing.T) { 1630 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 1631 // TODO: investigate LocalDb in TestContext and make it behave the same way 1632 // as in real context / docker tests. This test should fail without the fix 1633 // in ConvSource for marking is read, but does not currently. 1634 ctc := makeChatTestContext(t, "GetThreadLocalMarkAsRead", 2) 1635 defer ctc.cleanup() 1636 users := ctc.users() 1637 1638 withUser1 := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 1639 mt, ctc.as(t, users[1]).user()) 1640 1641 mustPostLocalForTest(t, ctc, users[0], withUser1, chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hello0"})) 1642 mustPostLocalForTest(t, ctc, users[1], withUser1, chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hello1"})) 1643 mustPostLocalForTest(t, ctc, users[0], withUser1, chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hello2"})) 1644 1645 ctx := ctc.as(t, users[0]).startCtx 1646 res, err := ctc.as(t, users[0]).chatLocalHandler().GetInboxSummaryForCLILocal(ctx, chat1.GetInboxSummaryForCLILocalQuery{ 1647 TopicType: chat1.TopicType_CHAT, 1648 }) 1649 require.NoError(t, err) 1650 require.Equal(t, 1, len(res.Conversations)) 1651 require.Equal(t, res.Conversations[0].Info.Id.String(), withUser1.Id.String()) 1652 var found bool 1653 for _, m := range res.Conversations[0].MaxMessages { 1654 if m.GetMessageType() == chat1.MessageType_TEXT { 1655 require.NotEqual(t, res.Conversations[0].ReaderInfo.ReadMsgid, m.GetMessageID()) 1656 found = true 1657 break 1658 } 1659 } 1660 require.True(t, found) 1661 1662 // Do a get thread local without requesting marking as read first. This 1663 // should cause HybridConversationSource to cache the thread. Then we do 1664 // another call requesting marking as read before checking if the thread is 1665 // marked as read. This is to ensure that when the query requests for a 1666 // mark-as-read, and the thread gets a cache hit, the 1667 // HybridConversationSource should not just return the thread, but also send 1668 // a MarkAsRead RPC to remote. (Currently this is done in 1669 // HybridConversationSource.Pull) 1670 // 1671 // TODO: This doesn't make sense! In integration tests, this isn't necessary 1672 // since a Pull() is called during PostLocal (when populating the Prev 1673 // pointers). However it seems in this test, it doesn't do so. This first 1674 // GetThreadLocal always gets a cache miss, resulting a remote call. If 1675 // PostLocal had worked like integration, this shouldn't be necessary. We 1676 // should find out where the problem is and fix it! Although after that fix, 1677 // this should probably still stay here just in case. 1678 _, err = ctc.as(t, users[0]).chatLocalHandler().GetThreadLocal(ctx, chat1.GetThreadLocalArg{ 1679 ConversationID: withUser1.Id, 1680 Query: &chat1.GetThreadQuery{ 1681 MarkAsRead: false, 1682 }, 1683 }) 1684 require.NoError(t, err) 1685 tv, err := ctc.as(t, users[0]).chatLocalHandler().GetThreadLocal(ctx, chat1.GetThreadLocalArg{ 1686 ConversationID: withUser1.Id, 1687 Query: &chat1.GetThreadQuery{ 1688 MarkAsRead: true, 1689 }, 1690 }) 1691 require.NoError(t, err) 1692 1693 expectedMessages := 4 // 3 messages and 1 TLF 1694 require.Len(t, tv.Thread.Messages, expectedMessages, 1695 "unexpected response from GetThreadLocal . number of messages") 1696 1697 res, err = ctc.as(t, users[0]).chatLocalHandler().GetInboxSummaryForCLILocal(ctx, chat1.GetInboxSummaryForCLILocalQuery{ 1698 TopicType: chat1.TopicType_CHAT, 1699 }) 1700 require.NoError(t, err) 1701 require.Equal(t, 1, len(res.Conversations)) 1702 found = false 1703 for _, m := range res.Conversations[0].MaxMessages { 1704 if m.GetMessageType() == chat1.MessageType_TEXT { 1705 require.Equal(t, res.Conversations[0].ReaderInfo.ReadMsgid, 1706 m.GetMessageID()) 1707 found = true 1708 break 1709 } 1710 } 1711 require.True(t, found) 1712 }) 1713 } 1714 1715 type messageSabotagerRemote struct { 1716 chat1.RemoteInterface 1717 } 1718 1719 func (m messageSabotagerRemote) GetThreadRemote(ctx context.Context, arg chat1.GetThreadRemoteArg) (chat1.GetThreadRemoteRes, error) { 1720 res, err := m.RemoteInterface.GetThreadRemote(ctx, arg) 1721 if err != nil { 1722 return res, err 1723 } 1724 if len(res.Thread.Messages) > 0 { 1725 res.Thread.Messages[0].BodyCiphertext.E[0] += 50 1726 } 1727 return res, nil 1728 } 1729 1730 func TestChatSrvGracefulUnboxing(t *testing.T) { 1731 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 1732 ctc := makeChatTestContext(t, "GracefulUnboxing", 2) 1733 defer ctc.cleanup() 1734 users := ctc.users() 1735 1736 listener := newServerChatListener() 1737 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener) 1738 1739 created := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 1740 mt, ctc.as(t, users[1]).user()) 1741 mustPostLocalForTest(t, ctc, users[0], created, chat1.NewMessageBodyWithText(chat1.MessageText{Body: "innocent hello"})) 1742 mustPostLocalForTest(t, ctc, users[0], created, chat1.NewMessageBodyWithText(chat1.MessageText{Body: "evil hello"})) 1743 1744 consumeNewMsgRemote(t, listener, chat1.MessageType_TEXT) 1745 consumeNewMsgRemote(t, listener, chat1.MessageType_TEXT) 1746 1747 // make evil hello evil 1748 tc := ctc.world.Tcs[users[0].Username] 1749 uid := users[0].User.GetUID().ToBytes() 1750 require.NoError(t, tc.Context().ConvSource.Clear(context.TODO(), created.Id, uid, nil)) 1751 1752 ri := ctc.as(t, users[0]).ri 1753 sabRemote := messageSabotagerRemote{RemoteInterface: ri} 1754 tc.Context().UIThreadLoader.(*UIThreadLoader).SetRemoteInterface(func() chat1.RemoteInterface { 1755 return sabRemote 1756 }) 1757 ctx := ctc.as(t, users[0]).startCtx 1758 tv, err := ctc.as(t, users[0]).chatLocalHandler().GetThreadLocal(ctx, chat1.GetThreadLocalArg{ 1759 ConversationID: created.Id, 1760 }) 1761 tc.Context().UIThreadLoader.(*UIThreadLoader).SetRemoteInterface(func() chat1.RemoteInterface { 1762 return ri 1763 }) 1764 if err != nil { 1765 t.Fatalf("GetThreadLocal error: %v", err) 1766 } 1767 1768 require.Len(t, tv.Thread.Messages, 3, 1769 "unexpected response from GetThreadLocal . number of messages") 1770 1771 if tv.Thread.Messages[0].IsValid() || len(tv.Thread.Messages[0].Error().ErrMsg) == 0 { 1772 t.Fatalf("unexpected response from GetThreadLocal. expected an error message from bad msg, got %#+v\n", tv.Thread.Messages[0]) 1773 } 1774 if !tv.Thread.Messages[1].IsValid() || tv.Thread.Messages[1].Valid().MessageBody.Text().Body != "innocent hello" { 1775 t.Fatalf("unexpected response from GetThreadLocal. expected 'innocent hello' got %#+v\n", tv.Thread.Messages[1].Valid()) 1776 } 1777 }) 1778 } 1779 1780 func TestChatSrvGetInboxSummaryForCLILocal(t *testing.T) { 1781 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 1782 ctc := makeChatTestContext(t, "GetInboxSummaryForCLILocal", 4) 1783 defer ctc.cleanup() 1784 users := ctc.users() 1785 1786 withUser1 := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 1787 mt, ctc.as(t, users[1]).user()) 1788 mustPostLocalForTest(t, ctc, users[0], withUser1, chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hello0"})) 1789 mustPostLocalForTest(t, ctc, users[1], withUser1, chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hello1"})) 1790 1791 withUser2 := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 1792 mt, ctc.as(t, users[2]).user()) 1793 mustPostLocalForTest(t, ctc, users[0], withUser2, chat1.NewMessageBodyWithText(chat1.MessageText{Body: fmt.Sprintf("Dude I just said hello to %s!", ctc.as(t, users[2]).user().Username)})) 1794 1795 withUser3 := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 1796 mt, ctc.as(t, users[3]).user()) 1797 mustPostLocalForTest(t, ctc, users[0], withUser3, chat1.NewMessageBodyWithText(chat1.MessageText{Body: "O_O"})) 1798 1799 withUser12 := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 1800 mt, ctc.as(t, users[1]).user(), ctc.as(t, users[2]).user()) 1801 mustPostLocalForTest(t, ctc, users[0], withUser12, chat1.NewMessageBodyWithText(chat1.MessageText{Body: "O_O"})) 1802 1803 withUser123 := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 1804 mt, ctc.as(t, users[1]).user(), ctc.as(t, users[2]).user(), ctc.as(t, users[3]).user()) 1805 mustPostLocalForTest(t, ctc, users[0], withUser123, chat1.NewMessageBodyWithText(chat1.MessageText{Body: "O_O"})) 1806 1807 ctx := ctc.as(t, users[0]).startCtx 1808 res, err := ctc.as(t, users[0]).chatLocalHandler().GetInboxSummaryForCLILocal(ctx, chat1.GetInboxSummaryForCLILocalQuery{ 1809 After: "1d", 1810 TopicType: chat1.TopicType_CHAT, 1811 }) 1812 if err != nil { 1813 t.Fatalf("GetInboxSummaryForCLILocal error: %v", err) 1814 } 1815 if len(res.Conversations) != 5 { 1816 t.Fatalf("unexpected response from GetInboxSummaryForCLILocal . expected 3 items, got %d\n", len(res.Conversations)) 1817 } 1818 if !res.Conversations[0].Info.Id.Eq(withUser123.Id) { 1819 t.Fatalf("unexpected response from GetInboxSummaryForCLILocal; newest updated conversation is not the first in response.\n") 1820 } 1821 // TODO: fix this when merging master back in... (what?) 1822 expectedMessages := 2 1823 require.Len(t, res.Conversations[0].MaxMessages, expectedMessages, 1824 "unexpected response from GetInboxSummaryForCLILocal . number of messages in the first conversation") 1825 1826 res, err = ctc.as(t, users[0]).chatLocalHandler().GetInboxSummaryForCLILocal(ctx, chat1.GetInboxSummaryForCLILocalQuery{ 1827 ActivitySortedLimit: 2, 1828 TopicType: chat1.TopicType_CHAT, 1829 }) 1830 if err != nil { 1831 t.Fatalf("GetInboxSummaryForCLILocal error: %v", err) 1832 } 1833 if len(res.Conversations) != 2 { 1834 t.Fatalf("unexpected response from GetInboxSummaryForCLILocal . expected 2 items, got %d\n", len(res.Conversations)) 1835 } 1836 1837 res, err = ctc.as(t, users[0]).chatLocalHandler().GetInboxSummaryForCLILocal(ctx, chat1.GetInboxSummaryForCLILocalQuery{ 1838 ActivitySortedLimit: 2, 1839 TopicType: chat1.TopicType_CHAT, 1840 }) 1841 if err != nil { 1842 t.Fatalf("GetInboxSummaryForCLILocal error: %v", err) 1843 } 1844 if len(res.Conversations) != 2 { 1845 t.Fatalf("unexpected response from GetInboxSummaryForCLILocal . expected 2 items, got %d\n", len(res.Conversations)) 1846 } 1847 1848 res, err = ctc.as(t, users[0]).chatLocalHandler().GetInboxSummaryForCLILocal(ctx, 1849 chat1.GetInboxSummaryForCLILocalQuery{ 1850 UnreadFirst: true, 1851 UnreadFirstLimit: chat1.UnreadFirstNumLimit{ 1852 AtLeast: 0, 1853 AtMost: 1000, 1854 NumRead: 1, 1855 }, 1856 TopicType: chat1.TopicType_CHAT, 1857 }) 1858 if err != nil { 1859 t.Fatalf("GetInboxSummaryForCLILocal error: %v", err) 1860 } 1861 if len(res.Conversations) != 2 { 1862 t.Fatalf("unexpected response from GetInboxSummaryForCLILocal . expected 2 items, got %d\n", len(res.Conversations)) 1863 } 1864 if !res.Conversations[0].Info.Id.Eq(withUser1.Id) { 1865 t.Fatalf("unexpected response from GetInboxSummaryForCLILocal; unread conversation is not the first in response.\n") 1866 } 1867 1868 res, err = ctc.as(t, users[0]).chatLocalHandler().GetInboxSummaryForCLILocal(ctx, chat1.GetInboxSummaryForCLILocalQuery{ 1869 UnreadFirst: true, 1870 UnreadFirstLimit: chat1.UnreadFirstNumLimit{ 1871 AtLeast: 0, 1872 AtMost: 2, 1873 NumRead: 5, 1874 }, 1875 TopicType: chat1.TopicType_CHAT, 1876 }) 1877 if err != nil { 1878 t.Fatalf("GetInboxSummaryForCLILocal error: %v", err) 1879 } 1880 if len(res.Conversations) != 2 { 1881 t.Fatalf("unexpected response from GetInboxSummaryForCLILocal . expected 1 item, got %d\n", len(res.Conversations)) 1882 } 1883 1884 res, err = ctc.as(t, users[0]).chatLocalHandler().GetInboxSummaryForCLILocal(ctx, chat1.GetInboxSummaryForCLILocalQuery{ 1885 UnreadFirst: true, 1886 UnreadFirstLimit: chat1.UnreadFirstNumLimit{ 1887 AtLeast: 3, 1888 AtMost: 100, 1889 NumRead: 0, 1890 }, 1891 TopicType: chat1.TopicType_CHAT, 1892 }) 1893 if err != nil { 1894 t.Fatalf("GetInboxSummaryForCLILocal error: %v", err) 1895 } 1896 if len(res.Conversations) != 3 { 1897 t.Fatalf("unexpected response from GetInboxSummaryForCLILocal . expected 1 item, got %d\n", len(res.Conversations)) 1898 } 1899 }) 1900 } 1901 1902 func TestChatSrvGetMessagesLocal(t *testing.T) { 1903 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 1904 ctc := makeChatTestContext(t, "GetMessagesLocal", 2) 1905 defer ctc.cleanup() 1906 users := ctc.users() 1907 1908 created := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 1909 mt, ctc.as(t, users[1]).user()) 1910 mustPostLocalForTest(t, ctc, users[0], created, chat1.NewMessageBodyWithText(chat1.MessageText{Body: "Sometimes you eat the bar"})) 1911 mustPostLocalForTest(t, ctc, users[1], created, chat1.NewMessageBodyWithText(chat1.MessageText{Body: "and sometimes"})) 1912 mustPostLocalForTest(t, ctc, users[0], created, chat1.NewMessageBodyWithText(chat1.MessageText{Body: "the bar eats you."})) 1913 1914 // GetMessagesLocal currently seems to return messages descending ID order. 1915 // It would probably be good if this changed to return either in req order or ascending. 1916 getIDs := []chat1.MessageID{3, 2, 1} 1917 1918 ctx := ctc.as(t, users[0]).startCtx 1919 res, err := ctc.as(t, users[0]).chatLocalHandler().GetMessagesLocal(ctx, chat1.GetMessagesLocalArg{ 1920 ConversationID: created.Id, 1921 MessageIDs: getIDs, 1922 }) 1923 if err != nil { 1924 t.Fatalf("GetMessagesLocal error: %v", err) 1925 } 1926 for i, msg := range res.Messages { 1927 if !msg.IsValid() { 1928 t.Fatalf("Missing message: %v", getIDs[i]) 1929 } 1930 msgID := msg.GetMessageID() 1931 if msgID != getIDs[i] { 1932 t.Fatalf("Wrong message ID: got %v but expected %v", msgID, getIDs[i]) 1933 } 1934 } 1935 if len(res.Messages) != len(getIDs) { 1936 t.Fatalf("GetMessagesLocal got %v items but expected %v", len(res.Messages), len(getIDs)) 1937 } 1938 }) 1939 } 1940 1941 func extractOutbox(t *testing.T, msgs []chat1.MessageUnboxed) []chat1.MessageUnboxed { 1942 var routbox []chat1.MessageUnboxed 1943 for _, msg := range msgs { 1944 typ, err := msg.State() 1945 require.NoError(t, err) 1946 if typ == chat1.MessageUnboxedState_OUTBOX { 1947 routbox = append(routbox, msg) 1948 } 1949 } 1950 return routbox 1951 } 1952 1953 func TestChatSrvGetOutbox(t *testing.T) { 1954 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 1955 ctc := makeChatTestContext(t, "GetOutbox", 3) 1956 defer ctc.cleanup() 1957 users := ctc.users() 1958 1959 var err error 1960 created := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 1961 mt, ctc.as(t, users[1]).user()) 1962 created2 := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 1963 mt, ctc.as(t, users[2]).user()) 1964 1965 u := users[0] 1966 h := ctc.as(t, users[0]).h 1967 ctx := ctc.as(t, users[0]).startCtx 1968 tc := ctc.world.Tcs[ctc.as(t, users[0]).user().Username] 1969 outbox := storage.NewOutbox(tc.Context(), users[0].User.GetUID().ToBytes()) 1970 1971 obr, err := outbox.PushMessage(ctx, created.Id, chat1.MessagePlaintext{ 1972 MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{}), 1973 ClientHeader: chat1.MessageClientHeader{ 1974 Sender: u.User.GetUID().ToBytes(), 1975 TlfName: u.Username, 1976 TlfPublic: false, 1977 OutboxInfo: &chat1.OutboxInfo{ 1978 Prev: 10, 1979 }, 1980 }, 1981 }, nil, nil, nil, keybase1.TLFIdentifyBehavior_CHAT_CLI) 1982 require.NoError(t, err) 1983 1984 // only badgeable messages are added to the thread 1985 _, err = outbox.PushMessage(ctx, created.Id, chat1.MessagePlaintext{ 1986 ClientHeader: chat1.MessageClientHeader{ 1987 Sender: u.User.GetUID().ToBytes(), 1988 TlfName: u.Username, 1989 TlfPublic: false, 1990 OutboxInfo: &chat1.OutboxInfo{ 1991 Prev: 10, 1992 }, 1993 }, 1994 }, nil, nil, nil, keybase1.TLFIdentifyBehavior_CHAT_CLI) 1995 require.NoError(t, err) 1996 1997 thread, err := h.GetThreadLocal(ctx, chat1.GetThreadLocalArg{ 1998 ConversationID: created.Id, 1999 }) 2000 require.NoError(t, err) 2001 2002 routbox := extractOutbox(t, thread.Thread.Messages) 2003 require.Equal(t, 1, len(routbox), "wrong size outbox") 2004 require.Equal(t, obr.OutboxID, routbox[0].Outbox().OutboxID, "wrong outbox ID") 2005 2006 thread, err = h.GetThreadLocal(ctx, chat1.GetThreadLocalArg{ 2007 ConversationID: created2.Id, 2008 }) 2009 require.NoError(t, err) 2010 routbox = extractOutbox(t, thread.Thread.Messages) 2011 require.Equal(t, 0, len(routbox), "non empty outbox") 2012 }) 2013 } 2014 2015 func TestChatSrvGap(t *testing.T) { 2016 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 2017 ctc := makeChatTestContext(t, "GetOutbox", 2) 2018 defer ctc.cleanup() 2019 users := ctc.users() 2020 2021 var err error 2022 created := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 2023 mt, ctc.as(t, users[1]).user()) 2024 2025 res, err := postLocalForTest(t, ctc, users[0], created, chat1.NewMessageBodyWithText(chat1.MessageText{Body: "Sometimes you eat the bar"})) 2026 require.NoError(t, err) 2027 2028 u := users[0] 2029 h := ctc.as(t, users[0]).h 2030 ctx := ctc.as(t, users[0]).startCtx 2031 tc := ctc.world.Tcs[ctc.as(t, users[0]).user().Username] 2032 msgID := res.MessageID 2033 mres, err := h.remoteClient().GetMessagesRemote(ctx, chat1.GetMessagesRemoteArg{ 2034 ConversationID: created.Id, 2035 MessageIDs: []chat1.MessageID{msgID}, 2036 }) 2037 require.NoError(t, err) 2038 2039 require.Len(t, mres.Msgs, 1, "number of messages") 2040 2041 ooMsg := mres.Msgs[0] 2042 ooMsg.ServerHeader.MessageID = 5 2043 2044 payload := chat1.NewMessagePayload{ 2045 Action: types.ActionNewMessage, 2046 ConvID: created.Id, 2047 Message: ooMsg, 2048 } 2049 2050 listener := newServerChatListener() 2051 tc.G.NotifyRouter.AddListener(listener) 2052 2053 mh := codec.MsgpackHandle{WriteExt: true} 2054 var data []byte 2055 enc := codec.NewEncoderBytes(&data, &mh) 2056 require.NoError(t, enc.Encode(payload)) 2057 ph := NewPushHandler(tc.Context()) 2058 ph.Start(context.TODO(), nil) 2059 defer func() { <-ph.Stop(context.TODO()) }() 2060 require.NoError(t, ph.Activity(ctx, &gregor1.OutOfBandMessage{ 2061 Uid_: u.User.GetUID().ToBytes(), 2062 System_: gregor1.System(types.PushActivity), 2063 Body_: data, 2064 })) 2065 2066 updates := consumeNewThreadsStale(t, listener) 2067 require.Equal(t, 1, len(updates)) 2068 require.Equal(t, created.Id, updates[0].ConvID, "wrong cid") 2069 require.Equal(t, chat1.StaleUpdateType_CLEAR, updates[0].UpdateType) 2070 2071 ooMsg.ServerHeader.MessageID = 6 2072 payload = chat1.NewMessagePayload{ 2073 Action: types.ActionNewMessage, 2074 ConvID: created.Id, 2075 Message: ooMsg, 2076 } 2077 enc = codec.NewEncoderBytes(&data, &mh) 2078 require.NoError(t, enc.Encode(payload)) 2079 require.NoError(t, ph.Activity(ctx, &gregor1.OutOfBandMessage{ 2080 Uid_: u.User.GetUID().ToBytes(), 2081 System_: gregor1.System(types.PushActivity), 2082 Body_: data, 2083 })) 2084 2085 select { 2086 case <-listener.threadsStale: 2087 require.Fail(t, "should not get stale event here") 2088 default: 2089 } 2090 }) 2091 } 2092 2093 type resolveRes struct { 2094 convID chat1.ConversationID 2095 info chat1.ConversationResolveInfo 2096 } 2097 2098 type serverChatListener struct { 2099 libkb.NoopNotifyListener 2100 2101 // ChatActivity channels 2102 newMessageLocal chan chat1.IncomingMessage 2103 newMessageRemote chan chat1.IncomingMessage 2104 newConversation chan chat1.NewConversationInfo 2105 membersUpdate chan chat1.MembersUpdateInfo 2106 appNotificationSettings chan chat1.SetAppNotificationSettingsInfo 2107 teamType chan chat1.TeamTypeInfo 2108 expunge chan chat1.ExpungeInfo 2109 ephemeralPurge chan chat1.EphemeralPurgeNotifInfo 2110 reactionUpdate chan chat1.ReactionUpdateNotif 2111 messagesUpdated chan chat1.MessagesUpdated 2112 readMessage chan chat1.ReadMessageInfo 2113 2114 threadsStale chan []chat1.ConversationStaleUpdate 2115 convUpdate chan chat1.ConversationID 2116 inboxStale chan struct{} 2117 joinedConv chan *chat1.InboxUIItem 2118 leftConv chan chat1.ConversationID 2119 resetConv chan chat1.ConversationID 2120 identifyUpdate chan keybase1.CanonicalTLFNameAndIDWithBreaks 2121 inboxSynced chan chat1.ChatSyncResult 2122 setConvRetention chan chat1.ConversationID 2123 setTeamRetention chan keybase1.TeamID 2124 setConvSettings chan chat1.ConversationID 2125 kbfsUpgrade chan chat1.ConversationID 2126 resolveConv chan resolveRes 2127 subteamRename chan []chat1.ConversationID 2128 unfurlPrompt chan chat1.MessageID 2129 welcomeMessage chan keybase1.TeamID 2130 setStatus chan chat1.SetStatusInfo 2131 teamChangedByID chan keybase1.TeamChangedByIDArg 2132 } 2133 2134 var _ libkb.NotifyListener = (*serverChatListener)(nil) 2135 2136 func (n *serverChatListener) ChatIdentifyUpdate(update keybase1.CanonicalTLFNameAndIDWithBreaks) { 2137 n.identifyUpdate <- update 2138 } 2139 func (n *serverChatListener) ChatInboxStale(uid keybase1.UID) { 2140 n.inboxStale <- struct{}{} 2141 } 2142 func (n *serverChatListener) ChatConvUpdate(uid keybase1.UID, convID chat1.ConversationID) { 2143 n.convUpdate <- convID 2144 } 2145 func (n *serverChatListener) ChatThreadsStale(uid keybase1.UID, cids []chat1.ConversationStaleUpdate) { 2146 n.threadsStale <- cids 2147 } 2148 func (n *serverChatListener) ChatInboxSynced(uid keybase1.UID, topicType chat1.TopicType, 2149 syncRes chat1.ChatSyncResult) { 2150 switch topicType { 2151 case chat1.TopicType_CHAT, chat1.TopicType_NONE: 2152 n.inboxSynced <- syncRes 2153 } 2154 } 2155 func (n *serverChatListener) NewChatActivity(uid keybase1.UID, activity chat1.ChatActivity, 2156 source chat1.ChatActivitySource) { 2157 typ, _ := activity.ActivityType() 2158 switch typ { 2159 case chat1.ChatActivityType_INCOMING_MESSAGE: 2160 switch source { 2161 case chat1.ChatActivitySource_LOCAL: 2162 n.newMessageLocal <- activity.IncomingMessage() 2163 case chat1.ChatActivitySource_REMOTE: 2164 n.newMessageRemote <- activity.IncomingMessage() 2165 } 2166 case chat1.ChatActivityType_READ_MESSAGE: 2167 n.readMessage <- activity.ReadMessage() 2168 case chat1.ChatActivityType_NEW_CONVERSATION: 2169 n.newConversation <- activity.NewConversation() 2170 case chat1.ChatActivityType_MEMBERS_UPDATE: 2171 n.membersUpdate <- activity.MembersUpdate() 2172 case chat1.ChatActivityType_SET_APP_NOTIFICATION_SETTINGS: 2173 n.appNotificationSettings <- activity.SetAppNotificationSettings() 2174 case chat1.ChatActivityType_TEAMTYPE: 2175 n.teamType <- activity.Teamtype() 2176 case chat1.ChatActivityType_EXPUNGE: 2177 n.expunge <- activity.Expunge() 2178 case chat1.ChatActivityType_EPHEMERAL_PURGE: 2179 n.ephemeralPurge <- activity.EphemeralPurge() 2180 case chat1.ChatActivityType_REACTION_UPDATE: 2181 n.reactionUpdate <- activity.ReactionUpdate() 2182 case chat1.ChatActivityType_MESSAGES_UPDATED: 2183 n.messagesUpdated <- activity.MessagesUpdated() 2184 case chat1.ChatActivityType_SET_STATUS: 2185 n.setStatus <- activity.SetStatus() 2186 } 2187 } 2188 func (n *serverChatListener) ChatJoinedConversation(uid keybase1.UID, convID chat1.ConversationID, 2189 conv *chat1.InboxUIItem) { 2190 n.joinedConv <- conv 2191 } 2192 func (n *serverChatListener) ChatLeftConversation(uid keybase1.UID, convID chat1.ConversationID) { 2193 n.leftConv <- convID 2194 } 2195 func (n *serverChatListener) ChatResetConversation(uid keybase1.UID, convID chat1.ConversationID) { 2196 n.resetConv <- convID 2197 } 2198 func (n *serverChatListener) ChatTLFResolve(uid keybase1.UID, convID chat1.ConversationID, 2199 info chat1.ConversationResolveInfo) { 2200 n.resolveConv <- resolveRes{ 2201 convID: convID, 2202 info: info, 2203 } 2204 } 2205 func (n *serverChatListener) ChatSetConvRetention(uid keybase1.UID, convID chat1.ConversationID) { 2206 n.setConvRetention <- convID 2207 } 2208 func (n *serverChatListener) ChatSetTeamRetention(uid keybase1.UID, teamID keybase1.TeamID) { 2209 n.setTeamRetention <- teamID 2210 } 2211 func (n *serverChatListener) ChatSetConvSettings(uid keybase1.UID, convID chat1.ConversationID) { 2212 n.setConvSettings <- convID 2213 } 2214 func (n *serverChatListener) ChatKBFSToImpteamUpgrade(uid keybase1.UID, convID chat1.ConversationID) { 2215 n.kbfsUpgrade <- convID 2216 } 2217 func (n *serverChatListener) ChatSubteamRename(uid keybase1.UID, convIDs []chat1.ConversationID) { 2218 n.subteamRename <- convIDs 2219 } 2220 func (n *serverChatListener) ChatPromptUnfurl(uid keybase1.UID, convID chat1.ConversationID, 2221 msgID chat1.MessageID, domain string) { 2222 n.unfurlPrompt <- msgID 2223 } 2224 func (n *serverChatListener) ChatWelcomeMessageLoaded(teamID keybase1.TeamID, 2225 _ chat1.WelcomeMessageDisplay) { 2226 n.welcomeMessage <- teamID 2227 } 2228 func (n *serverChatListener) TeamChangedByID(teamID keybase1.TeamID, latestSeqno keybase1.Seqno, implicitTeam bool, 2229 changes keybase1.TeamChangeSet, latestHiddenSeqno keybase1.Seqno, source keybase1.TeamChangedSource) { 2230 2231 n.teamChangedByID <- keybase1.TeamChangedByIDArg{ 2232 TeamID: teamID, 2233 LatestSeqno: latestSeqno, 2234 ImplicitTeam: implicitTeam, 2235 Changes: changes, 2236 LatestHiddenSeqno: latestHiddenSeqno, 2237 } 2238 } 2239 func newServerChatListener() *serverChatListener { 2240 buf := 100 2241 return &serverChatListener{ 2242 newMessageLocal: make(chan chat1.IncomingMessage, buf), 2243 newMessageRemote: make(chan chat1.IncomingMessage, buf), 2244 newConversation: make(chan chat1.NewConversationInfo, buf), 2245 readMessage: make(chan chat1.ReadMessageInfo, buf), 2246 membersUpdate: make(chan chat1.MembersUpdateInfo, buf), 2247 appNotificationSettings: make(chan chat1.SetAppNotificationSettingsInfo, buf), 2248 teamType: make(chan chat1.TeamTypeInfo, buf), 2249 expunge: make(chan chat1.ExpungeInfo, buf), 2250 ephemeralPurge: make(chan chat1.EphemeralPurgeNotifInfo, buf), 2251 reactionUpdate: make(chan chat1.ReactionUpdateNotif, buf), 2252 messagesUpdated: make(chan chat1.MessagesUpdated, buf), 2253 2254 threadsStale: make(chan []chat1.ConversationStaleUpdate, buf), 2255 convUpdate: make(chan chat1.ConversationID, buf), 2256 inboxStale: make(chan struct{}, buf), 2257 joinedConv: make(chan *chat1.InboxUIItem, buf), 2258 leftConv: make(chan chat1.ConversationID, buf), 2259 resetConv: make(chan chat1.ConversationID, buf), 2260 identifyUpdate: make(chan keybase1.CanonicalTLFNameAndIDWithBreaks, buf), 2261 inboxSynced: make(chan chat1.ChatSyncResult, buf), 2262 setConvRetention: make(chan chat1.ConversationID, buf), 2263 setTeamRetention: make(chan keybase1.TeamID, buf), 2264 setConvSettings: make(chan chat1.ConversationID, buf), 2265 kbfsUpgrade: make(chan chat1.ConversationID, buf), 2266 resolveConv: make(chan resolveRes, buf), 2267 subteamRename: make(chan []chat1.ConversationID, buf), 2268 unfurlPrompt: make(chan chat1.MessageID, buf), 2269 setStatus: make(chan chat1.SetStatusInfo, buf), 2270 teamChangedByID: make(chan keybase1.TeamChangedByIDArg, buf), 2271 } 2272 } 2273 2274 func TestChatSrvPostLocalNonblock(t *testing.T) { 2275 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 2276 runWithEphemeral(t, mt, func(ephemeralLifetime *gregor1.DurationSec) { 2277 ctc := makeChatTestContext(t, "PostLocalNonblock", 3) 2278 defer ctc.cleanup() 2279 users := ctc.users() 2280 2281 ui := kbtest.NewChatUI() 2282 ctc.as(t, users[0]).h.mockChatUI = ui 2283 tc := ctc.as(t, users[0]) 2284 listener := newServerChatListener() 2285 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener) 2286 ctc.world.Tcs[users[0].Username].ChatG.Syncer.(*Syncer).isConnected = true 2287 if ephemeralLifetime != nil { 2288 tc.m.G().GetUPAKLoader().ClearMemory() 2289 } 2290 2291 assertEphemeral := func(ephemeralLifetime *gregor1.DurationSec, unboxed chat1.UIMessage) { 2292 valid := unboxed.Valid() 2293 require.False(t, valid.IsEphemeralExpired) 2294 require.Nil(t, valid.ExplodedBy) 2295 require.False(t, valid.MessageBody.IsNil()) 2296 if ephemeralLifetime == nil { 2297 require.False(t, valid.IsEphemeral) 2298 require.EqualValues(t, valid.Etime, 0) 2299 } else { 2300 require.True(t, valid.IsEphemeral) 2301 lifetime := ephemeralLifetime.ToDuration() 2302 require.True(t, time.Now().Add(lifetime).Sub(valid.Etime.Time()) <= lifetime) 2303 } 2304 } 2305 2306 assertNotEphemeral := func(ephemeralLifetime *gregor1.DurationSec, unboxed chat1.UIMessage) { 2307 valid := unboxed.Valid() 2308 require.False(t, valid.IsEphemeralExpired) 2309 require.False(t, valid.IsEphemeral) 2310 require.EqualValues(t, valid.Etime, 0) 2311 require.Nil(t, valid.ExplodedBy) 2312 require.False(t, valid.MessageBody.IsNil()) 2313 } 2314 2315 assertReactionUpdate := func(convID chat1.ConversationID, targetMsgID chat1.MessageID, reactionMap chat1.ReactionMap) { 2316 info := consumeReactionUpdate(t, listener) 2317 require.Equal(t, convID, info.ConvID) 2318 require.Len(t, info.ReactionUpdates, 1) 2319 reactionUpdate := info.ReactionUpdates[0] 2320 require.Equal(t, targetMsgID, reactionUpdate.TargetMsgID) 2321 for _, reactions := range reactionUpdate.Reactions.Reactions { 2322 for k, r := range reactions.Users { 2323 require.NotZero(t, r.Ctime) 2324 r.Ctime = 0 2325 reactions.Users[k] = r 2326 } 2327 } 2328 var refMap chat1.UIReactionMap 2329 refMap.Reactions = make(map[string]chat1.UIReactionDesc) 2330 for emoji, users := range reactionMap.Reactions { 2331 refMap.Reactions[emoji] = chat1.UIReactionDesc{ 2332 Users: make(map[string]chat1.Reaction), 2333 } 2334 for username, reaction := range users { 2335 refMap.Reactions[emoji].Users[username] = reaction 2336 } 2337 } 2338 require.Equal(t, refMap, reactionUpdate.Reactions) 2339 } 2340 2341 var err error 2342 var created, fwd chat1.ConversationInfoLocal 2343 switch mt { 2344 case chat1.ConversationMembersType_TEAM: 2345 first := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 2346 mt, ctc.as(t, users[1]).user()) 2347 consumeNewConversation(t, listener, first.Id) 2348 topicName := "mike" 2349 ncres, err := ctc.as(t, users[0]).chatLocalHandler().NewConversationLocal(tc.startCtx, 2350 chat1.NewConversationLocalArg{ 2351 TlfName: first.TlfName, 2352 TopicName: &topicName, 2353 TopicType: chat1.TopicType_CHAT, 2354 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 2355 MembersType: chat1.ConversationMembersType_TEAM, 2356 }) 2357 require.NoError(t, err) 2358 created = ncres.Conv.Info 2359 consumeNewConversation(t, listener, created.Id) 2360 consumeNewMsgLocal(t, listener, chat1.MessageType_JOIN) 2361 consumeNewMsgRemote(t, listener, chat1.MessageType_JOIN) 2362 consumeNewPendingMsg(t, listener) 2363 consumeNewMsgLocal(t, listener, chat1.MessageType_SYSTEM) 2364 consumeNewMsgRemote(t, listener, chat1.MessageType_SYSTEM) 2365 consumeNewMsgLocal(t, listener, chat1.MessageType_SYSTEM) 2366 consumeNewMsgRemote(t, listener, chat1.MessageType_SYSTEM) 2367 2368 topicName = "fwd" 2369 ncres, err = ctc.as(t, users[0]).chatLocalHandler().NewConversationLocal(tc.startCtx, 2370 chat1.NewConversationLocalArg{ 2371 TlfName: first.TlfName, 2372 TopicName: &topicName, 2373 TopicType: chat1.TopicType_CHAT, 2374 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 2375 MembersType: chat1.ConversationMembersType_TEAM, 2376 }) 2377 require.NoError(t, err) 2378 fwd = ncres.Conv.Info 2379 consumeNewConversation(t, listener, fwd.Id) 2380 consumeNewMsgLocal(t, listener, chat1.MessageType_JOIN) 2381 consumeNewMsgRemote(t, listener, chat1.MessageType_JOIN) 2382 consumeNewMsgLocal(t, listener, chat1.MessageType_SYSTEM) 2383 consumeNewMsgRemote(t, listener, chat1.MessageType_SYSTEM) 2384 default: 2385 created = mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 2386 mt, ctc.as(t, users[1]).user()) 2387 fwd = mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 2388 mt, ctc.as(t, users[2]).user()) 2389 } 2390 2391 t.Logf("send a text message") 2392 arg := chat1.PostTextNonblockArg{ 2393 ConversationID: created.Id, 2394 TlfName: created.TlfName, 2395 TlfPublic: created.Visibility == keybase1.TLFVisibility_PUBLIC, 2396 Body: "hi", 2397 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 2398 EphemeralLifetime: ephemeralLifetime, 2399 } 2400 res, err := ctc.as(t, users[0]).chatLocalHandler().PostTextNonblock(tc.startCtx, arg) 2401 require.NoError(t, err) 2402 consumeNewPendingMsg(t, listener) 2403 var unboxed chat1.UIMessage 2404 select { 2405 case info := <-listener.newMessageRemote: 2406 unboxed = info.Message 2407 require.True(t, unboxed.IsValid(), "invalid message") 2408 require.NotNil(t, unboxed.Valid().OutboxID, "no outbox ID") 2409 require.Equal(t, chat1.MessageType_TEXT, unboxed.GetMessageType(), "invalid type") 2410 require.Equal(t, res.OutboxID.String(), *unboxed.Valid().OutboxID, "mismatch outbox ID") 2411 assertEphemeral(ephemeralLifetime, unboxed) 2412 case <-time.After(20 * time.Second): 2413 require.Fail(t, "no event received") 2414 } 2415 consumeNewMsgLocal(t, listener, chat1.MessageType_TEXT) 2416 2417 t.Logf("fwd a text message") 2418 arg1 := chat1.ForwardMessageNonblockArg{ 2419 SrcConvID: created.Id, 2420 DstConvID: fwd.Id, 2421 MsgID: unboxed.GetMessageID(), 2422 } 2423 res, err = ctc.as(t, users[0]).chatLocalHandler().ForwardMessageNonblock(tc.startCtx, arg1) 2424 require.NoError(t, err) 2425 consumeNewPendingMsg(t, listener) 2426 var fwdedMsg chat1.UIMessage 2427 select { 2428 case info := <-listener.newMessageRemote: 2429 fwdedMsg = info.Message 2430 require.True(t, fwdedMsg.IsValid(), "invalid message") 2431 require.NotNil(t, fwdedMsg.Valid().OutboxID, "no outbox ID") 2432 require.Equal(t, chat1.MessageType_TEXT, fwdedMsg.GetMessageType(), "invalid type") 2433 require.Equal(t, res.OutboxID.String(), *fwdedMsg.Valid().OutboxID, "mismatch outbox ID") 2434 assertEphemeral(ephemeralLifetime, fwdedMsg) 2435 case <-time.After(20 * time.Second): 2436 require.Fail(t, "no event received") 2437 } 2438 consumeNewMsgLocal(t, listener, chat1.MessageType_TEXT) 2439 2440 t.Logf("post text message with prefetched outbox ID") 2441 genOutboxID, err := ctc.as(t, users[0]).chatLocalHandler().GenerateOutboxID(context.TODO()) 2442 require.NoError(t, err) 2443 arg = chat1.PostTextNonblockArg{ 2444 ConversationID: created.Id, 2445 TlfName: created.TlfName, 2446 TlfPublic: created.Visibility == keybase1.TLFVisibility_PUBLIC, 2447 Body: "hi", 2448 OutboxID: &genOutboxID, 2449 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 2450 EphemeralLifetime: ephemeralLifetime, 2451 } 2452 res, err = ctc.as(t, users[0]).chatLocalHandler().PostTextNonblock(tc.startCtx, arg) 2453 require.NoError(t, err) 2454 consumeNewMsgLocal(t, listener, chat1.MessageType_TEXT) // pending message 2455 2456 select { 2457 case info := <-listener.newMessageRemote: 2458 unboxed = info.Message 2459 require.True(t, unboxed.IsValid(), "invalid message") 2460 require.NotNil(t, unboxed.Valid().OutboxID, "no outbox ID") 2461 require.Equal(t, genOutboxID.String(), *unboxed.Valid().OutboxID, "mismatch outbox ID") 2462 require.Equal(t, res.OutboxID.String(), *unboxed.Valid().OutboxID, "mismatch outbox ID") 2463 require.Equal(t, chat1.MessageType_TEXT, unboxed.GetMessageType(), "invalid type") 2464 assertEphemeral(ephemeralLifetime, unboxed) 2465 case <-time.After(20 * time.Second): 2466 require.Fail(t, "no event received") 2467 } 2468 consumeNewMsgLocal(t, listener, chat1.MessageType_TEXT) 2469 2470 textUnboxed := unboxed 2471 2472 t.Logf("react to the message") 2473 // An ephemeralLifetime is added if we are reacting to an ephemeral message 2474 reactionKey := ":+1:" 2475 rarg := chat1.PostReactionNonblockArg{ 2476 ConversationID: created.Id, 2477 TlfName: created.TlfName, 2478 TlfPublic: created.Visibility == keybase1.TLFVisibility_PUBLIC, 2479 Supersedes: textUnboxed.GetMessageID(), 2480 Body: reactionKey, 2481 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 2482 } 2483 res, err = ctc.as(t, users[0]).chatLocalHandler().PostReactionNonblock(tc.startCtx, rarg) 2484 require.NoError(t, err) 2485 consumeNewPendingMsg(t, listener) 2486 select { 2487 case info := <-listener.newMessageRemote: 2488 unboxed = info.Message 2489 require.True(t, unboxed.IsValid(), "invalid message") 2490 require.NotNil(t, unboxed.Valid().OutboxID, "no outbox ID") 2491 require.Equal(t, res.OutboxID.String(), *unboxed.Valid().OutboxID, "mismatch outbox ID") 2492 require.Equal(t, chat1.MessageType_REACTION, unboxed.GetMessageType(), "invalid type") 2493 assertEphemeral(ephemeralLifetime, unboxed) 2494 case <-time.After(20 * time.Second): 2495 require.Fail(t, "no event received") 2496 } 2497 consumeNewMsgLocal(t, listener, chat1.MessageType_REACTION) 2498 reactionUnboxed := unboxed 2499 expectedReactionMap := chat1.ReactionMap{ 2500 Reactions: map[string]map[string]chat1.Reaction{ 2501 ":+1:": { 2502 users[0].Username: { 2503 ReactionMsgID: reactionUnboxed.GetMessageID(), 2504 }, 2505 }, 2506 }, 2507 } 2508 assertReactionUpdate(created.Id, textUnboxed.GetMessageID(), expectedReactionMap) 2509 2510 t.Logf("edit the message") 2511 // An ephemeralLifetime is added if we are editing an ephemeral message 2512 targetMsgID := textUnboxed.GetMessageID() 2513 earg := chat1.PostEditNonblockArg{ 2514 ConversationID: created.Id, 2515 TlfName: created.TlfName, 2516 TlfPublic: created.Visibility == keybase1.TLFVisibility_PUBLIC, 2517 Target: chat1.EditTarget{ 2518 MessageID: &targetMsgID, 2519 }, 2520 Body: "hi2", 2521 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 2522 } 2523 res, err = ctc.as(t, users[0]).chatLocalHandler().PostEditNonblock(tc.startCtx, earg) 2524 require.NoError(t, err) 2525 consumeNewPendingMsg(t, listener) 2526 select { 2527 case info := <-listener.newMessageRemote: 2528 unboxed = info.Message 2529 require.True(t, unboxed.IsValid(), "invalid message") 2530 require.NotNil(t, unboxed.Valid().OutboxID, "no outbox ID") 2531 require.Equal(t, res.OutboxID.String(), *unboxed.Valid().OutboxID, "mismatch outbox ID") 2532 require.Equal(t, chat1.MessageType_EDIT, unboxed.GetMessageType(), "invalid type") 2533 assertEphemeral(ephemeralLifetime, unboxed) 2534 case <-time.After(20 * time.Second): 2535 require.Fail(t, "no event received") 2536 } 2537 consumeNewMsgLocal(t, listener, chat1.MessageType_EDIT) 2538 2539 // Repost a reaction and ensure it is deleted 2540 t.Logf("repost reaction = delete reaction") 2541 res, err = ctc.as(t, users[0]).chatLocalHandler().PostReactionNonblock(tc.startCtx, rarg) 2542 require.NoError(t, err) 2543 consumeNewPendingMsg(t, listener) 2544 select { 2545 case info := <-listener.newMessageRemote: 2546 unboxed = info.Message 2547 require.True(t, unboxed.IsValid(), "invalid message") 2548 require.NotNil(t, unboxed.Valid().OutboxID, "no outbox ID") 2549 require.Equal(t, res.OutboxID.String(), *unboxed.Valid().OutboxID, "mismatch outbox ID") 2550 require.Equal(t, chat1.MessageType_DELETE, unboxed.GetMessageType(), "invalid type") 2551 assertNotEphemeral(ephemeralLifetime, unboxed) 2552 case <-time.After(20 * time.Second): 2553 require.Fail(t, "no event received") 2554 } 2555 consumeNewMsgLocal(t, listener, chat1.MessageType_DELETE) 2556 assertReactionUpdate(created.Id, textUnboxed.GetMessageID(), chat1.ReactionMap{}) 2557 2558 t.Logf("delete the message") 2559 darg := chat1.PostDeleteNonblockArg{ 2560 ConversationID: created.Id, 2561 TlfName: created.TlfName, 2562 TlfPublic: created.Visibility == keybase1.TLFVisibility_PUBLIC, 2563 Supersedes: textUnboxed.GetMessageID(), 2564 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 2565 } 2566 res, err = ctc.as(t, users[0]).chatLocalHandler().PostDeleteNonblock(tc.startCtx, darg) 2567 require.NoError(t, err) 2568 consumeNewPendingMsg(t, listener) 2569 select { 2570 case info := <-listener.newMessageRemote: 2571 unboxed = info.Message 2572 require.True(t, unboxed.IsValid(), "invalid message") 2573 require.NotNil(t, unboxed.Valid().OutboxID, "no outbox ID") 2574 require.Equal(t, res.OutboxID.String(), *unboxed.Valid().OutboxID, "mismatch outbox ID") 2575 require.Equal(t, chat1.MessageType_DELETE, unboxed.GetMessageType(), "invalid type") 2576 assertNotEphemeral(ephemeralLifetime, unboxed) 2577 case <-time.After(20 * time.Second): 2578 require.Fail(t, "no event received") 2579 } 2580 consumeNewMsgLocal(t, listener, chat1.MessageType_DELETE) 2581 2582 t.Logf("post headline") 2583 headline := "SILENCE!" 2584 harg := chat1.PostHeadlineNonblockArg{ 2585 ConversationID: created.Id, 2586 TlfName: created.TlfName, 2587 TlfPublic: created.Visibility == keybase1.TLFVisibility_PUBLIC, 2588 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 2589 Headline: headline, 2590 } 2591 res, err = ctc.as(t, users[0]).chatLocalHandler().PostHeadlineNonblock(tc.startCtx, harg) 2592 require.NoError(t, err) 2593 consumeNewPendingMsg(t, listener) 2594 select { 2595 case info := <-listener.newMessageRemote: 2596 unboxed = info.Message 2597 require.True(t, unboxed.IsValid(), "invalid message") 2598 require.NotNil(t, unboxed.Valid().OutboxID, "no outbox ID") 2599 require.Equal(t, res.OutboxID.String(), *unboxed.Valid().OutboxID, "mismatch outbox ID") 2600 require.Equal(t, chat1.MessageType_HEADLINE, unboxed.GetMessageType(), "invalid type") 2601 switch mt { 2602 case chat1.ConversationMembersType_TEAM: 2603 require.Equal(t, headline, unboxed.Valid().MessageBody.Headline().Headline) 2604 default: 2605 // Nothing to do for other member types. 2606 } 2607 assertNotEphemeral(ephemeralLifetime, unboxed) 2608 case <-time.After(20 * time.Second): 2609 require.Fail(t, "no event received") 2610 } 2611 consumeNewMsgLocal(t, listener, chat1.MessageType_HEADLINE) 2612 2613 t.Logf("change name") 2614 topicName := "NEWNAME" 2615 marg := chat1.PostMetadataNonblockArg{ 2616 ConversationID: created.Id, 2617 TlfName: created.TlfName, 2618 TlfPublic: created.Visibility == keybase1.TLFVisibility_PUBLIC, 2619 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 2620 ChannelName: topicName, 2621 } 2622 res, err = ctc.as(t, users[0]).chatLocalHandler().PostMetadataNonblock(tc.startCtx, marg) 2623 require.NoError(t, err) 2624 consumeNewPendingMsg(t, listener) 2625 select { 2626 case info := <-listener.newMessageRemote: 2627 unboxed = info.Message 2628 require.True(t, unboxed.IsValid(), "invalid message") 2629 require.NotNil(t, unboxed.Valid().OutboxID, "no outbox ID") 2630 require.Equal(t, res.OutboxID.String(), *unboxed.Valid().OutboxID, "mismatch outbox ID") 2631 require.Equal(t, chat1.MessageType_METADATA, unboxed.GetMessageType(), "invalid type") 2632 switch mt { 2633 case chat1.ConversationMembersType_TEAM: 2634 require.Equal(t, topicName, unboxed.Valid().MessageBody.Metadata().ConversationTitle) 2635 default: 2636 // Nothing to do for other member types. 2637 } 2638 assertNotEphemeral(ephemeralLifetime, unboxed) 2639 case <-time.After(20 * time.Second): 2640 require.Fail(t, "no event received") 2641 } 2642 consumeNewMsgLocal(t, listener, chat1.MessageType_METADATA) 2643 }) 2644 }) 2645 } 2646 2647 func filterOutboxMessages(tv chat1.ThreadView) (res []chat1.MessageUnboxed) { 2648 for _, m := range tv.Messages { 2649 if !m.IsOutbox() { 2650 res = append(res, m) 2651 } 2652 } 2653 return res 2654 } 2655 2656 func TestChatSrvPostEditNonblock(t *testing.T) { 2657 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 2658 switch mt { 2659 case chat1.ConversationMembersType_KBFS: 2660 return 2661 default: 2662 // Fall through for other member types. 2663 } 2664 ctc := makeChatTestContext(t, "TestChatSrvPostEditNonblock", 1) 2665 defer ctc.cleanup() 2666 users := ctc.users() 2667 2668 listener := newServerChatListener() 2669 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener) 2670 tc := ctc.world.Tcs[users[0].Username] 2671 tc.ChatG.Syncer.(*Syncer).isConnected = true 2672 ctx := ctc.as(t, users[0]).startCtx 2673 conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt) 2674 t.Logf("test convID: %x", conv.Id.DbShortForm()) 2675 checkMessage := func(intended string, num int) { 2676 res, err := ctc.as(t, users[0]).chatLocalHandler().GetThreadLocal(ctx, chat1.GetThreadLocalArg{ 2677 ConversationID: conv.Id, 2678 Query: &chat1.GetThreadQuery{ 2679 MessageTypes: []chat1.MessageType{chat1.MessageType_TEXT}, 2680 }, 2681 }) 2682 require.NoError(t, err) 2683 thread := filterOutboxMessages(res.Thread) 2684 require.Equal(t, num, len(thread)) 2685 require.True(t, thread[0].IsValid()) 2686 require.Equal(t, intended, thread[0].Valid().MessageBody.Text().Body) 2687 } 2688 2689 outboxID, err := storage.NewOutboxID() 2690 require.NoError(t, err) 2691 postRes, err := ctc.as(t, users[0]).chatLocalHandler().PostLocal(ctx, chat1.PostLocalArg{ 2692 ConversationID: conv.Id, 2693 Msg: chat1.MessagePlaintext{ 2694 ClientHeader: chat1.MessageClientHeader{ 2695 Conv: conv.Triple, 2696 MessageType: chat1.MessageType_TEXT, 2697 TlfName: conv.TlfName, 2698 OutboxID: &outboxID, 2699 }, 2700 MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{ 2701 Body: "hi", 2702 }), 2703 }, 2704 }) 2705 require.NoError(t, err) 2706 consumeNewMsgLocal(t, listener, chat1.MessageType_TEXT) 2707 consumeNewMsgRemote(t, listener, chat1.MessageType_TEXT) 2708 _, err = ctc.as(t, users[0]).chatLocalHandler().PostEditNonblock(ctx, chat1.PostEditNonblockArg{ 2709 ConversationID: conv.Id, 2710 TlfName: conv.TlfName, 2711 Target: chat1.EditTarget{ 2712 MessageID: &postRes.MessageID, 2713 }, 2714 Body: "hi!", 2715 }) 2716 require.NoError(t, err) 2717 consumeNewMsgLocal(t, listener, chat1.MessageType_EDIT) 2718 consumeNewMsgRemote(t, listener, chat1.MessageType_EDIT) 2719 checkMessage("hi!", 1) 2720 2721 _, err = ctc.as(t, users[0]).chatLocalHandler().PostEditNonblock(ctx, chat1.PostEditNonblockArg{ 2722 ConversationID: conv.Id, 2723 TlfName: conv.TlfName, 2724 Target: chat1.EditTarget{ 2725 OutboxID: &outboxID, 2726 }, 2727 Body: "hi!!", 2728 }) 2729 require.NoError(t, err) 2730 consumeNewMsgLocal(t, listener, chat1.MessageType_EDIT) 2731 consumeNewMsgRemote(t, listener, chat1.MessageType_EDIT) 2732 checkMessage("hi!!", 1) 2733 }) 2734 } 2735 2736 func TestChatSrvFindConversations(t *testing.T) { 2737 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 2738 switch mt { 2739 case chat1.ConversationMembersType_TEAM, chat1.ConversationMembersType_KBFS: 2740 return 2741 } 2742 2743 ctc := makeChatTestContext(t, "FindConversations", 3) 2744 defer ctc.cleanup() 2745 users := ctc.users() 2746 2747 t.Logf("basic test") 2748 created := mustCreatePublicConversationForTest(t, ctc, users[2], chat1.TopicType_CHAT, 2749 mt, users[1]) 2750 t.Logf("created public conversation: %+v", created) 2751 if useRemoteMock { 2752 convRemote := ctc.world.GetConversationByID(created.Id) 2753 require.NotNil(t, convRemote) 2754 convRemote.Metadata.Visibility = keybase1.TLFVisibility_PUBLIC 2755 convRemote.Metadata.ActiveList = 2756 []gregor1.UID{users[2].User.GetUID().ToBytes(), users[1].User.GetUID().ToBytes()} 2757 } 2758 2759 ctx := ctc.as(t, users[0]).startCtx 2760 ctx2 := ctc.as(t, users[2]).startCtx 2761 res, err := ctc.as(t, users[0]).chatLocalHandler().FindConversationsLocal(ctx, 2762 chat1.FindConversationsLocalArg{ 2763 TlfName: created.TlfName, 2764 MembersType: mt, 2765 Visibility: keybase1.TLFVisibility_PUBLIC, 2766 TopicType: chat1.TopicType_CHAT, 2767 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 2768 }) 2769 require.NoError(t, err) 2770 require.Equal(t, 1, len(res.Conversations), "no conv found for %v", mt) 2771 require.Equal(t, created.Id, res.Conversations[0].GetConvID(), "wrong conv") 2772 2773 t.Logf("simple post") 2774 _, err = ctc.as(t, users[2]).chatLocalHandler().PostLocal(ctx2, chat1.PostLocalArg{ 2775 ConversationID: created.Id, 2776 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 2777 Msg: chat1.MessagePlaintext{ 2778 ClientHeader: chat1.MessageClientHeader{ 2779 Conv: created.Triple, 2780 MessageType: chat1.MessageType_TEXT, 2781 TlfName: created.TlfName, 2782 TlfPublic: true, 2783 }, 2784 MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{ 2785 Body: "PUBLIC", 2786 }), 2787 }, 2788 }) 2789 require.NoError(t, err) 2790 2791 t.Logf("read from conversation") 2792 tres, err := ctc.as(t, users[0]).chatLocalHandler().GetThreadLocal(ctx, chat1.GetThreadLocalArg{ 2793 ConversationID: res.Conversations[0].GetConvID(), 2794 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 2795 Query: &chat1.GetThreadQuery{ 2796 MessageTypes: []chat1.MessageType{chat1.MessageType_TEXT}, 2797 }, 2798 }) 2799 require.NoError(t, err) 2800 require.Equal(t, 1, len(tres.Thread.Messages), "wrong length") 2801 2802 t.Logf("test topic name") 2803 _, err = ctc.as(t, users[2]).chatLocalHandler().PostLocal(ctx2, chat1.PostLocalArg{ 2804 ConversationID: created.Id, 2805 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 2806 Msg: chat1.MessagePlaintext{ 2807 ClientHeader: chat1.MessageClientHeader{ 2808 Conv: created.Triple, 2809 MessageType: chat1.MessageType_METADATA, 2810 TlfName: created.TlfName, 2811 TlfPublic: true, 2812 }, 2813 MessageBody: chat1.NewMessageBodyWithMetadata(chat1.MessageConversationMetadata{ 2814 ConversationTitle: "MIKE", 2815 }), 2816 }, 2817 }) 2818 require.NoError(t, err) 2819 2820 res, err = ctc.as(t, users[0]).chatLocalHandler().FindConversationsLocal(ctx, 2821 chat1.FindConversationsLocalArg{ 2822 TlfName: created.TlfName, 2823 MembersType: mt, 2824 Visibility: keybase1.TLFVisibility_PUBLIC, 2825 TopicType: chat1.TopicType_CHAT, 2826 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 2827 }) 2828 require.NoError(t, err) 2829 require.Equal(t, 0, len(res.Conversations), "conv found") 2830 2831 res, err = ctc.as(t, users[0]).chatLocalHandler().FindConversationsLocal(ctx, 2832 chat1.FindConversationsLocalArg{ 2833 TlfName: created.TlfName, 2834 MembersType: mt, 2835 Visibility: keybase1.TLFVisibility_PUBLIC, 2836 TopicType: chat1.TopicType_CHAT, 2837 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 2838 TopicName: "MIKE", 2839 }) 2840 require.NoError(t, err) 2841 require.Equal(t, 1, len(res.Conversations), "conv found") 2842 require.Equal(t, created.Id, res.Conversations[0].GetConvID(), "wrong conv") 2843 }) 2844 } 2845 2846 func TestChatSrvFindConversationsWithSBS(t *testing.T) { 2847 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 2848 switch mt { 2849 case chat1.ConversationMembersType_TEAM, chat1.ConversationMembersType_KBFS: 2850 return 2851 } 2852 2853 ctc := makeChatTestContext(t, "FindConversations", 2) 2854 defer ctc.cleanup() 2855 users := ctc.users() 2856 2857 // Create a conversation between both users. Attempt to send to 2858 // `user1,user2@rooter` and make sure we resolve and find the 2859 // conversation correctly. 2860 created := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 2861 mt, users[1]) 2862 tc1 := ctc.world.Tcs[users[1].Username] 2863 sbsName := strings.Join([]string{users[0].Username, fmt.Sprintf("%s@rooter", users[1].Username)}, ",") 2864 2865 ctx := ctc.as(t, users[0]).startCtx 2866 // Fail since we haven't proved rooter yet 2867 res, err := ctc.as(t, users[0]).chatLocalHandler().FindConversationsLocal(ctx, 2868 chat1.FindConversationsLocalArg{ 2869 TlfName: sbsName, 2870 MembersType: mt, 2871 TopicType: chat1.TopicType_CHAT, 2872 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 2873 }) 2874 require.NoError(t, err) 2875 require.Zero(t, len(res.Conversations)) 2876 2877 proveRooter(t, tc1.Context().ExternalG(), users[1]) 2878 res, err = ctc.as(t, users[0]).chatLocalHandler().FindConversationsLocal(ctx, 2879 chat1.FindConversationsLocalArg{ 2880 TlfName: sbsName, 2881 MembersType: mt, 2882 TopicType: chat1.TopicType_CHAT, 2883 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 2884 }) 2885 require.NoError(t, err) 2886 require.Equal(t, 1, len(res.Conversations), "no conv found") 2887 require.Equal(t, created.Id, res.Conversations[0].GetConvID(), "wrong conv") 2888 }) 2889 } 2890 2891 func receiveThreadResult(t *testing.T, cb chan kbtest.NonblockThreadResult) (res *chat1.UIMessages) { 2892 var tres kbtest.NonblockThreadResult 2893 select { 2894 case tres = <-cb: 2895 res = tres.Thread 2896 case <-time.After(20 * time.Second): 2897 require.Fail(t, "no thread received") 2898 } 2899 if !tres.Full { 2900 select { 2901 case tres = <-cb: 2902 require.True(t, tres.Full) 2903 res = tres.Thread 2904 case <-time.After(20 * time.Second): 2905 require.Fail(t, "no thread received") 2906 } 2907 } 2908 return res 2909 } 2910 2911 func TestChatSrvGetThreadNonblockServerPage(t *testing.T) { 2912 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 2913 ctc := makeChatTestContext(t, "TestChatSrvGetThreadNonblockIncremental", 1) 2914 defer ctc.cleanup() 2915 users := ctc.users() 2916 2917 ui := kbtest.NewChatUI() 2918 ctc.as(t, users[0]).h.mockChatUI = ui 2919 2920 query := chat1.GetThreadQuery{ 2921 MessageTypes: []chat1.MessageType{chat1.MessageType_TEXT}, 2922 } 2923 ctx := ctc.as(t, users[0]).startCtx 2924 conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt) 2925 2926 t.Logf("send a bunch of messages") 2927 numMsgs := 5 2928 msg := chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hi"}) 2929 for i := 0; i < numMsgs; i++ { 2930 mustPostLocalForTest(t, ctc, users[0], conv, msg) 2931 } 2932 2933 // Basic 2934 delay := 10 * time.Minute 2935 clock := clockwork.NewFakeClock() 2936 tc := ctc.world.Tcs[users[0].Username] 2937 ri := ctc.as(t, users[0]).ri 2938 uiThreadLoader := NewUIThreadLoader(tc.Context(), func() chat1.RemoteInterface { return ri }) 2939 uiThreadLoader.clock = clock 2940 uiThreadLoader.cachedThreadDelay = nil 2941 uiThreadLoader.remoteThreadDelay = &delay 2942 uiThreadLoader.validatedDelay = 0 2943 tc.ChatG.UIThreadLoader = uiThreadLoader 2944 cb := make(chan struct{}) 2945 p := utils.PresentPagination(&chat1.Pagination{ 2946 Num: 1, 2947 }) 2948 go func() { 2949 _, err := ctc.as(t, users[0]).chatLocalHandler().GetThreadNonblock(ctx, 2950 chat1.GetThreadNonblockArg{ 2951 ConversationID: conv.Id, 2952 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 2953 Query: &query, 2954 Pagination: p, 2955 Pgmode: chat1.GetThreadNonblockPgMode_SERVER, 2956 }, 2957 ) 2958 require.NoError(t, err) 2959 close(cb) 2960 }() 2961 clock.Advance(50 * time.Millisecond) 2962 select { 2963 case res := <-ui.ThreadCb: 2964 require.False(t, res.Full) 2965 require.Equal(t, 1, len(res.Thread.Messages)) 2966 require.NotNil(t, res.Thread.Pagination) 2967 require.False(t, res.Thread.Pagination.Last) 2968 case <-time.After(20 * time.Second): 2969 require.Fail(t, "no thread cb") 2970 } 2971 recvRemote := func() bool { 2972 for i := 0; i < 5; i++ { 2973 clock.Advance(20 * time.Minute) 2974 select { 2975 case res := <-ui.ThreadCb: 2976 require.True(t, res.Full) 2977 require.Equal(t, 1, len(res.Thread.Messages)) 2978 require.Equal(t, chat1.MessageID(6), res.Thread.Messages[0].GetMessageID()) 2979 p = res.Thread.Pagination 2980 require.NotNil(t, p) 2981 require.False(t, p.Last) 2982 return true 2983 case <-time.After(2 * time.Second): 2984 require.Fail(t, "no thread cb") 2985 } 2986 } 2987 return false 2988 } 2989 require.True(t, recvRemote()) 2990 select { 2991 case <-cb: 2992 case <-time.After(20 * time.Second): 2993 require.Fail(t, "GetThread never finished") 2994 } 2995 2996 cb = make(chan struct{}) 2997 p.Num = 1 2998 p.Next = "deadbeef" 2999 go func() { 3000 _, err := ctc.as(t, users[0]).chatLocalHandler().GetThreadNonblock(ctx, 3001 chat1.GetThreadNonblockArg{ 3002 ConversationID: conv.Id, 3003 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 3004 Query: &query, 3005 Pagination: p, 3006 Pgmode: chat1.GetThreadNonblockPgMode_SERVER, 3007 }, 3008 ) 3009 require.NoError(t, err) 3010 close(cb) 3011 }() 3012 clock.Advance(50 * time.Millisecond) 3013 select { 3014 case res := <-ui.ThreadCb: 3015 require.False(t, res.Full) 3016 require.Equal(t, 1, len(res.Thread.Messages)) 3017 require.NotNil(t, res.Thread.Pagination) 3018 require.False(t, res.Thread.Pagination.Last) 3019 case <-time.After(20 * time.Second): 3020 require.Fail(t, "no thread cb") 3021 } 3022 recvRemote = func() bool { 3023 for i := 0; i < 5; i++ { 3024 clock.Advance(20 * time.Minute) 3025 select { 3026 case res := <-ui.ThreadCb: 3027 require.True(t, res.Full) 3028 require.Equal(t, 1, len(res.Thread.Messages)) 3029 require.Equal(t, chat1.MessageID(5), res.Thread.Messages[0].GetMessageID()) 3030 p = res.Thread.Pagination 3031 require.NotNil(t, p) 3032 require.False(t, p.Last) 3033 return true 3034 case <-time.After(2 * time.Second): 3035 t.Logf("no thread cb") 3036 } 3037 } 3038 return false 3039 } 3040 require.True(t, recvRemote()) 3041 select { 3042 case <-cb: 3043 case <-time.After(20 * time.Second): 3044 require.Fail(t, "GetThread never finished") 3045 } 3046 3047 for i := 0; i < 5; i++ { 3048 p.Num = 50 3049 cb = make(chan struct{}) 3050 go func() { 3051 _, err := ctc.as(t, users[0]).chatLocalHandler().GetThreadNonblock(ctx, 3052 chat1.GetThreadNonblockArg{ 3053 ConversationID: conv.Id, 3054 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 3055 Query: &query, 3056 Pagination: p, 3057 Pgmode: chat1.GetThreadNonblockPgMode_SERVER, 3058 }, 3059 ) 3060 require.NoError(t, err) 3061 close(cb) 3062 }() 3063 clock.Advance(50 * time.Millisecond) 3064 if i == 0 { 3065 select { 3066 case res := <-ui.ThreadCb: 3067 require.False(t, res.Full) 3068 require.Equal(t, 3, len(res.Thread.Messages)) 3069 require.NotNil(t, res.Thread.Pagination) 3070 require.True(t, res.Thread.Pagination.Last) 3071 case <-time.After(20 * time.Second): 3072 require.Fail(t, "no thread cb") 3073 } 3074 } else { 3075 select { 3076 case <-ui.ThreadCb: 3077 require.Fail(t, "no callback expected") 3078 default: 3079 } 3080 } 3081 recvRemote = func() bool { 3082 for j := 0; j < 5; j++ { 3083 clock.Advance(20 * time.Minute) 3084 if i == 0 { 3085 select { 3086 case res := <-ui.ThreadCb: 3087 require.True(t, res.Full) 3088 require.Equal(t, 3, len(res.Thread.Messages)) 3089 require.Equal(t, chat1.MessageID(4), res.Thread.Messages[0].GetMessageID()) 3090 require.NotNil(t, res.Thread.Pagination.Last) 3091 require.True(t, res.Thread.Pagination.Last) 3092 return true 3093 case <-time.After(2 * time.Second): 3094 t.Logf("no thread cb") 3095 } 3096 } else { 3097 select { 3098 case <-ui.ThreadCb: 3099 require.Fail(t, "no callback expected") 3100 default: 3101 return true 3102 } 3103 } 3104 } 3105 return false 3106 } 3107 require.True(t, recvRemote()) 3108 select { 3109 case <-cb: 3110 case <-time.After(20 * time.Second): 3111 require.Fail(t, "GetThread never finished") 3112 } 3113 } 3114 }) 3115 } 3116 3117 func TestChatSrvGetThreadNonblockIncremental(t *testing.T) { 3118 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 3119 ctc := makeChatTestContext(t, "TestChatSrvGetThreadNonblockIncremental", 1) 3120 defer ctc.cleanup() 3121 users := ctc.users() 3122 3123 ui := kbtest.NewChatUI() 3124 ctc.as(t, users[0]).h.mockChatUI = ui 3125 3126 query := chat1.GetThreadQuery{ 3127 MessageTypes: []chat1.MessageType{chat1.MessageType_TEXT}, 3128 } 3129 ctx := ctc.as(t, users[0]).startCtx 3130 conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt) 3131 3132 t.Logf("send a bunch of messages") 3133 numMsgs := 20 3134 msg := chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hi"}) 3135 for i := 0; i < numMsgs; i++ { 3136 mustPostLocalForTest(t, ctc, users[0], conv, msg) 3137 } 3138 3139 // Basic 3140 delay := 10 * time.Minute 3141 clock := clockwork.NewFakeClock() 3142 tc := ctc.world.Tcs[users[0].Username] 3143 ri := ctc.as(t, users[0]).ri 3144 uiThreadLoader := NewUIThreadLoader(tc.Context(), func() chat1.RemoteInterface { return ri }) 3145 uiThreadLoader.clock = clock 3146 uiThreadLoader.cachedThreadDelay = nil 3147 uiThreadLoader.remoteThreadDelay = &delay 3148 uiThreadLoader.validatedDelay = 0 3149 tc.ChatG.UIThreadLoader = uiThreadLoader 3150 cb := make(chan struct{}) 3151 go func() { 3152 _, err := ctc.as(t, users[0]).chatLocalHandler().GetThreadNonblock(ctx, 3153 chat1.GetThreadNonblockArg{ 3154 ConversationID: conv.Id, 3155 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 3156 Query: &query, 3157 }, 3158 ) 3159 require.NoError(t, err) 3160 close(cb) 3161 }() 3162 clock.Advance(50 * time.Millisecond) 3163 select { 3164 case res := <-ui.ThreadCb: 3165 require.False(t, res.Full) 3166 require.Equal(t, numMsgs, len(res.Thread.Messages)) 3167 case <-time.After(20 * time.Second): 3168 require.Fail(t, "no thread cb") 3169 } 3170 clock.Advance(20 * time.Minute) 3171 select { 3172 case res := <-ui.ThreadCb: 3173 require.True(t, res.Full) 3174 require.Equal(t, numMsgs, len(res.Thread.Messages)) 3175 case <-time.After(20 * time.Second): 3176 require.Fail(t, "no thread cb") 3177 } 3178 select { 3179 case <-cb: 3180 case <-time.After(20 * time.Second): 3181 require.Fail(t, "GetThread never finished") 3182 } 3183 3184 // Incremental 3185 cb = make(chan struct{}) 3186 go func() { 3187 _, err := ctc.as(t, users[0]).chatLocalHandler().GetThreadNonblock(ctx, 3188 chat1.GetThreadNonblockArg{ 3189 ConversationID: conv.Id, 3190 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 3191 Query: &query, 3192 CbMode: chat1.GetThreadNonblockCbMode_INCREMENTAL, 3193 }, 3194 ) 3195 require.NoError(t, err) 3196 close(cb) 3197 }() 3198 clock.Advance(50 * time.Millisecond) 3199 select { 3200 case res := <-ui.ThreadCb: 3201 require.False(t, res.Full) 3202 require.Equal(t, numMsgs, len(res.Thread.Messages)) 3203 case <-time.After(20 * time.Second): 3204 require.Fail(t, "no thread cb") 3205 } 3206 mustPostLocalForTest(t, ctc, users[0], conv, msg) 3207 clock.Advance(20 * time.Minute) 3208 select { 3209 case res := <-ui.ThreadCb: 3210 require.True(t, res.Full) 3211 require.Equal(t, 1, len(res.Thread.Messages)) 3212 require.True(t, res.Thread.Pagination.Last) 3213 case <-time.After(20 * time.Second): 3214 require.Fail(t, "no thread cb") 3215 } 3216 select { 3217 case <-cb: 3218 case <-time.After(20 * time.Second): 3219 require.Fail(t, "GetThread never finished") 3220 } 3221 3222 }) 3223 } 3224 3225 func msgState(t *testing.T, msg chat1.UIMessage) chat1.MessageUnboxedState { 3226 state, err := msg.State() 3227 require.NoError(t, err) 3228 return state 3229 } 3230 3231 func confirmIsPlaceholder(t *testing.T, msgID chat1.MessageID, msg chat1.UIMessage, hidden bool) { 3232 require.Equal(t, msgID, msg.GetMessageID()) 3233 require.Equal(t, chat1.MessageUnboxedState_PLACEHOLDER, msgState(t, msg)) 3234 require.Equal(t, hidden, msg.Placeholder().Hidden) 3235 } 3236 3237 func confirmIsText(t *testing.T, msgID chat1.MessageID, msg chat1.UIMessage, text string) { 3238 require.Equal(t, msgID, msg.GetMessageID()) 3239 require.Equal(t, chat1.MessageUnboxedState_VALID, msgState(t, msg)) 3240 require.Equal(t, chat1.MessageType_TEXT, msg.GetMessageType()) 3241 require.Equal(t, text, msg.Valid().MessageBody.Text().Body) 3242 } 3243 3244 func TestChatSrvGetThreadNonblockSupersedes(t *testing.T) { 3245 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 3246 ctc := makeChatTestContext(t, "GetThreadNonblockSupersedes", 1) 3247 defer ctc.cleanup() 3248 users := ctc.users() 3249 3250 uid := gregor1.UID(users[0].GetUID().ToBytes()) 3251 ui := kbtest.NewChatUI() 3252 ctc.as(t, users[0]).h.mockChatUI = ui 3253 ctx := ctc.as(t, users[0]).startCtx 3254 <-ctc.as(t, users[0]).h.G().ConvLoader.Stop(ctx) 3255 listener := newServerChatListener() 3256 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener) 3257 3258 conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt) 3259 cs := ctc.world.Tcs[users[0].Username].ChatG.ConvSource 3260 msg := chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hi"}) 3261 msgID1 := mustPostLocalForTest(t, ctc, users[0], conv, msg) 3262 consumeNewMsgRemote(t, listener, chat1.MessageType_TEXT) 3263 msgRes, err := ctc.as(t, users[0]).chatLocalHandler().GetMessagesLocal(ctx, chat1.GetMessagesLocalArg{ 3264 ConversationID: conv.Id, 3265 MessageIDs: []chat1.MessageID{msgID1}, 3266 DisableResolveSupersedes: true, 3267 }) 3268 require.NoError(t, err) 3269 require.Equal(t, 1, len(msgRes.Messages)) 3270 msg1 := msgRes.Messages[0] 3271 editMsgID1 := mustEditMsg(ctx, t, ctc, users[0], conv, msgID1) 3272 consumeNewMsgRemote(t, listener, chat1.MessageType_EDIT) 3273 3274 msgIDs := []chat1.MessageID{editMsgID1, msgID1, 1} 3275 require.NoError(t, cs.Clear(context.TODO(), conv.Id, uid, nil)) 3276 tc := ctc.world.Tcs[users[0].Username] 3277 rconv, err := utils.GetUnverifiedConv(ctx, tc.Context(), uid, conv.Id, 3278 types.InboxSourceDataSourceAll) 3279 require.NoError(t, err) 3280 err = cs.PushUnboxed(ctx, rconv, uid, []chat1.MessageUnboxed{msg1}) 3281 require.NoError(t, err) 3282 3283 delay := 10 * time.Minute 3284 clock := clockwork.NewFakeClock() 3285 ri := ctc.as(t, users[0]).ri 3286 uiThreadLoader := NewUIThreadLoader(tc.Context(), func() chat1.RemoteInterface { return ri }) 3287 uiThreadLoader.clock = clock 3288 uiThreadLoader.cachedThreadDelay = nil 3289 uiThreadLoader.remoteThreadDelay = &delay 3290 uiThreadLoader.validatedDelay = 0 3291 tc.ChatG.UIThreadLoader = uiThreadLoader 3292 cb := make(chan struct{}) 3293 query := chat1.GetThreadQuery{ 3294 MessageTypes: []chat1.MessageType{chat1.MessageType_TEXT}, 3295 } 3296 go func() { 3297 _, err := ctc.as(t, users[0]).chatLocalHandler().GetThreadNonblock(ctx, 3298 chat1.GetThreadNonblockArg{ 3299 ConversationID: conv.Id, 3300 Query: &query, 3301 CbMode: chat1.GetThreadNonblockCbMode_INCREMENTAL, 3302 }, 3303 ) 3304 require.NoError(t, err) 3305 close(cb) 3306 }() 3307 clock.Advance(50 * time.Millisecond) 3308 select { 3309 case res := <-ui.ThreadCb: 3310 require.False(t, res.Full) 3311 require.Equal(t, len(msgIDs), len(res.Thread.Messages)) 3312 // Not unread 3313 require.Equal(t, msgIDs, utils.PluckUIMessageIDs(res.Thread.Messages)) 3314 confirmIsText(t, msgID1, res.Thread.Messages[1], "hi") 3315 require.False(t, res.Thread.Messages[1].Valid().Superseded) 3316 confirmIsPlaceholder(t, editMsgID1, res.Thread.Messages[0], false) 3317 confirmIsPlaceholder(t, 1, res.Thread.Messages[2], false) 3318 case <-time.After(20 * time.Second): 3319 require.Fail(t, "no thread cb") 3320 } 3321 clock.Advance(20 * time.Minute) 3322 select { 3323 case res := <-ui.ThreadCb: 3324 require.True(t, res.Full) 3325 require.Equal(t, len(msgIDs), len(res.Thread.Messages)) 3326 // Not unread 3327 confirmIsPlaceholder(t, editMsgID1, res.Thread.Messages[0], true) 3328 confirmIsText(t, msgID1, res.Thread.Messages[1], "edited") 3329 confirmIsPlaceholder(t, 1, res.Thread.Messages[2], true) 3330 case <-time.After(20 * time.Second): 3331 require.Fail(t, "no thread cb") 3332 } 3333 select { 3334 case <-cb: 3335 case <-time.After(20 * time.Second): 3336 require.Fail(t, "GetThread never finished") 3337 } 3338 3339 deleteMsgID := mustDeleteMsg(ctx, t, ctc, users[0], conv, msgID1) 3340 consumeNewMsgRemote(t, listener, chat1.MessageType_DELETE) 3341 msgIDs = []chat1.MessageID{deleteMsgID, editMsgID1, msgID1, 1} 3342 require.NoError(t, cs.Clear(context.TODO(), conv.Id, uid, nil)) 3343 3344 err = cs.PushUnboxed(ctx, rconv, uid, []chat1.MessageUnboxed{msg1}) 3345 require.NoError(t, err) 3346 cb = make(chan struct{}) 3347 go func() { 3348 _, err := ctc.as(t, users[0]).chatLocalHandler().GetThreadNonblock(ctx, 3349 chat1.GetThreadNonblockArg{ 3350 ConversationID: conv.Id, 3351 Query: &query, 3352 CbMode: chat1.GetThreadNonblockCbMode_INCREMENTAL, 3353 }, 3354 ) 3355 require.NoError(t, err) 3356 close(cb) 3357 }() 3358 clock.Advance(50 * time.Millisecond) 3359 select { 3360 case res := <-ui.ThreadCb: 3361 require.False(t, res.Full) 3362 require.Equal(t, len(msgIDs), len(res.Thread.Messages)) 3363 // Not unread 3364 require.Equal(t, msgIDs, utils.PluckUIMessageIDs(res.Thread.Messages)) 3365 confirmIsPlaceholder(t, deleteMsgID, res.Thread.Messages[0], false) 3366 confirmIsPlaceholder(t, editMsgID1, res.Thread.Messages[1], false) 3367 confirmIsText(t, msgID1, res.Thread.Messages[2], "hi") 3368 require.False(t, res.Thread.Messages[2].Valid().Superseded) 3369 confirmIsPlaceholder(t, 1, res.Thread.Messages[3], false) 3370 case <-time.After(20 * time.Second): 3371 require.Fail(t, "no thread cb") 3372 } 3373 clock.Advance(20 * time.Minute) 3374 select { 3375 case res := <-ui.ThreadCb: 3376 require.True(t, res.Full) 3377 require.Equal(t, len(msgIDs), len(res.Thread.Messages)) 3378 // Not unread 3379 confirmIsPlaceholder(t, deleteMsgID, res.Thread.Messages[0], true) 3380 confirmIsPlaceholder(t, editMsgID1, res.Thread.Messages[1], true) 3381 confirmIsPlaceholder(t, msgID1, res.Thread.Messages[2], true) 3382 confirmIsPlaceholder(t, 1, res.Thread.Messages[3], true) 3383 case <-time.After(20 * time.Second): 3384 require.Fail(t, "no thread cb") 3385 } 3386 select { 3387 case <-cb: 3388 case <-time.After(20 * time.Second): 3389 require.Fail(t, "GetThread never finished") 3390 } 3391 }) 3392 } 3393 3394 func TestChatSrvGetUnreadLine(t *testing.T) { 3395 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 3396 ctc := makeChatTestContext(t, "GetUnreadLine", 2) 3397 defer ctc.cleanup() 3398 users := ctc.users() 3399 3400 ui := kbtest.NewChatUI() 3401 ctc.as(t, users[0]).h.mockChatUI = ui 3402 ctc.as(t, users[1]).h.mockChatUI = ui 3403 ctx1 := ctc.as(t, users[0]).startCtx 3404 ctx2 := ctc.as(t, users[1]).startCtx 3405 <-ctc.as(t, users[0]).h.G().ConvLoader.Stop(ctx1) 3406 <-ctc.as(t, users[1]).h.G().ConvLoader.Stop(ctx2) 3407 listener1 := newServerChatListener() 3408 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener1) 3409 listener2 := newServerChatListener() 3410 ctc.as(t, users[1]).h.G().NotifyRouter.AddListener(listener2) 3411 g1 := ctc.world.Tcs[users[0].Username].ChatG 3412 g2 := ctc.world.Tcs[users[1].Username].ChatG 3413 3414 conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt, users[1]) 3415 msg := chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hi"}) 3416 msgID1 := mustPostLocalForTest(t, ctc, users[0], conv, msg) 3417 consumeNewMsgRemote(t, listener1, chat1.MessageType_TEXT) 3418 consumeNewMsgRemote(t, listener2, chat1.MessageType_TEXT) 3419 3420 mustEditMsg(ctx1, t, ctc, users[0], conv, msgID1) 3421 consumeNewMsgRemote(t, listener1, chat1.MessageType_EDIT) 3422 consumeNewMsgRemote(t, listener2, chat1.MessageType_EDIT) 3423 3424 assertUnreadline := func(ctx context.Context, g *globals.ChatContext, user *kbtest.FakeUser, 3425 readMsgID, unreadLineID chat1.MessageID) { 3426 for i := 0; i < 1; i++ { 3427 if i == 0 { 3428 require.NoError(t, g.ConvSource.Clear(ctx, conv.Id, user.GetUID().ToBytes(), nil)) 3429 } 3430 res, err := ctc.as(t, user).chatLocalHandler().GetUnreadline(ctx, 3431 chat1.GetUnreadlineArg{ 3432 ConvID: conv.Id, 3433 ReadMsgID: readMsgID, 3434 }) 3435 require.NoError(t, err) 3436 if unreadLineID == 0 { 3437 require.Nil(t, res.UnreadlineID) 3438 } else { 3439 require.NotNil(t, res.UnreadlineID) 3440 require.Equal(t, unreadLineID, *res.UnreadlineID, "unread line mismatch") 3441 } 3442 } 3443 } 3444 3445 // user2 will have an unread id of the TEXT message even after the edit 3446 msgID0 := chat1.MessageID(1) 3447 assertUnreadline(ctx1, g1, users[0], msgID0, msgID1) 3448 assertUnreadline(ctx1, g1, users[0], msgID1, 0) 3449 assertUnreadline(ctx2, g2, users[1], msgID0, msgID1) 3450 assertUnreadline(ctx2, g2, users[1], msgID1, 0) 3451 3452 // subsequent TEXT post leaves unreadline unchanged. 3453 msg = chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hi"}) 3454 msgID2 := mustPostLocalForTest(t, ctc, users[0], conv, msg) 3455 consumeNewMsgRemote(t, listener1, chat1.MessageType_TEXT) 3456 consumeNewMsgRemote(t, listener2, chat1.MessageType_TEXT) 3457 assertUnreadline(ctx2, g2, users[1], msgID0, msgID1) 3458 // If user2 has read to msgID1, msgID2 is the next candidate 3459 assertUnreadline(ctx2, g2, users[1], msgID1, msgID2) 3460 3461 _, err := ctc.as(t, users[1]).chatLocalHandler().MarkAsReadLocal(ctx1, 3462 chat1.MarkAsReadLocalArg{ 3463 ConversationID: conv.Id, 3464 MsgID: &msgID0, 3465 ForceUnread: true, 3466 }) 3467 require.NoError(t, err) 3468 assertUnreadline(ctx2, g2, users[1], msgID0, msgID1) 3469 3470 // reaction does not affect things 3471 mustReactToMsg(ctx1, t, ctc, users[0], conv, msgID2, ":+1:") 3472 consumeNewMsgRemote(t, listener1, chat1.MessageType_REACTION) 3473 consumeNewMsgRemote(t, listener2, chat1.MessageType_REACTION) 3474 assertUnreadline(ctx2, g2, users[1], 1, msgID1) 3475 3476 // user2 will bump unreadLineID to msgID which the next visible msg 3477 mustDeleteMsg(ctx1, t, ctc, users[0], conv, msgID1) 3478 consumeNewMsgRemote(t, listener1, chat1.MessageType_DELETE) 3479 consumeNewMsgRemote(t, listener2, chat1.MessageType_DELETE) 3480 assertUnreadline(ctx2, g2, users[1], 1, msgID2) 3481 3482 // user2 will have no unread id since the only visible message was now deleted 3483 msgID3 := mustDeleteMsg(ctx1, t, ctc, users[0], conv, msgID2) 3484 consumeNewMsgRemote(t, listener1, chat1.MessageType_DELETE) 3485 consumeNewMsgRemote(t, listener2, chat1.MessageType_DELETE) 3486 assertUnreadline(ctx2, g2, users[1], 1, 0) 3487 3488 // if we are fully read, there is no line and we don't go to the server 3489 g1.ConvSource.SetRemoteInterface(func() chat1.RemoteInterface { 3490 return chat1.RemoteClient{Cli: errorClient{}} 3491 }) 3492 assertUnreadline(ctx1, g1, users[0], msgID3, 0) 3493 g2.ConvSource.SetRemoteInterface(func() chat1.RemoteInterface { 3494 return chat1.RemoteClient{Cli: errorClient{}} 3495 }) 3496 assertUnreadline(ctx2, g2, users[1], msgID3, 0) 3497 }) 3498 } 3499 3500 func mustDeleteHistory(ctx context.Context, t *testing.T, ctc *chatTestContext, user *kbtest.FakeUser, 3501 conv chat1.ConversationInfoLocal, upto chat1.MessageID) chat1.MessageID { 3502 delH := chat1.MessageDeleteHistory{ 3503 Upto: upto, 3504 } 3505 postRes, err := ctc.as(t, user).chatLocalHandler().PostLocal(ctx, chat1.PostLocalArg{ 3506 ConversationID: conv.Id, 3507 Msg: chat1.MessagePlaintext{ 3508 ClientHeader: chat1.MessageClientHeader{ 3509 Conv: conv.Triple, 3510 MessageType: chat1.MessageType_DELETEHISTORY, 3511 TlfName: conv.TlfName, 3512 DeleteHistory: &delH, 3513 }, 3514 MessageBody: chat1.NewMessageBodyWithDeletehistory(delH), 3515 }, 3516 }) 3517 require.NoError(t, err) 3518 return postRes.MessageID 3519 } 3520 3521 func mustDeleteMsg(ctx context.Context, t *testing.T, ctc *chatTestContext, user *kbtest.FakeUser, 3522 conv chat1.ConversationInfoLocal, msgID chat1.MessageID) chat1.MessageID { 3523 postRes, err := ctc.as(t, user).chatLocalHandler().PostLocal(ctx, chat1.PostLocalArg{ 3524 ConversationID: conv.Id, 3525 Msg: chat1.MessagePlaintext{ 3526 ClientHeader: chat1.MessageClientHeader{ 3527 Conv: conv.Triple, 3528 MessageType: chat1.MessageType_DELETE, 3529 TlfName: conv.TlfName, 3530 Supersedes: msgID, 3531 }, 3532 }, 3533 }) 3534 require.NoError(t, err) 3535 return postRes.MessageID 3536 } 3537 3538 func mustEditMsg(ctx context.Context, t *testing.T, ctc *chatTestContext, user *kbtest.FakeUser, 3539 conv chat1.ConversationInfoLocal, msgID chat1.MessageID) chat1.MessageID { 3540 postRes, err := ctc.as(t, user).chatLocalHandler().PostLocal(ctx, chat1.PostLocalArg{ 3541 ConversationID: conv.Id, 3542 Msg: chat1.MessagePlaintext{ 3543 ClientHeader: chat1.MessageClientHeader{ 3544 Conv: conv.Triple, 3545 MessageType: chat1.MessageType_EDIT, 3546 TlfName: conv.TlfName, 3547 Supersedes: msgID, 3548 }, 3549 MessageBody: chat1.NewMessageBodyWithEdit(chat1.MessageEdit{ 3550 MessageID: msgID, 3551 Body: "edited", 3552 }), 3553 }, 3554 }) 3555 require.NoError(t, err) 3556 return postRes.MessageID 3557 } 3558 3559 func mustReactToMsg(ctx context.Context, t *testing.T, ctc *chatTestContext, user *kbtest.FakeUser, 3560 conv chat1.ConversationInfoLocal, msgID chat1.MessageID, reaction string) chat1.MessageID { 3561 postRes, err := ctc.as(t, user).chatLocalHandler().PostLocal(ctx, chat1.PostLocalArg{ 3562 ConversationID: conv.Id, 3563 Msg: chat1.MessagePlaintext{ 3564 ClientHeader: chat1.MessageClientHeader{ 3565 Conv: conv.Triple, 3566 MessageType: chat1.MessageType_REACTION, 3567 TlfName: conv.TlfName, 3568 Supersedes: msgID, 3569 }, 3570 MessageBody: chat1.NewMessageBodyWithReaction(chat1.MessageReaction{ 3571 MessageID: msgID, 3572 Body: reaction, 3573 }), 3574 }, 3575 }) 3576 require.NoError(t, err) 3577 return postRes.MessageID 3578 } 3579 3580 func TestChatSrvGetThreadNonblockPlaceholders(t *testing.T) { 3581 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 3582 ctc := makeChatTestContext(t, "GetThreadNonblockPlaceholders", 1) 3583 defer ctc.cleanup() 3584 users := ctc.users() 3585 3586 uid := gregor1.UID(users[0].GetUID().ToBytes()) 3587 ui := kbtest.NewChatUI() 3588 ctc.as(t, users[0]).h.mockChatUI = ui 3589 ctx := ctc.as(t, users[0]).startCtx 3590 <-ctc.as(t, users[0]).h.G().ConvLoader.Stop(ctx) 3591 listener := newServerChatListener() 3592 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener) 3593 3594 conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt) 3595 cs := ctc.world.Tcs[users[0].Username].ChatG.ConvSource 3596 msg := chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hi"}) 3597 msgID1 := mustPostLocalForTest(t, ctc, users[0], conv, msg) 3598 consumeNewMsgRemote(t, listener, chat1.MessageType_TEXT) 3599 editMsgID1 := mustEditMsg(ctx, t, ctc, users[0], conv, msgID1) 3600 consumeNewMsgRemote(t, listener, chat1.MessageType_EDIT) 3601 msgID2 := mustPostLocalForTest(t, ctc, users[0], conv, msg) 3602 consumeNewMsgRemote(t, listener, chat1.MessageType_TEXT) 3603 editMsgID2 := mustEditMsg(ctx, t, ctc, users[0], conv, msgID2) 3604 consumeNewMsgRemote(t, listener, chat1.MessageType_EDIT) 3605 msgID3 := mustPostLocalForTest(t, ctc, users[0], conv, msg) 3606 consumeNewMsgRemote(t, listener, chat1.MessageType_TEXT) 3607 msgRes, err := ctc.as(t, users[0]).chatLocalHandler().GetMessagesLocal(ctx, chat1.GetMessagesLocalArg{ 3608 ConversationID: conv.Id, 3609 MessageIDs: []chat1.MessageID{msgID3}, 3610 }) 3611 require.NoError(t, err) 3612 require.Equal(t, 1, len(msgRes.Messages)) 3613 msg3 := msgRes.Messages[0] 3614 msgIDs := []chat1.MessageID{msgID3, editMsgID2, msgID2, editMsgID1, msgID1, 1} 3615 3616 require.NoError(t, cs.Clear(context.TODO(), conv.Id, uid, nil)) 3617 3618 tc := ctc.world.Tcs[users[0].Username] 3619 rconv, err := utils.GetUnverifiedConv(ctx, tc.Context(), uid, conv.Id, 3620 types.InboxSourceDataSourceAll) 3621 require.NoError(t, err) 3622 err = cs.PushUnboxed(ctx, rconv, uid, []chat1.MessageUnboxed{msg3}) 3623 require.NoError(t, err) 3624 3625 delay := 10 * time.Minute 3626 clock := clockwork.NewFakeClock() 3627 ri := ctc.as(t, users[0]).ri 3628 uiThreadLoader := NewUIThreadLoader(tc.Context(), func() chat1.RemoteInterface { return ri }) 3629 uiThreadLoader.clock = clock 3630 uiThreadLoader.cachedThreadDelay = nil 3631 uiThreadLoader.remoteThreadDelay = &delay 3632 uiThreadLoader.validatedDelay = 0 3633 tc.ChatG.UIThreadLoader = uiThreadLoader 3634 cb := make(chan struct{}) 3635 query := chat1.GetThreadQuery{ 3636 MessageTypes: []chat1.MessageType{chat1.MessageType_TEXT}, 3637 } 3638 go func() { 3639 _, err := ctc.as(t, users[0]).chatLocalHandler().GetThreadNonblock(ctx, 3640 chat1.GetThreadNonblockArg{ 3641 ConversationID: conv.Id, 3642 Query: &query, 3643 CbMode: chat1.GetThreadNonblockCbMode_INCREMENTAL, 3644 }, 3645 ) 3646 require.NoError(t, err) 3647 close(cb) 3648 }() 3649 select { 3650 case res := <-ui.ThreadCb: 3651 require.False(t, res.Full) 3652 require.Equal(t, len(msgIDs), len(res.Thread.Messages)) 3653 require.Equal(t, msgIDs, utils.PluckUIMessageIDs(res.Thread.Messages)) 3654 confirmIsText(t, msgID3, res.Thread.Messages[0], "hi") 3655 confirmIsPlaceholder(t, editMsgID2, res.Thread.Messages[1], false) 3656 confirmIsPlaceholder(t, msgID2, res.Thread.Messages[2], false) 3657 confirmIsPlaceholder(t, editMsgID1, res.Thread.Messages[3], false) 3658 confirmIsPlaceholder(t, msgID1, res.Thread.Messages[4], false) 3659 confirmIsPlaceholder(t, 1, res.Thread.Messages[5], false) 3660 case <-time.After(20 * time.Second): 3661 require.Fail(t, "no thread cb") 3662 } 3663 recvRemote := func() bool { 3664 for i := 0; i < 5; i++ { 3665 clock.Advance(20 * time.Minute) 3666 select { 3667 case res := <-ui.ThreadCb: 3668 require.True(t, res.Full) 3669 require.Equal(t, len(msgIDs)-1, len(res.Thread.Messages)) 3670 confirmIsPlaceholder(t, editMsgID2, res.Thread.Messages[0], true) 3671 confirmIsText(t, msgID2, res.Thread.Messages[1], "edited") 3672 confirmIsPlaceholder(t, editMsgID1, res.Thread.Messages[2], true) 3673 confirmIsText(t, msgID1, res.Thread.Messages[3], "edited") 3674 confirmIsPlaceholder(t, 1, res.Thread.Messages[4], true) 3675 return true 3676 case <-time.After(2 * time.Second): 3677 t.Logf("no cb received") 3678 } 3679 } 3680 return false 3681 } 3682 require.True(t, recvRemote()) 3683 select { 3684 case <-cb: 3685 case <-time.After(20 * time.Second): 3686 require.Fail(t, "GetThread never finished") 3687 } 3688 }) 3689 } 3690 3691 func TestChatSrvGetThreadNonblockPlaceholderFirst(t *testing.T) { 3692 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 3693 ctc := makeChatTestContext(t, "GetThreadNonblockPlaceholdersFirst", 1) 3694 defer ctc.cleanup() 3695 users := ctc.users() 3696 3697 uid := gregor1.UID(users[0].GetUID().ToBytes()) 3698 ui := kbtest.NewChatUI() 3699 ctc.as(t, users[0]).h.mockChatUI = ui 3700 ctx := ctc.as(t, users[0]).startCtx 3701 <-ctc.as(t, users[0]).h.G().ConvLoader.Stop(ctx) 3702 listener := newServerChatListener() 3703 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener) 3704 3705 conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt) 3706 tc := ctc.world.Tcs[users[0].Username] 3707 cs := tc.ChatG.ConvSource 3708 msg := chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hi"}) 3709 msgID1 := mustPostLocalForTest(t, ctc, users[0], conv, msg) 3710 consumeNewMsgRemote(t, listener, chat1.MessageType_TEXT) 3711 msgID2 := mustPostLocalForTest(t, ctc, users[0], conv, msg) 3712 consumeNewMsgRemote(t, listener, chat1.MessageType_TEXT) 3713 msgRes, err := ctc.as(t, users[0]).chatLocalHandler().GetMessagesLocal(ctx, chat1.GetMessagesLocalArg{ 3714 ConversationID: conv.Id, 3715 MessageIDs: []chat1.MessageID{msgID1}, 3716 }) 3717 require.NoError(t, err) 3718 require.Equal(t, 1, len(msgRes.Messages)) 3719 msg1 := msgRes.Messages[0] 3720 msgIDs := []chat1.MessageID{msgID2, msgID1, 1} 3721 3722 require.NoError(t, cs.Clear(context.TODO(), conv.Id, uid, nil)) 3723 rconv, err := utils.GetUnverifiedConv(ctx, tc.Context(), uid, conv.Id, 3724 types.InboxSourceDataSourceAll) 3725 require.NoError(t, err) 3726 err = cs.PushUnboxed(ctx, rconv, uid, []chat1.MessageUnboxed{msg1}) 3727 require.NoError(t, err) 3728 3729 delay := 10 * time.Minute 3730 clock := clockwork.NewFakeClock() 3731 ri := ctc.as(t, users[0]).ri 3732 uiThreadLoader := NewUIThreadLoader(tc.Context(), func() chat1.RemoteInterface { return ri }) 3733 uiThreadLoader.clock = clock 3734 uiThreadLoader.cachedThreadDelay = nil 3735 uiThreadLoader.remoteThreadDelay = &delay 3736 uiThreadLoader.validatedDelay = 0 3737 tc.ChatG.UIThreadLoader = uiThreadLoader 3738 cb := make(chan struct{}) 3739 query := chat1.GetThreadQuery{ 3740 MessageTypes: []chat1.MessageType{chat1.MessageType_TEXT}, 3741 } 3742 go func() { 3743 _, err := ctc.as(t, users[0]).chatLocalHandler().GetThreadNonblock(ctx, 3744 chat1.GetThreadNonblockArg{ 3745 ConversationID: conv.Id, 3746 Query: &query, 3747 CbMode: chat1.GetThreadNonblockCbMode_INCREMENTAL, 3748 }, 3749 ) 3750 require.NoError(t, err) 3751 close(cb) 3752 }() 3753 clock.Advance(50 * time.Millisecond) 3754 select { 3755 case res := <-ui.ThreadCb: 3756 require.False(t, res.Full) 3757 require.Equal(t, len(msgIDs), len(res.Thread.Messages)) 3758 require.Equal(t, msgIDs, utils.PluckUIMessageIDs(res.Thread.Messages)) 3759 confirmIsPlaceholder(t, msgID2, res.Thread.Messages[0], false) 3760 confirmIsText(t, msgID1, res.Thread.Messages[1], "hi") 3761 confirmIsPlaceholder(t, 1, res.Thread.Messages[2], false) 3762 case <-time.After(20 * time.Second): 3763 require.Fail(t, "no thread cb") 3764 } 3765 clock.Advance(20 * time.Minute) 3766 select { 3767 case res := <-ui.ThreadCb: 3768 require.True(t, res.Full) 3769 require.Equal(t, len(msgIDs)-1, len(res.Thread.Messages)) 3770 confirmIsText(t, msgID2, res.Thread.Messages[0], "hi") 3771 confirmIsPlaceholder(t, 1, res.Thread.Messages[1], true) 3772 case <-time.After(20 * time.Second): 3773 require.Fail(t, "no thread cb") 3774 } 3775 select { 3776 case <-cb: 3777 case <-time.After(20 * time.Second): 3778 require.Fail(t, "GetThread never finished") 3779 } 3780 }) 3781 } 3782 3783 func TestChatSrvGetThreadNonblockOldPages(t *testing.T) { 3784 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 3785 if mt == chat1.ConversationMembersType_KBFS { 3786 return 3787 } 3788 ctc := makeChatTestContext(t, "GetThreadNonblock", 1) 3789 defer ctc.cleanup() 3790 users := ctc.users() 3791 3792 uid := gregor1.UID(users[0].GetUID().ToBytes()) 3793 ui := kbtest.NewChatUI() 3794 ctc.as(t, users[0]).h.mockChatUI = ui 3795 ctx := ctc.as(t, users[0]).startCtx 3796 bgConvLoads := make(chan chat1.ConversationID, 10) 3797 ctc.as(t, users[0]).h.G().ConvLoader.Start(ctx, uid) 3798 ctc.as(t, users[0]).h.G().ConvLoader.(*BackgroundConvLoader).loads = bgConvLoads 3799 3800 t.Logf("send a bunch of messages") 3801 conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt) 3802 select { 3803 case <-bgConvLoads: 3804 case <-time.After(20 * time.Second): 3805 require.Fail(t, "no bkg load") 3806 } 3807 numMsgs := 20 3808 msg := chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hi"}) 3809 for i := 0; i < numMsgs; i++ { 3810 mustPostLocalForTest(t, ctc, users[0], conv, msg) 3811 } 3812 select { 3813 case <-bgConvLoads: 3814 require.Fail(t, "no more bg loads") 3815 default: 3816 } 3817 _, err := ctc.as(t, users[0]).chatLocalHandler().GetThreadNonblock(ctx, 3818 chat1.GetThreadNonblockArg{ 3819 ConversationID: conv.Id, 3820 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 3821 Pagination: utils.PresentPagination(&chat1.Pagination{Num: 1}), 3822 }, 3823 ) 3824 require.NoError(t, err) 3825 res := receiveThreadResult(t, ui.ThreadCb) 3826 require.Equal(t, 1, len(res.Messages)) 3827 select { 3828 case <-bgConvLoads: 3829 case <-time.After(20 * time.Second): 3830 require.Fail(t, "no bkg load") 3831 } 3832 select { 3833 case <-bgConvLoads: 3834 require.Fail(t, "no more bg loads") 3835 default: 3836 } 3837 }) 3838 } 3839 3840 func TestChatSrvGetThreadNonblock(t *testing.T) { 3841 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 3842 ctc := makeChatTestContext(t, "GetThreadNonblock", 1) 3843 defer ctc.cleanup() 3844 users := ctc.users() 3845 3846 ui := kbtest.NewChatUI() 3847 ctc.as(t, users[0]).h.mockChatUI = ui 3848 3849 t.Logf("test empty thread") 3850 query := chat1.GetThreadQuery{ 3851 MessageTypes: []chat1.MessageType{chat1.MessageType_TEXT}, 3852 } 3853 ctx := ctc.as(t, users[0]).startCtx 3854 conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt) 3855 _, err := ctc.as(t, users[0]).chatLocalHandler().GetThreadNonblock(ctx, 3856 chat1.GetThreadNonblockArg{ 3857 ConversationID: conv.Id, 3858 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 3859 Query: &query, 3860 }, 3861 ) 3862 require.NoError(t, err) 3863 res := receiveThreadResult(t, ui.ThreadCb) 3864 require.Zero(t, len(res.Messages)) 3865 3866 t.Logf("send a bunch of messages") 3867 numMsgs := 20 3868 msg := chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hi"}) 3869 for i := 0; i < numMsgs; i++ { 3870 mustPostLocalForTest(t, ctc, users[0], conv, msg) 3871 } 3872 3873 t.Logf("read back full thread") 3874 _, err = ctc.as(t, users[0]).chatLocalHandler().GetThreadNonblock(ctx, 3875 chat1.GetThreadNonblockArg{ 3876 ConversationID: conv.Id, 3877 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 3878 Query: &query, 3879 }, 3880 ) 3881 require.NoError(t, err) 3882 res = receiveThreadResult(t, ui.ThreadCb) 3883 require.Equal(t, numMsgs, len(res.Messages)) 3884 3885 t.Logf("read back with a delay on the local pull") 3886 3887 delay := 10 * time.Minute 3888 clock := clockwork.NewFakeClock() 3889 tc := ctc.world.Tcs[users[0].Username] 3890 ri := ctc.as(t, users[0]).ri 3891 uiThreadLoader := NewUIThreadLoader(tc.Context(), func() chat1.RemoteInterface { return ri }) 3892 uiThreadLoader.clock = clock 3893 uiThreadLoader.cachedThreadDelay = &delay 3894 tc.ChatG.UIThreadLoader = uiThreadLoader 3895 _, err = ctc.as(t, users[0]).chatLocalHandler().GetThreadNonblock(ctx, 3896 chat1.GetThreadNonblockArg{ 3897 ConversationID: conv.Id, 3898 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 3899 Query: &query, 3900 }, 3901 ) 3902 require.NoError(t, err) 3903 res = receiveThreadResult(t, ui.ThreadCb) 3904 require.Equal(t, numMsgs, len(res.Messages)) 3905 clock.Advance(20 * time.Minute) 3906 select { 3907 case <-ui.ThreadCb: 3908 require.Fail(t, "no cb expected") 3909 default: 3910 } 3911 }) 3912 } 3913 3914 func TestChatSrvGetThreadNonblockError(t *testing.T) { 3915 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 3916 ctc := makeChatTestContext(t, "GetThreadNonblock", 1) 3917 defer ctc.cleanup() 3918 users := ctc.users() 3919 3920 listener := newServerChatListener() 3921 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener) 3922 3923 uid := users[0].User.GetUID().ToBytes() 3924 ui := kbtest.NewChatUI() 3925 ctc.as(t, users[0]).h.mockChatUI = ui 3926 3927 query := chat1.GetThreadQuery{ 3928 MessageTypes: []chat1.MessageType{chat1.MessageType_TEXT}, 3929 } 3930 conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt) 3931 numMsgs := 20 3932 msg := chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hi"}) 3933 for i := 0; i < numMsgs; i++ { 3934 mustPostLocalForTest(t, ctc, users[0], conv, msg) 3935 } 3936 require.NoError(t, 3937 ctc.world.Tcs[users[0].Username].ChatG.ConvSource.Clear(context.TODO(), conv.Id, uid, nil)) 3938 g := ctc.world.Tcs[users[0].Username].ChatG 3939 ri := ctc.as(t, users[0]).ri 3940 g.UIThreadLoader.(*UIThreadLoader).SetRemoteInterface(func() chat1.RemoteInterface { 3941 return chat1.RemoteClient{Cli: errorClient{}} 3942 }) 3943 3944 ctx := ctc.as(t, users[0]).startCtx 3945 _, err := ctc.as(t, users[0]).chatLocalHandler().GetThreadNonblock(ctx, 3946 chat1.GetThreadNonblockArg{ 3947 ConversationID: conv.Id, 3948 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 3949 Query: &query, 3950 }, 3951 ) 3952 require.Error(t, err) 3953 3954 // Advance clock and look for stale 3955 g.UIThreadLoader.(*UIThreadLoader).SetRemoteInterface(func() chat1.RemoteInterface { return ri }) 3956 ctc.world.Fc.Advance(time.Hour) 3957 3958 updates := consumeNewThreadsStale(t, listener) 3959 require.Equal(t, 1, len(updates)) 3960 require.Equal(t, chat1.StaleUpdateType_NEWACTIVITY, updates[0].UpdateType) 3961 }) 3962 } 3963 3964 var errGetInboxNonblockFailingUI = errors.New("get outta here") 3965 3966 type getInboxNonblockFailingUI struct { 3967 *kbtest.ChatUI 3968 failUnverified, failVerified bool 3969 } 3970 3971 func (u *getInboxNonblockFailingUI) ChatInboxUnverified(ctx context.Context, 3972 arg chat1.ChatInboxUnverifiedArg) error { 3973 if u.failUnverified { 3974 return errGetInboxNonblockFailingUI 3975 } 3976 return u.ChatUI.ChatInboxUnverified(ctx, arg) 3977 } 3978 3979 func (u *getInboxNonblockFailingUI) ChatInboxConversation(ctx context.Context, 3980 arg chat1.ChatInboxConversationArg) error { 3981 if u.failVerified { 3982 return errGetInboxNonblockFailingUI 3983 } 3984 return u.ChatUI.ChatInboxConversation(ctx, arg) 3985 } 3986 3987 func TestChatSrvGetInboxNonblockChatUIError(t *testing.T) { 3988 useRemoteMock = false 3989 defer func() { useRemoteMock = true }() 3990 ctc := makeChatTestContext(t, "TestChatSrvGetInboxNonblockChatUIError", 2) 3991 defer ctc.cleanup() 3992 3993 timeout := 2 * time.Second 3994 users := ctc.users() 3995 tc := ctc.world.Tcs[users[0].Username] 3996 ctx := ctc.as(t, users[0]).startCtx 3997 tui := kbtest.NewChatUI() 3998 ui := &getInboxNonblockFailingUI{ChatUI: tui, failUnverified: true, failVerified: true} 3999 ctc.as(t, users[0]).h.mockChatUI = ui 4000 tc.G.UIRouter = kbtest.NewMockUIRouter(ui) 4001 uid := gregor1.UID(users[0].GetUID().ToBytes()) 4002 tc.ChatG.UIInboxLoader = NewUIInboxLoader(tc.Context()) 4003 tc.ChatG.UIInboxLoader.Start(ctx, uid) 4004 defer func() { <-tc.ChatG.UIInboxLoader.Stop(ctx) }() 4005 4006 listener0 := newServerChatListener() 4007 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener0) 4008 4009 conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 4010 chat1.ConversationMembersType_IMPTEAMNATIVE) 4011 mustPostLocalForTest(t, ctc, users[0], conv, chat1.NewMessageBodyWithText(chat1.MessageText{ 4012 Body: "HIIHIHIHI", 4013 })) 4014 _, err := ctc.as(t, users[0]).chatLocalHandler().GetInboxNonblockLocal(ctx, 4015 chat1.GetInboxNonblockLocalArg{ 4016 Query: &chat1.GetInboxLocalQuery{ 4017 ConvIDs: []chat1.ConversationID{conv.Id}, 4018 }, 4019 }) 4020 require.NoError(t, err) 4021 waitForThreadStale := func() { 4022 for i := 0; i < 5; i++ { 4023 tc.Context().FetchRetrier.Force(ctx) 4024 select { 4025 case <-listener0.threadsStale: 4026 return 4027 case <-time.After(timeout): 4028 t.Logf("no threads stale yet, trying again") 4029 } 4030 } 4031 require.Fail(t, "no threads stale") 4032 } 4033 waitForThreadStale() 4034 4035 _, err = ctc.as(t, users[0]).chatLocalHandler().GetInboxNonblockLocal(ctx, 4036 chat1.GetInboxNonblockLocalArg{}) 4037 require.NoError(t, err) 4038 tc.Context().FetchRetrier.Force(ctx) 4039 select { 4040 case <-listener0.inboxStale: 4041 case <-time.After(timeout): 4042 require.Fail(t, "no inbox stale") 4043 } 4044 4045 ui.failUnverified = false 4046 _, err = ctc.as(t, users[0]).chatLocalHandler().GetInboxNonblockLocal(ctx, 4047 chat1.GetInboxNonblockLocalArg{ 4048 Query: &chat1.GetInboxLocalQuery{ 4049 ConvIDs: []chat1.ConversationID{conv.Id}, 4050 }, 4051 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 4052 }) 4053 require.NoError(t, err) 4054 select { 4055 case <-ui.InboxCb: 4056 case <-time.After(timeout): 4057 require.Fail(t, "no untrusted inbox") 4058 } 4059 tc.Context().FetchRetrier.Force(ctx) 4060 select { 4061 case upds := <-listener0.threadsStale: 4062 require.Equal(t, 1, len(upds)) 4063 require.Equal(t, conv.Id, upds[0].ConvID) 4064 case <-time.After(timeout): 4065 require.Fail(t, "no conv stale") 4066 } 4067 } 4068 4069 func TestChatSrvGetInboxNonblockError(t *testing.T) { 4070 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 4071 ctc := makeChatTestContext(t, "GetInboxNonblockLocal", 1) 4072 defer ctc.cleanup() 4073 users := ctc.users() 4074 4075 listener := newServerChatListener() 4076 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener) 4077 4078 timeout := 2 * time.Second 4079 ctx := ctc.as(t, users[0]).startCtx 4080 uid := users[0].User.GetUID().ToBytes() 4081 ui := kbtest.NewChatUI() 4082 ctc.as(t, users[0]).h.mockChatUI = ui 4083 tc := ctc.world.Tcs[users[0].Username] 4084 tc.G.UIRouter = kbtest.NewMockUIRouter(ui) 4085 <-ctc.as(t, users[0]).h.G().ConvLoader.Stop(ctx) 4086 listener0 := newServerChatListener() 4087 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener0) 4088 tc.ChatG.UIInboxLoader = NewUIInboxLoader(tc.Context()) 4089 tc.ChatG.UIInboxLoader.Start(ctx, uid) 4090 defer func() { <-tc.ChatG.UIInboxLoader.Stop(ctx) }() 4091 4092 conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt) 4093 numMsgs := 20 4094 msg := chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hi"}) 4095 for i := 0; i < numMsgs; i++ { 4096 mustPostLocalForTest(t, ctc, users[0], conv, msg) 4097 consumeNewMsgRemote(t, listener0, chat1.MessageType_TEXT) 4098 } 4099 g := ctc.world.Tcs[users[0].Username].Context() 4100 g.ConvSource.SetRemoteInterface(func() chat1.RemoteInterface { 4101 return chat1.RemoteClient{Cli: errorClient{}} 4102 }) 4103 require.NoError(t, 4104 ctc.world.Tcs[users[0].Username].ChatG.ConvSource.Clear(context.TODO(), conv.Id, uid, nil)) 4105 ri := ctc.as(t, users[0]).ri 4106 4107 _, err := ctc.as(t, users[0]).chatLocalHandler().GetInboxNonblockLocal(ctx, 4108 chat1.GetInboxNonblockLocalArg{ 4109 Query: &chat1.GetInboxLocalQuery{ 4110 ConvIDs: []chat1.ConversationID{conv.Id}, 4111 }, 4112 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 4113 }) 4114 require.NoError(t, err) 4115 4116 // Eat untrusted CB 4117 select { 4118 case <-ui.InboxCb: 4119 case <-time.After(20 * time.Second): 4120 require.Fail(t, "no untrusted inbox") 4121 } 4122 4123 select { 4124 case nbres := <-ui.InboxCb: 4125 require.Error(t, nbres.Err) 4126 case <-time.After(20 * time.Second): 4127 require.Fail(t, "no inbox load event") 4128 } 4129 4130 // Advance clock and look for stale 4131 g.ConvSource.SetRemoteInterface(func() chat1.RemoteInterface { 4132 return ri 4133 }) 4134 waitForThreadsStale := func() { 4135 for i := 0; i < 5; i++ { 4136 tc.Context().FetchRetrier.Force(ctx) 4137 select { 4138 case updates := <-listener0.threadsStale: 4139 require.Equal(t, 1, len(updates)) 4140 require.Equal(t, chat1.StaleUpdateType_NEWACTIVITY, updates[0].UpdateType) 4141 return 4142 case <-time.After(timeout): 4143 t.Logf("no threads stale yet, trying again") 4144 } 4145 } 4146 } 4147 waitForThreadsStale() 4148 4149 t.Logf("testing untrusted inbox load failure") 4150 ttype := chat1.TopicType_CHAT 4151 require.NoError(t, storage.NewInbox(g).Clear(context.TODO(), uid)) 4152 g.InboxSource.SetRemoteInterface(func() chat1.RemoteInterface { 4153 return chat1.RemoteClient{Cli: errorClient{}} 4154 }) 4155 query := &chat1.GetInboxLocalQuery{ 4156 TopicType: &ttype, 4157 } 4158 _, err = ctc.as(t, users[0]).chatLocalHandler().GetInboxNonblockLocal(ctx, 4159 chat1.GetInboxNonblockLocalArg{ 4160 Query: query, 4161 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 4162 }) 4163 require.Error(t, err) 4164 g.InboxSource.SetRemoteInterface(func() chat1.RemoteInterface { 4165 return ri 4166 }) 4167 ctc.world.Fc.Advance(time.Hour) 4168 select { 4169 case <-listener.inboxStale: 4170 case <-time.After(20 * time.Second): 4171 require.Fail(t, "no threads stale message received") 4172 } 4173 4174 rquery, _, err := g.InboxSource.GetInboxQueryLocalToRemote(context.TODO(), query) 4175 require.NoError(t, err) 4176 _, lconvs, err := storage.NewInbox(g).Read(context.TODO(), uid, rquery) 4177 require.NoError(t, err) 4178 require.Equal(t, 1, len(lconvs)) 4179 require.Equal(t, lconvs[0].GetConvID(), conv.Id) 4180 }) 4181 } 4182 4183 func TestChatSrvMakePreview(t *testing.T) { 4184 ctc := makeChatTestContext(t, "MakePreview", 1) 4185 defer ctc.cleanup() 4186 user := ctc.users()[0] 4187 4188 // make a preview of a jpg 4189 outboxID, err := storage.NewOutboxID() 4190 require.NoError(t, err) 4191 arg := chat1.MakePreviewArg{ 4192 Filename: "testdata/ship.jpg", 4193 OutboxID: outboxID, 4194 } 4195 ri := ctc.as(t, user).ri 4196 tc := ctc.world.Tcs[user.Username] 4197 tc.ChatG.AttachmentURLSrv = NewAttachmentHTTPSrv(tc.Context(), 4198 manager.NewSrv(tc.Context().ExternalG()), 4199 types.DummyAttachmentFetcher{}, 4200 func() chat1.RemoteInterface { return ri }) 4201 res, err := ctc.as(t, user).chatLocalHandler().MakePreview(context.TODO(), arg) 4202 require.NoError(t, err) 4203 require.NotNil(t, res.Location) 4204 typ, err := res.Location.Ltyp() 4205 require.NoError(t, err) 4206 require.Equal(t, chat1.PreviewLocationTyp_URL, typ) 4207 resp, err := http.Get(res.Location.Url()) 4208 require.NoError(t, err) 4209 require.Equal(t, 200, resp.StatusCode) 4210 require.NotNil(t, res.Metadata) 4211 require.Equal(t, "image/jpeg", res.MimeType) 4212 img := res.Metadata.Image() 4213 require.Equal(t, 640, img.Width) 4214 require.Equal(t, 480, img.Height) 4215 4216 // MakePreview(pdf) shouldn't generate a preview file, but should return mimetype 4217 outboxID, err = storage.NewOutboxID() 4218 require.NoError(t, err) 4219 arg = chat1.MakePreviewArg{ 4220 Filename: "testdata/weather.pdf", 4221 OutboxID: outboxID, 4222 } 4223 res, err = ctc.as(t, user).chatLocalHandler().MakePreview(context.TODO(), arg) 4224 require.NoError(t, err) 4225 require.Nil(t, res.Location) 4226 require.Nil(t, res.Metadata) 4227 require.Equal(t, "application/pdf", res.MimeType) 4228 } 4229 4230 func inMessageTypes(x chat1.MessageType, ys []chat1.MessageType) bool { 4231 for _, y := range ys { 4232 if x == y { 4233 return true 4234 } 4235 } 4236 return false 4237 } 4238 4239 func consumeNewPendingMsg(t *testing.T, listener *serverChatListener) { 4240 select { 4241 case msg := <-listener.newMessageLocal: 4242 require.True(t, msg.Message.IsOutbox()) 4243 case <-time.After(20 * time.Second): 4244 require.Fail(t, "failed to get new pending message notification") 4245 } 4246 } 4247 4248 func consumeNewMsgLocal(t *testing.T, listener *serverChatListener, typ chat1.MessageType) chat1.UIMessage { 4249 return consumeNewMsgWhileIgnoring(t, listener, typ, nil, chat1.ChatActivitySource_LOCAL) 4250 } 4251 4252 func consumeNewMsgRemote(t *testing.T, listener *serverChatListener, typ chat1.MessageType) chat1.UIMessage { 4253 return consumeNewMsgWhileIgnoring(t, listener, typ, nil, chat1.ChatActivitySource_REMOTE) 4254 } 4255 4256 func consumeNewMsgWhileIgnoring(t *testing.T, listener *serverChatListener, typ chat1.MessageType, 4257 ignoreTypes []chat1.MessageType, source chat1.ChatActivitySource) chat1.UIMessage { 4258 require.False(t, inMessageTypes(typ, ignoreTypes), "can't ignore the hunted") 4259 timeoutCh := time.After(20 * time.Second) 4260 var newMsgCh chan chat1.IncomingMessage 4261 switch source { 4262 case chat1.ChatActivitySource_LOCAL: 4263 newMsgCh = listener.newMessageLocal 4264 case chat1.ChatActivitySource_REMOTE: 4265 newMsgCh = listener.newMessageRemote 4266 } 4267 for { 4268 select { 4269 case msg := <-newMsgCh: 4270 rtyp := msg.Message.GetMessageType() 4271 ignore := inMessageTypes(rtyp, ignoreTypes) 4272 ignoredStr := "" 4273 if ignore { 4274 ignoredStr = " (ignored)" 4275 } 4276 t.Logf("consumed newMessage(%v): %v%v", source, msg.Message.GetMessageType(), ignoredStr) 4277 if !ignore { 4278 require.Equal(t, typ, msg.Message.GetMessageType()) 4279 return msg.Message 4280 } 4281 case <-timeoutCh: 4282 require.Fail(t, fmt.Sprintf("failed to get newMessage %v notification: %v", source, typ)) 4283 return chat1.UIMessage{} 4284 } 4285 } 4286 } 4287 4288 func consumeNewThreadsStale(t *testing.T, listener *serverChatListener) []chat1.ConversationStaleUpdate { 4289 select { 4290 case updates := <-listener.threadsStale: 4291 return updates 4292 case <-time.After(20 * time.Second): 4293 require.Fail(t, "no threads stale message received") 4294 } 4295 return nil 4296 } 4297 4298 func assertNoNewConversation(t *testing.T, listener *serverChatListener) { 4299 select { 4300 case <-listener.newConversation: 4301 require.Fail(t, "unexpected newConversation") 4302 default: 4303 } 4304 } 4305 4306 func consumeNewConversation(t *testing.T, listener *serverChatListener, convID chat1.ConversationID) { 4307 select { 4308 case convInfo := <-listener.newConversation: 4309 require.Equal(t, convID, convInfo.ConvID) 4310 require.NotNil(t, convInfo.Conv) 4311 case <-time.After(20 * time.Second): 4312 require.Fail(t, "failed to get new conversation notification") 4313 } 4314 } 4315 4316 func consumeTeamType(t *testing.T, listener *serverChatListener) { 4317 select { 4318 case <-listener.teamType: 4319 case <-time.After(20 * time.Second): 4320 require.Fail(t, "failed to get team type notification") 4321 } 4322 } 4323 4324 func consumeMembersUpdate(t *testing.T, listener *serverChatListener) { 4325 select { 4326 case <-listener.membersUpdate: 4327 case <-time.After(20 * time.Second): 4328 require.Fail(t, "failed to get members update notification") 4329 } 4330 } 4331 4332 func consumeJoinConv(t *testing.T, listener *serverChatListener) { 4333 select { 4334 case <-listener.joinedConv: 4335 case <-time.After(20 * time.Second): 4336 require.Fail(t, "failed to get join conv notification") 4337 } 4338 } 4339 4340 func consumeLeaveConv(t *testing.T, listener *serverChatListener) { 4341 select { 4342 case <-listener.leftConv: 4343 case <-time.After(20 * time.Second): 4344 require.Fail(t, "failed to get leave conv notification") 4345 } 4346 } 4347 4348 func consumeSetConvRetention(t *testing.T, listener *serverChatListener) chat1.ConversationID { 4349 select { 4350 case x := <-listener.setConvRetention: 4351 return x 4352 case <-time.After(20 * time.Second): 4353 require.Fail(t, "failed to get setConvRetention notification") 4354 return chat1.ConversationID{} 4355 } 4356 } 4357 4358 func consumeSetTeamRetention(t *testing.T, listener *serverChatListener) (res keybase1.TeamID) { 4359 select { 4360 case x := <-listener.setTeamRetention: 4361 return x 4362 case <-time.After(20 * time.Second): 4363 require.Fail(t, "failed to get setTeamRetention notification") 4364 return res 4365 } 4366 } 4367 4368 func consumeSetConvSettings(t *testing.T, listener *serverChatListener) chat1.ConversationID { 4369 select { 4370 case x := <-listener.setConvSettings: 4371 return x 4372 case <-time.After(20 * time.Second): 4373 require.Fail(t, "failed to get setConvSettings notification") 4374 return chat1.ConversationID{} 4375 } 4376 } 4377 4378 func consumeSubteamRename(t *testing.T, listener *serverChatListener) []chat1.ConversationID { 4379 select { 4380 case x := <-listener.subteamRename: 4381 return x 4382 case <-time.After(20 * time.Second): 4383 require.Fail(t, "failed to get subteamRename notification") 4384 return nil 4385 } 4386 } 4387 4388 func consumeExpunge(t *testing.T, listener *serverChatListener) chat1.ExpungeInfo { 4389 select { 4390 case x := <-listener.expunge: 4391 return x 4392 case <-time.After(20 * time.Second): 4393 require.Fail(t, "failed to get expunge notification") 4394 return chat1.ExpungeInfo{} 4395 } 4396 } 4397 4398 func consumeReactionUpdate(t *testing.T, listener *serverChatListener) chat1.ReactionUpdateNotif { 4399 select { 4400 case x := <-listener.reactionUpdate: 4401 return x 4402 case <-time.After(20 * time.Second): 4403 require.Fail(t, "failed to get reactionUpdate notification") 4404 return chat1.ReactionUpdateNotif{} 4405 } 4406 } 4407 4408 func TestChatSrvTeamChannels(t *testing.T) { 4409 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 4410 ctc := makeChatTestContext(t, "TestChatTeamChannels", 3) 4411 defer ctc.cleanup() 4412 users := ctc.users() 4413 4414 // Only run this test for teams 4415 switch mt { 4416 case chat1.ConversationMembersType_TEAM: 4417 default: 4418 return 4419 } 4420 4421 ctx := ctc.as(t, users[0]).startCtx 4422 ctx1 := ctc.as(t, users[1]).startCtx 4423 ctx2 := ctc.as(t, users[2]).startCtx 4424 4425 listener0 := newServerChatListener() 4426 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener0) 4427 ctc.world.Tcs[users[0].Username].ChatG.Syncer.(*Syncer).isConnected = true 4428 4429 listener1 := newServerChatListener() 4430 ctc.as(t, users[1]).h.G().NotifyRouter.AddListener(listener1) 4431 ctc.world.Tcs[users[1].Username].ChatG.Syncer.(*Syncer).isConnected = true 4432 4433 listener2 := newServerChatListener() 4434 ctc.as(t, users[2]).h.G().NotifyRouter.AddListener(listener2) 4435 ctc.world.Tcs[users[2].Username].ChatG.Syncer.(*Syncer).isConnected = true 4436 4437 conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 4438 mt, ctc.as(t, users[1]).user(), ctc.as(t, users[2]).user()) 4439 t.Logf("first conv: %s", conv.Id) 4440 consumeNewConversation(t, listener0, conv.Id) 4441 consumeNewConversation(t, listener1, conv.Id) 4442 consumeNewConversation(t, listener2, conv.Id) 4443 4444 t.Logf("create a conversation, and join user 1 into by sending a message") 4445 topicName := "zjoinonsend" 4446 ncres, err := ctc.as(t, users[0]).chatLocalHandler().NewConversationLocal(ctx, 4447 chat1.NewConversationLocalArg{ 4448 TlfName: conv.TlfName, 4449 TopicName: &topicName, 4450 TopicType: chat1.TopicType_CHAT, 4451 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 4452 MembersType: chat1.ConversationMembersType_TEAM, 4453 }) 4454 require.NoError(t, err) 4455 consumeNewConversation(t, listener0, ncres.Conv.GetConvID()) 4456 assertNoNewConversation(t, listener1) 4457 assertNoNewConversation(t, listener2) 4458 consumeNewMsgRemote(t, listener0, chat1.MessageType_JOIN) 4459 consumeNewMsgRemote(t, listener0, chat1.MessageType_SYSTEM) 4460 consumeNewMsgRemote(t, listener0, chat1.MessageType_SYSTEM) 4461 consumeNewMsgRemote(t, listener1, chat1.MessageType_SYSTEM) 4462 consumeNewMsgRemote(t, listener1, chat1.MessageType_SYSTEM) 4463 consumeNewMsgRemote(t, listener2, chat1.MessageType_SYSTEM) 4464 consumeNewMsgRemote(t, listener2, chat1.MessageType_SYSTEM) 4465 _, err = postLocalForTest(t, ctc, users[1], ncres.Conv.Info, chat1.NewMessageBodyWithText(chat1.MessageText{ 4466 Body: "JOINME", 4467 })) 4468 require.NoError(t, err) 4469 consumeAllMsgJoins := func(listener *serverChatListener) { 4470 msgMap := make(map[chat1.MessageType]bool) 4471 rounds := 2 4472 for i := 0; i < rounds; i++ { 4473 select { 4474 case msg := <-listener.newMessageRemote: 4475 t.Logf("recvd: %v convID: %s", msg.Message.GetMessageType(), msg.ConvID) 4476 msgMap[msg.Message.GetMessageType()] = true 4477 case <-time.After(20 * time.Second): 4478 require.Fail(t, "missing incoming") 4479 } 4480 } 4481 require.True(t, msgMap[chat1.MessageType_TEXT]) 4482 require.True(t, msgMap[chat1.MessageType_JOIN]) 4483 } 4484 consumeAllMsgJoins(listener0) 4485 consumeAllMsgJoins(listener1) 4486 select { 4487 case conv := <-listener1.joinedConv: 4488 require.Equal(t, conv.GetConvID(), ncres.Conv.GetConvID()) 4489 require.Equal(t, topicName, conv.Channel) 4490 case <-time.After(20 * time.Second): 4491 require.Fail(t, "failed to get joined notification") 4492 } 4493 select { 4494 case act := <-listener0.membersUpdate: 4495 require.Equal(t, act.ConvID, ncres.Conv.GetConvID()) 4496 require.Equal(t, 1, len(act.Members)) 4497 require.Equal(t, chat1.ConversationMemberStatus_ACTIVE, act.Members[0].Status) 4498 require.Equal(t, users[1].Username, act.Members[0].Member) 4499 case <-time.After(20 * time.Second): 4500 require.Fail(t, "failed to get members update") 4501 } 4502 4503 t.Logf("send headline on first convo") 4504 headline := "The headline is foobar!" 4505 _, err = postLocalForTest(t, ctc, users[1], ncres.Conv.Info, chat1.NewMessageBodyWithHeadline(chat1.MessageHeadline{Headline: headline})) 4506 require.NoError(t, err) 4507 consumeNewMsgRemote(t, listener0, chat1.MessageType_HEADLINE) 4508 consumeNewMsgRemote(t, listener1, chat1.MessageType_HEADLINE) 4509 4510 t.Logf("create a new channel, and check GetTLFConversation result") 4511 topicName = "miketime" 4512 ncres, err = ctc.as(t, users[0]).chatLocalHandler().NewConversationLocal(ctx, 4513 chat1.NewConversationLocalArg{ 4514 TlfName: conv.TlfName, 4515 TopicName: &topicName, 4516 TopicType: chat1.TopicType_CHAT, 4517 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 4518 MembersType: chat1.ConversationMembersType_TEAM, 4519 }) 4520 require.NoError(t, err) 4521 consumeNewConversation(t, listener0, ncres.Conv.GetConvID()) 4522 assertNoNewConversation(t, listener1) 4523 assertNoNewConversation(t, listener2) 4524 consumeNewMsgRemote(t, listener0, chat1.MessageType_JOIN) 4525 consumeNewMsgRemote(t, listener0, chat1.MessageType_SYSTEM) 4526 consumeNewMsgRemote(t, listener1, chat1.MessageType_SYSTEM) 4527 consumeNewMsgRemote(t, listener2, chat1.MessageType_SYSTEM) 4528 getTLFRes, err := ctc.as(t, users[1]).chatLocalHandler().GetTLFConversationsLocal(ctx1, 4529 chat1.GetTLFConversationsLocalArg{ 4530 TlfName: conv.TlfName, 4531 TopicType: chat1.TopicType_CHAT, 4532 MembersType: chat1.ConversationMembersType_TEAM, 4533 }) 4534 require.NoError(t, err) 4535 require.Equal(t, 3, len(getTLFRes.Convs)) 4536 require.Equal(t, globals.DefaultTeamTopic, getTLFRes.Convs[0].Channel) 4537 require.Equal(t, topicName, getTLFRes.Convs[1].Channel) 4538 creatorInfo := getTLFRes.Convs[2].CreatorInfo 4539 require.NotNil(t, creatorInfo) 4540 require.Equal(t, creatorInfo.Username, users[0].Username) 4541 tvres, err := ctc.as(t, users[0]).chatLocalHandler().GetThreadLocal(ctx, chat1.GetThreadLocalArg{ 4542 ConversationID: getTLFRes.Convs[2].GetConvID(), 4543 }) 4544 require.NoError(t, err) 4545 require.Equal(t, headline, tvres.Thread.Messages[0].Valid().MessageBody.Headline().Headline) 4546 4547 t.Logf("join user 1 into new convo manually") 4548 _, err = ctc.as(t, users[1]).chatLocalHandler().JoinConversationLocal(ctx1, chat1.JoinConversationLocalArg{ 4549 TlfName: conv.TlfName, 4550 TopicType: chat1.TopicType_CHAT, 4551 Visibility: keybase1.TLFVisibility_PRIVATE, 4552 TopicName: topicName, 4553 }) 4554 require.NoError(t, err) 4555 consumeNewMsgRemote(t, listener0, chat1.MessageType_JOIN) 4556 consumeNewMsgRemote(t, listener1, chat1.MessageType_JOIN) 4557 select { 4558 case conv := <-listener1.joinedConv: 4559 require.Equal(t, conv.GetConvID(), getTLFRes.Convs[1].GetConvID()) 4560 require.Equal(t, topicName, conv.Channel) 4561 case <-time.After(20 * time.Second): 4562 require.Fail(t, "failed to get joined notification") 4563 } 4564 select { 4565 case act := <-listener0.membersUpdate: 4566 require.Equal(t, act.ConvID, getTLFRes.Convs[1].GetConvID()) 4567 require.Equal(t, 1, len(act.Members)) 4568 require.Equal(t, chat1.ConversationMemberStatus_ACTIVE, act.Members[0].Status) 4569 require.Equal(t, users[1].Username, act.Members[0].Member) 4570 case <-time.After(20 * time.Second): 4571 require.Fail(t, "failed to get members update") 4572 } 4573 4574 t.Logf("user 0 removes user 1 from the new convo") 4575 _, err = ctc.as(t, users[0]).chatLocalHandler().RemoveFromConversationLocal(ctx, chat1.RemoveFromConversationLocalArg{ 4576 ConvID: ncres.Conv.GetConvID(), 4577 Usernames: []string{users[1].Username}, 4578 }) 4579 require.NoError(t, err) 4580 select { 4581 case convID := <-listener1.leftConv: 4582 require.Equal(t, convID, getTLFRes.Convs[1].GetConvID()) 4583 case <-time.After(20 * time.Second): 4584 require.Fail(t, "failed to get left notification") 4585 } 4586 select { 4587 case act := <-listener0.membersUpdate: 4588 require.Equal(t, act.ConvID, getTLFRes.Convs[1].GetConvID()) 4589 require.Equal(t, 1, len(act.Members)) 4590 require.Equal(t, chat1.ConversationMemberStatus_REMOVED, act.Members[0].Status) 4591 require.Equal(t, users[1].Username, act.Members[0].Member) 4592 case <-time.After(20 * time.Second): 4593 require.Fail(t, "failed to get members update") 4594 } 4595 4596 t.Logf("rejoin user 1 into new convo manually") 4597 _, err = ctc.as(t, users[1]).chatLocalHandler().JoinConversationLocal(ctx1, chat1.JoinConversationLocalArg{ 4598 TlfName: conv.TlfName, 4599 TopicType: chat1.TopicType_CHAT, 4600 Visibility: keybase1.TLFVisibility_PRIVATE, 4601 TopicName: topicName, 4602 }) 4603 require.NoError(t, err) 4604 consumeNewMsgRemote(t, listener0, chat1.MessageType_JOIN) 4605 consumeNewMsgRemote(t, listener1, chat1.MessageType_JOIN) 4606 select { 4607 case conv := <-listener1.joinedConv: 4608 require.Equal(t, conv.GetConvID(), getTLFRes.Convs[1].GetConvID()) 4609 require.Equal(t, topicName, conv.Channel) 4610 case <-time.After(20 * time.Second): 4611 require.Fail(t, "failed to get joined notification") 4612 } 4613 select { 4614 case act := <-listener0.membersUpdate: 4615 require.Equal(t, act.ConvID, getTLFRes.Convs[1].GetConvID()) 4616 require.Equal(t, 1, len(act.Members)) 4617 require.Equal(t, chat1.ConversationMemberStatus_ACTIVE, act.Members[0].Status) 4618 require.Equal(t, users[1].Username, act.Members[0].Member) 4619 case <-time.After(20 * time.Second): 4620 require.Fail(t, "failed to get members update") 4621 } 4622 4623 t.Logf("@mention in user2") 4624 _, err = postLocalForTest(t, ctc, users[1], ncres.Conv.Info, chat1.NewMessageBodyWithText(chat1.MessageText{ 4625 Body: fmt.Sprintf("FAIL: @%s", users[2].Username), 4626 })) 4627 require.NoError(t, err) 4628 consumeJoinConv(t, listener2) 4629 consumeNewMsgRemote(t, listener0, chat1.MessageType_TEXT) 4630 consumeNewMsgRemote(t, listener1, chat1.MessageType_TEXT) 4631 consumeNewMsgRemote(t, listener2, chat1.MessageType_TEXT) 4632 4633 t.Logf("user1 leaves: %s", ncres.Conv.GetConvID()) 4634 _, err = ctc.as(t, users[1]).chatLocalHandler().LeaveConversationLocal(ctx1, 4635 ncres.Conv.GetConvID()) 4636 require.NoError(t, err) 4637 consumeNewMsgRemote(t, listener0, chat1.MessageType_LEAVE) 4638 consumeNewMsgRemote(t, listener2, chat1.MessageType_LEAVE) 4639 select { 4640 case convID := <-listener1.leftConv: 4641 require.Equal(t, convID, getTLFRes.Convs[1].GetConvID()) 4642 case <-time.After(20 * time.Second): 4643 require.Fail(t, "failed to get left notification") 4644 } 4645 select { 4646 case act := <-listener0.membersUpdate: 4647 require.Equal(t, act.ConvID, getTLFRes.Convs[1].GetConvID()) 4648 require.Equal(t, 1, len(act.Members)) 4649 require.Equal(t, chat1.ConversationMemberStatus_REMOVED, act.Members[0].Status) 4650 require.Equal(t, users[1].Username, act.Members[0].Member) 4651 case <-time.After(20 * time.Second): 4652 require.Fail(t, "failed to get members update") 4653 } 4654 4655 _, err = postLocalForTest(t, ctc, users[2], ncres.Conv.Info, chat1.NewMessageBodyWithText(chat1.MessageText{ 4656 Body: "FAIL", 4657 })) 4658 require.NoError(t, err) 4659 consumeAllMsgJoins(listener0) 4660 consumeAllMsgJoins(listener2) 4661 select { 4662 case conv := <-listener2.joinedConv: 4663 require.Equal(t, conv.GetConvID(), getTLFRes.Convs[1].GetConvID()) 4664 require.Equal(t, topicName, conv.Channel) 4665 case <-time.After(20 * time.Second): 4666 require.Fail(t, "failed to get joined notification") 4667 } 4668 select { 4669 case act := <-listener0.membersUpdate: 4670 require.Equal(t, act.ConvID, getTLFRes.Convs[1].GetConvID()) 4671 require.Equal(t, 1, len(act.Members)) 4672 require.Equal(t, chat1.ConversationMemberStatus_ACTIVE, act.Members[0].Status) 4673 require.Equal(t, users[2].Username, act.Members[0].Member) 4674 case <-time.After(20 * time.Second): 4675 require.Fail(t, "failed to get members update") 4676 } 4677 4678 _, err = ctc.as(t, users[1]).chatLocalHandler().JoinConversationLocal(ctx1, chat1.JoinConversationLocalArg{ 4679 TlfName: conv.TlfName, 4680 TopicType: chat1.TopicType_CHAT, 4681 Visibility: keybase1.TLFVisibility_PRIVATE, 4682 TopicName: topicName, 4683 }) 4684 require.NoError(t, err) 4685 consumeNewMsgRemote(t, listener0, chat1.MessageType_JOIN) 4686 consumeNewMsgRemote(t, listener1, chat1.MessageType_JOIN) 4687 consumeNewMsgRemote(t, listener2, chat1.MessageType_JOIN) 4688 4689 t.Logf("u2 gets messages and looks for u1's LEAVE message") 4690 tvres, err = ctc.as(t, users[2]).chatLocalHandler().GetThreadLocal(ctx, chat1.GetThreadLocalArg{ 4691 ConversationID: ncres.Conv.GetConvID(), 4692 Query: &chat1.GetThreadQuery{ 4693 MessageTypes: []chat1.MessageType{chat1.MessageType_LEAVE}, 4694 }, 4695 }) 4696 require.NoError(t, err) 4697 require.Len(t, tvres.Thread.Messages, 1, "expected number of LEAVE messages") 4698 4699 t.Logf("u2 leaves and explicitly previews channel") 4700 _, err = ctc.as(t, users[2]).chatLocalHandler().LeaveConversationLocal(ctx1, 4701 ncres.Conv.GetConvID()) 4702 require.NoError(t, err) 4703 consumeNewMsgRemote(t, listener0, chat1.MessageType_LEAVE) 4704 consumeNewMsgRemote(t, listener1, chat1.MessageType_LEAVE) 4705 consumeLeaveConv(t, listener2) 4706 _, err = ctc.as(t, users[2]).chatLocalHandler().PreviewConversationByIDLocal(ctx2, ncres.Conv.Info.Id) 4707 require.NoError(t, err) 4708 consumeJoinConv(t, listener2) 4709 iboxRes, err := ctc.as(t, users[2]).chatLocalHandler().GetInboxAndUnboxLocal(ctx2, 4710 chat1.GetInboxAndUnboxLocalArg{}) 4711 require.NoError(t, err) 4712 require.Equal(t, 2, len(iboxRes.Conversations)) 4713 for _, conv := range iboxRes.Conversations { 4714 if conv.GetConvID().Eq(ncres.Conv.Info.Id) { 4715 require.Equal(t, chat1.ConversationMemberStatus_PREVIEW, conv.Info.MemberStatus) 4716 } else { 4717 require.Equal(t, chat1.ConversationMemberStatus_ACTIVE, conv.Info.MemberStatus) 4718 } 4719 } 4720 }) 4721 } 4722 4723 func TestChatSrvTLFConversationsLocal(t *testing.T) { 4724 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 4725 ctc := makeChatTestContext(t, "TestChatSrvTLFConversationsLocal", 2) 4726 defer ctc.cleanup() 4727 users := ctc.users() 4728 4729 // Only run this test for teams 4730 switch mt { 4731 case chat1.ConversationMembersType_TEAM: 4732 default: 4733 return 4734 } 4735 4736 ctx := ctc.as(t, users[0]).startCtx 4737 ctx1 := ctc.as(t, users[1]).startCtx 4738 uid1 := users[1].User.GetUID().ToBytes() 4739 4740 listener0 := newServerChatListener() 4741 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener0) 4742 ctc.world.Tcs[users[0].Username].ChatG.Syncer.(*Syncer).isConnected = true 4743 4744 listener1 := newServerChatListener() 4745 ctc.as(t, users[1]).h.G().NotifyRouter.AddListener(listener1) 4746 ctc.world.Tcs[users[1].Username].ChatG.Syncer.(*Syncer).isConnected = true 4747 4748 conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 4749 mt, ctc.as(t, users[1]).user()) 4750 t.Logf("first conv: %s", conv.Id) 4751 t.Logf("create a conversation, and join user 1 into by sending a message") 4752 topicName := "zjoinonsend" 4753 ncres, err := ctc.as(t, users[0]).chatLocalHandler().NewConversationLocal(ctx, 4754 chat1.NewConversationLocalArg{ 4755 TlfName: conv.TlfName, 4756 TopicName: &topicName, 4757 TopicType: chat1.TopicType_CHAT, 4758 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 4759 MembersType: chat1.ConversationMembersType_TEAM, 4760 }) 4761 require.NoError(t, err) 4762 4763 _, err = postLocalForTest(t, ctc, users[1], ncres.Conv.Info, chat1.NewMessageBodyWithText(chat1.MessageText{ 4764 Body: "JOINME", 4765 })) 4766 require.NoError(t, err) 4767 4768 getTLFRes, err := ctc.as(t, users[1]).chatLocalHandler().GetTLFConversationsLocal(ctx1, 4769 chat1.GetTLFConversationsLocalArg{ 4770 TlfName: conv.TlfName, 4771 TopicType: chat1.TopicType_CHAT, 4772 MembersType: chat1.ConversationMembersType_TEAM, 4773 }) 4774 require.NoError(t, err) 4775 require.Equal(t, 2, len(getTLFRes.Convs)) 4776 require.Equal(t, globals.DefaultTeamTopic, getTLFRes.Convs[0].Channel) 4777 require.Equal(t, chat1.ConversationMemberStatus_ACTIVE, getTLFRes.Convs[1].MemberStatus) 4778 parts, err := ctc.world.Tcs[users[1].Username].Context().ParticipantsSource.Get(context.TODO(), 4779 uid1, getTLFRes.Convs[1].GetConvID(), types.InboxSourceDataSourceAll) 4780 require.NoError(t, err) 4781 require.Equal(t, 2, len(parts)) 4782 4783 _, err = ctc.as(t, users[1]).chatLocalHandler().LeaveConversationLocal(ctx1, 4784 ncres.Conv.GetConvID()) 4785 require.NoError(t, err) 4786 ignoreTypes := []chat1.MessageType{chat1.MessageType_SYSTEM, chat1.MessageType_JOIN, chat1.MessageType_TEXT} 4787 consumeNewMsgWhileIgnoring(t, listener0, chat1.MessageType_LEAVE, ignoreTypes, chat1.ChatActivitySource_REMOTE) 4788 4789 // make sure both users have processed the leave in their inbox 4790 for i, user := range users { 4791 getTLFRes, err = ctc.as(t, user).chatLocalHandler().GetTLFConversationsLocal( 4792 ctc.as(t, user).startCtx, 4793 chat1.GetTLFConversationsLocalArg{ 4794 TlfName: conv.TlfName, 4795 TopicType: chat1.TopicType_CHAT, 4796 MembersType: chat1.ConversationMembersType_TEAM, 4797 }) 4798 require.NoError(t, err) 4799 require.Equal(t, 2, len(getTLFRes.Convs)) 4800 require.Equal(t, globals.DefaultTeamTopic, getTLFRes.Convs[0].Channel) 4801 if i == 1 { 4802 require.Equal(t, chat1.ConversationMemberStatus_LEFT, getTLFRes.Convs[1].MemberStatus) 4803 } else { 4804 require.Equal(t, chat1.ConversationMemberStatus_ACTIVE, getTLFRes.Convs[1].MemberStatus) 4805 } 4806 t.Logf("checking num participants: i: %d uid: %s", i, user.Username) 4807 uid := user.GetUID().ToBytes() 4808 parts, err := ctc.world.Tcs[user.Username].Context().ParticipantsSource.Get(context.TODO(), 4809 uid, getTLFRes.Convs[1].GetConvID(), types.InboxSourceDataSourceAll) 4810 require.NoError(t, err) 4811 require.Equal(t, 1, len(parts)) 4812 } 4813 4814 // delete the channel make sure it's gone from both inboxes 4815 _, err = ctc.as(t, users[0]).chatLocalHandler().DeleteConversationLocal(ctx, 4816 chat1.DeleteConversationLocalArg{ 4817 ConvID: ncres.Conv.GetConvID(), 4818 Confirmed: true, 4819 }) 4820 require.NoError(t, err) 4821 consumeLeaveConv(t, listener0) 4822 consumeTeamType(t, listener0) 4823 consumeLeaveConv(t, listener1) 4824 consumeTeamType(t, listener1) 4825 4826 for _, user := range users { 4827 getTLFRes, err = ctc.as(t, user).chatLocalHandler().GetTLFConversationsLocal(ctc.as(t, user).startCtx, 4828 chat1.GetTLFConversationsLocalArg{ 4829 TlfName: conv.TlfName, 4830 TopicType: chat1.TopicType_CHAT, 4831 MembersType: chat1.ConversationMembersType_TEAM, 4832 }) 4833 require.NoError(t, err) 4834 require.Equal(t, 1, len(getTLFRes.Convs)) 4835 require.Equal(t, globals.DefaultTeamTopic, getTLFRes.Convs[0].Channel) 4836 } 4837 }) 4838 } 4839 4840 func TestChatSrvChatMembershipsLocal(t *testing.T) { 4841 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 4842 ctc := makeChatTestContext(t, "TestChatSrvChatMembershipsLocal", 2) 4843 defer ctc.cleanup() 4844 users := ctc.users() 4845 4846 // Only run this test for teams 4847 switch mt { 4848 case chat1.ConversationMembersType_TEAM: 4849 default: 4850 return 4851 } 4852 4853 ctx := ctc.as(t, users[0]).startCtx 4854 ctx1 := ctc.as(t, users[1]).startCtx 4855 4856 listener0 := newServerChatListener() 4857 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener0) 4858 ctc.world.Tcs[users[0].Username].ChatG.Syncer.(*Syncer).isConnected = true 4859 4860 listener1 := newServerChatListener() 4861 ctc.as(t, users[1]).h.G().NotifyRouter.AddListener(listener1) 4862 ctc.world.Tcs[users[1].Username].ChatG.Syncer.(*Syncer).isConnected = true 4863 4864 conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 4865 mt, ctc.as(t, users[1]).user()) 4866 4867 teamID, err := ctc.as(t, users[0]).chatLocalHandler().TeamIDFromTLFName(ctx, chat1.TeamIDFromTLFNameArg{ 4868 TlfName: conv.TlfName, 4869 MembersType: chat1.ConversationMembersType_TEAM, 4870 }) 4871 require.NoError(t, err) 4872 t.Logf("first conv: %s", conv.Id) 4873 t.Logf("create a conversation, and join user 1 into by sending a message") 4874 topicName := "zjoinonsend" 4875 ncres, err := ctc.as(t, users[0]).chatLocalHandler().NewConversationLocal(ctx, 4876 chat1.NewConversationLocalArg{ 4877 TlfName: conv.TlfName, 4878 TopicName: &topicName, 4879 TopicType: chat1.TopicType_CHAT, 4880 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 4881 MembersType: chat1.ConversationMembersType_TEAM, 4882 }) 4883 require.NoError(t, err) 4884 4885 _, err = postLocalForTest(t, ctc, users[1], ncres.Conv.Info, chat1.NewMessageBodyWithText(chat1.MessageText{ 4886 Body: "JOINME", 4887 })) 4888 require.NoError(t, err) 4889 4890 getChannelsRes, err := ctc.as(t, users[1]).chatLocalHandler().GetChannelMembershipsLocal(ctx1, 4891 chat1.GetChannelMembershipsLocalArg{ 4892 TeamID: teamID, 4893 Uid: users[1].GetUID().ToBytes(), 4894 }) 4895 require.NoError(t, err) 4896 require.Equal(t, 2, len(getChannelsRes.Channels)) 4897 require.Contains(t, getChannelsRes.Channels, chat1.ChannelNameMention{ 4898 ConvID: ncres.Conv.GetConvID(), 4899 TopicName: topicName, 4900 }) 4901 4902 // users[1] leaves the new convo 4903 _, err = ctc.as(t, users[1]).chatLocalHandler().LeaveConversationLocal(ctx1, 4904 ncres.Conv.GetConvID()) 4905 require.NoError(t, err) 4906 ignoreTypes := []chat1.MessageType{chat1.MessageType_SYSTEM, chat1.MessageType_JOIN, chat1.MessageType_TEXT} 4907 consumeNewMsgWhileIgnoring(t, listener0, chat1.MessageType_LEAVE, ignoreTypes, chat1.ChatActivitySource_REMOTE) 4908 4909 // make sure users processed leave correctly 4910 for i, user := range users { 4911 getChannelsRes, err = ctc.as(t, user).chatLocalHandler().GetChannelMembershipsLocal(ctc.as(t, user).startCtx, 4912 chat1.GetChannelMembershipsLocalArg{ 4913 TeamID: teamID, 4914 Uid: user.GetUID().ToBytes(), 4915 }) 4916 require.NoError(t, err) 4917 if i == 1 { 4918 require.Equal(t, 1, len(getChannelsRes.Channels)) 4919 require.Equal(t, globals.DefaultTeamTopic, getChannelsRes.Channels[0].TopicName) 4920 } else { 4921 require.Equal(t, 2, len(getChannelsRes.Channels)) 4922 } 4923 } 4924 4925 // delete the channel make sure it's gone from both inboxes 4926 _, err = ctc.as(t, users[0]).chatLocalHandler().DeleteConversationLocal(ctx, 4927 chat1.DeleteConversationLocalArg{ 4928 ConvID: ncres.Conv.GetConvID(), 4929 Confirmed: true, 4930 }) 4931 require.NoError(t, err) 4932 consumeLeaveConv(t, listener0) 4933 consumeTeamType(t, listener0) 4934 consumeLeaveConv(t, listener1) 4935 consumeTeamType(t, listener1) 4936 4937 for _, user := range users { 4938 getChannelsRes, err = ctc.as(t, user).chatLocalHandler().GetChannelMembershipsLocal(ctc.as(t, user).startCtx, 4939 chat1.GetChannelMembershipsLocalArg{ 4940 TeamID: teamID, 4941 Uid: user.GetUID().ToBytes(), 4942 }) 4943 require.NoError(t, err) 4944 require.Equal(t, 1, len(getChannelsRes.Channels)) 4945 require.Equal(t, globals.DefaultTeamTopic, getChannelsRes.Channels[0].TopicName) 4946 } 4947 }) 4948 } 4949 4950 func TestChatSrvMutualTeamsLocal(t *testing.T) { 4951 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 4952 ctc := makeChatTestContext(t, "TestChatSrvChatMembershipsLocal", 2) 4953 defer ctc.cleanup() 4954 users := ctc.users() 4955 4956 // Only run this test for teams 4957 switch mt { 4958 case chat1.ConversationMembersType_TEAM: 4959 default: 4960 return 4961 } 4962 4963 ctx := ctc.as(t, users[0]).startCtx 4964 4965 listener0 := newServerChatListener() 4966 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener0) 4967 ctc.world.Tcs[users[0].Username].ChatG.Syncer.(*Syncer).isConnected = true 4968 4969 listener1 := newServerChatListener() 4970 ctc.as(t, users[1]).h.G().NotifyRouter.AddListener(listener1) 4971 ctc.world.Tcs[users[1].Username].ChatG.Syncer.(*Syncer).isConnected = true 4972 4973 t.Logf("create team only one user is in") 4974 conv1 := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt) 4975 teamID1, err := ctc.as(t, users[0]).chatLocalHandler().TeamIDFromTLFName(ctx, chat1.TeamIDFromTLFNameArg{ 4976 TlfName: conv1.TlfName, 4977 MembersType: chat1.ConversationMembersType_TEAM, 4978 }) 4979 require.NoError(t, err) 4980 t.Logf("teamID 1: %s", teamID1) 4981 t.Logf("check that users share no mutual teams") 4982 emptyMutualTeamsRes, err := ctc.as(t, users[0]).chatLocalHandler().GetMutualTeamsLocal(ctx, []string{users[1].Username}) 4983 require.NoError(t, err) 4984 require.Equal(t, 0, len(emptyMutualTeamsRes.TeamIDs)) 4985 4986 t.Logf("create team with both users") 4987 conv2 := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt, users[1]) 4988 teamID2, err := ctc.as(t, users[0]).chatLocalHandler().TeamIDFromTLFName(ctx, chat1.TeamIDFromTLFNameArg{ 4989 TlfName: conv2.TlfName, 4990 MembersType: chat1.ConversationMembersType_TEAM, 4991 }) 4992 require.NoError(t, err) 4993 t.Logf("check that users have 1 mutual team") 4994 4995 getMutualTeamsRes, err := ctc.as(t, users[0]).chatLocalHandler().GetMutualTeamsLocal(ctx, []string{users[1].Username}) 4996 require.NoError(t, err) 4997 require.Equal(t, 1, len(getMutualTeamsRes.TeamIDs)) 4998 require.Equal(t, teamID2, getMutualTeamsRes.TeamIDs[0]) 4999 }) 5000 } 5001 5002 func TestChatSrvSetAppNotificationSettings(t *testing.T) { 5003 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 5004 ctc := makeChatTestContext(t, "TestChatSrvSetAppNotificationSettings", 2) 5005 defer ctc.cleanup() 5006 users := ctc.users() 5007 5008 // Only run this test for teams 5009 switch mt { 5010 case chat1.ConversationMembersType_TEAM: 5011 default: 5012 return 5013 } 5014 5015 listener0 := newServerChatListener() 5016 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener0) 5017 5018 conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt, 5019 ctc.as(t, users[1]).user()) 5020 ctx := ctc.as(t, users[0]).startCtx 5021 5022 gilres, err := ctc.as(t, users[0]).chatLocalHandler().GetInboxAndUnboxLocal(ctx, chat1.GetInboxAndUnboxLocalArg{ 5023 Query: &chat1.GetInboxLocalQuery{ 5024 ConvIDs: []chat1.ConversationID{conv.Id}, 5025 }, 5026 }) 5027 require.NoError(t, err) 5028 require.Equal(t, 1, len(gilres.Conversations)) 5029 require.Equal(t, conv.Id, gilres.Conversations[0].GetConvID()) 5030 gconv := gilres.Conversations[0] 5031 require.True(t, gconv.Notifications.Settings[keybase1.DeviceType_DESKTOP][chat1.NotificationKind_GENERIC]) 5032 require.Equal(t, 2, len(gconv.Notifications.Settings)) 5033 require.Equal(t, 2, len(gconv.Notifications.Settings[keybase1.DeviceType_DESKTOP])) 5034 require.Equal(t, 2, len(gconv.Notifications.Settings[keybase1.DeviceType_MOBILE])) 5035 5036 mustPostLocalForTest(t, ctc, users[1], conv, 5037 chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hello!"})) 5038 select { 5039 case info := <-listener0.newMessageRemote: 5040 require.Equal(t, chat1.MessageType_TEXT, info.Message.GetMessageType()) 5041 require.True(t, info.DisplayDesktopNotification) 5042 require.NotEqual(t, "", info.DesktopNotificationSnippet) 5043 case <-time.After(20 * time.Second): 5044 require.Fail(t, "no new message event") 5045 } 5046 setting := chat1.AppNotificationSettingLocal{ 5047 DeviceType: keybase1.DeviceType_DESKTOP, 5048 Kind: chat1.NotificationKind_ATMENTION, 5049 Enabled: false, 5050 } 5051 _, err = ctc.as(t, users[0]).chatLocalHandler().SetAppNotificationSettingsLocal(ctx, 5052 chat1.SetAppNotificationSettingsLocalArg{ 5053 ConvID: conv.Id, 5054 Settings: []chat1.AppNotificationSettingLocal{setting}, 5055 }) 5056 require.NoError(t, err) 5057 select { 5058 case rsettings := <-listener0.appNotificationSettings: 5059 require.Equal(t, gconv.GetConvID(), rsettings.ConvID) 5060 require.Equal(t, 2, len(rsettings.Settings.Settings)) 5061 require.False(t, rsettings.Settings.ChannelWide) 5062 case <-time.After(20 * time.Second): 5063 require.Fail(t, "no app notification received") 5064 } 5065 mustPostLocalForTest(t, ctc, users[1], conv, 5066 chat1.NewMessageBodyWithText(chat1.MessageText{Body: fmt.Sprintf("@%s", users[0].Username)})) 5067 select { 5068 case info := <-listener0.newMessageRemote: 5069 require.Equal(t, chat1.MessageType_TEXT, info.Message.GetMessageType()) 5070 require.True(t, info.DisplayDesktopNotification) 5071 require.NotEqual(t, "", info.DesktopNotificationSnippet) 5072 case <-time.After(20 * time.Second): 5073 require.Fail(t, "no new message event") 5074 } 5075 5076 setting = chat1.AppNotificationSettingLocal{ 5077 DeviceType: keybase1.DeviceType_DESKTOP, 5078 Kind: chat1.NotificationKind_GENERIC, 5079 Enabled: false, 5080 } 5081 setting2 := chat1.AppNotificationSettingLocal{ 5082 DeviceType: keybase1.DeviceType_DESKTOP, 5083 Kind: chat1.NotificationKind_ATMENTION, 5084 Enabled: true, 5085 } 5086 _, err = ctc.as(t, users[0]).chatLocalHandler().SetAppNotificationSettingsLocal(ctx, 5087 chat1.SetAppNotificationSettingsLocalArg{ 5088 ConvID: conv.Id, 5089 Settings: []chat1.AppNotificationSettingLocal{setting, setting2}, 5090 }) 5091 require.NoError(t, err) 5092 select { 5093 case rsettings := <-listener0.appNotificationSettings: 5094 require.Equal(t, gconv.GetConvID(), rsettings.ConvID) 5095 require.Equal(t, 2, len(rsettings.Settings.Settings)) 5096 require.False(t, rsettings.Settings.ChannelWide) 5097 case <-time.After(20 * time.Second): 5098 require.Fail(t, "no app notification received") 5099 } 5100 5101 gilres, err = ctc.as(t, users[0]).chatLocalHandler().GetInboxAndUnboxLocal(ctx, chat1.GetInboxAndUnboxLocalArg{ 5102 Query: &chat1.GetInboxLocalQuery{ 5103 ConvIDs: []chat1.ConversationID{conv.Id}, 5104 }, 5105 }) 5106 require.NoError(t, err) 5107 require.Equal(t, 1, len(gilres.Conversations)) 5108 require.Equal(t, conv.Id, gilres.Conversations[0].GetConvID()) 5109 gconv = gilres.Conversations[0] 5110 require.False(t, gconv.Notifications.Settings[keybase1.DeviceType_DESKTOP][chat1.NotificationKind_GENERIC]) 5111 require.Equal(t, 2, len(gconv.Notifications.Settings)) 5112 require.Equal(t, 2, len(gconv.Notifications.Settings[keybase1.DeviceType_DESKTOP])) 5113 require.Equal(t, 2, len(gconv.Notifications.Settings[keybase1.DeviceType_MOBILE])) 5114 require.False(t, gconv.Notifications.ChannelWide) 5115 5116 mustPostLocalForTest(t, ctc, users[1], conv, 5117 chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hello!"})) 5118 select { 5119 case info := <-listener0.newMessageRemote: 5120 require.False(t, info.DisplayDesktopNotification) 5121 require.Equal(t, "", info.DesktopNotificationSnippet) 5122 case <-time.After(20 * time.Second): 5123 require.Fail(t, "no new message event") 5124 } 5125 5126 validateDisplayAtMention := func(name string) { 5127 text := fmt.Sprintf("@%s", name) 5128 mustPostLocalForTest(t, ctc, users[1], conv, 5129 chat1.NewMessageBodyWithText(chat1.MessageText{Body: text})) 5130 select { 5131 case info := <-listener0.newMessageRemote: 5132 require.True(t, info.DisplayDesktopNotification) 5133 require.NotEqual(t, "", info.DesktopNotificationSnippet) 5134 case <-time.After(20 * time.Second): 5135 require.Fail(t, "no new message event") 5136 } 5137 } 5138 validateDisplayAtMention(users[0].Username) 5139 5140 _, err = ctc.as(t, users[0]).chatLocalHandler().SetAppNotificationSettingsLocal(ctx, 5141 chat1.SetAppNotificationSettingsLocalArg{ 5142 ConvID: conv.Id, 5143 ChannelWide: true, 5144 }) 5145 require.NoError(t, err) 5146 select { 5147 case rsettings := <-listener0.appNotificationSettings: 5148 require.Equal(t, gconv.GetConvID(), rsettings.ConvID) 5149 require.True(t, rsettings.Settings.ChannelWide) 5150 case <-time.After(20 * time.Second): 5151 require.Fail(t, "no app notification received") 5152 } 5153 validateDisplayAtMention("channel") 5154 validateDisplayAtMention("everyone") 5155 validateDisplayAtMention("here") 5156 }) 5157 5158 } 5159 5160 func randSweepChannel() uint64 { 5161 for { 5162 buf := make([]byte, 8) 5163 _, err := rand.Read(buf) 5164 if err != nil { 5165 panic(err) 5166 } 5167 x := binary.LittleEndian.Uint64(buf) 5168 // sql driver doesn't support all the bits 5169 // https://golang.org/src/database/sql/driver/types.go#L265 5170 if x < 1<<63 { 5171 return x 5172 } 5173 } 5174 } 5175 5176 func TestChatSrvRetentionSweepConv(t *testing.T) { 5177 sweepChannel := randSweepChannel() 5178 t.Logf("sweepChannel: %v", sweepChannel) 5179 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 5180 switch mt { 5181 case chat1.ConversationMembersType_KBFS: 5182 t.Logf("skipping kbfs stage") 5183 return 5184 default: 5185 // Fall through for other member types. 5186 } 5187 runWithRetentionPolicyTypes(t, func(policy chat1.RetentionPolicy, ephemeralLifetime *gregor1.DurationSec) { 5188 5189 ctc := makeChatTestContext(t, "TestChatSrvRetention", 2) 5190 defer ctc.cleanup() 5191 users := ctc.users() 5192 ctx := ctc.as(t, users[0]).startCtx 5193 5194 listener := newServerChatListener() 5195 ctc.as(t, users[1]).h.G().NotifyRouter.AddListener(listener) 5196 5197 conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 5198 mt, ctc.as(t, users[1]).user()) 5199 5200 mustPostLocalForTest(t, ctc, users[0], conv, chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hello!"})) 5201 consumeNewMsgRemote(t, listener, chat1.MessageType_TEXT) 5202 5203 mustPostLocalForTest(t, ctc, users[1], conv, chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hello!"})) 5204 consumeNewMsgRemote(t, listener, chat1.MessageType_TEXT) 5205 5206 mustSetConvRetention(t, ctc, users[0], conv.Id, policy, sweepChannel) 5207 require.True(t, consumeSetConvRetention(t, listener).Eq(conv.Id)) 5208 5209 // This will take at least 1 second. For the deletable message to get old enough. 5210 expungeInfo := sweepPollForDeletion(t, ctc, users[1], listener, conv.Id, 4) 5211 require.True(t, expungeInfo.ConvID.Eq(conv.Id)) 5212 require.Equal(t, chat1.Expunge{Upto: 4}, expungeInfo.Expunge, "expunge upto") 5213 5214 tvres, err := ctc.as(t, users[1]).chatLocalHandler().GetThreadLocal(ctx, chat1.GetThreadLocalArg{ConversationID: conv.Id}) 5215 require.NoError(t, err) 5216 t.Logf("messages: %v", chat1.MessageUnboxedDebugList(tvres.Thread.Messages)) 5217 for _, msg := range tvres.Thread.Messages { 5218 if msg.IsJourneycard() { 5219 continue 5220 } 5221 switch msg.GetMessageType() { 5222 case chat1.MessageType_METADATA, chat1.MessageType_TLFNAME: 5223 continue 5224 } 5225 require.FailNowf(t, "the TEXTs should be deleted", "%v, %v", chat1.MessageUnboxedDebugList(tvres.Thread.Messages), spew.Sdump(msg)) 5226 } 5227 5228 // If we are using an ephemeral policy make sure messages with a lifetime exceeding 5229 // the policy age are blocked. 5230 if ephemeralLifetime != nil { 5231 badLifetime := *ephemeralLifetime + 1 5232 _, err := postLocalEphemeralForTest(t, ctc, users[0], conv, chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hello!"}), &badLifetime) 5233 require.Error(t, err) 5234 require.IsType(t, libkb.ChatEphemeralRetentionPolicyViolatedError{}, err) 5235 5236 mustPostLocalEphemeralForTest(t, ctc, users[0], conv, 5237 chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hello!"}), ephemeralLifetime) 5238 } 5239 }) 5240 }) 5241 } 5242 5243 func tlfIDToTeamIDForce(t *testing.T, tlfID chat1.TLFID) keybase1.TeamID { 5244 res, err := keybase1.TeamIDFromString(tlfID.String()) 5245 require.NoError(t, err) 5246 return res 5247 } 5248 5249 func TestChatSrvRetentionSweepTeam(t *testing.T) { 5250 sweepChannel := randSweepChannel() 5251 t.Logf("sweepChannel: %v", sweepChannel) 5252 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 5253 switch mt { 5254 case chat1.ConversationMembersType_TEAM: 5255 default: 5256 t.Logf("skipping %v stage", mt) 5257 return 5258 } 5259 runWithRetentionPolicyTypes(t, func(policy chat1.RetentionPolicy, ephemeralLifetime *gregor1.DurationSec) { 5260 ctc := makeChatTestContext(t, "TestChatSrvTeamRetention", 2) 5261 defer ctc.cleanup() 5262 users := ctc.users() 5263 ctx := ctc.as(t, users[0]).startCtx 5264 _ = ctc.as(t, users[1]).startCtx 5265 for i, u := range users { 5266 t.Logf("user[%v] %v %v", i, u.Username, u.User.GetUID()) 5267 ctc.world.Tcs[u.Username].ChatG.Syncer.(*Syncer).isConnected = true 5268 } 5269 5270 listener := newServerChatListener() 5271 ctc.as(t, users[1]).h.G().NotifyRouter.AddListener(listener) 5272 5273 // 3 convs 5274 // convA: inherit team expire policy (default) 5275 // convB: expire policy 5276 // convC: retain policy 5277 var convs []chat1.ConversationInfoLocal 5278 for i := 0; i < 3; i++ { 5279 t.Logf("creating conv %v", i) 5280 var topicName *string 5281 if i > 0 { 5282 s := fmt.Sprintf("regarding-%v-gons", i) 5283 topicName = &s 5284 } 5285 conv := mustCreateChannelForTest(t, ctc, users[0], chat1.TopicType_CHAT, 5286 topicName, mt, ctc.as(t, users[1]).user()) 5287 convs = append(convs, conv) 5288 if i > 0 { 5289 mustJoinConversationByID(t, ctc, users[1], conv.Id) 5290 consumeJoinConv(t, listener) 5291 } 5292 } 5293 convA := convs[0] 5294 convB := convs[1] 5295 convC := convs[2] 5296 teamID := tlfIDToTeamIDForce(t, convA.Triple.Tlfid) 5297 5298 // policy can be EXPIRE or EPHEMERAL here. 5299 teamPolicy := policy 5300 convExpirePolicy := policy 5301 convRetainPolicy := chat1.NewRetentionPolicyWithRetain(chat1.RpRetain{}) 5302 5303 latestMsgMap := make(map[chat1.ConvIDStr]chat1.MessageID) 5304 latestMsg := func(convID chat1.ConversationID) chat1.MessageID { 5305 return latestMsgMap[convID.ConvIDStr()] 5306 } 5307 for i, conv := range convs { 5308 t.Logf("conv (%v/%v) %v in team %v", i+1, len(convs), conv.Id, tlfIDToTeamIDForce(t, conv.Triple.Tlfid)) 5309 msgID := mustPostLocalForTest(t, ctc, users[0], conv, chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hello!"})) 5310 latestMsgMap[conv.Id.ConvIDStr()] = msgID 5311 5312 ignoreTypes := []chat1.MessageType{chat1.MessageType_SYSTEM, chat1.MessageType_JOIN} 5313 consumeNewMsgWhileIgnoring(t, listener, chat1.MessageType_TEXT, ignoreTypes, chat1.ChatActivitySource_REMOTE) 5314 } 5315 5316 mustSetConvRetention(t, ctc, users[0], convB.Id, convExpirePolicy, sweepChannel) 5317 require.True(t, consumeSetConvRetention(t, listener).Eq(convB.Id)) 5318 mustSetTeamRetention(t, ctc, users[0], teamID, teamPolicy, sweepChannel) 5319 require.True(t, consumeSetTeamRetention(t, listener).Eq(teamID)) 5320 mustSetConvRetention(t, ctc, users[0], convC.Id, convRetainPolicy, sweepChannel) 5321 require.True(t, consumeSetConvRetention(t, listener).Eq(convC.Id)) 5322 5323 // This will take at least 1 second. 5324 sweepPollForDeletion(t, ctc, users[0], listener, convB.Id, latestMsg(convB.Id)+1) 5325 sweepPollForDeletion(t, ctc, users[0], listener, convA.Id, latestMsg(convA.Id)+1) 5326 sweepNoDeletion(t, ctc, users[0], convC.Id) 5327 5328 checkThread := func(convID chat1.ConversationID, expectDeleted bool) { 5329 tvres, err := ctc.as(t, users[1]).chatLocalHandler().GetThreadLocal(ctx, chat1.GetThreadLocalArg{ConversationID: convID}) 5330 require.NoError(t, err) 5331 var nText int 5332 for _, msg := range tvres.Thread.Messages { 5333 if msg.IsJourneycard() { 5334 continue 5335 } 5336 require.True(t, msg.IsValidFull()) 5337 require.Equal(t, chat1.MessageID(0), msg.Valid().ServerHeader.SupersededBy) 5338 if msg.GetMessageType() == chat1.MessageType_TEXT { 5339 nText++ 5340 } 5341 } 5342 if expectDeleted { 5343 require.Equal(t, 0, nText, "conv contents should be deleted: %v", convID.DbShortFormString()) 5344 } else { 5345 require.Equal(t, 1, nText) 5346 } 5347 } 5348 5349 checkThread(convA.Id, true) 5350 checkThread(convB.Id, true) 5351 checkThread(convC.Id, false) 5352 if ephemeralLifetime != nil { 5353 for _, conv := range []chat1.ConversationInfoLocal{convA, convB} { 5354 // If we are using an ephemeral policy make sure messages with a lifetime exceeding 5355 // the policy age are blocked. 5356 badLifetime := *ephemeralLifetime + 1 5357 _, err := postLocalEphemeralForTest(t, ctc, users[0], conv, chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hello!"}), &badLifetime) 5358 require.Error(t, err) 5359 require.IsType(t, libkb.ChatEphemeralRetentionPolicyViolatedError{}, err) 5360 5361 mustPostLocalEphemeralForTest(t, ctc, users[0], conv, 5362 chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hello!"}), ephemeralLifetime) 5363 } 5364 } 5365 }) 5366 }) 5367 } 5368 5369 func verifyChangeRetentionSystemMessage(t *testing.T, msg chat1.UIMessage, expectedMsg chat1.MessageSystemChangeRetention) { 5370 require.True(t, msg.IsValid()) 5371 body := msg.Valid().MessageBody 5372 typ, err := body.MessageType() 5373 require.NoError(t, err) 5374 require.Equal(t, chat1.MessageType_SYSTEM, typ) 5375 sysMsg := body.System() 5376 sysTyp, err := sysMsg.SystemType() 5377 require.NoError(t, err) 5378 require.Equal(t, chat1.MessageSystemType_CHANGERETENTION, sysTyp) 5379 retMsg := sysMsg.Changeretention() 5380 require.Equal(t, expectedMsg, retMsg) 5381 } 5382 5383 func TestChatSrvEphemeralConvRetention(t *testing.T) { 5384 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 5385 switch mt { 5386 case chat1.ConversationMembersType_KBFS: 5387 t.Logf("skipping kbfs stage") 5388 return 5389 default: 5390 // Fall through for other member types. 5391 } 5392 5393 ctc := makeChatTestContext(t, "TestChatSrvRetention", 2) 5394 defer ctc.cleanup() 5395 users := ctc.users() 5396 ctx := ctc.as(t, users[0]).startCtx 5397 5398 listener := newServerChatListener() 5399 ctc.as(t, users[1]).h.G().NotifyRouter.AddListener(listener) 5400 5401 conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 5402 mt, ctc.as(t, users[1]).user()) 5403 5404 msgID := mustPostLocalForTest(t, ctc, users[0], conv, chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hello!"})) 5405 consumeNewMsgRemote(t, listener, chat1.MessageType_TEXT) 5406 5407 // set an ephemeral policy 5408 age := gregor1.ToDurationSec(time.Hour * 24) 5409 policy := chat1.NewRetentionPolicyWithEphemeral(chat1.RpEphemeral{Age: age}) 5410 mustSetConvRetentionLocal(t, ctc, users[0], conv.Id, policy) 5411 require.True(t, consumeSetConvRetention(t, listener).Eq(conv.Id)) 5412 msg := consumeNewMsgRemote(t, listener, chat1.MessageType_SYSTEM) 5413 verifyChangeRetentionSystemMessage(t, msg, chat1.MessageSystemChangeRetention{ 5414 IsTeam: false, 5415 IsInherit: false, 5416 Policy: policy, 5417 MembersType: mt, 5418 User: users[0].Username, 5419 }) 5420 5421 // make sure we can supersede existing messages 5422 mustReactToMsg(ctx, t, ctc, users[0], conv, msgID, ":+1:") 5423 5424 ephemeralMsgID := mustPostLocalEphemeralForTest(t, ctc, users[0], conv, 5425 chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hello!"}), &age) 5426 mustReactToMsg(ctx, t, ctc, users[0], conv, ephemeralMsgID, ":+1:") 5427 }) 5428 } 5429 5430 func TestChatSrvEphemeralTeamRetention(t *testing.T) { 5431 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 5432 switch mt { 5433 case chat1.ConversationMembersType_TEAM: 5434 default: 5435 t.Logf("skipping %v stage", mt) 5436 return 5437 } 5438 ctc := makeChatTestContext(t, "TestChatSrvTeamRetention", 2) 5439 defer ctc.cleanup() 5440 users := ctc.users() 5441 ctx := ctc.as(t, users[0]).startCtx 5442 _ = ctc.as(t, users[1]).startCtx 5443 for i, u := range users { 5444 t.Logf("user[%v] %v %v", i, u.Username, u.User.GetUID()) 5445 ctc.world.Tcs[u.Username].ChatG.Syncer.(*Syncer).isConnected = true 5446 } 5447 listener := newServerChatListener() 5448 ctc.as(t, users[1]).h.G().NotifyRouter.AddListener(listener) 5449 5450 // 3 convs 5451 // convA: inherit team expire policy (default) 5452 // convB: expire policy 5453 // convC: retain policy 5454 var convs []chat1.ConversationInfoLocal 5455 for i := 0; i < 3; i++ { 5456 t.Logf("creating conv %v", i) 5457 var topicName *string 5458 if i > 0 { 5459 s := fmt.Sprintf("regarding-%v-gons", i) 5460 topicName = &s 5461 } 5462 conv := mustCreateChannelForTest(t, ctc, users[0], chat1.TopicType_CHAT, 5463 topicName, mt, ctc.as(t, users[1]).user()) 5464 convs = append(convs, conv) 5465 if i > 0 { 5466 mustJoinConversationByID(t, ctc, users[1], conv.Id) 5467 consumeJoinConv(t, listener) 5468 } 5469 } 5470 convA := convs[0] 5471 convB := convs[1] 5472 convC := convs[2] 5473 teamID := tlfIDToTeamIDForce(t, convA.Triple.Tlfid) 5474 5475 age := gregor1.ToDurationSec(time.Hour * 24) 5476 policy := chat1.NewRetentionPolicyWithEphemeral(chat1.RpEphemeral{Age: age}) 5477 teamPolicy := policy 5478 convExpirePolicy := policy 5479 convRetainPolicy := chat1.NewRetentionPolicyWithRetain(chat1.RpRetain{}) 5480 5481 latestMsgMap := make(map[string] /*convID*/ chat1.MessageID) 5482 latestMsg := func(convID chat1.ConversationID) chat1.MessageID { 5483 return latestMsgMap[convID.String()] 5484 } 5485 for i, conv := range convs { 5486 t.Logf("conv (%v/%v) %v in team %v", i+1, len(convs), conv.Id, tlfIDToTeamIDForce(t, conv.Triple.Tlfid)) 5487 msgID := mustPostLocalForTest(t, ctc, users[0], conv, chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hello!"})) 5488 latestMsgMap[conv.Id.String()] = msgID 5489 } 5490 5491 // drain remote messages 5492 drain := func() { 5493 for { 5494 select { 5495 case msg := <-listener.newMessageRemote: 5496 t.Logf("drained %v", msg.Message.GetMessageType()) 5497 case <-time.After(100 * time.Millisecond): 5498 return 5499 } 5500 } 5501 } 5502 drain() 5503 5504 mustSetConvRetentionLocal(t, ctc, users[0], convB.Id, convExpirePolicy) 5505 require.True(t, consumeSetConvRetention(t, listener).Eq(convB.Id)) 5506 msg := consumeNewMsgRemote(t, listener, chat1.MessageType_SYSTEM) 5507 verifyChangeRetentionSystemMessage(t, msg, chat1.MessageSystemChangeRetention{ 5508 IsTeam: false, 5509 IsInherit: false, 5510 Policy: convExpirePolicy, 5511 MembersType: mt, 5512 User: users[0].Username, 5513 }) 5514 5515 mustSetTeamRetentionLocal(t, ctc, users[0], teamID, teamPolicy) 5516 require.True(t, consumeSetTeamRetention(t, listener).Eq(teamID)) 5517 msg = consumeNewMsgRemote(t, listener, chat1.MessageType_SYSTEM) 5518 verifyChangeRetentionSystemMessage(t, msg, chat1.MessageSystemChangeRetention{ 5519 IsTeam: true, 5520 IsInherit: false, 5521 Policy: teamPolicy, 5522 MembersType: mt, 5523 User: users[0].Username, 5524 }) 5525 5526 mustSetConvRetentionLocal(t, ctc, users[0], convC.Id, convRetainPolicy) 5527 require.True(t, consumeSetConvRetention(t, listener).Eq(convC.Id)) 5528 msg = consumeNewMsgRemote(t, listener, chat1.MessageType_SYSTEM) 5529 verifyChangeRetentionSystemMessage(t, msg, chat1.MessageSystemChangeRetention{ 5530 IsTeam: false, 5531 IsInherit: false, 5532 Policy: convRetainPolicy, 5533 MembersType: mt, 5534 User: users[0].Username, 5535 }) 5536 5537 for _, conv := range []chat1.ConversationInfoLocal{convA, convB} { 5538 mustReactToMsg(ctx, t, ctc, users[0], conv, latestMsg(conv.Id), ":+1:") 5539 consumeNewMsgRemote(t, listener, chat1.MessageType_REACTION) 5540 ephemeralMsgID := mustPostLocalEphemeralForTest(t, ctc, users[0], conv, 5541 chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hello!"}), &age) 5542 consumeNewMsgRemote(t, listener, chat1.MessageType_TEXT) 5543 mustReactToMsg(ctx, t, ctc, users[0], conv, ephemeralMsgID, ":+1:") 5544 consumeNewMsgRemote(t, listener, chat1.MessageType_REACTION) 5545 } 5546 5547 // revert convC to inherit 5548 convInheritPolicy := chat1.NewRetentionPolicyWithInherit(chat1.RpInherit{}) 5549 mustSetConvRetentionLocal(t, ctc, users[0], convC.Id, convInheritPolicy) 5550 require.True(t, consumeSetConvRetention(t, listener).Eq(convC.Id)) 5551 msg = consumeNewMsgRemote(t, listener, chat1.MessageType_SYSTEM) 5552 verifyChangeRetentionSystemMessage(t, msg, chat1.MessageSystemChangeRetention{ 5553 IsTeam: false, 5554 IsInherit: true, 5555 MembersType: mt, 5556 Policy: teamPolicy, 5557 User: users[0].Username, 5558 }) 5559 }) 5560 } 5561 func TestChatSrvSetConvMinWriterRole(t *testing.T) { 5562 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 5563 // Only run this test for teams 5564 switch mt { 5565 case chat1.ConversationMembersType_TEAM: 5566 default: 5567 t.Logf("skipping %v stage", mt) 5568 return 5569 } 5570 5571 ctc := makeChatTestContext(t, "TestChatSrvSetConvMinWriterRole", 2) 5572 defer ctc.cleanup() 5573 users := ctc.users() 5574 ctx := ctc.as(t, users[0]).startCtx 5575 5576 tc1 := ctc.as(t, users[0]) 5577 tc2 := ctc.as(t, users[1]) 5578 5579 listener1 := newServerChatListener() 5580 tc1.h.G().NotifyRouter.AddListener(listener1) 5581 listener2 := newServerChatListener() 5582 tc2.h.G().NotifyRouter.AddListener(listener2) 5583 5584 created := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 5585 mt, tc2.user()) 5586 convID := created.Id 5587 consumeNewConversation(t, listener1, convID) 5588 consumeNewConversation(t, listener2, convID) 5589 5590 verifyMinWriterRoleInfoOnConv := func(user *kbtest.FakeUser, role *keybase1.TeamRole, cannotWrite bool) { 5591 tc := ctc.as(t, user) 5592 5593 var expectedInfo *chat1.ConversationMinWriterRoleInfo 5594 var expectedInfoLocal *chat1.ConversationMinWriterRoleInfoLocal 5595 if role != nil { 5596 expectedInfo = &chat1.ConversationMinWriterRoleInfo{ 5597 Role: *role, 5598 Uid: gregor1.UID(users[0].GetUID().ToBytes()), 5599 } 5600 expectedInfoLocal = &chat1.ConversationMinWriterRoleInfoLocal{ 5601 Role: *role, 5602 ChangedBy: users[0].Username, 5603 CannotWrite: cannotWrite, 5604 } 5605 } 5606 5607 conv, err := utils.GetUnverifiedConv(ctx, ctc.world.Tcs[user.Username].Context(), 5608 gregor1.UID(user.GetUID().ToBytes()), convID, types.InboxSourceDataSourceRemoteOnly) 5609 require.NoError(t, err) 5610 if role == nil { 5611 require.Nil(t, conv.Conv.ConvSettings) 5612 } else { 5613 require.NotNil(t, conv.Conv.ConvSettings) 5614 require.Equal(t, expectedInfo, conv.Conv.ConvSettings.MinWriterRoleInfo) 5615 } 5616 5617 gilres, err := tc.chatLocalHandler().GetInboxAndUnboxLocal(ctx, chat1.GetInboxAndUnboxLocalArg{ 5618 Query: &chat1.GetInboxLocalQuery{ 5619 ConvIDs: []chat1.ConversationID{convID}, 5620 }, 5621 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 5622 }) 5623 require.NoError(t, err) 5624 require.Len(t, gilres.Conversations, 1) 5625 convSettings := gilres.Conversations[0].ConvSettings 5626 if role == nil { 5627 require.Nil(t, convSettings) 5628 } else { 5629 require.NotNil(t, convSettings) 5630 require.Equal(t, expectedInfoLocal, convSettings.MinWriterRoleInfo) 5631 } 5632 } 5633 5634 mustPostLocalForTest(t, ctc, users[0], created, chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hello!"})) 5635 consumeNewMsgRemote(t, listener1, chat1.MessageType_TEXT) 5636 consumeNewMsgRemote(t, listener2, chat1.MessageType_TEXT) 5637 verifyMinWriterRoleInfoOnConv(users[0], nil, false) 5638 verifyMinWriterRoleInfoOnConv(users[1], nil, false) 5639 5640 role := keybase1.TeamRole_ADMIN 5641 err := tc1.chatLocalHandler().SetConvMinWriterRoleLocal(tc1.startCtx, chat1.SetConvMinWriterRoleLocalArg{ 5642 ConvID: convID, 5643 Role: role, 5644 }) 5645 require.NoError(t, err) 5646 require.True(t, consumeSetConvSettings(t, listener1).Eq(created.Id)) 5647 require.True(t, consumeSetConvSettings(t, listener2).Eq(created.Id)) 5648 5649 // u2 can't set this since they are not an admin 5650 err = tc2.chatLocalHandler().SetConvMinWriterRoleLocal(tc2.startCtx, chat1.SetConvMinWriterRoleLocalArg{ 5651 ConvID: convID, 5652 Role: keybase1.TeamRole_NONE, 5653 }) 5654 require.Error(t, err) 5655 // Only u1's role update went through 5656 verifyMinWriterRoleInfoOnConv(users[0], &role, false) 5657 verifyMinWriterRoleInfoOnConv(users[1], &role, true) 5658 5659 // u2 can't write anymore, only u1 can. 5660 _, err = postLocalForTest(t, ctc, users[1], created, chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hello!"})) 5661 require.Error(t, err) 5662 5663 mustPostLocalForTest(t, ctc, users[0], created, chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hello!"})) 5664 consumeNewMsgRemote(t, listener1, chat1.MessageType_TEXT) 5665 consumeNewMsgRemote(t, listener2, chat1.MessageType_TEXT) 5666 5667 // Both users can fully ready without issue 5668 for _, user := range users { 5669 tvres, err := ctc.as(t, user).chatLocalHandler().GetThreadLocal(ctx, chat1.GetThreadLocalArg{ConversationID: created.Id, 5670 Query: &chat1.GetThreadQuery{ 5671 MessageTypes: []chat1.MessageType{chat1.MessageType_TEXT}, 5672 }, 5673 }) 5674 filterOutJourneycards(&tvres.Thread) 5675 require.NoError(t, err) 5676 if len(tvres.Thread.Messages) != 2 { 5677 t.Logf("messages: %v", chat1.MessageUnboxedDebugList(tvres.Thread.Messages)) 5678 } 5679 require.Len(t, tvres.Thread.Messages, 2, "messages are accessible") 5680 } 5681 5682 role = keybase1.TeamRole_NONE 5683 err = tc1.chatLocalHandler().SetConvMinWriterRoleLocal(tc1.startCtx, chat1.SetConvMinWriterRoleLocalArg{ 5684 ConvID: convID, 5685 Role: role, 5686 }) 5687 require.NoError(t, err) 5688 require.True(t, consumeSetConvSettings(t, listener1).Eq(created.Id)) 5689 require.True(t, consumeSetConvSettings(t, listener2).Eq(created.Id)) 5690 verifyMinWriterRoleInfoOnConv(users[0], nil, false) 5691 verifyMinWriterRoleInfoOnConv(users[1], nil, false) 5692 5693 // Both users can write again 5694 mustPostLocalForTest(t, ctc, users[0], created, chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hello!"})) 5695 consumeNewMsgRemote(t, listener1, chat1.MessageType_TEXT) 5696 consumeNewMsgRemote(t, listener2, chat1.MessageType_TEXT) 5697 mustPostLocalForTest(t, ctc, users[1], created, chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hello!"})) 5698 consumeNewMsgRemote(t, listener1, chat1.MessageType_TEXT) 5699 consumeNewMsgRemote(t, listener2, chat1.MessageType_TEXT) 5700 5701 for _, user := range users { 5702 tvres, err := ctc.as(t, user).chatLocalHandler().GetThreadLocal(ctx, chat1.GetThreadLocalArg{ 5703 ConversationID: created.Id, 5704 Query: &chat1.GetThreadQuery{ 5705 MessageTypes: []chat1.MessageType{chat1.MessageType_TEXT}, 5706 }, 5707 }) 5708 filterOutJourneycards(&tvres.Thread) 5709 require.NoError(t, err) 5710 require.Len(t, tvres.Thread.Messages, 4, "messages are accessible") 5711 } 5712 5713 // create a new channel with a MinWriterRole set to ADMIN and ensure 5714 // new users can join/leave 5715 topicName := "zjoinonsend" 5716 channel, err := ctc.as(t, users[0]).chatLocalHandler().NewConversationLocal(ctx, 5717 chat1.NewConversationLocalArg{ 5718 TlfName: created.TlfName, 5719 TopicName: &topicName, 5720 TopicType: chat1.TopicType_CHAT, 5721 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 5722 MembersType: chat1.ConversationMembersType_TEAM, 5723 }) 5724 require.NoError(t, err) 5725 channelInfo := channel.Conv.Info 5726 consumeNewMsgRemote(t, listener1, chat1.MessageType_JOIN) 5727 consumeNewConversation(t, listener1, channelInfo.Id) 5728 assertNoNewConversation(t, listener2) 5729 consumeTeamType(t, listener1) 5730 consumeTeamType(t, listener2) 5731 consumeNewMsgRemote(t, listener1, chat1.MessageType_SYSTEM) 5732 consumeNewMsgRemote(t, listener1, chat1.MessageType_SYSTEM) 5733 consumeNewMsgRemote(t, listener2, chat1.MessageType_SYSTEM) 5734 consumeNewMsgRemote(t, listener2, chat1.MessageType_SYSTEM) 5735 5736 channelID := channelInfo.Id 5737 role = keybase1.TeamRole_ADMIN 5738 err = tc1.chatLocalHandler().SetConvMinWriterRoleLocal(tc1.startCtx, chat1.SetConvMinWriterRoleLocalArg{ 5739 ConvID: channelID, 5740 Role: role, 5741 }) 5742 require.NoError(t, err) 5743 5744 _, err = ctc.as(t, users[1]).chatLocalHandler().JoinConversationLocal(tc2.startCtx, chat1.JoinConversationLocalArg{ 5745 TlfName: channel.Conv.Info.TlfName, 5746 TopicType: chat1.TopicType_CHAT, 5747 Visibility: keybase1.TLFVisibility_PRIVATE, 5748 TopicName: topicName, 5749 }) 5750 require.NoError(t, err) 5751 consumeNewMsgRemote(t, listener1, chat1.MessageType_JOIN) 5752 consumeNewMsgRemote(t, listener2, chat1.MessageType_JOIN) 5753 5754 _, err = ctc.as(t, users[1]).chatLocalHandler().LeaveConversationLocal(tc2.startCtx, channelID) 5755 require.NoError(t, err) 5756 consumeNewMsgRemote(t, listener1, chat1.MessageType_LEAVE) 5757 }) 5758 } 5759 5760 func TestChatSrvTopicNameState(t *testing.T) { 5761 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 5762 // Only run this test for teams 5763 switch mt { 5764 case chat1.ConversationMembersType_TEAM: 5765 default: 5766 return 5767 } 5768 5769 ctc := makeChatTestContext(t, "TestChatSrvTopicNameState", 1) 5770 defer ctc.cleanup() 5771 users := ctc.users() 5772 5773 ui := kbtest.NewChatUI() 5774 ctc.as(t, users[0]).h.mockChatUI = ui 5775 listener0 := newServerChatListener() 5776 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener0) 5777 ctc.world.Tcs[users[0].Username].ChatG.Syncer.(*Syncer).isConnected = true 5778 tc := ctc.world.Tcs[users[0].Username] 5779 ri := ctc.as(t, users[0]).ri 5780 5781 firstConv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt) 5782 consumeNewConversation(t, listener0, firstConv.Id) 5783 5784 topicName := "MIKE" 5785 ctx := ctc.as(t, users[0]).startCtx 5786 ncres, err := ctc.as(t, users[0]).chatLocalHandler().NewConversationLocal(ctx, 5787 chat1.NewConversationLocalArg{ 5788 TlfName: firstConv.TlfName, 5789 TopicName: &topicName, 5790 TopicType: chat1.TopicType_CHAT, 5791 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 5792 MembersType: chat1.ConversationMembersType_TEAM, 5793 }) 5794 require.NoError(t, err) 5795 convInfo := ncres.Conv.Info 5796 consumeNewConversation(t, listener0, convInfo.Id) 5797 consumeNewMsgRemote(t, listener0, chat1.MessageType_JOIN) 5798 consumeTeamType(t, listener0) 5799 consumeNewMsgRemote(t, listener0, chat1.MessageType_SYSTEM) 5800 consumeNewMsgRemote(t, listener0, chat1.MessageType_SYSTEM) 5801 5802 // Delete the conv, make sure we can still create a new channel after 5803 _, err = ctc.as(t, users[0]).chatLocalHandler().DeleteConversationLocal(ctx, 5804 chat1.DeleteConversationLocalArg{ 5805 ConvID: convInfo.Id, 5806 }) 5807 require.NoError(t, err) 5808 consumeLeaveConv(t, listener0) 5809 consumeTeamType(t, listener0) 5810 t.Logf("Deleted conv") 5811 5812 topicName = "josh" 5813 ncres, err = ctc.as(t, users[0]).chatLocalHandler().NewConversationLocal(ctx, 5814 chat1.NewConversationLocalArg{ 5815 TlfName: firstConv.TlfName, 5816 TopicName: &topicName, 5817 TopicType: chat1.TopicType_CHAT, 5818 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 5819 MembersType: chat1.ConversationMembersType_TEAM, 5820 }) 5821 require.NoError(t, err) 5822 conv := ncres.Conv 5823 convInfo = conv.Info 5824 consumeNewConversation(t, listener0, convInfo.Id) 5825 consumeNewMsgRemote(t, listener0, chat1.MessageType_JOIN) 5826 consumeNewMsgRemote(t, listener0, chat1.MessageType_SYSTEM) 5827 consumeNewMsgRemote(t, listener0, chat1.MessageType_SYSTEM) 5828 5829 // Creating a conversation with same topic name just returns the matching one 5830 topicName = "random" 5831 ncarg := chat1.NewConversationLocalArg{ 5832 TlfName: convInfo.TlfName, 5833 TopicName: &topicName, 5834 TopicType: chat1.TopicType_CHAT, 5835 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 5836 MembersType: chat1.ConversationMembersType_TEAM, 5837 } 5838 ncres, err = ctc.as(t, users[0]).chatLocalHandler().NewConversationLocal(ctx, ncarg) 5839 require.NoError(t, err) 5840 randomConvID := ncres.Conv.GetConvID() 5841 consumeNewConversation(t, listener0, randomConvID) 5842 consumeNewMsgRemote(t, listener0, chat1.MessageType_JOIN) 5843 consumeNewMsgRemote(t, listener0, chat1.MessageType_SYSTEM) 5844 5845 ncres, err = ctc.as(t, users[0]).chatLocalHandler().NewConversationLocal(ctx, ncarg) 5846 require.NoError(t, err) 5847 require.Equal(t, randomConvID, ncres.Conv.GetConvID()) 5848 assertNoNewConversation(t, listener0) 5849 5850 // Try to change topic name to one that exists 5851 plarg := chat1.PostLocalArg{ 5852 ConversationID: convInfo.Id, 5853 Msg: chat1.MessagePlaintext{ 5854 ClientHeader: chat1.MessageClientHeader{ 5855 Conv: convInfo.Triple, 5856 MessageType: chat1.MessageType_METADATA, 5857 TlfName: convInfo.TlfName, 5858 }, 5859 MessageBody: chat1.NewMessageBodyWithMetadata(chat1.MessageConversationMetadata{ 5860 ConversationTitle: topicName, 5861 }), 5862 }, 5863 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 5864 } 5865 _, err = ctc.as(t, users[0]).chatLocalHandler().PostLocal(ctx, plarg) 5866 require.Error(t, err) 5867 require.IsType(t, DuplicateTopicNameError{}, err) 5868 plarg.Msg.MessageBody = chat1.NewMessageBodyWithMetadata(chat1.MessageConversationMetadata{ 5869 ConversationTitle: "EULALIA", 5870 }) 5871 _, err = ctc.as(t, users[0]).chatLocalHandler().PostLocal(ctx, plarg) 5872 require.NoError(t, err) 5873 consumeNewMsgRemote(t, listener0, chat1.MessageType_METADATA) 5874 5875 // Create race with topic name state, and make sure we do the right thing 5876 plarg.Msg.MessageBody = chat1.NewMessageBodyWithMetadata(chat1.MessageConversationMetadata{ 5877 ConversationTitle: "ANOTHERONE", 5878 }) 5879 sender := NewBlockingSender(tc.Context(), NewBoxer(tc.Context()), 5880 func() chat1.RemoteInterface { return ri }) 5881 prepareRes, err := sender.Prepare(ctx, plarg.Msg, mt, &conv, nil) 5882 require.NoError(t, err) 5883 msg1 := prepareRes.Boxed 5884 ts1 := prepareRes.TopicNameState 5885 prepareRes, err = sender.Prepare(ctx, plarg.Msg, mt, &conv, nil) 5886 require.NoError(t, err) 5887 msg2 := prepareRes.Boxed 5888 ts2 := prepareRes.TopicNameState 5889 require.True(t, ts1.Eq(*ts2)) 5890 5891 _, err = ri.PostRemote(ctx, chat1.PostRemoteArg{ 5892 ConversationID: convInfo.Id, 5893 MessageBoxed: msg1, 5894 TopicNameState: ts1, 5895 }) 5896 require.NoError(t, err) 5897 consumeNewMsgRemote(t, listener0, chat1.MessageType_METADATA) 5898 5899 _, err = ri.PostRemote(ctx, chat1.PostRemoteArg{ 5900 ConversationID: convInfo.Id, 5901 MessageBoxed: msg2, 5902 TopicNameState: ts2, 5903 }) 5904 require.Error(t, err) 5905 require.IsType(t, libkb.ChatStalePreviousStateError{}, err) 5906 }) 5907 } 5908 5909 func TestChatSrvUnboxMobilePushNotification(t *testing.T) { 5910 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 5911 ctc := makeChatTestContext(t, "TestChatSrvUnboxMobilePushNotification", 1) 5912 defer ctc.cleanup() 5913 users := ctc.users() 5914 5915 // Only run this test for teams 5916 switch mt { 5917 case chat1.ConversationMembersType_TEAM: 5918 default: 5919 return 5920 } 5921 5922 ctx := ctc.as(t, users[0]).startCtx 5923 tc := ctc.world.Tcs[users[0].Username] 5924 uid := users[0].User.GetUID().ToBytes() 5925 convInfo := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt) 5926 conv, err := utils.GetVerifiedConv(ctx, tc.Context(), uid, convInfo.Id, 5927 types.InboxSourceDataSourceAll) 5928 require.NoError(t, err) 5929 ri := ctc.as(t, users[0]).ri 5930 sender := NewBlockingSender(tc.Context(), NewBoxer(tc.Context()), 5931 func() chat1.RemoteInterface { return ri }) 5932 5933 assertUnboxMobilePushNotif := func(msgArg chat1.MessagePlaintext, expectedMsg string) { 5934 5935 prepareRes, err := sender.Prepare(ctx, msgArg, mt, &conv, nil) 5936 require.NoError(t, err) 5937 msg := prepareRes.Boxed 5938 msg.ServerHeader = &chat1.MessageServerHeader{ 5939 MessageID: 10, 5940 } 5941 5942 mh := codec.MsgpackHandle{WriteExt: true} 5943 var data []byte 5944 enc := codec.NewEncoderBytes(&data, &mh) 5945 require.NoError(t, enc.Encode(msg)) 5946 encMsg := base64.StdEncoding.EncodeToString(data) 5947 unboxRes, err := ctc.as(t, users[0]).chatLocalHandler().UnboxMobilePushNotification(context.TODO(), 5948 chat1.UnboxMobilePushNotificationArg{ 5949 ConvID: convInfo.Id.String(), 5950 MembersType: mt, 5951 Payload: encMsg, 5952 }) 5953 require.NoError(t, err) 5954 require.Equal(t, unboxRes, expectedMsg) 5955 } 5956 5957 // TEXT msg 5958 msgArg := chat1.MessagePlaintext{ 5959 ClientHeader: chat1.MessageClientHeader{ 5960 Conv: convInfo.Triple, 5961 MessageType: chat1.MessageType_TEXT, 5962 TlfName: convInfo.TlfName, 5963 Sender: uid, 5964 }, 5965 MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{ 5966 Body: "PUSH", 5967 }), 5968 } 5969 assertUnboxMobilePushNotif(msgArg, "PUSH") 5970 5971 // non-text type 5972 msgArg = chat1.MessagePlaintext{ 5973 ClientHeader: chat1.MessageClientHeader{ 5974 Conv: convInfo.Triple, 5975 MessageType: chat1.MessageType_HEADLINE, 5976 TlfName: convInfo.TlfName, 5977 Sender: uid, 5978 }, 5979 MessageBody: chat1.NewMessageBodyWithHeadline(chat1.MessageHeadline{ 5980 Headline: "hi", 5981 }), 5982 } 5983 assertUnboxMobilePushNotif(msgArg, "") 5984 }) 5985 } 5986 5987 func TestChatSrvImplicitConversation(t *testing.T) { 5988 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 5989 if mt != chat1.ConversationMembersType_IMPTEAMNATIVE { 5990 return 5991 } 5992 ctc := makeChatTestContext(t, "ImplicitConversation", 2) 5993 defer ctc.cleanup() 5994 5995 users := ctc.users() 5996 displayName := users[0].Username + "," + users[1].Username 5997 tc := ctc.world.Tcs[users[0].Username] 5998 tc1 := ctc.world.Tcs[users[1].Username] 5999 6000 listener0 := newServerChatListener() 6001 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener0) 6002 listener1 := newServerChatListener() 6003 ctc.as(t, users[1]).h.G().NotifyRouter.AddListener(listener1) 6004 6005 consumeIdentify := func(ctx context.Context, listener *serverChatListener) { 6006 // check identify updates 6007 var update keybase1.CanonicalTLFNameAndIDWithBreaks 6008 select { 6009 case update = <-listener.identifyUpdate: 6010 t.Logf("identify update: %+v", update) 6011 case <-time.After(20 * time.Second): 6012 require.Fail(t, "no identify") 6013 } 6014 require.Empty(t, update.Breaks.Breaks) 6015 globals.CtxIdentifyNotifier(ctx).Reset() 6016 globals.CtxKeyFinder(ctx, tc.Context()).Reset() 6017 } 6018 6019 ctx := ctc.as(t, users[0]).startCtx 6020 ctx = globals.CtxModifyIdentifyNotifier(ctx, NewSimpleIdentifyNotifier(tc.Context())) 6021 tc.Context().PushHandler.(*PushHandler).identNotifier = DummyIdentifyNotifier{} 6022 tc1.Context().PushHandler.(*PushHandler).identNotifier = DummyIdentifyNotifier{} 6023 6024 res, err := ctc.as(t, users[0]).chatLocalHandler().FindConversationsLocal(ctx, 6025 chat1.FindConversationsLocalArg{ 6026 TlfName: displayName, 6027 MembersType: chat1.ConversationMembersType_IMPTEAMNATIVE, 6028 Visibility: keybase1.TLFVisibility_PRIVATE, 6029 TopicType: chat1.TopicType_CHAT, 6030 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 6031 }) 6032 require.NoError(t, err) 6033 require.Equal(t, 0, len(res.Conversations), "conv found") 6034 6035 // create a new conversation 6036 ncres, err := ctc.as(t, users[0]).chatLocalHandler().NewConversationLocal(ctx, 6037 chat1.NewConversationLocalArg{ 6038 TlfName: displayName, 6039 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 6040 TopicType: chat1.TopicType_CHAT, 6041 MembersType: chat1.ConversationMembersType_IMPTEAMNATIVE, 6042 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 6043 }) 6044 require.NoError(t, err) 6045 consumeNewConversation(t, listener0, ncres.Conv.GetConvID()) 6046 assertNoNewConversation(t, listener1) 6047 consumeIdentify(ctx, listener0) // encrypt for first message 6048 6049 uid := users[0].User.GetUID().ToBytes() 6050 conv, err := utils.GetUnverifiedConv(ctx, tc.Context(), uid, ncres.Conv.Info.Id, 6051 types.InboxSourceDataSourceRemoteOnly) 6052 require.NoError(t, err) 6053 require.NotEmpty(t, conv.Conv.MaxMsgSummaries, "created conversation does not have a message") 6054 require.Equal(t, ncres.Conv.Info.MembersType, chat1.ConversationMembersType_IMPTEAMNATIVE, 6055 "implicit team") 6056 6057 t.Logf("ncres tlf name: %s", ncres.Conv.Info.TlfName) 6058 6059 // user 0 sends a message to conv 6060 _, err = ctc.as(t, users[0]).chatLocalHandler().PostLocal(ctx, chat1.PostLocalArg{ 6061 ConversationID: ncres.Conv.Info.Id, 6062 Msg: chat1.MessagePlaintext{ 6063 ClientHeader: chat1.MessageClientHeader{ 6064 Conv: ncres.Conv.Info.Triple, 6065 MessageType: chat1.MessageType_TEXT, 6066 TlfName: ncres.Conv.Info.TlfName, 6067 }, 6068 MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{ 6069 Body: "HI", 6070 }), 6071 }, 6072 }) 6073 require.NoError(t, err) 6074 consumeIdentify(ctx, listener0) // EncryptionKeys 6075 6076 // user 1 sends a message to conv 6077 ctx = ctc.as(t, users[1]).startCtx 6078 ctx = globals.CtxModifyIdentifyNotifier(ctx, NewSimpleIdentifyNotifier(tc1.Context())) 6079 _, err = ctc.as(t, users[1]).chatLocalHandler().PostLocal(ctx, chat1.PostLocalArg{ 6080 ConversationID: ncres.Conv.Info.Id, 6081 Msg: chat1.MessagePlaintext{ 6082 ClientHeader: chat1.MessageClientHeader{ 6083 Conv: ncres.Conv.Info.Triple, 6084 MessageType: chat1.MessageType_TEXT, 6085 TlfName: ncres.Conv.Info.TlfName, 6086 }, 6087 MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{ 6088 Body: "Hello", 6089 }), 6090 }, 6091 }) 6092 require.NoError(t, err) 6093 consumeIdentify(ctx, listener1) // EncryptionKeys 6094 6095 // user 1 finds the conversation 6096 res, err = ctc.as(t, users[1]).chatLocalHandler().FindConversationsLocal(ctx, 6097 chat1.FindConversationsLocalArg{ 6098 TlfName: displayName, 6099 MembersType: chat1.ConversationMembersType_IMPTEAMNATIVE, 6100 Visibility: keybase1.TLFVisibility_PRIVATE, 6101 TopicType: chat1.TopicType_CHAT, 6102 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 6103 }) 6104 require.NoError(t, err) 6105 require.Equal(t, 1, len(res.Conversations), "no convs found") 6106 }) 6107 } 6108 6109 func TestChatSrvImpTeamExistingKBFS(t *testing.T) { 6110 os.Setenv("KEYBASE_FEATURES", "admin") 6111 defer os.Setenv("KEYBASE_FEATURES", "") 6112 ctc := makeChatTestContext(t, "NewConversationLocal", 2) 6113 defer ctc.cleanup() 6114 users := ctc.users() 6115 6116 c1 := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, chat1.ConversationMembersType_KBFS, ctc.as(t, users[1]).user()) 6117 c2 := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, chat1.ConversationMembersType_IMPTEAMNATIVE, ctc.as(t, users[1]).user()) 6118 6119 t.Logf("c1: %v c2: %v", c1, c2) 6120 if !c2.Id.Eq(c1.Id) { 6121 t.Fatalf("2nd call to NewConversationLocal as IMPTEAM for a KBFS conversation did not return the same conversation ID") 6122 } 6123 } 6124 6125 func TestChatSrvTeamTypeChanged(t *testing.T) { 6126 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 6127 ctc := makeChatTestContext(t, "TestChatSrvTeamTypeChanged", 2) 6128 defer ctc.cleanup() 6129 users := ctc.users() 6130 6131 // Only run this test for teams 6132 switch mt { 6133 case chat1.ConversationMembersType_TEAM: 6134 default: 6135 return 6136 } 6137 6138 ctx := ctc.as(t, users[0]).startCtx 6139 listener0 := newServerChatListener() 6140 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener0) 6141 listener1 := newServerChatListener() 6142 ctc.as(t, users[1]).h.G().NotifyRouter.AddListener(listener1) 6143 6144 conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt, 6145 ctc.as(t, users[1]).user()) 6146 inboxRes, err := ctc.as(t, users[0]).chatLocalHandler().GetInboxAndUnboxLocal(ctx, 6147 chat1.GetInboxAndUnboxLocalArg{ 6148 Query: &chat1.GetInboxLocalQuery{ 6149 ConvIDs: []chat1.ConversationID{conv.Id}, 6150 }, 6151 }) 6152 require.NoError(t, err) 6153 require.NotNil(t, inboxRes.Conversations[0].Notifications) 6154 require.True(t, inboxRes.Conversations[0].Notifications.Settings[keybase1.DeviceType_DESKTOP][chat1.NotificationKind_GENERIC]) 6155 6156 topicName := "zjoinonsend" 6157 channel, err := ctc.as(t, users[0]).chatLocalHandler().NewConversationLocal(ctx, 6158 chat1.NewConversationLocalArg{ 6159 TlfName: conv.TlfName, 6160 TopicName: &topicName, 6161 TopicType: chat1.TopicType_CHAT, 6162 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 6163 MembersType: chat1.ConversationMembersType_TEAM, 6164 }) 6165 t.Logf("conv: %s chan: %s", conv.Id, channel.Conv.GetConvID()) 6166 require.NoError(t, err) 6167 consumeNewMsgRemote(t, listener0, chat1.MessageType_JOIN) 6168 select { 6169 case info := <-listener1.teamType: 6170 require.Equal(t, conv.Id, info.ConvID) 6171 require.Equal(t, chat1.TeamType_COMPLEX, info.TeamType) 6172 case <-time.After(20 * time.Second): 6173 require.Fail(t, "no team type") 6174 } 6175 select { 6176 case info := <-listener0.teamType: 6177 require.Equal(t, conv.Id, info.ConvID) 6178 require.Equal(t, chat1.TeamType_COMPLEX, info.TeamType) 6179 case <-time.After(20 * time.Second): 6180 require.Fail(t, "no team type") 6181 } 6182 6183 // Check remote notifications 6184 uconv, err := utils.GetUnverifiedConv(ctx, ctc.as(t, users[0]).h.G(), users[0].GetUID().ToBytes(), 6185 conv.Id, types.InboxSourceDataSourceRemoteOnly) 6186 require.NoError(t, err) 6187 require.NotNil(t, uconv.Conv.Notifications) 6188 require.True(t, 6189 uconv.Conv.Notifications.Settings[keybase1.DeviceType_DESKTOP][chat1.NotificationKind_GENERIC]) 6190 6191 inboxRes, err = ctc.as(t, users[0]).chatLocalHandler().GetInboxAndUnboxLocal(ctx, 6192 chat1.GetInboxAndUnboxLocalArg{ 6193 Query: &chat1.GetInboxLocalQuery{ 6194 ConvIDs: []chat1.ConversationID{conv.Id}, 6195 }, 6196 }) 6197 require.NoError(t, err) 6198 require.Equal(t, 1, len(inboxRes.Conversations)) 6199 require.Equal(t, chat1.TeamType_COMPLEX, inboxRes.Conversations[0].Info.TeamType) 6200 require.NotNil(t, inboxRes.Conversations[0].Notifications) 6201 require.True(t, inboxRes.Conversations[0].Notifications.Settings[keybase1.DeviceType_DESKTOP][chat1.NotificationKind_GENERIC]) 6202 }) 6203 } 6204 6205 func TestChatSrvDeleteConversation(t *testing.T) { 6206 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 6207 // Only run this test for teams 6208 switch mt { 6209 case chat1.ConversationMembersType_TEAM: 6210 default: 6211 return 6212 } 6213 6214 ctc := makeChatTestContext(t, "TestChatSrvDeleteConversation", 2) 6215 defer ctc.cleanup() 6216 users := ctc.users() 6217 6218 ctx := ctc.as(t, users[0]).startCtx 6219 ctx1 := ctc.as(t, users[1]).startCtx 6220 listener0 := newServerChatListener() 6221 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener0) 6222 listener1 := newServerChatListener() 6223 ctc.as(t, users[1]).h.G().NotifyRouter.AddListener(listener1) 6224 ui := kbtest.NewChatUI() 6225 ctc.as(t, users[0]).h.mockChatUI = ui 6226 ctc.as(t, users[1]).h.mockChatUI = ui 6227 ctc.world.Tcs[users[0].Username].ChatG.Syncer.(*Syncer).isConnected = true 6228 ctc.world.Tcs[users[1].Username].ChatG.Syncer.(*Syncer).isConnected = true 6229 6230 conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt, 6231 ctc.as(t, users[1]).user()) 6232 consumeNewConversation(t, listener0, conv.Id) 6233 consumeNewConversation(t, listener1, conv.Id) 6234 6235 _, err := ctc.as(t, users[0]).chatLocalHandler().DeleteConversationLocal(ctx, 6236 chat1.DeleteConversationLocalArg{ 6237 ConvID: conv.Id, 6238 }) 6239 require.Error(t, err) 6240 require.IsType(t, libkb.ChatClientError{}, err) 6241 6242 topicName := "zjoinonsend" 6243 channel, err := ctc.as(t, users[0]).chatLocalHandler().NewConversationLocal(ctx, 6244 chat1.NewConversationLocalArg{ 6245 TlfName: conv.TlfName, 6246 TopicName: &topicName, 6247 TopicType: chat1.TopicType_CHAT, 6248 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 6249 MembersType: chat1.ConversationMembersType_TEAM, 6250 }) 6251 require.NoError(t, err) 6252 channelConvID := channel.Conv.GetConvID() 6253 t.Logf("conv: %s chan: %s", conv.Id, channelConvID) 6254 consumeNewConversation(t, listener0, channelConvID) 6255 assertNoNewConversation(t, listener1) 6256 consumeNewMsgRemote(t, listener0, chat1.MessageType_JOIN) 6257 consumeTeamType(t, listener0) 6258 consumeTeamType(t, listener1) 6259 consumeNewMsgRemote(t, listener0, chat1.MessageType_SYSTEM) 6260 consumeNewMsgRemote(t, listener0, chat1.MessageType_SYSTEM) 6261 consumeNewMsgRemote(t, listener1, chat1.MessageType_SYSTEM) 6262 consumeNewMsgRemote(t, listener1, chat1.MessageType_SYSTEM) 6263 6264 uid := users[0].User.GetUID().ToBytes() 6265 g := ctc.world.Tcs[users[0].Username].Context() 6266 _, lconvs, err := storage.NewInbox(g).Read(context.TODO(), uid, &chat1.GetInboxQuery{ 6267 ConvID: &channelConvID, 6268 }) 6269 require.NoError(t, err) 6270 require.Equal(t, 1, len(lconvs)) 6271 require.Equal(t, lconvs[0].GetConvID(), channelConvID) 6272 require.Equal(t, chat1.ConversationExistence_ACTIVE, lconvs[0].Conv.Metadata.Existence) 6273 6274 _, err = ctc.as(t, users[1]).chatLocalHandler().JoinConversationByIDLocal(ctx1, 6275 channelConvID) 6276 require.NoError(t, err) 6277 consumeNewMsgRemote(t, listener0, chat1.MessageType_JOIN) 6278 consumeNewMsgRemote(t, listener1, chat1.MessageType_JOIN) 6279 consumeMembersUpdate(t, listener0) 6280 consumeJoinConv(t, listener1) 6281 // second join attempt doesn't error 6282 _, err = ctc.as(t, users[1]).chatLocalHandler().JoinConversationByIDLocal(ctx1, 6283 channelConvID) 6284 require.NoError(t, err) 6285 6286 _, err = ctc.as(t, users[1]).chatLocalHandler().DeleteConversationLocal(ctx1, 6287 chat1.DeleteConversationLocalArg{ 6288 ConvID: channelConvID, 6289 }) 6290 require.Error(t, err) 6291 require.IsType(t, libkb.ChatClientError{}, err) 6292 6293 _, err = ctc.as(t, users[0]).chatLocalHandler().DeleteConversationLocal(ctx, 6294 chat1.DeleteConversationLocalArg{ 6295 ConvID: channelConvID, 6296 }) 6297 require.NoError(t, err) 6298 consumeLeaveConv(t, listener0) 6299 consumeLeaveConv(t, listener1) 6300 consumeMembersUpdate(t, listener0) 6301 consumeMembersUpdate(t, listener1) 6302 consumeTeamType(t, listener0) 6303 consumeTeamType(t, listener1) 6304 6305 updates := consumeNewThreadsStale(t, listener0) 6306 require.Equal(t, 1, len(updates)) 6307 require.Equal(t, channelConvID, updates[0].ConvID, "wrong cid") 6308 require.Equal(t, chat1.StaleUpdateType_CLEAR, updates[0].UpdateType) 6309 6310 updates = consumeNewThreadsStale(t, listener1) 6311 require.Equal(t, 1, len(updates)) 6312 require.Equal(t, channelConvID, updates[0].ConvID, "wrong cid") 6313 require.Equal(t, chat1.StaleUpdateType_CLEAR, updates[0].UpdateType) 6314 6315 _, lconvs, err = storage.NewInbox(g).Read(context.TODO(), uid, &chat1.GetInboxQuery{ 6316 ConvID: &channelConvID, 6317 MemberStatus: []chat1.ConversationMemberStatus{chat1.ConversationMemberStatus_LEFT}, 6318 Existences: []chat1.ConversationExistence{chat1.ConversationExistence_DELETED}, 6319 }) 6320 require.NoError(t, err) 6321 require.Equal(t, 1, len(lconvs)) 6322 require.Equal(t, lconvs[0].GetConvID(), channelConvID) 6323 6324 iboxRes, err := ctc.as(t, users[0]).chatLocalHandler().GetInboxAndUnboxLocal(ctx, 6325 chat1.GetInboxAndUnboxLocalArg{}) 6326 require.NoError(t, err) 6327 require.Equal(t, 1, len(iboxRes.Conversations)) 6328 require.Equal(t, conv.Id, iboxRes.Conversations[0].GetConvID()) 6329 }) 6330 } 6331 6332 func kickTeamRekeyd(g *libkb.GlobalContext, t libkb.TestingTB) { 6333 mctx := libkb.NewMetaContextBackground(g) 6334 apiArg := libkb.APIArg{ 6335 Endpoint: "test/accelerate_team_rekeyd", 6336 Args: libkb.HTTPArgs{}, 6337 SessionType: libkb.APISessionTypeREQUIRED, 6338 } 6339 6340 _, err := g.API.Post(mctx, apiArg) 6341 if err != nil { 6342 t.Fatalf("Failed to accelerate team rekeyd: %s", err) 6343 } 6344 } 6345 6346 func logout(g *libkb.GlobalContext) error { 6347 m := libkb.NewMetaContextBackground(g) 6348 return m.LogoutKillSecrets() 6349 } 6350 6351 func TestChatSrvNewConvAfterReset(t *testing.T) { 6352 useRemoteMock = false 6353 defer func() { useRemoteMock = true }() 6354 ctc := makeChatTestContext(t, "TestChatSrvNewConvAfterReset", 2) 6355 defer ctc.cleanup() 6356 6357 users := ctc.users() 6358 ctx := ctc.as(t, users[0]).startCtx 6359 tc := ctc.world.Tcs[users[0].Username] 6360 uid := gregor1.UID(users[0].GetUID().ToBytes()) 6361 conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 6362 chat1.ConversationMembersType_IMPTEAMNATIVE, users[1]) 6363 mustPostLocalForTest(t, ctc, users[0], conv, chat1.NewMessageBodyWithText(chat1.MessageText{ 6364 Body: "HI", 6365 })) 6366 t.Logf("TLF: %s", conv.TlfName) 6367 require.NoError(t, libkb.ResetAccount(ctc.as(t, users[0]).m, users[0].NormalizedUsername(), 6368 users[0].Passphrase)) 6369 require.NoError(t, users[0].Login(tc.G)) 6370 conv2, created, err := NewConversation(ctx, tc.Context(), uid, users[0].Username+","+users[1].Username, nil, 6371 chat1.TopicType_CHAT, chat1.ConversationMembersType_IMPTEAMNATIVE, keybase1.TLFVisibility_PRIVATE, 6372 nil, func() chat1.RemoteInterface { return ctc.as(t, users[0]).ri }, NewConvFindExistingNormal) 6373 require.NoError(t, err) 6374 require.False(t, created) 6375 require.Equal(t, conv.Id, conv2.Info.Id) 6376 } 6377 6378 func TestChatSrvUserResetAndDeleted(t *testing.T) { 6379 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 6380 // Only run this test for teams 6381 switch mt { 6382 case chat1.ConversationMembersType_TEAM, chat1.ConversationMembersType_IMPTEAMNATIVE, 6383 chat1.ConversationMembersType_IMPTEAMUPGRADE: 6384 default: 6385 return 6386 } 6387 6388 ctc := makeChatTestContext(t, "TestChatSrvUserResetAndDeleted", 4) 6389 defer ctc.cleanup() 6390 users := ctc.users() 6391 6392 ctx := ctc.as(t, users[0]).startCtx 6393 ctx1 := ctc.as(t, users[1]).startCtx 6394 ctx2 := ctc.as(t, users[2]).startCtx 6395 ctx3 := ctc.as(t, users[3]).startCtx 6396 uid0 := users[0].User.GetUID().ToBytes() 6397 tc0 := ctc.world.Tcs[users[0].Username] 6398 listener0 := newServerChatListener() 6399 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener0) 6400 listener1 := newServerChatListener() 6401 ctc.as(t, users[1]).h.G().NotifyRouter.AddListener(listener1) 6402 listener2 := newServerChatListener() 6403 ctc.as(t, users[2]).h.G().NotifyRouter.AddListener(listener2) 6404 listener3 := newServerChatListener() 6405 ctc.as(t, users[3]).h.G().NotifyRouter.AddListener(listener3) 6406 t.Logf("u0: %s, u1: %s, u2: %s, u3: %s", users[0].GetUID(), users[1].GetUID(), users[2].GetUID(), users[3].GetUID()) 6407 6408 conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt, 6409 ctc.as(t, users[1]).user(), ctc.as(t, users[2]).user(), ctc.as(t, users[3]).user()) 6410 6411 t.Logf("reset user 1") 6412 ctcForUser := ctc.as(t, users[1]) 6413 require.NoError(t, libkb.ResetAccount(ctcForUser.m, users[1].NormalizedUsername(), users[1].Passphrase)) 6414 select { 6415 case act := <-listener0.membersUpdate: 6416 require.Equal(t, act.ConvID, conv.Id) 6417 require.Equal(t, 1, len(act.Members)) 6418 require.Equal(t, chat1.ConversationMemberStatus_RESET, act.Members[0].Status) 6419 require.Equal(t, users[1].Username, act.Members[0].Member) 6420 case <-time.After(20 * time.Second): 6421 require.Fail(t, "failed to get members update") 6422 } 6423 select { 6424 case act := <-listener2.membersUpdate: 6425 require.Equal(t, act.ConvID, conv.Id) 6426 require.Equal(t, 1, len(act.Members)) 6427 require.Equal(t, chat1.ConversationMemberStatus_RESET, act.Members[0].Status) 6428 require.Equal(t, users[1].Username, act.Members[0].Member) 6429 case <-time.After(20 * time.Second): 6430 require.Fail(t, "failed to get members update") 6431 } 6432 select { 6433 case act := <-listener3.membersUpdate: 6434 require.Equal(t, act.ConvID, conv.Id) 6435 require.Equal(t, 1, len(act.Members)) 6436 require.Equal(t, chat1.ConversationMemberStatus_RESET, act.Members[0].Status) 6437 require.Equal(t, users[1].Username, act.Members[0].Member) 6438 case <-time.After(20 * time.Second): 6439 require.Fail(t, "failed to get members update") 6440 } 6441 select { 6442 case convID := <-listener1.resetConv: 6443 require.Equal(t, convID, conv.Id) 6444 case <-time.After(20 * time.Second): 6445 require.Fail(t, "failed to get members update") 6446 } 6447 6448 t.Logf("check for correct state after user 1 reset") 6449 iboxRes, err := ctc.as(t, users[0]).chatLocalHandler().GetInboxAndUnboxLocal(ctx, 6450 chat1.GetInboxAndUnboxLocalArg{}) 6451 require.NoError(t, err) 6452 require.Equal(t, 1, len(iboxRes.Conversations)) 6453 require.Equal(t, conv.Id, iboxRes.Conversations[0].GetConvID()) 6454 parts, err := tc0.Context().ParticipantsSource.Get(ctx, uid0, iboxRes.Conversations[0].GetConvID(), 6455 types.InboxSourceDataSourceAll) 6456 require.NoError(t, err) 6457 require.Equal(t, 4, len(parts)) 6458 switch mt { 6459 case chat1.ConversationMembersType_TEAM: 6460 require.Zero(t, len(iboxRes.Conversations[0].Info.ResetNames)) 6461 default: 6462 require.Equal(t, 1, len(iboxRes.Conversations[0].Info.ResetNames)) 6463 require.Equal(t, users[1].Username, iboxRes.Conversations[0].Info.ResetNames[0]) 6464 } 6465 iboxRes, err = ctc.as(t, users[1]).chatLocalHandler().GetInboxAndUnboxLocal(ctx1, 6466 chat1.GetInboxAndUnboxLocalArg{}) 6467 require.NoError(t, err) 6468 switch mt { 6469 case chat1.ConversationMembersType_TEAM: 6470 require.Zero(t, len(iboxRes.Conversations)) 6471 default: 6472 require.Equal(t, 1, len(iboxRes.Conversations)) 6473 require.Equal(t, conv.Id, iboxRes.Conversations[0].GetConvID()) 6474 require.Equal(t, chat1.ConversationMemberStatus_RESET, iboxRes.Conversations[0].Info.MemberStatus) 6475 } 6476 _, err = ctc.as(t, users[1]).chatLocalHandler().PostLocal(ctx1, chat1.PostLocalArg{ 6477 ConversationID: conv.Id, 6478 Msg: chat1.MessagePlaintext{ 6479 ClientHeader: chat1.MessageClientHeader{ 6480 Conv: conv.Triple, 6481 MessageType: chat1.MessageType_TEXT, 6482 TlfName: conv.TlfName, 6483 }, 6484 MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{ 6485 Body: "Hello", 6486 }), 6487 }, 6488 }) 6489 require.Error(t, err) 6490 6491 t.Logf("delete user 3") 6492 ctcForUser3 := ctc.as(t, users[3]) 6493 require.NoError(t, libkb.DeleteAccount(ctcForUser3.m, users[3].NormalizedUsername(), &users[3].Passphrase)) 6494 select { 6495 case <-listener0.membersUpdate: 6496 require.Fail(t, "got members update after delete") 6497 case <-listener2.membersUpdate: 6498 require.Fail(t, "got members update after delete") 6499 default: 6500 } 6501 6502 select { 6503 case <-listener2.resetConv: 6504 require.Fail(t, "got reset conv after delete") 6505 default: 6506 } 6507 6508 // Once deleted we can't log back in or send 6509 g3 := ctc.as(t, users[3]).h.G().ExternalG() 6510 require.NoError(t, logout(g3)) 6511 require.Error(t, users[3].Login(g3)) 6512 _, err = ctc.as(t, users[3]).chatLocalHandler().PostLocal(ctx3, chat1.PostLocalArg{ 6513 ConversationID: conv.Id, 6514 Msg: chat1.MessagePlaintext{ 6515 ClientHeader: chat1.MessageClientHeader{ 6516 Conv: conv.Triple, 6517 MessageType: chat1.MessageType_TEXT, 6518 TlfName: conv.TlfName, 6519 }, 6520 MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{ 6521 Body: "Hello", 6522 }), 6523 }, 6524 }) 6525 require.Error(t, err) 6526 6527 t.Logf("reset user 2") 6528 ctcForUser2 := ctc.as(t, users[2]) 6529 require.NoError(t, libkb.ResetAccount(ctcForUser2.m, users[2].NormalizedUsername(), users[2].Passphrase)) 6530 select { 6531 case act := <-listener0.membersUpdate: 6532 require.Equal(t, act.ConvID, conv.Id) 6533 require.Equal(t, 1, len(act.Members)) 6534 require.Equal(t, chat1.ConversationMemberStatus_RESET, act.Members[0].Status) 6535 require.Equal(t, users[2].Username, act.Members[0].Member) 6536 case <-time.After(20 * time.Second): 6537 require.Fail(t, "failed to get members update") 6538 } 6539 select { 6540 case convID := <-listener2.resetConv: 6541 require.Equal(t, convID, conv.Id) 6542 case <-time.After(20 * time.Second): 6543 require.Fail(t, "failed to get members update") 6544 } 6545 6546 t.Logf("get a PUK for user 1 and not for user 2") 6547 g1 := ctc.as(t, users[1]).h.G().ExternalG() 6548 require.NoError(t, logout(g1)) 6549 require.NoError(t, users[1].Login(g1)) 6550 g2 := ctc.as(t, users[2]).h.G().ExternalG() 6551 require.NoError(t, logout(g2)) 6552 6553 require.NoError(t, ctc.as(t, users[0]).chatLocalHandler().AddTeamMemberAfterReset(ctx, 6554 chat1.AddTeamMemberAfterResetArg{ 6555 Username: users[1].Username, 6556 ConvID: conv.Id, 6557 })) 6558 consumeMembersUpdate(t, listener0) 6559 consumeJoinConv(t, listener1) 6560 require.NoError(t, ctc.as(t, users[0]).chatLocalHandler().AddTeamMemberAfterReset(ctx, 6561 chat1.AddTeamMemberAfterResetArg{ 6562 Username: users[2].Username, 6563 ConvID: conv.Id, 6564 })) 6565 consumeMembersUpdate(t, listener0) 6566 consumeMembersUpdate(t, listener1) 6567 6568 iboxRes, err = ctc.as(t, users[0]).chatLocalHandler().GetInboxAndUnboxLocal(ctx, 6569 chat1.GetInboxAndUnboxLocalArg{}) 6570 require.NoError(t, err) 6571 require.Equal(t, 1, len(iboxRes.Conversations)) 6572 require.Equal(t, conv.Id, iboxRes.Conversations[0].GetConvID()) 6573 parts, err = tc0.Context().ParticipantsSource.Get(ctx, uid0, iboxRes.Conversations[0].GetConvID(), 6574 types.InboxSourceDataSourceAll) 6575 require.NoError(t, err) 6576 require.Equal(t, 4, len(parts)) 6577 require.Zero(t, len(iboxRes.Conversations[0].Info.ResetNames)) 6578 6579 iboxRes, err = ctc.as(t, users[1]).chatLocalHandler().GetInboxAndUnboxLocal(ctx, 6580 chat1.GetInboxAndUnboxLocalArg{}) 6581 require.NoError(t, err) 6582 require.Equal(t, 1, len(iboxRes.Conversations)) 6583 require.Equal(t, conv.Id, iboxRes.Conversations[0].GetConvID()) 6584 require.Nil(t, iboxRes.Conversations[0].Error) 6585 parts, err = tc0.Context().ParticipantsSource.Get(ctx, uid0, iboxRes.Conversations[0].GetConvID(), 6586 types.InboxSourceDataSourceAll) 6587 require.NoError(t, err) 6588 require.Equal(t, 4, len(parts)) 6589 require.Zero(t, len(iboxRes.Conversations[0].Info.ResetNames)) 6590 require.Equal(t, chat1.ConversationMemberStatus_ACTIVE, iboxRes.Conversations[0].Info.MemberStatus) 6591 6592 _, err = ctc.as(t, users[1]).chatLocalHandler().PostLocal(ctx1, chat1.PostLocalArg{ 6593 ConversationID: conv.Id, 6594 Msg: chat1.MessagePlaintext{ 6595 ClientHeader: chat1.MessageClientHeader{ 6596 Conv: conv.Triple, 6597 MessageType: chat1.MessageType_TEXT, 6598 TlfName: conv.TlfName, 6599 }, 6600 MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{ 6601 Body: "Hello", 6602 }), 6603 }, 6604 }) 6605 require.NoError(t, err) 6606 6607 t.Logf("user 2 gets PUK and tries to do stuff") 6608 require.NoError(t, users[2].Login(g2)) 6609 kickTeamRekeyd(g2, t) 6610 for i := 0; i < 200; i++ { 6611 _, err = ctc.as(t, users[2]).chatLocalHandler().PostLocal(ctx2, chat1.PostLocalArg{ 6612 ConversationID: conv.Id, 6613 Msg: chat1.MessagePlaintext{ 6614 ClientHeader: chat1.MessageClientHeader{ 6615 Conv: conv.Triple, 6616 MessageType: chat1.MessageType_TEXT, 6617 TlfName: conv.TlfName, 6618 }, 6619 MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{ 6620 Body: "Hello", 6621 }), 6622 }, 6623 }) 6624 if err == nil { 6625 break 6626 } 6627 time.Sleep(50 * time.Millisecond) 6628 } 6629 require.NoError(t, err) 6630 6631 // Ensure everyone but user3 can access the conversation 6632 for i := range users { 6633 g := ctc.as(t, users[i]).h.G() 6634 ctx = ctc.as(t, users[i]).startCtx 6635 uid := gregor1.UID(users[i].GetUID().ToBytes()) 6636 tv, err := g.ConvSource.Pull(ctx, conv.Id, uid, 6637 chat1.GetThreadReason_GENERAL, nil, nil, nil) 6638 if i == 3 { 6639 require.Error(t, err) 6640 } else { 6641 require.NoError(t, err) 6642 require.NotZero(t, len(tv.Messages)) 6643 } 6644 } 6645 }) 6646 } 6647 6648 func TestChatSrvTeamChannelNameMentions(t *testing.T) { 6649 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 6650 // Only run this test for teams 6651 switch mt { 6652 case chat1.ConversationMembersType_TEAM: 6653 default: 6654 return 6655 } 6656 6657 ctc := makeChatTestContext(t, "TestChatSrvTeamChannelNameMentions", 2) 6658 defer ctc.cleanup() 6659 users := ctc.users() 6660 6661 ctx := ctc.as(t, users[0]).startCtx 6662 ctx1 := ctc.as(t, users[1]).startCtx 6663 listener0 := newServerChatListener() 6664 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener0) 6665 listener1 := newServerChatListener() 6666 ctc.as(t, users[1]).h.G().NotifyRouter.AddListener(listener1) 6667 6668 conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt, 6669 ctc.as(t, users[1]).user()) 6670 consumeNewConversation(t, listener0, conv.Id) 6671 consumeNewConversation(t, listener1, conv.Id) 6672 6673 topicNames := []string{"miketime", "random", "hi"} 6674 for index, topicName := range topicNames { 6675 channel, err := ctc.as(t, users[1]).chatLocalHandler().NewConversationLocal(ctx, 6676 chat1.NewConversationLocalArg{ 6677 TlfName: conv.TlfName, 6678 TopicName: &topicName, 6679 TopicType: chat1.TopicType_CHAT, 6680 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 6681 MembersType: chat1.ConversationMembersType_TEAM, 6682 }) 6683 t.Logf("conv: %s chan: %s, err: %v", conv.Id, channel.Conv.GetConvID(), err) 6684 require.NoError(t, err) 6685 assertNoNewConversation(t, listener0) 6686 consumeNewConversation(t, listener1, channel.Conv.GetConvID()) 6687 consumeNewMsgRemote(t, listener1, chat1.MessageType_JOIN) 6688 if index == 0 { 6689 consumeNewMsgRemote(t, listener0, chat1.MessageType_SYSTEM) 6690 consumeNewMsgRemote(t, listener1, chat1.MessageType_SYSTEM) 6691 } 6692 consumeNewMsgRemote(t, listener0, chat1.MessageType_SYSTEM) 6693 consumeNewMsgRemote(t, listener1, chat1.MessageType_SYSTEM) 6694 6695 _, err = ctc.as(t, users[0]).chatLocalHandler().JoinConversationByIDLocal(ctx1, 6696 channel.Conv.GetConvID()) 6697 require.NoError(t, err) 6698 consumeNewMsgRemote(t, listener0, chat1.MessageType_JOIN) 6699 consumeNewMsgRemote(t, listener1, chat1.MessageType_JOIN) 6700 6701 _, err = ctc.as(t, users[1]).chatLocalHandler().PostLocal(ctx1, chat1.PostLocalArg{ 6702 ConversationID: channel.Conv.GetConvID(), 6703 Msg: chat1.MessagePlaintext{ 6704 ClientHeader: chat1.MessageClientHeader{ 6705 Conv: channel.Conv.Info.Triple, 6706 MessageType: chat1.MessageType_TEXT, 6707 TlfName: channel.Conv.Info.TlfName, 6708 }, 6709 MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{ 6710 Body: fmt.Sprintf("The worst channel is #%s. #error", topicName), 6711 }), 6712 }, 6713 }) 6714 require.NoError(t, err) 6715 consumeNewMsgRemote(t, listener0, chat1.MessageType_TEXT) 6716 consumeNewMsgRemote(t, listener1, chat1.MessageType_TEXT) 6717 6718 tv, err := ctc.as(t, users[0]).chatLocalHandler().GetThreadLocal(ctx, chat1.GetThreadLocalArg{ 6719 ConversationID: channel.Conv.GetConvID(), 6720 Query: &chat1.GetThreadQuery{ 6721 MessageTypes: []chat1.MessageType{chat1.MessageType_TEXT}, 6722 }, 6723 }) 6724 require.NoError(t, err) 6725 uid := users[0].User.GetUID().ToBytes() 6726 ptv := utils.PresentThreadView(ctx, ctc.as(t, users[0]).h.G(), uid, tv.Thread, 6727 channel.Conv.GetConvID()) 6728 require.Equal(t, 1, len(ptv.Messages)) 6729 require.Equal(t, 1, len(ptv.Messages[0].Valid().ChannelNameMentions)) 6730 require.Equal(t, topicName, ptv.Messages[0].Valid().ChannelNameMentions[0].Name) 6731 } 6732 }) 6733 } 6734 6735 func TestChatSrvGetStaticConfig(t *testing.T) { 6736 ctc := makeChatTestContext(t, "GetStaticConfig", 2) 6737 defer ctc.cleanup() 6738 users := ctc.users() 6739 ctx := ctc.as(t, users[0]).startCtx 6740 tc := ctc.world.Tcs[users[0].Username] 6741 res, err := ctc.as(t, ctc.users()[0]).chatLocalHandler().GetStaticConfig(ctx) 6742 require.NoError(t, err) 6743 require.Equal(t, chat1.StaticConfig{ 6744 DeletableByDeleteHistory: chat1.DeletableMessageTypesByDeleteHistory(), 6745 BuiltinCommands: tc.Context().CommandsSource.GetBuiltins(ctx), 6746 }, res) 6747 } 6748 6749 type mockStellar struct { 6750 libkb.Stellar 6751 specFn func([]libkb.MiniChatPayment) (*libkb.MiniChatPaymentSummary, error) 6752 } 6753 6754 func (m *mockStellar) SendMiniChatPayments(mctx libkb.MetaContext, convID chat1.ConversationID, payments []libkb.MiniChatPayment) (res []libkb.MiniChatPaymentResult, err error) { 6755 for _, p := range payments { 6756 res = append(res, libkb.MiniChatPaymentResult{ 6757 Username: p.Username, 6758 PaymentID: stellar1.PaymentID("AHHH"), 6759 Error: nil, 6760 }) 6761 } 6762 return res, nil 6763 } 6764 6765 func (m *mockStellar) SpecMiniChatPayments(mctx libkb.MetaContext, payments []libkb.MiniChatPayment) (res *libkb.MiniChatPaymentSummary, err error) { 6766 return m.specFn(payments) 6767 } 6768 6769 func (m *mockStellar) KnownCurrencyCodeInstant(context.Context, string) (bool, bool) { 6770 return false, false 6771 } 6772 6773 type xlmDeclineChatUI struct { 6774 *kbtest.ChatUI 6775 } 6776 6777 func (c *xlmDeclineChatUI) ChatStellarDataConfirm(ctx context.Context, summary chat1.UIChatPaymentSummary) (bool, error) { 6778 c.StellarDataConfirm <- summary 6779 return false, nil 6780 } 6781 6782 func TestChatSrvStellarUI(t *testing.T) { 6783 useRemoteMock = false 6784 defer func() { useRemoteMock = true }() 6785 ctc := makeChatTestContext(t, "TestChatSrvStellarUI", 3) 6786 defer ctc.cleanup() 6787 users := ctc.users() 6788 6789 delay := 2 * time.Second 6790 // uid := users[0].User.GetUID().ToBytes() 6791 listener := newServerChatListener() 6792 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener) 6793 tc := ctc.world.Tcs[users[0].Username] 6794 ui := kbtest.NewChatUI() 6795 declineUI := &xlmDeclineChatUI{ChatUI: ui} 6796 ctx := ctc.as(t, users[0]).startCtx 6797 ctc.as(t, users[0]).h.mockChatUI = ui 6798 conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 6799 chat1.ConversationMembersType_IMPTEAMNATIVE, ctc.as(t, users[1]).user(), ctc.as(t, users[2]).user()) 6800 mst := &mockStellar{} 6801 tc.G.SetStellar(mst) 6802 tc.ChatG.StellarSender = wallet.NewSender(tc.Context()) 6803 specSuccess := func(payments []libkb.MiniChatPayment) (res *libkb.MiniChatPaymentSummary, err error) { 6804 res = new(libkb.MiniChatPaymentSummary) 6805 res.XLMTotal = "10 XLM" 6806 res.DisplayTotal = "10 USD" 6807 for _, p := range payments { 6808 res.Specs = append(res.Specs, libkb.MiniChatPaymentSpec{ 6809 Username: p.Username, 6810 XLMAmount: p.Amount + " XLM", 6811 }) 6812 } 6813 return res, nil 6814 } 6815 specFail := func(payments []libkb.MiniChatPayment) (res *libkb.MiniChatPaymentSummary, err error) { 6816 return res, errors.New("failed") 6817 } 6818 successCase := func(expectError bool) { 6819 mst.specFn = specSuccess 6820 _, err := ctc.as(t, users[0]).chatLocalHandler().PostTextNonblock(ctx, 6821 chat1.PostTextNonblockArg{ 6822 ConversationID: conv.Id, 6823 TlfName: conv.TlfName, 6824 Body: fmt.Sprintf("+1xlm@%s +5xlm@%s", users[1].Username, users[2].Username), 6825 }) 6826 if expectError { 6827 require.Error(t, err) 6828 } else { 6829 require.NoError(t, err) 6830 } 6831 select { 6832 case <-ui.StellarShowConfirm: 6833 case <-time.After(delay): 6834 require.Fail(t, "no confirm") 6835 } 6836 select { 6837 case data := <-ui.StellarDataConfirm: 6838 require.Equal(t, 2, len(data.Payments)) 6839 require.Equal(t, "10 XLM", data.XlmTotal) 6840 require.Equal(t, "1 XLM", data.Payments[0].XlmAmount) 6841 require.Equal(t, "5 XLM", data.Payments[1].XlmAmount) 6842 require.Equal(t, users[1].Username, data.Payments[0].Username) 6843 require.Equal(t, users[2].Username, data.Payments[1].Username) 6844 case <-time.After(delay): 6845 require.Fail(t, "no confirm") 6846 } 6847 select { 6848 case <-ui.StellarDone: 6849 case <-time.After(delay): 6850 require.Fail(t, "no done") 6851 } 6852 if !expectError { 6853 consumeNewPendingMsg(t, listener) 6854 select { 6855 case msg := <-listener.newMessageLocal: 6856 require.True(t, msg.Message.IsValid()) 6857 require.Equal(t, 2, len(msg.Message.Valid().AtMentions)) 6858 case <-time.After(delay): 6859 require.Fail(t, "no local msg") 6860 } 6861 select { 6862 case msg := <-listener.newMessageRemote: 6863 require.True(t, msg.Message.IsValid()) 6864 require.Equal(t, 2, len(msg.Message.Valid().AtMentions)) 6865 case <-time.After(delay): 6866 require.Fail(t, "no remote msg") 6867 } 6868 } 6869 } 6870 t.Logf("success accept") 6871 successCase(false) 6872 t.Logf("success decline") 6873 ctc.as(t, users[0]).h.mockChatUI = declineUI 6874 successCase(true) 6875 ctc.as(t, users[0]).h.mockChatUI = ui 6876 6877 t.Logf("fail") 6878 mst.specFn = specFail 6879 _, err := ctc.as(t, users[0]).chatLocalHandler().PostTextNonblock(ctx, chat1.PostTextNonblockArg{ 6880 ConversationID: conv.Id, 6881 TlfName: conv.TlfName, 6882 Body: fmt.Sprintf("+1xlm@%s +5xlm@%s", users[1].Username, users[2].Username), 6883 }) 6884 require.Error(t, err) 6885 select { 6886 case <-ui.StellarShowConfirm: 6887 case <-time.After(delay): 6888 require.Fail(t, "no confirm") 6889 } 6890 select { 6891 case <-ui.StellarDataConfirm: 6892 require.Fail(t, "no confirm") 6893 default: 6894 } 6895 select { 6896 case <-ui.StellarDataError: 6897 case <-time.After(delay): 6898 require.Fail(t, "no error") 6899 } 6900 select { 6901 case <-ui.StellarDone: 6902 case <-time.After(delay): 6903 require.Fail(t, "no done") 6904 } 6905 6906 t.Logf("no payments") 6907 _, err = ctc.as(t, users[0]).chatLocalHandler().PostTextNonblock(ctx, chat1.PostTextNonblockArg{ 6908 ConversationID: conv.Id, 6909 TlfName: conv.TlfName, 6910 Body: "pay me back", 6911 }) 6912 require.NoError(t, err) 6913 select { 6914 case <-ui.StellarShowConfirm: 6915 require.Fail(t, "confirm") 6916 default: 6917 } 6918 consumeNewPendingMsg(t, listener) 6919 } 6920 6921 func TestChatSrvEphemeralPolicy(t *testing.T) { 6922 ctc := makeChatTestContext(t, "TestChatSrvEphemeralPolicy", 1) 6923 defer ctc.cleanup() 6924 users := ctc.users() 6925 useRemoteMock = false 6926 defer func() { useRemoteMock = true }() 6927 6928 timeout := 20 * time.Second 6929 ctx := ctc.as(t, users[0]).startCtx 6930 tc := ctc.world.Tcs[users[0].Username] 6931 listener0 := newServerChatListener() 6932 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener0) 6933 getMsg := func(ctx context.Context, convID chat1.ConversationID, msgID chat1.MessageID) chat1.MessageUnboxed { 6934 res, err := ctc.as(t, users[0]).chatLocalHandler().GetMessagesLocal(ctx, chat1.GetMessagesLocalArg{ 6935 ConversationID: convID, 6936 MessageIDs: []chat1.MessageID{msgID}, 6937 }) 6938 require.NoError(t, err) 6939 require.Equal(t, 1, len(res.Messages)) 6940 return res.Messages[0] 6941 } 6942 checkEph := func(convID chat1.ConversationID, exp int) { 6943 select { 6944 case rmsg := <-listener0.newMessageRemote: 6945 msg := getMsg(ctx, convID, rmsg.Message.GetMessageID()) 6946 require.True(t, msg.IsValid()) 6947 require.NotNil(t, msg.Valid().ClientHeader.EphemeralMetadata) 6948 require.Equal(t, gregor1.DurationSec(exp), msg.Valid().ClientHeader.EphemeralMetadata.Lifetime) 6949 case <-time.After(timeout): 6950 require.Fail(t, "no message") 6951 } 6952 } 6953 6954 impconv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 6955 chat1.ConversationMembersType_IMPTEAMNATIVE) 6956 policy := chat1.NewRetentionPolicyWithEphemeral(chat1.RpEphemeral{ 6957 Age: gregor1.DurationSec(86400), 6958 }) 6959 mustSetConvRetentionLocal(t, ctc, users[0], impconv.Id, policy) 6960 consumeSetConvRetention(t, listener0) 6961 msg := consumeNewMsgRemote(t, listener0, chat1.MessageType_SYSTEM) 6962 verifyChangeRetentionSystemMessage(t, msg, chat1.MessageSystemChangeRetention{ 6963 IsTeam: false, 6964 IsInherit: false, 6965 Policy: policy, 6966 MembersType: chat1.ConversationMembersType_IMPTEAMNATIVE, 6967 User: users[0].Username, 6968 }) 6969 6970 mustPostLocalForTest(t, ctc, users[0], impconv, 6971 chat1.NewMessageBodyWithText(chat1.MessageText{ 6972 Body: "HI", 6973 })) 6974 checkEph(impconv.Id, 86400) 6975 _, err := tc.Context().GregorState.InjectItem(ctx, fmt.Sprintf("exploding:%s", impconv.Id), 6976 []byte("3600"), gregor1.TimeOrOffset{}) 6977 require.NoError(t, err) 6978 msgID := mustPostLocalForTest(t, ctc, users[0], impconv, 6979 chat1.NewMessageBodyWithText(chat1.MessageText{ 6980 Body: "HI", 6981 })) 6982 checkEph(impconv.Id, 3600) 6983 mustDeleteMsg(ctx, t, ctc, users[0], impconv, msgID) 6984 consumeNewMsgRemote(t, listener0, chat1.MessageType_DELETE) 6985 6986 teamconv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 6987 chat1.ConversationMembersType_TEAM) 6988 policy = chat1.NewRetentionPolicyWithEphemeral(chat1.RpEphemeral{ 6989 Age: gregor1.DurationSec(86400), 6990 }) 6991 mustSetTeamRetentionLocal(t, ctc, users[0], keybase1.TeamID(teamconv.Triple.Tlfid.String()), policy) 6992 consumeSetTeamRetention(t, listener0) 6993 msg = consumeNewMsgRemote(t, listener0, chat1.MessageType_SYSTEM) 6994 verifyChangeRetentionSystemMessage(t, msg, chat1.MessageSystemChangeRetention{ 6995 IsTeam: true, 6996 IsInherit: false, 6997 Policy: policy, 6998 MembersType: chat1.ConversationMembersType_TEAM, 6999 User: users[0].Username, 7000 }) 7001 msgID = mustPostLocalForTest(t, ctc, users[0], teamconv, 7002 chat1.NewMessageBodyWithText(chat1.MessageText{ 7003 Body: "HI", 7004 })) 7005 checkEph(teamconv.Id, 86400) 7006 mustDeleteMsg(ctx, t, ctc, users[0], teamconv, msgID) 7007 consumeNewMsgRemote(t, listener0, chat1.MessageType_DELETE) 7008 } 7009 7010 func TestChatSrvStellarMessages(t *testing.T) { 7011 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 7012 runWithEphemeral(t, mt, func(ephemeralLifetime *gregor1.DurationSec) { 7013 switch mt { 7014 case chat1.ConversationMembersType_KBFS: 7015 return 7016 default: 7017 // Fall through for other member types. 7018 } 7019 7020 ctc := makeChatTestContext(t, "SrvStellarMessages", 2) 7021 defer ctc.cleanup() 7022 users := ctc.users() 7023 7024 uid := users[0].User.GetUID().ToBytes() 7025 tc := ctc.world.Tcs[users[0].Username] 7026 ctx := ctc.as(t, users[0]).startCtx 7027 listener := newServerChatListener() 7028 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener) 7029 tc.ChatG.Syncer.(*Syncer).isConnected = true 7030 7031 created := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 7032 mt, ctc.as(t, users[1]).user()) 7033 7034 t.Logf("send a request message") 7035 body := chat1.NewMessageBodyWithRequestpayment(chat1.MessageRequestPayment{ 7036 RequestID: stellar1.KeybaseRequestID("dummy id"), 7037 Note: "Test note", 7038 }) 7039 7040 _, err := postLocalEphemeralForTest(t, ctc, users[0], created, body, ephemeralLifetime) 7041 require.NoError(t, err) 7042 7043 var unboxed chat1.UIMessage 7044 select { 7045 case info := <-listener.newMessageRemote: 7046 unboxed = info.Message 7047 require.True(t, unboxed.IsValid(), "invalid message") 7048 require.Equal(t, chat1.MessageType_REQUESTPAYMENT, unboxed.GetMessageType(), "invalid type") 7049 require.Equal(t, body.Requestpayment(), unboxed.Valid().MessageBody.Requestpayment()) 7050 require.False(t, unboxed.IsEphemeral()) 7051 case <-time.After(20 * time.Second): 7052 require.Fail(t, "no event received") 7053 } 7054 consumeNewMsgLocal(t, listener, chat1.MessageType_REQUESTPAYMENT) 7055 7056 tv, err := tc.Context().ConvSource.Pull(ctx, created.Id, uid, 7057 chat1.GetThreadReason_GENERAL, nil, nil, nil) 7058 require.NoError(t, err) 7059 require.NotZero(t, len(tv.Messages)) 7060 require.Equal(t, chat1.MessageType_REQUESTPAYMENT, tv.Messages[0].GetMessageType()) 7061 7062 t.Logf("delete the message") 7063 darg := chat1.PostDeleteNonblockArg{ 7064 ConversationID: created.Id, 7065 TlfName: created.TlfName, 7066 TlfPublic: created.Visibility == keybase1.TLFVisibility_PUBLIC, 7067 Supersedes: unboxed.GetMessageID(), 7068 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 7069 } 7070 res, err := ctc.as(t, users[0]).chatLocalHandler().PostDeleteNonblock(ctx, darg) 7071 require.NoError(t, err) 7072 select { 7073 case info := <-listener.newMessageRemote: 7074 unboxed = info.Message 7075 require.True(t, unboxed.IsValid(), "invalid message") 7076 require.NotNil(t, unboxed.Valid().OutboxID, "no outbox ID") 7077 require.Equal(t, res.OutboxID.String(), *unboxed.Valid().OutboxID, "mismatch outbox ID") 7078 require.Equal(t, chat1.MessageType_DELETE, unboxed.GetMessageType(), "invalid type") 7079 case <-time.After(20 * time.Second): 7080 require.Fail(t, "no event (DELETE) received") 7081 } 7082 consumeNewMsgLocal(t, listener, chat1.MessageType_DELETE) 7083 7084 tv, err = tc.Context().ConvSource.Pull(ctx, created.Id, uid, 7085 chat1.GetThreadReason_GENERAL, nil, nil, nil) 7086 require.NoError(t, err) 7087 require.NotZero(t, len(tv.Messages)) 7088 require.Equal(t, chat1.MessageType_DELETE, tv.Messages[0].GetMessageType()) 7089 for _, msg := range tv.Messages { 7090 require.NotEqual(t, chat1.MessageType_REQUESTPAYMENT, msg.GetMessageType()) 7091 } 7092 }) 7093 }) 7094 } 7095 7096 func TestChatBulkAddToConv(t *testing.T) { 7097 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 7098 switch mt { 7099 case chat1.ConversationMembersType_TEAM: 7100 default: 7101 return 7102 } 7103 7104 ctc := makeChatTestContext(t, "BulkAddToConv", 2) 7105 defer ctc.cleanup() 7106 users := ctc.users() 7107 t.Logf("uid1: %v, uid2: %v", users[0].User.GetUID(), users[1].User.GetUID()) 7108 7109 tc1 := ctc.world.Tcs[users[0].Username] 7110 tc2 := ctc.world.Tcs[users[0].Username] 7111 ctx := ctc.as(t, users[0]).startCtx 7112 7113 listener0 := newServerChatListener() 7114 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener0) 7115 tc1.ChatG.Syncer.(*Syncer).isConnected = true 7116 7117 listener1 := newServerChatListener() 7118 ctc.as(t, users[1]).h.G().NotifyRouter.AddListener(listener1) 7119 tc2.ChatG.Syncer.(*Syncer).isConnected = true 7120 7121 conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 7122 mt, ctc.as(t, users[1]).user()) 7123 7124 // create a channel and bulk add user1 to it 7125 topicName := "bulk" 7126 channel, err := ctc.as(t, users[0]).chatLocalHandler().NewConversationLocal(ctx, 7127 chat1.NewConversationLocalArg{ 7128 TlfName: conv.TlfName, 7129 TopicName: &topicName, 7130 TopicType: chat1.TopicType_CHAT, 7131 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 7132 MembersType: mt, 7133 }) 7134 t.Logf("conv: %s chan: %s, err: %v", conv.Id, channel.Conv.GetConvID(), err) 7135 require.NoError(t, err) 7136 consumeNewMsgRemote(t, listener0, chat1.MessageType_JOIN) 7137 consumeNewMsgRemote(t, listener0, chat1.MessageType_SYSTEM) 7138 consumeNewMsgRemote(t, listener0, chat1.MessageType_SYSTEM) 7139 consumeNewMsgRemote(t, listener1, chat1.MessageType_SYSTEM) 7140 consumeNewMsgRemote(t, listener1, chat1.MessageType_SYSTEM) 7141 7142 usernames := []string{users[1].Username} 7143 err = ctc.as(t, users[0]).chatLocalHandler().BulkAddToConv(ctx, 7144 chat1.BulkAddToConvArg{ 7145 Usernames: usernames, 7146 ConvID: channel.Conv.GetConvID(), 7147 }) 7148 require.NoError(t, err) 7149 7150 assertSysMsg := func(expectedMentions, expectedBody []string, listener *serverChatListener) { 7151 msg := consumeNewMsgRemote(t, listener, chat1.MessageType_SYSTEM) 7152 body := msg.Valid().MessageBody 7153 typ, err := body.MessageType() 7154 require.NoError(t, err) 7155 require.Equal(t, chat1.MessageType_SYSTEM, typ) 7156 sysMsg := body.System() 7157 sysTyp, err := sysMsg.SystemType() 7158 require.NoError(t, err) 7159 require.Equal(t, chat1.MessageSystemType_BULKADDTOCONV, sysTyp) 7160 retMsg := sysMsg.Bulkaddtoconv() 7161 require.Equal(t, expectedBody, retMsg.Usernames) 7162 require.True(t, msg.IsValid()) 7163 require.Equal(t, expectedMentions, msg.Valid().AtMentions) 7164 } 7165 assertSysMsg(usernames, usernames, listener0) 7166 assertSysMsg(usernames, usernames, listener1) 7167 consumeMembersUpdate(t, listener0) 7168 consumeJoinConv(t, listener1) 7169 // u1 can now send 7170 mustPostLocalForTest(t, ctc, users[1], channel.Conv.Info, 7171 chat1.NewMessageBodyWithText(chat1.MessageText{ 7172 Body: "hi", 7173 })) 7174 consumeNewMsgRemote(t, listener0, chat1.MessageType_TEXT) 7175 consumeNewMsgRemote(t, listener1, chat1.MessageType_TEXT) 7176 7177 // some users required 7178 err = ctc.as(t, users[0]).chatLocalHandler().BulkAddToConv(ctx, 7179 chat1.BulkAddToConvArg{ 7180 Usernames: nil, 7181 ConvID: channel.Conv.GetConvID(), 7182 }) 7183 require.Error(t, err) 7184 7185 usernames = []string{"foo"} 7186 err = ctc.as(t, users[0]).chatLocalHandler().BulkAddToConv(ctx, 7187 chat1.BulkAddToConvArg{ 7188 Usernames: usernames, 7189 ConvID: channel.Conv.GetConvID(), 7190 }) 7191 require.NoError(t, err) 7192 assertSysMsg(nil, usernames, listener0) 7193 assertSysMsg(nil, usernames, listener1) 7194 }) 7195 } 7196 7197 func TestChatBulkAddToManyConvs(t *testing.T) { 7198 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 7199 switch mt { 7200 case chat1.ConversationMembersType_TEAM: 7201 default: 7202 return 7203 } 7204 7205 ctc := makeChatTestContext(t, "BulkAddToManyConvs", 2) 7206 defer ctc.cleanup() 7207 users := ctc.users() 7208 t.Logf("uid1: %v, uid2: %v", users[0].User.GetUID(), users[1].User.GetUID()) 7209 7210 tc1 := ctc.world.Tcs[users[0].Username] 7211 tc2 := ctc.world.Tcs[users[0].Username] 7212 ctx := ctc.as(t, users[0]).startCtx 7213 7214 listener0 := newServerChatListener() 7215 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener0) 7216 tc1.ChatG.Syncer.(*Syncer).isConnected = true 7217 7218 listener1 := newServerChatListener() 7219 ctc.as(t, users[1]).h.G().NotifyRouter.AddListener(listener1) 7220 tc2.ChatG.Syncer.(*Syncer).isConnected = true 7221 7222 conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 7223 mt, ctc.as(t, users[1]).user()) 7224 7225 // create channels and bulk add user1 to them 7226 topicName0 := "bulk0" 7227 channel0, err := ctc.as(t, users[0]).chatLocalHandler().NewConversationLocal(ctx, 7228 chat1.NewConversationLocalArg{ 7229 TlfName: conv.TlfName, 7230 TopicName: &topicName0, 7231 TopicType: chat1.TopicType_CHAT, 7232 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 7233 MembersType: mt, 7234 }) 7235 t.Logf("conv: %s chan: %s, err: %v", conv.Id, channel0.Conv.GetConvID(), err) 7236 require.NoError(t, err) 7237 7238 topicName1 := "bulk1" 7239 channel1, err := ctc.as(t, users[0]).chatLocalHandler().NewConversationLocal(ctx, 7240 chat1.NewConversationLocalArg{ 7241 TlfName: conv.TlfName, 7242 TopicName: &topicName1, 7243 TopicType: chat1.TopicType_CHAT, 7244 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 7245 MembersType: mt, 7246 }) 7247 t.Logf("conv: %s chan: %s, err: %v", conv.Id, channel1.Conv.GetConvID(), err) 7248 require.NoError(t, err) 7249 consumeNewMsgRemote(t, listener0, chat1.MessageType_JOIN) 7250 consumeNewMsgRemote(t, listener0, chat1.MessageType_SYSTEM) 7251 consumeNewMsgRemote(t, listener0, chat1.MessageType_SYSTEM) 7252 consumeNewMsgRemote(t, listener0, chat1.MessageType_JOIN) 7253 consumeNewMsgRemote(t, listener0, chat1.MessageType_SYSTEM) 7254 consumeNewMsgRemote(t, listener1, chat1.MessageType_SYSTEM) 7255 consumeNewMsgRemote(t, listener1, chat1.MessageType_SYSTEM) 7256 consumeNewMsgRemote(t, listener1, chat1.MessageType_SYSTEM) 7257 7258 usernames := []string{users[1].Username} 7259 err = ctc.as(t, users[0]).chatLocalHandler().BulkAddToManyConvs(ctx, 7260 chat1.BulkAddToManyConvsArg{ 7261 Usernames: usernames, 7262 Conversations: []chat1.ConversationID{channel0.Conv.GetConvID(), channel1.Conv.GetConvID()}, 7263 }) 7264 require.NoError(t, err) 7265 7266 assertSysMsg := func(expectedMentions, expectedBody []string, listener *serverChatListener) { 7267 msg := consumeNewMsgRemote(t, listener, chat1.MessageType_SYSTEM) 7268 body := msg.Valid().MessageBody 7269 typ, err := body.MessageType() 7270 require.NoError(t, err) 7271 require.Equal(t, chat1.MessageType_SYSTEM, typ) 7272 sysMsg := body.System() 7273 sysTyp, err := sysMsg.SystemType() 7274 require.NoError(t, err) 7275 require.Equal(t, chat1.MessageSystemType_BULKADDTOCONV, sysTyp) 7276 retMsg := sysMsg.Bulkaddtoconv() 7277 require.Equal(t, expectedBody, retMsg.Usernames) 7278 require.True(t, msg.IsValid()) 7279 require.Equal(t, expectedMentions, msg.Valid().AtMentions) 7280 } 7281 //TODO: not sure how to deal with multiple system messages coming in for this? 7282 assertSysMsg(usernames, usernames, listener0) 7283 assertSysMsg(usernames, usernames, listener1) 7284 assertSysMsg(usernames, usernames, listener0) 7285 assertSysMsg(usernames, usernames, listener1) 7286 consumeMembersUpdate(t, listener0) 7287 consumeJoinConv(t, listener1) 7288 7289 // u1 can now send to both channels 7290 mustPostLocalForTest(t, ctc, users[1], channel0.Conv.Info, 7291 chat1.NewMessageBodyWithText(chat1.MessageText{ 7292 Body: "hi", 7293 })) 7294 // consumeNewMsgRemote(t, listener0, chat1.MessageType_TEXT) 7295 // consumeNewMsgRemote(t, listener1, chat1.MessageType_TEXT) 7296 mustPostLocalForTest(t, ctc, users[1], channel1.Conv.Info, 7297 chat1.NewMessageBodyWithText(chat1.MessageText{ 7298 Body: "hi", 7299 })) 7300 // consumeNewMsgRemote(t, listener0, chat1.MessageType_TEXT) 7301 // consumeNewMsgRemote(t, listener1, chat1.MessageType_TEXT) 7302 7303 // some users required 7304 err = ctc.as(t, users[0]).chatLocalHandler().BulkAddToManyConvs(ctx, 7305 chat1.BulkAddToManyConvsArg{ 7306 Usernames: nil, 7307 Conversations: []chat1.ConversationID{channel1.Conv.GetConvID(), channel0.Conv.GetConvID()}, 7308 }) 7309 require.Error(t, err) 7310 }) 7311 } 7312 7313 func TestReacjiStore(t *testing.T) { 7314 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 7315 switch mt { 7316 case chat1.ConversationMembersType_IMPTEAMNATIVE: 7317 default: 7318 return 7319 } 7320 ctc := makeChatTestContext(t, "ReacjiStore", 1) 7321 defer ctc.cleanup() 7322 7323 user := ctc.users()[0] 7324 uid := user.User.GetUID().ToBytes() 7325 tc := ctc.world.Tcs[user.Username] 7326 ctx := ctc.as(t, user).startCtx 7327 7328 listener := newServerChatListener() 7329 ctc.as(t, user).h.G().NotifyRouter.AddListener(listener) 7330 tc.ChatG.Syncer.(*Syncer).isConnected = true 7331 reacjiStore := storage.NewReacjiStore(ctc.as(t, user).h.G()) 7332 assertReacjiStore := func(actual, expected keybase1.UserReacjis, expectedData storage.ReacjiInternalStorage) { 7333 require.Equal(t, actual, expected) 7334 data := reacjiStore.GetInternalStore(ctx, uid) 7335 require.Equal(t, len(data.FrequencyMap), len(data.MtimeMap)) 7336 for name := range data.MtimeMap { 7337 _, ok := data.FrequencyMap[name] 7338 require.True(t, ok) 7339 } 7340 require.Equal(t, expectedData.FrequencyMap, data.FrequencyMap) 7341 require.Equal(t, expectedData.SkinTone, data.SkinTone) 7342 } 7343 7344 expectedData := storage.NewReacjiInternalStorage() 7345 for _, el := range storage.DefaultTopReacjis { 7346 expectedData.FrequencyMap[el.Name] = 0 7347 } 7348 conv := mustCreateConversationForTest(t, ctc, user, chat1.TopicType_CHAT, mt) 7349 // if the user has no history we return the default list 7350 userReacjis := tc.G.ChatHelper.UserReacjis(ctx, uid) 7351 assertReacjiStore(userReacjis, keybase1.UserReacjis{TopReacjis: storage.DefaultTopReacjis}, expectedData) 7352 7353 // post a bunch of reactions, we should end up with these reactions 7354 // replacing the defaults sorted alphabetically (since they tie on 7355 // being used once each) 7356 reactionKeys := []keybase1.UserReacji{ 7357 {Name: ":third_place_medal:"}, 7358 {Name: ":second_place_medal:"}, 7359 {Name: ":first_place_medal:"}, 7360 {Name: ":a:"}, 7361 {Name: ":8ball:"}, 7362 {Name: ":1234:"}, 7363 {Name: ":100:"}, 7364 } 7365 msg := chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hi"}) 7366 textID := mustPostLocalForTest(t, ctc, user, conv, msg) 7367 consumeNewMsgRemote(t, listener, chat1.MessageType_TEXT) 7368 expected := keybase1.UserReacjis{} 7369 for i, reaction := range reactionKeys { 7370 expectedData.FrequencyMap[reaction.Name]++ 7371 mustReactToMsg(ctx, t, ctc, user, conv, textID, reaction.Name) 7372 consumeNewMsgRemote(t, listener, chat1.MessageType_REACTION) 7373 info := consumeReactionUpdate(t, listener) 7374 t.Logf("DEBUG: info: %+v", info.UserReacjis) 7375 expected.TopReacjis = append([]keybase1.UserReacji{reaction}, expected.TopReacjis...) 7376 if i < 5 { 7377 // remove defaults as user values are added 7378 top := storage.DefaultTopReacjis[len(storage.DefaultTopReacjis)-i-1] 7379 delete(expectedData.FrequencyMap, top.Name) 7380 expected.TopReacjis = append(expected.TopReacjis, 7381 storage.DefaultTopReacjis...)[:len(storage.DefaultTopReacjis)] 7382 } 7383 assertReacjiStore(info.UserReacjis, expected, expectedData) 7384 } 7385 // bump "a" to the most frequent 7386 msg = chat1.NewMessageBodyWithText(chat1.MessageText{Body: "hi"}) 7387 textID2 := mustPostLocalForTest(t, ctc, user, conv, msg) 7388 consumeNewMsgRemote(t, listener, chat1.MessageType_TEXT) 7389 mustReactToMsg(ctx, t, ctc, user, conv, textID2, ":100:") 7390 consumeNewMsgRemote(t, listener, chat1.MessageType_REACTION) 7391 expectedData.FrequencyMap[":100:"]++ 7392 info := consumeReactionUpdate(t, listener) 7393 assertReacjiStore(info.UserReacjis, expected, expectedData) 7394 7395 // putSkinTone 7396 expectedSkinTone := keybase1.ReacjiSkinTone(4) 7397 userReacjis, err := ctc.as(t, user).chatLocalHandler().PutReacjiSkinTone(ctx, expectedSkinTone) 7398 require.NoError(t, err) 7399 expected.SkinTone = expectedSkinTone 7400 expectedData.SkinTone = expectedSkinTone 7401 assertReacjiStore(userReacjis, expected, expectedData) 7402 }) 7403 } 7404 7405 func TestGlobalAppNotificationSettings(t *testing.T) { 7406 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 7407 switch mt { 7408 case chat1.ConversationMembersType_IMPTEAMNATIVE: 7409 default: 7410 return 7411 } 7412 ctc := makeChatTestContext(t, "GlobalAppNotificationSettings", 1) 7413 defer ctc.cleanup() 7414 7415 user := ctc.users()[0] 7416 // tc := ctc.world.Tcs[user.Username] 7417 ctx := ctc.as(t, user).startCtx 7418 expectedSettings := map[chat1.GlobalAppNotificationSetting]bool{ 7419 chat1.GlobalAppNotificationSetting_NEWMESSAGES: true, 7420 chat1.GlobalAppNotificationSetting_PLAINTEXTDESKTOP: true, 7421 chat1.GlobalAppNotificationSetting_PLAINTEXTMOBILE: false, 7422 chat1.GlobalAppNotificationSetting_DISABLETYPING: false, 7423 chat1.GlobalAppNotificationSetting_CONVERTHEIC: true, 7424 } 7425 7426 // convert the expectedSettings to the RPC format 7427 strSettings := func() map[string]bool { 7428 s := make(map[string]bool) 7429 for k, v := range expectedSettings { 7430 s[strconv.Itoa(int(k))] = v 7431 } 7432 return s 7433 } 7434 7435 // Test default settings 7436 s, err := ctc.as(t, user).chatLocalHandler().GetGlobalAppNotificationSettingsLocal(ctx) 7437 require.NoError(t, err) 7438 for k, v := range expectedSettings { 7439 require.Equal(t, v, s.Settings[k], fmt.Sprintf("Not equal %v", k)) 7440 // flip all the defaults for the next test 7441 expectedSettings[k] = !v 7442 } 7443 7444 err = ctc.as(t, user).chatLocalHandler().SetGlobalAppNotificationSettingsLocal(ctx, strSettings()) 7445 require.NoError(t, err) 7446 s, err = ctc.as(t, user).chatLocalHandler().GetGlobalAppNotificationSettingsLocal(ctx) 7447 require.NoError(t, err) 7448 for k, v := range expectedSettings { 7449 require.Equal(t, v, s.Settings[k], fmt.Sprintf("Not equal %v", k)) 7450 } 7451 }) 7452 } 7453 7454 func TestMessageDrafts(t *testing.T) { 7455 useRemoteMock = false 7456 defer func() { useRemoteMock = true }() 7457 ctc := makeChatTestContext(t, "TestMessageDrafts", 1) 7458 defer ctc.cleanup() 7459 7460 user := ctc.users()[0] 7461 conv := mustCreateConversationForTest(t, ctc, user, chat1.TopicType_CHAT, 7462 chat1.ConversationMembersType_IMPTEAMNATIVE) 7463 7464 draft := "NEW MESSAAGE" 7465 require.NoError(t, ctc.as(t, user).chatLocalHandler().UpdateUnsentText(context.TODO(), 7466 chat1.UpdateUnsentTextArg{ 7467 ConversationID: conv.Id, 7468 TlfName: conv.TlfName, 7469 Text: draft, 7470 })) 7471 ibres, err := ctc.as(t, user).chatLocalHandler().GetInboxAndUnboxLocal(context.TODO(), 7472 chat1.GetInboxAndUnboxLocalArg{ 7473 Query: &chat1.GetInboxLocalQuery{ 7474 ConvIDs: []chat1.ConversationID{conv.Id}, 7475 }, 7476 }) 7477 require.NoError(t, err) 7478 require.Equal(t, 1, len(ibres.Conversations)) 7479 require.NotNil(t, ibres.Conversations[0].Info.Draft) 7480 require.Equal(t, draft, *ibres.Conversations[0].Info.Draft) 7481 7482 _, err = ctc.as(t, user).chatLocalHandler().PostLocalNonblock(context.TODO(), chat1.PostLocalNonblockArg{ 7483 ConversationID: conv.Id, 7484 Msg: chat1.MessagePlaintext{ 7485 ClientHeader: chat1.MessageClientHeader{ 7486 TlfName: conv.TlfName, 7487 MessageType: chat1.MessageType_TEXT, 7488 }, 7489 MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{ 7490 Body: "HIHIHI", 7491 }), 7492 }, 7493 }) 7494 require.NoError(t, err) 7495 7496 worked := false 7497 for i := 0; i < 5; i++ { 7498 ibres, err = ctc.as(t, user).chatLocalHandler().GetInboxAndUnboxLocal(context.TODO(), 7499 chat1.GetInboxAndUnboxLocalArg{ 7500 Query: &chat1.GetInboxLocalQuery{ 7501 ConvIDs: []chat1.ConversationID{conv.Id}, 7502 }, 7503 }) 7504 require.NoError(t, err) 7505 require.Equal(t, 1, len(ibres.Conversations)) 7506 if ibres.Conversations[0].Info.Draft == nil { 7507 worked = true 7508 break 7509 } 7510 time.Sleep(time.Second) 7511 } 7512 require.True(t, worked) 7513 } 7514 7515 func TestTeamBotSettings(t *testing.T) { 7516 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 7517 runWithEphemeral(t, mt, func(ephemeralLifetime *gregor1.DurationSec) { 7518 if mt == chat1.ConversationMembersType_KBFS { 7519 return 7520 } 7521 ctc := makeChatTestContext(t, "TeamBotSettings", 3) 7522 defer ctc.cleanup() 7523 users := ctc.users() 7524 7525 ui := kbtest.NewChatUI() 7526 ctc.as(t, users[0]).h.mockChatUI = ui 7527 tc := ctc.as(t, users[0]) 7528 listener := newServerChatListener() 7529 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener) 7530 ctc.world.Tcs[users[0].Username].ChatG.Syncer.(*Syncer).isConnected = true 7531 7532 botua := users[1] 7533 botua2 := users[2] 7534 botuaUID := gregor1.UID(botua.User.GetUID().ToBytes()) 7535 botuaUID2 := gregor1.UID(botua2.User.GetUID().ToBytes()) 7536 7537 botuaListener := newServerChatListener() 7538 ctc.as(t, botua).h.G().NotifyRouter.AddListener(botuaListener) 7539 ctc.world.Tcs[botua.Username].ChatG.Syncer.(*Syncer).isConnected = true 7540 botuaListener2 := newServerChatListener() 7541 ctc.as(t, botua2).h.G().NotifyRouter.AddListener(botuaListener2) 7542 ctc.world.Tcs[botua2.Username].ChatG.Syncer.(*Syncer).isConnected = true 7543 ctx := context.TODO() 7544 7545 var err error 7546 created := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt) 7547 7548 teamID, err := keybase1.TeamIDFromString(created.Triple.Tlfid.String()) 7549 require.NoError(t, err) 7550 7551 pollForSeqno := func(expectedSeqno keybase1.Seqno) { 7552 found := false 7553 for !found { 7554 select { 7555 case teamChange := <-listener.teamChangedByID: 7556 found = teamChange.TeamID == teamID && 7557 teamChange.LatestSeqno == expectedSeqno 7558 case <-time.After(20 * time.Second): 7559 require.Fail(t, "no event received") 7560 } 7561 } 7562 } 7563 7564 var unboxed chat1.UIMessage 7565 consumeBotMessage := func(botUsername string, msgTyp chat1.MessageType, l *serverChatListener) { 7566 select { 7567 case info := <-l.newMessageRemote: 7568 unboxed = info.Message 7569 require.True(t, unboxed.IsValid(), "invalid message") 7570 t.Logf("got message with searchable text: %v", unboxed.SearchableText()) 7571 require.Equal(t, msgTyp, unboxed.GetMessageType(), "invalid type") 7572 require.Equal(t, botUsername, unboxed.Valid().BotUsername) 7573 case <-time.After(20 * time.Second): 7574 require.Fail(t, "no event received") 7575 } 7576 } 7577 7578 assertNoMessage := func(l *serverChatListener) { 7579 select { 7580 case info := <-l.newMessageRemote: 7581 unboxed = info.Message 7582 require.Fail(t, "unexpected message received type %v", unboxed.GetMessageType()) 7583 default: 7584 } 7585 } 7586 7587 botSettings := keybase1.TeamBotSettings{ 7588 Triggers: []string{"HI"}, 7589 } 7590 err = ctc.as(t, users[0]).chatLocalHandler().AddBotMember(tc.startCtx, chat1.AddBotMemberArg{ 7591 ConvID: created.Id, 7592 Username: botua.Username, 7593 Role: keybase1.TeamRole_RESTRICTEDBOT, 7594 BotSettings: &botSettings, 7595 }) 7596 require.NoError(t, err) 7597 pollForSeqno(3) 7598 7599 // system message for adding the bot to the team 7600 consumeNewPendingMsg(t, listener) 7601 consumeBotMessage("", chat1.MessageType_SYSTEM, listener) 7602 assertNoMessage(botuaListener) 7603 assertNoMessage(botuaListener2) 7604 consumeNewMsgLocal(t, listener, chat1.MessageType_SYSTEM) 7605 7606 gilres, err := ctc.as(t, users[1]).chatLocalHandler().GetInboxAndUnboxLocal(ctx, chat1.GetInboxAndUnboxLocalArg{ 7607 Query: &chat1.GetInboxLocalQuery{ 7608 ConvIDs: []chat1.ConversationID{created.Id}, 7609 }, 7610 }) 7611 require.NoError(t, err) 7612 require.Equal(t, 1, len(gilres.Conversations)) 7613 require.Equal(t, created.Id, gilres.Conversations[0].GetConvID()) 7614 gconv := gilres.Conversations[0] 7615 require.NotNil(t, gconv.ReaderInfo) 7616 require.Equal(t, keybase1.TeamRole_RESTRICTEDBOT, gconv.ReaderInfo.UntrustedTeamRole) 7617 7618 botSettings2 := keybase1.TeamBotSettings{ 7619 Mentions: true, 7620 } 7621 err = ctc.as(t, users[0]).chatLocalHandler().AddBotMember(tc.startCtx, chat1.AddBotMemberArg{ 7622 ConvID: created.Id, 7623 Username: botua2.Username, 7624 Role: keybase1.TeamRole_RESTRICTEDBOT, 7625 BotSettings: &botSettings2, 7626 }) 7627 require.NoError(t, err) 7628 pollForSeqno(5) 7629 7630 // system message for adding the bot to the team 7631 consumeNewPendingMsg(t, listener) 7632 consumeBotMessage("", chat1.MessageType_SYSTEM, listener) 7633 assertNoMessage(botuaListener) 7634 assertNoMessage(botuaListener2) 7635 consumeNewMsgLocal(t, listener, chat1.MessageType_SYSTEM) 7636 7637 gilres, err = ctc.as(t, users[2]).chatLocalHandler().GetInboxAndUnboxLocal(ctx, chat1.GetInboxAndUnboxLocalArg{ 7638 Query: &chat1.GetInboxLocalQuery{ 7639 ConvIDs: []chat1.ConversationID{created.Id}, 7640 }, 7641 }) 7642 require.NoError(t, err) 7643 require.Equal(t, 1, len(gilres.Conversations)) 7644 require.Equal(t, created.Id, gilres.Conversations[0].GetConvID()) 7645 gconv = gilres.Conversations[0] 7646 require.NotNil(t, gconv.ReaderInfo) 7647 require.Equal(t, keybase1.TeamRole_RESTRICTEDBOT, gconv.ReaderInfo.UntrustedTeamRole) 7648 7649 t.Logf("send a text message") 7650 arg := chat1.PostTextNonblockArg{ 7651 ConversationID: created.Id, 7652 TlfName: created.TlfName, 7653 TlfPublic: created.Visibility == keybase1.TLFVisibility_PUBLIC, 7654 Body: "hi", 7655 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 7656 } 7657 _, err = ctc.as(t, users[0]).chatLocalHandler().PostTextNonblock(tc.startCtx, arg) 7658 require.NoError(t, err) 7659 consumeNewPendingMsg(t, listener) 7660 consumeBotMessage(botua.Username, chat1.MessageType_TEXT, listener) 7661 consumeBotMessage(botua.Username, chat1.MessageType_TEXT, botuaListener) 7662 assertNoMessage(botuaListener2) 7663 consumeNewMsgLocal(t, listener, chat1.MessageType_TEXT) 7664 7665 t.Logf("send ephemeral message") 7666 arg.EphemeralLifetime = ephemeralLifetime 7667 _, err = ctc.as(t, users[0]).chatLocalHandler().PostTextNonblock(tc.startCtx, arg) 7668 require.NoError(t, err) 7669 consumeNewPendingMsg(t, listener) 7670 consumeBotMessage(botua.Username, chat1.MessageType_TEXT, listener) 7671 consumeBotMessage(botua.Username, chat1.MessageType_TEXT, botuaListener) 7672 assertNoMessage(botuaListener2) 7673 consumeNewMsgLocal(t, listener, chat1.MessageType_TEXT) 7674 7675 textUnboxed := unboxed 7676 7677 t.Logf("react to the message") 7678 // An ephemeralLifetime is added if we are reacting to an ephemeral message 7679 reactionKey := ":+1:" 7680 rarg := chat1.PostReactionNonblockArg{ 7681 ConversationID: created.Id, 7682 TlfName: created.TlfName, 7683 TlfPublic: created.Visibility == keybase1.TLFVisibility_PUBLIC, 7684 Supersedes: textUnboxed.GetMessageID(), 7685 Body: reactionKey, 7686 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 7687 } 7688 _, err = ctc.as(t, users[0]).chatLocalHandler().PostReactionNonblock(tc.startCtx, rarg) 7689 require.NoError(t, err) 7690 consumeNewPendingMsg(t, listener) 7691 consumeBotMessage(botua.Username, chat1.MessageType_REACTION, listener) 7692 consumeBotMessage(botua.Username, chat1.MessageType_REACTION, botuaListener) 7693 assertNoMessage(botuaListener2) 7694 consumeNewMsgLocal(t, listener, chat1.MessageType_REACTION) 7695 7696 t.Logf("edit the message") 7697 // An ephemeralLifetime is added if we are editing an ephemeral message 7698 targetMsgID := textUnboxed.GetMessageID() 7699 earg := chat1.PostEditNonblockArg{ 7700 ConversationID: created.Id, 7701 TlfName: created.TlfName, 7702 TlfPublic: created.Visibility == keybase1.TLFVisibility_PUBLIC, 7703 Target: chat1.EditTarget{ 7704 MessageID: &targetMsgID, 7705 }, 7706 Body: "hi2", 7707 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 7708 } 7709 _, err = ctc.as(t, users[0]).chatLocalHandler().PostEditNonblock(tc.startCtx, earg) 7710 require.NoError(t, err) 7711 consumeNewPendingMsg(t, listener) 7712 consumeBotMessage(botua.Username, chat1.MessageType_EDIT, listener) 7713 consumeBotMessage(botua.Username, chat1.MessageType_EDIT, botuaListener) 7714 assertNoMessage(botuaListener2) 7715 consumeNewMsgLocal(t, listener, chat1.MessageType_EDIT) 7716 7717 _, err = ctc.as(t, users[0]).chatLocalHandler().PostLocal(ctx, chat1.PostLocalArg{ 7718 ConversationID: created.Id, 7719 Msg: chat1.MessagePlaintext{ 7720 ClientHeader: chat1.MessageClientHeader{ 7721 Conv: created.Triple, 7722 MessageType: chat1.MessageType_TEXT, 7723 TlfName: created.TlfName, 7724 }, 7725 MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{ 7726 Body: "REPLY", 7727 }), 7728 }, 7729 ReplyTo: &targetMsgID, 7730 }) 7731 require.NoError(t, err) 7732 consumeBotMessage("", chat1.MessageType_TEXT, listener) 7733 assertNoMessage(botuaListener) 7734 assertNoMessage(botuaListener2) 7735 consumeNewMsgLocal(t, listener, chat1.MessageType_TEXT) 7736 7737 // Repost a reaction and ensure it is deleted 7738 t.Logf("repost reaction = delete reaction") 7739 _, err = ctc.as(t, users[0]).chatLocalHandler().PostReactionNonblock(tc.startCtx, rarg) 7740 require.NoError(t, err) 7741 consumeNewPendingMsg(t, listener) 7742 consumeBotMessage(botua.Username, chat1.MessageType_DELETE, listener) 7743 consumeBotMessage(botua.Username, chat1.MessageType_DELETE, botuaListener) 7744 assertNoMessage(botuaListener2) 7745 consumeNewMsgLocal(t, listener, chat1.MessageType_DELETE) 7746 7747 t.Logf("delete the message") 7748 darg := chat1.PostDeleteNonblockArg{ 7749 ConversationID: created.Id, 7750 TlfName: created.TlfName, 7751 TlfPublic: created.Visibility == keybase1.TLFVisibility_PUBLIC, 7752 Supersedes: textUnboxed.GetMessageID(), 7753 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 7754 } 7755 _, err = ctc.as(t, users[0]).chatLocalHandler().PostDeleteNonblock(tc.startCtx, darg) 7756 require.NoError(t, err) 7757 consumeNewPendingMsg(t, listener) 7758 consumeBotMessage(botua.Username, chat1.MessageType_DELETE, listener) 7759 consumeBotMessage(botua.Username, chat1.MessageType_DELETE, botuaListener) 7760 assertNoMessage(botuaListener2) 7761 consumeNewMsgLocal(t, listener, chat1.MessageType_DELETE) 7762 7763 t.Logf("post headline") 7764 headline := "SILENCE!" 7765 harg := chat1.PostHeadlineNonblockArg{ 7766 ConversationID: created.Id, 7767 TlfName: created.TlfName, 7768 TlfPublic: created.Visibility == keybase1.TLFVisibility_PUBLIC, 7769 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 7770 Headline: headline, 7771 } 7772 _, err = ctc.as(t, users[0]).chatLocalHandler().PostHeadlineNonblock(tc.startCtx, harg) 7773 require.NoError(t, err) 7774 consumeNewPendingMsg(t, listener) 7775 consumeBotMessage("", chat1.MessageType_HEADLINE, listener) 7776 assertNoMessage(botuaListener) 7777 assertNoMessage(botuaListener2) 7778 consumeNewMsgLocal(t, listener, chat1.MessageType_HEADLINE) 7779 7780 topicName := "zjoinonsend" 7781 _, err = ctc.as(t, botua).chatLocalHandler().NewConversationLocal(context.TODO(), 7782 chat1.NewConversationLocalArg{ 7783 TlfName: created.TlfName, 7784 TopicName: &topicName, 7785 TopicType: chat1.TopicType_DEV, 7786 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 7787 MembersType: mt, 7788 }) 7789 require.NoError(t, err) 7790 7791 // take out botua1 by restricting them to a nonexistent conv. 7792 botSettings.Convs = []string{chat1.ConversationID("foo").String()} 7793 err = ctc.as(t, users[0]).chatLocalHandler().SetBotMemberSettings(tc.startCtx, chat1.SetBotMemberSettingsArg{ 7794 ConvID: created.Id, 7795 Username: botua.Username, 7796 BotSettings: botSettings, 7797 }) 7798 require.NoError(t, err) 7799 7800 actualBotSettings, err := ctc.as(t, users[0]).chatLocalHandler().GetBotMemberSettings(tc.startCtx, chat1.GetBotMemberSettingsArg{ 7801 ConvID: created.Id, 7802 Username: botua.Username, 7803 }) 7804 require.NoError(t, err) 7805 require.Equal(t, botSettings, actualBotSettings) 7806 7807 pollForSeqno(6) 7808 7809 arg = chat1.PostTextNonblockArg{ 7810 ConversationID: created.Id, 7811 TlfName: created.TlfName, 7812 TlfPublic: created.Visibility == keybase1.TLFVisibility_PUBLIC, 7813 Body: "@" + botua2.Username, 7814 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, 7815 } 7816 _, err = ctc.as(t, users[0]).chatLocalHandler().PostTextNonblock(tc.startCtx, arg) 7817 require.NoError(t, err) 7818 consumeNewPendingMsg(t, listener) 7819 consumeBotMessage(botua2.Username, chat1.MessageType_TEXT, listener) 7820 consumeBotMessage(botua2.Username, chat1.MessageType_TEXT, botuaListener2) 7821 assertNoMessage(botuaListener) 7822 consumeNewMsgLocal(t, listener, chat1.MessageType_TEXT) 7823 7824 // send as a bot 7825 larg := chat1.PostLocalArg{ 7826 ConversationID: created.Id, 7827 Msg: chat1.MessagePlaintext{ 7828 ClientHeader: chat1.MessageClientHeader{ 7829 Conv: created.Triple, 7830 TlfName: created.TlfName, 7831 MessageType: chat1.MessageType_TEXT, 7832 }, 7833 MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{Body: "blah"}), 7834 }, 7835 } 7836 _, err = ctc.as(t, botua).chatLocalHandler().PostLocal(ctc.as(t, botua).startCtx, larg) 7837 require.Error(t, err) 7838 7839 _, err = ctc.as(t, botua2).chatLocalHandler().PostLocal(ctc.as(t, botua2).startCtx, larg) 7840 require.NoError(t, err) 7841 consumeBotMessage(botua2.Username, chat1.MessageType_TEXT, listener) 7842 consumeBotMessage(botua2.Username, chat1.MessageType_TEXT, botuaListener2) 7843 assertNoMessage(botuaListener) 7844 7845 // ensure gregor withholds messages for restricted bot members 7846 // unless it is specifically keyed for them. 7847 tv, err := ctc.world.Tcs[botua.Username].Context().ConvSource.Pull(ctc.as(t, botua).startCtx, 7848 created.Id, botuaUID, chat1.GetThreadReason_GENERAL, nil, nil, nil) 7849 require.NoError(t, err) 7850 // expected botua keyed messages 7851 // NOTE that for ephemeral messages we include deleted messages in the thread 7852 var expectedBotuaTyps []chat1.MessageType 7853 if ephemeralLifetime == nil { 7854 expectedBotuaTyps = []chat1.MessageType{ 7855 chat1.MessageType_DELETE, 7856 chat1.MessageType_DELETE, 7857 chat1.MessageType_TEXT, 7858 } 7859 } else { 7860 expectedBotuaTyps = []chat1.MessageType{ 7861 chat1.MessageType_DELETE, 7862 chat1.MessageType_DELETE, 7863 chat1.MessageType_EDIT, 7864 chat1.MessageType_REACTION, 7865 chat1.MessageType_TEXT, 7866 chat1.MessageType_TEXT, 7867 } 7868 } 7869 validIndex := 0 7870 for i, msg := range tv.Messages { 7871 t.Logf("INDEX: %d VALID: %v, %s", i, msg.IsValid(), msg.DebugString()) 7872 if msg.IsValid() { 7873 require.Equal(t, expectedBotuaTyps[validIndex], msg.GetMessageType()) 7874 validIndex++ 7875 } 7876 } 7877 require.Equal(t, len(expectedBotuaTyps), validIndex) 7878 7879 tv, err = ctc.world.Tcs[botua2.Username].Context().ConvSource.Pull(ctc.as(t, botua2).startCtx, 7880 created.Id, botuaUID2, chat1.GetThreadReason_GENERAL, nil, nil, nil) 7881 require.NoError(t, err) 7882 // expected botua2 keyed messages 7883 // NOTE that for ephemeral messages we include deleted messages in the thread 7884 expectedBotua2Typs := []chat1.MessageType{ 7885 chat1.MessageType_TEXT, 7886 chat1.MessageType_TEXT, 7887 } 7888 validIndex = 0 7889 require.Equal(t, 13, len(tv.Messages)) 7890 for _, msg := range tv.Messages { 7891 if msg.IsValid() { 7892 require.Equal(t, expectedBotua2Typs[validIndex], msg.GetMessageType()) 7893 validIndex++ 7894 } 7895 } 7896 require.Equal(t, 2, validIndex) 7897 7898 // take out botua2 by upgrading them to BOT 7899 err = ctc.as(t, users[0]).chatLocalHandler().EditBotMember(tc.startCtx, chat1.EditBotMemberArg{ 7900 ConvID: created.Id, 7901 Username: botua2.Username, 7902 Role: keybase1.TeamRole_BOT, 7903 }) 7904 require.NoError(t, err) 7905 pollForSeqno(7) 7906 7907 // messages is not keyed for any restricted bot 7908 _, err = ctc.as(t, users[0]).chatLocalHandler().PostTextNonblock(tc.startCtx, arg) 7909 require.NoError(t, err) 7910 consumeNewPendingMsg(t, listener) 7911 consumeBotMessage("", chat1.MessageType_TEXT, listener) 7912 consumeBotMessage("", chat1.MessageType_TEXT, botuaListener2) 7913 assertNoMessage(botuaListener) 7914 7915 // botua2 can send without issue 7916 _, err = ctc.as(t, botua2).chatLocalHandler().PostLocal(ctc.as(t, botua2).startCtx, larg) 7917 require.NoError(t, err) 7918 consumeBotMessage("", chat1.MessageType_TEXT, listener) 7919 consumeBotMessage("", chat1.MessageType_TEXT, botuaListener2) 7920 assertNoMessage(botuaListener) 7921 7922 // remove both bots. 7923 err = ctc.as(t, users[0]).chatLocalHandler().RemoveBotMember(tc.startCtx, chat1.RemoveBotMemberArg{ 7924 ConvID: created.Id, 7925 Username: botua.Username, 7926 }) 7927 require.NoError(t, err) 7928 err = ctc.as(t, users[0]).chatLocalHandler().RemoveBotMember(tc.startCtx, chat1.RemoveBotMemberArg{ 7929 ConvID: created.Id, 7930 Username: botua2.Username, 7931 }) 7932 require.NoError(t, err) 7933 team, err := teams.Load(ctx, tc.m.G(), keybase1.LoadTeamArg{ 7934 ID: teamID, 7935 }) 7936 require.NoError(t, err) 7937 isMember := team.IsMember(ctx, botua.GetUserVersion()) 7938 require.False(t, isMember) 7939 isMember = team.IsMember(ctx, botua2.GetUserVersion()) 7940 require.False(t, isMember) 7941 }) 7942 }) 7943 } 7944 7945 // Filter out journey cards. The test doesn't need to know about them. Mutates thread.Messages 7946 func filterOutJourneycards(thread *chat1.ThreadView) { 7947 filtered := thread.Messages[:0] 7948 for _, msg := range thread.Messages { 7949 if msg.Journeycard__ == nil { 7950 filtered = append(filtered, msg) 7951 } 7952 } 7953 thread.Messages = filtered 7954 } 7955 7956 func TestTeamBotChannelMembership(t *testing.T) { 7957 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 7958 // Only test for TEAM type 7959 if mt != chat1.ConversationMembersType_TEAM { 7960 return 7961 } 7962 ctc := makeChatTestContext(t, "TeamBotChannelMembership", 3) 7963 defer ctc.cleanup() 7964 users := ctc.users() 7965 7966 ui := kbtest.NewChatUI() 7967 ctc.as(t, users[0]).h.mockChatUI = ui 7968 tc := ctc.as(t, users[0]) 7969 listener := newServerChatListener() 7970 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener) 7971 ctc.world.Tcs[users[0].Username].ChatG.Syncer.(*Syncer).isConnected = true 7972 7973 botua := users[1] 7974 botua2 := users[2] 7975 7976 botuaListener := newServerChatListener() 7977 ctc.as(t, botua).h.G().NotifyRouter.AddListener(botuaListener) 7978 ctc.world.Tcs[botua.Username].ChatG.Syncer.(*Syncer).isConnected = true 7979 botuaListener2 := newServerChatListener() 7980 ctc.as(t, botua2).h.G().NotifyRouter.AddListener(botuaListener2) 7981 ctc.world.Tcs[botua2.Username].ChatG.Syncer.(*Syncer).isConnected = true 7982 7983 var err error 7984 created := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt) 7985 7986 teamID, err := keybase1.TeamIDFromString(created.Triple.Tlfid.String()) 7987 require.NoError(t, err) 7988 7989 topicName := "zjoinonsend" 7990 _, err = tc.chatLocalHandler().NewConversationLocal(context.TODO(), 7991 chat1.NewConversationLocalArg{ 7992 TlfName: created.TlfName, 7993 TopicName: &topicName, 7994 TopicType: chat1.TopicType_CHAT, 7995 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 7996 MembersType: mt, 7997 }) 7998 require.NoError(t, err) 7999 8000 pollForSeqno := func(expectedSeqno keybase1.Seqno) { 8001 found := false 8002 timeout := time.After(30 * time.Second) 8003 for !found { 8004 select { 8005 case teamChange := <-listener.teamChangedByID: 8006 found = teamChange.TeamID == teamID && 8007 teamChange.LatestSeqno == expectedSeqno 8008 if !found { 8009 t.Logf("Got teamChangedByID, but not one we are looking for: %s", spew.Sdump(teamChange)) 8010 } 8011 case <-timeout: 8012 require.Failf(t, "teamChangedByID not received", 8013 "team id: %s, waiting for seqno: %d", teamID, expectedSeqno) 8014 } 8015 } 8016 } 8017 8018 // add botua as a RESTRICTEDBOT with no conv restrictions, make sure 8019 // they are auto-added to the conv 8020 err = ctc.as(t, users[0]).chatLocalHandler().AddBotMember(tc.startCtx, chat1.AddBotMemberArg{ 8021 ConvID: created.Id, 8022 Username: botua.Username, 8023 Role: keybase1.TeamRole_RESTRICTEDBOT, 8024 BotSettings: &keybase1.TeamBotSettings{}, 8025 }) 8026 require.NoError(t, err) 8027 // AddBotMember should have posted two links: change_membership and bot_settings. Wait 8028 // for seqno=3. 8029 pollForSeqno(3) 8030 consumeMembersUpdate(t, listener) 8031 consumeMembersUpdate(t, listener) 8032 consumeJoinConv(t, botuaListener) 8033 consumeJoinConv(t, botuaListener) 8034 8035 // add botua2 as a BOT, make sure they auto-added to the convs 8036 err = ctc.as(t, users[0]).chatLocalHandler().AddBotMember(tc.startCtx, chat1.AddBotMemberArg{ 8037 ConvID: created.Id, 8038 Username: botua2.Username, 8039 Role: keybase1.TeamRole_BOT, 8040 }) 8041 require.NoError(t, err) 8042 pollForSeqno(4) 8043 consumeMembersUpdate(t, listener) 8044 consumeMembersUpdate(t, listener) 8045 consumeMembersUpdate(t, botuaListener) 8046 consumeMembersUpdate(t, botuaListener) 8047 consumeJoinConv(t, botuaListener2) 8048 consumeJoinConv(t, botuaListener2) 8049 8050 // downgrade botua to be restricted to 0 channels 8051 err = ctc.as(t, users[0]).chatLocalHandler().EditBotMember(tc.startCtx, chat1.EditBotMemberArg{ 8052 ConvID: created.Id, 8053 Username: botua.Username, 8054 Role: keybase1.TeamRole_RESTRICTEDBOT, 8055 BotSettings: &keybase1.TeamBotSettings{ 8056 Convs: []string{"deadbeef"}, 8057 }, 8058 }) 8059 require.NoError(t, err) 8060 // `EditBotMember` should have posted two links: change_membership and bot_settings. Wait 8061 // for seqno=6. 8062 pollForSeqno(6) 8063 consumeMembersUpdate(t, listener) 8064 consumeMembersUpdate(t, listener) 8065 consumeMembersUpdate(t, botuaListener2) 8066 consumeMembersUpdate(t, botuaListener2) 8067 consumeLeaveConv(t, botuaListener) 8068 consumeLeaveConv(t, botuaListener) 8069 8070 // create a new conv and makes sure bots are auto-added 8071 topicName = "zjoinonsend2" 8072 convres, err := tc.chatLocalHandler().NewConversationLocal(context.TODO(), 8073 chat1.NewConversationLocalArg{ 8074 TlfName: created.TlfName, 8075 TopicName: &topicName, 8076 TopicType: chat1.TopicType_CHAT, 8077 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 8078 MembersType: mt, 8079 }) 8080 require.NoError(t, err) 8081 8082 expectedUsers := []gregor1.UID{users[0].User.GetUID().ToBytes(), users[2].User.GetUID().ToBytes()} 8083 sort.Slice(expectedUsers, func(i, j int) bool { 8084 return bytes.Compare(expectedUsers[i], expectedUsers[j]) < 0 8085 }) 8086 for _, user := range users { 8087 g := ctc.world.Tcs[user.Username].Context() 8088 uids, err := g.ParticipantsSource.Get(context.TODO(), user.GetUID().ToBytes(), 8089 convres.Conv.GetConvID(), types.InboxSourceDataSourceAll) 8090 require.NoError(t, err) 8091 sort.Slice(uids, func(i, j int) bool { 8092 return bytes.Compare(expectedUsers[i], expectedUsers[j]) < 0 8093 }) 8094 require.Equal(t, expectedUsers, uids) 8095 } 8096 }) 8097 } 8098 8099 func TestChatSrvNewConversationsLocal(t *testing.T) { 8100 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 8101 switch mt { 8102 case chat1.ConversationMembersType_TEAM: 8103 default: 8104 return 8105 } 8106 8107 ctc := makeChatTestContext(t, "NewConversationsLocal", 3) 8108 defer ctc.cleanup() 8109 users := ctc.users() 8110 8111 key := teamKey(users) 8112 name := createTeamWithWriters(ctc.world.Tcs[users[0].Username].TestContext, users[1:]) 8113 ctc.teamCache[key] = name 8114 tlfName := strings.ToLower(name) 8115 8116 type L struct { 8117 tlfName string 8118 channelName *string 8119 ok bool 8120 } 8121 sp := func(x string) *string { return &x } 8122 argument := func(ll []L) (arg chat1.NewConversationsLocalArg) { 8123 arg.IdentifyBehavior = keybase1.TLFIdentifyBehavior_CHAT_CLI 8124 for _, l := range ll { 8125 arg.NewConversationLocalArguments = append(arg.NewConversationLocalArguments, chat1.NewConversationLocalArgument{ 8126 TlfName: name, 8127 TopicType: chat1.TopicType_CHAT, 8128 TopicName: l.channelName, 8129 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 8130 MembersType: chat1.ConversationMembersType_TEAM, 8131 }) 8132 } 8133 return arg 8134 } 8135 8136 t.Logf("creating many channels, some with bad names") 8137 ll := []L{ 8138 {tlfName: tlfName, channelName: nil, ok: true}, 8139 {tlfName: tlfName, channelName: sp("now"), ok: true}, 8140 {tlfName: tlfName, channelName: sp("i"), ok: true}, 8141 {tlfName: tlfName, channelName: sp("am"), ok: true}, 8142 {tlfName: tlfName, channelName: sp("#become"), ok: false}, 8143 {tlfName: tlfName, channelName: sp("death"), ok: true}, 8144 {tlfName: tlfName, channelName: sp("destroyer.of"), ok: false}, 8145 {tlfName: tlfName, channelName: sp("worlds/"), ok: false}, 8146 {tlfName: tlfName, channelName: sp("!"), ok: false}, 8147 } 8148 tc := ctc.as(t, users[0]) 8149 res, err := tc.chatLocalHandler().NewConversationsLocal(tc.startCtx, argument(ll)) 8150 require.Error(t, err) 8151 require.Equal(t, len(ll), len(res.Results)) 8152 for idx, l := range ll { 8153 result := res.Results[idx] 8154 if l.ok { 8155 require.NotNil(t, result.Result) 8156 require.Equal(t, strings.ToLower(l.tlfName), result.Result.Conv.Info.TlfName) 8157 if n := l.channelName; n != nil { 8158 require.Equal(t, *n, result.Result.Conv.Info.TopicName) 8159 } 8160 } else { 8161 require.NotNil(t, result.Err) 8162 require.Nil(t, result.Result) 8163 } 8164 } 8165 8166 t.Logf("testing idempotency") 8167 ll[4].channelName = sp("become") 8168 ll[6].channelName = sp("destroyerof") 8169 ll[7].channelName = sp("worlds") 8170 ll[8].channelName = sp("exclam") 8171 res, err = tc.chatLocalHandler().NewConversationsLocal(tc.startCtx, argument(ll)) 8172 require.NoError(t, err) 8173 require.Equal(t, len(ll), len(res.Results)) 8174 }) 8175 } 8176 8177 func TestChatSrvDefaultTeamChannels(t *testing.T) { 8178 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 8179 switch mt { 8180 case chat1.ConversationMembersType_TEAM: 8181 default: 8182 return 8183 } 8184 8185 ctc := makeChatTestContext(t, "DefaultTeamChannels", 2) 8186 defer ctc.cleanup() 8187 users := ctc.users() 8188 8189 created := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt) 8190 listener := newServerChatListener() 8191 ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener) 8192 ctc.world.Tcs[users[0].Username].ChatG.Syncer.(*Syncer).isConnected = true 8193 teamID, err := keybase1.TeamIDFromString(created.Triple.Tlfid.String()) 8194 require.NoError(t, err) 8195 8196 tc := ctc.as(t, users[0]) 8197 _ = ctc.as(t, users[1]) 8198 topicName := "zjoinonsend" 8199 convres, err := tc.chatLocalHandler().NewConversationLocal(context.TODO(), 8200 chat1.NewConversationLocalArg{ 8201 TlfName: created.TlfName, 8202 TopicName: &topicName, 8203 TopicType: chat1.TopicType_CHAT, 8204 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 8205 MembersType: mt, 8206 }) 8207 require.NoError(t, err) 8208 8209 res, err := tc.chatLocalHandler().GetDefaultTeamChannelsLocal(context.TODO(), teamID) 8210 require.NoError(t, err) 8211 require.Zero(t, len(res.Convs)) 8212 8213 _, err = tc.chatLocalHandler().SetDefaultTeamChannelsLocal(context.TODO(), chat1.SetDefaultTeamChannelsLocalArg{ 8214 TeamID: teamID, 8215 Convs: []chat1.ConvIDStr{convres.Conv.GetConvID().ConvIDStr()}, 8216 }) 8217 require.NoError(t, err) 8218 8219 res, err = tc.chatLocalHandler().GetDefaultTeamChannelsLocal(context.TODO(), teamID) 8220 require.NoError(t, err) 8221 require.Len(t, res.Convs, 1) 8222 require.Equal(t, topicName, res.Convs[0].Channel) 8223 8224 pollForSeqno := func(expectedSeqno keybase1.Seqno) { 8225 found := false 8226 for !found { 8227 select { 8228 case teamChange := <-listener.teamChangedByID: 8229 found = teamChange.TeamID == teamID && 8230 teamChange.LatestSeqno == expectedSeqno 8231 case <-time.After(20 * time.Second): 8232 require.Fail(t, "no event received") 8233 } 8234 } 8235 } 8236 err = ctc.as(t, users[0]).chatLocalHandler().AddBotMember(tc.startCtx, chat1.AddBotMemberArg{ 8237 ConvID: created.Id, 8238 Username: users[1].Username, 8239 Role: keybase1.TeamRole_BOT, 8240 }) 8241 require.NoError(t, err) 8242 pollForSeqno(2) 8243 8244 expectedUsers := []gregor1.UID{users[0].User.GetUID().ToBytes(), users[1].User.GetUID().ToBytes()} 8245 sort.Slice(expectedUsers, func(i, j int) bool { 8246 return bytes.Compare(expectedUsers[i], expectedUsers[j]) < 0 8247 }) 8248 for _, user := range users { 8249 g := ctc.world.Tcs[user.Username].Context() 8250 uids, err := g.ParticipantsSource.Get(context.TODO(), user.GetUID().ToBytes(), 8251 convres.Conv.GetConvID(), types.InboxSourceDataSourceAll) 8252 require.NoError(t, err) 8253 sort.Slice(uids, func(i, j int) bool { 8254 return bytes.Compare(expectedUsers[i], expectedUsers[j]) < 0 8255 }) 8256 require.Equal(t, expectedUsers, uids) 8257 } 8258 }) 8259 } 8260 8261 func TestChatSrvTeamActivity(t *testing.T) { 8262 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 8263 switch mt { 8264 case chat1.ConversationMembersType_TEAM: 8265 default: 8266 return 8267 } 8268 8269 ctc := makeChatTestContext(t, "TeamActivity", 2) 8270 defer ctc.cleanup() 8271 users := ctc.users() 8272 8273 created := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt) 8274 tlfID1 := created.Triple.Tlfid.TLFIDStr() 8275 created2 := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt, users[1]) 8276 tlfID2 := created2.Triple.Tlfid.TLFIDStr() 8277 8278 tc := ctc.as(t, users[0]) 8279 topicName := "zjoinonsend" 8280 nc1, err := tc.chatLocalHandler().NewConversationLocal(context.TODO(), 8281 chat1.NewConversationLocalArg{ 8282 TlfName: created.TlfName, 8283 TopicName: &topicName, 8284 TopicType: chat1.TopicType_CHAT, 8285 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 8286 MembersType: mt, 8287 }) 8288 require.NoError(t, err) 8289 nc2, err := tc.chatLocalHandler().NewConversationLocal(context.TODO(), 8290 chat1.NewConversationLocalArg{ 8291 TlfName: created2.TlfName, 8292 TopicName: &topicName, 8293 TopicType: chat1.TopicType_CHAT, 8294 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 8295 MembersType: mt, 8296 }) 8297 require.NoError(t, err) 8298 8299 lastActive, err := tc.chatLocalHandler().GetLastActiveForTLF(context.TODO(), tlfID1) 8300 require.NoError(t, err) 8301 require.Equal(t, chat1.LastActiveStatus_ACTIVE, lastActive) 8302 8303 res, err := tc.chatLocalHandler().GetLastActiveForTeams(context.TODO()) 8304 require.NoError(t, err) 8305 require.Equal(t, 2, len(res.Teams)) 8306 require.Equal(t, chat1.LastActiveStatus_ACTIVE, res.Teams[tlfID1]) 8307 require.Equal(t, chat1.LastActiveStatus_ACTIVE, res.Teams[tlfID2]) 8308 require.Equal(t, 4, len(res.Channels)) 8309 require.Equal(t, chat1.LastActiveStatus_ACTIVE, res.Channels[nc1.UiConv.ConvID]) 8310 require.Equal(t, chat1.LastActiveStatus_ACTIVE, res.Channels[nc2.UiConv.ConvID]) 8311 }) 8312 } 8313 8314 func TestChatSrvGetRecentJoins(t *testing.T) { 8315 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 8316 switch mt { 8317 case chat1.ConversationMembersType_TEAM: 8318 default: 8319 return 8320 } 8321 8322 ctc := makeChatTestContext(t, "TestChatSrvGetRecentJoins", 2) 8323 defer ctc.cleanup() 8324 users := ctc.users() 8325 tc := ctc.as(t, users[0]) 8326 created := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt, users[1]) 8327 numJoins, err := tc.chatLocalHandler().GetRecentJoinsLocal(context.TODO(), created.Id) 8328 require.NoError(t, err) 8329 require.Equal(t, 2, numJoins) 8330 }) 8331 } 8332 8333 func TestChatSrvGetLastActiveAt(t *testing.T) { 8334 runWithMemberTypes(t, func(mt chat1.ConversationMembersType) { 8335 switch mt { 8336 case chat1.ConversationMembersType_TEAM: 8337 default: 8338 return 8339 } 8340 8341 ctc := makeChatTestContext(t, "TestChatSrvGetLastActiveAt", 2) 8342 defer ctc.cleanup() 8343 users := ctc.users() 8344 8345 username1 := users[0].User.GetName() 8346 username2 := users[1].User.GetName() 8347 tc := ctc.as(t, users[0]) 8348 created := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, mt, users[1]) 8349 mustPostLocalForTest(t, ctc, users[0], created, chat1.NewMessageBodyWithText(chat1.MessageText{ 8350 Body: "HI", 8351 })) 8352 8353 teamID := keybase1.TeamID(created.Triple.Tlfid.String()) 8354 t.Logf("teamID: %v, username1: %v, username2: %v", teamID, username1, username2) 8355 lastActiveAt, err := tc.chatLocalHandler().GetLastActiveAtLocal(context.TODO(), chat1.GetLastActiveAtLocalArg{ 8356 TeamID: teamID, 8357 Username: username1, 8358 }) 8359 require.NoError(t, err) 8360 require.NotZero(t, lastActiveAt) 8361 8362 lastActiveAt, err = tc.chatLocalHandler().GetLastActiveAtLocal(context.TODO(), chat1.GetLastActiveAtLocalArg{ 8363 TeamID: teamID, 8364 Username: username2, 8365 }) 8366 require.NoError(t, err) 8367 require.Zero(t, lastActiveAt) 8368 8369 mustPostLocalForTest(t, ctc, users[1], created, chat1.NewMessageBodyWithText(chat1.MessageText{ 8370 Body: "HI", 8371 })) 8372 lastActiveAt, err = tc.chatLocalHandler().GetLastActiveAtLocal(context.TODO(), chat1.GetLastActiveAtLocalArg{ 8373 TeamID: teamID, 8374 Username: username2, 8375 }) 8376 require.NoError(t, err) 8377 require.Zero(t, lastActiveAt) 8378 8379 err = tc.h.G().TeamChannelSource.OnDbNuke(tc.m) 8380 require.NoError(t, err) 8381 lastActiveAt, err = tc.chatLocalHandler().GetLastActiveAtLocal(context.TODO(), chat1.GetLastActiveAtLocalArg{ 8382 TeamID: teamID, 8383 Username: username2, 8384 }) 8385 require.NoError(t, err) 8386 require.NotZero(t, lastActiveAt) 8387 }) 8388 }