diff --git a/go.mod b/go.mod index 3554815..c1edcd8 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/google/go-github/v45 v45.1.0 github.com/gorilla/mux v1.8.0 github.com/slack-go/slack v0.11.0 + github.com/smartystreets/goconvey v1.8.1 go.uber.org/zap v1.21.0 golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c @@ -22,7 +23,9 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/gopherjs/gopherjs v1.17.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/jtolds/gls v4.20.0+incompatible // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect @@ -31,6 +34,7 @@ require ( github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect + github.com/smarty/assertions v1.15.0 // indirect github.com/spf13/afero v1.8.2 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.3.0 // indirect @@ -81,7 +85,7 @@ require ( go.uber.org/multierr v1.6.0 // indirect golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 // indirect - golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect + golang.org/x/sys v0.6.0 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect google.golang.org/appengine v1.6.7 // indirect @@ -91,7 +95,7 @@ require ( gopkg.in/yaml.v3 v3.0.0 // indirect k8s.io/klog/v2 v2.60.1 // indirect k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect - k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect + k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect sigs.k8s.io/yaml v1.2.0 // indirect diff --git a/go.sum b/go.sum index b6311cd..ab09360 100644 --- a/go.sum +++ b/go.sum @@ -230,6 +230,8 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= +github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= @@ -259,6 +261,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -365,6 +369,10 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/slack-go/slack v0.11.0 h1:sBBjQz8LY++6eeWhGJNZpRm5jvLRNnWBFZ/cAq58a6k= github.com/slack-go/slack v0.11.0/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= +github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= +github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= +github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= +github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= @@ -590,8 +598,8 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff --git a/pkg/slack/command_test.go b/pkg/slack/command_test.go new file mode 100644 index 0000000..e4750e3 --- /dev/null +++ b/pkg/slack/command_test.go @@ -0,0 +1,162 @@ +package slack + +import ( + "context" + "testing" + + "github.com/oursky/github-actions-manager/pkg/kv" + "github.com/slack-go/slack" + . "github.com/smartystreets/goconvey/convey" + + "go.uber.org/zap" +) + +func TestSpec(t *testing.T) { + testCommand := "test-gha" + + NewTestSlackChannel := func(channelID string) (string, func(string) slack.SlashCommand) { + return channelID, func(command string) slack.SlashCommand { + return slack.SlashCommand{ + ChannelID: channelID, + Command: "/" + testCommand, + Text: command, + } + } + } + channelID1, commandFromChannel1 := NewTestSlackChannel("TestChannelID1") + channelID2, commandFromChannel2 := NewTestSlackChannel("TestChannelID2") + + Convey("When receiving commands, the bot", t, func() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + testApp := &App{ + logger: zap.NewNop(), + store: kv.NewInMemoryStore(), + commandName: testCommand, + } + + cli := NewCLI(testApp.logger) + cli.SetContext(ctx, testApp) + + Convey("responds", func() { + response := cli.Parse(commandFromChannel1("meow")) + So(response["text"], ShouldEqual, "meow") + }) + Convey("rejects unrecognised commmands", func() { + response := cli.Parse(commandFromChannel1("fhqwhgads")) + So(response["response_type"], ShouldBeNil) + So(response["text"], ShouldContainSubstring, "fhqwhgads") + }) + Convey("When asked to subscribe", func() { + Convey("rejects an insufficient number of arguments", func() { + response := cli.Parse(commandFromChannel1("subscribe")) + So(response["printToChannel"], ShouldBeNil) + So(response["text"], ShouldContainSubstring, "repo") + }) + Convey("rejects an unrecognised conclusion", func() { + response := cli.Parse(commandFromChannel1("subscribe owner/repo foo")) + So(response["printToChannel"], ShouldBeNil) + So(response["text"], ShouldContainSubstring, "conclusion") + }) + Convey("rejects a malformed filter", func() { + response := cli.Parse(commandFromChannel1("subscribe owner/repo foo:bar")) + So(response["printToChannel"], ShouldBeNil) + So(response["text"], ShouldContainSubstring, "filter") + + response = cli.Parse(commandFromChannel1("subscribe owner/repo foo:bar:success")) + So(response["printToChannel"], ShouldBeNil) + So(response["text"], ShouldContainSubstring, "filter") + }) + Convey("rejects a duplicated filter", func() { + response := cli.Parse(commandFromChannel1("subscribe owner/repo success failure")) + So(response["printToChannel"], ShouldBeNil) + So(response["text"], ShouldContainSubstring, "duplicated") + + response = cli.Parse(commandFromChannel1("subscribe owner/repo workflows:bar:success workflows:bar:failure")) + So(response["printToChannel"], ShouldBeNil) + So(response["text"], ShouldContainSubstring, "duplicated") + }) + Convey("accepts a well-formed filter", func() { + response := cli.Parse(commandFromChannel1("subscribe owner/repo workflows:workflow1:success failure")) + So(response["response_type"], ShouldEqual, "in_channel") + So(response["text"], ShouldContainSubstring, "Subscribed") + So(response["text"], ShouldContainSubstring, "workflow1") + }) + Convey("overrides an existing subscription", func() { + response := cli.Parse(commandFromChannel1("subscribe owner/repo workflows:workflow2:success failure")) + So(response["response_type"], ShouldEqual, "in_channel") + So(response["text"], ShouldContainSubstring, "Subscribed") + So(response["text"], ShouldContainSubstring, "workflow2") + + // Anachronistic usage of list command, this needs to be fixed + response = cli.Parse(commandFromChannel1("list owner/repo")) + So(response["response_type"], ShouldEqual, "in_channel") + So(response["text"], ShouldNotContainSubstring, "workflow1") + }) + }) + Convey("When asked to list", func() { + Convey("rejects an insufficient number of arguments", func() { + response := cli.Parse(commandFromChannel1("list")) + So(response["response_type"], ShouldBeNil) + So(response["text"], ShouldContainSubstring, "repo") + }) + Convey("correct lists subscribed channels", func() { + cli.Parse(commandFromChannel1("subscribe owner/repo")) + cli.Parse(commandFromChannel2("subscribe owner/repo workflows:workflow1 failure")) + response := cli.Parse(commandFromChannel1("list owner/repo")) + So(response["response_type"], ShouldEqual, "in_channel") + So(response["text"], ShouldContainSubstring, channelID1) + So(response["text"], ShouldContainSubstring, channelID2) + So(response["text"], ShouldContainSubstring, "workflow1") + So(response["text"], ShouldContainSubstring, "failure") + }) + Convey("responds correctly if no channels are subscribed", func() { + response := cli.Parse(commandFromChannel1("list owner/repo")) + So(response["response_type"], ShouldEqual, "in_channel") + So(response["text"], ShouldContainSubstring, " no") + }) + }) + Convey("When asked to unsubscribe", func() { + Convey("rejects an insufficient number of arguments", func() { + response := cli.Parse(commandFromChannel1("unsubscribe")) + So(response["response_type"], ShouldBeNil) + So(response["text"], ShouldContainSubstring, "repo") + }) + Convey("notifies if the channel is not subscribed to the repo", func() { + response := cli.Parse(commandFromChannel1("unsubscribe owner/repo")) + So(response["response_type"], ShouldBeNil) + So(response["text"], ShouldContainSubstring, "subscribed") + }) + Convey("correctly unsubscribes from a channel", func() { + cli.Parse(commandFromChannel1("subscribe owner/repo")) + cli.Parse(commandFromChannel1("unsubscribe owner/repo")) + response := cli.Parse(commandFromChannel1("list owner/repo")) + So(response["response_type"], ShouldEqual, "in_channel") + So(response["text"], ShouldContainSubstring, " no") + }) + Convey("correctly unsubscribes from only the requested channel", func() { + cli.Parse(commandFromChannel1("subscribe owner/repo")) + cli.Parse(commandFromChannel2("subscribe owner/repo workflows:workflow1 failure")) + cli.Parse(commandFromChannel1("unsubscribe owner/repo")) + response := cli.Parse(commandFromChannel1("list owner/repo")) + So(response["response_type"], ShouldEqual, "in_channel") + So(response["text"], ShouldContainSubstring, channelID2) + So(response["text"], ShouldContainSubstring, "workflow1") + So(response["text"], ShouldContainSubstring, "failure") + }) + Convey("is able to resubscribe", func() { + cli.Parse(commandFromChannel1("subscribe owner/repo")) + cli.Parse(commandFromChannel1("unsubscribe owner/repo")) + cli.Parse(commandFromChannel1("subscribe owner/repo")) + response := cli.Parse(commandFromChannel1("list owner/repo")) + So(response["response_type"], ShouldEqual, "in_channel") + So(response["text"], ShouldContainSubstring, channelID1) + }) + }) + }) + Convey("When receiving webhooks, the bot", t, func() { + Convey("has no tests at the moment", func() { + }) + }) +}