1// Copyright 2015 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package engine
5
6import (
7	"errors"
8	"fmt"
9	"net/http"
10	"testing"
11	"time"
12
13	"github.com/keybase/client/go/libkb"
14	keybase1 "github.com/keybase/client/go/protocol/keybase1"
15	"github.com/keybase/clockwork"
16	jsonw "github.com/keybase/go-jsonw"
17	"github.com/stretchr/testify/require"
18)
19
20func TestListTracking(t *testing.T) {
21	doWithSigChainVersions(func(sigVersion libkb.SigVersion) {
22		_testListTracking(t, sigVersion)
23	})
24}
25
26func _verifyListTrackingEntries(entries []keybase1.UserSummary) error {
27	if len(entries) != 2 {
28		return fmt.Errorf("Num tracks: %d, exected 2.", len(entries))
29	}
30
31	alice := entries[0]
32	if alice.Username != "t_alice" {
33		return fmt.Errorf("Username: %q, Expected t_alice.", alice.Username)
34	}
35	bob := entries[1]
36	if bob.Username != "t_bob" {
37		return fmt.Errorf("Username: %q, Expected t_bob.", bob.Username)
38	}
39
40	return nil
41}
42
43func _testListTracking(t *testing.T, sigVersion libkb.SigVersion) {
44	tc := SetupEngineTest(t, "track")
45	defer tc.Cleanup()
46	fu := CreateAndSignupFakeUser(tc, "track")
47	fu.LoginOrBust(tc)
48
49	trackAlice(tc, fu, sigVersion)
50	trackBob(tc, fu, sigVersion)
51	defer untrackAlice(tc, fu, sigVersion)
52	defer untrackBob(tc, fu, sigVersion)
53
54	// Perform a proof to make sure that the last item of the chain isn't a track
55	proveUI, _, err := proveRooter(tc.G, fu, sigVersion)
56	require.NoError(t, err)
57	require.False(t, proveUI.overwrite)
58	require.False(t, proveUI.warning)
59	require.False(t, proveUI.recheck)
60	require.True(t, proveUI.checked)
61
62	eng := NewListTrackingEngine(tc.G, &ListTrackingEngineArg{})
63	if err := RunEngine2(NewMetaContextForTest(tc), eng); err != nil {
64		t.Fatal("Error in ListTrackingEngine:", err)
65	}
66	if err := _verifyListTrackingEntries(eng.TableResult().Users); err != nil {
67		t.Fatal("Error in tracking engine result entries verification:", err)
68	}
69
70	// We're running this again using a different, non-signed-in cache to test
71	// the manual stubbing override.
72	tc2 := SetupEngineTest(t, "track-anonymous")
73	defer tc2.Cleanup()
74
75	eng = NewListTrackingEngine(tc2.G, &ListTrackingEngineArg{
76		Assertion: fu.Username,
77	})
78	if err := RunEngine2(NewMetaContextForTest(tc2), eng); err != nil {
79		t.Fatal("Error in ListTrackingEngine:", err)
80	}
81	if err := _verifyListTrackingEntries(eng.TableResult().Users); err != nil {
82		t.Fatal("Error in tracking engine result entries verification:", err)
83	}
84}
85
86func TestListTrackingJSON(t *testing.T) {
87	tc := SetupEngineTest(t, "track")
88	defer tc.Cleanup()
89	sigVersion := libkb.GetDefaultSigVersion(tc.G)
90	fu := CreateAndSignupFakeUser(tc, "track")
91	fu.LoginOrBust(tc)
92
93	trackAlice(tc, fu, sigVersion)
94	defer untrackAlice(tc, fu, sigVersion)
95
96	arg := ListTrackingEngineArg{JSON: true, Verbose: true}
97	eng := NewListTrackingEngine(tc.G, &arg)
98	m := NewMetaContextForTest(tc)
99	err := RunEngine2(m, eng)
100	if err != nil {
101		t.Fatal("Error in ListTrackingEngine:", err)
102	}
103
104	_, err = jsonw.Unmarshal([]byte(eng.JSONResult()))
105	if err != nil {
106		t.Fatal(err)
107	}
108}
109
110func TestListTrackingLocal(t *testing.T) {
111	t.Skip("Skipping test for local tracks in list tracking (milestone 2)")
112	tc := SetupEngineTest(t, "track")
113	defer tc.Cleanup()
114	sigVersion := libkb.GetDefaultSigVersion(tc.G)
115	fu := CreateAndSignupFakeUser(tc, "track")
116
117	trackAlice(tc, fu, sigVersion)
118	defer untrackAlice(tc, fu, sigVersion)
119
120	sv := keybase1.SigVersion(sigVersion)
121	trackBobWithOptions(tc, fu, keybase1.TrackOptions{LocalOnly: true, SigVersion: &sv}, fu.NewSecretUI())
122	defer untrackBob(tc, fu, sigVersion)
123
124	arg := ListTrackingEngineArg{}
125	eng := NewListTrackingEngine(tc.G, &arg)
126	m := NewMetaContextForTest(tc)
127	err := RunEngine2(m, eng)
128	if err != nil {
129		t.Fatal("Error in ListTrackingEngine:", err)
130	}
131
132	entries := eng.TableResult().Users
133	if len(entries) != 2 {
134		t.Errorf("Num tracks: %d, exected 2", len(entries))
135	}
136}
137
138func TestListTrackingServerInterference(t *testing.T) {
139	atc := SetupEngineTest(t, "track")
140	defer atc.Cleanup()
141	btc := SetupEngineTest(t, "track")
142	defer btc.Cleanup()
143	ctc := SetupEngineTest(t, "track")
144	defer ctc.Cleanup()
145	sigVersion := libkb.GetDefaultSigVersion(atc.G)
146
147	alice := CreateAndSignupFakeUser(atc, "track")
148	bob := CreateAndSignupFakeUser(btc, "track")
149	charlie := CreateAndSignupFakeUser(ctc, "track")
150	alice.LoginOrBust(atc)
151
152	_, _, err := runTrack(atc, alice, bob.Username, sigVersion)
153	require.NoError(t, err)
154
155	eng := NewListTrackingEngine(atc.G, &ListTrackingEngineArg{})
156	if err := RunEngine2(NewMetaContextForTest(atc), eng); err != nil {
157		t.Fatal("Error in ListTrackingEngine:", err)
158	}
159	found := false
160	for _, user := range eng.TableResult().Users {
161		if user.Username == bob.Username {
162			found = true
163		}
164	}
165	if !found {
166		t.Fatalf("expected to be following bob, but wasn't")
167	}
168
169	ResetAccount(btc, bob)
170
171	// (MD/TRIAGE-1837) Due to a server bug, it seems the follow version doesn't
172	// update immediately on resets, only on the next follow, so we're not
173	// going to get the proper filtration until we bump it e.g. by following
174	// another random user.
175	_, _, err = runTrack(atc, alice, charlie.Username, sigVersion)
176	require.NoError(t, err)
177
178	eng = NewListTrackingEngine(atc.G, &ListTrackingEngineArg{})
179	if err := RunEngine2(NewMetaContextForTest(atc), eng); err != nil {
180		t.Fatal("Error in ListTrackingEngine:", err)
181	}
182	found = false
183	for _, user := range eng.TableResult().Users {
184		if user.Username == bob.Username {
185			found = true
186		}
187	}
188	if found {
189		t.Fatalf("expected server to filter out reset bob, but didn't; still following after reset")
190	}
191
192	eng = NewListTrackingEngine(atc.G, &ListTrackingEngineArg{})
193	eng.disableTrackerSyncerForTest = true
194	if err := RunEngine2(NewMetaContextForTest(atc), eng); err != nil {
195		t.Fatal("Error in ListTrackingEngine:", err)
196	}
197	found = false
198	for _, user := range eng.TableResult().Users {
199		if user.Username == bob.Username {
200			found = true
201		}
202	}
203	if !found {
204		t.Fatalf("tracker syncer returned error; so we should still succeed but not filter")
205	}
206}
207
208type errorAPIMock struct {
209	*libkb.APIArgRecorder
210	callCount int
211}
212
213func (r *errorAPIMock) GetDecode(mctx libkb.MetaContext, arg libkb.APIArg, w libkb.APIResponseWrapper) error {
214	r.callCount++
215	return errors.New("timeout or something")
216}
217
218func (r errorAPIMock) GetResp(mctx libkb.MetaContext, arg libkb.APIArg) (*http.Response, func(), error) {
219	r.callCount++
220	return nil, func() {}, errors.New("timeout or something")
221}
222
223func (r errorAPIMock) Get(mctx libkb.MetaContext, arg libkb.APIArg) (*libkb.APIRes, error) {
224	r.callCount++
225	return nil, errors.New("timeout or something")
226}
227
228func TestListTrackingOfflineBehavior(t *testing.T) {
229	atc := SetupEngineTest(t, "track")
230	defer atc.Cleanup()
231	btc := SetupEngineTest(t, "track")
232	defer btc.Cleanup()
233	ctc := SetupEngineTest(t, "track")
234	defer ctc.Cleanup()
235	sigVersion := libkb.GetDefaultSigVersion(atc.G)
236
237	alice := CreateAndSignupFakeUser(atc, "track")
238	bob := CreateAndSignupFakeUser(btc, "track")
239	charlie := CreateAndSignupFakeUser(ctc, "track")
240	alice.LoginOrBust(atc)
241
242	_, _, err := runTrack(atc, alice, bob.Username, sigVersion)
243	require.NoError(t, err)
244
245	// Prime UPAK and TrackerSyncer caches when online
246	eng := NewListTrackingEngine(atc.G, &ListTrackingEngineArg{})
247	if err := RunEngine2(NewMetaContextForTest(atc), eng); err != nil {
248		t.Fatal("Error in ListTrackingEngine:", err)
249	}
250	res1 := eng.TableResult()
251
252	// realAPI := atc.G.API
253	fakeAPI := &errorAPIMock{}
254	atc.G.API = fakeAPI
255
256	// We're offline now
257	_, _, err = runTrack(atc, alice, charlie.Username, sigVersion)
258	require.Error(t, err)
259	require.Contains(t, err.Error(), "timeout or something")
260
261	c := clockwork.NewFakeClockAt(atc.G.Clock().Now())
262	atc.G.SetClock(c)
263
264	t.Logf("Test CachedOnly")
265	// But ListTracking with CachedOnly=true should still work.
266	eng = NewListTrackingEngine(atc.G, &ListTrackingEngineArg{CachedOnly: true})
267	err = RunEngine2(NewMetaContextForTest(atc), eng)
268	require.NoError(t, err)
269	res2 := eng.TableResult()
270	require.Equal(t, res1, res2, "got same results even when offline")
271
272	staleness := time.Hour * 24 * 7
273	t.Logf("Test offline 1 day later")
274	// Should work even if we're still offline 1 day later (longer than the 10
275	// minute UPAK staleness window), if CachedOnlyStalenessWindow passed.
276	stalenesseng := NewListTrackingEngine(atc.G, &ListTrackingEngineArg{CachedOnly: true, CachedOnlyStalenessWindow: &staleness})
277	c.Advance(time.Hour * 24)
278	err = RunEngine2(NewMetaContextForTest(atc), stalenesseng)
279	require.NoError(t, err)
280
281	t.Logf("Should return an error past the staleness window")
282	c.Advance(time.Hour * 24 * 8)
283	err = RunEngine2(NewMetaContextForTest(atc), stalenesseng)
284	require.Error(t, err)
285	require.IsType(t, err, libkb.UserNotFoundError{})
286}
287