github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/kbfsedits/tlf_history_test.go (about) 1 // Copyright 2018 Keybase Inc. All rights reserved. 2 // Use of this source code is governed by a BSD 3 // license that can be found in the LICENSE file. 4 5 package kbfsedits 6 7 import ( 8 "encoding/json" 9 "sort" 10 "strconv" 11 "testing" 12 "time" 13 14 "github.com/keybase/client/go/kbfs/kbfsmd" 15 "github.com/keybase/client/go/kbfs/tlf" 16 "github.com/keybase/client/go/protocol/keybase1" 17 "github.com/stretchr/testify/require" 18 ) 19 20 func checkTlfHistory(t *testing.T, th *TlfHistory, expected writersByRevision, 21 loggedInUser string) { 22 writersWhoNeedMore := th.Recompute(loggedInUser) 23 history := th.getHistory(loggedInUser) // should use cached history. 24 require.Len(t, history, len(expected)) 25 26 for i, e := range expected { 27 require.Equal(t, e.writerName, history[i].writerName) 28 require.Len(t, history[i].notifications, len(e.notifications)) 29 for j, n := range e.notifications { 30 require.Equal(t, n, history[i].notifications[j], i) 31 } 32 if len(e.notifications) < maxEditsPerWriter { 33 require.Contains(t, writersWhoNeedMore, e.writerName, i) 34 } 35 } 36 } 37 38 type nextNotification struct { 39 nextRevision kbfsmd.Revision 40 numWithin int 41 tlfID tlf.ID 42 nextEvents []NotificationMessage 43 } 44 45 func (nn *nextNotification) makeWithType( 46 filename string, nt NotificationOpType, uid keybase1.UID, 47 params *NotificationParams, now time.Time, 48 entryType EntryType) NotificationMessage { 49 n := NotificationMessage{ 50 Version: NotificationV2, 51 Revision: nn.nextRevision, 52 Filename: filename, 53 Type: nt, 54 FolderID: nn.tlfID, 55 UID: uid, 56 Params: params, 57 FileType: entryType, 58 Time: now, 59 numWithinRevision: nn.numWithin, 60 } 61 nn.numWithin++ 62 nn.nextEvents = append(nn.nextEvents, n) 63 return n 64 } 65 66 func (nn *nextNotification) make( 67 filename string, nt NotificationOpType, uid keybase1.UID, 68 params *NotificationParams, now time.Time) NotificationMessage { 69 return nn.makeWithType(filename, nt, uid, params, now, EntryTypeFile) 70 } 71 72 func (nn *nextNotification) encode(t *testing.T) string { 73 nn.nextRevision++ 74 msg, err := json.Marshal(nn.nextEvents) 75 require.NoError(t, err) 76 nn.nextEvents = nil 77 nn.numWithin = 0 78 return string(msg) 79 } 80 81 func TestTlfHistorySimple(t *testing.T) { 82 aliceName, bobName := "alice", "bob" 83 aliceUID, bobUID := keybase1.MakeTestUID(1), keybase1.MakeTestUID(2) 84 tlfID, err := tlf.MakeRandomID(tlf.Private) 85 require.NoError(t, err) 86 87 nn := nextNotification{1, 0, tlfID, nil} 88 aliceWrite := nn.make("a", NotificationCreate, aliceUID, nil, time.Time{}) 89 aliceMessage := nn.encode(t) 90 bobWrite := nn.make("b", NotificationCreate, bobUID, nil, time.Time{}) 91 bobMessage := nn.encode(t) 92 93 expected := writersByRevision{ 94 {bobName, []NotificationMessage{bobWrite}, nil}, 95 {aliceName, []NotificationMessage{aliceWrite}, nil}, 96 } 97 98 // Alice, then Bob. 99 th := NewTlfHistory() 100 rev, err := th.AddNotifications(aliceName, []string{aliceMessage}) 101 require.NoError(t, err) 102 require.Equal(t, aliceWrite.Revision, rev) 103 rev, err = th.AddNotifications(bobName, []string{bobMessage}) 104 require.NoError(t, err) 105 require.Equal(t, bobWrite.Revision, rev) 106 checkTlfHistory(t, th, expected, aliceName) 107 108 // Bob, then Alice. 109 th = NewTlfHistory() 110 rev, err = th.AddNotifications(bobName, []string{bobMessage}) 111 require.NoError(t, err) 112 require.Equal(t, bobWrite.Revision, rev) 113 rev, err = th.AddNotifications(aliceName, []string{aliceMessage}) 114 require.NoError(t, err) 115 require.Equal(t, aliceWrite.Revision, rev) 116 checkTlfHistory(t, th, expected, aliceName) 117 118 // Add a duplicate notification. 119 _, err = th.AddNotifications(bobName, []string{bobMessage}) 120 require.NoError(t, err) 121 checkTlfHistory(t, th, expected, aliceName) 122 } 123 124 func TestTlfHistoryMultipleWrites(t *testing.T) { 125 aliceName, bobName := "alice", "bob" 126 aliceUID, bobUID := keybase1.MakeTestUID(1), keybase1.MakeTestUID(2) 127 tlfID, err := tlf.MakeRandomID(tlf.Private) 128 require.NoError(t, err) 129 130 var aliceMessages, bobMessages []string 131 nn := nextNotification{1, 0, tlfID, nil} 132 133 // Alice creates and writes to "a". 134 _ = nn.make("a", NotificationCreate, aliceUID, nil, time.Time{}) 135 aliceModA := nn.make("a", NotificationModify, aliceUID, nil, time.Time{}) 136 aliceMessages = append(aliceMessages, nn.encode(t)) 137 138 // Bob creates "b", writes to existing file "c", and writes to "a". 139 bobCreateB := nn.make("b", NotificationCreate, bobUID, nil, time.Time{}) 140 bobModC := nn.make("c", NotificationModify, bobUID, nil, time.Time{}) 141 bobModA := nn.make("a", NotificationModify, bobUID, nil, time.Time{}) 142 bobMessages = append(bobMessages, nn.encode(t)) 143 144 // Alice writes to "c". 145 aliceModC := nn.make("c", NotificationModify, aliceUID, nil, time.Time{}) 146 aliceMessages = append(aliceMessages, nn.encode(t)) 147 148 // Alice writes to "._c", which should be ignored. 149 _ = nn.make( 150 "._c", NotificationModify, aliceUID, nil, time.Time{}) 151 aliceMessages = append(aliceMessages, nn.encode(t)) 152 153 // Alice writes to ".DS_Store (conflicted copy)", which should be ignored. 154 aliceModConflictedC := nn.make( 155 ".DS_Store (conflicted copy)", NotificationModify, aliceUID, nil, 156 time.Time{}) 157 aliceMessages = append(aliceMessages, nn.encode(t)) 158 159 expected := writersByRevision{ 160 {aliceName, []NotificationMessage{aliceModC, aliceModA}, nil}, 161 {bobName, []NotificationMessage{bobModA, bobModC, bobCreateB}, nil}, 162 } 163 164 // Alice, then Bob. 165 th := NewTlfHistory() 166 rev, err := th.AddNotifications(aliceName, aliceMessages) 167 require.NoError(t, err) 168 require.Equal(t, aliceModConflictedC.Revision, rev) 169 rev, err = th.AddNotifications(bobName, bobMessages) 170 require.NoError(t, err) 171 require.Equal(t, bobModA.Revision, rev) 172 checkTlfHistory(t, th, expected, aliceName) 173 174 // Add each message one at a time, alternating users. 175 th = NewTlfHistory() 176 for i := 0; i < len(aliceMessages); i++ { 177 _, err = th.AddNotifications(aliceName, []string{aliceMessages[i]}) 178 require.NoError(t, err) 179 if i < len(bobMessages) { 180 _, err = th.AddNotifications(bobName, []string{bobMessages[i]}) 181 require.NoError(t, err) 182 } 183 } 184 checkTlfHistory(t, th, expected, aliceName) 185 } 186 187 func TestTlfHistoryRenamesAndDeletes(t *testing.T) { 188 aliceName, bobName := "alice", "bob" 189 aliceUID, bobUID := keybase1.MakeTestUID(1), keybase1.MakeTestUID(2) 190 tlfID, err := tlf.MakeRandomID(tlf.Private) 191 require.NoError(t, err) 192 193 var aliceMessages, bobMessages []string 194 nn := nextNotification{1, 0, tlfID, nil} 195 196 // Alice creates modifies "c" (later overwritten). 197 _ = nn.make("c", NotificationCreate, aliceUID, nil, time.Time{}) 198 aliceMessages = append(aliceMessages, nn.encode(t)) 199 200 // Bob modifies "c" (later overwritten). 201 _ = nn.make("c", NotificationModify, bobUID, nil, time.Time{}) 202 bobMessages = append(bobMessages, nn.encode(t)) 203 204 // Alice creates "b". 205 aliceCreateB := nn.make("b", NotificationCreate, aliceUID, nil, time.Time{}) 206 aliceMessages = append(aliceMessages, nn.encode(t)) 207 208 // Bob moves "b" to "c". 209 _ = nn.make("c", NotificationRename, bobUID, &NotificationParams{ 210 OldFilename: "b", 211 }, time.Time{}) 212 bobMessages = append(bobMessages, nn.encode(t)) 213 aliceCreateB.Filename = "c" 214 215 // Alice creates "a". 216 _ = nn.make("a", NotificationCreate, aliceUID, nil, time.Time{}) 217 aliceMessages = append(aliceMessages, nn.encode(t)) 218 219 // Bob modifies "a". 220 _ = nn.make("a", NotificationModify, aliceUID, nil, time.Time{}) 221 bobMessages = append(bobMessages, nn.encode(t)) 222 223 // Alice moves "a" to "b". 224 _ = nn.make("b", NotificationRename, aliceUID, &NotificationParams{ 225 OldFilename: "a", 226 }, time.Time{}) 227 bobMessages = append(bobMessages, nn.encode(t)) 228 229 // Bob creates "a". 230 _ = nn.make("a", NotificationCreate, bobUID, nil, time.Time{}) 231 bobMessages = append(bobMessages, nn.encode(t)) 232 233 // Alice deletes "b". 234 aliceDeleteB := nn.make("b", NotificationDelete, aliceUID, nil, time.Time{}) 235 aliceMessages = append(aliceMessages, nn.encode(t)) 236 237 // Bob modifies "a". 238 bobModA := nn.make("a", NotificationModify, bobUID, nil, time.Time{}) 239 bobMessages = append(bobMessages, nn.encode(t)) 240 241 expected := writersByRevision{ 242 {bobName, []NotificationMessage{bobModA}, nil}, 243 {aliceName, []NotificationMessage{aliceCreateB}, nil}, 244 } 245 246 // Alice, then Bob. 247 th := NewTlfHistory() 248 rev, err := th.AddNotifications(aliceName, aliceMessages) 249 require.NoError(t, err) 250 require.Equal(t, aliceDeleteB.Revision, rev) 251 rev, err = th.AddNotifications(bobName, bobMessages) 252 require.NoError(t, err) 253 require.Equal(t, bobModA.Revision, rev) 254 checkTlfHistory(t, th, expected, aliceName) 255 } 256 257 func TestTlfHistoryNeedsMoreThenComplete(t *testing.T) { 258 aliceName := "alice" 259 aliceUID := keybase1.MakeTestUID(1) 260 tlfID, err := tlf.MakeRandomID(tlf.Private) 261 require.NoError(t, err) 262 263 var allExpected notificationsByRevision 264 265 var aliceMessages []string 266 nn := nextNotification{1, 0, tlfID, nil} 267 for i := 0; i < maxEditsPerWriter; i++ { 268 event := nn.make( 269 strconv.Itoa(i), NotificationCreate, aliceUID, nil, time.Time{}) 270 allExpected = append(allExpected, event) 271 aliceMessages = append(aliceMessages, nn.encode(t)) 272 } 273 sort.Sort(allExpected) 274 275 // Input most recent half of messages first. 276 expected := writersByRevision{ 277 {aliceName, allExpected[:maxEditsPerWriter/2], nil}, 278 } 279 th := NewTlfHistory() 280 rev, err := th.AddNotifications( 281 aliceName, aliceMessages[maxEditsPerWriter/2:]) 282 require.NoError(t, err) 283 require.Equal(t, allExpected[0].Revision, rev) 284 checkTlfHistory(t, th, expected, aliceName) 285 286 // Then input the rest, and we'll have a complete set. 287 rev, err = th.AddNotifications( 288 aliceName, aliceMessages[:maxEditsPerWriter/2]) 289 require.NoError(t, err) 290 require.Equal(t, allExpected[0].Revision, rev) 291 expected = writersByRevision{ 292 {aliceName, allExpected, nil}, 293 } 294 checkTlfHistory(t, th, expected, aliceName) 295 } 296 297 func TestTlfHistoryTrimming(t *testing.T) { 298 aliceName := "alice" 299 aliceUID := keybase1.MakeTestUID(1) 300 tlfID, err := tlf.MakeRandomID(tlf.Private) 301 require.NoError(t, err) 302 303 var allExpected notificationsByRevision 304 305 var aliceMessages []string 306 nn := nextNotification{1, 0, tlfID, nil} 307 for i := 0; i < maxEditsPerWriter+2; i++ { 308 event := nn.make(strconv.Itoa(i), NotificationCreate, aliceUID, nil, 309 time.Time{}) 310 allExpected = append(allExpected, event) 311 aliceMessages = append(aliceMessages, nn.encode(t)) 312 } 313 sort.Sort(allExpected) 314 315 // Input the max+1. 316 expected := writersByRevision{ 317 {aliceName, allExpected[1 : maxEditsPerWriter+1], nil}, 318 } 319 th := NewTlfHistory() 320 rev, err := th.AddNotifications( 321 aliceName, aliceMessages[:maxEditsPerWriter+1]) 322 require.NoError(t, err) 323 require.Equal(t, allExpected[1].Revision, rev) 324 checkTlfHistory(t, th, expected, aliceName) 325 326 // Then input the last one, and make sure the correct item was trimmed. 327 rev, err = th.AddNotifications( 328 aliceName, aliceMessages[maxEditsPerWriter+1:]) 329 require.NoError(t, err) 330 require.Equal(t, allExpected[0].Revision, rev) 331 expected = writersByRevision{ 332 {aliceName, allExpected[:maxEditsPerWriter], nil}, 333 } 334 checkTlfHistory(t, th, expected, aliceName) 335 } 336 337 func TestTlfHistoryWithUnflushed(t *testing.T) { 338 aliceName, bobName := "alice", "bob" 339 aliceUID, bobUID := keybase1.MakeTestUID(1), keybase1.MakeTestUID(2) 340 tlfID, err := tlf.MakeRandomID(tlf.Private) 341 require.NoError(t, err) 342 nn := nextNotification{1, 0, tlfID, nil} 343 344 aliceWrite1 := nn.make("a", NotificationCreate, aliceUID, nil, time.Time{}) 345 aliceMessage1 := nn.encode(t) 346 bobWrite2 := nn.make("b", NotificationCreate, bobUID, nil, time.Time{}) 347 bobMessage2 := nn.encode(t) 348 349 th := NewTlfHistory() 350 rev, err := th.AddNotifications(aliceName, []string{aliceMessage1}) 351 require.NoError(t, err) 352 require.Equal(t, aliceWrite1.Revision, rev) 353 rev, err = th.AddNotifications(bobName, []string{bobMessage2}) 354 require.NoError(t, err) 355 require.Equal(t, bobWrite2.Revision, rev) 356 357 // Alice takes over revision 2 with a few more unflushed writes. 358 nn.nextRevision-- 359 aliceWrite2 := nn.make("c", NotificationCreate, aliceUID, nil, time.Time{}) 360 _ = nn.encode(t) 361 aliceWrite3 := nn.make("d", NotificationCreate, aliceUID, nil, time.Time{}) 362 _ = nn.encode(t) 363 th.AddUnflushedNotifications( 364 aliceName, []NotificationMessage{aliceWrite2, aliceWrite3}) 365 366 expected := writersByRevision{ 367 {aliceName, []NotificationMessage{ 368 aliceWrite3, 369 aliceWrite2, 370 aliceWrite1}, 371 nil, 372 }, 373 } 374 checkTlfHistory(t, th, expected, aliceName) 375 376 th.FlushRevision(2) 377 expected = writersByRevision{ 378 {aliceName, []NotificationMessage{ 379 aliceWrite3, 380 aliceWrite1}, 381 nil, 382 }, 383 {bobName, []NotificationMessage{bobWrite2}, nil}, 384 } 385 checkTlfHistory(t, th, expected, aliceName) 386 387 th.ClearAllUnflushed() 388 expected = writersByRevision{ 389 {bobName, []NotificationMessage{bobWrite2}, nil}, 390 {aliceName, []NotificationMessage{aliceWrite1}, nil}, 391 } 392 checkTlfHistory(t, th, expected, aliceName) 393 } 394 395 func TestTlfHistoryRenameParentSimple(t *testing.T) { 396 aliceName, bobName := "alice", "bob" 397 aliceUID, bobUID := keybase1.MakeTestUID(1), keybase1.MakeTestUID(2) 398 tlfID, err := tlf.MakeRandomID(tlf.Private) 399 require.NoError(t, err) 400 401 var aliceMessages, bobMessages []string 402 nn := nextNotification{1, 0, tlfID, nil} 403 404 // Alice creates modifies "a/b". (Use truncated Keybase canonical 405 // paths because renames only matter beyond the TLF name.) 406 aliceModifyB := nn.make( 407 "/k/p/a,b/a/b", NotificationModify, aliceUID, nil, time.Time{}) 408 aliceMessages = append(aliceMessages, nn.encode(t)) 409 410 // Bob renames "a" to "c". 411 bobRename := nn.makeWithType( 412 "/k/p/a,b/c", NotificationRename, bobUID, &NotificationParams{ 413 OldFilename: "/k/p/a,b/a", 414 }, time.Time{}, EntryTypeDir) 415 bobMessages = append(bobMessages, nn.encode(t)) 416 aliceModifyB.Filename = "/k/p/a,b/c/b" 417 418 expected := writersByRevision{ 419 {aliceName, []NotificationMessage{aliceModifyB}, nil}, 420 } 421 422 // Alice, then Bob. 423 th := NewTlfHistory() 424 rev, err := th.AddNotifications(aliceName, aliceMessages) 425 require.NoError(t, err) 426 require.Equal(t, aliceModifyB.Revision, rev) 427 rev, err = th.AddNotifications(bobName, bobMessages) 428 require.NoError(t, err) 429 require.Equal(t, bobRename.Revision, rev) 430 checkTlfHistory(t, th, expected, aliceName) 431 432 aliceDeleteB := nn.make( 433 "/k/p/a,b/c/b", NotificationDelete, aliceUID, nil, time.Time{}) 434 aliceMessages = append(aliceMessages, nn.encode(t)) 435 _, err = th.AddNotifications(aliceName, aliceMessages) 436 require.NoError(t, err) 437 expected = writersByRevision{ 438 {aliceName, nil, []NotificationMessage{aliceDeleteB}}, 439 } 440 checkTlfHistory(t, th, expected, aliceName) 441 } 442 443 // Regression test for HOTPOT-616. 444 func TestTlfHistoryRenameDirAndReuseNameForFile(t *testing.T) { 445 aliceName, bobName := "alice", "bob" 446 aliceUID, bobUID := keybase1.MakeTestUID(1), keybase1.MakeTestUID(2) 447 tlfID, err := tlf.MakeRandomID(tlf.Private) 448 require.NoError(t, err) 449 450 var aliceMessages, bobMessages []string 451 nn := nextNotification{1, 0, tlfID, nil} 452 453 // Alice creates file "x". (Use truncated Keybase canonical 454 // paths because renames only matter beyond the TLF name.) 455 aliceCreateA := nn.make( 456 "/k/p/a,b/x", NotificationCreate, aliceUID, nil, time.Time{}) 457 aliceMessages = append(aliceMessages, nn.encode(t)) 458 459 // Alice modifies existing file "a/b". 460 aliceModifyB := nn.make( 461 "/k/p/a,b/a/b", NotificationModify, aliceUID, nil, time.Time{}) 462 aliceMessages = append(aliceMessages, nn.encode(t)) 463 464 // Bob renames "a" to "c". 465 bobRename := nn.makeWithType( 466 "/k/p/a,b/c", NotificationRename, bobUID, &NotificationParams{ 467 OldFilename: "/k/p/a,b/a", 468 }, time.Time{}, EntryTypeDir) 469 bobMessages = append(bobMessages, nn.encode(t)) 470 aliceModifyB.Filename = "/k/p/a,b/c/b" 471 472 // Alice renames "x" to "a". 473 _ = nn.makeWithType( 474 "/k/p/a,b/a", NotificationRename, aliceUID, &NotificationParams{ 475 OldFilename: "/k/p/a,b/x", 476 }, time.Time{}, EntryTypeFile) 477 aliceMessages = append(aliceMessages, nn.encode(t)) 478 aliceCreateA.Filename = "/k/p/a,b/a" 479 480 // Alice modifies file "a". 481 aliceModifyA := nn.make( 482 "/k/p/a,b/a", NotificationModify, aliceUID, nil, time.Time{}) 483 aliceMessages = append(aliceMessages, nn.encode(t)) 484 485 expected := writersByRevision{ 486 {aliceName, []NotificationMessage{aliceModifyA, aliceModifyB}, nil}, 487 } 488 489 // Alice, then Bob. 490 th := NewTlfHistory() 491 rev, err := th.AddNotifications(aliceName, aliceMessages) 492 require.NoError(t, err) 493 require.Equal(t, aliceModifyA.Revision, rev) 494 rev, err = th.AddNotifications(bobName, bobMessages) 495 require.NoError(t, err) 496 require.Equal(t, bobRename.Revision, rev) 497 checkTlfHistory(t, th, expected, aliceName) 498 } 499 500 func TestTlfHistoryDeleteHistory(t *testing.T) { 501 aliceName, bobName := "alice", "bob" 502 aliceUID, bobUID := keybase1.MakeTestUID(1), keybase1.MakeTestUID(2) 503 tlfID, err := tlf.MakeRandomID(tlf.Private) 504 require.NoError(t, err) 505 506 var aliceMessages, bobMessages []string 507 nn := nextNotification{1, 0, tlfID, nil} 508 509 // Alice and bob each delete one file, then create different files. 510 aliceDeleteA := nn.make("a", NotificationDelete, aliceUID, nil, time.Time{}) 511 aliceMessages = append(aliceMessages, nn.encode(t)) 512 bobDeleteB := nn.make("b", NotificationDelete, bobUID, nil, time.Time{}) 513 bobMessages = append(bobMessages, nn.encode(t)) 514 515 aliceWrite := nn.make("c", NotificationCreate, aliceUID, nil, time.Time{}) 516 aliceMessages = append(aliceMessages, nn.encode(t)) 517 bobWrite := nn.make("d", NotificationCreate, bobUID, nil, time.Time{}) 518 bobMessages = append(bobMessages, nn.encode(t)) 519 520 expected := writersByRevision{ 521 {bobName, 522 []NotificationMessage{bobWrite}, 523 []NotificationMessage{bobDeleteB}, 524 }, 525 {aliceName, 526 []NotificationMessage{aliceWrite}, 527 []NotificationMessage{aliceDeleteA}, 528 }, 529 } 530 531 // Alice, then Bob. 532 th := NewTlfHistory() 533 rev, err := th.AddNotifications(aliceName, aliceMessages) 534 require.NoError(t, err) 535 require.Equal(t, aliceWrite.Revision, rev) 536 rev, err = th.AddNotifications(bobName, bobMessages) 537 require.NoError(t, err) 538 require.Equal(t, bobWrite.Revision, rev) 539 checkTlfHistory(t, th, expected, aliceName) 540 541 // Another delete from alice, which should change the order of the 542 // expected history (bob should still come first). 543 aliceDeleteE := nn.make("e", NotificationDelete, aliceUID, nil, time.Time{}) 544 aliceMessages = append(aliceMessages, nn.encode(t)) 545 expected[1].deletes = []NotificationMessage{aliceDeleteE, aliceDeleteA} 546 rev, err = th.AddNotifications(aliceName, aliceMessages) 547 require.NoError(t, err) 548 require.Equal(t, aliceDeleteE.Revision, rev) 549 checkTlfHistory(t, th, expected, aliceName) 550 551 // Now add > 10 writes each, to make sure the deletes remain in 552 // the history. 553 var allAliceExpected, allBobExpected notificationsByRevision 554 for i := 0; i < 2*(maxEditsPerWriter+1); i += 2 { 555 event := nn.make( 556 strconv.Itoa(i), NotificationCreate, aliceUID, nil, time.Time{}) 557 allAliceExpected = append(allAliceExpected, event) 558 aliceMessages = append(aliceMessages, nn.encode(t)) 559 event = nn.make( 560 strconv.Itoa(i+1), NotificationCreate, bobUID, nil, time.Time{}) 561 allBobExpected = append(allBobExpected, event) 562 bobMessages = append(bobMessages, nn.encode(t)) 563 } 564 sort.Sort(allAliceExpected) 565 sort.Sort(allBobExpected) 566 567 expected[0].notifications = allBobExpected[:maxEditsPerWriter] 568 expected[1].notifications = allAliceExpected[:maxEditsPerWriter] 569 rev, err = th.AddNotifications(aliceName, aliceMessages) 570 require.NoError(t, err) 571 require.Equal(t, allAliceExpected[0].Revision, rev) 572 rev, err = th.AddNotifications(bobName, bobMessages) 573 require.NoError(t, err) 574 require.Equal(t, allBobExpected[0].Revision, rev) 575 checkTlfHistory(t, th, expected, aliceName) 576 577 // Re-creating a deleted file should remove the delete. 578 aliceRecreateA := nn.make( 579 "a", NotificationCreate, aliceUID, nil, time.Time{}) 580 aliceMessages = append(aliceMessages, nn.encode(t)) 581 582 expected = writersByRevision{ 583 {aliceName, 584 append([]NotificationMessage{aliceRecreateA}, 585 allAliceExpected[:maxEditsPerWriter-1]...), 586 []NotificationMessage{aliceDeleteE}, 587 }, 588 expected[0], 589 } 590 rev, err = th.AddNotifications(aliceName, aliceMessages) 591 require.NoError(t, err) 592 require.Equal(t, aliceRecreateA.Revision, rev) 593 checkTlfHistory(t, th, expected, aliceName) 594 595 // Max out the deletes for alice. 596 var allAliceDeletesExpected notificationsByRevision 597 for i := 0; i < 2*(maxEditsPerWriter+1); i += 2 { 598 event := nn.make( 599 strconv.Itoa(i), NotificationDelete, aliceUID, nil, time.Time{}) 600 allAliceDeletesExpected = append(allAliceDeletesExpected, event) 601 aliceMessages = append(aliceMessages, nn.encode(t)) 602 } 603 sort.Sort(allAliceDeletesExpected) 604 605 expected = writersByRevision{ 606 {aliceName, 607 []NotificationMessage{aliceRecreateA, aliceWrite}, 608 allAliceDeletesExpected[:maxDeletesPerWriter], 609 }, 610 expected[1], 611 } 612 rev, err = th.AddNotifications(aliceName, aliceMessages) 613 require.NoError(t, err) 614 require.Equal(t, allAliceDeletesExpected[0].Revision, rev) 615 checkTlfHistory(t, th, expected, aliceName) 616 } 617 618 // Regression test for HOTPOT-856. 619 func TestTlfHistoryComplexRename(t *testing.T) { 620 aliceName := "alice" 621 aliceUID := keybase1.MakeTestUID(1) 622 tlfID, err := tlf.MakeRandomID(tlf.Private) 623 require.NoError(t, err) 624 625 var aliceMessages []string 626 nn := nextNotification{1, 0, tlfID, nil} 627 628 // Alice creates "a", and adds a file to it. 629 _ = nn.makeWithType( 630 "/k/p/a/a", NotificationCreate, aliceUID, nil, time.Time{}, 631 EntryTypeDir) 632 aliceMessages = append(aliceMessages, nn.encode(t)) 633 fooCreate := nn.make( 634 "/k/p/a/a/foo", NotificationCreate, aliceUID, nil, time.Time{}) 635 aliceMessages = append(aliceMessages, nn.encode(t)) 636 637 // Alice renames "a" to "b". 638 _ = nn.makeWithType( 639 "/k/p/a/b", NotificationRename, aliceUID, &NotificationParams{ 640 OldFilename: "/k/p/a/a", 641 }, time.Time{}, EntryTypeDir) 642 aliceMessages = append(aliceMessages, nn.encode(t)) 643 644 // Alice makes new dir "c". 645 _ = nn.makeWithType( 646 "/k/p/a/c", NotificationCreate, aliceUID, nil, time.Time{}, 647 EntryTypeDir) 648 aliceMessages = append(aliceMessages, nn.encode(t)) 649 650 // Alice renames "c" to "a". 651 _ = nn.makeWithType( 652 "/k/p/a/a", NotificationRename, aliceUID, &NotificationParams{ 653 OldFilename: "/k/p/a/c", 654 }, time.Time{}, EntryTypeDir) 655 aliceMessages = append(aliceMessages, nn.encode(t)) 656 657 // Alice renames "b" to "a/d". 658 bRename := nn.makeWithType( 659 "/k/p/a/a/d", NotificationRename, aliceUID, &NotificationParams{ 660 OldFilename: "/k/p/a/b", 661 }, time.Time{}, EntryTypeDir) 662 aliceMessages = append(aliceMessages, nn.encode(t)) 663 fooCreate.Filename = "/k/p/a/a/d/foo" 664 665 expected := writersByRevision{ 666 {aliceName, 667 []NotificationMessage{fooCreate}, 668 nil, 669 }, 670 } 671 672 th := NewTlfHistory() 673 rev, err := th.AddNotifications(aliceName, aliceMessages) 674 require.NoError(t, err) 675 require.Equal(t, bRename.Revision, rev) 676 checkTlfHistory(t, th, expected, aliceName) 677 }