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  }