github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/chat/storage/storage_test.go (about) 1 package storage 2 3 import ( 4 "crypto/rand" 5 "math/big" 6 "sort" 7 "testing" 8 9 "github.com/keybase/client/go/chat/globals" 10 "github.com/keybase/client/go/chat/pager" 11 "github.com/keybase/client/go/chat/types" 12 "github.com/keybase/client/go/chat/utils" 13 "github.com/keybase/client/go/externalstest" 14 "github.com/keybase/client/go/kbtest" 15 "github.com/keybase/client/go/libkb" 16 "github.com/keybase/client/go/protocol/chat1" 17 "github.com/keybase/client/go/protocol/gregor1" 18 insecureTriplesec "github.com/keybase/go-triplesec-insecure" 19 "github.com/stretchr/testify/require" 20 "golang.org/x/net/context" 21 ) 22 23 func setupCommonTest(t testing.TB, name string) kbtest.ChatTestContext { 24 tc := externalstest.SetupTest(t, name, 2) 25 26 // use an insecure triplesec in tests 27 tc.G.NewTriplesec = func(passphrase []byte, salt []byte) (libkb.Triplesec, error) { 28 warner := func() { tc.G.Log.Warning("Installing insecure Triplesec with weak stretch parameters") } 29 isProduction := func() bool { 30 return tc.G.Env.GetRunMode() == libkb.ProductionRunMode 31 } 32 return insecureTriplesec.NewCipher(passphrase, salt, libkb.ClientTriplesecVersion, warner, isProduction) 33 } 34 ctc := kbtest.ChatTestContext{ 35 TestContext: tc, 36 ChatG: &globals.ChatContext{ 37 AttachmentUploader: types.DummyAttachmentUploader{}, 38 Unfurler: types.DummyUnfurler{}, 39 EphemeralPurger: types.DummyEphemeralPurger{}, 40 EphemeralTracker: types.DummyEphemeralTracker{}, 41 Indexer: types.DummyIndexer{}, 42 CtxFactory: dummyContextFactory{}, 43 }, 44 } 45 ctc.Context().ServerCacheVersions = NewServerVersions(ctc.Context()) 46 return ctc 47 } 48 49 func setupStorageTest(t testing.TB, name string) (kbtest.ChatTestContext, *Storage, gregor1.UID) { 50 ctc := setupCommonTest(t, name) 51 u, err := kbtest.CreateAndSignupFakeUser("cs", ctc.TestContext.G) 52 require.NoError(t, err) 53 return ctc, New(ctc.Context(), kbtest.NewDummyAssetDeleter()), gregor1.UID(u.User.GetUID().ToBytes()) 54 } 55 56 func mustMerge(t testing.TB, storage *Storage, 57 convID chat1.ConversationID, uid gregor1.UID, msgs []chat1.MessageUnboxed) MergeResult { 58 conv, err := NewInbox(storage.G()).GetConversation(context.Background(), uid, convID) 59 switch err.(type) { 60 case nil: 61 case MissError: 62 conv = types.NewEmptyRemoteConversation(convID) 63 default: 64 require.NoError(t, err) 65 } 66 res, err := storage.Merge(context.Background(), conv, uid, msgs) 67 require.NoError(t, err) 68 return res 69 } 70 71 func makeMsgRange(max int) (res []chat1.MessageUnboxed) { 72 for i := max; i > 0; i-- { 73 res = append(res, MakeText(chat1.MessageID(i), "junk text")) 74 } 75 return res 76 } 77 78 func addMsgs(num int, msgs []chat1.MessageUnboxed) []chat1.MessageUnboxed { 79 maxID := msgs[0].GetMessageID() 80 for i := 0; i < num; i++ { 81 msgs = append([]chat1.MessageUnboxed{MakeText(chat1.MessageID(int(maxID)+i+1), "addMsgs junk text")}, 82 msgs...) 83 } 84 return msgs 85 } 86 87 func doSimpleBench(b *testing.B, storage *Storage, uid gregor1.UID) { 88 msgs := makeMsgRange(100000) 89 conv := MakeConversation(msgs[0].GetMessageID()) 90 b.ResetTimer() 91 92 for i := 0; i < b.N; i++ { 93 mustMerge(b, storage, conv.Metadata.ConversationID, uid, msgs) 94 _, err := storage.Fetch(context.TODO(), conv, uid, nil, nil, nil) 95 require.NoError(b, err) 96 err = storage.ClearAll(context.TODO(), conv.Metadata.ConversationID, uid) 97 require.NoError(b, err) 98 } 99 } 100 101 func doCommonBench(b *testing.B, storage *Storage, uid gregor1.UID) { 102 msgs := makeMsgRange(107) 103 conv := MakeConversation(msgs[0].GetMessageID()) 104 b.ResetTimer() 105 for i := 0; i < b.N; i++ { 106 mustMerge(b, storage, conv.Metadata.ConversationID, uid, msgs) 107 _, err := storage.Fetch(context.TODO(), conv, uid, nil, nil, nil) 108 require.NoError(b, err) 109 110 // Add some msgs 111 b.StopTimer() 112 newmsgs := addMsgs(15, msgs) 113 newconv := MakeConversation(newmsgs[0].GetMessageID()) 114 b.StartTimer() 115 116 mustMerge(b, storage, conv.Metadata.ConversationID, uid, newmsgs) 117 _, err = storage.Fetch(context.TODO(), newconv, uid, nil, nil, nil) 118 require.NoError(b, err) 119 } 120 } 121 122 func doRandomBench(b *testing.B, storage *Storage, uid gregor1.UID, num, len int) { 123 msgs := makeMsgRange(num) 124 conv := MakeConversation(msgs[0].GetMessageID()) 125 b.ResetTimer() 126 for i := 0; i < b.N; i++ { 127 mustMerge(b, storage, conv.Metadata.ConversationID, uid, msgs) 128 for j := 0; j < 300; j++ { 129 130 b.StopTimer() 131 var bi *big.Int 132 var err error 133 for { 134 bi, err = rand.Int(rand.Reader, big.NewInt(int64(num))) 135 require.NoError(b, err) 136 if bi.Int64() > 1 { 137 break 138 } 139 } 140 next, err := encode(chat1.MessageID(bi.Int64())) 141 require.NoError(b, err) 142 p := chat1.Pagination{ 143 Num: len, 144 Next: next, 145 } 146 b.StartTimer() 147 148 _, err = storage.Fetch(context.TODO(), conv, uid, nil, nil, &p) 149 require.NoError(b, err) 150 } 151 } 152 } 153 154 func BenchmarkStorageSimpleBlockEngine(b *testing.B) { 155 tc, storage, uid := setupStorageTest(b, "basic") 156 defer tc.Cleanup() 157 storage.setEngine(newBlockEngine(tc.Context())) 158 doSimpleBench(b, storage, uid) 159 } 160 161 func BenchmarkStorageCommonBlockEngine(b *testing.B) { 162 tc, storage, uid := setupStorageTest(b, "basic") 163 defer tc.Cleanup() 164 storage.setEngine(newBlockEngine(tc.Context())) 165 doCommonBench(b, storage, uid) 166 } 167 168 func BenchmarkStorageRandomBlockEngine(b *testing.B) { 169 tc, storage, uid := setupStorageTest(b, "basic") 170 defer tc.Cleanup() 171 storage.setEngine(newBlockEngine(tc.Context())) 172 doRandomBench(b, storage, uid, 127, 1) 173 } 174 175 func BenchmarkStorageRandomLongBlockEngine(b *testing.B) { 176 tc, storage, uid := setupStorageTest(b, "basic") 177 defer tc.Cleanup() 178 storage.setEngine(newBlockEngine(tc.Context())) 179 doRandomBench(b, storage, uid, 127, 1) 180 } 181 182 func TestStorageBasic(t *testing.T) { 183 tc, storage, uid := setupStorageTest(t, "basic") 184 defer tc.Cleanup() 185 186 msgs := makeMsgRange(10) 187 conv := MakeConversation(msgs[0].GetMessageID()) 188 189 mustMerge(t, storage, conv.Metadata.ConversationID, uid, msgs) 190 fetchRes, err := storage.Fetch(context.TODO(), conv, uid, nil, nil, nil) 191 require.NoError(t, err) 192 res := fetchRes.Thread 193 require.Equal(t, len(msgs), len(res.Messages), "wrong amount of messages") 194 for i := 0; i < len(res.Messages); i++ { 195 require.Equal(t, msgs[i].GetMessageID(), res.Messages[i].GetMessageID(), "msg mismatch") 196 } 197 } 198 199 func TestStorageLargeList(t *testing.T) { 200 tc, storage, uid := setupStorageTest(t, "large list") 201 defer tc.Cleanup() 202 203 msgs := makeMsgRange(1000) 204 conv := MakeConversation(msgs[0].GetMessageID()) 205 206 mustMerge(t, storage, conv.Metadata.ConversationID, uid, msgs) 207 fetchRes, err := storage.Fetch(context.TODO(), conv, uid, nil, nil, nil) 208 require.NoError(t, err) 209 res := fetchRes.Thread 210 require.Equal(t, len(msgs), len(res.Messages), "wrong amount of messages") 211 require.Equal(t, utils.PluckMUMessageIDs(msgs), utils.PluckMUMessageIDs(res.Messages)) 212 213 } 214 215 func TestStorageBlockBoundary(t *testing.T) { 216 tc, storage, uid := setupStorageTest(t, "block boundary") 217 defer tc.Cleanup() 218 msgs := makeMsgRange(blockSize - 1) 219 conv := MakeConversation(msgs[0].GetMessageID()) 220 mustMerge(t, storage, conv.Metadata.ConversationID, uid, msgs) 221 fetchRes, err := storage.Fetch(context.TODO(), conv, uid, nil, nil, nil) 222 require.NoError(t, err) 223 res := fetchRes.Thread 224 require.Equal(t, len(msgs), len(res.Messages), "wrong amount of messages") 225 require.Equal(t, utils.PluckMUMessageIDs(msgs), utils.PluckMUMessageIDs(res.Messages)) 226 appendMsg := MakeText(chat1.MessageID(blockSize), "COMBOBREAKER") 227 mustMerge(t, storage, conv.Metadata.ConversationID, uid, []chat1.MessageUnboxed{appendMsg}) 228 conv.ReaderInfo.MaxMsgid = chat1.MessageID(blockSize) 229 fetchRes, err = storage.Fetch(context.TODO(), conv, uid, nil, nil, nil) 230 msgs = append([]chat1.MessageUnboxed{appendMsg}, msgs...) 231 require.NoError(t, err) 232 res = fetchRes.Thread 233 require.Equal(t, len(msgs), len(res.Messages), "wrong amount of messages") 234 require.Equal(t, utils.PluckMUMessageIDs(msgs), utils.PluckMUMessageIDs(res.Messages)) 235 } 236 237 func TestStorageSupersedes(t *testing.T) { 238 var err error 239 240 tc, storage, uid := setupStorageTest(t, "supersedes") 241 defer tc.Cleanup() 242 243 // First test an Edit message. 244 supersedingEdit := MakeEdit(chat1.MessageID(111), 6) 245 246 msgs := makeMsgRange(110) 247 msgs = append([]chat1.MessageUnboxed{supersedingEdit}, msgs...) 248 conv := MakeConversation(msgs[0].GetMessageID()) 249 250 mustMerge(t, storage, conv.Metadata.ConversationID, uid, msgs) 251 fetchRes, err := storage.Fetch(context.TODO(), conv, uid, nil, nil, nil) 252 require.NoError(t, err) 253 res := fetchRes.Thread 254 require.Equal(t, len(msgs), len(res.Messages), "wrong amount of messages") 255 for i := 0; i < len(res.Messages); i++ { 256 require.Equal(t, msgs[i].GetMessageID(), res.Messages[i].GetMessageID(), "msg mismatch") 257 } 258 sheader := res.Messages[len(msgs)-6].Valid().ServerHeader 259 require.Equal(t, chat1.MessageID(6), sheader.MessageID, "MessageID incorrect") 260 require.Equal(t, chat1.MessageID(111), sheader.SupersededBy, "supersededBy incorrect") 261 262 // Now test a delete message. This should result in the deletion of *both* 263 // the original message's body and the body of the edit above. 264 supersedingDelete := MakeDelete(chat1.MessageID(112), 6, []chat1.MessageID{111}) 265 266 mustMerge(t, storage, conv.Metadata.ConversationID, uid, []chat1.MessageUnboxed{supersedingDelete}) 267 conv.ReaderInfo.MaxMsgid = 112 268 msgs = append([]chat1.MessageUnboxed{supersedingDelete}, msgs...) 269 fetchRes, err = storage.Fetch(context.TODO(), conv, uid, nil, nil, nil) 270 require.NoError(t, err) 271 res = fetchRes.Thread 272 273 deletedMessage := res.Messages[len(msgs)-6].Valid() 274 deletedHeader := deletedMessage.ServerHeader 275 require.Equal(t, chat1.MessageID(6), deletedHeader.MessageID, "MessageID incorrect") 276 require.Equal(t, chat1.MessageID(112), deletedHeader.SupersededBy, "supersededBy incorrect") 277 // Check that the body is deleted. 278 deletedBodyType, err := deletedMessage.MessageBody.MessageType() 279 require.NoError(t, err) 280 require.Equal(t, chat1.MessageType_NONE, deletedBodyType, "expected the body to be deleted, but it's not!!!") 281 // Check that the body of the edit is *also* is deleted. 282 deletedEdit := res.Messages[len(msgs)-111].Valid() 283 deletedEditBodyType, err := deletedEdit.MessageBody.MessageType() 284 require.NoError(t, err) 285 require.Equal(t, chat1.MessageType_NONE, deletedEditBodyType, "expected the edit's body to be deleted also, but it's not!!!") 286 } 287 288 func TestStorageDeleteHistory(t *testing.T) { 289 // Uses this conversation: 290 // A start <not deletable> 291 // B text 292 // C text <----\ edited by E 293 // D headline | 294 // E edit -----^ edits C 295 // F text <---\ deleted by G 296 // G delete --^ ___ deletes F <not deletable> 297 // H text | 298 // I headline | <not deletable> 299 // J delete-history ^ upto H 300 // K delete-history upto itself 301 // L text 302 303 tc, storage, uid := setupStorageTest(t, "delh") 304 defer tc.Cleanup() 305 inbox := NewInbox(tc.Context()) 306 conv := makeConvo(gregor1.Time(0), 1, 1) 307 conv.Conv.MaxMsgSummaries = append(conv.Conv.MaxMsgSummaries, chat1.MessageSummary{MessageType: chat1.MessageType_HEADLINE, MsgID: 9}) 308 require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs([]types.RemoteConversation{conv}), nil)) 309 310 convID := conv.GetConvID() 311 msgA := MakeMsgWithType(1, chat1.MessageType_TLFNAME) 312 msgB := MakeText(2, "some text") 313 msgC := MakeText(3, "some text") 314 msgD := MakeHeadlineMessage(4) 315 msgE := MakeEdit(5, msgC.GetMessageID()) 316 msgF := MakeText(6, "some text") 317 msgG := MakeDelete(7, msgF.GetMessageID(), nil) 318 msgH := MakeText(8, "some text") 319 msgI := MakeHeadlineMessage(9) 320 msgJ := MakeDeleteHistory(10, msgI.GetMessageID()) 321 msgK := MakeDeleteHistory(11, 11) 322 msgL := MakeText(12, "some text") 323 324 type expectedM struct { 325 Name string // letter label 326 MsgID chat1.MessageID 327 BodyPresent bool 328 SupersededBy chat1.MessageID 329 } 330 331 var expectedState []expectedM // expectations sorted by ID ascending 332 setExpected := func(name string, msg chat1.MessageUnboxed, bodyPresent bool, supersededBy chat1.MessageID) { 333 xset := expectedM{name, msg.GetMessageID(), bodyPresent, supersededBy} 334 var found bool 335 for i, x := range expectedState { 336 if x.Name == name { 337 found = true 338 expectedState[i] = xset 339 } 340 } 341 if !found { 342 expectedState = append(expectedState, xset) 343 } 344 sort.Slice(expectedState, func(i, j int) bool { 345 return expectedState[i].MsgID < expectedState[j].MsgID 346 }) 347 } 348 assertStateHelper := func(maxMsgID chat1.MessageID, allowHoles bool) { 349 var rc ResultCollector 350 if allowHoles { 351 rc = NewInsatiableResultCollector() 352 } 353 fetchRes, err := storage.Fetch(context.Background(), MakeConversationAt(convID, maxMsgID), uid, rc, 354 nil, nil) 355 require.NoError(t, err) 356 res := fetchRes.Thread 357 if len(res.Messages) != len(expectedState) { 358 t.Logf("wrong number of messages") 359 for _, m := range res.Messages { 360 t.Logf("msgid:%v type:%v", m.GetMessageID(), m.GetMessageType()) 361 } 362 require.Equal(t, len(expectedState), len(res.Messages), "wrong number of messages") 363 } 364 for i, x := range expectedState { 365 t.Logf("[%v] checking msgID:%v supersededBy:%v", x.Name, x.MsgID, x.SupersededBy) 366 m := res.Messages[len(res.Messages)-1-i] 367 require.True(t, m.IsValid(), "[%v] message should be valid", x.Name) 368 require.Equal(t, x.MsgID, m.Valid().ServerHeader.MessageID, "[%v] message ID", x.Name) 369 if m.GetMessageType() != chat1.MessageType_TLFNAME { 370 if !x.BodyPresent && x.SupersededBy == 0 { 371 t.Fatalf("You expected the body to be deleted but the message not to be superseded. Are you sure?") 372 } 373 } 374 require.Equal(t, x.SupersededBy, m.Valid().ServerHeader.SupersededBy, "[%v] superseded by", x.Name) 375 if x.BodyPresent { 376 require.False(t, m.Valid().MessageBody.IsNil(), "[%v] message body should not be deleted", x.Name) 377 } else { 378 require.True(t, m.Valid().MessageBody.IsNil(), "[%v] message body should be deleted", x.Name) 379 } 380 } 381 } 382 assertState := func(maxMsgID chat1.MessageID) { 383 assertStateHelper(maxMsgID, false) 384 } 385 assertStateAllowHoles := func(maxMsgID chat1.MessageID) { 386 assertStateHelper(maxMsgID, true) 387 } 388 merge := func(msgsUnsorted []chat1.MessageUnboxed, expectedDeletedHistory bool) { 389 res := mustMerge(t, storage, convID, uid, SortMessagesDesc(msgsUnsorted)) 390 if expectedDeletedHistory { 391 require.NotNil(t, res.Expunged, "deleted history merge response") 392 } else { 393 require.Nil(t, res.Expunged, "deleted history merge response") 394 } 395 t.Logf("merge complete") 396 } 397 398 t.Logf("initial merge") 399 // merge with no delh messages 400 merge([]chat1.MessageUnboxed{msgA, msgB, msgC, msgD, msgE, msgF, msgG}, false) 401 setExpected("A", msgA, false, 0) // TLFNAME messages have no body 402 setExpected("B", msgB, true, 0) 403 setExpected("C", msgC, true, msgE.GetMessageID()) 404 setExpected("D", msgD, true, 0) 405 setExpected("E", msgE, true, 0) 406 setExpected("F", msgF, false, msgG.GetMessageID()) 407 setExpected("G", msgG, true, 0) 408 assertState(msgG.GetMessageID()) 409 410 t.Logf("merge first delh") 411 // merge with one delh 412 merge([]chat1.MessageUnboxed{msgH, msgI, msgJ}, true) 413 setExpected("A", msgA, false, 0) 414 setExpected("B", msgB, false, msgJ.GetMessageID()) 415 setExpected("C", msgC, false, msgJ.GetMessageID()) 416 setExpected("D", msgD, false, msgJ.GetMessageID()) 417 setExpected("E", msgE, false, msgJ.GetMessageID()) 418 setExpected("F", msgF, false, msgG.GetMessageID()) 419 setExpected("G", msgG, true, 0) // delete does not get deleted 420 setExpected("H", msgH, false, msgJ.GetMessageID()) // after the cutoff 421 setExpected("I", msgI, true, 0) // not deletable 422 setExpected("J", msgJ, true, 0) 423 assertState(msgJ.GetMessageID()) 424 425 t.Logf("merge an already-processed delh") 426 merge([]chat1.MessageUnboxed{msgI, msgJ}, false) 427 assertState(msgJ.GetMessageID()) 428 429 t.Logf("merge second delh (J)") 430 merge([]chat1.MessageUnboxed{msgK, msgL}, true) 431 setExpected("I", msgI, true, 0) // last headline can't be deleted 432 setExpected("J", msgJ, true, 0) // delh can't be deleted 433 setExpected("K", msgK, true, 0) // delh can't be deleted 434 setExpected("L", msgL, true, 0) // after the cutoff 435 assertState(msgL.GetMessageID()) 436 437 t.Logf("merge non-latest delh") 438 merge([]chat1.MessageUnboxed{msgJ}, false) 439 assertState(msgL.GetMessageID()) 440 441 t.Logf("discard storage") 442 // Start over on storage, this time try things while missing 443 // the beginning of the chat, and having holes in storage. 444 _, err := storage.G().LocalChatDb.Nuke() 445 require.NoError(t, err) 446 expectedState = nil 447 448 t.Logf("merge early part") 449 merge([]chat1.MessageUnboxed{msgA, msgB}, false) 450 setExpected("A", msgA, false, 0) 451 setExpected("B", msgB, true, 0) 452 assertState(msgB.GetMessageID()) 453 454 t.Logf("merge after gap") 455 merge([]chat1.MessageUnboxed{msgH, msgI, msgJ, msgK, msgL}, true) 456 // B gets deleted even through it was across a gap 457 setExpected("B", msgB, false, msgK.GetMessageID()) 458 setExpected("H", msgH, false, msgK.GetMessageID()) 459 setExpected("I", msgI, true, 0) // headline can't be deleted 460 setExpected("J", msgJ, true, 0) // delh can't be deleted 461 setExpected("K", msgK, true, 0) // delh can't be deleted 462 setExpected("L", msgL, true, 0) // after the cutoff 463 assertStateAllowHoles(msgL.GetMessageID()) 464 } 465 466 func TestStorageExpunge(t *testing.T) { 467 // Uses this conversation: 468 // A start <not deletable> 469 // B text 470 // C text <----\ edited by E 471 // D headline | 472 // E edit -----^ edits C 473 // F text <---\ deleted by G 474 // G delete --^ ___ deletes F <not deletable> 475 // H text | 476 // I headline 477 // J delete-history ^ upto I 478 479 tc, storage, uid := setupStorageTest(t, "delh") 480 defer tc.Cleanup() 481 482 inbox := NewInbox(tc.Context()) 483 conv := makeConvo(gregor1.Time(0), 1, 1) 484 conv.Conv.MaxMsgSummaries = append(conv.Conv.MaxMsgSummaries, chat1.MessageSummary{MessageType: chat1.MessageType_HEADLINE, MsgID: 9}) 485 require.NoError(t, inbox.Merge(context.TODO(), uid, 1, utils.PluckConvs([]types.RemoteConversation{conv}), nil)) 486 487 convID := conv.GetConvID() 488 msgA := MakeMsgWithType(1, chat1.MessageType_TLFNAME) 489 msgB := MakeText(2, "some text") 490 msgC := MakeText(3, "some text") 491 msgD := MakeHeadlineMessage(4) 492 msgE := MakeEdit(5, msgC.GetMessageID()) 493 msgF := MakeText(6, "some text") 494 msgG := MakeDelete(7, msgF.GetMessageID(), nil) 495 msgH := MakeText(8, "some text") 496 msgI := MakeHeadlineMessage(9) 497 msgJ := MakeDeleteHistory(10, msgI.GetMessageID()) 498 499 type expectedM struct { 500 Name string // letter label 501 MsgID chat1.MessageID 502 BodyPresent bool 503 SupersededBy chat1.MessageID 504 } 505 dontCare := chat1.MessageID(12341234) 506 507 var expectedState []expectedM // expectations sorted by ID ascending 508 setExpected := func(name string, msg chat1.MessageUnboxed, bodyPresent bool, supersededBy chat1.MessageID) { 509 xset := expectedM{name, msg.GetMessageID(), bodyPresent, supersededBy} 510 var found bool 511 for i, x := range expectedState { 512 if x.Name == name { 513 found = true 514 expectedState[i] = xset 515 } 516 } 517 if !found { 518 expectedState = append(expectedState, xset) 519 } 520 sort.Slice(expectedState, func(i, j int) bool { 521 return expectedState[i].MsgID < expectedState[j].MsgID 522 }) 523 } 524 assertState := func(maxMsgID chat1.MessageID) { 525 var rc ResultCollector 526 fetchRes, err := storage.Fetch(context.Background(), MakeConversationAt(convID, maxMsgID), uid, rc, 527 nil, nil) 528 require.NoError(t, err) 529 res := fetchRes.Thread 530 if len(res.Messages) != len(expectedState) { 531 t.Logf("wrong number of messages") 532 for _, m := range res.Messages { 533 t.Logf("msgid:%v type:%v", m.GetMessageID(), m.GetMessageType()) 534 } 535 require.Equal(t, len(expectedState), len(res.Messages), "wrong number of messages") 536 } 537 for i, x := range expectedState { 538 t.Logf("[%v] checking msgID:%v supersededBy:%v", x.Name, x.MsgID, x.SupersededBy) 539 m := res.Messages[len(res.Messages)-1-i] 540 require.True(t, m.IsValid(), "[%v] message should be valid", x.Name) 541 require.Equal(t, x.MsgID, m.Valid().ServerHeader.MessageID, "[%v] message ID", x.Name) 542 if m.GetMessageType() != chat1.MessageType_TLFNAME && !x.BodyPresent && x.SupersededBy == 0 { 543 t.Fatalf("You expected the body to be deleted but the message not to be superseded. Are you sure?") 544 } 545 if x.SupersededBy != dontCare { 546 require.Equal(t, x.SupersededBy, m.Valid().ServerHeader.SupersededBy, "[%v] superseded by", x.Name) 547 } 548 if x.BodyPresent { 549 require.False(t, m.Valid().MessageBody.IsNil(), "[%v] message body should not be deleted", x.Name) 550 } else { 551 require.True(t, m.Valid().MessageBody.IsNil(), "[%v] message body should be deleted", x.Name) 552 } 553 } 554 } 555 merge := func(msgsUnsorted []chat1.MessageUnboxed, expectedDeletedHistory bool) { 556 res := mustMerge(t, storage, convID, uid, SortMessagesDesc(msgsUnsorted)) 557 if expectedDeletedHistory { 558 require.NotNil(t, res.Expunged, "deleted history merge response") 559 } else { 560 require.Nil(t, res.Expunged, "deleted history merge response") 561 } 562 t.Logf("merge complete") 563 } 564 expunge := func(upto chat1.MessageID, expectedDeletedHistory bool) { 565 res, err := storage.Expunge(context.Background(), conv, uid, chat1.Expunge{Upto: upto}) 566 require.NoError(t, err) 567 if expectedDeletedHistory { 568 require.NotNil(t, res.Expunged, "deleted history merge response") 569 } else { 570 require.Nil(t, res.Expunged, "deleted history merge response") 571 } 572 } 573 574 t.Logf("initial merge") 575 // merge with no delh messages 576 merge([]chat1.MessageUnboxed{msgA, msgB, msgC, msgD, msgE, msgF, msgG}, false) 577 setExpected("A", msgA, false, 0) // TLFNAME messages have no body 578 setExpected("B", msgB, true, 0) 579 setExpected("C", msgC, true, msgE.GetMessageID()) 580 setExpected("D", msgD, true, 0) 581 setExpected("E", msgE, true, 0) 582 setExpected("F", msgF, false, msgG.GetMessageID()) 583 setExpected("G", msgG, true, 0) 584 assertState(msgG.GetMessageID()) 585 586 t.Logf("expunge up to E") 587 setExpected("B", msgB, false, dontCare) 588 setExpected("C", msgC, false, dontCare) 589 setExpected("D", msgD, false, dontCare) 590 expunge(msgE.GetMessageID(), true) 591 assertState(msgG.GetMessageID()) 592 593 t.Logf("expunge with no effect") 594 expunge(msgE.GetMessageID(), false) 595 596 t.Logf("another expunge with no effect") 597 expunge(msgC.GetMessageID(), false) 598 599 t.Logf("merge first delh") 600 // merge with one delh 601 merge([]chat1.MessageUnboxed{msgH, msgI, msgJ}, true) 602 setExpected("E", msgE, false, msgJ.GetMessageID()) 603 setExpected("F", msgF, false, msgG.GetMessageID()) 604 setExpected("H", msgH, false, msgJ.GetMessageID()) 605 setExpected("I", msgI, true, 0) // after the cutoff 606 setExpected("J", msgJ, true, 0) 607 assertState(msgJ.GetMessageID()) 608 609 t.Logf("expunge the rest") 610 setExpected("I", msgI, false, dontCare) // after the cutoff 611 expunge(msgJ.GetMessageID()+12, true) 612 } 613 614 func TestStorageMiss(t *testing.T) { 615 tc, storage, uid := setupStorageTest(t, "miss") 616 defer tc.Cleanup() 617 618 msgs := makeMsgRange(10) 619 conv := MakeConversation(15) 620 621 mustMerge(t, storage, conv.Metadata.ConversationID, uid, msgs) 622 _, err := storage.Fetch(context.TODO(), conv, uid, nil, nil, nil) 623 require.Error(t, err, "expected error") 624 require.IsType(t, MissError{}, err, "wrong error type") 625 } 626 627 func TestStoragePagination(t *testing.T) { 628 629 tc, storage, uid := setupStorageTest(t, "basic") 630 defer tc.Cleanup() 631 632 msgs := makeMsgRange(300) 633 conv := MakeConversation(msgs[0].GetMessageID()) 634 mustMerge(t, storage, conv.Metadata.ConversationID, uid, msgs) 635 636 t.Logf("test next input") 637 tp := pager.NewThreadPager() 638 index, err := tp.MakeIndex(MakeText(120, "TestStoragePagination junk text")) 639 require.NoError(t, err) 640 p := chat1.Pagination{ 641 Num: 100, 642 Next: index, 643 } 644 fetchRes, err := storage.Fetch(context.TODO(), conv, uid, nil, nil, &p) 645 require.NoError(t, err) 646 res := fetchRes.Thread 647 require.Equal(t, chat1.MessageID(119), msgs[181].GetMessageID(), "wrong msg id at border") 648 require.Equal(t, 100, len(res.Messages), "wrong amount of messages") 649 for i := 0; i < len(res.Messages); i++ { 650 require.Equal(t, msgs[i+181].GetMessageID(), res.Messages[i].GetMessageID(), "msg mismatch") 651 } 652 p = chat1.Pagination{ 653 Num: 100, 654 Previous: res.Pagination.Previous, 655 } 656 t.Logf("fetching previous from result") 657 fetchRes, err = storage.Fetch(context.TODO(), conv, uid, nil, nil, &p) 658 require.NoError(t, err) 659 res = fetchRes.Thread 660 require.Equal(t, chat1.MessageID(219), msgs[81].GetMessageID(), "wrong msg id at broder") 661 require.Equal(t, 100, len(res.Messages), "wrong amount of messages") 662 for i := 0; i < len(res.Messages); i++ { 663 require.Equal(t, msgs[i+81].GetMessageID(), res.Messages[i].GetMessageID(), "msg mismatch") 664 } 665 666 t.Logf("test prev input") 667 index, err = tp.MakeIndex(MakeText(120, "TestStoragePagination junk text #2")) 668 require.NoError(t, err) 669 p = chat1.Pagination{ 670 Num: 100, 671 Previous: index, 672 } 673 fetchRes, err = storage.Fetch(context.TODO(), conv, uid, nil, nil, &p) 674 require.NoError(t, err) 675 res = fetchRes.Thread 676 require.Equal(t, chat1.MessageID(220), msgs[80].GetMessageID(), "wrong msg id at border") 677 require.Equal(t, 100, len(res.Messages), "wrong amount of messages") 678 for i := 0; i < len(res.Messages); i++ { 679 require.Equal(t, msgs[i+80].GetMessageID(), res.Messages[i].GetMessageID(), "msg mismatch") 680 } 681 p = chat1.Pagination{ 682 Num: 100, 683 Next: res.Pagination.Next, 684 } 685 t.Logf("fetching next from result") 686 fetchRes, err = storage.Fetch(context.TODO(), conv, uid, nil, nil, &p) 687 require.NoError(t, err) 688 res = fetchRes.Thread 689 require.Equal(t, 100, len(res.Messages), "wrong amount of messages") 690 for i := 0; i < len(res.Messages); i++ { 691 require.Equal(t, msgs[i+180].GetMessageID(), res.Messages[i].GetMessageID(), "msg mismatch") 692 } 693 } 694 695 func mkarray(m chat1.MessageUnboxed) []chat1.MessageUnboxed { 696 return []chat1.MessageUnboxed{m} 697 } 698 699 func TestStorageTypeFilter(t *testing.T) { 700 tc, storage, uid := setupStorageTest(t, "basic") 701 defer tc.Cleanup() 702 703 textmsgs := makeMsgRange(300) 704 msgs := append(mkarray(MakeMsgWithType(chat1.MessageID(301), chat1.MessageType_EDIT)), textmsgs...) 705 msgs = append(mkarray(MakeMsgWithType(chat1.MessageID(302), chat1.MessageType_TLFNAME)), msgs...) 706 msgs = append(mkarray(MakeMsgWithType(chat1.MessageID(303), chat1.MessageType_ATTACHMENT)), msgs...) 707 msgs = append(mkarray(MakeMsgWithType(chat1.MessageID(304), chat1.MessageType_TEXT)), msgs...) 708 textmsgs = append(mkarray(MakeMsgWithType(chat1.MessageID(304), chat1.MessageType_TEXT)), textmsgs...) 709 conv := MakeConversation(msgs[0].GetMessageID()) 710 711 query := chat1.GetThreadQuery{ 712 MessageTypes: []chat1.MessageType{chat1.MessageType_TEXT}, 713 } 714 715 mustMerge(t, storage, conv.Metadata.ConversationID, uid, msgs) 716 fetchRes, err := storage.Fetch(context.TODO(), conv, uid, nil, &query, nil) 717 require.NoError(t, err) 718 res := fetchRes.Thread 719 require.Equal(t, len(msgs), len(res.Messages), "wrong amount of messages") 720 restexts := utils.FilterByType(res.Messages, &query, true) 721 require.Equal(t, len(textmsgs), len(restexts), "wrong amount of text messages") 722 for i := 0; i < len(restexts); i++ { 723 require.Equal(t, textmsgs[i].GetMessageID(), restexts[i].GetMessageID(), "msg mismatch") 724 } 725 726 } 727 728 func TestStorageLocalMax(t *testing.T) { 729 tc, storage, uid := setupStorageTest(t, "local-max") 730 defer tc.Cleanup() 731 732 msgs := makeMsgRange(10) 733 conv := MakeConversation(15) 734 735 _, err := storage.FetchUpToLocalMaxMsgID(context.TODO(), conv.Metadata.ConversationID, uid, nil, 0, 736 nil, nil) 737 require.Error(t, err) 738 require.IsType(t, MissError{}, err, "wrong error type") 739 740 mustMerge(t, storage, conv.Metadata.ConversationID, uid, msgs) 741 tv, err := storage.FetchUpToLocalMaxMsgID(context.TODO(), conv.Metadata.ConversationID, uid, nil, 0, 742 nil, nil) 743 require.NoError(t, err) 744 require.Len(t, tv.Thread.Messages, 10) 745 } 746 747 func TestStorageFetchMessages(t *testing.T) { 748 tc, storage, uid := setupStorageTest(t, "fetchMessages") 749 defer tc.Cleanup() 750 751 msgs := makeMsgRange(20) 752 conv := MakeConversation(25) 753 754 mustMerge(t, storage, conv.Metadata.ConversationID, uid, msgs) 755 756 msgIDs := []chat1.MessageID{10, 15, 6} 757 umsgs, err := storage.FetchMessages(context.TODO(), conv.Metadata.ConversationID, uid, msgIDs) 758 require.NoError(t, err) 759 require.Equal(t, len(msgIDs), len(umsgs), "size mismatch") 760 for _, umsg := range umsgs { 761 require.NotNil(t, umsg, "msg not found") 762 } 763 764 msgIDs = []chat1.MessageID{10, 15, 6, 21} 765 umsgs, err = storage.FetchMessages(context.TODO(), conv.Metadata.ConversationID, uid, msgIDs) 766 require.NoError(t, err) 767 require.Equal(t, len(msgIDs), len(umsgs), "size mismatch") 768 nils := 0 769 for _, umsg := range umsgs { 770 if umsg == nil { 771 nils++ 772 } 773 } 774 require.Equal(t, 1, nils, "wrong number of nils") 775 } 776 777 func TestStorageClearMessages(t *testing.T) { 778 tc, storage, uid := setupStorageTest(t, "clearMessages") 779 defer tc.Cleanup() 780 781 msgs := makeMsgRange(20) 782 conv := MakeConversation(20) 783 mustMerge(t, storage, conv.Metadata.ConversationID, uid, msgs) 784 785 ctx := context.TODO() 786 tv, err := storage.Fetch(ctx, conv, uid, nil, nil, nil) 787 require.NoError(t, err) 788 require.Equal(t, 20, len(tv.Thread.Messages)) 789 require.NoError(t, storage.ClearBefore(ctx, conv.GetConvID(), uid, 10)) 790 tv, err = storage.Fetch(ctx, conv, uid, NewInsatiableResultCollector(), nil, nil) 791 require.NoError(t, err) 792 require.Equal(t, 11, len(tv.Thread.Messages)) 793 require.Equal(t, chat1.MessageID(20), tv.Thread.Messages[0].GetMessageID()) 794 require.Equal(t, chat1.MessageID(10), tv.Thread.Messages[len(tv.Thread.Messages)-1].GetMessageID()) 795 } 796 797 func TestStorageServerVersion(t *testing.T) { 798 tc, storage, uid := setupStorageTest(t, "serverVersion") 799 defer tc.Cleanup() 800 801 msgs := makeMsgRange(300) 802 conv := MakeConversation(msgs[0].GetMessageID()) 803 mustMerge(t, storage, conv.Metadata.ConversationID, uid, msgs) 804 res, err := storage.Fetch(context.TODO(), conv, uid, nil, nil, nil) 805 require.NoError(t, err) 806 require.Equal(t, len(msgs), len(res.Thread.Messages)) 807 808 cerr := tc.Context().ServerCacheVersions.Set(context.TODO(), chat1.ServerCacheVers{ 809 BodiesVers: 5, 810 }) 811 require.NoError(t, cerr) 812 813 res, err = storage.Fetch(context.TODO(), conv, uid, nil, nil, nil) 814 require.Error(t, err) 815 require.IsType(t, MissError{}, err) 816 817 mustMerge(t, storage, conv.Metadata.ConversationID, uid, msgs) 818 res, err = storage.Fetch(context.TODO(), conv, uid, nil, nil, nil) 819 require.NoError(t, err) 820 require.Equal(t, len(msgs), len(res.Thread.Messages)) 821 } 822 823 func TestStorageDetectBodyHashReplay(t *testing.T) { 824 tc, _, _ := setupStorageTest(t, "fetchMessages") 825 defer tc.Cleanup() 826 827 // The first time we encounter a body hash it's stored. 828 err := CheckAndRecordBodyHash(context.Background(), tc.Context(), chat1.Hash("foo"), 1, chat1.ConversationID("bar")) 829 require.NoError(t, err) 830 831 // Seeing the same body hash again in the same message is fine. That just 832 // means we uboxed it twice. 833 err = CheckAndRecordBodyHash(context.Background(), tc.Context(), chat1.Hash("foo"), 1, chat1.ConversationID("bar")) 834 require.NoError(t, err) 835 836 // But seeing the hash again with a different convID/msgID is a replay, and 837 // it must trigger an error. 838 err = CheckAndRecordBodyHash(context.Background(), tc.Context(), chat1.Hash("foo"), 1, chat1.ConversationID("bar2")) 839 require.Error(t, err) 840 err = CheckAndRecordBodyHash(context.Background(), tc.Context(), chat1.Hash("foo"), 2, chat1.ConversationID("bar")) 841 require.Error(t, err) 842 } 843 844 func TestStorageDetectPrevPtrInconsistency(t *testing.T) { 845 tc, _, _ := setupStorageTest(t, "fetchMessages") 846 defer tc.Cleanup() 847 848 // The first time we encounter a message ID (either in unboxing or in 849 // another message's prev pointer) its header hash is stored. 850 err := CheckAndRecordPrevPointer(context.Background(), tc.Context(), 1, chat1.ConversationID("bar"), chat1.Hash("foo")) 851 require.NoError(t, err) 852 853 // Seeing the same header hash again in the same message is fine. That just 854 // means we uboxed it twice. 855 err = CheckAndRecordPrevPointer(context.Background(), tc.Context(), 1, chat1.ConversationID("bar"), chat1.Hash("foo")) 856 require.NoError(t, err) 857 858 // But seeing the same convID/msgID with a different header hash is a 859 // consistency violation, and it must trigger an error. 860 err = CheckAndRecordPrevPointer(context.Background(), tc.Context(), 1, chat1.ConversationID("bar"), chat1.Hash("foo2")) 861 require.Error(t, err) 862 } 863 864 func TestStorageMultipleEdits(t *testing.T) { 865 tc, s, uid := setupStorageTest(t, "multiEdits") 866 defer tc.Cleanup() 867 868 msgText := MakeText(1, "initial") 869 edit1 := MakeEdit(2, msgText.GetMessageID()) 870 edit2 := MakeEdit(3, msgText.GetMessageID()) 871 conv := MakeConversation(edit2.GetMessageID()) 872 873 // Merge in text message 874 mustMerge(t, s, conv.GetConvID(), uid, []chat1.MessageUnboxed{msgText}) 875 conv.ReaderInfo.MaxMsgid = msgText.GetMessageID() 876 fetchRes, err := s.Fetch(context.TODO(), conv, uid, nil, nil, nil) 877 require.NoError(t, err) 878 require.Equal(t, 1, len(fetchRes.Thread.Messages)) 879 require.Equal(t, msgText.GetMessageID(), fetchRes.Thread.Messages[0].GetMessageID()) 880 require.Zero(t, fetchRes.Thread.Messages[0].Valid().ServerHeader.SupersededBy) 881 882 // Merge in both edits 883 mustMerge(t, s, conv.GetConvID(), uid, []chat1.MessageUnboxed{edit2, edit1}) 884 conv.ReaderInfo.MaxMsgid = edit2.GetMessageID() 885 fetchRes, err = s.Fetch(context.TODO(), conv, uid, nil, nil, nil) 886 require.NoError(t, err) 887 require.Equal(t, 3, len(fetchRes.Thread.Messages)) 888 require.Equal(t, msgText.GetMessageID(), fetchRes.Thread.Messages[2].GetMessageID()) 889 require.Equal(t, edit2.GetMessageID(), fetchRes.Thread.Messages[2].Valid().ServerHeader.SupersededBy) 890 }