1package libkb
2
3import (
4	"encoding/hex"
5	"testing"
6	"time"
7
8	keybase1 "github.com/keybase/client/go/protocol/keybase1"
9	"github.com/keybase/clockwork"
10	"github.com/stretchr/testify/require"
11	context "golang.org/x/net/context"
12)
13
14type id3FakeUIRouter struct {
15	ui id3FakeUI
16}
17
18func (i *id3FakeUIRouter) SetUI(ConnectionID, UIKind)         {}
19func (i *id3FakeUIRouter) GetIdentifyUI() (IdentifyUI, error) { return nil, nil }
20func (i *id3FakeUIRouter) GetIdentifyUICtx(ctx context.Context) (int, IdentifyUI, error) {
21	return 0, nil, nil
22}
23func (i *id3FakeUIRouter) GetSecretUI(sessionID int) (SecretUI, error)               { return nil, nil }
24func (i *id3FakeUIRouter) GetRekeyUI() (keybase1.RekeyUIInterface, int, error)       { return nil, 0, nil }
25func (i *id3FakeUIRouter) GetRekeyUINoSessionID() (keybase1.RekeyUIInterface, error) { return nil, nil }
26func (i *id3FakeUIRouter) GetHomeUI() (keybase1.HomeUIInterface, error)              { return nil, nil }
27func (i *id3FakeUIRouter) GetChatUI() (ChatUI, error)                                { return nil, nil }
28func (i *id3FakeUIRouter) GetLogUI() (LogUI, error)                                  { return nil, nil }
29func (i *id3FakeUIRouter) GetIdentify3UIAdapter(MetaContext) (IdentifyUI, error) {
30	return nil, nil
31}
32func (i *id3FakeUIRouter) DumpUIs() map[UIKind]ConnectionID {
33	return nil
34}
35func (i *id3FakeUIRouter) Shutdown() {}
36
37func (i *id3FakeUIRouter) GetIdentify3UI(MetaContext) (keybase1.Identify3UiInterface, error) {
38	return &i.ui, nil
39}
40
41func (i *id3FakeUIRouter) WaitForUIType(uiKind UIKind, timeout time.Duration) bool {
42	return false
43}
44
45type id3FakeUI struct {
46	timeOuts []keybase1.Identify3GUIID
47}
48
49func (i *id3FakeUI) assertAndCleanState(t *testing.T, expected []keybase1.Identify3GUIID) {
50	require.Equal(t, len(expected), len(i.timeOuts))
51	for j, v := range expected {
52		require.Equal(t, v, i.timeOuts[j])
53	}
54	i.timeOuts = nil
55}
56
57func (i *id3FakeUI) Identify3ShowTracker(context.Context, keybase1.Identify3ShowTrackerArg) error {
58	return nil
59}
60func (i *id3FakeUI) Identify3UpdateRow(context.Context, keybase1.Identify3Row) error {
61	return nil
62}
63func (i *id3FakeUI) Identify3UpdateUserCard(context.Context, keybase1.Identify3UpdateUserCardArg) error {
64	return nil
65}
66func (i *id3FakeUI) Identify3UserReset(_ context.Context, id keybase1.Identify3GUIID) error {
67	return nil
68}
69func (i *id3FakeUI) Identify3TrackerTimedOut(_ context.Context, id keybase1.Identify3GUIID) error {
70	i.timeOuts = append(i.timeOuts, id)
71	return nil
72}
73func (i *id3FakeUI) Identify3Result(context.Context, keybase1.Identify3ResultArg) error { return nil }
74func (i *id3FakeUI) Identify3Summary(_ context.Context, summary keybase1.Identify3Summary) error {
75	return nil
76}
77
78func TestIdentify3State(t *testing.T) {
79	tc := SetupTest(t, "TestIdentify3State()", 1)
80	defer tc.Cleanup()
81
82	fakeClock := clockwork.NewFakeClock()
83	tc.G.SetClock(fakeClock)
84	uiRouter := id3FakeUIRouter{}
85	tc.G.UIRouter = &uiRouter
86	tc.G.Identify3State.Shutdown()
87
88	id3state, testCompletionCh := NewIdentify3StateForTest(tc.G)
89	tc.G.Identify3State = id3state
90
91	mkID := func(i int) keybase1.Identify3GUIID {
92		var buf [1]byte
93		buf[0] = byte(i)
94		return keybase1.Identify3GUIID(hex.EncodeToString(buf[:]))
95	}
96	mkSession := func(i int) *Identify3Session {
97		return &Identify3Session{
98			created: fakeClock.Now(),
99			id:      mkID(i),
100		}
101	}
102
103	assertState := func(cache, queue []int) {
104		id3state.Lock()
105		require.Equal(t, len(cache), len(id3state.cache))
106		for _, v := range cache {
107			_, found := id3state.cache[mkID(v)]
108			require.True(t, found)
109		}
110		require.Equal(t, len(queue), len(id3state.expirationQueue))
111		for i, v := range queue {
112			require.Equal(t, mkID(v), id3state.expirationQueue[i].id)
113		}
114		id3state.Unlock()
115	}
116
117	advance := func(d time.Duration) {
118		id3state.bgThreadTimeMu.Lock()
119		fakeClock.Advance(d)
120		now := fakeClock.Now()
121		id3state.bgThreadTimeMu.Unlock()
122		for {
123			completedThough := <-testCompletionCh
124			if !completedThough.Before(now) {
125				break
126			}
127		}
128	}
129
130	inc := id3state.expireTime / 10000
131	epsilon := inc / 2
132
133	// put in 3 items all inc time apart.
134	err := id3state.Put(mkSession(1))
135	require.NoError(t, err)
136	fakeClock.Advance(inc)
137	err = id3state.Put(mkSession(2))
138	require.NoError(t, err)
139	fakeClock.Advance(inc)
140	err = id3state.Put(mkSession(3))
141	require.NoError(t, err)
142
143	// make sure that all 3 items hit the cache, and in the right order.
144	set := []int{1, 2, 3}
145	assertState(set, set)
146
147	// After advancing a little bit more than necessary expiration time, we should
148	// see a situation where item 1 is going from both the cache and the queue,
149	// and that it shows up as expired in the UI.
150	advance(id3state.expireTime - inc - epsilon)
151	set = []int{2, 3}
152	assertState(set, set)
153	uiRouter.ui.assertAndCleanState(t, []keybase1.Identify3GUIID{mkID(1)})
154
155	// When we remove an item explicitly, it's removed from the cache, but not
156	// the queue, so check this expectation.
157	id3state.Remove(mkID(3))
158	assertState([]int{2}, []int{2, 3})
159
160	// Now item 2 is about to expire, which will leave just 3 sitting around in
161	// the queue.
162	advance(inc)
163	assertState([]int{}, []int{3})
164	uiRouter.ui.assertAndCleanState(t, []keybase1.Identify3GUIID{mkID(2)})
165
166	// Because 3 was removed prior to its expiration, it shouldn't trigger a UI
167	// event.
168	advance(inc)
169	assertState([]int{}, []int{})
170	uiRouter.ui.assertAndCleanState(t, []keybase1.Identify3GUIID{})
171}
172