github.com/argoproj/argo-events@v1.9.1/eventsources/sources/slack/start_test.go (about) 1 /* 2 Copyright 2018 BlackRock, Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package slack 18 19 import ( 20 "bytes" 21 "crypto/hmac" 22 "crypto/sha256" 23 "encoding/hex" 24 "encoding/json" 25 "io" 26 "net/http" 27 "strconv" 28 "strings" 29 "testing" 30 "time" 31 32 "github.com/ghodss/yaml" 33 "github.com/slack-go/slack/slackevents" 34 "github.com/smartystreets/goconvey/convey" 35 36 "github.com/argoproj/argo-events/eventsources/common/webhook" 37 "github.com/argoproj/argo-events/pkg/apis/eventsource/v1alpha1" 38 ) 39 40 func TestRouteActiveHandler(t *testing.T) { 41 convey.Convey("Given a route configuration", t, func() { 42 router := &Router{ 43 route: webhook.GetFakeRoute(), 44 slackEventSource: &v1alpha1.SlackEventSource{}, 45 } 46 47 convey.Convey("Inactive route should return 404", func() { 48 writer := &webhook.FakeHttpWriter{} 49 router.HandleRoute(writer, &http.Request{}) 50 convey.So(writer.HeaderStatus, convey.ShouldEqual, http.StatusBadRequest) 51 }) 52 53 router.token = "Jhj5dZrVaK7ZwHHjRyZWjbDl" 54 router.route.Active = true 55 56 convey.Convey("Test url verification request", func() { 57 writer := &webhook.FakeHttpWriter{} 58 urlVer := slackevents.EventsAPIURLVerificationEvent{ 59 Type: slackevents.URLVerification, 60 Token: "Jhj5dZrVaK7ZwHHjRyZWjbDl", 61 Challenge: "3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P", 62 } 63 payload, err := yaml.Marshal(urlVer) 64 convey.So(err, convey.ShouldBeNil) 65 convey.So(payload, convey.ShouldNotBeNil) 66 router.HandleRoute(writer, &http.Request{ 67 Body: io.NopCloser(bytes.NewReader(payload)), 68 }) 69 convey.So(writer.HeaderStatus, convey.ShouldEqual, http.StatusInternalServerError) 70 }) 71 }) 72 } 73 74 func TestSlackSignature(t *testing.T) { 75 convey.Convey("Given a route that receives a message from Slack", t, func() { 76 router := &Router{ 77 route: webhook.GetFakeRoute(), 78 slackEventSource: &v1alpha1.SlackEventSource{}, 79 } 80 81 router.signingSecret = "abcdefghiklm1234567890" 82 convey.Convey("Validate request signature", func() { 83 writer := &webhook.FakeHttpWriter{} 84 payload := []byte("payload=%7B%22type%22%3A%22block_actions%22%2C%22team%22%3A%7B%22id%22%3A%22T0CAG%22%2C%22domain%22%3A%22acme-creamery%22%7D%2C%22user%22%3A%7B%22id%22%3A%22U0CA5%22%2C%22username%22%3A%22Amy%20McGee%22%2C%22name%22%3A%22Amy%20McGee%22%2C%22team_id%22%3A%22T3MDE%22%7D%2C%22api_app_id%22%3A%22A0CA5%22%2C%22token%22%3A%22Shh_its_a_seekrit%22%2C%22container%22%3A%7B%22type%22%3A%22message%22%2C%22text%22%3A%22The%20contents%20of%20the%20original%20message%20where%20the%20action%20originated%22%7D%2C%22trigger_id%22%3A%2212466734323.1395872398%22%2C%22response_url%22%3A%22https%3A%2F%2Fwww.postresponsestome.com%2FT123567%2F1509734234%22%2C%22actions%22%3A%5B%7B%22type%22%3A%22button%22%2C%22block_id%22%3A%22actionblock789%22%2C%22action_id%22%3A%2227S%22%2C%22text%22%3A%7B%22type%22%3A%22plain_text%22%2C%22text%22%3A%22Link%20Button%22%2C%22emoji%22%3Atrue%7D%2C%22action_ts%22%3A%221564701248.149432%22%7D%5D%7D") 85 h := make(http.Header) 86 87 rts := int(time.Now().UTC().UnixNano()) 88 hmac := hmac.New(sha256.New, []byte(router.signingSecret)) 89 b := strings.Join([]string{"v0", strconv.Itoa(rts), string(payload)}, ":") 90 _, err := hmac.Write([]byte(b)) 91 convey.So(err, convey.ShouldBeNil) 92 hash := hex.EncodeToString(hmac.Sum(nil)) 93 genSig := strings.TrimRight(strings.Join([]string{"v0=", hash}, ""), "\n") 94 h.Add("Content-Type", "application/x-www-form-urlencoded") 95 h.Add("X-Slack-Signature", genSig) 96 h.Add("X-Slack-Request-Timestamp", strconv.FormatInt(int64(rts), 10)) 97 98 router.route.Active = true 99 100 go func() { 101 <-router.route.DataCh 102 }() 103 104 router.HandleRoute(writer, &http.Request{ 105 Body: io.NopCloser(bytes.NewReader(payload)), 106 Header: h, 107 Method: "POST", 108 }) 109 convey.So(writer.HeaderStatus, convey.ShouldEqual, http.StatusOK) 110 }) 111 }) 112 } 113 114 func TestInteractionHandler(t *testing.T) { 115 convey.Convey("Given a route that receives an interaction event", t, func() { 116 router := &Router{ 117 route: webhook.GetFakeRoute(), 118 slackEventSource: &v1alpha1.SlackEventSource{}, 119 } 120 121 convey.Convey("Test an interaction action message", func() { 122 writer := &webhook.FakeHttpWriter{} 123 actionString := `{"type":"block_actions","team":{"id":"T9TK3CUKW","domain":"example"},"user":{"id":"UA8RXUSPL","username":"jtorrance","team_id":"T9TK3CUKW"},"api_app_id":"AABA1ABCD","token":"9s8d9as89d8as9d8as989","container":{"type":"message_attachment","message_ts":"1548261231.000200","attachment_id":1,"channel_id":"CBR2V3XEX","is_ephemeral":false,"is_app_unfurl":false},"trigger_id":"12321423423.333649436676.d8c1bb837935619ccad0f624c448ffb3","channel":{"id":"CBR2V3XEX","name":"review-updates"},"message":{"bot_id":"BAH5CA16Z","type":"message","text":"This content can't be displayed.","user":"UAJ2RU415","ts":"1548261231.000200"},"response_url":"https://hooks.slack.com/actions/AABA1ABCD/1232321423432/D09sSasdasdAS9091209","actions":[{"action_id":"WaXA","block_id":"=qXel","text":{"type":"plain_text","text":"View","emoji":true},"value":"click_me_123","type":"button","action_ts":"1548426417.840180"}]}` 124 payload := []byte(`payload=` + actionString) 125 out := make(chan []byte) 126 router.route.Active = true 127 128 go func() { 129 out <- <-router.route.DataCh 130 }() 131 132 var buf bytes.Buffer 133 buf.Write(payload) 134 135 headers := make(map[string][]string) 136 headers["Content-Type"] = append(headers["Content-Type"], "application/x-www-form-urlencoded") 137 router.HandleRoute(writer, &http.Request{ 138 Method: http.MethodPost, 139 Header: headers, 140 Body: io.NopCloser(strings.NewReader(buf.String())), 141 }) 142 result := <-out 143 convey.So(writer.HeaderStatus, convey.ShouldEqual, http.StatusOK) 144 convey.So(string(result), convey.ShouldContainSubstring, "\"type\":\"block_actions\"") 145 convey.So(string(result), convey.ShouldContainSubstring, "\"token\":\"9s8d9as89d8as9d8as989\"") 146 }) 147 }) 148 } 149 150 func TestSlackCommandHandler(t *testing.T) { 151 convey.Convey("Given a route that receives a slash command event", t, func() { 152 router := &Router{ 153 route: webhook.GetFakeRoute(), 154 slackEventSource: &v1alpha1.SlackEventSource{}, 155 } 156 157 convey.Convey("Test a slash command message", func() { 158 writer := &webhook.FakeHttpWriter{} 159 // Test values pulled from example here: https://api.slack.com/interactivity/slash-commands#app_command_handling 160 payload := []byte(`token=gIkuvaNzQIHg97ATvDxqgjtO&team_id=T0001&team_domain=example&enterprise_id=E0001&enterprise_name=Globular%20Construct%20Inc&channel_id=C2147483705&channel_name=test&user_id=U2147483697&user_name=Steve&command=/weather&text=94070&response_url=https://hooks.slack.com/commands/1234/5678&trigger_id=13345224609.738474920.8088930838d88f008e0&api_app_id=A123456`) 161 out := make(chan []byte) 162 router.route.Active = true 163 164 go func() { 165 out <- <-router.route.DataCh 166 }() 167 168 var buf bytes.Buffer 169 buf.Write(payload) 170 171 headers := make(map[string][]string) 172 headers["Content-Type"] = append(headers["Content-Type"], "application/x-www-form-urlencoded") 173 router.HandleRoute(writer, &http.Request{ 174 Method: http.MethodPost, 175 Header: headers, 176 Body: io.NopCloser(strings.NewReader(buf.String())), 177 }) 178 result := <-out 179 convey.So(writer.HeaderStatus, convey.ShouldEqual, http.StatusOK) 180 convey.So(string(result), convey.ShouldContainSubstring, "\"command\":\"/weather\"") 181 convey.So(string(result), convey.ShouldContainSubstring, "\"token\":\"gIkuvaNzQIHg97ATvDxqgjtO\"") 182 }) 183 }) 184 } 185 186 func TestEventHandler(t *testing.T) { 187 convey.Convey("Given a route that receives an event", t, func() { 188 router := &Router{ 189 route: webhook.GetFakeRoute(), 190 slackEventSource: &v1alpha1.SlackEventSource{}, 191 } 192 193 convey.Convey("Test an event notification", func() { 194 writer := &webhook.FakeHttpWriter{} 195 event := []byte(` 196 { 197 "type": "name_of_event", 198 "event_ts": "1234567890.123456", 199 "user": "UXXXXXXX1" 200 } 201 `) 202 203 j := json.RawMessage(event) 204 ce := slackevents.EventsAPICallbackEvent{ 205 Token: "Jhj5dZrVaK7ZwHHjRyZWjbDl", 206 Type: slackevents.CallbackEvent, 207 EventTime: 1234567890, 208 APIAppID: "AXXXXXXXXX", 209 AuthedUsers: []string{ 210 "UXXXXXXX1", 211 "UXXXXXXX2", 212 }, 213 EventID: "Ev08MFMKH6", 214 InnerEvent: &j, 215 } 216 payload, err := yaml.Marshal(ce) 217 convey.So(err, convey.ShouldBeNil) 218 219 router.route.Active = true 220 221 go func() { 222 <-router.route.DataCh 223 }() 224 225 router.HandleRoute(writer, &http.Request{ 226 Body: io.NopCloser(bytes.NewBuffer(payload)), 227 }) 228 convey.So(writer.HeaderStatus, convey.ShouldEqual, http.StatusInternalServerError) 229 }) 230 }) 231 }