1// Copyright 2018 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package engine
5
6import (
7	"encoding/hex"
8	"testing"
9
10	"github.com/keybase/client/go/libkb"
11	"github.com/stretchr/testify/require"
12)
13
14func persistDeviceCloneState(m libkb.MetaContext, d libkb.DeviceCloneState) error {
15	return libkb.SetDeviceCloneState(m, d)
16}
17
18func runAndGetDeviceCloneState(m libkb.MetaContext) (d libkb.DeviceCloneState, err error) {
19	_, _, err = libkb.UpdateDeviceCloneState(m)
20	if err != nil {
21		return d, err
22	}
23	d, _ = libkb.GetDeviceCloneState(m)
24	return d, err
25}
26
27func assertIsValidToken(tc libkb.TestContext, token string) {
28	_, err := hex.DecodeString(token)
29	require.NoError(tc.T, err)
30	require.Equal(tc.T, len(token), 32)
31}
32
33func assertSuccessfulRun(tc libkb.TestContext, d libkb.DeviceCloneState, err error) {
34	require.NoError(tc.T, err)
35	require.Equal(tc.T, d.Stage, "")
36	assertIsValidToken(tc, d.Prior)
37}
38
39func TestDeviceCloneStateFirstRun(t *testing.T) {
40	tc := SetupEngineTest(t, "DeviceCloneState")
41	defer tc.Cleanup()
42	_ = CreateAndSignupFakeUser(tc, "fu")
43	m := NewMetaContextForTest(tc)
44
45	d, err := runAndGetDeviceCloneState(m)
46	assertSuccessfulRun(tc, d, err)
47	require.Equal(tc.T, d.Clones, 1)
48}
49
50func TestDeviceCloneStateSuccessfulUpdate(t *testing.T) {
51	tc := SetupEngineTest(t, "DeviceCloneState")
52	defer tc.Cleanup()
53	_ = CreateAndSignupFakeUser(tc, "fu")
54	m := NewMetaContextForTest(tc)
55	//setup: perform an initial run
56	d0, err := runAndGetDeviceCloneState(m)
57	require.NoError(tc.T, err)
58
59	d, err := runAndGetDeviceCloneState(m)
60	assertSuccessfulRun(tc, d, err)
61	require.NotEqual(tc.T, d.Prior, d0.Prior)
62	require.Equal(tc.T, d.Clones, 1)
63}
64
65func TestDeviceCloneStateRecoveryFromFailureBeforeServer(t *testing.T) {
66	tc := SetupEngineTest(t, "DeviceCloneState")
67	defer tc.Cleanup()
68	_ = CreateAndSignupFakeUser(tc, "fu")
69	m := NewMetaContextForTest(tc)
70	// setup: persist tokens as if the process failed
71	// before the server received the update
72	d0 := libkb.DeviceCloneState{
73		Prior:  libkb.DefaultCloneTokenValue,
74		Stage:  "22222222222222222222222222222222",
75		Clones: 1,
76	}
77	err := persistDeviceCloneState(m, d0)
78	require.NoError(t, err)
79
80	d, err := runAndGetDeviceCloneState(m)
81	assertSuccessfulRun(tc, d, err)
82	require.Equal(tc.T, d.Prior, d0.Stage)
83	require.Equal(tc.T, d.Clones, 1)
84}
85
86func TestDeviceCloneStateRecoveryFromFailureAfterServer(t *testing.T) {
87	tc := SetupEngineTest(t, "DeviceCloneState")
88	defer tc.Cleanup()
89	_ = CreateAndSignupFakeUser(tc, "fu")
90	m := NewMetaContextForTest(tc)
91	// setup: run twice. then reset the persistence to where it would have been
92	// if the server got the second update but did not ack it successfully to the client.
93	d0, err := runAndGetDeviceCloneState(m)
94	require.NoError(t, err)
95	d1, err := runAndGetDeviceCloneState(m)
96	require.NoError(t, err)
97	tmp := libkb.DeviceCloneState{Prior: d0.Prior, Stage: d1.Prior, Clones: 1}
98	err = persistDeviceCloneState(m, tmp)
99	require.NoError(t, err)
100
101	d, err := runAndGetDeviceCloneState(m)
102	assertSuccessfulRun(tc, d, err)
103	require.Equal(tc.T, d.Prior, d1.Prior)
104	require.Equal(tc.T, d.Clones, 1)
105}
106
107func TestDeviceCloneStateCloneDetected(t *testing.T) {
108	tc := SetupEngineTest(t, "DeviceCloneState")
109	defer tc.Cleanup()
110	_ = CreateAndSignupFakeUser(tc, "fu")
111	m := NewMetaContextForTest(tc)
112	// setup: perform two runs, and then manually persist the earlier
113	// prior token to simulate a subsequent run by a cloned device
114	d0, err := runAndGetDeviceCloneState(m)
115	require.NoError(tc.T, err)
116	_, err = runAndGetDeviceCloneState(m)
117	require.NoError(tc.T, err)
118	err = persistDeviceCloneState(m, d0)
119	require.NoError(t, err)
120
121	before, after, err := libkb.UpdateDeviceCloneState(m)
122	require.NoError(t, err)
123
124	d, err := libkb.GetDeviceCloneState(m)
125	assertSuccessfulRun(tc, d, err)
126	require.NotEqual(tc.T, d.Prior, d0.Stage, "despite there being a clone, the prior still needs to change")
127	require.Equal(tc.T, d.Clones, 2)
128	require.Equal(tc.T, before, 1, "there was one clone before the test run")
129	require.Equal(tc.T, after, 2, "there were two clones after the test run")
130}
131
132func TestDeviceCloneStateBeforeAndAfterOnFirstRun(t *testing.T) {
133	tc := SetupEngineTest(t, "DeviceCloneState")
134	defer tc.Cleanup()
135	_ = CreateAndSignupFakeUser(tc, "fu")
136	m := NewMetaContextForTest(tc)
137
138	before, after, err := libkb.UpdateDeviceCloneState(m)
139	require.NoError(tc.T, err)
140	require.Equal(tc.T, before, 1, "there was one clone before the test run")
141	require.Equal(tc.T, after, 1, "there was one clone after the test run")
142}
143