github.com/adacta-ru/mattermost-server/v6@v6.0.0/app/slashcommands/command_test.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package slashcommands
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"net/http/httptest"
    11  	"net/url"
    12  	"strings"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/stretchr/testify/assert"
    17  	"github.com/stretchr/testify/require"
    18  
    19  	"github.com/adacta-ru/mattermost-server/v6/model"
    20  	"github.com/adacta-ru/mattermost-server/v6/services/httpservice"
    21  )
    22  
    23  type InfiniteReader struct {
    24  	Prefix string
    25  }
    26  
    27  func (r InfiniteReader) Read(p []byte) (n int, err error) {
    28  	for i := range p {
    29  		p[i] = 'a'
    30  	}
    31  
    32  	return len(p), nil
    33  }
    34  
    35  func TestMoveCommand(t *testing.T) {
    36  	th := setup(t)
    37  	defer th.tearDown()
    38  
    39  	sourceTeam := th.createTeam()
    40  	targetTeam := th.createTeam()
    41  
    42  	command := &model.Command{}
    43  	command.CreatorId = model.NewId()
    44  	command.Method = model.COMMAND_METHOD_POST
    45  	command.TeamId = sourceTeam.Id
    46  	command.URL = "http://nowhere.com/"
    47  	command.Trigger = "trigger1"
    48  
    49  	command, err := th.App.CreateCommand(command)
    50  	assert.Nil(t, err)
    51  
    52  	defer func() {
    53  		th.App.PermanentDeleteTeam(sourceTeam)
    54  		th.App.PermanentDeleteTeam(targetTeam)
    55  	}()
    56  
    57  	// Move a command and check the team is updated.
    58  	assert.Nil(t, th.App.MoveCommand(targetTeam, command))
    59  	retrievedCommand, err := th.App.GetCommand(command.Id)
    60  	assert.Nil(t, err)
    61  	assert.EqualValues(t, targetTeam.Id, retrievedCommand.TeamId)
    62  
    63  	// Move it to the team it's already in. Nothing should change.
    64  	assert.Nil(t, th.App.MoveCommand(targetTeam, command))
    65  	retrievedCommand, err = th.App.GetCommand(command.Id)
    66  	assert.Nil(t, err)
    67  	assert.EqualValues(t, targetTeam.Id, retrievedCommand.TeamId)
    68  }
    69  
    70  func TestCreateCommandPost(t *testing.T) {
    71  	th := setup(t).initBasic()
    72  	defer th.tearDown()
    73  
    74  	post := &model.Post{
    75  		ChannelId: th.BasicChannel.Id,
    76  		UserId:    th.BasicUser.Id,
    77  		Type:      model.POST_SYSTEM_GENERIC,
    78  	}
    79  
    80  	resp := &model.CommandResponse{
    81  		Text: "some message",
    82  	}
    83  
    84  	skipSlackParsing := false
    85  	_, err := th.App.CreateCommandPost(post, th.BasicTeam.Id, resp, skipSlackParsing)
    86  	require.NotNil(t, err)
    87  	require.Equal(t, err.Id, "api.context.invalid_param.app_error")
    88  }
    89  
    90  func TestExecuteCommand(t *testing.T) {
    91  	th := setup(t).initBasic()
    92  	defer th.tearDown()
    93  
    94  	t.Run("valid tests with different whitespace characters", func(t *testing.T) {
    95  		TestCases := map[string]string{
    96  			"/code happy path":             "    happy path",
    97  			"/code\nnewline path":          "    newline path",
    98  			"/code\n/nDouble newline path": "    /nDouble newline path",
    99  			"/code  double space":          "     double space",
   100  			"/code\ttab":                   "    tab",
   101  		}
   102  
   103  		for TestCase, result := range TestCases {
   104  			args := &model.CommandArgs{
   105  				Command:   TestCase,
   106  				TeamId:    th.BasicTeam.Id,
   107  				ChannelId: th.BasicChannel.Id,
   108  				UserId:    th.BasicUser.Id,
   109  				T:         func(s string, args ...interface{}) string { return s },
   110  			}
   111  			resp, err := th.App.ExecuteCommand(args)
   112  			require.Nil(t, err)
   113  			require.NotNil(t, resp)
   114  
   115  			assert.Equal(t, resp.Text, result)
   116  		}
   117  	})
   118  
   119  	t.Run("missing slash character", func(t *testing.T) {
   120  		argsMissingSlashCharacter := &model.CommandArgs{
   121  			Command: "missing leading slash character",
   122  			T:       func(s string, args ...interface{}) string { return s },
   123  		}
   124  		_, err := th.App.ExecuteCommand(argsMissingSlashCharacter)
   125  		require.Equal(t, "api.command.execute_command.format.app_error", err.Id)
   126  	})
   127  
   128  	t.Run("empty", func(t *testing.T) {
   129  		argsMissingSlashCharacter := &model.CommandArgs{
   130  			Command: "",
   131  			T:       func(s string, args ...interface{}) string { return s },
   132  		}
   133  		_, err := th.App.ExecuteCommand(argsMissingSlashCharacter)
   134  		require.Equal(t, "api.command.execute_command.format.app_error", err.Id)
   135  	})
   136  }
   137  
   138  func TestHandleCommandResponsePost(t *testing.T) {
   139  	th := setup(t).initBasic()
   140  	defer th.tearDown()
   141  
   142  	command := &model.Command{}
   143  	args := &model.CommandArgs{
   144  		ChannelId: th.BasicChannel.Id,
   145  		TeamId:    th.BasicTeam.Id,
   146  		UserId:    th.BasicUser.Id,
   147  		RootId:    "",
   148  		ParentId:  "",
   149  	}
   150  
   151  	resp := &model.CommandResponse{
   152  		Type:         model.POST_DEFAULT,
   153  		ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL,
   154  		Props:        model.StringInterface{"some_key": "some value"},
   155  		Text:         "some message",
   156  	}
   157  
   158  	builtIn := true
   159  
   160  	post, err := th.App.HandleCommandResponsePost(command, args, resp, builtIn)
   161  	assert.Nil(t, err)
   162  	assert.Equal(t, args.ChannelId, post.ChannelId)
   163  	assert.Equal(t, args.RootId, post.RootId)
   164  	assert.Equal(t, args.ParentId, post.ParentId)
   165  	assert.Equal(t, args.UserId, post.UserId)
   166  	assert.Equal(t, resp.Type, post.Type)
   167  	assert.Equal(t, resp.Props, post.GetProps())
   168  	assert.Equal(t, resp.Text, post.Message)
   169  	assert.Nil(t, post.GetProp("override_icon_url"))
   170  	assert.Nil(t, post.GetProp("override_username"))
   171  	assert.Nil(t, post.GetProp("from_webhook"))
   172  
   173  	// Command is not built in, so it is a bot command.
   174  	builtIn = false
   175  	post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn)
   176  	assert.Equal(t, "true", post.GetProp("from_webhook"))
   177  
   178  	builtIn = true
   179  
   180  	// Channel id is specified by response, it should override the command args value.
   181  	channel := th.CreateChannel(th.BasicTeam)
   182  	resp.ChannelId = channel.Id
   183  	th.addUserToChannel(th.BasicUser, channel)
   184  
   185  	post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn)
   186  	assert.Nil(t, err)
   187  	assert.Equal(t, resp.ChannelId, post.ChannelId)
   188  	assert.NotEqual(t, args.ChannelId, post.ChannelId)
   189  
   190  	// Override username config is turned off. No override should occur.
   191  	*th.App.Config().ServiceSettings.EnablePostUsernameOverride = false
   192  	resp.ChannelId = ""
   193  	command.Username = "Command username"
   194  	resp.Username = "Response username"
   195  
   196  	post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn)
   197  	assert.Nil(t, err)
   198  	assert.Nil(t, post.GetProp("override_username"))
   199  
   200  	*th.App.Config().ServiceSettings.EnablePostUsernameOverride = true
   201  
   202  	// Override username config is turned on. Override username through command property.
   203  	post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn)
   204  	assert.Nil(t, err)
   205  	assert.Equal(t, command.Username, post.GetProp("override_username"))
   206  	assert.Equal(t, "true", post.GetProp("from_webhook"))
   207  
   208  	command.Username = ""
   209  
   210  	// Override username through response property.
   211  	post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn)
   212  	assert.Nil(t, err)
   213  	assert.Equal(t, resp.Username, post.GetProp("override_username"))
   214  	assert.Equal(t, "true", post.GetProp("from_webhook"))
   215  
   216  	*th.App.Config().ServiceSettings.EnablePostUsernameOverride = false
   217  
   218  	// Override icon url config is turned off. No override should occur.
   219  	*th.App.Config().ServiceSettings.EnablePostIconOverride = false
   220  	command.IconURL = "Command icon url"
   221  	resp.IconURL = "Response icon url"
   222  
   223  	post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn)
   224  	assert.Nil(t, err)
   225  	assert.Nil(t, post.GetProp("override_icon_url"))
   226  
   227  	*th.App.Config().ServiceSettings.EnablePostIconOverride = true
   228  
   229  	// Override icon url config is turned on. Override icon url through command property.
   230  	post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn)
   231  	assert.Nil(t, err)
   232  	assert.Equal(t, command.IconURL, post.GetProp("override_icon_url"))
   233  	assert.Equal(t, "true", post.GetProp("from_webhook"))
   234  
   235  	command.IconURL = ""
   236  
   237  	// Override icon url through response property.
   238  	post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn)
   239  	assert.Nil(t, err)
   240  	assert.Equal(t, resp.IconURL, post.GetProp("override_icon_url"))
   241  	assert.Equal(t, "true", post.GetProp("from_webhook"))
   242  
   243  	// Test Slack text conversion.
   244  	resp.Text = "<!channel>"
   245  
   246  	post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn)
   247  	assert.Nil(t, err)
   248  	assert.Equal(t, "@channel", post.Message)
   249  	assert.Equal(t, "true", post.GetProp("from_webhook"))
   250  
   251  	// Test Slack attachments text conversion.
   252  	resp.Attachments = []*model.SlackAttachment{
   253  		{
   254  			Text: "<!here>",
   255  		},
   256  	}
   257  
   258  	post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn)
   259  	assert.Nil(t, err)
   260  	assert.Equal(t, "@channel", post.Message)
   261  	if assert.Len(t, post.Attachments(), 1) {
   262  		assert.Equal(t, "@here", post.Attachments()[0].Text)
   263  	}
   264  	assert.Equal(t, "true", post.GetProp("from_webhook"))
   265  
   266  	channel = th.createPrivateChannel(th.BasicTeam)
   267  	resp.ChannelId = channel.Id
   268  	args.UserId = th.BasicUser2.Id
   269  	post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn)
   270  
   271  	require.NotNil(t, err)
   272  	require.Equal(t, err.Id, "api.command.command_post.forbidden.app_error")
   273  
   274  	// Test that /code text is not converted with the Slack text conversion.
   275  	command.Trigger = "code"
   276  	resp.ChannelId = ""
   277  	resp.Text = "<test.com|test website>"
   278  	resp.Attachments = []*model.SlackAttachment{
   279  		{
   280  			Text: "<!here>",
   281  		},
   282  	}
   283  
   284  	// set and unset SkipSlackParsing here seems the nicest way as no separate response objects are created for every testcase.
   285  	resp.SkipSlackParsing = true
   286  	post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn)
   287  	resp.SkipSlackParsing = false
   288  
   289  	assert.Nil(t, err)
   290  	assert.Equal(t, resp.Text, post.Message, "/code text should not be converted to Slack links")
   291  	assert.Equal(t, "<!here>", resp.Attachments[0].Text)
   292  }
   293  
   294  func TestHandleCommandResponse(t *testing.T) {
   295  	th := setup(t).initBasic()
   296  	defer th.tearDown()
   297  
   298  	command := &model.Command{}
   299  
   300  	args := &model.CommandArgs{
   301  		Command:   "/invite username",
   302  		UserId:    th.BasicUser.Id,
   303  		ChannelId: th.BasicChannel.Id,
   304  	}
   305  
   306  	resp := &model.CommandResponse{
   307  		Text: "message 1",
   308  		Type: model.POST_SYSTEM_GENERIC,
   309  	}
   310  
   311  	builtIn := true
   312  
   313  	_, err := th.App.HandleCommandResponse(command, args, resp, builtIn)
   314  	require.NotNil(t, err)
   315  	require.Equal(t, err.Id, "api.command.execute_command.create_post_failed.app_error")
   316  
   317  	resp = &model.CommandResponse{
   318  		Text: "message 1",
   319  	}
   320  
   321  	_, err = th.App.HandleCommandResponse(command, args, resp, builtIn)
   322  	assert.Nil(t, err)
   323  
   324  	resp = &model.CommandResponse{
   325  		Text: "message 1",
   326  		ExtraResponses: []*model.CommandResponse{
   327  			{
   328  				Text: "message 2",
   329  			},
   330  			{
   331  				Type: model.POST_SYSTEM_GENERIC,
   332  				Text: "message 3",
   333  			},
   334  		},
   335  	}
   336  
   337  	_, err = th.App.HandleCommandResponse(command, args, resp, builtIn)
   338  	require.NotNil(t, err)
   339  	require.Equal(t, err.Id, "api.command.execute_command.create_post_failed.app_error")
   340  
   341  	resp = &model.CommandResponse{
   342  		ExtraResponses: []*model.CommandResponse{
   343  			{},
   344  			{},
   345  		},
   346  	}
   347  
   348  	_, err = th.App.HandleCommandResponse(command, args, resp, builtIn)
   349  	assert.Nil(t, err)
   350  }
   351  
   352  func TestDoCommandRequest(t *testing.T) {
   353  	th := setup(t)
   354  	defer th.tearDown()
   355  
   356  	th.App.UpdateConfig(func(cfg *model.Config) {
   357  		cfg.ServiceSettings.AllowedUntrustedInternalConnections = model.NewString("127.0.0.1")
   358  		cfg.ServiceSettings.EnableCommands = model.NewBool(true)
   359  	})
   360  
   361  	t.Run("with a valid text response", func(t *testing.T) {
   362  		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   363  			io.Copy(w, strings.NewReader("Hello, World!"))
   364  		}))
   365  		defer server.Close()
   366  
   367  		_, resp, err := th.App.DoCommandRequest(&model.Command{URL: server.URL}, url.Values{})
   368  		require.Nil(t, err)
   369  
   370  		assert.NotNil(t, resp)
   371  		assert.Equal(t, "Hello, World!", resp.Text)
   372  	})
   373  
   374  	t.Run("with a valid json response", func(t *testing.T) {
   375  		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   376  			w.Header().Add("Content-Type", "application/json")
   377  
   378  			io.Copy(w, strings.NewReader(`{"text": "Hello, World!"}`))
   379  		}))
   380  		defer server.Close()
   381  
   382  		_, resp, err := th.App.DoCommandRequest(&model.Command{URL: server.URL}, url.Values{})
   383  		require.Nil(t, err)
   384  
   385  		assert.NotNil(t, resp)
   386  		assert.Equal(t, "Hello, World!", resp.Text)
   387  	})
   388  
   389  	t.Run("with a large text response", func(t *testing.T) {
   390  		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   391  			io.Copy(w, InfiniteReader{})
   392  		}))
   393  		defer server.Close()
   394  
   395  		// Since we limit the length of the response, no error will be returned and resp.Text will be a finite string
   396  
   397  		_, resp, err := th.App.DoCommandRequest(&model.Command{URL: server.URL}, url.Values{})
   398  		require.Nil(t, err)
   399  		require.NotNil(t, resp)
   400  	})
   401  
   402  	t.Run("with a large, valid json response", func(t *testing.T) {
   403  		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   404  			w.Header().Add("Content-Type", "application/json")
   405  
   406  			io.Copy(w, io.MultiReader(strings.NewReader(`{"text": "`), InfiniteReader{}, strings.NewReader(`"}`)))
   407  		}))
   408  		defer server.Close()
   409  
   410  		_, _, err := th.App.DoCommandRequest(&model.Command{URL: server.URL}, url.Values{})
   411  		require.NotNil(t, err)
   412  		require.Equal(t, "api.command.execute_command.failed.app_error", err.Id)
   413  	})
   414  
   415  	t.Run("with a large, invalid json response", func(t *testing.T) {
   416  		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   417  			w.Header().Add("Content-Type", "application/json")
   418  
   419  			io.Copy(w, InfiniteReader{})
   420  		}))
   421  		defer server.Close()
   422  
   423  		_, _, err := th.App.DoCommandRequest(&model.Command{URL: server.URL}, url.Values{})
   424  		require.NotNil(t, err)
   425  		require.Equal(t, "api.command.execute_command.failed.app_error", err.Id)
   426  	})
   427  
   428  	t.Run("with a slow response", func(t *testing.T) {
   429  		done := make(chan bool)
   430  		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   431  			<-done
   432  			io.Copy(w, strings.NewReader(`{"text": "Hello, World!"}`))
   433  		}))
   434  		defer server.Close()
   435  
   436  		th.App.HTTPService().(*httpservice.HTTPServiceImpl).RequestTimeout = 100 * time.Millisecond
   437  		defer func() {
   438  			th.App.HTTPService().(*httpservice.HTTPServiceImpl).RequestTimeout = httpservice.RequestTimeout
   439  		}()
   440  
   441  		_, _, err := th.App.DoCommandRequest(&model.Command{URL: server.URL}, url.Values{})
   442  		require.NotNil(t, err)
   443  		require.Equal(t, "api.command.execute_command.failed.app_error", err.Id)
   444  		close(done)
   445  	})
   446  }
   447  
   448  func TestMentionsToTeamMembers(t *testing.T) {
   449  	th := setup(t).initBasic()
   450  	defer th.tearDown()
   451  
   452  	otherTeam := th.createTeam()
   453  	otherUser := th.createUser()
   454  	th.linkUserToTeam(otherUser, otherTeam)
   455  
   456  	fixture := []struct {
   457  		message     string
   458  		inTeam      string
   459  		expectedMap model.UserMentionMap
   460  	}{
   461  		{
   462  			"",
   463  			th.BasicTeam.Id,
   464  			model.UserMentionMap{},
   465  		},
   466  		{
   467  			"/trigger",
   468  			th.BasicTeam.Id,
   469  			model.UserMentionMap{},
   470  		},
   471  		{
   472  			"/trigger 0 mentions",
   473  			th.BasicTeam.Id,
   474  			model.UserMentionMap{},
   475  		},
   476  		{
   477  			fmt.Sprintf("/trigger 1 valid user @%s", th.BasicUser.Username),
   478  			th.BasicTeam.Id,
   479  			model.UserMentionMap{th.BasicUser.Username: th.BasicUser.Id},
   480  		},
   481  		{
   482  			fmt.Sprintf("/trigger 2 valid users @%s @%s",
   483  				th.BasicUser.Username, th.BasicUser2.Username,
   484  			),
   485  			th.BasicTeam.Id,
   486  			model.UserMentionMap{
   487  				th.BasicUser.Username:  th.BasicUser.Id,
   488  				th.BasicUser2.Username: th.BasicUser2.Id,
   489  			},
   490  		},
   491  		{
   492  			fmt.Sprintf("/trigger 1 user from another team @%s", otherUser.Username),
   493  			th.BasicTeam.Id,
   494  			model.UserMentionMap{},
   495  		},
   496  		{
   497  			fmt.Sprintf("/trigger 2 valid users + 1 from another team @%s @%s @%s",
   498  				th.BasicUser.Username, th.BasicUser2.Username, otherUser.Username,
   499  			),
   500  			th.BasicTeam.Id,
   501  			model.UserMentionMap{
   502  				th.BasicUser.Username:  th.BasicUser.Id,
   503  				th.BasicUser2.Username: th.BasicUser2.Id,
   504  			},
   505  		},
   506  		{
   507  			fmt.Sprintf("/trigger a valid channel ~%s", th.BasicChannel.Name),
   508  			th.BasicTeam.Id,
   509  			model.UserMentionMap{},
   510  		},
   511  		{
   512  			fmt.Sprintf("/trigger channel and mentions ~%s @%s",
   513  				th.BasicChannel.Name, th.BasicUser.Username),
   514  			th.BasicTeam.Id,
   515  			model.UserMentionMap{th.BasicUser.Username: th.BasicUser.Id},
   516  		},
   517  		{
   518  			fmt.Sprintf("/trigger repeated users @%s @%s @%s",
   519  				th.BasicUser.Username, th.BasicUser2.Username, th.BasicUser.Username),
   520  			th.BasicTeam.Id,
   521  			model.UserMentionMap{
   522  				th.BasicUser.Username:  th.BasicUser.Id,
   523  				th.BasicUser2.Username: th.BasicUser2.Id,
   524  			},
   525  		},
   526  	}
   527  
   528  	for _, data := range fixture {
   529  		actualMap := th.App.MentionsToTeamMembers(data.message, data.inTeam)
   530  		require.Equal(t, actualMap, data.expectedMap)
   531  	}
   532  }
   533  
   534  func TestMentionsToPublicChannels(t *testing.T) {
   535  	th := setup(t).initBasic()
   536  	defer th.tearDown()
   537  
   538  	otherPublicChannel := th.CreateChannel(th.BasicTeam)
   539  	privateChannel := th.createPrivateChannel(th.BasicTeam)
   540  
   541  	fixture := []struct {
   542  		message     string
   543  		inTeam      string
   544  		expectedMap model.ChannelMentionMap
   545  	}{
   546  		{
   547  			"",
   548  			th.BasicTeam.Id,
   549  			model.ChannelMentionMap{},
   550  		},
   551  		{
   552  			"/trigger",
   553  			th.BasicTeam.Id,
   554  			model.ChannelMentionMap{},
   555  		},
   556  		{
   557  			"/trigger 0 mentions",
   558  			th.BasicTeam.Id,
   559  			model.ChannelMentionMap{},
   560  		},
   561  		{
   562  			fmt.Sprintf("/trigger 1 public channel ~%s", th.BasicChannel.Name),
   563  			th.BasicTeam.Id,
   564  			model.ChannelMentionMap{th.BasicChannel.Name: th.BasicChannel.Id},
   565  		},
   566  		{
   567  			fmt.Sprintf("/trigger 2 public channels ~%s ~%s",
   568  				th.BasicChannel.Name, otherPublicChannel.Name,
   569  			),
   570  			th.BasicTeam.Id,
   571  			model.ChannelMentionMap{
   572  				th.BasicChannel.Name:    th.BasicChannel.Id,
   573  				otherPublicChannel.Name: otherPublicChannel.Id,
   574  			},
   575  		},
   576  		{
   577  			fmt.Sprintf("/trigger 1 private channel ~%s", privateChannel.Name),
   578  			th.BasicTeam.Id,
   579  			model.ChannelMentionMap{},
   580  		},
   581  		{
   582  			fmt.Sprintf("/trigger 2 public channel + 1 private ~%s ~%s ~%s",
   583  				th.BasicChannel.Name, otherPublicChannel.Name, privateChannel.Name,
   584  			),
   585  			th.BasicTeam.Id,
   586  			model.ChannelMentionMap{
   587  				th.BasicChannel.Name:    th.BasicChannel.Id,
   588  				otherPublicChannel.Name: otherPublicChannel.Id,
   589  			},
   590  		},
   591  		{
   592  			fmt.Sprintf("/trigger a valid user @%s", th.BasicUser.Username),
   593  			th.BasicTeam.Id,
   594  			model.ChannelMentionMap{},
   595  		},
   596  		{
   597  			fmt.Sprintf("/trigger channel and mentions ~%s @%s",
   598  				th.BasicChannel.Name, th.BasicUser.Username),
   599  			th.BasicTeam.Id,
   600  			model.ChannelMentionMap{th.BasicChannel.Name: th.BasicChannel.Id},
   601  		},
   602  		{
   603  			fmt.Sprintf("/trigger repeated channels ~%s ~%s ~%s",
   604  				th.BasicChannel.Name, otherPublicChannel.Name, th.BasicChannel.Name),
   605  			th.BasicTeam.Id,
   606  			model.ChannelMentionMap{
   607  				th.BasicChannel.Name:    th.BasicChannel.Id,
   608  				otherPublicChannel.Name: otherPublicChannel.Id,
   609  			},
   610  		},
   611  	}
   612  
   613  	for _, data := range fixture {
   614  		actualMap := th.App.MentionsToPublicChannels(data.message, data.inTeam)
   615  		require.Equal(t, actualMap, data.expectedMap)
   616  	}
   617  }