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  }