1package bots
2
3import (
4	"context"
5	"crypto/sha256"
6	"testing"
7
8	"github.com/keybase/client/go/chat/globals"
9	"github.com/keybase/client/go/chat/types"
10	"github.com/keybase/client/go/chat/utils"
11	"github.com/keybase/client/go/externalstest"
12	"github.com/keybase/client/go/libkb"
13	"github.com/keybase/client/go/protocol/chat1"
14	"github.com/keybase/client/go/protocol/gregor1"
15	"github.com/keybase/client/go/protocol/keybase1"
16	"github.com/stretchr/testify/require"
17)
18
19var mockCmdOutput []chat1.UserBotCommandOutput
20
21type MockBotCommandManager struct{ types.DummyBotCommandManager }
22
23func (m MockBotCommandManager) ListCommands(context.Context, chat1.ConversationID) ([]chat1.UserBotCommandOutput, map[string]string, error) {
24	return mockCmdOutput, make(map[string]string), nil
25}
26
27type mockUPAKLoader struct {
28	libkb.UPAKLoader
29}
30
31func (m mockUPAKLoader) LookupUsername(ctx context.Context, uid keybase1.UID) (libkb.NormalizedUsername, error) {
32	return libkb.NewNormalizedUsername("botua"), nil
33}
34
35func TestApplyTeamBotSettings(t *testing.T) {
36	tc := externalstest.SetupTest(t, "chat-utils", 0)
37	defer tc.Cleanup()
38
39	g := globals.NewContext(tc.G, &globals.ChatContext{})
40	tc.G.OverrideUPAKLoader(mockUPAKLoader{})
41	debugLabeler := utils.NewDebugLabeler(g.ExternalG(), "ApplyTeamBotSettings", false)
42	ctx := context.TODO()
43	convID := chat1.ConversationID([]byte("conv"))
44	botUID := gregor1.UID([]byte("botua"))
45	botSettings := keybase1.TeamBotSettings{}
46	msg := chat1.MessagePlaintext{}
47	mentionMap := make(map[string]struct{})
48
49	assertMatch := func(expected bool) {
50		isMatch, err := ApplyTeamBotSettings(ctx, g, botUID, botSettings, msg, &convID,
51			mentionMap, debugLabeler)
52		require.NoError(t, err)
53		require.Equal(t, expected, isMatch)
54	}
55
56	assertMatch(false)
57
58	// DELETEHISTORY always matches
59	msg.ClientHeader = chat1.MessageClientHeader{
60		MessageType: chat1.MessageType_DELETEHISTORY,
61	}
62	assertMatch(true)
63
64	bannedTypes := []chat1.MessageType{
65		chat1.MessageType_NONE,
66		chat1.MessageType_METADATA,
67		chat1.MessageType_TLFNAME,
68		chat1.MessageType_HEADLINE,
69		chat1.MessageType_JOIN,
70		chat1.MessageType_LEAVE,
71		chat1.MessageType_SYSTEM,
72	}
73	for _, typ := range bannedTypes {
74		msg.ClientHeader.MessageType = typ
75		assertMatch(false)
76	}
77
78	// if the sender is botUID, always match
79	msg.ClientHeader.MessageType = chat1.MessageType_TEXT
80	msg.ClientHeader.Sender = botUID
81	assertMatch(true)
82
83	// restrict the bot to certain convs
84	botSettings.Convs = []string{"conv"}
85	assertMatch(false)
86	msg.ClientHeader.Sender = gregor1.UID("hi")
87	botSettings.Convs = nil
88
89	// mentions
90	mentionMap[botUID.String()] = struct{}{}
91	assertMatch(false)
92
93	botSettings.Mentions = true
94	assertMatch(true)
95
96	delete(mentionMap, botUID.String())
97	assertMatch(false)
98
99	botSettings.Mentions = false
100	assertMatch(false)
101
102	// triggers
103	msg.MessageBody = chat1.NewMessageBodyWithText(chat1.MessageText{
104		Body: "shipit",
105	})
106	assertMatch(false)
107
108	botSettings.Triggers = []string{"shipit"}
109	assertMatch(true)
110
111	botSettings.Triggers = []string{".+"}
112	assertMatch(true)
113
114	msg.MessageBody = chat1.NewMessageBodyWithText(chat1.MessageText{
115		Body: "",
116	})
117	assertMatch(false)
118
119	// invalid trigger regex ignored
120	botSettings.Triggers = []string{"*"}
121	assertMatch(false)
122
123	botSettings.Triggers = nil
124	assertMatch(false)
125
126	g.BotCommandManager = &MockBotCommandManager{}
127	mockCmdOutput = []chat1.UserBotCommandOutput{
128		{
129			Name:     "remind me",
130			Username: "botua",
131		},
132	}
133	msg.MessageBody = chat1.NewMessageBodyWithText(chat1.MessageText{
134		Body: "!remind me ",
135	})
136	assertMatch(false)
137	botSettings.Cmds = true
138	assertMatch(true)
139
140	// make sure we only match if the given bot username also matches
141	mockCmdOutput = []chat1.UserBotCommandOutput{
142		{
143			Name:     "remind me",
144			Username: "notbotua",
145		},
146	}
147	msg.MessageBody = chat1.NewMessageBodyWithText(chat1.MessageText{
148		Body: "!remind me ",
149	})
150	assertMatch(false)
151
152	// make sure we don't match an erroneous command
153	msg.MessageBody = chat1.NewMessageBodyWithText(chat1.MessageText{
154		Body: "!help ",
155	})
156	assertMatch(false)
157}
158
159func TestBotInfoHash(t *testing.T) {
160	hash := sha256.New()
161	nilHash := chat1.BotInfoHash(hash.Sum(nil))
162
163	// Ensure that the latest ClientBotInfoHashVers case is handled if the
164	// version number is incremented.
165	var botInfo chat1.BotInfo
166	for i := chat1.BotInfoHashVers(0); i <= chat1.ClientBotInfoHashVers; i++ {
167		botInfo = chat1.BotInfo{
168			ClientHashVers: i,
169		}
170		require.NotEqual(t, nilHash, botInfo.Hash())
171	}
172
173	// bumping the server version changes the hash
174	botInfo2 := chat1.BotInfo{
175		ServerHashVers: chat1.ServerBotInfoHashVers + 1,
176		ClientHashVers: chat1.ClientBotInfoHashVers,
177	}
178	require.NotEqual(t, botInfo.Hash(), botInfo2.Hash())
179
180	// non-existent client version returns a nil hash
181	botInfo = chat1.BotInfo{
182		ClientHashVers: chat1.ClientBotInfoHashVers + 1,
183	}
184	require.Equal(t, nilHash, botInfo.Hash())
185}
186