github.com/hahmadia/mattermost-server@v5.11.1+incompatible/app/command_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  	"io"
     8  	"net/http"
     9  	"net/http/httptest"
    10  	"net/url"
    11  	"strings"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/stretchr/testify/assert"
    16  	"github.com/stretchr/testify/require"
    17  
    18  	"github.com/mattermost/mattermost-server/model"
    19  	"github.com/mattermost/mattermost-server/services/httpservice"
    20  )
    21  
    22  func TestMoveCommand(t *testing.T) {
    23  	th := Setup(t).InitBasic()
    24  	defer th.TearDown()
    25  
    26  	sourceTeam := th.CreateTeam()
    27  	targetTeam := th.CreateTeam()
    28  
    29  	command := &model.Command{}
    30  	command.CreatorId = model.NewId()
    31  	command.Method = model.COMMAND_METHOD_POST
    32  	command.TeamId = sourceTeam.Id
    33  	command.URL = "http://nowhere.com/"
    34  	command.Trigger = "trigger1"
    35  
    36  	command, err := th.App.CreateCommand(command)
    37  	assert.Nil(t, err)
    38  
    39  	defer func() {
    40  		th.App.PermanentDeleteTeam(sourceTeam)
    41  		th.App.PermanentDeleteTeam(targetTeam)
    42  	}()
    43  
    44  	// Move a command and check the team is updated.
    45  	assert.Nil(t, th.App.MoveCommand(targetTeam, command))
    46  	retrievedCommand, err := th.App.GetCommand(command.Id)
    47  	assert.Nil(t, err)
    48  	assert.EqualValues(t, targetTeam.Id, retrievedCommand.TeamId)
    49  
    50  	// Move it to the team it's already in. Nothing should change.
    51  	assert.Nil(t, th.App.MoveCommand(targetTeam, command))
    52  	retrievedCommand, err = th.App.GetCommand(command.Id)
    53  	assert.Nil(t, err)
    54  	assert.EqualValues(t, targetTeam.Id, retrievedCommand.TeamId)
    55  }
    56  
    57  func TestCreateCommandPost(t *testing.T) {
    58  	th := Setup(t).InitBasic()
    59  	defer th.TearDown()
    60  
    61  	post := &model.Post{
    62  		ChannelId: th.BasicChannel.Id,
    63  		UserId:    th.BasicUser.Id,
    64  		Type:      model.POST_SYSTEM_GENERIC,
    65  	}
    66  
    67  	resp := &model.CommandResponse{
    68  		Text: "some message",
    69  	}
    70  
    71  	_, err := th.App.CreateCommandPost(post, th.BasicTeam.Id, resp)
    72  	if err == nil || err.Id != "api.context.invalid_param.app_error" {
    73  		t.Fatal("should have failed - bad post type")
    74  	}
    75  }
    76  
    77  func TestHandleCommandResponsePost(t *testing.T) {
    78  	th := Setup(t).InitBasic()
    79  	defer th.TearDown()
    80  
    81  	command := &model.Command{}
    82  	args := &model.CommandArgs{
    83  		ChannelId: th.BasicChannel.Id,
    84  		TeamId:    th.BasicTeam.Id,
    85  		UserId:    th.BasicUser.Id,
    86  		RootId:    "",
    87  		ParentId:  "",
    88  	}
    89  
    90  	resp := &model.CommandResponse{
    91  		Type:         model.POST_DEFAULT,
    92  		ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL,
    93  		Props:        model.StringInterface{"some_key": "some value"},
    94  		Text:         "some message",
    95  	}
    96  
    97  	builtIn := true
    98  
    99  	post, err := th.App.HandleCommandResponsePost(command, args, resp, builtIn)
   100  	assert.Nil(t, err)
   101  	assert.Equal(t, args.ChannelId, post.ChannelId)
   102  	assert.Equal(t, args.RootId, post.RootId)
   103  	assert.Equal(t, args.ParentId, post.ParentId)
   104  	assert.Equal(t, args.UserId, post.UserId)
   105  	assert.Equal(t, resp.Type, post.Type)
   106  	assert.Equal(t, resp.Props, post.Props)
   107  	assert.Equal(t, resp.Text, post.Message)
   108  	assert.Nil(t, post.Props["override_icon_url"])
   109  	assert.Nil(t, post.Props["override_username"])
   110  	assert.Nil(t, post.Props["from_webhook"])
   111  
   112  	// Command is not built in, so it is a bot command.
   113  	builtIn = false
   114  	post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn)
   115  	assert.Equal(t, "true", post.Props["from_webhook"])
   116  
   117  	builtIn = true
   118  
   119  	// Channel id is specified by response, it should override the command args value.
   120  	channel := th.CreateChannel(th.BasicTeam)
   121  	resp.ChannelId = channel.Id
   122  	th.AddUserToChannel(th.BasicUser, channel)
   123  
   124  	post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn)
   125  	assert.Nil(t, err)
   126  	assert.Equal(t, resp.ChannelId, post.ChannelId)
   127  	assert.NotEqual(t, args.ChannelId, post.ChannelId)
   128  
   129  	// Override username config is turned off. No override should occur.
   130  	*th.App.Config().ServiceSettings.EnablePostUsernameOverride = false
   131  	resp.ChannelId = ""
   132  	command.Username = "Command username"
   133  	resp.Username = "Response username"
   134  
   135  	post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn)
   136  	assert.Nil(t, err)
   137  	assert.Nil(t, post.Props["override_username"])
   138  
   139  	*th.App.Config().ServiceSettings.EnablePostUsernameOverride = true
   140  
   141  	// Override username config is turned on. Override username through command property.
   142  	post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn)
   143  	assert.Nil(t, err)
   144  	assert.Equal(t, command.Username, post.Props["override_username"])
   145  	assert.Equal(t, "true", post.Props["from_webhook"])
   146  
   147  	command.Username = ""
   148  
   149  	// Override username through response property.
   150  	post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn)
   151  	assert.Nil(t, err)
   152  	assert.Equal(t, resp.Username, post.Props["override_username"])
   153  	assert.Equal(t, "true", post.Props["from_webhook"])
   154  
   155  	*th.App.Config().ServiceSettings.EnablePostUsernameOverride = false
   156  
   157  	// Override icon url config is turned off. No override should occur.
   158  	*th.App.Config().ServiceSettings.EnablePostIconOverride = false
   159  	command.IconURL = "Command icon url"
   160  	resp.IconURL = "Response icon url"
   161  
   162  	post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn)
   163  	assert.Nil(t, err)
   164  	assert.Nil(t, post.Props["override_icon_url"])
   165  
   166  	*th.App.Config().ServiceSettings.EnablePostIconOverride = true
   167  
   168  	// Override icon url config is turned on. Override icon url through command property.
   169  	post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn)
   170  	assert.Nil(t, err)
   171  	assert.Equal(t, command.IconURL, post.Props["override_icon_url"])
   172  	assert.Equal(t, "true", post.Props["from_webhook"])
   173  
   174  	command.IconURL = ""
   175  
   176  	// Override icon url through response property.
   177  	post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn)
   178  	assert.Nil(t, err)
   179  	assert.Equal(t, resp.IconURL, post.Props["override_icon_url"])
   180  	assert.Equal(t, "true", post.Props["from_webhook"])
   181  
   182  	// Test Slack text conversion.
   183  	resp.Text = "<!channel>"
   184  
   185  	post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn)
   186  	assert.Nil(t, err)
   187  	assert.Equal(t, "@channel", post.Message)
   188  
   189  	// Test Slack attachments text conversion.
   190  	resp.Attachments = []*model.SlackAttachment{
   191  		&model.SlackAttachment{
   192  			Text: "<!here>",
   193  		},
   194  	}
   195  
   196  	post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn)
   197  	assert.Nil(t, err)
   198  	assert.Equal(t, "@here", resp.Attachments[0].Text)
   199  
   200  	channel = th.CreatePrivateChannel(th.BasicTeam)
   201  	resp.ChannelId = channel.Id
   202  	args.UserId = th.BasicUser2.Id
   203  	post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn)
   204  
   205  	if err == nil || err.Id != "api.command.command_post.forbidden.app_error" {
   206  		t.Fatal("should have failed - forbidden channel post")
   207  	}
   208  }
   209  func TestHandleCommandResponse(t *testing.T) {
   210  	th := Setup(t).InitBasic()
   211  	defer th.TearDown()
   212  
   213  	command := &model.Command{}
   214  
   215  	args := &model.CommandArgs{
   216  		Command:   "/invite username",
   217  		UserId:    th.BasicUser.Id,
   218  		ChannelId: th.BasicChannel.Id,
   219  	}
   220  
   221  	resp := &model.CommandResponse{
   222  		Text: "message 1",
   223  		Type: model.POST_SYSTEM_GENERIC,
   224  	}
   225  
   226  	builtIn := true
   227  
   228  	_, err := th.App.HandleCommandResponse(command, args, resp, builtIn)
   229  	if err == nil || err.Id != "api.command.execute_command.create_post_failed.app_error" {
   230  		t.Fatal("should have failed - invalid post type")
   231  	}
   232  
   233  	resp = &model.CommandResponse{
   234  		Text: "message 1",
   235  	}
   236  
   237  	_, err = th.App.HandleCommandResponse(command, args, resp, builtIn)
   238  	assert.Nil(t, err)
   239  
   240  	resp = &model.CommandResponse{
   241  		Text: "message 1",
   242  		ExtraResponses: []*model.CommandResponse{
   243  			&model.CommandResponse{
   244  				Text: "message 2",
   245  			},
   246  			&model.CommandResponse{
   247  				Type: model.POST_SYSTEM_GENERIC,
   248  				Text: "message 3",
   249  			},
   250  		},
   251  	}
   252  
   253  	_, err = th.App.HandleCommandResponse(command, args, resp, builtIn)
   254  	if err == nil || err.Id != "api.command.execute_command.create_post_failed.app_error" {
   255  		t.Fatal("should have failed - invalid post type on extra response")
   256  	}
   257  
   258  	resp = &model.CommandResponse{
   259  		ExtraResponses: []*model.CommandResponse{
   260  			&model.CommandResponse{},
   261  			&model.CommandResponse{},
   262  		},
   263  	}
   264  
   265  	_, err = th.App.HandleCommandResponse(command, args, resp, builtIn)
   266  	assert.Nil(t, err)
   267  }
   268  
   269  func TestDoCommandRequest(t *testing.T) {
   270  	th := Setup(t).InitBasic()
   271  	defer th.TearDown()
   272  
   273  	th.App.UpdateConfig(func(cfg *model.Config) {
   274  		cfg.ServiceSettings.AllowedUntrustedInternalConnections = model.NewString("127.0.0.1")
   275  		cfg.ServiceSettings.EnableCommands = model.NewBool(true)
   276  	})
   277  
   278  	t.Run("with a valid text response", func(t *testing.T) {
   279  		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   280  			io.Copy(w, strings.NewReader("Hello, World!"))
   281  		}))
   282  		defer server.Close()
   283  
   284  		_, resp, err := th.App.doCommandRequest(&model.Command{URL: server.URL}, url.Values{})
   285  		require.Nil(t, err)
   286  
   287  		assert.NotNil(t, resp)
   288  		assert.Equal(t, "Hello, World!", resp.Text)
   289  	})
   290  
   291  	t.Run("with a valid json response", func(t *testing.T) {
   292  		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   293  			w.Header().Add("Content-Type", "application/json")
   294  
   295  			io.Copy(w, strings.NewReader(`{"text": "Hello, World!"}`))
   296  		}))
   297  		defer server.Close()
   298  
   299  		_, resp, err := th.App.doCommandRequest(&model.Command{URL: server.URL}, url.Values{})
   300  		require.Nil(t, err)
   301  
   302  		assert.NotNil(t, resp)
   303  		assert.Equal(t, "Hello, World!", resp.Text)
   304  	})
   305  
   306  	t.Run("with a large text response", func(t *testing.T) {
   307  		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   308  			io.Copy(w, InfiniteReader{})
   309  		}))
   310  		defer server.Close()
   311  
   312  		// Since we limit the length of the response, no error will be returned and resp.Text will be a finite string
   313  
   314  		_, resp, err := th.App.doCommandRequest(&model.Command{URL: server.URL}, url.Values{})
   315  		require.Nil(t, err)
   316  		require.NotNil(t, resp)
   317  	})
   318  
   319  	t.Run("with a large, valid json response", func(t *testing.T) {
   320  		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   321  			w.Header().Add("Content-Type", "application/json")
   322  
   323  			io.Copy(w, io.MultiReader(strings.NewReader(`{"text": "`), InfiniteReader{}, strings.NewReader(`"}`)))
   324  		}))
   325  		defer server.Close()
   326  
   327  		_, _, err := th.App.doCommandRequest(&model.Command{URL: server.URL}, url.Values{})
   328  		require.NotNil(t, err)
   329  		require.Equal(t, "api.command.execute_command.failed.app_error", err.Id)
   330  	})
   331  
   332  	t.Run("with a large, invalid json response", func(t *testing.T) {
   333  		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   334  			w.Header().Add("Content-Type", "application/json")
   335  
   336  			io.Copy(w, InfiniteReader{})
   337  		}))
   338  		defer server.Close()
   339  
   340  		_, _, err := th.App.doCommandRequest(&model.Command{URL: server.URL}, url.Values{})
   341  		require.NotNil(t, err)
   342  		require.Equal(t, "api.command.execute_command.failed.app_error", err.Id)
   343  	})
   344  
   345  	t.Run("with a slow response", func(t *testing.T) {
   346  		done := make(chan bool)
   347  		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   348  			<-done
   349  			io.Copy(w, strings.NewReader(`{"text": "Hello, World!"}`))
   350  		}))
   351  		defer server.Close()
   352  
   353  		th.App.HTTPService.(*httpservice.HTTPServiceImpl).RequestTimeout = 100 * time.Millisecond
   354  		defer func() {
   355  			th.App.HTTPService.(*httpservice.HTTPServiceImpl).RequestTimeout = httpservice.RequestTimeout
   356  		}()
   357  
   358  		_, _, err := th.App.doCommandRequest(&model.Command{URL: server.URL}, url.Values{})
   359  		require.NotNil(t, err)
   360  		require.Equal(t, "api.command.execute_command.failed.app_error", err.Id)
   361  		close(done)
   362  	})
   363  }