github.com/rr250/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 }