github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/systests/delegate_id3_ui_test.go (about) 1 // Copyright 2015 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 package systests 5 6 import ( 7 "fmt" 8 "sync" 9 "testing" 10 "time" 11 12 "github.com/keybase/client/go/client" 13 "github.com/keybase/client/go/libkb" 14 "github.com/keybase/client/go/service" 15 16 keybase1 "github.com/keybase/client/go/protocol/keybase1" 17 "github.com/keybase/go-framed-msgpack-rpc/rpc" 18 "github.com/stretchr/testify/require" 19 context "golang.org/x/net/context" 20 ) 21 22 type delegateID3UI struct { 23 libkb.Contextified 24 sync.Mutex 25 T *testing.T 26 ch chan struct{} 27 28 guiid keybase1.Identify3GUIID 29 displayedCard bool 30 31 launchedGithub bool 32 foundGithub bool 33 launchedTwitter bool 34 foundTwitter bool 35 } 36 37 // delegateUI implements the keybase1.IdentifyUiInterface 38 var _ keybase1.Identify3UiInterface = (*delegateID3UI)(nil) 39 40 func (d *delegateID3UI) Identify3ShowTracker(_ context.Context, arg keybase1.Identify3ShowTrackerArg) error { 41 d.Lock() 42 defer d.Unlock() 43 d.guiid = arg.GuiID 44 require.Equal(d.T, string(arg.Assertion), "t_alice") 45 return nil 46 } 47 48 func (d *delegateID3UI) Identify3UpdateRow(_ context.Context, arg keybase1.Identify3Row) error { 49 d.Lock() 50 defer d.Unlock() 51 require.Equal(d.T, d.guiid, arg.GuiID) 52 53 poke := func(launched *bool, found *bool) { 54 switch arg.State { 55 case keybase1.Identify3RowState_CHECKING: 56 require.False(d.T, *found) 57 require.False(d.T, *launched) 58 *launched = true 59 case keybase1.Identify3RowState_VALID: 60 require.True(d.T, *launched) 61 require.False(d.T, *found) 62 *found = true 63 default: 64 require.Fail(d.T, fmt.Sprintf("unexpected state: %+v", arg)) 65 } 66 } 67 switch arg.Key { 68 case "twitter": 69 poke(&d.launchedTwitter, &d.foundTwitter) 70 case "github": 71 poke(&d.launchedGithub, &d.foundGithub) 72 } 73 return nil 74 } 75 76 func (d *delegateID3UI) Identify3UserReset(context.Context, keybase1.Identify3GUIID) error { 77 require.Fail(d.T, "did not exect reset user scenario") 78 return nil 79 } 80 81 func (d *delegateID3UI) Identify3UpdateUserCard(_ context.Context, arg keybase1.Identify3UpdateUserCardArg) error { 82 d.Lock() 83 defer d.Unlock() 84 require.Equal(d.T, d.guiid, arg.GuiID) 85 d.displayedCard = true 86 return nil 87 } 88 89 func (d *delegateID3UI) Identify3TrackerTimedOut(context.Context, keybase1.Identify3GUIID) error { 90 require.Fail(d.T, "did not expect a tracker time out") 91 return nil 92 } 93 94 func (d *delegateID3UI) Identify3Result(_ context.Context, arg keybase1.Identify3ResultArg) error { 95 d.Lock() 96 require.Equal(d.T, arg.GuiID, d.guiid) 97 require.Equal(d.T, arg.Result, keybase1.Identify3ResultType_OK) 98 d.Unlock() 99 close(d.ch) 100 return nil 101 } 102 103 func (d *delegateID3UI) Identify3Summary(_ context.Context, arg keybase1.Identify3Summary) error { 104 return nil 105 } 106 107 func newDelegateID3UI(g *libkb.GlobalContext, t *testing.T) *delegateID3UI { 108 return &delegateID3UI{ 109 Contextified: libkb.NewContextified(g), 110 T: t, 111 ch: make(chan struct{}), 112 } 113 } 114 115 // checkSuccess makes sure that all 3 success markers are true. It would be nice 116 // if we just checked all 3 bools, but there's a race because of Notify() use, 117 // since we don't get a guarantee of when the Notify()s go out. 118 func (d *delegateID3UI) checkSuccess() { 119 120 check := func() bool { 121 d.Lock() 122 defer d.Unlock() 123 if !d.foundTwitter { 124 d.T.Logf("delegate3IDUI#checkSuccess: check twitter failed") 125 return false 126 } 127 if !d.foundGithub { 128 d.T.Logf("delegate3IDUI#checkSuccess: check github failed") 129 return false 130 } 131 if !d.displayedCard { 132 d.T.Logf("delegate3IDUI#checkSuccess: didn't display card") 133 return false 134 } 135 return true 136 } 137 n := 10 138 wait := 2 * time.Millisecond 139 for i := 0; i < n; i++ { 140 if check() { 141 return 142 } 143 d.T.Logf("Hit a race! Waiting %v for delegateID3UI#checkSuccess check to work", wait) 144 time.Sleep(wait) 145 wait *= 2 146 } 147 d.T.Fatalf("Tried %d times to get successes and failed", n) 148 } 149 150 func TestDelegateIdentify3UI(t *testing.T) { 151 tc := setupTest(t, "delegate_ui") 152 defer tc.Cleanup() 153 tc1 := cloneContext(tc) 154 defer tc1.Cleanup() 155 tc2 := cloneContext(tc) 156 defer tc2.Cleanup() 157 158 stopCh := make(chan error) 159 svc := service.NewService(tc.G, false) 160 startCh := svc.GetStartChannel() 161 go func() { 162 err := svc.Run() 163 if err != nil { 164 t.Logf("Running the service produced an error: %v", err) 165 } 166 stopCh <- err 167 }() 168 169 // Wait for the server to start up 170 <-startCh 171 dui := newDelegateID3UI(tc.G, t) 172 173 launchDelegateUI := func(dui *delegateID3UI) error { 174 cli, xp, err := client.GetRPCClientWithContext(tc2.G) 175 if err != nil { 176 return err 177 } 178 srv := rpc.NewServer(xp, nil) 179 if err = srv.Register(keybase1.Identify3UiProtocol(dui)); err != nil { 180 return err 181 } 182 ncli := keybase1.DelegateUiCtlClient{Cli: cli} 183 return ncli.RegisterIdentify3UI(context.TODO()) 184 } 185 186 // Launch the delegate UI 187 err := launchDelegateUI(dui) 188 require.NoError(t, err) 189 190 id := client.NewCmdIDRunner(tc1.G) 191 id.SetUser("t_alice") 192 id.UseDelegateUI() 193 err = id.Run() 194 require.NoError(t, err) 195 196 // We should get a close on this channel when the UI is read to go. 197 _, eof := <-dui.ch 198 require.False(t, eof) 199 dui.checkSuccess() 200 201 err = CtlStop(tc1.G) 202 require.NoError(t, err) 203 204 // If the server failed, it's also an error 205 err = <-stopCh 206 require.NoError(t, err) 207 }