ScuttleBot

scuttlebot / internal / bots / cmdparse / cmdparse_test.go
Blame History Raw 422 lines
1
package cmdparse
2
3
import (
4
"strings"
5
"testing"
6
)
7
8
func testRouter() *CommandRouter {
9
r := NewRouter("scroll")
10
r.Register(Command{
11
Name: "replay",
12
Usage: "replay #channel [last=N]",
13
Description: "replay channel history",
14
Handler: func(ctx *Context, args string) string {
15
return "replaying: " + args
16
},
17
})
18
r.Register(Command{
19
Name: "status",
20
Usage: "status",
21
Description: "show bot status",
22
Handler: func(ctx *Context, args string) string {
23
return "ok"
24
},
25
})
26
return r
27
}
28
29
// --- DM input form ---
30
31
func TestDM_BasicCommand(t *testing.T) {
32
r := testRouter()
33
reply := r.Dispatch("alice", "scroll", "replay #general last=10")
34
if reply == nil {
35
t.Fatal("expected reply, got nil")
36
}
37
if reply.Target != "alice" {
38
t.Errorf("target = %q, want %q", reply.Target, "alice")
39
}
40
if reply.Text != "replaying: #general last=10" {
41
t.Errorf("text = %q, want %q", reply.Text, "replaying: #general last=10")
42
}
43
}
44
45
func TestDM_CaseInsensitive(t *testing.T) {
46
r := testRouter()
47
reply := r.Dispatch("alice", "scroll", "REPLAY #general")
48
if reply == nil {
49
t.Fatal("expected reply, got nil")
50
}
51
if reply.Text != "replaying: #general" {
52
t.Errorf("text = %q, want %q", reply.Text, "replaying: #general")
53
}
54
}
55
56
func TestDM_NoArgs(t *testing.T) {
57
r := testRouter()
58
reply := r.Dispatch("alice", "scroll", "status")
59
if reply == nil {
60
t.Fatal("expected reply, got nil")
61
}
62
if reply.Text != "ok" {
63
t.Errorf("text = %q, want %q", reply.Text, "ok")
64
}
65
}
66
67
func TestDM_EmptyMessage(t *testing.T) {
68
r := testRouter()
69
reply := r.Dispatch("alice", "scroll", "")
70
if reply != nil {
71
t.Errorf("expected nil for empty message, got %+v", reply)
72
}
73
}
74
75
func TestDM_WhitespaceOnly(t *testing.T) {
76
r := testRouter()
77
reply := r.Dispatch("alice", "scroll", " ")
78
if reply != nil {
79
t.Errorf("expected nil for whitespace-only, got %+v", reply)
80
}
81
}
82
83
func TestDM_ContextFields(t *testing.T) {
84
r := NewRouter("testbot")
85
var gotCtx *Context
86
r.Register(Command{
87
Name: "ping",
88
Usage: "ping",
89
Description: "ping",
90
Handler: func(ctx *Context, args string) string {
91
gotCtx = ctx
92
return "pong"
93
},
94
})
95
r.Dispatch("bob", "testbot", "ping")
96
if gotCtx == nil {
97
t.Fatal("handler not called")
98
}
99
if !gotCtx.IsDM {
100
t.Error("expected IsDM=true")
101
}
102
if gotCtx.Channel != "" {
103
t.Errorf("channel = %q, want empty", gotCtx.Channel)
104
}
105
if gotCtx.Nick != "bob" {
106
t.Errorf("nick = %q, want %q", gotCtx.Nick, "bob")
107
}
108
}
109
110
// --- Fantasy input form ---
111
112
func TestFantasy_BasicCommand(t *testing.T) {
113
r := testRouter()
114
reply := r.Dispatch("alice", "#general", "!replay #logs last=20")
115
if reply == nil {
116
t.Fatal("expected reply, got nil")
117
}
118
if reply.Target != "#general" {
119
t.Errorf("target = %q, want %q", reply.Target, "#general")
120
}
121
if reply.Text != "replaying: #logs last=20" {
122
t.Errorf("text = %q, want %q", reply.Text, "replaying: #logs last=20")
123
}
124
}
125
126
func TestFantasy_CaseInsensitive(t *testing.T) {
127
r := testRouter()
128
reply := r.Dispatch("alice", "#general", "!STATUS")
129
if reply == nil {
130
t.Fatal("expected reply, got nil")
131
}
132
if reply.Text != "ok" {
133
t.Errorf("text = %q, want %q", reply.Text, "ok")
134
}
135
}
136
137
func TestFantasy_ContextFields(t *testing.T) {
138
r := NewRouter("testbot")
139
var gotCtx *Context
140
r.Register(Command{
141
Name: "ping",
142
Usage: "ping",
143
Description: "ping",
144
Handler: func(ctx *Context, args string) string {
145
gotCtx = ctx
146
return "pong"
147
},
148
})
149
r.Dispatch("bob", "#dev", "!ping")
150
if gotCtx == nil {
151
t.Fatal("handler not called")
152
}
153
if gotCtx.IsDM {
154
t.Error("expected IsDM=false")
155
}
156
if gotCtx.Channel != "#dev" {
157
t.Errorf("channel = %q, want %q", gotCtx.Channel, "#dev")
158
}
159
}
160
161
func TestFantasy_BangOnly(t *testing.T) {
162
r := testRouter()
163
reply := r.Dispatch("alice", "#general", "!")
164
if reply != nil {
165
t.Errorf("expected nil for bare !, got %+v", reply)
166
}
167
}
168
169
func TestFantasy_NotAddressed(t *testing.T) {
170
r := testRouter()
171
reply := r.Dispatch("alice", "#general", "just a normal message")
172
if reply != nil {
173
t.Errorf("expected nil for unaddressed channel message, got %+v", reply)
174
}
175
}
176
177
// --- Addressed input form ---
178
179
func TestAddressed_ColonSpace(t *testing.T) {
180
r := testRouter()
181
reply := r.Dispatch("alice", "#general", "scroll: replay #logs")
182
if reply == nil {
183
t.Fatal("expected reply, got nil")
184
}
185
if reply.Target != "#general" {
186
t.Errorf("target = %q, want %q", reply.Target, "#general")
187
}
188
if reply.Text != "replaying: #logs" {
189
t.Errorf("text = %q, want %q", reply.Text, "replaying: #logs")
190
}
191
}
192
193
func TestAddressed_ColonNoSpace(t *testing.T) {
194
r := testRouter()
195
reply := r.Dispatch("alice", "#general", "scroll:replay #logs")
196
if reply == nil {
197
t.Fatal("expected reply, got nil")
198
}
199
if reply.Text != "replaying: #logs" {
200
t.Errorf("text = %q, want %q", reply.Text, "replaying: #logs")
201
}
202
}
203
204
func TestAddressed_Comma(t *testing.T) {
205
r := testRouter()
206
reply := r.Dispatch("alice", "#general", "scroll, status")
207
if reply == nil {
208
t.Fatal("expected reply, got nil")
209
}
210
if reply.Text != "ok" {
211
t.Errorf("text = %q, want %q", reply.Text, "ok")
212
}
213
}
214
215
func TestAddressed_CaseInsensitiveBotNick(t *testing.T) {
216
r := testRouter()
217
reply := r.Dispatch("alice", "#general", "Scroll: status")
218
if reply == nil {
219
t.Fatal("expected reply, got nil")
220
}
221
if reply.Text != "ok" {
222
t.Errorf("text = %q, want %q", reply.Text, "ok")
223
}
224
}
225
226
func TestAddressed_ContextFields(t *testing.T) {
227
r := NewRouter("testbot")
228
var gotCtx *Context
229
r.Register(Command{
230
Name: "ping",
231
Usage: "ping",
232
Description: "ping",
233
Handler: func(ctx *Context, args string) string {
234
gotCtx = ctx
235
return "pong"
236
},
237
})
238
r.Dispatch("bob", "#ops", "testbot: ping")
239
if gotCtx == nil {
240
t.Fatal("handler not called")
241
}
242
if gotCtx.IsDM {
243
t.Error("expected IsDM=false")
244
}
245
if gotCtx.Channel != "#ops" {
246
t.Errorf("channel = %q, want %q", gotCtx.Channel, "#ops")
247
}
248
}
249
250
// --- HELP generation ---
251
252
func TestHelp_DM(t *testing.T) {
253
r := testRouter()
254
reply := r.Dispatch("alice", "scroll", "help")
255
if reply == nil {
256
t.Fatal("expected reply, got nil")
257
}
258
if reply.Target != "alice" {
259
t.Errorf("target = %q, want %q", reply.Target, "alice")
260
}
261
if !strings.Contains(reply.Text, "REPLAY") {
262
t.Errorf("help should list REPLAY, got: %s", reply.Text)
263
}
264
if !strings.Contains(reply.Text, "STATUS") {
265
t.Errorf("help should list STATUS, got: %s", reply.Text)
266
}
267
if !strings.Contains(reply.Text, "commands for scroll") {
268
t.Errorf("help should include bot name, got: %s", reply.Text)
269
}
270
}
271
272
func TestHelp_Fantasy(t *testing.T) {
273
r := testRouter()
274
reply := r.Dispatch("alice", "#general", "!help")
275
if reply == nil {
276
t.Fatal("expected reply, got nil")
277
}
278
if reply.Target != "#general" {
279
t.Errorf("target = %q, want %q", reply.Target, "#general")
280
}
281
if !strings.Contains(reply.Text, "REPLAY") {
282
t.Errorf("help should list REPLAY, got: %s", reply.Text)
283
}
284
}
285
286
func TestHelp_Addressed(t *testing.T) {
287
r := testRouter()
288
reply := r.Dispatch("alice", "#general", "scroll: help")
289
if reply == nil {
290
t.Fatal("expected reply, got nil")
291
}
292
if reply.Target != "#general" {
293
t.Errorf("target = %q, want %q", reply.Target, "#general")
294
}
295
}
296
297
func TestHelp_SpecificCommand(t *testing.T) {
298
r := testRouter()
299
reply := r.Dispatch("alice", "scroll", "help replay")
300
if reply == nil {
301
t.Fatal("expected reply, got nil")
302
}
303
if !strings.Contains(reply.Text, "replay channel history") {
304
t.Errorf("help replay should show description, got: %s", reply.Text)
305
}
306
if !strings.Contains(reply.Text, "replay #channel [last=N]") {
307
t.Errorf("help replay should show usage, got: %s", reply.Text)
308
}
309
}
310
311
func TestHelp_SpecificCommandCaseInsensitive(t *testing.T) {
312
r := testRouter()
313
reply := r.Dispatch("alice", "scroll", "HELP REPLAY")
314
if reply == nil {
315
t.Fatal("expected reply, got nil")
316
}
317
if !strings.Contains(reply.Text, "replay channel history") {
318
t.Errorf("expected description, got: %s", reply.Text)
319
}
320
}
321
322
func TestHelp_UnknownCommand(t *testing.T) {
323
r := testRouter()
324
reply := r.Dispatch("alice", "scroll", "help nosuchcmd")
325
if reply == nil {
326
t.Fatal("expected reply, got nil")
327
}
328
if !strings.Contains(reply.Text, "unknown command") {
329
t.Errorf("expected unknown command message, got: %s", reply.Text)
330
}
331
}
332
333
// --- Unknown command handling ---
334
335
func TestUnknown_DM(t *testing.T) {
336
r := testRouter()
337
reply := r.Dispatch("alice", "scroll", "frobnicate something")
338
if reply == nil {
339
t.Fatal("expected reply, got nil")
340
}
341
if !strings.Contains(reply.Text, `unknown command "frobnicate"`) {
342
t.Errorf("expected unknown command message, got: %s", reply.Text)
343
}
344
if !strings.Contains(reply.Text, "REPLAY") {
345
t.Errorf("should list available commands, got: %s", reply.Text)
346
}
347
if !strings.Contains(reply.Text, "STATUS") {
348
t.Errorf("should list available commands, got: %s", reply.Text)
349
}
350
}
351
352
func TestUnknown_Fantasy(t *testing.T) {
353
r := testRouter()
354
reply := r.Dispatch("alice", "#general", "!frobnicate")
355
if reply == nil {
356
t.Fatal("expected reply, got nil")
357
}
358
if reply.Target != "#general" {
359
t.Errorf("target = %q, want %q", reply.Target, "#general")
360
}
361
if !strings.Contains(reply.Text, `unknown command "frobnicate"`) {
362
t.Errorf("expected unknown command message, got: %s", reply.Text)
363
}
364
}
365
366
func TestUnknown_Addressed(t *testing.T) {
367
r := testRouter()
368
reply := r.Dispatch("alice", "#general", "scroll: frobnicate")
369
if reply == nil {
370
t.Fatal("expected reply, got nil")
371
}
372
if !strings.Contains(reply.Text, `unknown command "frobnicate"`) {
373
t.Errorf("expected unknown command message, got: %s", reply.Text)
374
}
375
}
376
377
// --- Edge cases ---
378
379
func TestRegister_EmptyNamePanics(t *testing.T) {
380
r := NewRouter("bot")
381
defer func() {
382
if r := recover(); r == nil {
383
t.Error("expected panic for empty command name")
384
}
385
}()
386
r.Register(Command{Name: "", Handler: func(*Context, string) string { return "" }})
387
}
388
389
func TestRegister_DuplicatePanics(t *testing.T) {
390
r := NewRouter("bot")
391
r.Register(Command{Name: "ping", Handler: func(*Context, string) string { return "" }})
392
defer func() {
393
if r := recover(); r == nil {
394
t.Error("expected panic for duplicate command")
395
}
396
}()
397
r.Register(Command{Name: "ping", Handler: func(*Context, string) string { return "" }})
398
}
399
400
func TestHandlerReturnsEmpty(t *testing.T) {
401
r := NewRouter("bot")
402
r.Register(Command{
403
Name: "quiet",
404
Handler: func(*Context, string) string { return "" },
405
})
406
reply := r.Dispatch("alice", "bot", "quiet")
407
if reply != nil {
408
t.Errorf("expected nil reply for empty handler return, got %+v", reply)
409
}
410
}
411
412
func TestLeadingTrailingWhitespace(t *testing.T) {
413
r := testRouter()
414
reply := r.Dispatch("alice", "scroll", " status ")
415
if reply == nil {
416
t.Fatal("expected reply, got nil")
417
}
418
if reply.Text != "ok" {
419
t.Errorf("text = %q, want %q", reply.Text, "ok")
420
}
421
}
422

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button