1// Copyright 2015 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package systests
5
6import (
7	"fmt"
8	"testing"
9	"time"
10
11	"github.com/keybase/client/go/client"
12	"github.com/keybase/client/go/service"
13
14	keybase1 "github.com/keybase/client/go/protocol/keybase1"
15	"github.com/keybase/go-framed-msgpack-rpc/rpc"
16	context "golang.org/x/net/context"
17)
18
19type delegateUI struct {
20	T         *testing.T
21	ch        chan error
22	delegated bool
23	started   bool
24	finished  bool
25	canceled  bool
26
27	launchedGithub  bool
28	foundGithub     bool
29	launchedTwitter bool
30	foundTwitter    bool
31}
32
33// delegateUI implements the keybase1.IdentifyUiInterface
34var _ keybase1.IdentifyUiInterface = (*delegateUI)(nil)
35
36func (d *delegateUI) checkDelegated() error {
37	if !d.delegated {
38		return d.setError(fmt.Errorf("Can't run UI since it wasn't properly delegated"))
39	}
40	return nil
41}
42
43func (d *delegateUI) setError(e error) error {
44	d.T.Logf("delegateUI error: %v", e)
45	fmt.Printf("delegateUI error: %v\n", e)
46	go func() { d.ch <- e }()
47	return e
48}
49
50func (d *delegateUI) checkStarted() error {
51	if err := d.checkDelegated(); err != nil {
52		return err
53	}
54	if !d.started {
55		return d.setError(fmt.Errorf("Can't run UI since it wasn't properly started"))
56	}
57	if d.canceled {
58		return d.setError(fmt.Errorf("Can't run UI after Cancel() was called"))
59	}
60	return nil
61}
62
63func (d *delegateUI) DelegateIdentifyUI(context.Context) (int, error) {
64	d.delegated = true
65	return 1, nil
66}
67func (d *delegateUI) Start(context.Context, keybase1.StartArg) error {
68	if err := d.checkDelegated(); err != nil {
69		return err
70	}
71	d.started = true
72	return nil
73}
74
75func (d *delegateUI) DisplayKey(context.Context, keybase1.DisplayKeyArg) error {
76	return d.checkStarted()
77}
78func (d *delegateUI) ReportLastTrack(context.Context, keybase1.ReportLastTrackArg) error {
79	return d.checkStarted()
80}
81func (d *delegateUI) LaunchNetworkChecks(_ context.Context, arg keybase1.LaunchNetworkChecksArg) error {
82	if err := d.checkStarted(); err != nil {
83		return err
84	}
85	for _, proof := range arg.Identity.Proofs {
86		switch proof.Proof.Key {
87		case "twitter":
88			d.launchedTwitter = true
89		case "github":
90			d.launchedGithub = true
91		}
92	}
93	return nil
94}
95func (d *delegateUI) DisplayTrackStatement(context.Context, keybase1.DisplayTrackStatementArg) error {
96	return d.checkStarted()
97}
98func (d *delegateUI) ReportTrackToken(context.Context, keybase1.ReportTrackTokenArg) error {
99	return d.checkStarted()
100}
101func (d *delegateUI) FinishWebProofCheck(context.Context, keybase1.FinishWebProofCheckArg) error {
102	return d.checkStarted()
103}
104func (d *delegateUI) FinishSocialProofCheck(_ context.Context, arg keybase1.FinishSocialProofCheckArg) error {
105	if err := d.checkStarted(); err != nil {
106		return err
107	}
108	switch arg.Rp.Key {
109	case "twitter":
110		d.foundTwitter = true
111	case "github":
112		d.foundGithub = true
113	}
114	return nil
115}
116func (d *delegateUI) DisplayCryptocurrency(context.Context, keybase1.DisplayCryptocurrencyArg) error {
117	return d.checkStarted()
118}
119func (d *delegateUI) DisplayStellarAccount(context.Context, keybase1.DisplayStellarAccountArg) error {
120	return d.checkStarted()
121}
122func (d *delegateUI) DisplayUserCard(context.Context, keybase1.DisplayUserCardArg) error {
123	return d.checkStarted()
124}
125func (d *delegateUI) Confirm(context.Context, keybase1.ConfirmArg) (res keybase1.ConfirmResult, err error) {
126	if err = d.checkStarted(); err != nil {
127		return res, err
128	}
129	res.IdentityConfirmed = true
130	res.RemoteConfirmed = true
131	return res, nil
132}
133func (d *delegateUI) Cancel(context.Context, int) error {
134	close(d.ch)
135	d.canceled = true
136	return nil
137}
138func (d *delegateUI) Finish(context.Context, int) error {
139	if err := d.checkStarted(); err != nil {
140		return err
141	}
142	d.finished = true
143	return nil
144}
145func (d *delegateUI) Dismiss(context.Context, keybase1.DismissArg) error {
146	return d.checkStarted()
147}
148
149func (d *delegateUI) DisplayTLFCreateWithInvite(context.Context, keybase1.DisplayTLFCreateWithInviteArg) error {
150	return nil
151}
152
153func (d *delegateUI) checkSuccess() error {
154	if !d.launchedGithub || !d.foundGithub || !d.launchedTwitter || !d.foundTwitter || !d.canceled {
155		return fmt.Errorf("Bad final state for delegate UI: %+v", d)
156	}
157	return nil
158}
159
160func newDelegateUI(t *testing.T) *delegateUI {
161	return &delegateUI{
162		T:  t,
163		ch: make(chan error),
164	}
165}
166
167func TestDelegateUI(t *testing.T) {
168	tc := setupTest(t, "delegate_ui")
169	defer tc.Cleanup()
170
171	tc1 := cloneContext(tc)
172	defer tc1.Cleanup()
173	tc2 := cloneContext(tc)
174	defer tc2.Cleanup()
175
176	stopCh := make(chan error)
177	svc := service.NewService(tc.G, false)
178	startCh := svc.GetStartChannel()
179	go func() {
180		err := svc.Run()
181		if err != nil {
182			t.Logf("Running the service produced an error: %v", err)
183		}
184		stopCh <- err
185	}()
186
187	// Wait for the server to start up
188	<-startCh
189	dui := newDelegateUI(t)
190
191	launchDelegateUI := func(dui *delegateUI) error {
192		cli, xp, err := client.GetRPCClientWithContext(tc2.G)
193		if err != nil {
194			return err
195		}
196		srv := rpc.NewServer(xp, nil)
197		if err = srv.Register(keybase1.IdentifyUiProtocol(dui)); err != nil {
198			return err
199		}
200		ncli := keybase1.DelegateUiCtlClient{Cli: cli}
201		return ncli.RegisterIdentifyUI(context.TODO())
202	}
203
204	// Launch the delegate UI
205	if err := launchDelegateUI(dui); err != nil {
206		t.Fatal(err)
207	}
208
209	id := client.NewCmdIDRunner(tc1.G)
210	id.SetUser("t_alice")
211	id.UseDelegateUI()
212	if err := id.Run(); err != nil {
213		t.Fatalf("Error in Run: %v", err)
214	}
215
216	// We should get either a 'done' or an 'error' from the delegateUI.
217	select {
218	case err, ok := <-dui.ch:
219		if err != nil {
220			t.Errorf("Error with delegate UI: %v", err)
221		} else if ok {
222			t.Errorf("Delegate UI didn't close the channel properly")
223		} else if err = dui.checkSuccess(); err != nil {
224			t.Error(err)
225		}
226	case <-time.After(20 * time.Second):
227		t.Fatal("no callback from delegate UI")
228	}
229
230	if err := CtlStop(tc1.G); err != nil {
231		t.Errorf("Error in stopping service: %v", err)
232	}
233
234	// If the server failed, it's also an error
235	if err := <-stopCh; err != nil {
236		t.Fatal(err)
237	}
238}
239