github.com/gigforks/mattermost-server@v4.9.1-0.20180619094218-800d97fa55d0+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  	"strings"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/mattermost/mattermost-server/model"
    14  )
    15  
    16  func TestCreateIncomingWebhookForChannel(t *testing.T) {
    17  	th := Setup().InitBasic()
    18  	defer th.TearDown()
    19  
    20  	type TestCase struct {
    21  		EnableIncomingHooks        bool
    22  		EnablePostUsernameOverride bool
    23  		EnablePostIconOverride     bool
    24  		IncomingWebhook            model.IncomingWebhook
    25  
    26  		ExpectedError           bool
    27  		ExpectedIncomingWebhook *model.IncomingWebhook
    28  	}
    29  
    30  	for name, tc := range map[string]TestCase{
    31  		"webhooks not enabled": {
    32  			EnableIncomingHooks:        false,
    33  			EnablePostUsernameOverride: false,
    34  			EnablePostIconOverride:     false,
    35  			IncomingWebhook: model.IncomingWebhook{
    36  				DisplayName: "title",
    37  				Description: "description",
    38  				ChannelId:   th.BasicChannel.Id,
    39  			},
    40  
    41  			ExpectedError:           true,
    42  			ExpectedIncomingWebhook: nil,
    43  		},
    44  		"valid: username and post icon url ignored, since override not enabled": {
    45  			EnableIncomingHooks:        true,
    46  			EnablePostUsernameOverride: false,
    47  			EnablePostIconOverride:     false,
    48  			IncomingWebhook: model.IncomingWebhook{
    49  				DisplayName: "title",
    50  				Description: "description",
    51  				ChannelId:   th.BasicChannel.Id,
    52  				Username:    ":invalid and ignored:",
    53  				IconURL:     "ignored",
    54  			},
    55  
    56  			ExpectedError: false,
    57  			ExpectedIncomingWebhook: &model.IncomingWebhook{
    58  				DisplayName: "title",
    59  				Description: "description",
    60  				ChannelId:   th.BasicChannel.Id,
    61  			},
    62  		},
    63  		"invalid username, override enabled": {
    64  			EnableIncomingHooks:        true,
    65  			EnablePostUsernameOverride: true,
    66  			EnablePostIconOverride:     false,
    67  			IncomingWebhook: model.IncomingWebhook{
    68  				DisplayName: "title",
    69  				Description: "description",
    70  				ChannelId:   th.BasicChannel.Id,
    71  				Username:    ":invalid:",
    72  			},
    73  
    74  			ExpectedError:           true,
    75  			ExpectedIncomingWebhook: nil,
    76  		},
    77  		"valid, no username or post icon url provided": {
    78  			EnableIncomingHooks:        true,
    79  			EnablePostUsernameOverride: true,
    80  			EnablePostIconOverride:     true,
    81  			IncomingWebhook: model.IncomingWebhook{
    82  				DisplayName: "title",
    83  				Description: "description",
    84  				ChannelId:   th.BasicChannel.Id,
    85  			},
    86  
    87  			ExpectedError: false,
    88  			ExpectedIncomingWebhook: &model.IncomingWebhook{
    89  				DisplayName: "title",
    90  				Description: "description",
    91  				ChannelId:   th.BasicChannel.Id,
    92  			},
    93  		},
    94  		"valid, with username and post icon": {
    95  			EnableIncomingHooks:        true,
    96  			EnablePostUsernameOverride: true,
    97  			EnablePostIconOverride:     true,
    98  			IncomingWebhook: model.IncomingWebhook{
    99  				DisplayName: "title",
   100  				Description: "description",
   101  				ChannelId:   th.BasicChannel.Id,
   102  				Username:    "valid",
   103  				IconURL:     "http://example.com/icon",
   104  			},
   105  
   106  			ExpectedError: false,
   107  			ExpectedIncomingWebhook: &model.IncomingWebhook{
   108  				DisplayName: "title",
   109  				Description: "description",
   110  				ChannelId:   th.BasicChannel.Id,
   111  				Username:    "valid",
   112  				IconURL:     "http://example.com/icon",
   113  			},
   114  		},
   115  	} {
   116  		t.Run(name, func(t *testing.T) {
   117  			assert := assert.New(t)
   118  
   119  			th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = tc.EnableIncomingHooks })
   120  			th.App.UpdateConfig(func(cfg *model.Config) {
   121  				cfg.ServiceSettings.EnablePostUsernameOverride = tc.EnablePostUsernameOverride
   122  			})
   123  			th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnablePostIconOverride = tc.EnablePostIconOverride })
   124  
   125  			createdHook, err := th.App.CreateIncomingWebhookForChannel(th.BasicUser.Id, th.BasicChannel, &tc.IncomingWebhook)
   126  			if tc.ExpectedError && err == nil {
   127  				t.Fatal("should have failed")
   128  			} else if !tc.ExpectedError && err != nil {
   129  				t.Fatalf("should not have failed: %v", err.Error())
   130  			}
   131  			if createdHook != nil {
   132  				defer th.App.DeleteIncomingWebhook(createdHook.Id)
   133  			}
   134  			if tc.ExpectedIncomingWebhook == nil {
   135  				assert.Nil(createdHook, "expected nil webhook")
   136  			} else if assert.NotNil(createdHook, "expected non-nil webhook") {
   137  				assert.Equal(tc.ExpectedIncomingWebhook.DisplayName, createdHook.DisplayName)
   138  				assert.Equal(tc.ExpectedIncomingWebhook.Description, createdHook.Description)
   139  				assert.Equal(tc.ExpectedIncomingWebhook.ChannelId, createdHook.ChannelId)
   140  				assert.Equal(tc.ExpectedIncomingWebhook.Username, createdHook.Username)
   141  				assert.Equal(tc.ExpectedIncomingWebhook.IconURL, createdHook.IconURL)
   142  			}
   143  		})
   144  	}
   145  }
   146  
   147  func TestUpdateIncomingWebhook(t *testing.T) {
   148  	th := Setup().InitBasic()
   149  	defer th.TearDown()
   150  
   151  	type TestCase struct {
   152  		EnableIncomingHooks        bool
   153  		EnablePostUsernameOverride bool
   154  		EnablePostIconOverride     bool
   155  		IncomingWebhook            model.IncomingWebhook
   156  
   157  		ExpectedError           bool
   158  		ExpectedIncomingWebhook *model.IncomingWebhook
   159  	}
   160  
   161  	for name, tc := range map[string]TestCase{
   162  		"webhooks not enabled": {
   163  			EnableIncomingHooks:        false,
   164  			EnablePostUsernameOverride: false,
   165  			EnablePostIconOverride:     false,
   166  			IncomingWebhook: model.IncomingWebhook{
   167  				DisplayName: "title",
   168  				Description: "description",
   169  				ChannelId:   th.BasicChannel.Id,
   170  			},
   171  
   172  			ExpectedError:           true,
   173  			ExpectedIncomingWebhook: nil,
   174  		},
   175  		"valid: username and post icon url ignored, since override not enabled": {
   176  			EnableIncomingHooks:        true,
   177  			EnablePostUsernameOverride: false,
   178  			EnablePostIconOverride:     false,
   179  			IncomingWebhook: model.IncomingWebhook{
   180  				DisplayName: "title",
   181  				Description: "description",
   182  				ChannelId:   th.BasicChannel.Id,
   183  				Username:    ":invalid and ignored:",
   184  				IconURL:     "ignored",
   185  			},
   186  
   187  			ExpectedError: false,
   188  			ExpectedIncomingWebhook: &model.IncomingWebhook{
   189  				DisplayName: "title",
   190  				Description: "description",
   191  				ChannelId:   th.BasicChannel.Id,
   192  			},
   193  		},
   194  		"invalid username, override enabled": {
   195  			EnableIncomingHooks:        true,
   196  			EnablePostUsernameOverride: true,
   197  			EnablePostIconOverride:     false,
   198  			IncomingWebhook: model.IncomingWebhook{
   199  				DisplayName: "title",
   200  				Description: "description",
   201  				ChannelId:   th.BasicChannel.Id,
   202  				Username:    ":invalid:",
   203  			},
   204  
   205  			ExpectedError:           true,
   206  			ExpectedIncomingWebhook: nil,
   207  		},
   208  		"valid, no username or post icon url provided": {
   209  			EnableIncomingHooks:        true,
   210  			EnablePostUsernameOverride: true,
   211  			EnablePostIconOverride:     true,
   212  			IncomingWebhook: model.IncomingWebhook{
   213  				DisplayName: "title",
   214  				Description: "description",
   215  				ChannelId:   th.BasicChannel.Id,
   216  			},
   217  
   218  			ExpectedError: false,
   219  			ExpectedIncomingWebhook: &model.IncomingWebhook{
   220  				DisplayName: "title",
   221  				Description: "description",
   222  				ChannelId:   th.BasicChannel.Id,
   223  			},
   224  		},
   225  		"valid, with username and post icon": {
   226  			EnableIncomingHooks:        true,
   227  			EnablePostUsernameOverride: true,
   228  			EnablePostIconOverride:     true,
   229  			IncomingWebhook: model.IncomingWebhook{
   230  				DisplayName: "title",
   231  				Description: "description",
   232  				ChannelId:   th.BasicChannel.Id,
   233  				Username:    "valid",
   234  				IconURL:     "http://example.com/icon",
   235  			},
   236  
   237  			ExpectedError: false,
   238  			ExpectedIncomingWebhook: &model.IncomingWebhook{
   239  				DisplayName: "title",
   240  				Description: "description",
   241  				ChannelId:   th.BasicChannel.Id,
   242  				Username:    "valid",
   243  				IconURL:     "http://example.com/icon",
   244  			},
   245  		},
   246  	} {
   247  		t.Run(name, func(t *testing.T) {
   248  			assert := assert.New(t)
   249  
   250  			th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true })
   251  
   252  			hook, err := th.App.CreateIncomingWebhookForChannel(th.BasicUser.Id, th.BasicChannel, &model.IncomingWebhook{
   253  				ChannelId: th.BasicChannel.Id,
   254  			})
   255  			if err != nil {
   256  				t.Fatal(err.Error())
   257  			}
   258  			defer th.App.DeleteIncomingWebhook(hook.Id)
   259  
   260  			th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = tc.EnableIncomingHooks })
   261  			th.App.UpdateConfig(func(cfg *model.Config) {
   262  				cfg.ServiceSettings.EnablePostUsernameOverride = tc.EnablePostUsernameOverride
   263  			})
   264  			th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnablePostIconOverride = tc.EnablePostIconOverride })
   265  
   266  			updatedHook, err := th.App.UpdateIncomingWebhook(hook, &tc.IncomingWebhook)
   267  			if tc.ExpectedError && err == nil {
   268  				t.Fatal("should have failed")
   269  			} else if !tc.ExpectedError && err != nil {
   270  				t.Fatalf("should not have failed: %v", err.Error())
   271  			}
   272  			if tc.ExpectedIncomingWebhook == nil {
   273  				assert.Nil(updatedHook, "expected nil webhook")
   274  			} else if assert.NotNil(updatedHook, "expected non-nil webhook") {
   275  				assert.Equal(tc.ExpectedIncomingWebhook.DisplayName, updatedHook.DisplayName)
   276  				assert.Equal(tc.ExpectedIncomingWebhook.Description, updatedHook.Description)
   277  				assert.Equal(tc.ExpectedIncomingWebhook.ChannelId, updatedHook.ChannelId)
   278  				assert.Equal(tc.ExpectedIncomingWebhook.Username, updatedHook.Username)
   279  				assert.Equal(tc.ExpectedIncomingWebhook.IconURL, updatedHook.IconURL)
   280  			}
   281  		})
   282  	}
   283  }
   284  
   285  func TestCreateWebhookPost(t *testing.T) {
   286  	th := Setup().InitBasic()
   287  	defer th.TearDown()
   288  
   289  	th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true })
   290  
   291  	hook, err := th.App.CreateIncomingWebhookForChannel(th.BasicUser.Id, th.BasicChannel, &model.IncomingWebhook{ChannelId: th.BasicChannel.Id})
   292  	if err != nil {
   293  		t.Fatal(err.Error())
   294  	}
   295  	defer th.App.DeleteIncomingWebhook(hook.Id)
   296  
   297  	post, err := th.App.CreateWebhookPost(hook.UserId, th.BasicChannel, "foo", "user", "http://iconurl", model.StringInterface{
   298  		"attachments": []*model.SlackAttachment{
   299  			{
   300  				Text: "text",
   301  			},
   302  		},
   303  		"webhook_display_name": hook.DisplayName,
   304  	}, model.POST_SLACK_ATTACHMENT, "")
   305  	if err != nil {
   306  		t.Fatal(err.Error())
   307  	}
   308  
   309  	for _, k := range []string{"from_webhook", "attachments", "webhook_display_name"} {
   310  		if _, ok := post.Props[k]; !ok {
   311  			t.Log("missing one props: " + k)
   312  			t.Fatal(k)
   313  		}
   314  	}
   315  
   316  	_, err = th.App.CreateWebhookPost(hook.UserId, th.BasicChannel, "foo", "user", "http://iconurl", nil, model.POST_SYSTEM_GENERIC, "")
   317  	if err == nil {
   318  		t.Fatal("should have failed - bad post type")
   319  	}
   320  
   321  	expectedText := "`<>|<>|`"
   322  	post, err = th.App.CreateWebhookPost(hook.UserId, th.BasicChannel, expectedText, "user", "http://iconurl", model.StringInterface{
   323  		"attachments": []*model.SlackAttachment{
   324  			{
   325  				Text: "text",
   326  			},
   327  		},
   328  		"webhook_display_name": hook.DisplayName,
   329  	}, model.POST_SLACK_ATTACHMENT, "")
   330  	if err != nil {
   331  		t.Fatal(err.Error())
   332  	}
   333  	assert.Equal(t, expectedText, post.Message)
   334  
   335  	expectedText = "< | \n|\n>"
   336  	post, err = th.App.CreateWebhookPost(hook.UserId, th.BasicChannel, expectedText, "user", "http://iconurl", model.StringInterface{
   337  		"attachments": []*model.SlackAttachment{
   338  			{
   339  				Text: "text",
   340  			},
   341  		},
   342  		"webhook_display_name": hook.DisplayName,
   343  	}, model.POST_SLACK_ATTACHMENT, "")
   344  	if err != nil {
   345  		t.Fatal(err.Error())
   346  	}
   347  	assert.Equal(t, expectedText, post.Message)
   348  
   349  	expectedText = `commit bc95839e4a430ace453e8b209a3723c000c1729a
   350  Author: foo <foo@example.org>
   351  Date:   Thu Mar 1 19:46:54 2018 +0300
   352  
   353      commit message 2
   354  
   355    test | 1 +
   356   1 file changed, 1 insertion(+)
   357  
   358  commit 5df78b7139b543997838071cd912e375d8bd69b2
   359  Author: foo <foo@example.org>
   360  Date:   Thu Mar 1 19:46:48 2018 +0300
   361  
   362      commit message 1
   363  
   364   test | 3 +++
   365   1 file changed, 3 insertions(+)`
   366  	post, err = th.App.CreateWebhookPost(hook.UserId, th.BasicChannel, expectedText, "user", "http://iconurl", model.StringInterface{
   367  		"attachments": []*model.SlackAttachment{
   368  			{
   369  				Text: "text",
   370  			},
   371  		},
   372  		"webhook_display_name": hook.DisplayName,
   373  	}, model.POST_SLACK_ATTACHMENT, "")
   374  	if err != nil {
   375  		t.Fatal(err.Error())
   376  	}
   377  	assert.Equal(t, expectedText, post.Message)
   378  }
   379  
   380  func TestSplitWebhookPost(t *testing.T) {
   381  	type TestCase struct {
   382  		Post     *model.Post
   383  		Expected []*model.Post
   384  	}
   385  
   386  	maxPostSize := 10000
   387  
   388  	for name, tc := range map[string]TestCase{
   389  		"LongPost": {
   390  			Post: &model.Post{
   391  				Message: strings.Repeat("本", maxPostSize*3/2),
   392  			},
   393  			Expected: []*model.Post{
   394  				{
   395  					Message: strings.Repeat("本", maxPostSize),
   396  				},
   397  				{
   398  					Message: strings.Repeat("本", maxPostSize/2),
   399  				},
   400  			},
   401  		},
   402  		"LongPostAndMultipleAttachments": {
   403  			Post: &model.Post{
   404  				Message: strings.Repeat("本", maxPostSize*3/2),
   405  				Props: map[string]interface{}{
   406  					"attachments": []*model.SlackAttachment{
   407  						&model.SlackAttachment{
   408  							Text: strings.Repeat("本", 1000),
   409  						},
   410  						&model.SlackAttachment{
   411  							Text: strings.Repeat("本", 2000),
   412  						},
   413  						&model.SlackAttachment{
   414  							Text: strings.Repeat("本", model.POST_PROPS_MAX_USER_RUNES-1000),
   415  						},
   416  					},
   417  				},
   418  			},
   419  			Expected: []*model.Post{
   420  				{
   421  					Message: strings.Repeat("本", maxPostSize),
   422  				},
   423  				{
   424  					Message: strings.Repeat("本", maxPostSize/2),
   425  					Props: map[string]interface{}{
   426  						"attachments": []*model.SlackAttachment{
   427  							&model.SlackAttachment{
   428  								Text: strings.Repeat("本", 1000),
   429  							},
   430  							&model.SlackAttachment{
   431  								Text: strings.Repeat("本", 2000),
   432  							},
   433  						},
   434  					},
   435  				},
   436  				{
   437  					Props: map[string]interface{}{
   438  						"attachments": []*model.SlackAttachment{
   439  							&model.SlackAttachment{
   440  								Text: strings.Repeat("本", model.POST_PROPS_MAX_USER_RUNES-1000),
   441  							},
   442  						},
   443  					},
   444  				},
   445  			},
   446  		},
   447  		"UnsplittableProps": {
   448  			Post: &model.Post{
   449  				Message: "foo",
   450  				Props: map[string]interface{}{
   451  					"foo": strings.Repeat("x", model.POST_PROPS_MAX_USER_RUNES*2),
   452  				},
   453  			},
   454  		},
   455  	} {
   456  		t.Run(name, func(t *testing.T) {
   457  			splits, err := SplitWebhookPost(tc.Post, maxPostSize)
   458  			if tc.Expected == nil {
   459  				require.NotNil(t, err)
   460  			} else {
   461  				require.Nil(t, err)
   462  			}
   463  			assert.Equal(t, len(tc.Expected), len(splits))
   464  			for i, split := range splits {
   465  				if i < len(tc.Expected) {
   466  					assert.Equal(t, tc.Expected[i].Message, split.Message)
   467  					assert.Equal(t, tc.Expected[i].Props["attachments"], split.Props["attachments"])
   468  				}
   469  			}
   470  		})
   471  	}
   472  }