github.com/ashishbhate/mattermost-server@v5.11.1+incompatible/app/webhook_test.go (about)

     1  // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package app
     5  
     6  import (
     7  	"encoding/json"
     8  	"io"
     9  	"net/http"
    10  	"net/http/httptest"
    11  	"net/url"
    12  	"strings"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/mattermost/mattermost-server/model"
    17  	"github.com/mattermost/mattermost-server/services/httpservice"
    18  	"github.com/stretchr/testify/assert"
    19  	"github.com/stretchr/testify/require"
    20  )
    21  
    22  func TestCreateIncomingWebhookForChannel(t *testing.T) {
    23  	th := Setup(t).InitBasic()
    24  	defer th.TearDown()
    25  
    26  	type TestCase struct {
    27  		EnableIncomingHooks        bool
    28  		EnablePostUsernameOverride bool
    29  		EnablePostIconOverride     bool
    30  		IncomingWebhook            model.IncomingWebhook
    31  
    32  		ExpectedError           bool
    33  		ExpectedIncomingWebhook *model.IncomingWebhook
    34  	}
    35  
    36  	for name, tc := range map[string]TestCase{
    37  		"webhooks not enabled": {
    38  			EnableIncomingHooks:        false,
    39  			EnablePostUsernameOverride: false,
    40  			EnablePostIconOverride:     false,
    41  			IncomingWebhook: model.IncomingWebhook{
    42  				DisplayName: "title",
    43  				Description: "description",
    44  				ChannelId:   th.BasicChannel.Id,
    45  			},
    46  
    47  			ExpectedError:           true,
    48  			ExpectedIncomingWebhook: nil,
    49  		},
    50  		"valid: username and post icon url ignored, since override not enabled": {
    51  			EnableIncomingHooks:        true,
    52  			EnablePostUsernameOverride: false,
    53  			EnablePostIconOverride:     false,
    54  			IncomingWebhook: model.IncomingWebhook{
    55  				DisplayName: "title",
    56  				Description: "description",
    57  				ChannelId:   th.BasicChannel.Id,
    58  				Username:    ":invalid and ignored:",
    59  				IconURL:     "ignored",
    60  			},
    61  
    62  			ExpectedError: false,
    63  			ExpectedIncomingWebhook: &model.IncomingWebhook{
    64  				DisplayName: "title",
    65  				Description: "description",
    66  				ChannelId:   th.BasicChannel.Id,
    67  			},
    68  		},
    69  		"invalid username, override enabled": {
    70  			EnableIncomingHooks:        true,
    71  			EnablePostUsernameOverride: true,
    72  			EnablePostIconOverride:     false,
    73  			IncomingWebhook: model.IncomingWebhook{
    74  				DisplayName: "title",
    75  				Description: "description",
    76  				ChannelId:   th.BasicChannel.Id,
    77  				Username:    ":invalid:",
    78  			},
    79  
    80  			ExpectedError:           true,
    81  			ExpectedIncomingWebhook: nil,
    82  		},
    83  		"valid, no username or post icon url provided": {
    84  			EnableIncomingHooks:        true,
    85  			EnablePostUsernameOverride: true,
    86  			EnablePostIconOverride:     true,
    87  			IncomingWebhook: model.IncomingWebhook{
    88  				DisplayName: "title",
    89  				Description: "description",
    90  				ChannelId:   th.BasicChannel.Id,
    91  			},
    92  
    93  			ExpectedError: false,
    94  			ExpectedIncomingWebhook: &model.IncomingWebhook{
    95  				DisplayName: "title",
    96  				Description: "description",
    97  				ChannelId:   th.BasicChannel.Id,
    98  			},
    99  		},
   100  		"valid, with username and post icon": {
   101  			EnableIncomingHooks:        true,
   102  			EnablePostUsernameOverride: true,
   103  			EnablePostIconOverride:     true,
   104  			IncomingWebhook: model.IncomingWebhook{
   105  				DisplayName: "title",
   106  				Description: "description",
   107  				ChannelId:   th.BasicChannel.Id,
   108  				Username:    "valid",
   109  				IconURL:     "http://example.com/icon",
   110  			},
   111  
   112  			ExpectedError: false,
   113  			ExpectedIncomingWebhook: &model.IncomingWebhook{
   114  				DisplayName: "title",
   115  				Description: "description",
   116  				ChannelId:   th.BasicChannel.Id,
   117  				Username:    "valid",
   118  				IconURL:     "http://example.com/icon",
   119  			},
   120  		},
   121  	} {
   122  		t.Run(name, func(t *testing.T) {
   123  			assert := assert.New(t)
   124  
   125  			th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableIncomingWebhooks = tc.EnableIncomingHooks })
   126  			th.App.UpdateConfig(func(cfg *model.Config) {
   127  				*cfg.ServiceSettings.EnablePostUsernameOverride = tc.EnablePostUsernameOverride
   128  			})
   129  			th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnablePostIconOverride = tc.EnablePostIconOverride })
   130  
   131  			createdHook, err := th.App.CreateIncomingWebhookForChannel(th.BasicUser.Id, th.BasicChannel, &tc.IncomingWebhook)
   132  			if tc.ExpectedError && err == nil {
   133  				t.Fatal("should have failed")
   134  			} else if !tc.ExpectedError && err != nil {
   135  				t.Fatalf("should not have failed: %v", err.Error())
   136  			}
   137  			if createdHook != nil {
   138  				defer th.App.DeleteIncomingWebhook(createdHook.Id)
   139  			}
   140  			if tc.ExpectedIncomingWebhook == nil {
   141  				assert.Nil(createdHook, "expected nil webhook")
   142  			} else if assert.NotNil(createdHook, "expected non-nil webhook") {
   143  				assert.Equal(tc.ExpectedIncomingWebhook.DisplayName, createdHook.DisplayName)
   144  				assert.Equal(tc.ExpectedIncomingWebhook.Description, createdHook.Description)
   145  				assert.Equal(tc.ExpectedIncomingWebhook.ChannelId, createdHook.ChannelId)
   146  				assert.Equal(tc.ExpectedIncomingWebhook.Username, createdHook.Username)
   147  				assert.Equal(tc.ExpectedIncomingWebhook.IconURL, createdHook.IconURL)
   148  			}
   149  		})
   150  	}
   151  }
   152  
   153  func TestUpdateIncomingWebhook(t *testing.T) {
   154  	th := Setup(t).InitBasic()
   155  	defer th.TearDown()
   156  
   157  	type TestCase struct {
   158  		EnableIncomingHooks        bool
   159  		EnablePostUsernameOverride bool
   160  		EnablePostIconOverride     bool
   161  		IncomingWebhook            model.IncomingWebhook
   162  
   163  		ExpectedError           bool
   164  		ExpectedIncomingWebhook *model.IncomingWebhook
   165  	}
   166  
   167  	for name, tc := range map[string]TestCase{
   168  		"webhooks not enabled": {
   169  			EnableIncomingHooks:        false,
   170  			EnablePostUsernameOverride: false,
   171  			EnablePostIconOverride:     false,
   172  			IncomingWebhook: model.IncomingWebhook{
   173  				DisplayName: "title",
   174  				Description: "description",
   175  				ChannelId:   th.BasicChannel.Id,
   176  			},
   177  
   178  			ExpectedError:           true,
   179  			ExpectedIncomingWebhook: nil,
   180  		},
   181  		"valid: username and post icon url ignored, since override not enabled": {
   182  			EnableIncomingHooks:        true,
   183  			EnablePostUsernameOverride: false,
   184  			EnablePostIconOverride:     false,
   185  			IncomingWebhook: model.IncomingWebhook{
   186  				DisplayName: "title",
   187  				Description: "description",
   188  				ChannelId:   th.BasicChannel.Id,
   189  				Username:    ":invalid and ignored:",
   190  				IconURL:     "ignored",
   191  			},
   192  
   193  			ExpectedError: false,
   194  			ExpectedIncomingWebhook: &model.IncomingWebhook{
   195  				DisplayName: "title",
   196  				Description: "description",
   197  				ChannelId:   th.BasicChannel.Id,
   198  			},
   199  		},
   200  		"invalid username, override enabled": {
   201  			EnableIncomingHooks:        true,
   202  			EnablePostUsernameOverride: true,
   203  			EnablePostIconOverride:     false,
   204  			IncomingWebhook: model.IncomingWebhook{
   205  				DisplayName: "title",
   206  				Description: "description",
   207  				ChannelId:   th.BasicChannel.Id,
   208  				Username:    ":invalid:",
   209  			},
   210  
   211  			ExpectedError:           true,
   212  			ExpectedIncomingWebhook: nil,
   213  		},
   214  		"valid, no username or post icon url provided": {
   215  			EnableIncomingHooks:        true,
   216  			EnablePostUsernameOverride: true,
   217  			EnablePostIconOverride:     true,
   218  			IncomingWebhook: model.IncomingWebhook{
   219  				DisplayName: "title",
   220  				Description: "description",
   221  				ChannelId:   th.BasicChannel.Id,
   222  			},
   223  
   224  			ExpectedError: false,
   225  			ExpectedIncomingWebhook: &model.IncomingWebhook{
   226  				DisplayName: "title",
   227  				Description: "description",
   228  				ChannelId:   th.BasicChannel.Id,
   229  			},
   230  		},
   231  		"valid, with username and post icon": {
   232  			EnableIncomingHooks:        true,
   233  			EnablePostUsernameOverride: true,
   234  			EnablePostIconOverride:     true,
   235  			IncomingWebhook: model.IncomingWebhook{
   236  				DisplayName: "title",
   237  				Description: "description",
   238  				ChannelId:   th.BasicChannel.Id,
   239  				Username:    "valid",
   240  				IconURL:     "http://example.com/icon",
   241  			},
   242  
   243  			ExpectedError: false,
   244  			ExpectedIncomingWebhook: &model.IncomingWebhook{
   245  				DisplayName: "title",
   246  				Description: "description",
   247  				ChannelId:   th.BasicChannel.Id,
   248  				Username:    "valid",
   249  				IconURL:     "http://example.com/icon",
   250  			},
   251  		},
   252  	} {
   253  		t.Run(name, func(t *testing.T) {
   254  			assert := assert.New(t)
   255  
   256  			th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableIncomingWebhooks = true })
   257  
   258  			hook, err := th.App.CreateIncomingWebhookForChannel(th.BasicUser.Id, th.BasicChannel, &model.IncomingWebhook{
   259  				ChannelId: th.BasicChannel.Id,
   260  			})
   261  			if err != nil {
   262  				t.Fatal(err.Error())
   263  			}
   264  			defer th.App.DeleteIncomingWebhook(hook.Id)
   265  
   266  			th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableIncomingWebhooks = tc.EnableIncomingHooks })
   267  			th.App.UpdateConfig(func(cfg *model.Config) {
   268  				*cfg.ServiceSettings.EnablePostUsernameOverride = tc.EnablePostUsernameOverride
   269  			})
   270  			th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnablePostIconOverride = tc.EnablePostIconOverride })
   271  
   272  			updatedHook, err := th.App.UpdateIncomingWebhook(hook, &tc.IncomingWebhook)
   273  			if tc.ExpectedError && err == nil {
   274  				t.Fatal("should have failed")
   275  			} else if !tc.ExpectedError && err != nil {
   276  				t.Fatalf("should not have failed: %v", err.Error())
   277  			}
   278  			if tc.ExpectedIncomingWebhook == nil {
   279  				assert.Nil(updatedHook, "expected nil webhook")
   280  			} else if assert.NotNil(updatedHook, "expected non-nil webhook") {
   281  				assert.Equal(tc.ExpectedIncomingWebhook.DisplayName, updatedHook.DisplayName)
   282  				assert.Equal(tc.ExpectedIncomingWebhook.Description, updatedHook.Description)
   283  				assert.Equal(tc.ExpectedIncomingWebhook.ChannelId, updatedHook.ChannelId)
   284  				assert.Equal(tc.ExpectedIncomingWebhook.Username, updatedHook.Username)
   285  				assert.Equal(tc.ExpectedIncomingWebhook.IconURL, updatedHook.IconURL)
   286  			}
   287  		})
   288  	}
   289  }
   290  
   291  func TestCreateWebhookPost(t *testing.T) {
   292  	th := Setup(t).InitBasic()
   293  	defer th.TearDown()
   294  
   295  	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableIncomingWebhooks = true })
   296  
   297  	hook, err := th.App.CreateIncomingWebhookForChannel(th.BasicUser.Id, th.BasicChannel, &model.IncomingWebhook{ChannelId: th.BasicChannel.Id})
   298  	if err != nil {
   299  		t.Fatal(err.Error())
   300  	}
   301  	defer th.App.DeleteIncomingWebhook(hook.Id)
   302  
   303  	post, err := th.App.CreateWebhookPost(hook.UserId, th.BasicChannel, "foo", "user", "http://iconurl", model.StringInterface{
   304  		"attachments": []*model.SlackAttachment{
   305  			{
   306  				Text: "text",
   307  			},
   308  		},
   309  		"webhook_display_name": hook.DisplayName,
   310  	}, model.POST_SLACK_ATTACHMENT, "")
   311  	if err != nil {
   312  		t.Fatal(err.Error())
   313  	}
   314  
   315  	for _, k := range []string{"from_webhook", "attachments", "webhook_display_name"} {
   316  		if _, ok := post.Props[k]; !ok {
   317  			t.Log("missing one props: " + k)
   318  			t.Fatal(k)
   319  		}
   320  	}
   321  
   322  	_, err = th.App.CreateWebhookPost(hook.UserId, th.BasicChannel, "foo", "user", "http://iconurl", nil, model.POST_SYSTEM_GENERIC, "")
   323  	if err == nil {
   324  		t.Fatal("should have failed - bad post type")
   325  	}
   326  
   327  	expectedText := "`<>|<>|`"
   328  	post, err = th.App.CreateWebhookPost(hook.UserId, th.BasicChannel, expectedText, "user", "http://iconurl", model.StringInterface{
   329  		"attachments": []*model.SlackAttachment{
   330  			{
   331  				Text: "text",
   332  			},
   333  		},
   334  		"webhook_display_name": hook.DisplayName,
   335  	}, model.POST_SLACK_ATTACHMENT, "")
   336  	if err != nil {
   337  		t.Fatal(err.Error())
   338  	}
   339  	assert.Equal(t, expectedText, post.Message)
   340  
   341  	expectedText = "< | \n|\n>"
   342  	post, err = th.App.CreateWebhookPost(hook.UserId, th.BasicChannel, expectedText, "user", "http://iconurl", model.StringInterface{
   343  		"attachments": []*model.SlackAttachment{
   344  			{
   345  				Text: "text",
   346  			},
   347  		},
   348  		"webhook_display_name": hook.DisplayName,
   349  	}, model.POST_SLACK_ATTACHMENT, "")
   350  	if err != nil {
   351  		t.Fatal(err.Error())
   352  	}
   353  	assert.Equal(t, expectedText, post.Message)
   354  
   355  	expectedText = `commit bc95839e4a430ace453e8b209a3723c000c1729a
   356  Author: foo <foo@example.org>
   357  Date:   Thu Mar 1 19:46:54 2018 +0300
   358  
   359      commit message 2
   360  
   361    test | 1 +
   362   1 file changed, 1 insertion(+)
   363  
   364  commit 5df78b7139b543997838071cd912e375d8bd69b2
   365  Author: foo <foo@example.org>
   366  Date:   Thu Mar 1 19:46:48 2018 +0300
   367  
   368      commit message 1
   369  
   370   test | 3 +++
   371   1 file changed, 3 insertions(+)`
   372  	post, err = th.App.CreateWebhookPost(hook.UserId, th.BasicChannel, expectedText, "user", "http://iconurl", model.StringInterface{
   373  		"attachments": []*model.SlackAttachment{
   374  			{
   375  				Text: "text",
   376  			},
   377  		},
   378  		"webhook_display_name": hook.DisplayName,
   379  	}, model.POST_SLACK_ATTACHMENT, "")
   380  	if err != nil {
   381  		t.Fatal(err.Error())
   382  	}
   383  	assert.Equal(t, expectedText, post.Message)
   384  }
   385  
   386  func TestSplitWebhookPost(t *testing.T) {
   387  	type TestCase struct {
   388  		Post     *model.Post
   389  		Expected []*model.Post
   390  	}
   391  
   392  	maxPostSize := 10000
   393  
   394  	for name, tc := range map[string]TestCase{
   395  		"LongPost": {
   396  			Post: &model.Post{
   397  				Message: strings.Repeat("本", maxPostSize*3/2),
   398  			},
   399  			Expected: []*model.Post{
   400  				{
   401  					Message: strings.Repeat("本", maxPostSize),
   402  				},
   403  				{
   404  					Message: strings.Repeat("本", maxPostSize/2),
   405  				},
   406  			},
   407  		},
   408  		"LongPostAndMultipleAttachments": {
   409  			Post: &model.Post{
   410  				Message: strings.Repeat("本", maxPostSize*3/2),
   411  				Props: map[string]interface{}{
   412  					"attachments": []*model.SlackAttachment{
   413  						&model.SlackAttachment{
   414  							Text: strings.Repeat("本", 1000),
   415  						},
   416  						&model.SlackAttachment{
   417  							Text: strings.Repeat("本", 2000),
   418  						},
   419  						&model.SlackAttachment{
   420  							Text: strings.Repeat("本", model.POST_PROPS_MAX_USER_RUNES-1000),
   421  						},
   422  					},
   423  				},
   424  			},
   425  			Expected: []*model.Post{
   426  				{
   427  					Message: strings.Repeat("本", maxPostSize),
   428  				},
   429  				{
   430  					Message: strings.Repeat("本", maxPostSize/2),
   431  					Props: map[string]interface{}{
   432  						"attachments": []*model.SlackAttachment{
   433  							&model.SlackAttachment{
   434  								Text: strings.Repeat("本", 1000),
   435  							},
   436  							&model.SlackAttachment{
   437  								Text: strings.Repeat("本", 2000),
   438  							},
   439  						},
   440  					},
   441  				},
   442  				{
   443  					Props: map[string]interface{}{
   444  						"attachments": []*model.SlackAttachment{
   445  							&model.SlackAttachment{
   446  								Text: strings.Repeat("本", model.POST_PROPS_MAX_USER_RUNES-1000),
   447  							},
   448  						},
   449  					},
   450  				},
   451  			},
   452  		},
   453  		"UnsplittableProps": {
   454  			Post: &model.Post{
   455  				Message: "foo",
   456  				Props: map[string]interface{}{
   457  					"foo": strings.Repeat("x", model.POST_PROPS_MAX_USER_RUNES*2),
   458  				},
   459  			},
   460  		},
   461  	} {
   462  		t.Run(name, func(t *testing.T) {
   463  			splits, err := SplitWebhookPost(tc.Post, maxPostSize)
   464  			if tc.Expected == nil {
   465  				require.NotNil(t, err)
   466  			} else {
   467  				require.Nil(t, err)
   468  			}
   469  			assert.Equal(t, len(tc.Expected), len(splits))
   470  			for i, split := range splits {
   471  				if i < len(tc.Expected) {
   472  					assert.Equal(t, tc.Expected[i].Message, split.Message)
   473  					assert.Equal(t, tc.Expected[i].Props["attachments"], split.Props["attachments"])
   474  				}
   475  			}
   476  		})
   477  	}
   478  }
   479  
   480  func TestCreateOutGoingWebhookWithUsernameAndIconURL(t *testing.T) {
   481  	th := Setup(t).InitBasic()
   482  	defer th.TearDown()
   483  
   484  	outgoingWebhook := model.OutgoingWebhook{
   485  		ChannelId:    th.BasicChannel.Id,
   486  		TeamId:       th.BasicChannel.TeamId,
   487  		CallbackURLs: []string{"http://nowhere.com"},
   488  		Username:     "some-user-name",
   489  		IconURL:      "http://some-icon/",
   490  		DisplayName:  "some-display-name",
   491  		Description:  "some-description",
   492  		CreatorId:    th.BasicUser.Id,
   493  	}
   494  
   495  	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOutgoingWebhooks = true })
   496  
   497  	createdHook, err := th.App.CreateOutgoingWebhook(&outgoingWebhook)
   498  
   499  	if err != nil {
   500  		t.Fatalf("should not have failed: %v", err.Error())
   501  	}
   502  
   503  	assert.NotNil(t, createdHook, "should not be null")
   504  
   505  	assert.Equal(t, createdHook.ChannelId, outgoingWebhook.ChannelId)
   506  	assert.Equal(t, createdHook.TeamId, outgoingWebhook.TeamId)
   507  	assert.Equal(t, createdHook.CallbackURLs, outgoingWebhook.CallbackURLs)
   508  	assert.Equal(t, createdHook.Username, outgoingWebhook.Username)
   509  	assert.Equal(t, createdHook.IconURL, outgoingWebhook.IconURL)
   510  	assert.Equal(t, createdHook.DisplayName, outgoingWebhook.DisplayName)
   511  	assert.Equal(t, createdHook.Description, outgoingWebhook.Description)
   512  
   513  }
   514  
   515  func TestTriggerOutGoingWebhookWithUsernameAndIconURL(t *testing.T) {
   516  
   517  	getPayload := func(hook *model.OutgoingWebhook, th *TestHelper, channel *model.Channel) *model.OutgoingWebhookPayload {
   518  		return &model.OutgoingWebhookPayload{
   519  			Token:       hook.Token,
   520  			TeamId:      hook.TeamId,
   521  			TeamDomain:  th.BasicTeam.Name,
   522  			ChannelId:   channel.Id,
   523  			ChannelName: channel.Name,
   524  			Timestamp:   th.BasicPost.CreateAt,
   525  			UserId:      th.BasicPost.UserId,
   526  			UserName:    th.BasicUser.Username,
   527  			PostId:      th.BasicPost.Id,
   528  			Text:        th.BasicPost.Message,
   529  			TriggerWord: "Abracadabra",
   530  			FileIds:     strings.Join(th.BasicPost.FileIds, ","),
   531  		}
   532  	}
   533  
   534  	waitUntilWebhookResposeIsCreatedAsPost := func(channel *model.Channel, th *TestHelper, t *testing.T, createdPost chan *model.Post) {
   535  		go func() {
   536  			for i := 0; i < 5; i++ {
   537  				time.Sleep(time.Second)
   538  				posts, _ := th.App.GetPosts(channel.Id, 0, 5)
   539  				if len(posts.Posts) > 0 {
   540  					for _, post := range posts.Posts {
   541  						createdPost <- post
   542  						return
   543  					}
   544  				}
   545  			}
   546  		}()
   547  	}
   548  
   549  	type TestCaseOutgoing struct {
   550  		EnablePostUsernameOverride bool
   551  		EnablePostIconOverride     bool
   552  		ExpectedUsername           string
   553  		ExpectedIconUrl            string
   554  		WebhookResponse            *model.OutgoingWebhookResponse
   555  	}
   556  
   557  	createOutgoingWebhook := func(channel *model.Channel, testCallBackUrl string, th *TestHelper) (*model.OutgoingWebhook, *model.AppError) {
   558  		outgoingWebhook := model.OutgoingWebhook{
   559  			ChannelId:    channel.Id,
   560  			TeamId:       channel.TeamId,
   561  			CallbackURLs: []string{testCallBackUrl},
   562  			Username:     "some-user-name",
   563  			IconURL:      "http://some-icon/",
   564  			DisplayName:  "some-display-name",
   565  			Description:  "some-description",
   566  			CreatorId:    th.BasicUser.Id,
   567  			TriggerWords: []string{"Abracadabra"},
   568  			ContentType:  "application/json",
   569  		}
   570  
   571  		return th.App.CreateOutgoingWebhook(&outgoingWebhook)
   572  	}
   573  
   574  	getTestCases := func() map[string]TestCaseOutgoing {
   575  
   576  		webHookResponse := "sample response text from test server"
   577  		testCasesOutgoing := map[string]TestCaseOutgoing{
   578  
   579  			"Should override username and Icon": {
   580  				EnablePostUsernameOverride: true,
   581  				EnablePostIconOverride:     true,
   582  				ExpectedUsername:           "some-user-name",
   583  				ExpectedIconUrl:            "http://some-icon/",
   584  			},
   585  			"Should not override username and Icon": {
   586  				EnablePostUsernameOverride: false,
   587  				EnablePostIconOverride:     false,
   588  			},
   589  			"Should not override username and Icon if the webhook response already has it": {
   590  				EnablePostUsernameOverride: true,
   591  				EnablePostIconOverride:     true,
   592  				ExpectedUsername:           "webhookuser",
   593  				ExpectedIconUrl:            "http://webhok/icon",
   594  				WebhookResponse:            &model.OutgoingWebhookResponse{Text: &webHookResponse, Username: "webhookuser", IconURL: "http://webhok/icon"},
   595  			},
   596  		}
   597  		return testCasesOutgoing
   598  	}
   599  
   600  	th := Setup(t).InitBasic()
   601  	defer th.TearDown()
   602  
   603  	th.App.UpdateConfig(func(cfg *model.Config) {
   604  		*cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost 127.0.0.1"
   605  	})
   606  	createdPost := make(chan *model.Post)
   607  
   608  	for name, testCase := range getTestCases() {
   609  		t.Run(name, func(t *testing.T) {
   610  
   611  			th.App.UpdateConfig(func(cfg *model.Config) {
   612  				*cfg.ServiceSettings.EnableOutgoingWebhooks = true
   613  				*cfg.ServiceSettings.EnablePostUsernameOverride = testCase.EnablePostUsernameOverride
   614  				*cfg.ServiceSettings.EnablePostIconOverride = testCase.EnablePostIconOverride
   615  			})
   616  
   617  			ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   618  				if testCase.WebhookResponse != nil {
   619  					w.Write([]byte(testCase.WebhookResponse.ToJson()))
   620  				} else {
   621  					w.Write([]byte(`{"text": "sample response text from test server"}`))
   622  				}
   623  			}))
   624  			defer ts.Close()
   625  
   626  			channel := th.CreateChannel(th.BasicTeam)
   627  			hook, _ := createOutgoingWebhook(channel, ts.URL, th)
   628  			payload := getPayload(hook, th, channel)
   629  
   630  			th.App.TriggerWebhook(payload, hook, th.BasicPost, channel)
   631  
   632  			waitUntilWebhookResposeIsCreatedAsPost(channel, th, t, createdPost)
   633  
   634  			select {
   635  			case webhookPost := <-createdPost:
   636  				assert.Equal(t, webhookPost.Message, "sample response text from test server")
   637  				assert.Equal(t, webhookPost.Props["from_webhook"], "true")
   638  				if testCase.ExpectedIconUrl != "" {
   639  					assert.Equal(t, webhookPost.Props["override_icon_url"], testCase.ExpectedIconUrl)
   640  				} else {
   641  					assert.Nil(t, webhookPost.Props["override_icon_url"])
   642  				}
   643  
   644  				if testCase.ExpectedUsername != "" {
   645  					assert.Equal(t, webhookPost.Props["override_username"], testCase.ExpectedUsername)
   646  				} else {
   647  					assert.Nil(t, webhookPost.Props["override_username"])
   648  				}
   649  			case <-time.After(5 * time.Second):
   650  				t.Fatal("Timeout, webhook response not created as post")
   651  			}
   652  
   653  		})
   654  	}
   655  
   656  }
   657  
   658  type InfiniteReader struct {
   659  	Prefix string
   660  }
   661  
   662  func (r InfiniteReader) Read(p []byte) (n int, err error) {
   663  	for i := range p {
   664  		p[i] = 'a'
   665  	}
   666  
   667  	return len(p), nil
   668  }
   669  
   670  func TestDoOutgoingWebhookRequest(t *testing.T) {
   671  	th := Setup(t).InitBasic()
   672  	defer th.TearDown()
   673  
   674  	th.App.UpdateConfig(func(cfg *model.Config) {
   675  		cfg.ServiceSettings.AllowedUntrustedInternalConnections = model.NewString("127.0.0.1")
   676  		*cfg.ServiceSettings.EnableOutgoingWebhooks = true
   677  	})
   678  
   679  	t.Run("with a valid response", func(t *testing.T) {
   680  		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   681  			io.Copy(w, strings.NewReader(`{"text": "Hello, World!"}`))
   682  		}))
   683  		defer server.Close()
   684  
   685  		resp, err := th.App.doOutgoingWebhookRequest(server.URL, strings.NewReader(""), "application/json")
   686  		require.Nil(t, err)
   687  
   688  		assert.NotNil(t, resp)
   689  		assert.NotNil(t, resp.Text)
   690  		assert.Equal(t, "Hello, World!", *resp.Text)
   691  	})
   692  
   693  	t.Run("with an invalid response", func(t *testing.T) {
   694  		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   695  			io.Copy(w, strings.NewReader("aaaaaaaa"))
   696  		}))
   697  		defer server.Close()
   698  
   699  		_, err := th.App.doOutgoingWebhookRequest(server.URL, strings.NewReader(""), "application/json")
   700  		require.NotNil(t, err)
   701  		require.IsType(t, &json.SyntaxError{}, err)
   702  	})
   703  
   704  	t.Run("with a large, valid response", func(t *testing.T) {
   705  		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   706  			io.Copy(w, io.MultiReader(strings.NewReader(`{"text": "`), InfiniteReader{}, strings.NewReader(`"}`)))
   707  		}))
   708  		defer server.Close()
   709  
   710  		_, err := th.App.doOutgoingWebhookRequest(server.URL, strings.NewReader(""), "application/json")
   711  		require.NotNil(t, err)
   712  		require.Equal(t, io.ErrUnexpectedEOF, err)
   713  	})
   714  
   715  	t.Run("with a large, invalid response", func(t *testing.T) {
   716  		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   717  			io.Copy(w, InfiniteReader{})
   718  		}))
   719  		defer server.Close()
   720  
   721  		_, err := th.App.doOutgoingWebhookRequest(server.URL, strings.NewReader(""), "application/json")
   722  		require.NotNil(t, err)
   723  		require.IsType(t, &json.SyntaxError{}, err)
   724  	})
   725  
   726  	t.Run("with a slow response", func(t *testing.T) {
   727  		timeout := 100 * time.Millisecond
   728  
   729  		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   730  			time.Sleep(timeout + time.Millisecond)
   731  			io.Copy(w, strings.NewReader(`{"text": "Hello, World!"}`))
   732  		}))
   733  		defer server.Close()
   734  
   735  		th.App.HTTPService.(*httpservice.HTTPServiceImpl).RequestTimeout = timeout
   736  		defer func() {
   737  			th.App.HTTPService.(*httpservice.HTTPServiceImpl).RequestTimeout = httpservice.RequestTimeout
   738  		}()
   739  
   740  		_, err := th.App.doOutgoingWebhookRequest(server.URL, strings.NewReader(""), "application/json")
   741  		require.NotNil(t, err)
   742  		require.IsType(t, &url.Error{}, err)
   743  	})
   744  }