github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/test/edit_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 test
     6  
     7  import (
     8  	"fmt"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/keybase/client/go/protocol/keybase1"
    13  )
    14  
    15  func TestEditHistorySimple(t *testing.T) {
    16  	// Bob writes one file.
    17  	expectedEdits1 := []expectedEdit{
    18  		{
    19  			"alice,bob",
    20  			keybase1.FolderType_PRIVATE,
    21  			"bob",
    22  			[]string{"/keybase/private/alice,bob/a/b"},
    23  			nil,
    24  		},
    25  	}
    26  	// Alice writes one file.
    27  	expectedEdits2 := []expectedEdit{
    28  		{
    29  			"alice,bob",
    30  			keybase1.FolderType_PRIVATE,
    31  			"alice",
    32  			[]string{"/keybase/private/alice,bob/a/c"},
    33  			nil,
    34  		},
    35  		expectedEdits1[0],
    36  	}
    37  	// Bob overwrites his first file.
    38  	expectedEdits3 := []expectedEdit{
    39  		expectedEdits1[0],
    40  		expectedEdits2[0],
    41  	}
    42  	// Alice deletes the file she wrote.
    43  	expectedEdits4 := []expectedEdit{
    44  		expectedEdits3[0],
    45  		{
    46  			"alice,bob",
    47  			keybase1.FolderType_PRIVATE,
    48  			"alice",
    49  			nil,
    50  			[]string{"/keybase/private/alice,bob/a/c"},
    51  		},
    52  	}
    53  
    54  	test(t,
    55  		users("alice", "bob"),
    56  		as(alice,
    57  			mkdir("a"),
    58  		),
    59  		as(bob,
    60  			mkfile("a/b", "hello"),
    61  		),
    62  		as(bob,
    63  			checkUserEditHistory(expectedEdits1),
    64  		),
    65  		as(alice,
    66  			checkUserEditHistory(expectedEdits1),
    67  		),
    68  		as(alice,
    69  			addTime(1*time.Minute),
    70  			mkfile("a/c", "hello2"),
    71  		),
    72  		as(alice,
    73  			checkUserEditHistory(expectedEdits2),
    74  		),
    75  		as(bob,
    76  			checkUserEditHistory(expectedEdits2),
    77  		),
    78  		as(bob,
    79  			addTime(1*time.Minute),
    80  			write("a/b", "hello again"),
    81  		),
    82  		as(bob,
    83  			checkUserEditHistory(expectedEdits3),
    84  		),
    85  		as(alice,
    86  			checkUserEditHistory(expectedEdits3),
    87  		),
    88  		as(alice,
    89  			addTime(1*time.Minute),
    90  			rm("a/c"),
    91  		),
    92  		as(alice,
    93  			checkUserEditHistory(expectedEdits4),
    94  		),
    95  		as(bob,
    96  			checkUserEditHistory(expectedEdits4),
    97  		),
    98  	)
    99  }
   100  
   101  func TestEditHistoryMultiTlf(t *testing.T) {
   102  	// Bob writes one file to private.
   103  	expectedEdits1 := []expectedEdit{
   104  		{
   105  			"alice,bob",
   106  			keybase1.FolderType_PRIVATE,
   107  			"bob",
   108  			[]string{"/keybase/private/alice,bob/a"},
   109  			nil,
   110  		},
   111  	}
   112  	// Alice writes one file to public.
   113  	expectedEdits2 := []expectedEdit{
   114  		{
   115  			"alice,bob",
   116  			keybase1.FolderType_PUBLIC,
   117  			"alice",
   118  			[]string{"/keybase/public/alice,bob/b"},
   119  			nil,
   120  		},
   121  		expectedEdits1[0],
   122  	}
   123  	// Bob writes one file to team TLF.
   124  	expectedEdits3 := []expectedEdit{
   125  		{
   126  			"ab",
   127  			keybase1.FolderType_TEAM,
   128  			"bob",
   129  			[]string{"/keybase/team/ab/c"},
   130  			nil,
   131  		},
   132  		expectedEdits2[0],
   133  		expectedEdits1[0],
   134  	}
   135  
   136  	test(t,
   137  		users("alice", "bob"),
   138  		team("ab", "alice,bob", ""),
   139  		as(bob,
   140  			mkfile("a", "hello"),
   141  		),
   142  		as(bob,
   143  			checkUserEditHistory(expectedEdits1),
   144  		),
   145  		as(alice,
   146  			checkUserEditHistory(expectedEdits1),
   147  		),
   148  		inPublicTlf("alice,bob"),
   149  		as(alice,
   150  			addTime(1*time.Minute),
   151  			mkfile("b", "hello"),
   152  		),
   153  		as(alice,
   154  			checkUserEditHistory(expectedEdits2),
   155  		),
   156  		as(bob,
   157  			checkUserEditHistory(expectedEdits2),
   158  		),
   159  		inSingleTeamTlf("ab"),
   160  		as(bob,
   161  			addTime(1*time.Minute),
   162  			write("c", "hello again"),
   163  		),
   164  		as(bob,
   165  			checkUserEditHistory(expectedEdits3),
   166  		),
   167  		as(alice,
   168  			checkUserEditHistory(expectedEdits3),
   169  		),
   170  	)
   171  }
   172  
   173  func TestEditHistorySelfClusters(t *testing.T) {
   174  	// Bob writes one file to private.
   175  	expectedEdits1 := []expectedEdit{
   176  		{
   177  			"alice,bob",
   178  			keybase1.FolderType_PRIVATE,
   179  			"bob",
   180  			[]string{"/keybase/private/alice,bob/a"},
   181  			nil,
   182  		},
   183  	}
   184  	// Alice writes to ten team TLFs, but bob should still see his own
   185  	// write from above.
   186  	expectedEdits2Alice := make([]expectedEdit, 0, 10)
   187  	expectedEdits2Bob := make([]expectedEdit, 0, 10)
   188  	for i := 9; i >= 0; i-- {
   189  		team := fmt.Sprintf("ab%d", i)
   190  		e := expectedEdit{
   191  			team,
   192  			keybase1.FolderType_TEAM,
   193  			"alice",
   194  			[]string{fmt.Sprintf("/keybase/team/%s/a", team)},
   195  			nil,
   196  		}
   197  		expectedEdits2Alice = append(expectedEdits2Alice, e)
   198  		expectedEdits2Bob = append(expectedEdits2Bob, e)
   199  	}
   200  	expectedEdits2Bob[9] = expectedEdits1[0]
   201  
   202  	test(t,
   203  		users("alice", "bob"),
   204  		team("ab0", "alice,bob", ""),
   205  		team("ab1", "alice,bob", ""),
   206  		team("ab2", "alice,bob", ""),
   207  		team("ab3", "alice,bob", ""),
   208  		team("ab4", "alice,bob", ""),
   209  		team("ab5", "alice,bob", ""),
   210  		team("ab6", "alice,bob", ""),
   211  		team("ab7", "alice,bob", ""),
   212  		team("ab8", "alice,bob", ""),
   213  		team("ab9", "alice,bob", ""),
   214  		as(bob,
   215  			mkfile("a", "hello"),
   216  		),
   217  		as(bob,
   218  			checkUserEditHistory(expectedEdits1),
   219  		),
   220  		as(alice,
   221  			checkUserEditHistory(expectedEdits1),
   222  		),
   223  		inSingleTeamTlf("ab0"),
   224  		as(alice,
   225  			addTime(1*time.Minute),
   226  			mkfile("a", "hello"),
   227  		),
   228  		inSingleTeamTlf("ab1"),
   229  		as(alice,
   230  			addTime(1*time.Minute),
   231  			mkfile("a", "hello"),
   232  		),
   233  		inSingleTeamTlf("ab2"),
   234  		as(alice,
   235  			addTime(1*time.Minute),
   236  			mkfile("a", "hello"),
   237  		),
   238  		inSingleTeamTlf("ab3"),
   239  		as(alice,
   240  			addTime(1*time.Minute),
   241  			mkfile("a", "hello"),
   242  		),
   243  		inSingleTeamTlf("ab4"),
   244  		as(alice,
   245  			addTime(1*time.Minute),
   246  			mkfile("a", "hello"),
   247  		),
   248  		inSingleTeamTlf("ab5"),
   249  		as(alice,
   250  			addTime(1*time.Minute),
   251  			mkfile("a", "hello"),
   252  		),
   253  		inSingleTeamTlf("ab6"),
   254  		as(alice,
   255  			addTime(1*time.Minute),
   256  			mkfile("a", "hello"),
   257  		),
   258  		inSingleTeamTlf("ab7"),
   259  		as(alice,
   260  			addTime(1*time.Minute),
   261  			mkfile("a", "hello"),
   262  		),
   263  		inSingleTeamTlf("ab8"),
   264  		as(alice,
   265  			addTime(1*time.Minute),
   266  			mkfile("a", "hello"),
   267  		),
   268  		inSingleTeamTlf("ab9"),
   269  		as(alice,
   270  			addTime(1*time.Minute),
   271  			mkfile("a", "hello"),
   272  		),
   273  		as(alice,
   274  			checkUserEditHistory(expectedEdits2Alice),
   275  		),
   276  		as(bob,
   277  			checkUserEditHistory(expectedEdits2Bob),
   278  		),
   279  	)
   280  }
   281  
   282  func TestEditHistoryUnflushed(t *testing.T) {
   283  	// Bob writes one file.
   284  	expectedEdits1 := []expectedEdit{
   285  		{
   286  			"alice,bob",
   287  			keybase1.FolderType_PRIVATE,
   288  			"bob",
   289  			[]string{"/keybase/private/alice,bob/a/b"},
   290  			nil,
   291  		},
   292  	}
   293  	// Alice and Bob both write a second file, but alice's is unflushed.
   294  	expectedEdits2Alice := []expectedEdit{
   295  		{
   296  			"alice,bob",
   297  			keybase1.FolderType_PRIVATE,
   298  			"alice",
   299  			[]string{"/keybase/private/alice,bob/a/c"},
   300  			nil,
   301  		},
   302  		expectedEdits1[0],
   303  	}
   304  	expectedEdits2Bob := []expectedEdit{
   305  		{
   306  			"alice,bob",
   307  			keybase1.FolderType_PRIVATE,
   308  			"bob",
   309  			[]string{
   310  				"/keybase/private/alice,bob/a/d",
   311  				"/keybase/private/alice,bob/a/b",
   312  			},
   313  			nil,
   314  		},
   315  	}
   316  	// Alice runs CR and flushes her journal.
   317  	expectedEdits3 := []expectedEdit{
   318  		expectedEdits2Alice[0],
   319  		expectedEdits2Bob[0],
   320  	}
   321  
   322  	expectedEdits4 := []expectedEdit{
   323  		{
   324  			"alice,bob",
   325  			keybase1.FolderType_PRIVATE,
   326  			"alice",
   327  			nil,
   328  			[]string{
   329  				"/keybase/private/alice,bob/a/d",
   330  				"/keybase/private/alice,bob/a/c",
   331  				"/keybase/private/alice,bob/a/b",
   332  			},
   333  		},
   334  	}
   335  
   336  	test(t, journal(),
   337  		users("alice", "bob"),
   338  		as(alice,
   339  			mkdir("a"),
   340  		),
   341  		as(alice,
   342  			enableJournal(),
   343  		),
   344  		as(bob,
   345  			mkfile("a/b", "hello"),
   346  		),
   347  		as(bob,
   348  			checkUserEditHistory(expectedEdits1),
   349  		),
   350  		as(alice,
   351  			checkUserEditHistory(expectedEdits1),
   352  		),
   353  		as(alice,
   354  			pauseJournal(),
   355  			addTime(1*time.Minute),
   356  			mkfile("a/c", "hello2"),
   357  		),
   358  		as(bob,
   359  			addTime(1*time.Minute),
   360  			mkfile("a/d", "hello"),
   361  		),
   362  		as(bob,
   363  			checkUserEditHistory(expectedEdits2Bob),
   364  		),
   365  		as(alice, noSync(),
   366  			checkUserEditHistory(expectedEdits2Alice),
   367  		),
   368  		as(alice, noSync(),
   369  			resumeJournal(),
   370  			// This should kick off conflict resolution.
   371  			flushJournal(),
   372  		),
   373  		as(alice,
   374  			// Extra flush to make sure the edit history messages have
   375  			// been received by all users.
   376  			flushJournal(),
   377  		),
   378  		as(alice,
   379  			checkUserEditHistory(expectedEdits3),
   380  		),
   381  		as(bob,
   382  			checkUserEditHistory(expectedEdits3),
   383  		),
   384  		as(alice,
   385  			pauseJournal(),
   386  			addTime(1*time.Minute),
   387  			rm("a/b"),
   388  			rm("a/c"),
   389  			rm("a/d"),
   390  			rmdir("a"),
   391  		),
   392  		as(alice,
   393  			checkUnflushedPaths([]string{
   394  				"/keybase/private/alice,bob",
   395  				"/keybase/private/alice,bob/a",
   396  			}),
   397  		),
   398  		as(alice, noSync(),
   399  			resumeJournal(),
   400  			flushJournal(),
   401  		),
   402  		as(alice,
   403  			checkUserEditHistory(expectedEdits4),
   404  		),
   405  		as(bob,
   406  			checkUserEditHistory(expectedEdits4),
   407  		),
   408  	)
   409  }
   410  
   411  func TestEditHistoryRenameParent(t *testing.T) {
   412  	// Bob writes one file, and alice renames the parent dir.
   413  	expectedEdits := []expectedEdit{
   414  		{
   415  			"alice,bob",
   416  			keybase1.FolderType_PRIVATE,
   417  			"bob",
   418  			[]string{"/keybase/private/alice,bob/c/b"},
   419  			nil,
   420  		},
   421  	}
   422  
   423  	expectedEdits2 := []expectedEdit{
   424  		{
   425  			"alice,bob",
   426  			keybase1.FolderType_PRIVATE,
   427  			"alice",
   428  			nil,
   429  			[]string{"/keybase/private/alice,bob/c/b"},
   430  		},
   431  	}
   432  
   433  	test(t,
   434  		users("alice", "bob"),
   435  		as(alice,
   436  			mkdir("a"),
   437  		),
   438  		as(bob,
   439  			mkfile("a/b", "hello"),
   440  		),
   441  		as(alice,
   442  			addTime(1*time.Minute),
   443  			rename("a", "c"),
   444  		),
   445  		as(alice,
   446  			checkUserEditHistory(expectedEdits),
   447  		),
   448  		as(bob,
   449  			checkUserEditHistory(expectedEdits),
   450  		),
   451  		as(alice,
   452  			addTime(1*time.Minute),
   453  			rm("c/b"),
   454  		),
   455  		as(alice,
   456  			checkUserEditHistory(expectedEdits2),
   457  		),
   458  		as(bob,
   459  			checkUserEditHistory(expectedEdits2),
   460  		),
   461  	)
   462  }
   463  
   464  func TestEditHistoryRenameParentAcrossDirs(t *testing.T) {
   465  	// Bob writes one file, and alice renames the parent dir into a
   466  	// different subdirectory.
   467  	expectedEdits := []expectedEdit{
   468  		{
   469  			"alice,bob",
   470  			keybase1.FolderType_PRIVATE,
   471  			"bob",
   472  			[]string{"/keybase/private/alice,bob/d/c/b"},
   473  			nil,
   474  		},
   475  	}
   476  
   477  	expectedEdits2 := []expectedEdit{
   478  		{
   479  			"alice,bob",
   480  			keybase1.FolderType_PRIVATE,
   481  			"alice",
   482  			nil,
   483  			[]string{"/keybase/private/alice,bob/d/c/b"},
   484  		},
   485  	}
   486  
   487  	test(t,
   488  		users("alice", "bob"),
   489  		as(alice,
   490  			mkdir("a"),
   491  			mkdir("d"),
   492  		),
   493  		as(bob,
   494  			mkfile("a/b", "hello"),
   495  		),
   496  		as(alice,
   497  			addTime(1*time.Minute),
   498  			rename("a", "d/c"),
   499  		),
   500  		as(alice,
   501  			checkUserEditHistory(expectedEdits),
   502  		),
   503  		as(bob,
   504  			checkUserEditHistory(expectedEdits),
   505  		),
   506  		as(alice,
   507  			addTime(1*time.Minute),
   508  			rm("d/c/b"),
   509  		),
   510  		as(alice,
   511  			checkUserEditHistory(expectedEdits2),
   512  		),
   513  		as(bob,
   514  			checkUserEditHistory(expectedEdits2),
   515  		),
   516  	)
   517  }
   518  
   519  // Regression test for HOTPOT-616.
   520  func TestEditHistoryRenameDirAndReuseNameForFile(t *testing.T) {
   521  	// Alice creates a dir and puts files in it, creates a file,
   522  	// renames the dir to something new, renames the file to
   523  	// the old dir name, and then nukes the old directory.
   524  	expectedEdits := []expectedEdit{
   525  		{
   526  			"alice",
   527  			keybase1.FolderType_PRIVATE,
   528  			"alice",
   529  			[]string{"/keybase/private/alice/a"},
   530  			[]string{
   531  				"/keybase/private/alice/b/c/d",
   532  				"/keybase/private/alice/b/b",
   533  			},
   534  		},
   535  	}
   536  
   537  	test(t, batchSize(20),
   538  		users("alice"),
   539  		as(alice,
   540  			mkdir("a"),
   541  			mkfile("a/b", ""),
   542  			pwriteBSSync("a/b", []byte("hello"), 0, false),
   543  			mkdir("a/c"),
   544  			mkfile("a/c/d", ""),
   545  			pwriteBSSync("a/c/d", []byte("hello"), 0, false),
   546  		),
   547  		as(alice,
   548  			mkfile("e", ""),
   549  			pwriteBSSync("e", []byte("world"), 0, false),
   550  			rename("a", "b"),
   551  			rename("e", "a"),
   552  			rm("b/c/d"),
   553  			rmdir("b/c"),
   554  			rm("b/b"),
   555  			rmdir("b"),
   556  		),
   557  		as(alice,
   558  			checkUserEditHistory(expectedEdits),
   559  		),
   560  	)
   561  }
   562  
   563  // Regression test for https://github.com/keybase/client/issues/19151.
   564  func TestEditHistoryRenameDirAndReuseNameForLink(t *testing.T) {
   565  	// Alice creates a dir and puts files in it, renames the dir to
   566  	// something new and then removes it, and makes a symlink using
   567  	// the old name to the new name.
   568  	expectedEdits := []expectedEdit{
   569  		{
   570  			"alice",
   571  			keybase1.FolderType_PRIVATE,
   572  			"alice",
   573  			nil,
   574  			[]string{
   575  				"/keybase/private/alice/b/c/d",
   576  				"/keybase/private/alice/b/b",
   577  			},
   578  		},
   579  	}
   580  
   581  	test(t, batchSize(20),
   582  		users("alice"),
   583  		as(alice,
   584  			mkdir("a"),
   585  			mkfile("a/b", ""),
   586  			pwriteBSSync("a/b", []byte("hello"), 0, false),
   587  			mkdir("a/c"),
   588  			mkfile("a/c/d", ""),
   589  			pwriteBSSync("a/c/d", []byte("hello"), 0, false),
   590  		),
   591  		as(alice,
   592  			rename("a", "b"),
   593  			rm("b/c/d"),
   594  			rmdir("b/c"),
   595  			rm("b/b"),
   596  			rmdir("b"),
   597  			mkdir("e"),
   598  			link("a", "e"),
   599  		),
   600  		as(alice,
   601  			checkUserEditHistory(expectedEdits),
   602  		),
   603  	)
   604  }
   605  
   606  // Regression test for HOTPOT-803.
   607  func TestEditHistoryUnflushedRenameOverNewFile(t *testing.T) {
   608  	// Alice creates a file in the first revision, but then creates
   609  	// a file in the second revision that is renamed over the
   610  	// original file.  She also creates a file that is removed.
   611  	expectedEdits := []expectedEdit{
   612  		{
   613  			"alice",
   614  			keybase1.FolderType_PRIVATE,
   615  			"alice",
   616  			[]string{
   617  				"/keybase/private/alice/a",
   618  			},
   619  			[]string{
   620  				"/keybase/private/alice/c",
   621  			},
   622  		},
   623  	}
   624  
   625  	test(t, journal(),
   626  		users("alice"),
   627  		as(alice,
   628  			mkfile("a", "a foo"),
   629  		),
   630  		as(alice,
   631  			enableJournal(),
   632  		),
   633  		as(alice,
   634  			pwriteBSSync("b", []byte("b foo"), 0, false),
   635  			rename("a", "c"),
   636  			pwriteBSSync("a", []byte("a2 foo"), 0, false),
   637  			rename("b", "a"),
   638  			rm("c"),
   639  		),
   640  		as(alice,
   641  			lsdir("", m{"a$": "FILE"}),
   642  			read("a", "b foo"),
   643  			checkUserEditHistory(expectedEdits),
   644  		),
   645  	)
   646  }
   647  
   648  // A more complex regression test for HOTPOT-803 than the above test,
   649  // but it is more faithful to the actual user log.
   650  func TestEditHistoryUnflushedRenameOverTwoNewFiles(t *testing.T) {
   651  	// Alice creates two files in the first revision, but then creates
   652  	// 2 files in the second revision that are renamed over the
   653  	// original two files.  She also creates two files that are removed.
   654  	expectedEdits := []expectedEdit{
   655  		{
   656  			"alice",
   657  			keybase1.FolderType_PRIVATE,
   658  			"alice",
   659  			[]string{
   660  				"/keybase/private/alice/a",
   661  				"/keybase/private/alice/b",
   662  			},
   663  			[]string{
   664  				"/keybase/private/alice/f",
   665  				"/keybase/private/alice/e",
   666  			},
   667  		},
   668  	}
   669  
   670  	test(t, journal(),
   671  		users("alice"),
   672  		as(alice,
   673  			mkfile("a", "a foo"),
   674  			mkfile("b", "b foo"),
   675  		),
   676  		as(alice,
   677  			enableJournal(),
   678  		),
   679  		as(alice,
   680  			pwriteBSSync("c", []byte("c foo"), 0, false),
   681  			pwriteBSSync("d", []byte("d foo"), 0, false),
   682  			rename("b", "e"),
   683  			pwriteBSSync("f", []byte("f foo"), 0, false),
   684  			rename("a", "f"),
   685  			rename("c", "b"),
   686  			pwriteBSSync("a", []byte("a2 foo"), 0, false),
   687  			rename("d", "a"),
   688  			rm("e"),
   689  			rm("f"),
   690  		),
   691  		as(alice,
   692  			lsdir("", m{"a$": "FILE", "b$": "FILE"}),
   693  			read("a", "d foo"),
   694  			read("b", "c foo"),
   695  			// Sort the entries because when we add in the rename
   696  			// operations, the order is undefined and 'a' and 'b'
   697  			// could be swapped since they happen in the same revision
   698  			// and are independent.
   699  			checkUserEditHistoryWithSort(expectedEdits, true),
   700  		),
   701  	)
   702  }