Skip to content

Commit

Permalink
dev: Add more tests for parseCmd
Browse files Browse the repository at this point in the history
  • Loading branch information
jdkaplan committed Sep 10, 2024
1 parent daaab7d commit bbc1605
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 124 deletions.
2 changes: 2 additions & 0 deletions parse_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ func parseCmd(cmdStr string) (string, []string, error) {
switch {
// if there's nothing in the command string array
case len(cmd) == 0:
// This case is unreachable because strings.Split always returns at
// least one element.
err = errors.New("the user-issued command was blank")
return "help", nil, err

Expand Down
231 changes: 107 additions & 124 deletions parse_cmd_test.go
Original file line number Diff line number Diff line change
@@ -1,151 +1,134 @@
package main

import (
"errors"
"reflect"
"testing"
)

var daysList = map[string]struct{}{
"monday": {},
"tuesday": {},
"wednesday": {},
"thursday": {},
"friday": {},
"saturday": {},
"sunday": {},
type parseResult struct {
Cmd string
Args []string
}

// due to the difficulties around comparing error return values (and because we don't want to compare error messages),
// the struct contains expectErr to indicate whether an error is expected, instead of an actual error value
var tableNoArgs = []struct {
testName string
inputStr string
wantedCmd string
wantedArgs []string
expectErr bool
}{
{"subscribe_correct_usage", "subscribe", "subscribe", nil, false},
{"subscribe_wrong_usage", "subscribe mon", "help", nil, true},
{"unsubscribe_correct_usage", "unsubscribe", "unsubscribe", nil, false},
{"unsubscribe_wrong_usage", "unsubscribe tuesday", "help", nil, true},
{"help_correct_usage", "help", "help", nil, false},
{"help_wrong_usage", "help me", "help", nil, true},
{"status_correct_usage", "status", "status", nil, false},
{"status_wrong_usage", "status me", "help", nil, true},
{"version", "version", "version", nil, false},
}
var acceptedCommands = map[string]parseResult{
"subscribe": {"subscribe", nil},
"unsubscribe": {"unsubscribe", nil},
"help": {"help", nil},
"status": {"status", nil},
"get-reviews": {"get-reviews", nil},
"cookie": {"cookie", nil},
"version": {"version", nil},

func TestParseCmdNoArgs(t *testing.T) {
for _, tt := range tableNoArgs {
t.Run(tt.testName, func(t *testing.T) {
gotCmd, gotArgs, gotErr := parseCmd(tt.inputStr)
if gotCmd != tt.wantedCmd {
t.Errorf("got %v, %v\n", gotCmd, gotArgs)
}
// This command ignores its arguments.
"version info": {"version", nil},

_, ok := gotErr.(*parsingErr)
// These commands require exact literal arguments.
"skip tomorrow": {"skip", []string{"tomorrow"}},
"unskip tomorrow": {"unskip", []string{"tomorrow"}},

if tt.expectErr && !ok {
t.Errorf("Expected parsingErr but didn't get one\n")
} else if !tt.expectErr && ok {
t.Errorf("Got unexpected parsingError\n")
}
})
}
// Schedules!
"schedule monday": {"schedule", []string{"monday"}},
"schedule sunday": {"schedule", []string{"sunday"}},
"schedule friday tuesday": {"schedule", []string{"friday", "tuesday"}},
"schedule mon tue wed thu fri sat sun": {
"schedule",
[]string{"monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"},
},

// BUG(@jdkaplan): Don't squash spaces *inside* the review.
"add-review :pear: ing :robot:": {"add-review", []string{":pear: ing :robot:"}},

"get-reviews 0": {"get-reviews", []string{"0"}},
"get-reviews 1": {"get-reviews", []string{"1"}},
"get-reviews 5": {"get-reviews", []string{"5"}},
"get-reviews 10": {"get-reviews", []string{"10"}},

// Commands are case-insensitive.
"Help": {"help", nil},
"hElP": {"help", nil},
"sUbScRiBe": {"subscribe", nil},

// Day names (as keywords) are also case-insensitive
"schedule MoN WED fRi": {"schedule", []string{"monday", "wednesday", "friday"}},

// Review content *is* case-sensitive.
"add-review I :heart: Pairing Bot!\n": {"add-review", []string{"I :heart: Pairing Bot!"}},
}

var tableWithArgs = []struct {
testName string
inputStr string
wantedCmd string
wantedArgs []string
expectErr bool
}{
{"schedule_1_arg", "schedule monday", "schedule", []string{"monday"}, false},
{"schedule_2_args", "schedule monday friday", "schedule", []string{"monday", "friday"}, false},
{"schedule_3_args", "schedule monday wednesday friday", "schedule", []string{"monday", "wednesday", "friday"}, false},
{"schedule_4_args", "schedule monday wednesday friday sunday", "schedule", []string{"monday", "wednesday", "friday", "sunday"}, false},
{"schedule_weekend_only", "schedule sunday", "schedule", []string{"sunday"}, false},
{"schedule_wrong_usage", "schedule", "help", nil, true},
{"skip_correct_usage", "skip tomorrow", "skip", []string{"tomorrow"}, false},
{"skip_wrong_usage", "skip monday", "help", nil, true},
{"skip_wrong_usage", "skip whenever", "help", nil, true},
{"skip_wrong_usage", "skip", "help", nil, true},
{"unskip_correct_usage", "unskip tomorrow", "unskip", []string{"tomorrow"}, false},
{"unskip_wrong_usage", "unskip today", "help", nil, true},
{"unskip_wrong_usage", "unskip friday", "help", nil, true},
{"unskip_wrong_usage", "unskip", "help", nil, true},
{"version_extra_args_ok", "version info", "version", nil, false},
var rejectedCommands = []string{
"",

// Funnily enough, these *do* give you what you want!
"help me",
"halp",
"schedule help",

// Unexpected arguments
"status me",
"cookie me",

// Did they really want `schedule`?
"subscribe tue",
"unsubscribe thu",

// (Un)skipping requires an argument.
"skip",
"unskip",

// TODO(#49): Allow (un)skipping days other than tomorrow
"skip friday",
"unskip next",

// This is not the way to delete reviews you don't like 😛
"get-reviews -1",
"get-reviews -10",

// Unknown commands
"scheduleing monday",
"schedul monday",
"mooh",
}

func TestParseCmdWithArgs(t *testing.T) {
for _, tt := range tableWithArgs {
t.Run(tt.testName, func(t *testing.T) {
gotCmd, gotArgs, gotErr := parseCmd(tt.inputStr)
if gotCmd != tt.wantedCmd || len(gotArgs) != len(tt.wantedArgs) {
t.Errorf("got %v, %v, wanted %v, %v\n", gotCmd, gotArgs, tt.wantedCmd, tt.wantedArgs)
func Test_parseCmd(t *testing.T) {
for input, expected := range acceptedCommands {
t.Run(input, func(t *testing.T) {
cmd, args, err := parseCmd(input)
if err != nil {
t.Fatalf("unexpected error: %#+v", err)
}

switch gotCmd {
case "schedule":
for i, arg := range gotArgs {
if _, ok := daysList[arg]; !ok {
t.Errorf("Wrong argument %v for command %v\n", gotArgs[i], gotCmd)
}
}
case "skip":
if gotArgs[0] != "tomorrow" {
t.Errorf("Wrong argument %v for command %v\n", gotArgs[0], gotCmd)
}
case "unskip":
if gotArgs[0] != "tomorrow" {
t.Errorf("Wrong argument %v for command %v\n", gotArgs[0], gotCmd)
}
case "version":
// Always fine as long as the wantedCmd check passes.
default:
if gotCmd != "help" {
t.Errorf("unknown command %v\n", gotCmd)
}
}
assertEqual(t, cmd, expected.Cmd)
assertEqual(t, args, expected.Args)
})
}

_, ok := gotErr.(*parsingErr)
for _, input := range rejectedCommands {
t.Run(input, func(t *testing.T) {
cmd, args, err := parseCmd(input)

if tt.expectErr && !ok {
t.Errorf("Expected parsingErr but didn't get one\n")
} else if !tt.expectErr && ok {
t.Errorf("Got unexpected parsingError\n")
}
_, _ = assertErrorAs[*parsingErr](t, err)

assertEqual(t, cmd, "help")
assertEqual(t, args, nil)
})
}
}

var tableMisc = []struct {
testName string
inputStr string
wantedCmd string
wantedArgs []string
expectErr bool
}{
{"command_is_superstring", "scheduleing monday", "help", nil, true},
{"command_is_substring", "schedul monday", "help", nil, true},
{"command_is_undefined", "mooh", "help", nil, true},
{"command_is_capitalized", "Help", "help", nil, false},
}
func assertEqual[T any](t *testing.T, a, b T) {
t.Helper()

func TestParseCmdMisc(t *testing.T) {
for _, tt := range tableMisc {
t.Run(tt.testName, func(t *testing.T) {
gotCmd, gotArgs, gotErr := parseCmd(tt.inputStr)
if gotCmd != tt.wantedCmd {
t.Errorf("got %v, %v, wanted %v, %v\n", gotCmd, gotArgs, tt.wantedCmd, tt.wantedArgs)
}
_, ok := gotErr.(*parsingErr)
if reflect.DeepEqual(a, b) {
return
}

if tt.expectErr && !ok {
t.Errorf("Expected parsingErr but didn't get one\n")
} else if !tt.expectErr && ok {
t.Errorf("Got unexpected parsingError\n")
}
})
t.Errorf("expected %#+v to equal %#+v", a, b)
}

func assertErrorAs[T error](t *testing.T, err error) (target T, ok bool) {
ok = errors.As(err, &target)
if !ok {
t.Errorf("expected error as %T, got %#+v", target, err)
}
return target, ok
}

0 comments on commit bbc1605

Please sign in to comment.