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  }