github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/systests/home_test.go (about) 1 package systests 2 3 import ( 4 "testing" 5 "time" 6 7 "github.com/keybase/client/go/client" 8 "github.com/keybase/client/go/libkb" 9 keybase1 "github.com/keybase/client/go/protocol/keybase1" 10 "github.com/keybase/go-framed-msgpack-rpc/rpc" 11 "github.com/stretchr/testify/require" 12 context "golang.org/x/net/context" 13 ) 14 15 func getHome(t *testing.T, u *userPlusDevice, markViewed bool) keybase1.HomeScreen { 16 g := u.tc.G 17 cli, err := client.GetHomeClient(g) 18 require.NoError(t, err) 19 home, err := cli.HomeGetScreen(context.TODO(), keybase1.HomeGetScreenArg{MarkViewed: markViewed, NumFollowSuggestionsWanted: 10}) 20 require.NoError(t, err) 21 return home 22 } 23 24 func markViewed(t *testing.T, u *userPlusDevice) { 25 g := u.tc.G 26 cli, err := client.GetHomeClient(g) 27 require.NoError(t, err) 28 err = cli.HomeMarkViewed(context.TODO()) 29 require.NoError(t, err) 30 } 31 32 func getBadgeState(t *testing.T, u *userPlusDevice) keybase1.BadgeState { 33 g := u.tc.G 34 cli, err := client.GetBadgerClient(g) 35 require.NoError(t, err) 36 ret, err := cli.GetBadgeState(context.TODO()) 37 require.NoError(t, err) 38 return ret 39 } 40 41 func assertTodoPresent(t *testing.T, home keybase1.HomeScreen, wanted keybase1.HomeScreenTodoType, isBadged bool) { 42 for _, item := range home.Items { 43 typ, err := item.Data.T() 44 if err != nil { 45 t.Fatal(err) 46 } 47 if typ == keybase1.HomeScreenItemType_TODO { 48 todo := item.Data.Todo() 49 t.Logf("Checking todo item %v", todo) 50 typ, err := todo.T() 51 if err != nil { 52 t.Fatal(err) 53 } 54 if typ == wanted { 55 require.Equal(t, item.Badged, isBadged) 56 return 57 } 58 } else { 59 t.Logf("Non-todo item: %v", typ) 60 } 61 } 62 t.Fatalf("Failed to find type %s in %+v", wanted, home) 63 } 64 65 func assertTodoNotPresent(t *testing.T, home keybase1.HomeScreen, wanted keybase1.HomeScreenTodoType) { 66 for _, item := range home.Items { 67 typ, err := item.Data.T() 68 if err != nil { 69 t.Fatal(err) 70 } 71 if typ == keybase1.HomeScreenItemType_TODO { 72 todo := item.Data.Todo() 73 typ, err := todo.T() 74 if err != nil { 75 t.Fatal(err) 76 } 77 if typ == wanted { 78 t.Fatalf("Found type %s in %+v, but didn't want to ", wanted, home) 79 } 80 } 81 } 82 } 83 84 func findFollowerInHome(t *testing.T, home keybase1.HomeScreen, f string) (present, badged bool) { 85 for _, item := range home.Items { 86 typ, err := item.Data.T() 87 if err != nil { 88 t.Fatal(err) 89 } 90 if typ != keybase1.HomeScreenItemType_PEOPLE { 91 continue 92 } 93 people := item.Data.People() 94 ptyp, err := people.T() 95 if err != nil { 96 t.Fatal(err) 97 } 98 switch ptyp { 99 case keybase1.HomeScreenPeopleNotificationType_FOLLOWED: 100 follow := people.Followed() 101 if follow.User.Username == f { 102 return true, item.Badged 103 } 104 case keybase1.HomeScreenPeopleNotificationType_FOLLOWED_MULTI: 105 for _, follow := range people.FollowedMulti().Followers { 106 if follow.User.Username == f { 107 return true, item.Badged 108 } 109 } 110 } 111 } 112 return false, false 113 } 114 115 func postBio(t *testing.T, u *userPlusDevice) { 116 g := u.tc.G 117 cli, err := client.GetUserClient(g) 118 require.NoError(t, err) 119 arg := keybase1.ProfileEditArg{ 120 FullName: "Boaty McBoatface", 121 Location: "The Sea, The Sea", 122 Bio: "Just your average stupidly named vessel", 123 } 124 err = cli.ProfileEdit(context.TODO(), arg) 125 require.NoError(t, err) 126 } 127 128 type homeUI struct { 129 refreshed bool 130 } 131 132 func (h *homeUI) HomeUIRefresh(_ context.Context) (err error) { 133 h.refreshed = true 134 return nil 135 } 136 137 // Wait for a gregor message to fill in the badge state, for at most ~10s. 138 // Hopefully this is enough for slow CI but you never know. 139 func pollForTrue(t *testing.T, g *libkb.GlobalContext, poller func(i int) bool) { 140 // Hopefully this is enough for slow CI but you never know. 141 wait := 10 * time.Millisecond * libkb.CITimeMultiplier(g) 142 found := false 143 for i := 0; i < 10; i++ { 144 if poller(i) { 145 found = true 146 break 147 } 148 g.Log.Debug("Didn't get an update; waiting %s more", wait) 149 time.Sleep(wait) 150 wait *= 2 151 } 152 require.True(t, found, "whether condition was satisfied after polling ended") 153 } 154 155 func TestHome(t *testing.T) { 156 tt := newTeamTester(t) 157 defer tt.cleanup() 158 159 // Let's add user with paper key, so we burn through "Paper Key" todo 160 // item, and then we will still have two todo items lined up next with 161 // one badged. This is a bit brittle since if the server-side logic 162 // changes, this test will break. But let's leave it for now. 163 tt.addUserWithPaper("alice") 164 alice := tt.users[0] 165 g := alice.tc.G 166 167 home := getHome(t, alice, false) 168 initialVersion := home.Version 169 170 require.True(t, (initialVersion > 0), "initial version should be > 0") 171 // Our first todo is VERIFY_ALL_EMAIL, that's badged 172 assertTodoPresent(t, home, keybase1.HomeScreenTodoType_VERIFY_ALL_EMAIL, true /* isBadged */) 173 // followed by BIO todo 174 assertTodoPresent(t, home, keybase1.HomeScreenTodoType_BIO, false /* isBadged */) 175 176 var countPre int 177 pollForTrue(t, g, func(i int) bool { 178 badges := getBadgeState(t, alice) 179 g.Log.Debug("Iter loop %d badge state: %+v", i, badges) 180 countPre = badges.HomeTodoItems 181 return (countPre == 1) 182 }) 183 184 hui := homeUI{} 185 attachHomeUI(t, g, &hui) 186 187 postBio(t, alice) 188 189 pollForTrue(t, g, func(i int) bool { 190 home = getHome(t, alice, false) 191 badges := getBadgeState(t, alice) 192 g.Log.Debug("Iter %d of check loop: Home is: %+v; BadgeState is: %+v", i, home, badges) 193 return (home.Version > initialVersion && badges.HomeTodoItems < countPre) 194 }) 195 196 pollForTrue(t, g, func(i int) bool { 197 g.Log.Debug("Iter %d of check loop for home refresh; value is %v", i, hui.refreshed) 198 return hui.refreshed 199 }) 200 201 assertTodoNotPresent(t, home, keybase1.HomeScreenTodoType_BIO) 202 203 tt.addUser("bob") 204 bob := tt.users[1] 205 iui := newSimpleIdentifyUI() 206 attachIdentifyUI(t, bob.tc.G, iui) 207 iui.confirmRes = keybase1.ConfirmResult{IdentityConfirmed: true, RemoteConfirmed: true, AutoConfirmed: true} 208 bob.track(alice.username) 209 210 var badged bool 211 pollForTrue(t, g, func(i int) bool { 212 home = getHome(t, alice, false) 213 var present bool 214 present, badged = findFollowerInHome(t, home, bob.username) 215 return present 216 }) 217 require.True(t, badged, "when we find bob, he should be badged") 218 219 // This should clear the badge on bob in the home screen. 220 markViewed(t, alice) 221 222 pollForTrue(t, g, func(i int) bool { 223 var present bool 224 home = getHome(t, alice, false) 225 present, badged = findFollowerInHome(t, home, bob.username) 226 return present && !badged 227 }) 228 } 229 230 func attachHomeUI(t *testing.T, g *libkb.GlobalContext, hui keybase1.HomeUIInterface) { 231 cli, xp, err := client.GetRPCClientWithContext(g) 232 require.NoError(t, err) 233 srv := rpc.NewServer(xp, nil) 234 err = srv.Register(keybase1.HomeUIProtocol(hui)) 235 require.NoError(t, err) 236 ncli := keybase1.DelegateUiCtlClient{Cli: cli} 237 err = ncli.RegisterHomeUI(context.TODO()) 238 require.NoError(t, err) 239 } 240 241 func TestHomeBlockSpammer(t *testing.T) { 242 tt := newTeamTester(t) 243 defer tt.cleanup() 244 245 tt.addUserWithPaper("alice") 246 alice := tt.users[0] 247 248 tt.addUser("bob") 249 bob := tt.users[1] 250 tt.addUser("chaz") 251 charlie := tt.users[2] 252 253 trackAlice := func(u *userPlusDevice) { 254 iui := newSimpleIdentifyUI() 255 attachIdentifyUI(t, u.tc.G, iui) 256 iui.confirmRes = keybase1.ConfirmResult{IdentityConfirmed: true, RemoteConfirmed: true, AutoConfirmed: true} 257 u.track(alice.username) 258 } 259 260 trackAlice(bob) 261 trackAlice(charlie) 262 263 probeUserInAliceHome := func(u *userPlusDevice, shouldBePresent bool) { 264 pollForTrue(t, alice.tc.G, func(i int) bool { 265 home := getHome(t, alice, false) 266 var present bool 267 present, _ = findFollowerInHome(t, home, u.username) 268 return (present == shouldBePresent) 269 }) 270 } 271 272 probeUserInAliceHome(bob, true) 273 probeUserInAliceHome(charlie, true) 274 275 alice.block(bob.username, true /*chat*/, false /*follow*/) 276 probeUserInAliceHome(charlie, true) 277 probeUserInAliceHome(bob, false) 278 279 alice.block(charlie.username, false /*chat*/, true /*follow*/) 280 probeUserInAliceHome(charlie, false) 281 probeUserInAliceHome(bob, false) 282 }