diff --git a/.travis.yml b/.travis.yml index 95694de..b67c5f9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,8 @@ language: go # Specify Go versions to test (".x" suffix means latest minor version, "tip" is # the latest development version) go: - # - "1.9.x" - # - "1.10.x" + - "1.9.x" + - "1.10.x" - "1.11.x" - tip @@ -25,8 +25,7 @@ matrix: # Install all dependencies (including dependencies of tests) without installing # binaries. install: - - -# - go get -d -t -v ./... + - ./internal/scripts/travis-install.sh # Check if gofmt or go vet report any errors, run all tests with the race # detector enabled, then build all examples to make sure they still compile. diff --git a/commands/command.go b/commands/command.go index 0404ee1..035a54d 100644 --- a/commands/command.go +++ b/commands/command.go @@ -1,18 +1,11 @@ -// Package commands provides command handling capable of ingesting transport.Events -// and responding over the provided transport wire. -// If the userDB is set permissions on a command will be honored otherwise -// if no userDB is set permissions will be ignored entirely (this could possibly -// use improvement). -package commands // import "github.com/justanotherorganization/justanotherbotkit/commands" +package commands import ( - "context" "fmt" "strings" "github.com/justanotherorganization/justanotherbotkit/transport" "github.com/justanotherorganization/justanotherbotkit/users" - "github.com/pkg/errors" ) type ( @@ -39,22 +32,22 @@ type ( // It attempts to match the event against any children commands first // allowing a base Command to act as a command tree. func (c *Command) Execute(ev *transport.Event) error { - if c.Disabled { + if ev == nil || + ev.GetBody() == "" || + c.Disabled { return nil } - if c.UserDB != nil && len(c.Perms) > 0 { - ok, err := _hasPerms(c, ev.Origin.Sender.ID) - if err != nil { - return err - } + ok, err := checkPerms(c, ev) + if err != nil { + return err + } - if !ok { - return nil - } + if !ok { + return nil } - fields := strings.Fields(ev.Body) + fields := strings.Fields(ev.GetBody()) if strings.Compare("help", fields[0]) == 0 { ev.Body = strings.Join(fields[1:], " ") @@ -62,19 +55,18 @@ func (c *Command) Execute(ev *transport.Event) error { } _c := c.match(ev) - if _c == nil { + if _c == nil || + _c.Disabled { return nil } - if _c.UserDB != nil && len(_c.Perms) > 0 { - ok, err := _hasPerms(c, ev.Origin.Sender.ID) - if err != nil { - return err - } + ok, err = checkPerms(_c, ev) + if err != nil { + return err + } - if !ok { - return nil - } + if !ok { + return nil } if _c.ExecFunc == nil { @@ -114,69 +106,7 @@ func (c *Command) help(ev *transport.Event) error { } return ev.SendMessage( - ev.Origin.ID, + ev.GetOrigin().GetID(), fmt.Sprintf("%s:\n%s\n", _c.Use, _c.Long), ) } - -func (c *Command) match(ev *transport.Event) *Command { - if ev.Body == "" { - return c - } - - fields := strings.Fields(ev.Body) - if len(fields) == 0 { - return c - } - - var cmd *Command - for _, _c := range c.children { - if _isCommand(_c, fields[0]) { - ev.Body = strings.Join(fields[1:], " ") - cmd = _c - break - } - } - - if cmd == nil { - cmd = c - } - - return cmd -} - -func _isCommand(c *Command, s string) bool { - if strings.Compare(c.Use, s) == 0 { - return true - } - - for _, a := range c.Aliases { - if strings.Compare(a, s) == 0 { - return true - } - } - - return false -} - -func _hasPerms(c *Command, id string) (bool, error) { - u, err := c.UserDB.GetUser(context.Background(), id) - if err != nil { - return false, errors.Wrap(err, "UserDB.GetUser") - } - - for _, p := range u.GetPermissions() { - // Root users can do all the things!!! - if p == "root" { - return true, nil - } - - for _, _p := range c.Perms { - if p == _p { - return true, nil - } - } - } - - return false, nil -} diff --git a/commands/command_test.go b/commands/command_test.go new file mode 100644 index 0000000..aeeca37 --- /dev/null +++ b/commands/command_test.go @@ -0,0 +1,39 @@ +package commands_test + +import ( + "testing" + + . "github.com/justanotherorganization/justanotherbotkit/commands" + "github.com/justanotherorganization/justanotherbotkit/internal/test" + "github.com/justanotherorganization/justanotherbotkit/transport" + "github.com/justanotherorganization/justanotherbotkit/transport/proto" +) + +func TestSingleCommand(t *testing.T) { + tt := test.TableTest{ + Cases: []*test.TableTestCase{ + // Disabled commands should return nil. + // TODO: test that nothing was sent over the transport in response. + &test.TableTestCase{ + Val: &Command{Disabled: true}, + Exp: nil, + }, + // Perms are set but no database is present (perms will be un-checked). + // TODO: confirm that the command sent someting over the transport in response. + &test.TableTestCase{ + Val: &Command{Perms: []string{"foo"}}, + Exp: nil, + }, + }, + F: func(v interface{}) interface{} { + cmd := v.(*Command) + return cmd.Execute(&transport.Event{ + BaseEvent: &pb.BaseEvent{ + Body: "test", + }, + }) + }, + } + + tt.Run(t) +} diff --git a/commands/doc.go b/commands/doc.go new file mode 100644 index 0000000..5ab1880 --- /dev/null +++ b/commands/doc.go @@ -0,0 +1,7 @@ +// Package commands provides command handling capable of ingesting transport.Events +// and responding over the provided transport wire. +// +// If the userDB is set permissions on a command will be honored otherwise +// if no userDB is set permissions will be ignored entirely (this could possibly +// use improvement). +package commands // import "github.com/justanotherorganization/justanotherbotkit/commands" diff --git a/commands/go.mod b/commands/go.mod index 3d44fe9..a67a9cc 100644 --- a/commands/go.mod +++ b/commands/go.mod @@ -1,7 +1,8 @@ module github.com/justanotherorganization/justanotherbotkit/commands require ( - github.com/justanotherorganization/justanotherbotkit/transport v0.0.2 + github.com/justanotherorganization/justanotherbotkit/internal v0.0.2 + github.com/justanotherorganization/justanotherbotkit/transport v0.0.3-pre1 github.com/justanotherorganization/justanotherbotkit/users v0.0.1 github.com/pkg/errors v0.8.0 ) diff --git a/commands/go.sum b/commands/go.sum index fdc40e1..a4ee858 100644 --- a/commands/go.sum +++ b/commands/go.sum @@ -5,10 +5,12 @@ github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/justanotherorganization/justanotherbotkit/internal v0.0.2 h1:lj5ClN0JpGuOOwDTw2NHzmMJVM8VlDvN2veHVcfOBRk= +github.com/justanotherorganization/justanotherbotkit/internal v0.0.2/go.mod h1:Ky6Plt9xEtoSkl64huLSkq0lq1xYKBxI58Wb45mucV8= github.com/justanotherorganization/justanotherbotkit/proto v0.0.1 h1:N2hHF03EYoOfKXFlU4Sx97laA1fGu5K7K0ZjgbaGjF8= github.com/justanotherorganization/justanotherbotkit/proto v0.0.1/go.mod h1:r2hwUKNIK21pZ+e1KuoNWCRDJIpotmDrwS02cMKZu6c= -github.com/justanotherorganization/justanotherbotkit/transport v0.0.2 h1:ooS6/mfTebytKcyzOvGSYr/Ay0K3uxegSIrEumrCqxw= -github.com/justanotherorganization/justanotherbotkit/transport v0.0.2/go.mod h1:gbF/m3czwTohZm7mRNbG/E7ycOmsSNMThrdMC+DuOjg= +github.com/justanotherorganization/justanotherbotkit/transport v0.0.3-pre1 h1:kgnhaJeOZ2Y64/Dv9XLQFGA5AMLGcrsN80YBHeJcZ88= +github.com/justanotherorganization/justanotherbotkit/transport v0.0.3-pre1/go.mod h1:gbF/m3czwTohZm7mRNbG/E7ycOmsSNMThrdMC+DuOjg= github.com/justanotherorganization/justanotherbotkit/users v0.0.1 h1:g96VVlYIYV5kRYfG2eLBQa5Cptg1WaKu2Xv8eftOz6M= github.com/justanotherorganization/justanotherbotkit/users v0.0.1/go.mod h1:pSY/4vMnSHd4qjLQzLFe3WyGhpVYhNibLQuZTjkIpE4= github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5/go.mod h1:c2mYKRyMb1BPkO5St0c/ps62L4S0W2NAkaTXj9qEI+0= diff --git a/commands/match.go b/commands/match.go new file mode 100644 index 0000000..7fa9146 --- /dev/null +++ b/commands/match.go @@ -0,0 +1,47 @@ +package commands + +import ( + "strings" + + "github.com/justanotherorganization/justanotherbotkit/transport" +) + +func (c *Command) match(ev *transport.Event) *Command { + if ev.GetBody() == "" { + return c + } + + fields := strings.Fields(ev.GetBody()) + if len(fields) == 0 { + return c + } + + var cmd *Command + for _, _c := range c.children { + if isMatch(_c, fields[0]) { + ev.Body = strings.Join(fields[1:], " ") + cmd = _c + break + } + } + + if cmd == nil { + cmd = c + } + + return cmd +} + +func isMatch(c *Command, s string) bool { + if strings.Compare(c.Use, s) == 0 { + return true + } + + for _, a := range c.Aliases { + if strings.Compare(a, s) == 0 { + return true + } + } + + return false +} diff --git a/commands/match_test.go b/commands/match_test.go new file mode 100644 index 0000000..9865a80 --- /dev/null +++ b/commands/match_test.go @@ -0,0 +1,76 @@ +package commands + +import ( + "testing" + + "github.com/justanotherorganization/justanotherbotkit/internal/test" + "github.com/justanotherorganization/justanotherbotkit/transport" +) + +func TestMatch(t *testing.T) { + type _mt struct { + cmd *Command + ev *transport.Event + } + + tt := test.TableTest{ + Cases: []*test.TableTestCase{ + &test.TableTestCase{ + Val: &_mt{cmd: &Command{}, ev: &transport.Event{}}, + Exp: &Command{}, + }, + }, + F: func(v interface{}) interface{} { + mt := v.(*_mt) + return mt.cmd.match(mt.ev) + }, + } + + tt.Run(t) +} + +func TestIsMatch(t *testing.T) { + type _mt struct { + cmd *Command + s string + } + + tt := test.TableTest{ + Cases: []*test.TableTestCase{ + &test.TableTestCase{ + Val: &_mt{cmd: &Command{Use: ""}, s: ""}, + Exp: true, + }, + &test.TableTestCase{ + Val: &_mt{cmd: &Command{Use: "foo"}, s: ""}, + Exp: false, + }, + &test.TableTestCase{ + Val: &_mt{cmd: &Command{Use: "foo"}, s: "bar"}, + Exp: false, + }, + &test.TableTestCase{ + Val: &_mt{cmd: &Command{Use: "foo"}, s: "foo"}, + Exp: true, + }, + &test.TableTestCase{ + Val: &_mt{cmd: &Command{Use: "foo", Aliases: []string{"bar"}}, s: "bar"}, + Exp: true, + }, + &test.TableTestCase{ + Val: &_mt{cmd: &Command{Use: "foo", Aliases: []string{"bar", "foobar"}}, s: "bar"}, + Exp: true, + }, + &test.TableTestCase{ + Val: &_mt{cmd: &Command{Use: "foo", Aliases: []string{"bar", "foobar"}}, s: "foobar"}, + Exp: true, + }, + }, + F: func(v interface{}) interface{} { + mt := v.(*_mt) + return isMatch(mt.cmd, mt.s) + }, + } + + tt.Run(t) +} diff --git a/commands/perms.go b/commands/perms.go new file mode 100644 index 0000000..f9cce35 --- /dev/null +++ b/commands/perms.go @@ -0,0 +1,38 @@ +package commands + +import ( + "context" + + "github.com/justanotherorganization/justanotherbotkit/transport" + "github.com/pkg/errors" +) + +func checkPerms(c *Command, ev *transport.Event) (bool, error) { + if c.UserDB != nil && len(c.Perms) > 0 { + return hasPerms(c, ev.GetOrigin().GetSender().GetID()) + } + + return true, nil +} + +func hasPerms(c *Command, id string) (bool, error) { + u, err := c.UserDB.GetUser(context.Background(), id) + if err != nil { + return false, errors.Wrap(err, "UserDB.GetUser") + } + + for _, p := range u.GetPermissions() { + // Root users can do all the things!!! + if p == "root" { + return true, nil + } + + for _, _p := range c.Perms { + if p == _p { + return true, nil + } + } + } + + return false, nil +} diff --git a/internal/scripts/travis-install.sh b/internal/scripts/travis-install.sh new file mode 100755 index 0000000..45862f4 --- /dev/null +++ b/internal/scripts/travis-install.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +# Disable install for module builds. +if [ "$GO111MODULE" != on ]; then + go get -d -t -v ./... +fi diff --git a/internal/test/helper.go b/internal/test/helper.go index 15343ce..eb35087 100644 --- a/internal/test/helper.go +++ b/internal/test/helper.go @@ -34,3 +34,35 @@ func Equals(tb testing.TB, exp, act interface{}) { tb.FailNow() } } + +// EqualsMsg fails the test if exp is not equal to act, allowing a message to be provided. +func EqualsMsg(tb testing.TB, exp, act interface{}, msg string, v ...interface{}) { + if !reflect.DeepEqual(exp, act) { + _, file, line, _ := runtime.Caller(1) + // FIXME: I don't like this sprintf in here, but I'm having issues making this work lol + fmt.Printf("\033[31m%s:%d: "+fmt.Sprintf(msg, v...)+"\n\n\texp: %#v\n\n\tgot: %#v\033[39m\n\n", filepath.Base(file), line, exp, act) + tb.FailNow() + } +} + +type ( + // TableTest is used to run a simple table test. + TableTest struct { + Cases []*TableTestCase + F func(interface{}) interface{} + } + + // TableTestCase ... + TableTestCase struct { + Val interface{} + Exp interface{} + } +) + +// Run a TableTest +func (t *TableTest) Run(tb testing.TB) { + for i, tc := range t.Cases { + out := t.F(tc.Val) + EqualsMsg(tb, tc.Exp, out, "test case %d", i+1) + } +} diff --git a/transport/channel.go b/transport/channel.go index b022640..1e3e119 100644 --- a/transport/channel.go +++ b/transport/channel.go @@ -1,6 +1,6 @@ package transport // package github.com/justanotherorganization/justanotherbotkit/transport -import "github.com/justanotherorganization/justanotherbotkit/transport/internal/proto" +import "github.com/justanotherorganization/justanotherbotkit/transport/proto" type ( // Channel wraps a pb.BaseChannel up with it's accomanied transport. diff --git a/transport/discord/transport.go b/transport/discord/transport.go index a81989b..dc82f81 100644 --- a/transport/discord/transport.go +++ b/transport/discord/transport.go @@ -6,7 +6,7 @@ import ( "github.com/bwmarrin/discordgo" "github.com/justanotherorganization/justanotherbotkit/transport" - "github.com/justanotherorganization/justanotherbotkit/transport/internal/proto" + "github.com/justanotherorganization/justanotherbotkit/transport/proto" ) type ( @@ -22,8 +22,8 @@ var _ transport.Transport = &Discord{} // New returns a new instance of Discord. func New(cfg *transport.Config) (*Discord, error) { - if cfg == nil { - return nil, errors.New("cfg cannot be nil") + if err := cfg.Validate(); err != nil { + return nil, err } session, err := discordgo.New("Bot " + cfg.Token) diff --git a/transport/event.go b/transport/event.go index f8ab378..987e04a 100644 --- a/transport/event.go +++ b/transport/event.go @@ -1,6 +1,6 @@ package transport // package github.com/justanotherorganization/justanotherbotkit/transport -import "github.com/justanotherorganization/justanotherbotkit/transport/internal/proto" +import "github.com/justanotherorganization/justanotherbotkit/transport/proto" type ( // Event wraps a pb.BaseEvent up with it's accompanied transport. diff --git a/transport/internal/proto/channel.pb.go b/transport/proto/channel.pb.go similarity index 100% rename from transport/internal/proto/channel.pb.go rename to transport/proto/channel.pb.go diff --git a/transport/internal/proto/channel.proto b/transport/proto/channel.proto similarity index 100% rename from transport/internal/proto/channel.proto rename to transport/proto/channel.proto diff --git a/transport/internal/proto/event.pb.go b/transport/proto/event.pb.go similarity index 100% rename from transport/internal/proto/event.pb.go rename to transport/proto/event.pb.go diff --git a/transport/internal/proto/event.proto b/transport/proto/event.proto similarity index 100% rename from transport/internal/proto/event.proto rename to transport/proto/event.proto diff --git a/transport/internal/proto/regenerate.sh b/transport/proto/regenerate.sh similarity index 90% rename from transport/internal/proto/regenerate.sh rename to transport/proto/regenerate.sh index 57f403f..ed18cdc 100644 --- a/transport/internal/proto/regenerate.sh +++ b/transport/proto/regenerate.sh @@ -33,4 +33,4 @@ if [[ -z "${PROTOC// }" ]]; then fi ### Pre-flight complete, let's do stuff -"${PROTOC}" -I "${PROTO_DIR}/." "${PROTO_DIR}/"*.proto --gogo_out=plugins=grpc:"${PROTO_DIR}/." --proto_path=../../../../../:. +"${PROTOC}" -I "${PROTO_DIR}/." "${PROTO_DIR}/"*.proto --gogo_out=plugins=grpc:"${PROTO_DIR}/." --proto_path=../../../../:. diff --git a/transport/slack/transport.go b/transport/slack/transport.go index 95a5bf0..33fc076 100644 --- a/transport/slack/transport.go +++ b/transport/slack/transport.go @@ -7,7 +7,7 @@ import ( bkPb "github.com/justanotherorganization/justanotherbotkit/proto" "github.com/justanotherorganization/justanotherbotkit/transport" - "github.com/justanotherorganization/justanotherbotkit/transport/internal/proto" + "github.com/justanotherorganization/justanotherbotkit/transport/proto" "github.com/nlopes/slack" ) @@ -25,8 +25,8 @@ var _ transport.Transport = &Slack{} // cfg.IgnoreUsers must be set with the bot name or ID otherwise it will potentially read // it's own messages. func New(cfg *transport.Config) (*Slack, error) { - if cfg == nil { - return nil, errors.New("cfg cannot be nil") + if err := cfg.Validate(); err != nil { + return nil, err } _slack := &Slack{ diff --git a/transport/transport.go b/transport/transport.go index afd54e0..01edba1 100644 --- a/transport/transport.go +++ b/transport/transport.go @@ -34,8 +34,25 @@ type ( ) var ( + // ErrNilConfig is returned if no config is passed into a transport 'New' function. + ErrNilConfig = errors.New("cfg cannot be nil") + // ErrEmptyToken is returned if no token is provided in the given config. + ErrEmptyToken = errors.New("token cannot be empty") // ErrNilTransport should be returned in places where a transport is required but is not set. ErrNilTransport = errors.New("transport cannot be nil") // ErrUserNotFound is returned if teh transport cannot locate a given user. ErrUserNotFound = errors.New("user not found") ) + +// Validate a configuration. +func (c *Config) Validate() error { + if c == nil { + return ErrNilConfig + } + + if c.Token == "" { + return ErrEmptyToken + } + + return nil +}