github.com/keybase/client/go@v0.0.0-20240520164431-4f512a4c85a3/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  }