1package systests
2
3import (
4	"context"
5	"io/ioutil"
6	"net/http"
7	"testing"
8
9	"github.com/davecgh/go-spew/spew"
10	"github.com/keybase/client/go/engine"
11	"github.com/keybase/client/go/kbtest"
12	"github.com/keybase/client/go/libkb"
13	keybase1 "github.com/keybase/client/go/protocol/keybase1"
14	"github.com/stretchr/testify/require"
15)
16
17func TestProofSuggestions(t *testing.T) {
18	tt := newTeamTester(t)
19	defer tt.cleanup()
20
21	alice := tt.addUser("abc")
22
23	res, err := alice.userClient.ProofSuggestions(context.Background(), 0)
24	require.NoError(t, err)
25	t.Logf("suggestions: %v", spew.Sdump(res))
26	expected := keybase1.ProofSuggestionsRes{
27		ShowMore: true,
28		Suggestions: []keybase1.ProofSuggestion{{
29			Key:           "twitter",
30			ProfileText:   "Prove your Twitter",
31			PickerText:    "Twitter",
32			PickerSubtext: "twitter.com",
33		}, {
34			Key:           "github",
35			ProfileText:   "Prove your GitHub",
36			PickerText:    "GitHub",
37			PickerSubtext: "github.com",
38		}, {
39			Key:           "reddit",
40			ProfileText:   "Prove your Reddit",
41			PickerText:    "Reddit",
42			PickerSubtext: "reddit.com",
43		}, {
44			Key:           "hackernews",
45			ProfileText:   "Prove your Hacker News",
46			PickerText:    "Hacker News",
47			PickerSubtext: "news.ycombinator.com",
48		}, {
49			Key:           "rooter",
50			ProfileText:   "Prove your Rooter",
51			PickerText:    "Rooter",
52			PickerSubtext: "",
53		}, {
54			Key:           "gubble.social",
55			ProfileText:   "Prove your Gubble.social",
56			PickerText:    "Gubble.social",
57			PickerSubtext: "Gubble instance",
58		}, {
59			Key:           "web",
60			ProfileText:   "Prove your website",
61			PickerText:    "Your own website",
62			PickerSubtext: "",
63		}, {
64			Key:           "pgp",
65			ProfileText:   "Add a PGP key",
66			PickerText:    "PGP key",
67			PickerSubtext: "",
68		}, {
69			Key:           "btc",
70			ProfileText:   "Set a Bitcoin address",
71			PickerText:    "Bitcoin address",
72			PickerSubtext: "",
73		}, {
74			Key:           "zcash",
75			ProfileText:   "Set a Zcash address",
76			PickerText:    "Zcash address",
77			PickerSubtext: "",
78		}, {
79			Key:           "gubble.cloud",
80			BelowFold:     true,
81			ProfileText:   "Prove your Gubble.cloud",
82			PickerText:    "Gubble.cloud",
83			PickerSubtext: "Gubble instance",
84		}, {
85			Key:           "theqrl.org",
86			BelowFold:     true,
87			ProfileText:   "Prove your Quantum Resistant Ledger",
88			PickerText:    "Quantum Resistant Ledger",
89			PickerSubtext: "theqrl.org",
90		}}}
91	require.Equal(t, expected.ShowMore, res.ShowMore)
92	require.True(t, len(res.Suggestions) >= len(expected.Suggestions), "should be at least as many results as expected")
93	iconExempt := map[string]struct{}{
94		"gubble-with-dashes.dot": {},
95		"mastodon.local":         {},
96	}
97	for _, b := range res.Suggestions {
98		if _, exempt := iconExempt[b.Key]; exempt {
99			// Skip checking for logos for this one.
100			continue
101		}
102		require.Len(t, b.ProfileIcon, 2)
103		for _, icon := range b.ProfileIcon {
104			checkIcon(t, icon)
105		}
106		require.Len(t, b.ProfileIconDarkmode, 2)
107		for _, icon := range b.ProfileIconDarkmode {
108			checkIcon(t, icon)
109		}
110		require.Len(t, b.ProfileIcon, 2)
111		for _, icon := range b.PickerIcon {
112			checkIcon(t, icon)
113		}
114		require.Len(t, b.PickerIconDarkmode, 2)
115		for _, icon := range b.PickerIconDarkmode {
116			checkIcon(t, icon)
117		}
118
119	}
120	var found int
121	for i, b := range res.Suggestions {
122		if found >= len(expected.Suggestions) {
123			t.Logf("done")
124			break
125		}
126		t.Logf("row %v %v", i, b.Key)
127		a := expected.Suggestions[found]
128		if a.Key != b.Key {
129			t.Logf("skipping %v (mismatch)", a.Key)
130			continue
131		}
132		found++
133		require.Equal(t, a.Key, b.Key)
134		require.Equal(t, a.BelowFold, b.BelowFold)
135		require.Equal(t, a.ProfileText, b.ProfileText)
136		require.Equal(t, a.PickerText, b.PickerText)
137		require.Equal(t, a.PickerSubtext, b.PickerSubtext)
138
139	}
140	require.Len(t, expected.Suggestions, found)
141}
142
143func checkIcon(t testing.TB, icon keybase1.SizedImage) {
144	if icon.Width < 2 {
145		t.Fatalf("unreasonable icon size")
146	}
147	if kbtest.SkipIconRemoteTest() {
148		t.Logf("Skipping icon remote test")
149		require.True(t, len(icon.Path) > 8)
150	} else {
151		resp, err := http.Get(icon.Path)
152		require.Equal(t, 200, resp.StatusCode, "icon file should be reachable: %v", icon.Path)
153		require.NoError(t, err)
154		body, err := ioutil.ReadAll(resp.Body)
155		require.NoError(t, err)
156		if len(body) < 150 {
157			t.Fatalf("unreasonable icon payload size")
158		}
159	}
160}
161
162func TestProofSuggestionsOmitProven(t *testing.T) {
163	tt := newTeamTester(t)
164	defer tt.cleanup()
165	alice := tt.addUser("abc")
166
167	assertOmitted := func(service string) {
168		res, err := alice.userClient.ProofSuggestions(context.Background(), 0)
169		require.NoError(t, err)
170		for _, suggestion := range res.Suggestions {
171			require.NotEqual(t, service, suggestion.Key)
172		}
173	}
174
175	alice.proveRooter()
176	t.Logf("alice proved rooter, so rooter is no longer suggested")
177	assertOmitted("rooter")
178
179	eng := engine.NewCryptocurrencyEngine(alice.MetaContext().G(), keybase1.RegisterAddressArg{
180		Address: "zcCk6rKzynC4tT1Rmg325A5Xw81Ck3S6nD6mtPWCXaMtyFczkyU4kYjEhrcz2QKfF5T2siWGyJNxWo43XWT3qk5YpPhFGj2",
181	})
182	err := engine.RunEngine2(alice.MetaContext().WithUIs(libkb.UIs{
183		LogUI:    alice.MetaContext().G().Log,
184		SecretUI: alice.newSecretUI(),
185	}), eng)
186	require.NoError(t, err)
187	t.Logf("alice added a zcash address, so zcash is no longer suggested")
188	assertOmitted("zcash")
189}
190