github.com/cjdelisle/matterfoss@v5.11.1+incompatible/web/webhook_test.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See License.txt for license information. 3 4 package web 5 6 import ( 7 "bytes" 8 "fmt" 9 "net/http" 10 "strings" 11 "testing" 12 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 16 "github.com/mattermost/mattermost-server/model" 17 ) 18 19 func TestIncomingWebhook(t *testing.T) { 20 th := Setup().InitBasic() 21 defer th.TearDown() 22 23 if !*th.App.Config().ServiceSettings.EnableIncomingWebhooks { 24 _, err := http.Post(ApiClient.Url+"/hooks/123", "", strings.NewReader("123")) 25 assert.NotNil(t, err, "should have errored - webhooks turned off") 26 return 27 } 28 29 hook, err := th.App.CreateIncomingWebhookForChannel(th.BasicUser.Id, th.BasicChannel, &model.IncomingWebhook{ChannelId: th.BasicChannel.Id}) 30 require.Nil(t, err) 31 32 url := ApiClient.Url + "/hooks/" + hook.Id 33 34 tooLongText := "" 35 for i := 0; i < 8200; i++ { 36 tooLongText += "a" 37 } 38 39 t.Run("WebhookBasics", func(t *testing.T) { 40 payload := "payload={\"text\": \"test text\"}" 41 resp, err := http.Post(url, "application/x-www-form-urlencoded", strings.NewReader(payload)) 42 require.Nil(t, err) 43 assert.True(t, resp.StatusCode == http.StatusOK) 44 45 payload = "payload={\"text\": \"\"}" 46 resp, err = http.Post(url, "application/x-www-form-urlencoded", strings.NewReader(payload)) 47 require.Nil(t, err) 48 assert.True(t, resp.StatusCode != http.StatusOK, "should have errored - no text to post") 49 50 payload = "payload={\"text\": \"test text\", \"channel\": \"junk\"}" 51 resp, err = http.Post(url, "application/x-www-form-urlencoded", strings.NewReader(payload)) 52 require.Nil(t, err) 53 assert.True(t, resp.StatusCode != http.StatusOK, "should have errored - bad channel") 54 55 payload = "payload={\"text\": \"test text\"}" 56 resp, err = http.Post(ApiClient.Url+"/hooks/abc123", "application/x-www-form-urlencoded", strings.NewReader(payload)) 57 require.Nil(t, err) 58 assert.True(t, resp.StatusCode != http.StatusOK, "should have errored - bad hook") 59 60 resp, err = http.Post(url, "application/json", strings.NewReader("{\"text\":\"this is a test\"}")) 61 require.Nil(t, err) 62 assert.True(t, resp.StatusCode == http.StatusOK) 63 64 text := `this is a \"test\" 65 that contains a newline and a tab` 66 resp, err = http.Post(url, "application/json", strings.NewReader("{\"text\":\""+text+"\"}")) 67 require.Nil(t, err) 68 assert.True(t, resp.StatusCode == http.StatusOK) 69 70 resp, err = http.Post(url, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"%s\"}", th.BasicChannel.Name))) 71 require.Nil(t, err) 72 assert.True(t, resp.StatusCode == http.StatusOK) 73 74 resp, err = http.Post(url, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"#%s\"}", th.BasicChannel.Name))) 75 require.Nil(t, err) 76 assert.True(t, resp.StatusCode == http.StatusOK) 77 78 resp, err = http.Post(url, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"@%s\"}", th.BasicUser.Username))) 79 require.Nil(t, err) 80 assert.True(t, resp.StatusCode == http.StatusOK) 81 82 resp, err = http.Post(url, "application/x-www-form-urlencoded", strings.NewReader("payload={\"text\":\"this is a test\"}")) 83 require.Nil(t, err) 84 assert.True(t, resp.StatusCode == http.StatusOK) 85 86 resp, err = http.Post(url, "application/x-www-form-urlencoded", strings.NewReader("payload={\"text\":\""+text+"\"}")) 87 assert.Nil(t, err) 88 assert.True(t, resp.StatusCode == http.StatusOK) 89 90 resp, err = http.Post(url, "application/json", strings.NewReader("{\"text\":\""+tooLongText+"\"}")) 91 require.Nil(t, err) 92 assert.True(t, resp.StatusCode == http.StatusOK) 93 94 payloadMultiPart := "------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"username\"\r\n\r\nwebhook-bot\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"text\"\r\n\r\nthis is a test :tada:\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW--" 95 resp, err = http.Post(ApiClient.Url+"/hooks/"+hook.Id, "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW", strings.NewReader(payloadMultiPart)) 96 require.Nil(t, err) 97 assert.True(t, resp.StatusCode == http.StatusOK) 98 }) 99 100 t.Run("WebhookExperimentalReadOnly", func(t *testing.T) { 101 th.App.SetLicense(model.NewTestLicense()) 102 th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = true }) 103 104 // Read only default channel should fail. 105 resp, err := http.Post(url, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"%s\"}", model.DEFAULT_CHANNEL))) 106 require.Nil(t, err) 107 assert.True(t, resp.StatusCode != http.StatusOK) 108 109 // None-default channel should still work. 110 resp, err = http.Post(url, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"%s\"}", th.BasicChannel.Name))) 111 require.Nil(t, err) 112 assert.True(t, resp.StatusCode == http.StatusOK) 113 114 // System-Admin Owned Hook 115 adminHook, err := th.App.CreateIncomingWebhookForChannel(th.SystemAdminUser.Id, th.BasicChannel, &model.IncomingWebhook{ChannelId: th.BasicChannel.Id}) 116 require.Nil(t, err) 117 adminUrl := ApiClient.Url + "/hooks/" + adminHook.Id 118 119 resp, err = http.Post(adminUrl, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"%s\"}", model.DEFAULT_CHANNEL))) 120 require.Nil(t, err) 121 assert.True(t, resp.StatusCode == http.StatusOK) 122 123 th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = false }) 124 }) 125 126 t.Run("WebhookAttachments", func(t *testing.T) { 127 attachmentPayload := `{ 128 "text": "this is a test", 129 "attachments": [ 130 { 131 "fallback": "Required plain-text summary of the attachment.", 132 133 "color": "#36a64f", 134 135 "pretext": "Optional text that appears above the attachment block", 136 137 "author_name": "Bobby Tables", 138 "author_link": "http://flickr.com/bobby/", 139 "author_icon": "http://flickr.com/icons/bobby.jpg", 140 141 "title": "Slack API Documentation", 142 "title_link": "https://api.slack.com/", 143 144 "text": "Optional text that appears within the attachment", 145 146 "fields": [ 147 { 148 "title": "Priority", 149 "value": "High", 150 "short": false 151 } 152 ], 153 154 "image_url": "http://my-website.com/path/to/image.jpg", 155 "thumb_url": "http://example.com/path/to/thumb.png" 156 } 157 ] 158 }` 159 160 resp, err := http.Post(url, "application/json", strings.NewReader(attachmentPayload)) 161 require.Nil(t, err) 162 assert.True(t, resp.StatusCode == http.StatusOK) 163 164 attachmentPayload = `{ 165 "text": "this is a test", 166 "attachments": [ 167 { 168 "fallback": "Required plain-text summary of the attachment.", 169 170 "color": "#36a64f", 171 172 "pretext": "Optional text that appears above the attachment block", 173 174 "author_name": "Bobby Tables", 175 "author_link": "http://flickr.com/bobby/", 176 "author_icon": "http://flickr.com/icons/bobby.jpg", 177 178 "title": "Slack API Documentation", 179 "title_link": "https://api.slack.com/", 180 181 "text": "` + tooLongText + `", 182 183 "fields": [ 184 { 185 "title": "Priority", 186 "value": "High", 187 "short": false 188 } 189 ], 190 191 "image_url": "http://my-website.com/path/to/image.jpg", 192 "thumb_url": "http://example.com/path/to/thumb.png" 193 } 194 ] 195 }` 196 197 resp, err = http.Post(url, "application/json", strings.NewReader(attachmentPayload)) 198 require.Nil(t, err) 199 assert.True(t, resp.StatusCode == http.StatusOK) 200 }) 201 202 t.Run("ChannelLockedWebhook", func(t *testing.T) { 203 channel, err := th.App.CreateChannel(&model.Channel{TeamId: th.BasicTeam.Id, Name: model.NewId(), DisplayName: model.NewId(), Type: model.CHANNEL_OPEN, CreatorId: th.BasicUser.Id}, true) 204 require.Nil(t, err) 205 206 hook, err := th.App.CreateIncomingWebhookForChannel(th.BasicUser.Id, th.BasicChannel, &model.IncomingWebhook{ChannelId: th.BasicChannel.Id, ChannelLocked: true}) 207 require.Nil(t, err) 208 require.NotNil(t, hook) 209 210 apiHookUrl := ApiClient.Url + "/hooks/" + hook.Id 211 212 payload := "payload={\"text\": \"test text\"}" 213 resp, err2 := http.Post(apiHookUrl, "application/x-www-form-urlencoded", strings.NewReader(payload)) 214 require.Nil(t, err2) 215 assert.True(t, resp.StatusCode == http.StatusOK) 216 217 resp, err2 = http.Post(apiHookUrl, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"%s\"}", th.BasicChannel.Name))) 218 require.Nil(t, err2) 219 assert.True(t, resp.StatusCode == http.StatusOK) 220 221 resp, err2 = http.Post(apiHookUrl, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"%s\"}", channel.Name))) 222 require.Nil(t, err2) 223 assert.True(t, resp.StatusCode == http.StatusForbidden) 224 }) 225 226 t.Run("DisableWebhooks", func(t *testing.T) { 227 th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableIncomingWebhooks = false }) 228 resp, err := http.Post(url, "application/json", strings.NewReader("{\"text\":\"this is a test\"}")) 229 require.Nil(t, err) 230 assert.True(t, resp.StatusCode == http.StatusNotImplemented) 231 }) 232 } 233 234 func TestCommandWebhooks(t *testing.T) { 235 th := Setup().InitBasic() 236 defer th.TearDown() 237 238 cmd, appErr := th.App.CreateCommand(&model.Command{ 239 CreatorId: th.BasicUser.Id, 240 TeamId: th.BasicTeam.Id, 241 URL: "http://nowhere.com", 242 Method: model.COMMAND_METHOD_POST, 243 Trigger: "delayed"}) 244 require.Nil(t, appErr) 245 246 args := &model.CommandArgs{ 247 TeamId: th.BasicTeam.Id, 248 UserId: th.BasicUser.Id, 249 ChannelId: th.BasicChannel.Id, 250 } 251 252 hook, appErr := th.App.CreateCommandWebhook(cmd.Id, args) 253 if appErr != nil { 254 t.Fatal(appErr) 255 } 256 257 resp, err := http.Post(ApiClient.Url+"/hooks/commands/123123123123", "application/json", bytes.NewBufferString(`{"text":"this is a test"}`)) 258 require.NoError(t, err) 259 assert.Equal(t, http.StatusNotFound, resp.StatusCode, "expected not-found for non-existent hook") 260 261 resp, err = http.Post(ApiClient.Url+"/hooks/commands/"+hook.Id, "application/json", bytes.NewBufferString(`{"text":"invalid`)) 262 require.NoError(t, err) 263 assert.Equal(t, http.StatusBadRequest, resp.StatusCode) 264 265 for i := 0; i < 5; i++ { 266 if resp, appErr := http.Post(ApiClient.Url+"/hooks/commands/"+hook.Id, "application/json", bytes.NewBufferString(`{"text":"this is a test"}`)); err != nil || resp.StatusCode != http.StatusOK { 267 t.Fatal(appErr) 268 } 269 } 270 271 if resp, _ := http.Post(ApiClient.Url+"/hooks/commands/"+hook.Id, "application/json", bytes.NewBufferString(`{"text":"this is a test"}`)); resp.StatusCode != http.StatusBadRequest { 272 t.Fatal("expected error for sixth usage") 273 } 274 }