diff --git a/.circleci/config.yml b/.circleci/config.yml index 859ffce0b..5167ea207 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -363,6 +363,7 @@ workflows: - test: context: - Gruntwork Admin + - Slack Token For Test requires: - setup filters: diff --git a/go.mod b/go.mod index 4c818d593..7241cc1b6 100644 --- a/go.mod +++ b/go.mod @@ -48,6 +48,8 @@ require ( k8s.io/client-go v0.20.6 ) +require github.com/slack-go/slack v0.10.3 + require ( github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect @@ -72,10 +74,11 @@ require ( github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.3 // indirect - github.com/google/go-cmp v0.5.6 // indirect + github.com/google/go-cmp v0.5.7 // indirect github.com/google/gofuzz v1.1.0 // indirect github.com/googleapis/gax-go/v2 v2.0.5 // indirect github.com/googleapis/gnostic v0.4.1 // indirect + github.com/gorilla/websocket v1.4.2 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect diff --git a/go.sum b/go.sum index a4f2c8092..24cd45229 100644 --- a/go.sum +++ b/go.sum @@ -354,6 +354,7 @@ github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZp github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M= github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8= github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= @@ -419,8 +420,9 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-containerregistry v0.6.0 h1:niQ+8XD//kKgArIFwDVBXsWVWbde16LPdHMyNwSC8h4= github.com/google/go-containerregistry v0.6.0/go.mod h1:euCCtNbZ6tKqi1E72vwDj2xZcN5ttKpZLfa/wSo5iLw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -461,6 +463,7 @@ github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -739,6 +742,8 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/slack-go/slack v0.10.3 h1:kKYwlKY73AfSrtAk9UHWCXXfitudkDztNI9GYBviLxw= +github.com/slack-go/slack v0.10.3/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= diff --git a/modules/slack/doc.go b/modules/slack/doc.go new file mode 100644 index 000000000..4e5ec56d9 --- /dev/null +++ b/modules/slack/doc.go @@ -0,0 +1,2 @@ +// Package slack contains routines useful for testing slack integrations. +package slack diff --git a/modules/slack/validate.go b/modules/slack/validate.go new file mode 100644 index 000000000..e8b1feab9 --- /dev/null +++ b/modules/slack/validate.go @@ -0,0 +1,98 @@ +package slack + +import ( + "fmt" + "strconv" + "strings" + "time" + + "github.com/gruntwork-io/terratest/modules/testing" + "github.com/slack-go/slack" +) + +// ValidateExpectedSlackMessage validates whether a message containing the expected text was posted in the given channel +// ID, looking back historyLimit messages up to the given duration. For example, if you set (15*time.Minute) as the +// lookBack parameter with historyLimit set to 50, then this will look back the last 50 messages, up to 15 minutes ago. +// This expects a slack token to be provided. This returns MessageNotFoundErr when there is no match. +// For the purposes of matching, this only checks the following blocks: +// - Section block text +// - Header block text +// All other blocks are ignored in the validation. +// NOTE: This only looks for bot posted messages. +func ValidateExpectedSlackMessageE( + t testing.TestingT, + token, + channelID, + expectedText string, + historyLimit int, + lookBack time.Duration, +) error { + lookBackTime := time.Now().Add(-1 * lookBack) + slackClt := slack.New(token) + params := slack.GetConversationHistoryParameters{ + ChannelID: channelID, + Limit: historyLimit, + Oldest: strconv.FormatInt(lookBackTime.Unix(), 10), + } + + resp, err := slackClt.GetConversationHistory(¶ms) + if err != nil { + return err + } + + for _, msg := range resp.Messages { + if checkMessageContainsText(msg.Msg, expectedText) { + return nil + } + + if msg.SubMessage != nil { + if checkMessageContainsText(*msg.SubMessage, expectedText) { + return nil + } + } + } + return fmt.Errorf("still no message") +} + +func checkMessageContainsText(msg slack.Msg, expectedText string) bool { + // If this message is not a bot message, ignore. + if msg.Type != slack.MsgSubTypeBotMessage && msg.BotID == "" { + return false + } + + // Check message text + if strings.Contains(msg.Text, expectedText) { + return true + } + + // Check attachments + for _, attachment := range msg.Attachments { + if strings.Contains(attachment.Text, expectedText) { + return true + } + } + + // Check blocks + for _, block := range msg.Blocks.BlockSet { + switch block.BlockType() { + case slack.MBTSection: + sectionBlk := block.(*slack.SectionBlock) + if sectionBlk.Text != nil && strings.Contains(sectionBlk.Text.Text, expectedText) { + return true + } + case slack.MBTHeader: + headerBlk := block.(*slack.HeaderBlock) + if headerBlk.Text != nil && strings.Contains(headerBlk.Text.Text, expectedText) { + return true + } + } + } + + return false +} + +type MessageNotFoundErr struct{} + +func (err MessageNotFoundErr) Error() string { + return "Could not find the expected text in any of the messages posted in the given channel." +} diff --git a/modules/slack/validate_test.go b/modules/slack/validate_test.go new file mode 100644 index 000000000..752d21dbb --- /dev/null +++ b/modules/slack/validate_test.go @@ -0,0 +1,51 @@ +package slack + +import ( + "fmt" + "os" + "testing" + "time" + + "github.com/slack-go/slack" + "github.com/stretchr/testify/require" + + "github.com/gruntwork-io/terratest/modules/environment" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/gruntwork-io/terratest/modules/retry" +) + +const ( + slackTokenEnv = "SLACK_TOKEN_FOR_TEST" + slackChannelIDEnv = "SLACK_CHANNEL_ID_FOR_TEST" +) + +func TestValidateSlackMessage(t *testing.T) { + t.Parallel() + + environment.RequireEnvVar(t, slackTokenEnv) + environment.RequireEnvVar(t, slackChannelIDEnv) + + token := os.Getenv(slackTokenEnv) + channelID := os.Getenv(slackChannelIDEnv) + + uniqueID := random.UniqueId() + msgTxt := fmt.Sprintf("Test message from terratest: %s", uniqueID) + + slackClt := slack.New(token) + + _, _, err := slackClt.PostMessage( + channelID, + slack.MsgOptionText(msgTxt, false), + ) + require.NoError(t, err) + + retry.DoWithRetry( + t, + "wait for slack message", + 6, 10*time.Second, + func() (string, error) { + err := ValidateExpectedSlackMessageE(t, token, channelID, msgTxt, 10, 5*time.Minute) + return "", err + }, + ) +}