github.com/jlevesy/mattermost-server@v5.3.2-0.20181003190404-7468f35cb0c8+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 "net/http" 15 "net/http/httptest" 16 "time" 17 ) 18 19 func TestCreateIncomingWebhookForChannel(t *testing.T) { 20 th := Setup().InitBasic() 21 defer th.TearDown() 22 23 type TestCase struct { 24 EnableIncomingHooks bool 25 EnablePostUsernameOverride bool 26 EnablePostIconOverride bool 27 IncomingWebhook model.IncomingWebhook 28 29 ExpectedError bool 30 ExpectedIncomingWebhook *model.IncomingWebhook 31 } 32 33 for name, tc := range map[string]TestCase{ 34 "webhooks not enabled": { 35 EnableIncomingHooks: false, 36 EnablePostUsernameOverride: false, 37 EnablePostIconOverride: false, 38 IncomingWebhook: model.IncomingWebhook{ 39 DisplayName: "title", 40 Description: "description", 41 ChannelId: th.BasicChannel.Id, 42 }, 43 44 ExpectedError: true, 45 ExpectedIncomingWebhook: nil, 46 }, 47 "valid: username and post icon url ignored, since override not enabled": { 48 EnableIncomingHooks: true, 49 EnablePostUsernameOverride: false, 50 EnablePostIconOverride: false, 51 IncomingWebhook: model.IncomingWebhook{ 52 DisplayName: "title", 53 Description: "description", 54 ChannelId: th.BasicChannel.Id, 55 Username: ":invalid and ignored:", 56 IconURL: "ignored", 57 }, 58 59 ExpectedError: false, 60 ExpectedIncomingWebhook: &model.IncomingWebhook{ 61 DisplayName: "title", 62 Description: "description", 63 ChannelId: th.BasicChannel.Id, 64 }, 65 }, 66 "invalid username, override enabled": { 67 EnableIncomingHooks: true, 68 EnablePostUsernameOverride: true, 69 EnablePostIconOverride: false, 70 IncomingWebhook: model.IncomingWebhook{ 71 DisplayName: "title", 72 Description: "description", 73 ChannelId: th.BasicChannel.Id, 74 Username: ":invalid:", 75 }, 76 77 ExpectedError: true, 78 ExpectedIncomingWebhook: nil, 79 }, 80 "valid, no username or post icon url provided": { 81 EnableIncomingHooks: true, 82 EnablePostUsernameOverride: true, 83 EnablePostIconOverride: true, 84 IncomingWebhook: model.IncomingWebhook{ 85 DisplayName: "title", 86 Description: "description", 87 ChannelId: th.BasicChannel.Id, 88 }, 89 90 ExpectedError: false, 91 ExpectedIncomingWebhook: &model.IncomingWebhook{ 92 DisplayName: "title", 93 Description: "description", 94 ChannelId: th.BasicChannel.Id, 95 }, 96 }, 97 "valid, with username and post icon": { 98 EnableIncomingHooks: true, 99 EnablePostUsernameOverride: true, 100 EnablePostIconOverride: true, 101 IncomingWebhook: model.IncomingWebhook{ 102 DisplayName: "title", 103 Description: "description", 104 ChannelId: th.BasicChannel.Id, 105 Username: "valid", 106 IconURL: "http://example.com/icon", 107 }, 108 109 ExpectedError: false, 110 ExpectedIncomingWebhook: &model.IncomingWebhook{ 111 DisplayName: "title", 112 Description: "description", 113 ChannelId: th.BasicChannel.Id, 114 Username: "valid", 115 IconURL: "http://example.com/icon", 116 }, 117 }, 118 } { 119 t.Run(name, func(t *testing.T) { 120 assert := assert.New(t) 121 122 th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = tc.EnableIncomingHooks }) 123 th.App.UpdateConfig(func(cfg *model.Config) { 124 cfg.ServiceSettings.EnablePostUsernameOverride = tc.EnablePostUsernameOverride 125 }) 126 th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnablePostIconOverride = tc.EnablePostIconOverride }) 127 128 createdHook, err := th.App.CreateIncomingWebhookForChannel(th.BasicUser.Id, th.BasicChannel, &tc.IncomingWebhook) 129 if tc.ExpectedError && err == nil { 130 t.Fatal("should have failed") 131 } else if !tc.ExpectedError && err != nil { 132 t.Fatalf("should not have failed: %v", err.Error()) 133 } 134 if createdHook != nil { 135 defer th.App.DeleteIncomingWebhook(createdHook.Id) 136 } 137 if tc.ExpectedIncomingWebhook == nil { 138 assert.Nil(createdHook, "expected nil webhook") 139 } else if assert.NotNil(createdHook, "expected non-nil webhook") { 140 assert.Equal(tc.ExpectedIncomingWebhook.DisplayName, createdHook.DisplayName) 141 assert.Equal(tc.ExpectedIncomingWebhook.Description, createdHook.Description) 142 assert.Equal(tc.ExpectedIncomingWebhook.ChannelId, createdHook.ChannelId) 143 assert.Equal(tc.ExpectedIncomingWebhook.Username, createdHook.Username) 144 assert.Equal(tc.ExpectedIncomingWebhook.IconURL, createdHook.IconURL) 145 } 146 }) 147 } 148 } 149 150 func TestUpdateIncomingWebhook(t *testing.T) { 151 th := Setup().InitBasic() 152 defer th.TearDown() 153 154 type TestCase struct { 155 EnableIncomingHooks bool 156 EnablePostUsernameOverride bool 157 EnablePostIconOverride bool 158 IncomingWebhook model.IncomingWebhook 159 160 ExpectedError bool 161 ExpectedIncomingWebhook *model.IncomingWebhook 162 } 163 164 for name, tc := range map[string]TestCase{ 165 "webhooks not enabled": { 166 EnableIncomingHooks: false, 167 EnablePostUsernameOverride: false, 168 EnablePostIconOverride: false, 169 IncomingWebhook: model.IncomingWebhook{ 170 DisplayName: "title", 171 Description: "description", 172 ChannelId: th.BasicChannel.Id, 173 }, 174 175 ExpectedError: true, 176 ExpectedIncomingWebhook: nil, 177 }, 178 "valid: username and post icon url ignored, since override not enabled": { 179 EnableIncomingHooks: true, 180 EnablePostUsernameOverride: false, 181 EnablePostIconOverride: false, 182 IncomingWebhook: model.IncomingWebhook{ 183 DisplayName: "title", 184 Description: "description", 185 ChannelId: th.BasicChannel.Id, 186 Username: ":invalid and ignored:", 187 IconURL: "ignored", 188 }, 189 190 ExpectedError: false, 191 ExpectedIncomingWebhook: &model.IncomingWebhook{ 192 DisplayName: "title", 193 Description: "description", 194 ChannelId: th.BasicChannel.Id, 195 }, 196 }, 197 "invalid username, override enabled": { 198 EnableIncomingHooks: true, 199 EnablePostUsernameOverride: true, 200 EnablePostIconOverride: false, 201 IncomingWebhook: model.IncomingWebhook{ 202 DisplayName: "title", 203 Description: "description", 204 ChannelId: th.BasicChannel.Id, 205 Username: ":invalid:", 206 }, 207 208 ExpectedError: true, 209 ExpectedIncomingWebhook: nil, 210 }, 211 "valid, no username or post icon url provided": { 212 EnableIncomingHooks: true, 213 EnablePostUsernameOverride: true, 214 EnablePostIconOverride: true, 215 IncomingWebhook: model.IncomingWebhook{ 216 DisplayName: "title", 217 Description: "description", 218 ChannelId: th.BasicChannel.Id, 219 }, 220 221 ExpectedError: false, 222 ExpectedIncomingWebhook: &model.IncomingWebhook{ 223 DisplayName: "title", 224 Description: "description", 225 ChannelId: th.BasicChannel.Id, 226 }, 227 }, 228 "valid, with username and post icon": { 229 EnableIncomingHooks: true, 230 EnablePostUsernameOverride: true, 231 EnablePostIconOverride: true, 232 IncomingWebhook: model.IncomingWebhook{ 233 DisplayName: "title", 234 Description: "description", 235 ChannelId: th.BasicChannel.Id, 236 Username: "valid", 237 IconURL: "http://example.com/icon", 238 }, 239 240 ExpectedError: false, 241 ExpectedIncomingWebhook: &model.IncomingWebhook{ 242 DisplayName: "title", 243 Description: "description", 244 ChannelId: th.BasicChannel.Id, 245 Username: "valid", 246 IconURL: "http://example.com/icon", 247 }, 248 }, 249 } { 250 t.Run(name, func(t *testing.T) { 251 assert := assert.New(t) 252 253 th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) 254 255 hook, err := th.App.CreateIncomingWebhookForChannel(th.BasicUser.Id, th.BasicChannel, &model.IncomingWebhook{ 256 ChannelId: th.BasicChannel.Id, 257 }) 258 if err != nil { 259 t.Fatal(err.Error()) 260 } 261 defer th.App.DeleteIncomingWebhook(hook.Id) 262 263 th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = tc.EnableIncomingHooks }) 264 th.App.UpdateConfig(func(cfg *model.Config) { 265 cfg.ServiceSettings.EnablePostUsernameOverride = tc.EnablePostUsernameOverride 266 }) 267 th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnablePostIconOverride = tc.EnablePostIconOverride }) 268 269 updatedHook, err := th.App.UpdateIncomingWebhook(hook, &tc.IncomingWebhook) 270 if tc.ExpectedError && err == nil { 271 t.Fatal("should have failed") 272 } else if !tc.ExpectedError && err != nil { 273 t.Fatalf("should not have failed: %v", err.Error()) 274 } 275 if tc.ExpectedIncomingWebhook == nil { 276 assert.Nil(updatedHook, "expected nil webhook") 277 } else if assert.NotNil(updatedHook, "expected non-nil webhook") { 278 assert.Equal(tc.ExpectedIncomingWebhook.DisplayName, updatedHook.DisplayName) 279 assert.Equal(tc.ExpectedIncomingWebhook.Description, updatedHook.Description) 280 assert.Equal(tc.ExpectedIncomingWebhook.ChannelId, updatedHook.ChannelId) 281 assert.Equal(tc.ExpectedIncomingWebhook.Username, updatedHook.Username) 282 assert.Equal(tc.ExpectedIncomingWebhook.IconURL, updatedHook.IconURL) 283 } 284 }) 285 } 286 } 287 288 func TestCreateWebhookPost(t *testing.T) { 289 th := Setup().InitBasic() 290 defer th.TearDown() 291 292 th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) 293 294 hook, err := th.App.CreateIncomingWebhookForChannel(th.BasicUser.Id, th.BasicChannel, &model.IncomingWebhook{ChannelId: th.BasicChannel.Id}) 295 if err != nil { 296 t.Fatal(err.Error()) 297 } 298 defer th.App.DeleteIncomingWebhook(hook.Id) 299 300 post, err := th.App.CreateWebhookPost(hook.UserId, th.BasicChannel, "foo", "user", "http://iconurl", model.StringInterface{ 301 "attachments": []*model.SlackAttachment{ 302 { 303 Text: "text", 304 }, 305 }, 306 "webhook_display_name": hook.DisplayName, 307 }, model.POST_SLACK_ATTACHMENT, "") 308 if err != nil { 309 t.Fatal(err.Error()) 310 } 311 312 for _, k := range []string{"from_webhook", "attachments", "webhook_display_name"} { 313 if _, ok := post.Props[k]; !ok { 314 t.Log("missing one props: " + k) 315 t.Fatal(k) 316 } 317 } 318 319 _, err = th.App.CreateWebhookPost(hook.UserId, th.BasicChannel, "foo", "user", "http://iconurl", nil, model.POST_SYSTEM_GENERIC, "") 320 if err == nil { 321 t.Fatal("should have failed - bad post type") 322 } 323 324 expectedText := "`<>|<>|`" 325 post, err = th.App.CreateWebhookPost(hook.UserId, th.BasicChannel, expectedText, "user", "http://iconurl", model.StringInterface{ 326 "attachments": []*model.SlackAttachment{ 327 { 328 Text: "text", 329 }, 330 }, 331 "webhook_display_name": hook.DisplayName, 332 }, model.POST_SLACK_ATTACHMENT, "") 333 if err != nil { 334 t.Fatal(err.Error()) 335 } 336 assert.Equal(t, expectedText, post.Message) 337 338 expectedText = "< | \n|\n>" 339 post, err = th.App.CreateWebhookPost(hook.UserId, th.BasicChannel, expectedText, "user", "http://iconurl", model.StringInterface{ 340 "attachments": []*model.SlackAttachment{ 341 { 342 Text: "text", 343 }, 344 }, 345 "webhook_display_name": hook.DisplayName, 346 }, model.POST_SLACK_ATTACHMENT, "") 347 if err != nil { 348 t.Fatal(err.Error()) 349 } 350 assert.Equal(t, expectedText, post.Message) 351 352 expectedText = `commit bc95839e4a430ace453e8b209a3723c000c1729a 353 Author: foo <foo@example.org> 354 Date: Thu Mar 1 19:46:54 2018 +0300 355 356 commit message 2 357 358 test | 1 + 359 1 file changed, 1 insertion(+) 360 361 commit 5df78b7139b543997838071cd912e375d8bd69b2 362 Author: foo <foo@example.org> 363 Date: Thu Mar 1 19:46:48 2018 +0300 364 365 commit message 1 366 367 test | 3 +++ 368 1 file changed, 3 insertions(+)` 369 post, err = th.App.CreateWebhookPost(hook.UserId, th.BasicChannel, expectedText, "user", "http://iconurl", model.StringInterface{ 370 "attachments": []*model.SlackAttachment{ 371 { 372 Text: "text", 373 }, 374 }, 375 "webhook_display_name": hook.DisplayName, 376 }, model.POST_SLACK_ATTACHMENT, "") 377 if err != nil { 378 t.Fatal(err.Error()) 379 } 380 assert.Equal(t, expectedText, post.Message) 381 } 382 383 func TestSplitWebhookPost(t *testing.T) { 384 type TestCase struct { 385 Post *model.Post 386 Expected []*model.Post 387 } 388 389 maxPostSize := 10000 390 391 for name, tc := range map[string]TestCase{ 392 "LongPost": { 393 Post: &model.Post{ 394 Message: strings.Repeat("本", maxPostSize*3/2), 395 }, 396 Expected: []*model.Post{ 397 { 398 Message: strings.Repeat("本", maxPostSize), 399 }, 400 { 401 Message: strings.Repeat("本", maxPostSize/2), 402 }, 403 }, 404 }, 405 "LongPostAndMultipleAttachments": { 406 Post: &model.Post{ 407 Message: strings.Repeat("本", maxPostSize*3/2), 408 Props: map[string]interface{}{ 409 "attachments": []*model.SlackAttachment{ 410 &model.SlackAttachment{ 411 Text: strings.Repeat("本", 1000), 412 }, 413 &model.SlackAttachment{ 414 Text: strings.Repeat("本", 2000), 415 }, 416 &model.SlackAttachment{ 417 Text: strings.Repeat("本", model.POST_PROPS_MAX_USER_RUNES-1000), 418 }, 419 }, 420 }, 421 }, 422 Expected: []*model.Post{ 423 { 424 Message: strings.Repeat("本", maxPostSize), 425 }, 426 { 427 Message: strings.Repeat("本", maxPostSize/2), 428 Props: map[string]interface{}{ 429 "attachments": []*model.SlackAttachment{ 430 &model.SlackAttachment{ 431 Text: strings.Repeat("本", 1000), 432 }, 433 &model.SlackAttachment{ 434 Text: strings.Repeat("本", 2000), 435 }, 436 }, 437 }, 438 }, 439 { 440 Props: map[string]interface{}{ 441 "attachments": []*model.SlackAttachment{ 442 &model.SlackAttachment{ 443 Text: strings.Repeat("本", model.POST_PROPS_MAX_USER_RUNES-1000), 444 }, 445 }, 446 }, 447 }, 448 }, 449 }, 450 "UnsplittableProps": { 451 Post: &model.Post{ 452 Message: "foo", 453 Props: map[string]interface{}{ 454 "foo": strings.Repeat("x", model.POST_PROPS_MAX_USER_RUNES*2), 455 }, 456 }, 457 }, 458 } { 459 t.Run(name, func(t *testing.T) { 460 splits, err := SplitWebhookPost(tc.Post, maxPostSize) 461 if tc.Expected == nil { 462 require.NotNil(t, err) 463 } else { 464 require.Nil(t, err) 465 } 466 assert.Equal(t, len(tc.Expected), len(splits)) 467 for i, split := range splits { 468 if i < len(tc.Expected) { 469 assert.Equal(t, tc.Expected[i].Message, split.Message) 470 assert.Equal(t, tc.Expected[i].Props["attachments"], split.Props["attachments"]) 471 } 472 } 473 }) 474 } 475 } 476 477 func TestCreateOutGoingWebhookWithUsernameAndIconURL(t *testing.T) { 478 th := Setup().InitBasic() 479 defer th.TearDown() 480 481 outgoingWebhook := model.OutgoingWebhook{ 482 ChannelId: th.BasicChannel.Id, 483 TeamId: th.BasicChannel.TeamId, 484 CallbackURLs: []string{"http://nowhere.com"}, 485 Username: "some-user-name", 486 IconURL: "http://some-icon/", 487 DisplayName: "some-display-name", 488 Description: "some-description", 489 CreatorId: th.BasicUser.Id, 490 } 491 492 th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) 493 494 createdHook, err := th.App.CreateOutgoingWebhook(&outgoingWebhook) 495 496 if err != nil { 497 t.Fatalf("should not have failed: %v", err.Error()) 498 } 499 500 assert.NotNil(t, createdHook, "should not be null") 501 502 assert.Equal(t, createdHook.ChannelId, outgoingWebhook.ChannelId) 503 assert.Equal(t, createdHook.TeamId, outgoingWebhook.TeamId) 504 assert.Equal(t, createdHook.CallbackURLs, outgoingWebhook.CallbackURLs) 505 assert.Equal(t, createdHook.Username, outgoingWebhook.Username) 506 assert.Equal(t, createdHook.IconURL, outgoingWebhook.IconURL) 507 assert.Equal(t, createdHook.DisplayName, outgoingWebhook.DisplayName) 508 assert.Equal(t, createdHook.Description, outgoingWebhook.Description) 509 510 } 511 512 func TestTriggerOutGoingWebhookWithUsernameAndIconURL(t *testing.T) { 513 514 getPayload := func(hook *model.OutgoingWebhook, th *TestHelper, channel *model.Channel) *model.OutgoingWebhookPayload { 515 return &model.OutgoingWebhookPayload{ 516 Token: hook.Token, 517 TeamId: hook.TeamId, 518 TeamDomain: th.BasicTeam.Name, 519 ChannelId: channel.Id, 520 ChannelName: channel.Name, 521 Timestamp: th.BasicPost.CreateAt, 522 UserId: th.BasicPost.UserId, 523 UserName: th.BasicUser.Username, 524 PostId: th.BasicPost.Id, 525 Text: th.BasicPost.Message, 526 TriggerWord: "Abracadabra", 527 FileIds: strings.Join(th.BasicPost.FileIds, ","), 528 } 529 } 530 531 waitUntilWebhookResposeIsCreatedAsPost := func(channel *model.Channel, th *TestHelper, t *testing.T, createdPost chan *model.Post) { 532 go func() { 533 for i := 0; i < 5; i++ { 534 time.Sleep(time.Second) 535 posts, _ := th.App.GetPosts(channel.Id, 0, 5) 536 if len(posts.Posts) > 0 { 537 for _, post := range posts.Posts { 538 createdPost <- post 539 return 540 } 541 } 542 } 543 }() 544 } 545 546 type TestCaseOutgoing struct { 547 EnablePostUsernameOverride bool 548 EnablePostIconOverride bool 549 ExpectedUsername string 550 ExpectedIconUrl string 551 WebhookResponse *model.OutgoingWebhookResponse 552 } 553 554 createOutgoingWebhook := func(channel *model.Channel, testCallBackUrl string, th *TestHelper) (*model.OutgoingWebhook, *model.AppError) { 555 outgoingWebhook := model.OutgoingWebhook{ 556 ChannelId: channel.Id, 557 TeamId: channel.TeamId, 558 CallbackURLs: []string{testCallBackUrl}, 559 Username: "some-user-name", 560 IconURL: "http://some-icon/", 561 DisplayName: "some-display-name", 562 Description: "some-description", 563 CreatorId: th.BasicUser.Id, 564 TriggerWords: []string{"Abracadabra"}, 565 ContentType: "application/json", 566 } 567 568 return th.App.CreateOutgoingWebhook(&outgoingWebhook) 569 } 570 571 getTestCases := func() map[string]TestCaseOutgoing { 572 573 webHookResponse := "sample response text from test server" 574 testCasesOutgoing := map[string]TestCaseOutgoing{ 575 576 "Should override username and Icon": { 577 EnablePostUsernameOverride: true, 578 EnablePostIconOverride: true, 579 ExpectedUsername: "some-user-name", 580 ExpectedIconUrl: "http://some-icon/", 581 }, 582 "Should not override username and Icon": { 583 EnablePostUsernameOverride: false, 584 EnablePostIconOverride: false, 585 }, 586 "Should not override username and Icon if the webhook response already has it": { 587 EnablePostUsernameOverride: true, 588 EnablePostIconOverride: true, 589 ExpectedUsername: "webhookuser", 590 ExpectedIconUrl: "http://webhok/icon", 591 WebhookResponse: &model.OutgoingWebhookResponse{Text: &webHookResponse, Username: "webhookuser", IconURL: "http://webhok/icon"}, 592 }, 593 } 594 return testCasesOutgoing 595 } 596 597 th := Setup().InitBasic() 598 defer th.TearDown() 599 600 th.App.UpdateConfig(func(cfg *model.Config) { 601 *cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost 127.0.0.1" 602 }) 603 createdPost := make(chan *model.Post) 604 605 for name, testCase := range getTestCases() { 606 t.Run(name, func(t *testing.T) { 607 608 th.App.UpdateConfig(func(cfg *model.Config) { 609 cfg.ServiceSettings.EnableOutgoingWebhooks = true 610 cfg.ServiceSettings.EnablePostUsernameOverride = testCase.EnablePostUsernameOverride 611 cfg.ServiceSettings.EnablePostIconOverride = testCase.EnablePostIconOverride 612 }) 613 614 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 615 if testCase.WebhookResponse != nil { 616 w.Write([]byte(testCase.WebhookResponse.ToJson())) 617 } else { 618 w.Write([]byte(`{"text": "sample response text from test server"}`)) 619 } 620 })) 621 defer ts.Close() 622 623 channel := th.CreateChannel(th.BasicTeam) 624 hook, _ := createOutgoingWebhook(channel, ts.URL, th) 625 payload := getPayload(hook, th, channel) 626 627 th.App.TriggerWebhook(payload, hook, th.BasicPost, channel) 628 629 waitUntilWebhookResposeIsCreatedAsPost(channel, th, t, createdPost) 630 631 select { 632 case webhookPost := <-createdPost: 633 assert.Equal(t, webhookPost.Message, "sample response text from test server") 634 assert.Equal(t, webhookPost.Props["from_webhook"], "true") 635 if testCase.ExpectedIconUrl != "" { 636 assert.Equal(t, webhookPost.Props["override_icon_url"], testCase.ExpectedIconUrl) 637 } else { 638 assert.Nil(t, webhookPost.Props["override_icon_url"]) 639 } 640 641 if testCase.ExpectedUsername != "" { 642 assert.Equal(t, webhookPost.Props["override_username"], testCase.ExpectedUsername) 643 } else { 644 assert.Nil(t, webhookPost.Props["override_username"]) 645 } 646 case <-time.After(5 * time.Second): 647 t.Fatal("Timeout, webhook response not created as post") 648 } 649 650 }) 651 } 652 653 }