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
5package libkbfs
6
7import (
8	"fmt"
9	"testing"
10	"time"
11
12	"github.com/keybase/client/go/kbfs/data"
13	"github.com/keybase/client/go/kbfs/kbfsblock"
14	"github.com/keybase/client/go/kbfs/kbfscodec"
15	"github.com/keybase/client/go/kbfs/kbfsmd"
16	"github.com/keybase/client/go/kbfs/test/clocktest"
17	"github.com/keybase/client/go/kbfs/tlf"
18	"github.com/keybase/client/go/kbfs/tlfhandle"
19	kbname "github.com/keybase/client/go/kbun"
20	"github.com/keybase/client/go/logger"
21	"github.com/pkg/errors"
22	"github.com/stretchr/testify/require"
23	"golang.org/x/net/context"
24)
25
26type testBlockCache struct {
27	b data.Block
28}
29
30func (c testBlockCache) Get(ptr data.BlockPointer) (data.Block, error) {
31	return c.b, nil
32}
33
34func (testBlockCache) Put(ptr data.BlockPointer, tlf tlf.ID, block data.Block,
35	lifetime data.BlockCacheLifetime, _ data.BlockCacheHashBehavior) error {
36	return errors.New("Shouldn't be called")
37}
38
39type blockChangesNoInfo struct {
40	// An ordered list of operations completed in this update
41	Ops opsList `codec:"o,omitempty"`
42}
43
44func TestReembedBlockChanges(t *testing.T) {
45	codec := kbfscodec.NewMsgpack()
46	RegisterOps(codec)
47
48	oldDir := data.BlockPointer{ID: kbfsblock.FakeID(1)}
49	co, err := newCreateOp("file", oldDir, data.File)
50	require.NoError(t, err)
51
52	changes := blockChangesNoInfo{
53		Ops: opsList{co},
54	}
55
56	encodedChanges, err := codec.Encode(changes)
57	require.NoError(t, err)
58	block := &data.FileBlock{Contents: encodedChanges}
59
60	ctx := context.Background()
61	bcache := testBlockCache{block}
62	tlfID := tlf.FakeID(1, tlf.Private)
63	mode := modeTest{NewInitModeFromType(InitDefault)}
64
65	ptr := data.BlockPointer{ID: kbfsblock.FakeID(2)}
66	pmd := PrivateMetadata{
67		Changes: BlockChanges{
68			Info: data.BlockInfo{
69				BlockPointer: ptr,
70			},
71		},
72	}
73
74	// We make the cache always return a block, so we can pass in
75	// nil for bops and rmdWithKeys.
76	err = reembedBlockChanges(
77		ctx, codec, bcache, nil, mode, tlfID, &pmd, nil,
78		logger.NewTestLogger(t))
79	require.NoError(t, err)
80
81	// We expect to get changes back, except with the implicit ref
82	// block added.
83	expectedCO, err := newCreateOp("file", oldDir, data.File)
84	require.NoError(t, err)
85	expectedCO.AddRefBlock(ptr)
86	expectedChanges := BlockChanges{
87		Ops: opsList{expectedCO},
88	}
89
90	// In particular, Info should be empty.
91	expectedPmd := PrivateMetadata{
92		Changes: expectedChanges,
93
94		cachedChanges: BlockChanges{
95			Info: data.BlockInfo{
96				BlockPointer: ptr,
97			},
98		},
99	}
100	require.Equal(t, expectedPmd, pmd)
101}
102
103func TestGetRevisionByTime(t *testing.T) {
104	var u1 kbname.NormalizedUsername = "u1"
105	config, _, ctx, cancel := kbfsOpsInitNoMocks(t, u1)
106	defer kbfsTestShutdownNoMocks(ctx, t, config, cancel)
107
108	clock, t1 := clocktest.NewTestClockAndTimeNow()
109	config.SetClock(clock)
110
111	t.Log("Create revision 1")
112	h, err := tlfhandle.ParseHandle(
113		ctx, config.KBPKI(), config.MDOps(), nil, string(u1), tlf.Private)
114	require.NoError(t, err)
115	kbfsOps := config.KBFSOps()
116	rootNode, _, err := kbfsOps.GetOrCreateRootNode(ctx, h, data.MasterBranch)
117	require.NoError(t, err)
118	err = kbfsOps.SyncAll(ctx, rootNode.GetFolderBranch())
119	require.NoError(t, err)
120
121	t.Log("Create revision 2")
122	t2 := t1.Add(1 * time.Minute)
123	clock.Set(t2)
124	nodeA, _, err := kbfsOps.CreateFile(
125		ctx, rootNode, testPPS("a"), false, NoExcl)
126	require.NoError(t, err)
127	data := []byte{1}
128	err = kbfsOps.Write(ctx, nodeA, data, 0)
129	require.NoError(t, err)
130	err = kbfsOps.SyncAll(ctx, rootNode.GetFolderBranch())
131	require.NoError(t, err)
132
133	t.Log("Clear the MD cache, to make sure it gets repopulated")
134	config.ResetCaches()
135
136	t.Log(ctx, "Check exact times")
137	rev, err := GetMDRevisionByTime(ctx, config, h, t2)
138	require.NoError(t, err)
139	require.Equal(t, kbfsmd.Revision(2), rev)
140	_, err = config.MDCache().Get(h.TlfID(), rev, kbfsmd.NullBranchID)
141	require.NoError(t, err)
142	rev, err = GetMDRevisionByTime(ctx, config, h, t1)
143	require.NoError(t, err)
144	require.Equal(t, kbfsmd.Revision(1), rev)
145	_, err = config.MDCache().Get(h.TlfID(), rev, kbfsmd.NullBranchID)
146	require.NoError(t, err)
147
148	t.Log(ctx, "Check in-between times")
149	rev, err = GetMDRevisionByTime(ctx, config, h, t2.Add(30*time.Second))
150	require.NoError(t, err)
151	require.Equal(t, kbfsmd.Revision(2), rev)
152	rev, err = GetMDRevisionByTime(ctx, config, h, t1.Add(30*time.Second))
153	require.NoError(t, err)
154	require.Equal(t, kbfsmd.Revision(1), rev)
155
156	t.Log(ctx, "Check too-early time")
157	_, err = GetMDRevisionByTime(ctx, config, h, t1.Add(-30*time.Second))
158	require.Error(t, err)
159}
160
161func TestGetChangesBetweenRevisions(t *testing.T) {
162	var u1 kbname.NormalizedUsername = "u1"
163	config, _, ctx, cancel := kbfsOpsInitNoMocks(t, u1)
164	defer kbfsTestShutdownNoMocks(ctx, t, config, cancel)
165
166	t.Log("Create revision 1")
167	h, err := tlfhandle.ParseHandle(
168		ctx, config.KBPKI(), config.MDOps(), nil, string(u1), tlf.Private)
169	require.NoError(t, err)
170	kbfsOps := config.KBFSOps()
171	rootNode, _, err := kbfsOps.GetOrCreateRootNode(ctx, h, data.MasterBranch)
172	require.NoError(t, err)
173
174	t.Log("Add more revisions")
175	nodeA, _, err := kbfsOps.CreateDir(ctx, rootNode, testPPS("a"))
176	require.NoError(t, err)
177	// Revision 2.
178	err = kbfsOps.SyncAll(ctx, rootNode.GetFolderBranch())
179	require.NoError(t, err)
180
181	nodeB, _, err := kbfsOps.CreateFile(ctx, nodeA, testPPS("b"), false, NoExcl)
182	require.NoError(t, err)
183	// Revision 3.
184	err = kbfsOps.SyncAll(ctx, rootNode.GetFolderBranch())
185	require.NoError(t, err)
186
187	err = kbfsOps.Write(ctx, nodeB, []byte("test"), 0)
188	require.NoError(t, err)
189	// Revision 4.
190	err = kbfsOps.SyncAll(ctx, rootNode.GetFolderBranch())
191	require.NoError(t, err)
192
193	nodeC, _, err := kbfsOps.CreateDir(ctx, rootNode, testPPS("c"))
194	require.NoError(t, err)
195	// Revision 5.
196	err = kbfsOps.SyncAll(ctx, rootNode.GetFolderBranch())
197	require.NoError(t, err)
198
199	err = kbfsOps.Rename(ctx, nodeA, testPPS("b"), nodeC, testPPS("d"))
200	require.NoError(t, err)
201	// Revision 6.
202	err = kbfsOps.SyncAll(ctx, rootNode.GetFolderBranch())
203	require.NoError(t, err)
204
205	err = kbfsOps.RemoveDir(ctx, rootNode, testPPS("a"))
206	require.NoError(t, err)
207	// Revision 7.
208	err = kbfsOps.SyncAll(ctx, rootNode.GetFolderBranch())
209	require.NoError(t, err)
210
211	type e struct {
212		t    ChangeType
213		p    string
214		u    int // len(UnrefsForDelete)
215		used bool
216	}
217	checkChanges := func(changes []*ChangeItem, expectedChanges []e) {
218		require.Len(t, changes, len(expectedChanges))
219		for _, c := range changes {
220			found := false
221			for _, ec := range expectedChanges {
222				if !ec.used && ec.t == c.Type &&
223					ec.u == len(c.UnrefsForDelete) &&
224					ec.p == c.CurrPath.CanonicalPathPlaintext() {
225					found = true
226					ec.used = true
227					break
228				}
229			}
230			require.True(
231				t, found, fmt.Sprintf(
232					"Didn't expect change: %#v, changes=%#v, expected=%#v",
233					*c, changes, expectedChanges))
234		}
235	}
236
237	t.Log("Check single revision")
238	tlfID := rootNode.GetFolderBranch().Tlf
239	changes, _, err := GetChangesBetweenRevisions(
240		ctx, config, tlfID, kbfsmd.Revision(1), kbfsmd.Revision(2))
241	require.NoError(t, err)
242	checkChanges(
243		changes, []e{
244			{ChangeTypeWrite, "/keybase/private/u1", 0, false},
245			{ChangeTypeWrite, "/keybase/private/u1/a", 0, false},
246		})
247
248	t.Log("Check multiple revisions")
249	changes, _, err = GetChangesBetweenRevisions(
250		ctx, config, tlfID, kbfsmd.Revision(1), kbfsmd.Revision(5))
251	require.NoError(t, err)
252	checkChanges(
253		changes, []e{
254			{ChangeTypeWrite, "/keybase/private/u1", 0, false},
255			{ChangeTypeWrite, "/keybase/private/u1/a", 0, false},
256			{ChangeTypeWrite, "/keybase/private/u1/a/b", 0, false},
257			{ChangeTypeWrite, "/keybase/private/u1/c", 0, false},
258		})
259
260	t.Log("Check rename")
261	changes, _, err = GetChangesBetweenRevisions(
262		ctx, config, tlfID, kbfsmd.Revision(5), kbfsmd.Revision(6))
263	require.NoError(t, err)
264	checkChanges(
265		changes, []e{
266			{ChangeTypeWrite, "/keybase/private/u1", 0, false},
267			{ChangeTypeWrite, "/keybase/private/u1/c", 0, false},
268			{ChangeTypeRename, "/keybase/private/u1/c/d", 0, false},
269		})
270
271	t.Log("Check internal rename")
272	changes, _, err = GetChangesBetweenRevisions(
273		ctx, config, tlfID, kbfsmd.Revision(1), kbfsmd.Revision(6))
274	require.NoError(t, err)
275	checkChanges(
276		changes, []e{
277			{ChangeTypeWrite, "/keybase/private/u1", 0, false},
278			{ChangeTypeWrite, "/keybase/private/u1/a", 0, false},
279			{ChangeTypeWrite, "/keybase/private/u1/c", 0, false},
280			{ChangeTypeWrite, "/keybase/private/u1/c/d", 0, false},
281		})
282
283	t.Log("Check delete")
284	changes, _, err = GetChangesBetweenRevisions(
285		ctx, config, tlfID, kbfsmd.Revision(6), kbfsmd.Revision(7))
286	require.NoError(t, err)
287	checkChanges(
288		changes, []e{
289			{ChangeTypeWrite, "/keybase/private/u1", 0, false},
290			{ChangeTypeDelete, "/keybase/private/u1/a", 1, false},
291		})
292
293	t.Log("Check full sequence")
294	changes, _, err = GetChangesBetweenRevisions(
295		ctx, config, tlfID, kbfsmd.Revision(1), kbfsmd.Revision(7))
296	require.NoError(t, err)
297	checkChanges(
298		changes, []e{
299			{ChangeTypeWrite, "/keybase/private/u1", 0, false},
300			{ChangeTypeWrite, "/keybase/private/u1/c", 0, false},
301			{ChangeTypeWrite, "/keybase/private/u1/c/d", 0, false},
302		})
303}
304