github.com/dschalla/mattermost-server@v4.8.1-rc1+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  
   322  func TestSplitWebhookPost(t *testing.T) {
   323  	type TestCase struct {
   324  		Post     *model.Post
   325  		Expected []*model.Post
   326  	}
   327  
   328  	for name, tc := range map[string]TestCase{
   329  		"LongPost": {
   330  			Post: &model.Post{
   331  				Message: strings.Repeat("本", model.POST_MESSAGE_MAX_RUNES*3/2),
   332  			},
   333  			Expected: []*model.Post{
   334  				{
   335  					Message: strings.Repeat("本", model.POST_MESSAGE_MAX_RUNES),
   336  				},
   337  				{
   338  					Message: strings.Repeat("本", model.POST_MESSAGE_MAX_RUNES/2),
   339  				},
   340  			},
   341  		},
   342  		"LongPostAndMultipleAttachments": {
   343  			Post: &model.Post{
   344  				Message: strings.Repeat("本", model.POST_MESSAGE_MAX_RUNES*3/2),
   345  				Props: map[string]interface{}{
   346  					"attachments": []*model.SlackAttachment{
   347  						&model.SlackAttachment{
   348  							Text: strings.Repeat("本", 1000),
   349  						},
   350  						&model.SlackAttachment{
   351  							Text: strings.Repeat("本", 2000),
   352  						},
   353  						&model.SlackAttachment{
   354  							Text: strings.Repeat("本", model.POST_PROPS_MAX_USER_RUNES-1000),
   355  						},
   356  					},
   357  				},
   358  			},
   359  			Expected: []*model.Post{
   360  				{
   361  					Message: strings.Repeat("本", model.POST_MESSAGE_MAX_RUNES),
   362  				},
   363  				{
   364  					Message: strings.Repeat("本", model.POST_MESSAGE_MAX_RUNES/2),
   365  					Props: map[string]interface{}{
   366  						"attachments": []*model.SlackAttachment{
   367  							&model.SlackAttachment{
   368  								Text: strings.Repeat("本", 1000),
   369  							},
   370  							&model.SlackAttachment{
   371  								Text: strings.Repeat("本", 2000),
   372  							},
   373  						},
   374  					},
   375  				},
   376  				{
   377  					Props: map[string]interface{}{
   378  						"attachments": []*model.SlackAttachment{
   379  							&model.SlackAttachment{
   380  								Text: strings.Repeat("本", model.POST_PROPS_MAX_USER_RUNES-1000),
   381  							},
   382  						},
   383  					},
   384  				},
   385  			},
   386  		},
   387  		"UnsplittableProps": {
   388  			Post: &model.Post{
   389  				Message: "foo",
   390  				Props: map[string]interface{}{
   391  					"foo": strings.Repeat("x", model.POST_PROPS_MAX_USER_RUNES*2),
   392  				},
   393  			},
   394  		},
   395  	} {
   396  		t.Run(name, func(t *testing.T) {
   397  			splits, err := SplitWebhookPost(tc.Post)
   398  			if tc.Expected == nil {
   399  				require.NotNil(t, err)
   400  			} else {
   401  				require.Nil(t, err)
   402  			}
   403  			assert.Equal(t, len(tc.Expected), len(splits))
   404  			for i, split := range splits {
   405  				if i < len(tc.Expected) {
   406  					assert.Equal(t, tc.Expected[i].Message, split.Message)
   407  					assert.Equal(t, tc.Expected[i].Props["attachments"], split.Props["attachments"])
   408  				}
   409  			}
   410  		})
   411  	}
   412  }