1// Copyright 2015 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package libkb
5
6import (
7	"bytes"
8	"fmt"
9	"os"
10	"path/filepath"
11	"regexp"
12	"strings"
13	"testing"
14	"time"
15
16	"github.com/keybase/clockwork"
17
18	"github.com/stretchr/testify/require"
19)
20
21type cmpTest struct {
22	a, b string
23	eq   bool
24}
25
26var nameCmpTest = []cmpTest{
27	{a: "UpperCase", b: "uppercase", eq: true},
28	{a: " Space prefix", b: "Space prefix", eq: true},
29	{a: "Space suffix ", b: "Space suffix", eq: true},
30	{a: "Space Inside", b: "SpaceInside", eq: true},
31	{a: "work iPad", b: "work ipad", eq: true},
32	{a: "my_ipad", b: "MY IPAD", eq: true},
33	{a: "device a", b: "device b", eq: false},
34	{a: "mike's computer", b: "mikes computer", eq: true},
35	{a: "my+-'_device", b: "my device", eq: true},
36}
37
38func TestNameCmp(t *testing.T) {
39	for _, test := range nameCmpTest {
40		eq := NameCmp(test.a, test.b)
41		if eq != test.eq {
42			t.Errorf("name compare %q == %q => %v, expected %v", test.a, test.b, eq, test.eq)
43		}
44	}
45}
46
47func TestCombineErrors(t *testing.T) {
48	err := CombineErrors(fmt.Errorf("error1"), nil, fmt.Errorf("error3"))
49	expected := "There were multiple errors: error1; error3"
50	if err.Error() != expected {
51		t.Errorf("Wrong output for combine errors: %#v != %#v", err.Error(), expected)
52	}
53}
54
55func TestWhitespaceNormalize(t *testing.T) {
56
57	data := []struct {
58		in, out string
59	}{
60		{" ab   cd    ef   gh ", "ab cd ef gh"},
61		{"a\nb  c\nd", "a b c d"},
62		{" a ", "a"},
63		{"\na\nb ", "a b"},
64		{
65			" Verifying myself: I am pomf on Keybase.io. 8a6cewzit2o7zuLKGbDqQADhzfOlGerGuBpq\n/ https://keybase.io/pomf/sigs/8a6cewzit2o7zuLKGbDqQADhzfOlGerGuBpq ",
66			"Verifying myself: I am pomf on Keybase.io. 8a6cewzit2o7zuLKGbDqQADhzfOlGerGuBpq / https://keybase.io/pomf/sigs/8a6cewzit2o7zuLKGbDqQADhzfOlGerGuBpq",
67		},
68	}
69
70	for i, p := range data {
71		out := WhitespaceNormalize(p.in)
72		if out != p.out {
73			t.Errorf("Failed on test %d: %s != %s", i, out, p.out)
74		}
75	}
76
77}
78
79func TestMakeByte24(t *testing.T) {
80	var x1 [24]byte
81	var x2 [31]byte
82	var x3 [33]byte
83
84	x1[3] = 5
85
86	y := MakeByte24(x1[:])
87	require.Equal(t, x1, y)
88
89	require.Panics(t, func() {
90		MakeByte24(x2[:])
91	})
92
93	require.Panics(t, func() {
94		MakeByte24(x3[:])
95	})
96}
97
98func TestMakeByte32(t *testing.T) {
99	var x1 [32]byte
100	var x2 [31]byte
101	var x3 [33]byte
102
103	x1[3] = 5
104
105	y := MakeByte32(x1[:])
106	require.Equal(t, x1, y)
107
108	require.Panics(t, func() {
109		MakeByte32(x2[:])
110	})
111
112	require.Panics(t, func() {
113		MakeByte32(x3[:])
114	})
115}
116
117func TestAppDataDir(t *testing.T) {
118	dir, err := AppDataDir()
119	if err != nil {
120		// Non-Windows case.
121		require.True(t, strings.HasPrefix(err.Error(), "unsupported"))
122		return
123	}
124
125	// Windows case. AppDataDir should exist, at least on our test
126	// machines.
127	require.NoError(t, err)
128	exists, err := FileExists(dir)
129	require.NoError(t, err)
130	require.True(t, exists)
131}
132
133func TestLocalDataDir(t *testing.T) {
134	dir, err := LocalDataDir()
135	if err != nil {
136		// Non-Windows case.
137		require.True(t, strings.HasPrefix(err.Error(), "unsupported"))
138		return
139	}
140
141	// Windows case. LocalDataDir should exist, at least on our
142	// test machines.
143	require.NoError(t, err)
144	exists, err := FileExists(dir)
145	require.NoError(t, err)
146	require.True(t, exists)
147}
148
149func hasMonotonicClock(t time.Time) bool {
150	re := regexp.MustCompile(" m=[-+]([.0-9]+)$")
151	return re.FindString(t.String()) != ""
152}
153
154func TestForceWallClock(t *testing.T) {
155	n := time.Now()
156	require.True(t, hasMonotonicClock(n))
157	require.False(t, hasMonotonicClock(ForceWallClock(n)))
158}
159
160func TestDecodeHexFixed(t *testing.T) {
161	units := []struct {
162		src string
163		dst []byte
164		err string
165	}{
166		{"", []byte{}, ""},
167		{"aa", []byte{170}, ""},
168		{"abcd", []byte{171, 205}, ""},
169
170		{"a", []byte{}, "encoding/hex: odd length hex string"},
171		{"aaa", []byte{170}, "encoding/hex: odd length hex string"},
172		{"aa", []byte{}, "error decoding fixed-length hex: expected 0 bytes but got 1"},
173		{"", []byte{170}, "error decoding fixed-length hex: expected 1 bytes but got 0"},
174		{"abcd", []byte{171, 205, 0}, "error decoding fixed-length hex: expected 3 bytes but got 2"},
175		{"abcd", []byte{171}, "error decoding fixed-length hex: expected 1 bytes but got 2"},
176	}
177	for i, unit := range units {
178		t.Logf("units[%v]", i)
179		buf := make([]byte, len(unit.dst))
180		err := DecodeHexFixed(buf, []byte(unit.src))
181		if unit.err != "" {
182			require.Error(t, err)
183			require.Equal(t, unit.err, err.Error())
184			empty := make([]byte, len(unit.dst))
185			require.True(t, bytes.Equal(empty, buf))
186		} else {
187			require.NoError(t, err)
188			require.Equal(t, unit.dst, buf)
189		}
190	}
191}
192
193func TestDownloadGetFilenames(t *testing.T) {
194	var tests = map[string]string{
195		"abc.def":       "abc.def",
196		"文件.def":        "文件.def",
197		"abc.\u202edef": "abc.%E2%80%AEdef",
198		"abc.\u200fdef": "abc.%E2%80%8Fdef",
199	}
200	for original, expected := range tests {
201		safeFilename := GetSafeFilename(original)
202		require.Equal(t, expected, safeFilename)
203	}
204}
205
206func TestSecureRandomRndRange(t *testing.T) {
207	s := SecureRandom{}
208
209	var tests = []struct {
210		lo        int64
211		hi        int64
212		shouldErr bool
213	}{
214		{0, 0, false},
215		{1, 1, false},
216		{-1, -1, false},
217		{1, 0, true},
218		{-1, -2, true},
219		{2, 5, false},
220		{-1, 10, false},
221		{1, 50, false},
222		{-40, -1, false},
223	}
224
225	for _, test := range tests {
226		for i := 0; i < 10; i++ {
227			r, err := s.RndRange(test.lo, test.hi)
228			if test.shouldErr {
229				require.Error(t, err)
230				continue
231			}
232			require.True(t, r >= test.lo)
233			require.True(t, r <= test.hi)
234		}
235	}
236
237	// basic consistency check that sampled random numbers are different (the
238	// random function is not a constant). Duplicate outputs should happen very
239	// infrequently on a large range. In this case, the test will flake with
240	// probability exactly 1/10^12.
241	r1, err := s.RndRange(0, 1000000000000)
242	require.NoError(t, err)
243	r2, err := s.RndRange(0, 1000000000000)
244	require.NoError(t, err)
245	require.NotEqual(t, r1, r2)
246}
247
248func TestThrottleBatch(t *testing.T) {
249	clock := clockwork.NewFakeClock()
250	throttleBatchClock = clock
251	ch := make(chan int, 100)
252	handler := func(arg interface{}) {
253		v, ok := arg.(int)
254		require.True(t, ok)
255		ch <- v
256	}
257	getVal := func(expected int) {
258		select {
259		case v := <-ch:
260			require.Equal(t, expected, v)
261		case <-time.After(2 * time.Second):
262			require.Fail(t, "no value received")
263		}
264	}
265	noVal := func() {
266		time.Sleep(10 * time.Millisecond)
267		select {
268		case <-ch:
269			require.Fail(t, "no value should have been received")
270		default:
271		}
272	}
273	batcher := func(batchedInt interface{}, singleInt interface{}) interface{} {
274		batched, ok := batchedInt.(int)
275		require.True(t, ok)
276		single, ok := singleInt.(int)
277		require.True(t, ok)
278		return batched + single
279	}
280	reset := func() interface{} {
281		return 0
282	}
283
284	f, cancel := ThrottleBatch(handler, batcher, reset, 200, true)
285	f(2)
286	getVal(2)
287	f(3)
288	f(2)
289	noVal()
290	clock.Advance(300 * time.Millisecond)
291	getVal(5)
292
293	clock.Advance(time.Hour)
294	f(2)
295	getVal(2)
296
297	f(2)
298	noVal()
299	cancel()
300	time.Sleep(100 * time.Millisecond)
301	clock.Advance(300 * time.Millisecond)
302	noVal()
303}
304
305func TestFindFilePathWithNumberSuffix(t *testing.T) {
306	parentDir := os.TempDir()
307	path, err := FindFilePathWithNumberSuffix(parentDir, "", true)
308	require.NoError(t, err)
309	require.True(t, strings.HasPrefix(path, parentDir))
310
311	path, err = FindFilePathWithNumberSuffix(parentDir, "test.txt", true)
312	require.NoError(t, err)
313	require.True(t, strings.HasPrefix(path, parentDir))
314	require.True(t, strings.HasSuffix(path, ".txt"))
315
316	path, err = FindFilePathWithNumberSuffix(parentDir, "", false)
317	require.NoError(t, err)
318	require.Equal(t, filepath.Join(parentDir, " (1)"), path)
319
320	path, err = FindFilePathWithNumberSuffix(parentDir, "test.txt", false)
321	require.NoError(t, err)
322	require.Equal(t, filepath.Join(parentDir, "test.txt"), path)
323
324	path, err = FindFilePathWithNumberSuffix(parentDir, ".txt", false)
325	require.NoError(t, err)
326	require.Equal(t, filepath.Join(parentDir, ".txt"), path)
327}
328