github.com/coincircle/mattermost-server@v4.8.1-0.20180321182714-9d701c704416+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  	for name, tc := range map[string]TestCase{
   387  		"LongPost": {
   388  			Post: &model.Post{
   389  				Message: strings.Repeat("本", model.POST_MESSAGE_MAX_RUNES*3/2),
   390  			},
   391  			Expected: []*model.Post{
   392  				{
   393  					Message: strings.Repeat("本", model.POST_MESSAGE_MAX_RUNES),
   394  				},
   395  				{
   396  					Message: strings.Repeat("本", model.POST_MESSAGE_MAX_RUNES/2),
   397  				},
   398  			},
   399  		},
   400  		"LongPostAndMultipleAttachments": {
   401  			Post: &model.Post{
   402  				Message: strings.Repeat("本", model.POST_MESSAGE_MAX_RUNES*3/2),
   403  				Props: map[string]interface{}{
   404  					"attachments": []*model.SlackAttachment{
   405  						&model.SlackAttachment{
   406  							Text: strings.Repeat("本", 1000),
   407  						},
   408  						&model.SlackAttachment{
   409  							Text: strings.Repeat("本", 2000),
   410  						},
   411  						&model.SlackAttachment{
   412  							Text: strings.Repeat("本", model.POST_PROPS_MAX_USER_RUNES-1000),
   413  						},
   414  					},
   415  				},
   416  			},
   417  			Expected: []*model.Post{
   418  				{
   419  					Message: strings.Repeat("本", model.POST_MESSAGE_MAX_RUNES),
   420  				},
   421  				{
   422  					Message: strings.Repeat("本", model.POST_MESSAGE_MAX_RUNES/2),
   423  					Props: map[string]interface{}{
   424  						"attachments": []*model.SlackAttachment{
   425  							&model.SlackAttachment{
   426  								Text: strings.Repeat("本", 1000),
   427  							},
   428  							&model.SlackAttachment{
   429  								Text: strings.Repeat("本", 2000),
   430  							},
   431  						},
   432  					},
   433  				},
   434  				{
   435  					Props: map[string]interface{}{
   436  						"attachments": []*model.SlackAttachment{
   437  							&model.SlackAttachment{
   438  								Text: strings.Repeat("本", model.POST_PROPS_MAX_USER_RUNES-1000),
   439  							},
   440  						},
   441  					},
   442  				},
   443  			},
   444  		},
   445  		"UnsplittableProps": {
   446  			Post: &model.Post{
   447  				Message: "foo",
   448  				Props: map[string]interface{}{
   449  					"foo": strings.Repeat("x", model.POST_PROPS_MAX_USER_RUNES*2),
   450  				},
   451  			},
   452  		},
   453  	} {
   454  		t.Run(name, func(t *testing.T) {
   455  			splits, err := SplitWebhookPost(tc.Post)
   456  			if tc.Expected == nil {
   457  				require.NotNil(t, err)
   458  			} else {
   459  				require.Nil(t, err)
   460  			}
   461  			assert.Equal(t, len(tc.Expected), len(splits))
   462  			for i, split := range splits {
   463  				if i < len(tc.Expected) {
   464  					assert.Equal(t, tc.Expected[i].Message, split.Message)
   465  					assert.Equal(t, tc.Expected[i].Props["attachments"], split.Props["attachments"])
   466  				}
   467  			}
   468  		})
   469  	}
   470  }