1package storage 2 3import ( 4 "context" 5 "sort" 6 "testing" 7 "time" 8 9 "encoding/hex" 10 11 "github.com/keybase/client/go/chat/types" 12 "github.com/keybase/client/go/chat/utils" 13 "github.com/keybase/client/go/kbtest" 14 "github.com/keybase/client/go/libkb" 15 "github.com/keybase/client/go/protocol/chat1" 16 "github.com/keybase/client/go/protocol/gregor1" 17 "github.com/keybase/client/go/protocol/keybase1" 18 "github.com/stretchr/testify/require" 19) 20 21func setupInboxTest(t testing.TB, name string) (kbtest.ChatTestContext, *Inbox, gregor1.UID) { 22 ctc := setupCommonTest(t, name) 23 24 u, err := kbtest.CreateAndSignupFakeUser("ib", ctc.TestContext.G) 25 require.NoError(t, err) 26 uid := gregor1.UID(u.User.GetUID().ToBytes()) 27 return ctc, NewInbox(ctc.Context()), uid 28} 29 30func makeTlfID() chat1.TLFID { 31 return randBytes(8) 32} 33 34func makeConvo(mtime gregor1.Time, rmsg chat1.MessageID, mmsg chat1.MessageID) types.RemoteConversation { 35 conv := chat1.Conversation{ 36 Metadata: chat1.ConversationMetadata{ 37 ConversationID: randBytes(8), 38 IdTriple: chat1.ConversationIDTriple{ 39 Tlfid: makeTlfID(), 40 TopicType: chat1.TopicType_CHAT, 41 TopicID: randBytes(8), 42 }, 43 Visibility: keybase1.TLFVisibility_PRIVATE, 44 Status: chat1.ConversationStatus_UNFILED, 45 }, 46 ReaderInfo: &chat1.ConversationReaderInfo{ 47 Mtime: mtime, 48 ReadMsgid: rmsg, 49 MaxMsgid: mmsg, 50 }, 51 // Make it look like there's a visible message in here too 52 MaxMsgSummaries: []chat1.MessageSummary{{MessageType: chat1.MessageType_TEXT, MsgID: 1}}, 53 } 54 return utils.RemoteConv(conv) 55} 56 57func makeInboxMsg(id chat1.MessageID, typ chat1.MessageType) chat1.MessageBoxed { 58 return chat1.MessageBoxed{ 59 ClientHeader: chat1.MessageClientHeader{ 60 MessageType: typ, 61 }, 62 ServerHeader: &chat1.MessageServerHeader{ 63 MessageID: id, 64 Ctime: gregor1.ToTime(time.Now()), 65 }, 66 } 67} 68 69func convListCompare(t *testing.T, ref []types.RemoteConversation, res []types.RemoteConversation, 70 name string) { 71 require.Equal(t, len(ref), len(res), name+" size mismatch") 72 refMap := make(map[chat1.ConvIDStr]types.RemoteConversation) 73 for _, conv := range ref { 74 refMap[conv.GetConvID().ConvIDStr()] = conv 75 } 76 for _, conv := range res { 77 require.Equal(t, refMap[conv.GetConvID().ConvIDStr()], conv) 78 } 79} 80 81func TestInboxBasic(t *testing.T) { 82 83 tc, inbox, uid := setupInboxTest(t, "basic") 84 defer tc.Cleanup() 85 86 // Create an inbox with a bunch of convos, merge it and read it back out 87 numConvs := 10 88 var convs []types.RemoteConversation 89 for i := numConvs - 1; i >= 0; i-- { 90 convs = append(convs, makeConvo(gregor1.Time(i), 1, 1)) 91 } 92 93 // Fetch with no query parameter 94 require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil)) 95 vers, res, err := inbox.Read(context.TODO(), uid, nil) 96 97 require.NoError(t, err) 98 require.Equal(t, chat1.InboxVers(1), vers, "version mismatch") 99 convListCompare(t, convs, res, "basic") 100} 101 102func TestInboxSummarize(t *testing.T) { 103 tc, inbox, uid := setupInboxTest(t, "summarize") 104 defer tc.Cleanup() 105 106 conv := makeConvo(gregor1.Time(1), 1, 1) 107 maxMsgID := chat1.MessageID(6) 108 conv.Conv.MaxMsgs = []chat1.MessageBoxed{{ 109 ClientHeader: chat1.MessageClientHeader{ 110 MessageType: chat1.MessageType_TEXT, 111 }, 112 ServerHeader: &chat1.MessageServerHeader{ 113 MessageID: maxMsgID, 114 }, 115 }} 116 117 require.NoError(t, inbox.Merge(context.TODO(), uid, 1, []chat1.Conversation{conv.Conv}, nil)) 118 _, res, err := inbox.Read(context.TODO(), uid, nil) 119 require.NoError(t, err) 120 require.Zero(t, len(res[0].Conv.MaxMsgs)) 121 require.Equal(t, 1, len(res[0].Conv.MaxMsgSummaries)) 122 require.Equal(t, maxMsgID, res[0].Conv.MaxMsgSummaries[0].GetMessageID()) 123} 124 125func TestInboxQueries(t *testing.T) { 126 tc, inbox, uid := setupInboxTest(t, "queries") 127 defer tc.Cleanup() 128 129 // Create an inbox with a bunch of convos, merge it and read it back out 130 numConvs := 20 131 var convs []types.RemoteConversation 132 for i := 0; i < numConvs; i++ { 133 conv := makeConvo(gregor1.Time(i), 1, 1) 134 convs = append(convs, conv) 135 } 136 137 // Make two dev convos 138 var devs, publics, unreads, ignored, muted, full []types.RemoteConversation 139 convs[3].Conv.Metadata.IdTriple.TopicType = chat1.TopicType_DEV 140 convs[7].Conv.Metadata.IdTriple.TopicType = chat1.TopicType_DEV 141 devs = append(devs, []types.RemoteConversation{convs[7], convs[3]}...) 142 143 // Make one public convos 144 convs[13].Conv.Metadata.Visibility = keybase1.TLFVisibility_PUBLIC 145 publics = append(publics, convs[13]) 146 147 // Make three unread convos 148 makeUnread := func(ri *chat1.ConversationReaderInfo) { 149 ri.ReadMsgid = 0 150 } 151 makeUnread(convs[5].Conv.ReaderInfo) 152 makeUnread(convs[13].Conv.ReaderInfo) 153 makeUnread(convs[19].Conv.ReaderInfo) 154 unreads = append(unreads, []types.RemoteConversation{convs[19], convs[13], convs[5]}...) 155 156 // Make two ignored 157 convs[18].Conv.Metadata.Status = chat1.ConversationStatus_IGNORED 158 convs[4].Conv.Metadata.Status = chat1.ConversationStatus_IGNORED 159 ignored = append(ignored, []types.RemoteConversation{convs[18], convs[4]}...) 160 161 // Make one muted 162 convs[12].Conv.Metadata.Status = chat1.ConversationStatus_MUTED 163 muted = append(muted, []types.RemoteConversation{convs[12]}...) 164 165 // Mark one as finalized and superseded by 166 convs[6].Conv.Metadata.FinalizeInfo = &chat1.ConversationFinalizeInfo{ 167 ResetFull: "reset", 168 } 169 convs[6].Conv.Metadata.SupersededBy = append(convs[6].Conv.Metadata.SupersededBy, convs[17].Conv.Metadata) 170 convs[17].Conv.Metadata.Supersedes = append(convs[17].Conv.Metadata.Supersedes, convs[6].Conv.Metadata) 171 for i := len(convs) - 1; i >= 0; i-- { 172 if i == 6 { 173 continue 174 } 175 full = append(full, convs[i]) 176 } 177 for _, conv := range full { 178 t.Logf("convID: %s", conv.GetConvID()) 179 } 180 181 require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil)) 182 183 // Merge in queries and try to read them back out 184 var q *chat1.GetInboxQuery 185 mergeReadAndCheck := func(t *testing.T, ref []types.RemoteConversation, name string) { 186 require.NoError(t, inbox.Merge(context.TODO(), uid, 1, []chat1.Conversation{}, q)) 187 _, res, err := inbox.Read(context.TODO(), uid, q) 188 require.NoError(t, err) 189 convListCompare(t, ref, res, name) 190 } 191 t.Logf("merging all convs with nil query") 192 q = nil 193 mergeReadAndCheck(t, full, "all") 194 195 t.Logf("merging dev query") 196 devtype := chat1.TopicType_DEV 197 q = &chat1.GetInboxQuery{TopicType: &devtype} 198 mergeReadAndCheck(t, devs, "devs") 199 200 t.Logf("merging public query") 201 publicVis := keybase1.TLFVisibility_PUBLIC 202 q = &chat1.GetInboxQuery{TlfVisibility: &publicVis} 203 mergeReadAndCheck(t, publics, "public") 204 205 t.Logf("merging unread query") 206 q = &chat1.GetInboxQuery{UnreadOnly: true} 207 mergeReadAndCheck(t, unreads, "unread") 208 209 t.Logf("merging ignore query") 210 q = &chat1.GetInboxQuery{Status: []chat1.ConversationStatus{chat1.ConversationStatus_IGNORED}} 211 mergeReadAndCheck(t, ignored, "ignored") 212 213 t.Logf("merging muted query") 214 q = &chat1.GetInboxQuery{Status: []chat1.ConversationStatus{chat1.ConversationStatus_MUTED}} 215 mergeReadAndCheck(t, muted, "muted") 216 217 t.Logf("merging tlf ID query") 218 q = &chat1.GetInboxQuery{TlfID: &full[0].Conv.Metadata.IdTriple.Tlfid} 219 tlfIDs := []types.RemoteConversation{full[0]} 220 mergeReadAndCheck(t, tlfIDs, "tlfids") 221 222 t.Logf("merging after query") 223 after := full[:4] 224 atime := gregor1.Time(15) 225 q = &chat1.GetInboxQuery{After: &atime} 226 mergeReadAndCheck(t, after, "after") 227 228 t.Logf("merging before query") 229 before := full[5:] 230 var beforeConvIDs []chat1.ConversationID 231 for _, bconv := range before { 232 beforeConvIDs = append(beforeConvIDs, bconv.GetConvID()) 233 } 234 btime := gregor1.Time(15) 235 q = &chat1.GetInboxQuery{Before: &btime} 236 mergeReadAndCheck(t, before, "before") 237 238 t.Logf("check conv IDs queries work") 239 q = &chat1.GetInboxQuery{Before: &btime, ConvIDs: beforeConvIDs} 240 _, cres, err := inbox.Read(context.TODO(), uid, q) 241 require.NoError(t, err) 242 convListCompare(t, before, cres, "convIDs") 243} 244 245func TestInboxEmptySuperseder(t *testing.T) { 246 tc, inbox, uid := setupInboxTest(t, "queries") 247 defer tc.Cleanup() 248 249 // Create an inbox with a bunch of convos, merge it and read it back out 250 numConvs := 20 251 var convs []types.RemoteConversation 252 for i := 0; i < numConvs; i++ { 253 conv := makeConvo(gregor1.Time(i), 1, 1) 254 conv.Conv.MaxMsgSummaries = nil 255 convs = append(convs, conv) 256 } 257 var full, superseded []types.RemoteConversation 258 convs[6].Conv.Metadata.SupersededBy = append(convs[6].Conv.Metadata.SupersededBy, convs[17].Conv.Metadata) 259 convs[17].Conv.Metadata.Supersedes = append(convs[17].Conv.Metadata.Supersedes, convs[6].Conv.Metadata) 260 for i := len(convs) - 1; i >= 0; i-- { 261 // Don't skip the superseded one, since it's not supposed to be filtered out 262 // by an empty superseder 263 full = append(full, convs[i]) 264 } 265 for _, conv := range full { 266 t.Logf("convID: %s", conv.GetConvID()) 267 } 268 269 require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil)) 270 271 // Merge in queries and try to read them back out 272 var q *chat1.GetInboxQuery 273 mergeReadAndCheck := func(t *testing.T, ref []types.RemoteConversation, name string) { 274 require.NoError(t, inbox.Merge(context.TODO(), uid, 1, []chat1.Conversation{}, q)) 275 _, res, err := inbox.Read(context.TODO(), uid, q) 276 require.NoError(t, err) 277 convListCompare(t, ref, res, name) 278 } 279 t.Logf("merging all convs with nil query") 280 q = nil 281 mergeReadAndCheck(t, full, "all") 282 283 t.Logf("merging empty superseder query") 284 // Don't skip the superseded one, since it's not supposed to be filtered out 285 // by an empty superseder 286 superseded = append(superseded, full...) 287 q = &chat1.GetInboxQuery{} 288 // OneChatTypePerTLF 289 t.Logf("full has %d, superseded has %d", len(full), len(superseded)) 290 291 mergeReadAndCheck(t, superseded, "superseded") 292 293 // Now test OneChatTypePerTLF 294 tc, inbox, uid = setupInboxTest(t, "queries2") 295 defer tc.Cleanup() 296 297 full = []types.RemoteConversation{} 298 superseded = []types.RemoteConversation{} 299 for i := len(convs) - 1; i >= 0; i-- { 300 // skip the superseded one, since it's supposed to be filtered out 301 // if not OneChatTypePerTLF 302 if i == 6 { 303 continue 304 } 305 full = append(full, convs[i]) 306 } 307 require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(full), nil)) 308 superseded = append(superseded, full...) 309 oneChatTypePerTLF := false 310 q = &chat1.GetInboxQuery{OneChatTypePerTLF: &oneChatTypePerTLF} 311 mergeReadAndCheck(t, superseded, "superseded") 312 313} 314 315func TestInboxNewConversation(t *testing.T) { 316 tc, inbox, uid := setupInboxTest(t, "basic") 317 defer tc.Cleanup() 318 319 // Create an inbox with a bunch of convos, merge it and read it back out 320 numConvs := 10 321 var convs []types.RemoteConversation 322 for i := numConvs - 1; i >= 0; i-- { 323 convs = append(convs, makeConvo(gregor1.Time(i), 1, 1)) 324 } 325 convs[5].Conv.Metadata.FinalizeInfo = &chat1.ConversationFinalizeInfo{ 326 ResetFull: "reset", 327 } 328 329 t.Logf("basic newconv") 330 require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil)) 331 newConv := makeConvo(gregor1.Time(11), 1, 1) 332 require.NoError(t, inbox.NewConversation(context.TODO(), uid, 2, newConv.Conv)) 333 _, res, err := inbox.Read(context.TODO(), uid, nil) 334 require.NoError(t, err) 335 convs = append([]types.RemoteConversation{newConv}, convs...) 336 convListCompare(t, convs, res, "newconv") 337 338 t.Logf("repeat conv") 339 require.NoError(t, inbox.NewConversation(context.TODO(), uid, 3, newConv.Conv)) 340 _, res, err = inbox.Read(context.TODO(), uid, nil) 341 require.NoError(t, err) 342 convListCompare(t, convs, res, "repeatconv") 343 344 t.Logf("supersede newconv") 345 newConv = makeConvo(gregor1.Time(12), 1, 1) 346 newConv.Conv.Metadata.Supersedes = append(newConv.Conv.Metadata.Supersedes, convs[6].Conv.Metadata) 347 require.NoError(t, inbox.NewConversation(context.TODO(), uid, 4, newConv.Conv)) 348 _, res, err = inbox.Read(context.TODO(), uid, nil) 349 require.NoError(t, err) 350 convs = append([]types.RemoteConversation{newConv}, convs...) 351 convListCompare(t, append(convs[:7], convs[8:]...), res, "newconv finalized") 352 353 require.Equal(t, numConvs+2, len(convs), "n convs") 354 355 err = inbox.NewConversation(context.TODO(), uid, 10, newConv.Conv) 356 require.IsType(t, VersionMismatchError{}, err) 357} 358 359func TestInboxNewMessage(t *testing.T) { 360 361 tc, inbox, uid := setupInboxTest(t, "basic") 362 defer tc.Cleanup() 363 364 // Create an inbox with a bunch of convos, merge it and read it back out 365 numConvs := 10 366 var convs []types.RemoteConversation 367 for i := numConvs - 1; i >= 0; i-- { 368 convs = append(convs, makeConvo(gregor1.Time(i), 1, 1)) 369 } 370 371 uid1 := uid 372 uid2, err := hex.DecodeString("22") 373 require.NoError(t, err) 374 uid3, err := hex.DecodeString("33") 375 require.NoError(t, err) 376 377 convs[5].Conv.Metadata.ActiveList = []gregor1.UID{uid2, uid3, uid1} 378 convs[6].Conv.Metadata.ActiveList = []gregor1.UID{uid2, uid3} 379 conv := convs[5] 380 msg := makeInboxMsg(2, chat1.MessageType_TEXT) 381 msg.ClientHeader.Sender = uid1 382 require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil)) 383 convID := conv.GetConvID() 384 require.NoError(t, inbox.NewMessage(context.TODO(), uid, 2, conv.GetConvID(), msg, nil)) 385 _, res, err := inbox.Read(context.TODO(), uid, &chat1.GetInboxQuery{ 386 ConvID: &convID, 387 }) 388 require.NoError(t, err) 389 require.Equal(t, conv.GetConvID(), res[0].GetConvID(), "conv not promoted") 390 require.Equal(t, chat1.MessageID(2), res[0].Conv.ReaderInfo.MaxMsgid, "wrong max msgid") 391 require.Equal(t, chat1.MessageID(2), res[0].Conv.ReaderInfo.ReadMsgid, "wrong read msgid") 392 require.Equal(t, msg.Ctime(), res[0].Conv.ReaderInfo.LastSendTime) 393 require.Equal(t, []gregor1.UID{uid1, uid2, uid3}, res[0].Conv.Metadata.ActiveList, "active list") 394 maxMsg, err := res[0].Conv.GetMaxMessage(chat1.MessageType_TEXT) 395 require.NoError(t, err) 396 require.Equal(t, chat1.MessageID(2), maxMsg.GetMessageID(), "max msg not updated") 397 398 // Test incomplete active list 399 conv = convs[6] 400 convID = conv.GetConvID() 401 require.NoError(t, inbox.NewMessage(context.TODO(), uid, 3, conv.GetConvID(), msg, nil)) 402 _, res, err = inbox.Read(context.TODO(), uid, &chat1.GetInboxQuery{ 403 ConvID: &convID, 404 }) 405 require.NoError(t, err) 406 require.Equal(t, []gregor1.UID{uid1, uid2, uid3}, res[0].Conv.Metadata.ActiveList, "active list") 407 408 // Send another one from a diff User 409 msg2 := makeInboxMsg(3, chat1.MessageType_TEXT) 410 msg2.ClientHeader.Sender = uid2 411 convID = conv.GetConvID() 412 require.NoError(t, inbox.NewMessage(context.TODO(), uid, 4, conv.GetConvID(), msg2, nil)) 413 _, res, err = inbox.Read(context.TODO(), uid, &chat1.GetInboxQuery{ 414 ConvID: &convID, 415 }) 416 require.NoError(t, err) 417 require.Equal(t, chat1.MessageID(3), res[0].Conv.ReaderInfo.MaxMsgid, "wrong max msgid") 418 require.Equal(t, chat1.MessageID(2), res[0].Conv.ReaderInfo.ReadMsgid, "wrong read msgid") 419 require.Equal(t, msg.Ctime(), res[0].Conv.ReaderInfo.LastSendTime) 420 require.Equal(t, []gregor1.UID{uid2, uid1, uid3}, res[0].Conv.Metadata.ActiveList, "active list") 421 maxMsg, err = res[0].Conv.GetMaxMessage(chat1.MessageType_TEXT) 422 require.NoError(t, err) 423 require.Equal(t, chat1.MessageID(3), maxMsg.GetMessageID()) 424 425 // Test delete mechanics 426 delMsg := makeInboxMsg(4, chat1.MessageType_TEXT) 427 require.NoError(t, inbox.NewMessage(context.TODO(), uid, 5, conv.GetConvID(), delMsg, nil)) 428 _, res, err = inbox.Read(context.TODO(), uid, &chat1.GetInboxQuery{ 429 ConvID: &convID, 430 }) 431 require.NoError(t, err) 432 maxMsg, err = res[0].Conv.GetMaxMessage(chat1.MessageType_TEXT) 433 require.NoError(t, err) 434 require.Equal(t, delMsg.GetMessageID(), maxMsg.GetMessageID()) 435 delete := makeInboxMsg(5, chat1.MessageType_DELETE) 436 require.NoError(t, inbox.NewMessage(context.TODO(), uid, 0, conv.GetConvID(), delete, nil)) 437 require.NoError(t, inbox.NewMessage(context.TODO(), uid, 6, conv.GetConvID(), delete, 438 []chat1.MessageSummary{msg2.Summary()})) 439 _, res, err = inbox.Read(context.TODO(), uid, &chat1.GetInboxQuery{ 440 ConvID: &convID, 441 }) 442 require.NoError(t, err) 443 maxMsg, err = res[0].Conv.GetMaxMessage(chat1.MessageType_TEXT) 444 require.NoError(t, err) 445 require.Equal(t, msg2.GetMessageID(), maxMsg.GetMessageID()) 446 delete = makeInboxMsg(6, chat1.MessageType_DELETE) 447 err = inbox.NewMessage(context.TODO(), uid, 7, conv.GetConvID(), delete, nil) 448 require.Error(t, err) 449 require.IsType(t, VersionMismatchError{}, err) 450 451 err = inbox.NewMessage(context.TODO(), uid, 10, conv.GetConvID(), msg2, nil) 452 require.IsType(t, VersionMismatchError{}, err) 453} 454 455func TestInboxReadMessage(t *testing.T) { 456 457 tc, inbox, uid := setupInboxTest(t, "basic") 458 defer tc.Cleanup() 459 460 uid2, err := hex.DecodeString("22") 461 require.NoError(t, err) 462 463 // Create an inbox with a bunch of convos, merge it and read it back out 464 numConvs := 10 465 var convs []types.RemoteConversation 466 for i := numConvs - 1; i >= 0; i-- { 467 convs = append(convs, makeConvo(gregor1.Time(i), 1, 1)) 468 } 469 470 require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil)) 471 _, _, err = inbox.Read(context.TODO(), uid, nil) 472 require.NoError(t, err) 473 474 conv := convs[5] 475 convID := conv.GetConvID() 476 msg := makeInboxMsg(2, chat1.MessageType_TEXT) 477 msg.ClientHeader.Sender = uid2 478 require.NoError(t, inbox.NewMessage(context.TODO(), uid, 2, conv.GetConvID(), msg, nil)) 479 _, res, err := inbox.Read(context.TODO(), uid, &chat1.GetInboxQuery{ 480 ConvID: &convID, 481 }) 482 require.NoError(t, err) 483 require.Equal(t, chat1.MessageID(2), res[0].Conv.ReaderInfo.MaxMsgid, "wrong max msgid") 484 require.Equal(t, chat1.MessageID(1), res[0].Conv.ReaderInfo.ReadMsgid, "wrong read msgid") 485 require.Equal(t, gregor1.Time(0), res[0].Conv.ReaderInfo.LastSendTime) 486 require.NoError(t, inbox.ReadMessage(context.TODO(), uid, 3, conv.GetConvID(), 2)) 487 _, res, err = inbox.Read(context.TODO(), uid, &chat1.GetInboxQuery{ 488 ConvID: &convID, 489 }) 490 require.NoError(t, err) 491 require.Equal(t, chat1.MessageID(2), res[0].Conv.ReaderInfo.MaxMsgid, "wrong max msgid") 492 require.Equal(t, chat1.MessageID(2), res[0].Conv.ReaderInfo.ReadMsgid, "wrong read msgid") 493 require.Equal(t, gregor1.Time(0), res[0].Conv.ReaderInfo.LastSendTime) 494 495 err = inbox.ReadMessage(context.TODO(), uid, 10, conv.GetConvID(), 3) 496 require.IsType(t, VersionMismatchError{}, err) 497} 498 499func TestInboxSetStatus(t *testing.T) { 500 501 tc, inbox, uid := setupInboxTest(t, "basic") 502 defer tc.Cleanup() 503 504 // Create an inbox with a bunch of convos, merge it and read it back out 505 numConvs := 10 506 var convs []types.RemoteConversation 507 for i := numConvs - 1; i >= 0; i-- { 508 convs = append(convs, makeConvo(gregor1.Time(i), 1, 1)) 509 } 510 511 conv := convs[5] 512 require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil)) 513 require.NoError(t, inbox.SetStatus(context.TODO(), uid, 2, conv.GetConvID(), 514 chat1.ConversationStatus_IGNORED)) 515 516 q := chat1.GetInboxQuery{ 517 Status: []chat1.ConversationStatus{chat1.ConversationStatus_IGNORED}, 518 } 519 require.NoError(t, inbox.Merge(context.TODO(), uid, 2, []chat1.Conversation{}, &q)) 520 _, res, err := inbox.Read(context.TODO(), uid, &q) 521 require.NoError(t, err) 522 require.Equal(t, 1, len(res), "length") 523 require.Equal(t, conv.GetConvID(), res[0].GetConvID(), "id") 524 525 t.Logf("sending new message to wake up conv") 526 msg := makeInboxMsg(3, chat1.MessageType_TEXT) 527 msg.ClientHeader.Sender = uid 528 require.NoError(t, inbox.NewMessage(context.TODO(), uid, 3, conv.GetConvID(), msg, nil)) 529 _, res, err = inbox.Read(context.TODO(), uid, &q) 530 require.NoError(t, err) 531 require.Equal(t, 0, len(res), "ignore not unset") 532 533 err = inbox.SetStatus(context.TODO(), uid, 10, conv.GetConvID(), chat1.ConversationStatus_BLOCKED) 534 require.IsType(t, VersionMismatchError{}, err) 535} 536 537func TestInboxSetStatusMuted(t *testing.T) { 538 539 tc, inbox, uid := setupInboxTest(t, "basic") 540 defer tc.Cleanup() 541 542 // Create an inbox with a bunch of convos, merge it and read it back out 543 numConvs := 10 544 var convs []types.RemoteConversation 545 for i := numConvs - 1; i >= 0; i-- { 546 convs = append(convs, makeConvo(gregor1.Time(i), 1, 1)) 547 } 548 549 conv := convs[5] 550 require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil)) 551 require.NoError(t, inbox.SetStatus(context.TODO(), uid, 2, conv.GetConvID(), 552 chat1.ConversationStatus_MUTED)) 553 554 q := chat1.GetInboxQuery{ 555 Status: []chat1.ConversationStatus{chat1.ConversationStatus_MUTED}, 556 } 557 require.NoError(t, inbox.Merge(context.TODO(), uid, 2, []chat1.Conversation{}, &q)) 558 _, res, err := inbox.Read(context.TODO(), uid, &q) 559 require.NoError(t, err) 560 require.Equal(t, 1, len(res), "length") 561 require.Equal(t, conv.GetConvID(), res[0].GetConvID(), "id") 562 563 t.Logf("sending new message to wake up conv") 564 msg := makeInboxMsg(3, chat1.MessageType_TEXT) 565 msg.ClientHeader.Sender = uid 566 require.NoError(t, inbox.NewMessage(context.TODO(), uid, 3, conv.GetConvID(), msg, nil)) 567 _, res, err = inbox.Read(context.TODO(), uid, &q) 568 require.NoError(t, err) 569 require.Equal(t, 1, len(res), "muted wrongly unset") 570 571 err = inbox.SetStatus(context.TODO(), uid, 10, conv.GetConvID(), chat1.ConversationStatus_BLOCKED) 572 require.IsType(t, VersionMismatchError{}, err) 573} 574 575func TestInboxTlfFinalize(t *testing.T) { 576 577 tc, inbox, uid := setupInboxTest(t, "basic") 578 defer tc.Cleanup() 579 580 // Create an inbox with a bunch of convos, merge it and read it back out 581 numConvs := 10 582 var convs []types.RemoteConversation 583 for i := numConvs - 1; i >= 0; i-- { 584 convs = append(convs, makeConvo(gregor1.Time(i), 1, 1)) 585 } 586 587 conv := convs[5] 588 convID := conv.GetConvID() 589 require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil)) 590 require.NoError(t, inbox.TlfFinalize(context.TODO(), uid, 2, []chat1.ConversationID{conv.GetConvID()}, 591 chat1.ConversationFinalizeInfo{ResetFull: "reset"})) 592 _, res, err := inbox.Read(context.TODO(), uid, &chat1.GetInboxQuery{ 593 ConvID: &convID, 594 }) 595 require.NoError(t, err) 596 require.Equal(t, 1, len(res), "length") 597 require.Equal(t, conv.GetConvID(), res[0].GetConvID(), "id") 598 require.NotNil(t, res[0].Conv.Metadata.FinalizeInfo, "finalize info") 599 600 err = inbox.TlfFinalize(context.TODO(), uid, 10, []chat1.ConversationID{conv.GetConvID()}, 601 chat1.ConversationFinalizeInfo{ResetFull: "reset"}) 602 require.IsType(t, VersionMismatchError{}, err) 603} 604 605func TestInboxSync(t *testing.T) { 606 tc, inbox, uid := setupInboxTest(t, "basic") 607 defer tc.Cleanup() 608 609 // Create an inbox with a bunch of convos, merge it and read it back out 610 numConvs := 10 611 var convs []types.RemoteConversation 612 for i := numConvs - 1; i >= 0; i-- { 613 convs = append(convs, makeConvo(gregor1.Time(i), 1, 1)) 614 } 615 616 require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil)) 617 _, res, err := inbox.Read(context.TODO(), uid, nil) 618 require.NoError(t, err) 619 620 var syncConvs []chat1.Conversation 621 convs[0].Conv.Metadata.Status = chat1.ConversationStatus_MUTED 622 convs[6].Conv.Metadata.Status = chat1.ConversationStatus_MUTED 623 syncConvs = append(syncConvs, convs[0].Conv) 624 syncConvs = append(syncConvs, convs[6].Conv) 625 newConv := makeConvo(gregor1.Time(60), 1, 1) 626 syncConvs = append(syncConvs, newConv.Conv) 627 628 vers, err := inbox.Version(context.TODO(), uid) 629 require.NoError(t, err) 630 syncRes, err := inbox.Sync(context.TODO(), uid, vers+1, syncConvs) 631 require.NoError(t, err) 632 newVers, newRes, err := inbox.Read(context.TODO(), uid, nil) 633 require.NoError(t, err) 634 sort.Sort(ByDatabaseOrder(newRes)) 635 require.Equal(t, vers+1, newVers) 636 require.Equal(t, len(res)+1, len(newRes)) 637 require.Equal(t, newConv.GetConvID(), newRes[0].GetConvID()) 638 require.Equal(t, chat1.ConversationStatus_MUTED, newRes[1].Conv.Metadata.Status) 639 require.Equal(t, chat1.ConversationStatus_MUTED, newRes[7].Conv.Metadata.Status) 640 require.Equal(t, chat1.ConversationStatus_UNFILED, newRes[4].Conv.Metadata.Status) 641 require.False(t, syncRes.TeamTypeChanged) 642 require.Len(t, syncRes.Expunges, 0) 643 644 syncConvs = nil 645 vers, err = inbox.Version(context.TODO(), uid) 646 require.NoError(t, err) 647 convs[8].Conv.Metadata.TeamType = chat1.TeamType_COMPLEX 648 syncConvs = append(syncConvs, convs[8].Conv) 649 convs[9].Conv.Expunge = chat1.Expunge{Upto: 3} 650 syncConvs = append(syncConvs, convs[9].Conv) 651 syncRes, err = inbox.Sync(context.TODO(), uid, vers+1, syncConvs) 652 require.NoError(t, err) 653 newVers, newRes, err = inbox.Read(context.TODO(), uid, nil) 654 require.NoError(t, err) 655 sort.Sort(ByDatabaseOrder(newRes)) 656 require.Equal(t, vers+1, newVers) 657 require.Equal(t, chat1.TeamType_COMPLEX, newRes[9].Conv.Metadata.TeamType) 658 require.True(t, syncRes.TeamTypeChanged) 659 require.Len(t, syncRes.Expunges, 1) 660 require.True(t, convs[9].Conv.GetConvID().Eq(syncRes.Expunges[0].ConvID)) 661 require.Equal(t, convs[9].Conv.Expunge, syncRes.Expunges[0].Expunge) 662} 663 664func TestInboxServerVersion(t *testing.T) { 665 tc, inbox, uid := setupInboxTest(t, "basic") 666 defer tc.Cleanup() 667 668 // Create an inbox with a bunch of convos, merge it and read it back out 669 numConvs := 10 670 var convs []types.RemoteConversation 671 for i := numConvs - 1; i >= 0; i-- { 672 convs = append(convs, makeConvo(gregor1.Time(i), 1, 1)) 673 } 674 675 require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil)) 676 _, res, err := inbox.Read(context.TODO(), uid, nil) 677 require.NoError(t, err) 678 require.Equal(t, numConvs, len(res)) 679 680 // Increase server version 681 cerr := tc.Context().ServerCacheVersions.Set(context.TODO(), chat1.ServerCacheVers{ 682 InboxVers: 5, 683 }) 684 require.NoError(t, cerr) 685 686 _, _, err = inbox.Read(context.TODO(), uid, nil) 687 require.Error(t, err) 688 require.IsType(t, MissError{}, err) 689 690 require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil)) 691 idata, err := inbox.readDiskVersions(context.TODO(), uid, true) 692 require.NoError(t, err) 693 require.Equal(t, 5, idata.ServerVersion) 694} 695 696func TestInboxKBFSUpgrade(t *testing.T) { 697 tc, inbox, uid := setupInboxTest(t, "kbfs") 698 defer tc.Cleanup() 699 numConvs := 10 700 var convs []types.RemoteConversation 701 for i := numConvs - 1; i >= 0; i-- { 702 convs = append(convs, makeConvo(gregor1.Time(i), 1, 1)) 703 } 704 conv := convs[5] 705 convID := conv.GetConvID() 706 require.Equal(t, chat1.ConversationMembersType_KBFS, conv.Conv.GetMembersType()) 707 require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil)) 708 require.NoError(t, inbox.UpgradeKBFSToImpteam(context.TODO(), uid, 2, conv.GetConvID())) 709 _, res, err := inbox.Read(context.TODO(), uid, &chat1.GetInboxQuery{ 710 ConvID: &convID, 711 }) 712 require.NoError(t, err) 713 require.Equal(t, 1, len(res), "length") 714 require.Equal(t, conv.GetConvID(), res[0].GetConvID(), "id") 715 require.Equal(t, chat1.ConversationMembersType_IMPTEAMUPGRADE, res[0].Conv.Metadata.MembersType) 716} 717 718func makeUID(t *testing.T) gregor1.UID { 719 b, err := libkb.RandBytes(16) 720 require.NoError(t, err) 721 return gregor1.UID(b) 722} 723 724func TestInboxMembershipDupUpdate(t *testing.T) { 725 ctc, inbox, uid := setupInboxTest(t, "membership") 726 defer ctc.Cleanup() 727 728 uid2 := makeUID(t) 729 conv := makeConvo(gregor1.Time(1), 1, 1) 730 conv.Conv.Metadata.AllList = []gregor1.UID{uid, uid2} 731 require.NoError(t, inbox.Merge(context.TODO(), uid, 1, []chat1.Conversation{conv.Conv}, nil)) 732 733 otherJoinedConvs := []chat1.ConversationMember{{ 734 Uid: uid2, 735 ConvID: conv.GetConvID(), 736 }} 737 roleUpdates, err := inbox.MembershipUpdate(context.TODO(), uid, 2, []chat1.Conversation{conv.Conv}, 738 nil, otherJoinedConvs, nil, nil, nil, nil) 739 require.NoError(t, err) 740 require.Nil(t, roleUpdates) 741 742 _, res, err := inbox.ReadAll(context.TODO(), uid, true) 743 require.NoError(t, err) 744 require.Equal(t, 1, len(res)) 745 require.Equal(t, 2, len(res[0].Conv.Metadata.AllList)) 746} 747 748func TestInboxMembershipUpdate(t *testing.T) { 749 ctc, inbox, uid := setupInboxTest(t, "membership") 750 defer ctc.Cleanup() 751 752 u2, err := kbtest.CreateAndSignupFakeUser("ib", ctc.G) 753 require.NoError(t, err) 754 uid2 := gregor1.UID(u2.User.GetUID().ToBytes()) 755 756 u3, err := kbtest.CreateAndSignupFakeUser("ib", ctc.G) 757 require.NoError(t, err) 758 uid3 := gregor1.UID(u3.User.GetUID().ToBytes()) 759 760 u4, err := kbtest.CreateAndSignupFakeUser("ib", ctc.G) 761 require.NoError(t, err) 762 uid4 := gregor1.UID(u4.User.GetUID().ToBytes()) 763 764 t.Logf("uid: %s uid2: %s uid3: %s uid4: %s", uid, uid2, uid3, uid4) 765 766 // Create an inbox with a bunch of convos, merge it and read it back out 767 numConvs := 10 768 var convs []types.RemoteConversation 769 tlfID := makeTlfID() 770 for i := numConvs - 1; i >= 0; i-- { 771 conv := makeConvo(gregor1.Time(i), 1, 1) 772 conv.Conv.Metadata.IdTriple.Tlfid = tlfID 773 conv.Conv.Metadata.AllList = []gregor1.UID{uid, uid3, uid4} 774 convs = append(convs, conv) 775 } 776 777 require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil)) 778 var joinedConvs []types.RemoteConversation 779 numJoinedConvs := 5 780 for i := 0; i < numJoinedConvs; i++ { 781 conv := makeConvo(gregor1.Time(i), 1, 1) 782 conv.Conv.Metadata.IdTriple.Tlfid = tlfID 783 conv.Conv.Metadata.AllList = []gregor1.UID{uid, uid3, uid4} 784 joinedConvs = append(joinedConvs, conv) 785 } 786 787 otherJoinConvID := convs[0].GetConvID() 788 otherJoinedConvs := []chat1.ConversationMember{{ 789 Uid: uid2, 790 ConvID: otherJoinConvID, 791 }} 792 otherRemovedConvID := convs[1].GetConvID() 793 otherRemovedConvs := []chat1.ConversationMember{{ 794 Uid: uid3, 795 ConvID: otherRemovedConvID, 796 }} 797 otherResetConvID := convs[2].GetConvID() 798 otherResetConvs := []chat1.ConversationMember{{ 799 Uid: uid4, 800 ConvID: otherResetConvID, 801 }} 802 userRemovedConvID := convs[5].GetConvID() 803 userRemovedConvs := []chat1.ConversationMember{{ 804 Uid: uid, 805 ConvID: userRemovedConvID, 806 }} 807 userResetConvID := convs[6].GetConvID() 808 userResetConvs := []chat1.ConversationMember{{ 809 Uid: uid, 810 ConvID: userResetConvID, 811 }} 812 813 roleUpdates, err := inbox.MembershipUpdate(context.TODO(), uid, 2, utils.PluckConvs(joinedConvs), 814 userRemovedConvs, otherJoinedConvs, otherRemovedConvs, 815 userResetConvs, otherResetConvs, &chat1.TeamMemberRoleUpdate{ 816 TlfID: tlfID, 817 Role: keybase1.TeamRole_WRITER, 818 }) 819 require.NoError(t, err) 820 require.NotNil(t, roleUpdates) 821 822 vers, res, err := inbox.ReadAll(context.TODO(), uid, true) 823 require.NoError(t, err) 824 require.Equal(t, chat1.InboxVers(2), vers) 825 for i, c := range res { 826 // make sure we bump the local version during the membership update for a role change 827 require.EqualValues(t, 1, c.Conv.Metadata.LocalVersion) 828 res[i].Conv.Metadata.LocalVersion = 0 // zero it out for later equality checks 829 if c.GetConvID().Eq(convs[5].GetConvID()) { 830 require.Equal(t, chat1.ConversationMemberStatus_LEFT, c.Conv.ReaderInfo.Status) 831 require.Equal(t, keybase1.TeamRole_WRITER, c.Conv.ReaderInfo.UntrustedTeamRole) 832 convs[5].Conv.ReaderInfo.Status = chat1.ConversationMemberStatus_LEFT 833 convs[5].Conv.Metadata.Version = chat1.ConversationVers(2) 834 } else if c.GetConvID().Eq(convs[6].GetConvID()) { 835 require.Equal(t, chat1.ConversationMemberStatus_RESET, c.Conv.ReaderInfo.Status) 836 require.Equal(t, keybase1.TeamRole_WRITER, c.Conv.ReaderInfo.UntrustedTeamRole) 837 convs[6].Conv.ReaderInfo.Status = chat1.ConversationMemberStatus_RESET 838 convs[6].Conv.Metadata.Version = chat1.ConversationVers(2) 839 } 840 } 841 expected := append(convs, joinedConvs...) 842 sort.Sort(utils.RemoteConvByConvID(expected)) 843 sort.Sort(utils.ByConvID(roleUpdates)) 844 sort.Sort(utils.RemoteConvByConvID(res)) 845 require.Equal(t, len(expected), len(res)) 846 for i := 0; i < len(res); i++ { 847 sort.Sort(chat1.ByUID(res[i].Conv.Metadata.AllList)) 848 sort.Sort(chat1.ByUID(expected[i].Conv.Metadata.AllList)) 849 require.Equal(t, keybase1.TeamRole_WRITER, res[i].Conv.ReaderInfo.UntrustedTeamRole) 850 require.True(t, expected[i].GetConvID().Eq(roleUpdates[i])) 851 if res[i].GetConvID().Eq(otherJoinConvID) { 852 allUsers := []gregor1.UID{uid, uid2, uid3, uid4} 853 sort.Sort(chat1.ByUID(allUsers)) 854 require.Equal(t, allUsers, res[i].Conv.Metadata.AllList) 855 require.Zero(t, len(res[i].Conv.Metadata.ResetList)) 856 } else if res[i].GetConvID().Eq(otherRemovedConvID) { 857 allUsers := []gregor1.UID{uid, uid4} 858 sort.Sort(chat1.ByUID(allUsers)) 859 require.Equal(t, allUsers, res[i].Conv.Metadata.AllList) 860 require.Zero(t, len(res[i].Conv.Metadata.ResetList)) 861 } else if res[i].GetConvID().Eq(otherResetConvID) { 862 allUsers := []gregor1.UID{uid, uid3, uid4} 863 sort.Sort(chat1.ByUID(allUsers)) 864 resetUsers := []gregor1.UID{uid4} 865 require.Len(t, res[i].Conv.Metadata.AllList, len(allUsers)) 866 require.Equal(t, allUsers, res[i].Conv.Metadata.AllList) 867 require.Equal(t, resetUsers, res[i].Conv.Metadata.ResetList) 868 } else if res[i].GetConvID().Eq(userRemovedConvID) { 869 allUsers := []gregor1.UID{uid3, uid4} 870 sort.Sort(chat1.ByUID(allUsers)) 871 require.Len(t, res[i].Conv.Metadata.AllList, len(allUsers)) 872 require.Equal(t, allUsers, res[i].Conv.Metadata.AllList) 873 require.Zero(t, len(res[i].Conv.Metadata.ResetList)) 874 } else if res[i].GetConvID().Eq(userResetConvID) { 875 allUsers := []gregor1.UID{uid, uid3, uid4} 876 sort.Sort(chat1.ByUID(allUsers)) 877 resetUsers := []gregor1.UID{uid} 878 require.Len(t, res[i].Conv.Metadata.AllList, len(allUsers)) 879 require.Equal(t, allUsers, res[i].Conv.Metadata.AllList) 880 require.Equal(t, resetUsers, res[i].Conv.Metadata.ResetList) 881 } else { 882 allUsers := []gregor1.UID{uid, uid3, uid4} 883 sort.Sort(chat1.ByUID(allUsers)) 884 require.Equal(t, allUsers, res[i].Conv.Metadata.AllList) 885 expected[i].Conv.ReaderInfo.UntrustedTeamRole = keybase1.TeamRole_WRITER 886 require.Equal(t, expected[i], res[i]) 887 } 888 } 889} 890 891// TestInboxCacheOnLogout checks that calling OnLogout() clears the cache. 892func TestInboxCacheOnLogout(t *testing.T) { 893 uid := keybase1.MakeTestUID(3) 894 inboxMemCache.PutVersions(gregor1.UID(uid), &inboxDiskVersions{}) 895 require.NotEmpty(t, len(inboxMemCache.versMap)) 896 err := inboxMemCache.OnLogout(libkb.NewMetaContextTODO(nil)) 897 require.NoError(t, err) 898 require.Nil(t, inboxMemCache.GetVersions(gregor1.UID(uid))) 899 require.Empty(t, len(inboxMemCache.versMap)) 900} 901 902func TestUpdateLocalMtime(t *testing.T) { 903 tc, inbox, uid := setupInboxTest(t, "local conv") 904 defer tc.Cleanup() 905 convs := []types.RemoteConversation{ 906 makeConvo(gregor1.Time(1), 1, 1), 907 makeConvo(gregor1.Time(0), 1, 1), 908 } 909 err := inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs(convs), nil) 910 require.NoError(t, err) 911 mtime1 := gregor1.Time(5) 912 mtime2 := gregor1.Time(1) 913 err = inbox.UpdateLocalMtime(context.TODO(), uid, []chat1.LocalMtimeUpdate{ 914 { 915 ConvID: convs[0].GetConvID(), 916 Mtime: mtime1, 917 }, 918 { 919 ConvID: convs[1].GetConvID(), 920 Mtime: mtime2, 921 }, 922 }) 923 require.NoError(t, err) 924 925 diskIndex, err := inbox.readDiskIndex(context.TODO(), uid, true) 926 require.NoError(t, err) 927 convs = nil 928 for _, convID := range diskIndex.ConversationIDs { 929 conv, err := inbox.readConv(context.TODO(), uid, convID) 930 require.NoError(t, err) 931 convs = append(convs, conv) 932 } 933 934 sort.Slice(convs, func(i, j int) bool { 935 return convs[i].GetMtime() > convs[j].GetMtime() 936 }) 937 require.Equal(t, mtime1, convs[0].GetMtime()) 938 require.Equal(t, mtime2, convs[1].GetMtime()) 939} 940