github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/chat/botcommands_test.go (about)

     1  package chat
     2  
     3  import (
     4  	"errors"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/keybase/client/go/chat/types"
     9  	"github.com/keybase/client/go/chat/utils"
    10  	"github.com/keybase/client/go/libkb"
    11  	"github.com/keybase/client/go/protocol/chat1"
    12  	"github.com/keybase/client/go/protocol/gregor1"
    13  	"github.com/keybase/client/go/protocol/keybase1"
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  type dummyUIRouter struct {
    18  	libkb.UIRouter
    19  }
    20  
    21  func (d dummyUIRouter) GetChatUI() (libkb.ChatUI, error) {
    22  	return nil, nil
    23  }
    24  
    25  func (d dummyUIRouter) Shutdown() {}
    26  
    27  func TestBotCommandManager(t *testing.T) {
    28  	useRemoteMock = false
    29  	defer func() { useRemoteMock = true }()
    30  	ctc := makeChatTestContext(t, "TestBotCommandManager", 3)
    31  	defer ctc.cleanup()
    32  
    33  	timeout := 20 * time.Second
    34  	users := ctc.users()
    35  	listener := newServerChatListener()
    36  	ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener)
    37  	tc := ctc.world.Tcs[users[0].Username]
    38  	tc.G.UIRouter = dummyUIRouter{}
    39  	tc1 := ctc.world.Tcs[users[1].Username]
    40  	tc1.G.UIRouter = dummyUIRouter{}
    41  	tc2 := ctc.world.Tcs[users[2].Username]
    42  	tc2.G.UIRouter = dummyUIRouter{}
    43  	ctx := ctc.as(t, users[0]).startCtx
    44  	ctx1 := ctc.as(t, users[1]).startCtx
    45  	ctx2 := ctc.as(t, users[2]).startCtx
    46  	uid := gregor1.UID(users[0].GetUID().ToBytes())
    47  	botua := users[2]
    48  	t.Logf("uid: %s", uid)
    49  	listener0 := newServerChatListener()
    50  	ctc.as(t, users[0]).h.G().NotifyRouter.AddListener(listener0)
    51  
    52  	impConv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT,
    53  		chat1.ConversationMembersType_IMPTEAMNATIVE)
    54  	impConv1 := mustCreateConversationForTest(t, ctc, users[1], chat1.TopicType_CHAT,
    55  		chat1.ConversationMembersType_IMPTEAMNATIVE, users[0])
    56  	t.Logf("impconv: %s", impConv.Id)
    57  	t.Logf("impconv1: %s", impConv1.Id)
    58  	teamConv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT,
    59  		chat1.ConversationMembersType_TEAM)
    60  	t.Logf("teamconv: %x", teamConv.Id.DbShortForm())
    61  
    62  	// test public
    63  	alias := "MIKE BOT"
    64  	commands := []chat1.AdvertiseCommandsParam{
    65  		{
    66  			Typ: chat1.BotCommandsAdvertisementTyp_PUBLIC,
    67  			Commands: []chat1.UserBotCommandInput{
    68  				{
    69  					Name:        "status",
    70  					Description: "get status",
    71  					Usage:       "just type it",
    72  				},
    73  			},
    74  		},
    75  	}
    76  	require.NoError(t, tc.Context().BotCommandManager.Advertise(ctx, &alias, commands))
    77  	cmds, _, err := tc.Context().BotCommandManager.ListCommands(ctx, impConv.Id)
    78  	require.NoError(t, err)
    79  	require.Zero(t, len(cmds))
    80  
    81  	errCh, err := tc.Context().BotCommandManager.UpdateCommands(ctx, impConv.Id, nil)
    82  	require.NoError(t, err)
    83  
    84  	errCh1, err := tc1.Context().BotCommandManager.UpdateCommands(ctx1, impConv1.Id, nil)
    85  	require.NoError(t, err)
    86  
    87  	readErrCh := func(errCh chan error) error {
    88  		select {
    89  		case err := <-errCh:
    90  			return err
    91  		case <-time.After(timeout):
    92  			return errors.New("timeout")
    93  		}
    94  	}
    95  	select {
    96  	case convID := <-listener0.convUpdate:
    97  		require.Equal(t, impConv.Id, convID)
    98  	case <-time.After(timeout):
    99  		require.Fail(t, "no stale")
   100  	}
   101  	impConvLocal, err := utils.GetVerifiedConv(ctx, tc.Context(), uid, impConv.Id,
   102  		types.InboxSourceDataSourceAll)
   103  	require.NoError(t, err)
   104  	typ, err := impConvLocal.BotCommands.Typ()
   105  	require.NoError(t, err)
   106  	require.Equal(t, chat1.ConversationCommandGroupsTyp_CUSTOM, typ)
   107  	require.Equal(t, 1, len(impConvLocal.BotCommands.Custom().Commands))
   108  	require.Equal(t, "status", impConvLocal.BotCommands.Custom().Commands[0].Name)
   109  	require.NoError(t, readErrCh(errCh))
   110  	cmds, _, err = tc.Context().BotCommandManager.ListCommands(ctx, impConv.Id)
   111  	require.NoError(t, err)
   112  	require.Equal(t, 1, len(cmds))
   113  	require.Equal(t, "status", cmds[0].Name)
   114  
   115  	// make sure the cmds are cached
   116  	for i := 0; i < 5; i++ {
   117  		errCh, err = tc.Context().BotCommandManager.UpdateCommands(ctx, impConv.Id, nil)
   118  		require.NoError(t, err)
   119  		require.NoError(t, readErrCh(errCh))
   120  	}
   121  	select {
   122  	case <-listener0.convUpdate:
   123  		require.Fail(t, "commands not cached")
   124  	default:
   125  	}
   126  
   127  	require.NoError(t, readErrCh(errCh1))
   128  	cmds, _, err = tc1.Context().BotCommandManager.ListCommands(ctx1, impConv1.Id)
   129  	require.NoError(t, err)
   130  	require.Equal(t, 1, len(cmds))
   131  	require.Equal(t, "status", cmds[0].Name)
   132  
   133  	// test team
   134  	teamID, err := keybase1.TeamIDFromString(teamConv.Triple.Tlfid.String())
   135  	require.NoError(t, err)
   136  	pollForSeqno := func(expectedSeqno keybase1.Seqno) {
   137  		found := false
   138  		for !found {
   139  			select {
   140  			case teamChange := <-listener.teamChangedByID:
   141  				found = teamChange.TeamID == teamID &&
   142  					teamChange.LatestSeqno == expectedSeqno
   143  			case <-time.After(20 * time.Second):
   144  				require.Fail(t, "no event received")
   145  			}
   146  		}
   147  	}
   148  
   149  	commands = append(commands, chat1.AdvertiseCommandsParam{
   150  		Typ: chat1.BotCommandsAdvertisementTyp_TLFID_CONVS,
   151  		Commands: []chat1.UserBotCommandInput{{
   152  			Name: "teamconvonly",
   153  		}},
   154  		TeamName: &teamConv.TlfName,
   155  	}, chat1.AdvertiseCommandsParam{
   156  		Typ: chat1.BotCommandsAdvertisementTyp_TLFID_MEMBERS,
   157  		Commands: []chat1.UserBotCommandInput{{
   158  			Name: "teammembsonly",
   159  		}},
   160  		TeamName: &teamConv.TlfName,
   161  	}, chat1.AdvertiseCommandsParam{
   162  		Typ: chat1.BotCommandsAdvertisementTyp_CONV,
   163  		Commands: []chat1.UserBotCommandInput{{
   164  			Name: "conv",
   165  		}},
   166  		ConvID: &teamConv.Id,
   167  	})
   168  	require.NoError(t, tc.Context().BotCommandManager.Advertise(ctx, &alias, commands))
   169  
   170  	errCh, err = tc.Context().BotCommandManager.UpdateCommands(ctx, impConv.Id, nil)
   171  	require.NoError(t, err)
   172  	require.NoError(t, readErrCh(errCh))
   173  
   174  	errChT, err := tc.Context().BotCommandManager.UpdateCommands(ctx, teamConv.Id, nil)
   175  	require.NoError(t, err)
   176  	require.NoError(t, readErrCh(errChT))
   177  
   178  	errCh1, err = tc1.Context().BotCommandManager.UpdateCommands(ctx, impConv1.Id, nil)
   179  	require.NoError(t, err)
   180  	require.NoError(t, readErrCh(errCh1))
   181  
   182  	cmds, _, err = tc.Context().BotCommandManager.ListCommands(ctx, impConv.Id)
   183  	require.NoError(t, err)
   184  	require.Equal(t, 2, len(cmds))
   185  	cmds, _, err = tc.Context().BotCommandManager.ListCommands(ctx, teamConv.Id)
   186  	require.NoError(t, err)
   187  	require.Equal(t, 4, len(cmds))
   188  	cmds, _, err = tc1.Context().BotCommandManager.ListCommands(ctx1, impConv1.Id)
   189  	require.NoError(t, err)
   190  	require.Equal(t, 1, len(cmds))
   191  
   192  	err = ctc.as(t, users[0]).chatLocalHandler().AddBotMember(ctx, chat1.AddBotMemberArg{
   193  		ConvID:      teamConv.Id,
   194  		Username:    botua.Username,
   195  		Role:        keybase1.TeamRole_RESTRICTEDBOT,
   196  		BotSettings: &keybase1.TeamBotSettings{Cmds: true},
   197  	})
   198  	require.NoError(t, err)
   199  	require.NoError(t, tc2.Context().BotCommandManager.Advertise(ctx2, &alias, commands))
   200  	pollForSeqno(3)
   201  
   202  	errChT, err = tc.Context().BotCommandManager.UpdateCommands(ctx, teamConv.Id, nil)
   203  	require.NoError(t, err)
   204  	require.NoError(t, readErrCh(errChT))
   205  
   206  	cmds, _, err = tc.Context().BotCommandManager.ListCommands(ctx, teamConv.Id)
   207  	require.NoError(t, err)
   208  	require.Equal(t, 8, len(cmds))
   209  
   210  	// impteams can't advertise on team types
   211  	commands = append(commands, chat1.AdvertiseCommandsParam{
   212  		Typ: chat1.BotCommandsAdvertisementTyp_TLFID_CONVS,
   213  		Commands: []chat1.UserBotCommandInput{{
   214  			Name: "teamconvonly_implicit",
   215  		}},
   216  		TeamName: &impConv.TlfName,
   217  	})
   218  	require.Error(t, tc.Context().BotCommandManager.Advertise(ctx, &alias, commands))
   219  }