github.com/qichengzx/mattermost-server@v4.5.1-0.20180604164826-2c75247c97d0+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("WebhookExperimentReadOnly", func(t *testing.T) { 101 th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = false }) 102 _, err := http.Post(url, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"%s\"}", model.DEFAULT_CHANNEL))) 103 assert.Nil(t, err, "Not read only") 104 105 th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = true }) 106 th.App.SetLicense(model.NewTestLicense()) 107 }) 108 109 t.Run("WebhookAttachments", func(t *testing.T) { 110 attachmentPayload := `{ 111 "text": "this is a test", 112 "attachments": [ 113 { 114 "fallback": "Required plain-text summary of the attachment.", 115 116 "color": "#36a64f", 117 118 "pretext": "Optional text that appears above the attachment block", 119 120 "author_name": "Bobby Tables", 121 "author_link": "http://flickr.com/bobby/", 122 "author_icon": "http://flickr.com/icons/bobby.jpg", 123 124 "title": "Slack API Documentation", 125 "title_link": "https://api.slack.com/", 126 127 "text": "Optional text that appears within the attachment", 128 129 "fields": [ 130 { 131 "title": "Priority", 132 "value": "High", 133 "short": false 134 } 135 ], 136 137 "image_url": "http://my-website.com/path/to/image.jpg", 138 "thumb_url": "http://example.com/path/to/thumb.png" 139 } 140 ] 141 }` 142 143 resp, err := http.Post(url, "application/json", strings.NewReader(attachmentPayload)) 144 require.Nil(t, err) 145 assert.True(t, resp.StatusCode == http.StatusOK) 146 147 attachmentPayload = `{ 148 "text": "this is a test", 149 "attachments": [ 150 { 151 "fallback": "Required plain-text summary of the attachment.", 152 153 "color": "#36a64f", 154 155 "pretext": "Optional text that appears above the attachment block", 156 157 "author_name": "Bobby Tables", 158 "author_link": "http://flickr.com/bobby/", 159 "author_icon": "http://flickr.com/icons/bobby.jpg", 160 161 "title": "Slack API Documentation", 162 "title_link": "https://api.slack.com/", 163 164 "text": "` + tooLongText + `", 165 166 "fields": [ 167 { 168 "title": "Priority", 169 "value": "High", 170 "short": false 171 } 172 ], 173 174 "image_url": "http://my-website.com/path/to/image.jpg", 175 "thumb_url": "http://example.com/path/to/thumb.png" 176 } 177 ] 178 }` 179 180 resp, err = http.Post(url, "application/json", strings.NewReader(attachmentPayload)) 181 require.Nil(t, err) 182 assert.True(t, resp.StatusCode == http.StatusOK) 183 }) 184 185 t.Run("ChannelLockedWebhook", func(t *testing.T) { 186 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) 187 require.Nil(t, err) 188 189 hook, err := th.App.CreateIncomingWebhookForChannel(th.BasicUser.Id, th.BasicChannel, &model.IncomingWebhook{ChannelId: th.BasicChannel.Id, ChannelLocked: true}) 190 require.Nil(t, err) 191 192 url := ApiClient.Url + "/hooks/" + hook.Id 193 194 payload := "payload={\"text\": \"test text\"}" 195 resp, err2 := http.Post(url, "application/x-www-form-urlencoded", strings.NewReader(payload)) 196 require.Nil(t, err2) 197 assert.True(t, resp.StatusCode == http.StatusOK) 198 199 resp, err2 = http.Post(url, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"%s\"}", th.BasicChannel.Name))) 200 require.Nil(t, err2) 201 assert.True(t, resp.StatusCode == http.StatusOK) 202 203 resp, err2 = http.Post(url, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"%s\"}", channel.Name))) 204 require.Nil(t, err2) 205 assert.True(t, resp.StatusCode == http.StatusForbidden) 206 }) 207 208 t.Run("DisableWebhooks", func(t *testing.T) { 209 th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = false }) 210 resp, err := http.Post(url, "application/json", strings.NewReader("{\"text\":\"this is a test\"}")) 211 require.Nil(t, err) 212 assert.True(t, resp.StatusCode == http.StatusNotImplemented) 213 }) 214 } 215 216 func TestCommandWebhooks(t *testing.T) { 217 th := Setup().InitBasic() 218 defer th.TearDown() 219 220 cmd, err := th.App.CreateCommand(&model.Command{ 221 CreatorId: th.BasicUser.Id, 222 TeamId: th.BasicTeam.Id, 223 URL: "http://nowhere.com", 224 Method: model.COMMAND_METHOD_POST, 225 Trigger: "delayed"}) 226 require.Nil(t, err) 227 228 args := &model.CommandArgs{ 229 TeamId: th.BasicTeam.Id, 230 UserId: th.BasicUser.Id, 231 ChannelId: th.BasicChannel.Id, 232 } 233 234 hook, err := th.App.CreateCommandWebhook(cmd.Id, args) 235 if err != nil { 236 t.Fatal(err) 237 } 238 239 if resp, _ := http.Post(ApiClient.Url+"/hooks/commands/123123123123", "application/json", bytes.NewBufferString(`{"text":"this is a test"}`)); resp.StatusCode != http.StatusNotFound { 240 t.Fatal("expected not-found for non-existent hook") 241 } 242 243 if resp, err := http.Post(ApiClient.Url+"/hooks/commands/"+hook.Id, "application/json", bytes.NewBufferString(`{"text":"invalid`)); err != nil || resp.StatusCode != http.StatusBadRequest { 244 t.Fatal(err) 245 } 246 247 for i := 0; i < 5; i++ { 248 if resp, err := http.Post(ApiClient.Url+"/hooks/commands/"+hook.Id, "application/json", bytes.NewBufferString(`{"text":"this is a test"}`)); err != nil || resp.StatusCode != http.StatusOK { 249 t.Fatal(err) 250 } 251 } 252 253 if resp, _ := http.Post(ApiClient.Url+"/hooks/commands/"+hook.Id, "application/json", bytes.NewBufferString(`{"text":"this is a test"}`)); resp.StatusCode != http.StatusBadRequest { 254 t.Fatal("expected error for sixth usage") 255 } 256 }