ScuttleBot

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

Keyboard Shortcuts

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