github.com/livekit/protocol@v1.39.3/webhook/webhook_test.go (about) 1 // Copyright 2023 LiveKit, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package webhook 16 17 import ( 18 "context" 19 "fmt" 20 "net" 21 "net/http" 22 "sync" 23 "testing" 24 "time" 25 26 "github.com/prometheus/client_golang/prometheus" 27 "github.com/stretchr/testify/require" 28 "go.uber.org/atomic" 29 30 "github.com/livekit/protocol/auth" 31 "github.com/livekit/protocol/livekit" 32 ) 33 34 const ( 35 testAPIKey = "mykey" 36 testAPISecret = "mysecret" 37 testAddr = ":8765" 38 testUrl = "http://localhost:8765" 39 webhookCheckInterval = 100 * time.Millisecond 40 ) 41 42 var authProvider = auth.NewSimpleKeyProvider( 43 testAPIKey, testAPISecret, 44 ) 45 46 func TestWebHook(t *testing.T) { 47 InitWebhookStats(prometheus.Labels{}) 48 49 s := newServer(testAddr) 50 require.NoError(t, s.Start()) 51 defer s.Stop() 52 53 t.Run("test event payload", func(t *testing.T) { 54 notifier := newTestNotifier() 55 defer notifier.Stop(false) 56 57 event := &livekit.WebhookEvent{ 58 Event: EventTrackPublished, 59 Participant: &livekit.ParticipantInfo{ 60 Identity: "test", 61 }, 62 Track: &livekit.TrackInfo{ 63 Sid: "TR_abcde", 64 }, 65 } 66 67 wg := sync.WaitGroup{} 68 wg.Add(1) 69 expectedUrl := "/" 70 s.handler = func(w http.ResponseWriter, r *http.Request) { 71 defer wg.Done() 72 decodedEvent, err := ReceiveWebhookEvent(r, authProvider) 73 require.NoError(t, err) 74 75 require.EqualValues(t, event, decodedEvent) 76 require.Equal(t, expectedUrl, r.URL.String()) 77 } 78 require.NoError(t, notifier.QueueNotify(context.Background(), event)) 79 wg.Wait() 80 81 wg.Add(1) 82 expectedUrl = "/wh" 83 require.NoError(t, notifier.QueueNotify(context.Background(), event, WithExtraWebhooks([]*livekit.WebhookConfig{&livekit.WebhookConfig{Url: "http://localhost:8765/wh"}}))) 84 wg.Wait() 85 86 }) 87 } 88 89 func TestURLNotifierDropped(t *testing.T) { 90 InitWebhookStats(prometheus.Labels{}) 91 92 s := newServer(testAddr) 93 require.NoError(t, s.Start()) 94 defer s.Stop() 95 96 urlNotifier := newTestNotifier() 97 defer urlNotifier.Stop(true) 98 totalDropped := atomic.Int32{} 99 totalReceived := atomic.Int32{} 100 s.handler = func(w http.ResponseWriter, r *http.Request) { 101 decodedEvent, err := ReceiveWebhookEvent(r, authProvider) 102 require.NoError(t, err) 103 totalReceived.Inc() 104 totalDropped.Add(decodedEvent.NumDropped) 105 } 106 // send multiple notifications 107 for i := 0; i < 10; i++ { 108 _ = urlNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomStarted}) 109 _ = urlNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventParticipantJoined}) 110 _ = urlNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomFinished}) 111 } 112 113 time.Sleep(webhookCheckInterval) 114 115 require.Equal(t, int32(30), totalDropped.Load()+totalReceived.Load()) 116 // at least one request dropped 117 require.Less(t, int32(0), totalDropped.Load()) 118 } 119 120 func TestURLNotifierLifecycle(t *testing.T) { 121 InitWebhookStats(prometheus.Labels{}) 122 123 s := newServer(testAddr) 124 require.NoError(t, s.Start()) 125 defer s.Stop() 126 127 t.Run("start/stop without use", func(t *testing.T) { 128 urlNotifier := newTestNotifier() 129 urlNotifier.Stop(false) 130 }) 131 132 t.Run("stop allowing to drain", func(t *testing.T) { 133 urlNotifier := newTestNotifier() 134 numCalled := atomic.Int32{} 135 s.handler = func(w http.ResponseWriter, r *http.Request) { 136 numCalled.Inc() 137 } 138 for i := 0; i < 10; i++ { 139 _ = urlNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomStarted}) 140 _ = urlNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomFinished}) 141 } 142 urlNotifier.Stop(false) 143 require.Eventually(t, func() bool { return numCalled.Load() == 20 }, 5*time.Second, webhookCheckInterval) 144 }) 145 146 t.Run("force stop", func(t *testing.T) { 147 urlNotifier := newTestNotifier() 148 numCalled := atomic.Int32{} 149 s.handler = func(w http.ResponseWriter, r *http.Request) { 150 numCalled.Inc() 151 } 152 for i := 0; i < 10; i++ { 153 _ = urlNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomStarted}) 154 _ = urlNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomFinished}) 155 } 156 urlNotifier.Stop(true) 157 time.Sleep(time.Second) 158 require.Greater(t, int32(20), numCalled.Load()) 159 }) 160 161 t.Run("times out after accepting connection", func(t *testing.T) { 162 urlNotifier := NewURLNotifier(URLNotifierParams{ 163 URL: testUrl, 164 APIKey: testAPIKey, 165 APISecret: testAPISecret, 166 HTTPClientParams: HTTPClientParams{ 167 RetryWaitMax: time.Millisecond, 168 MaxRetries: 1, 169 ClientTimeout: 100 * time.Millisecond, 170 }, 171 Config: URLNotifierConfig{ 172 QueueSize: 20, 173 }, 174 }) 175 176 numCalled := atomic.Int32{} 177 s.handler = func(w http.ResponseWriter, r *http.Request) { 178 w.WriteHeader(200) 179 w.Write([]byte("ok")) 180 181 // delay the request to cause it to fail 182 time.Sleep(time.Second) 183 if r.Context().Err() == nil { 184 // inc if not canceled 185 numCalled.Inc() 186 } 187 } 188 defer urlNotifier.Stop(false) 189 190 err := urlNotifier.send(&livekit.WebhookEvent{Event: EventRoomStarted}, &urlNotifier.params) 191 require.Error(t, err) 192 }) 193 194 t.Run("times out before connection", func(t *testing.T) { 195 ln, err := net.Listen("tcp", ":9987") 196 require.NoError(t, err) 197 defer ln.Close() 198 urlNotifier := NewURLNotifier(URLNotifierParams{ 199 URL: "http://localhost:9987", 200 APIKey: testAPIKey, 201 APISecret: testAPISecret, 202 HTTPClientParams: HTTPClientParams{ 203 RetryWaitMax: time.Millisecond, 204 MaxRetries: 1, 205 ClientTimeout: 100 * time.Millisecond, 206 }, 207 }) 208 defer urlNotifier.Stop(false) 209 210 startedAt := time.Now() 211 err = urlNotifier.send(&livekit.WebhookEvent{Event: EventRoomStarted}, &urlNotifier.params) 212 require.Error(t, err) 213 require.Less(t, time.Since(startedAt).Seconds(), float64(2)) 214 }) 215 } 216 217 func TestURLNotifierFilter(t *testing.T) { 218 InitWebhookStats(prometheus.Labels{}) 219 220 s := newServer(testAddr) 221 require.NoError(t, s.Start()) 222 defer s.Stop() 223 224 t.Run("none", func(t *testing.T) { 225 urlNotifier := NewURLNotifier(URLNotifierParams{ 226 URL: testUrl, 227 APIKey: testAPIKey, 228 APISecret: testAPISecret, 229 Config: URLNotifierConfig{ 230 QueueSize: 20, 231 }, 232 }) 233 defer urlNotifier.Stop(false) 234 235 numCalled := atomic.Int32{} 236 s.handler = func(w http.ResponseWriter, r *http.Request) { 237 numCalled.Inc() 238 } 239 240 _ = urlNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomStarted}) 241 _ = urlNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomFinished}) 242 require.Eventually( 243 t, 244 func() bool { 245 return numCalled.Load() == 2 246 }, 247 5*time.Second, 248 webhookCheckInterval, 249 ) 250 }) 251 252 t.Run("includes", func(t *testing.T) { 253 urlNotifier := NewURLNotifier(URLNotifierParams{ 254 URL: testUrl, 255 APIKey: testAPIKey, 256 APISecret: testAPISecret, 257 FilterParams: FilterParams{ 258 IncludeEvents: []string{EventRoomStarted}, 259 }, 260 Config: URLNotifierConfig{ 261 QueueSize: 20, 262 }, 263 }) 264 defer urlNotifier.Stop(false) 265 266 numCalled := atomic.Int32{} 267 s.handler = func(w http.ResponseWriter, r *http.Request) { 268 numCalled.Inc() 269 } 270 271 _ = urlNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomStarted}) 272 _ = urlNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomFinished}) 273 require.Eventually( 274 t, 275 func() bool { 276 return numCalled.Load() == 1 277 }, 278 5*time.Second, 279 webhookCheckInterval, 280 ) 281 }) 282 283 t.Run("excludes", func(t *testing.T) { 284 urlNotifier := NewURLNotifier(URLNotifierParams{ 285 URL: testUrl, 286 APIKey: testAPIKey, 287 APISecret: testAPISecret, 288 FilterParams: FilterParams{ 289 ExcludeEvents: []string{EventRoomStarted}, 290 }, 291 Config: URLNotifierConfig{ 292 QueueSize: 20, 293 }, 294 }) 295 defer urlNotifier.Stop(false) 296 297 numCalled := atomic.Int32{} 298 s.handler = func(w http.ResponseWriter, r *http.Request) { 299 numCalled.Inc() 300 } 301 302 _ = urlNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomStarted}) 303 _ = urlNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomFinished}) 304 require.Eventually( 305 t, 306 func() bool { 307 return numCalled.Load() == 1 308 }, 309 5*time.Second, 310 webhookCheckInterval, 311 ) 312 }) 313 314 t.Run("includes + excludes", func(t *testing.T) { 315 urlNotifier := NewURLNotifier(URLNotifierParams{ 316 URL: testUrl, 317 APIKey: testAPIKey, 318 APISecret: testAPISecret, 319 FilterParams: FilterParams{ 320 IncludeEvents: []string{EventRoomStarted}, 321 ExcludeEvents: []string{EventRoomStarted, EventRoomFinished}, 322 }, 323 Config: URLNotifierConfig{ 324 QueueSize: 20, 325 }, 326 }) 327 defer urlNotifier.Stop(false) 328 329 numCalled := atomic.Int32{} 330 s.handler = func(w http.ResponseWriter, r *http.Request) { 331 numCalled.Inc() 332 } 333 334 // EventRoomStarted should be allowed as IncludeEvents take precedence 335 _ = urlNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomStarted}) 336 _ = urlNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomFinished}) 337 require.Eventually( 338 t, 339 func() bool { 340 return numCalled.Load() == 1 341 }, 342 5*time.Second, 343 webhookCheckInterval, 344 ) 345 }) 346 } 347 348 func newTestNotifier() *URLNotifier { 349 return NewURLNotifier(URLNotifierParams{ 350 URL: testUrl, 351 APIKey: testAPIKey, 352 APISecret: testAPISecret, 353 Config: URLNotifierConfig{ 354 QueueSize: 20, 355 }, 356 }) 357 } 358 359 // -------------------------------------------- 360 361 func TestResourceWebHook(t *testing.T) { 362 s := newServer(testAddr) 363 require.NoError(t, s.Start()) 364 defer s.Stop() 365 366 t.Run("test event payload", func(t *testing.T) { 367 resourceURLNotifier, err := NewDefaultNotifier( 368 WebHookConfig{ 369 URLs: []string{testUrl}, 370 APIKey: testAPIKey, 371 }, 372 authProvider, 373 ) 374 require.NoError(t, err) 375 defer resourceURLNotifier.Stop(false) 376 377 event := &livekit.WebhookEvent{ 378 Event: EventTrackPublished, 379 Participant: &livekit.ParticipantInfo{ 380 Identity: "test", 381 }, 382 Track: &livekit.TrackInfo{ 383 Sid: "TR_abcde", 384 }, 385 } 386 387 wg := sync.WaitGroup{} 388 wg.Add(1) 389 s.handler = func(w http.ResponseWriter, r *http.Request) { 390 defer wg.Done() 391 decodedEvent, err := ReceiveWebhookEvent(r, authProvider) 392 require.NoError(t, err) 393 394 require.EqualValues(t, event, decodedEvent) 395 } 396 require.NoError(t, resourceURLNotifier.QueueNotify(context.Background(), event)) 397 wg.Wait() 398 }) 399 400 } 401 402 func TestResourceURLNotifierDropped(t *testing.T) { 403 s := newServer(testAddr) 404 require.NoError(t, s.Start()) 405 defer s.Stop() 406 407 t.Run("depth drop", func(t *testing.T) { 408 resourceURLNotifier := newTestResourceNotifier(time.Minute, time.Minute, 5) 409 defer resourceURLNotifier.Stop(true) 410 totalDropped := atomic.Int32{} 411 totalReceived := atomic.Int32{} 412 s.handler = func(w http.ResponseWriter, r *http.Request) { 413 _, err := ReceiveWebhookEvent(r, authProvider) 414 require.NoError(t, err) 415 totalReceived.Inc() 416 } 417 // send multiple notifications 418 for i := 0; i < 10; i++ { 419 err := resourceURLNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomStarted}) 420 if err == errQueueFull { 421 totalDropped.Inc() 422 } 423 err = resourceURLNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventParticipantJoined}) 424 if err == errQueueFull { 425 totalDropped.Inc() 426 } 427 err = resourceURLNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomFinished}) 428 if err == errQueueFull { 429 totalDropped.Inc() 430 } 431 } 432 433 time.Sleep(webhookCheckInterval) 434 435 require.Eventually( 436 t, 437 func() bool { 438 return totalDropped.Load()+totalReceived.Load() == 30 439 }, 440 5*time.Second, 441 webhookCheckInterval, 442 ) 443 // at least one request dropped, but not all dropped 444 require.Less(t, int32(0), totalDropped.Load()) 445 require.Less(t, int32(0), totalReceived.Load()) 446 }) 447 448 t.Run("age drop", func(t *testing.T) { 449 resourceURLNotifier := newTestResourceNotifier(time.Minute, 10*time.Millisecond, 500) 450 defer resourceURLNotifier.Stop(true) 451 totalReceived := atomic.Int32{} 452 s.handler = func(w http.ResponseWriter, r *http.Request) { 453 time.Sleep(5 * time.Millisecond) 454 _, err := ReceiveWebhookEvent(r, authProvider) 455 require.NoError(t, err) 456 totalReceived.Inc() 457 } 458 // send multiple notifications 459 for i := 0; i < 10; i++ { 460 _ = resourceURLNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomStarted}) 461 time.Sleep(time.Millisecond) 462 _ = resourceURLNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventParticipantJoined}) 463 time.Sleep(time.Millisecond) 464 _ = resourceURLNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomFinished}) 465 time.Sleep(time.Millisecond) 466 } 467 468 time.Sleep(2 * webhookCheckInterval) 469 470 // at least one request dropped 471 require.Greater(t, int32(30), totalReceived.Load()) 472 require.Less(t, int32(0), totalReceived.Load()) 473 }) 474 475 t.Run("resource queue timeout", func(t *testing.T) { 476 resourceURLNotifier := newTestResourceNotifier(5*time.Millisecond, time.Minute, 500) 477 defer resourceURLNotifier.Stop(true) 478 totalReceived := atomic.Int32{} 479 s.handler = func(w http.ResponseWriter, r *http.Request) { 480 _, err := ReceiveWebhookEvent(r, authProvider) 481 require.NoError(t, err) 482 totalReceived.Inc() 483 } 484 485 // check that resource queues change for the same event key 486 for i := 0; i < 3; i++ { 487 var rq *resourceQueue 488 489 roomName := fmt.Sprintf("room%d", i) 490 491 _ = resourceURLNotifier.QueueNotify( 492 context.Background(), 493 &livekit.WebhookEvent{ 494 Event: EventRoomStarted, 495 Room: &livekit.Room{ 496 Name: roomName, 497 }, 498 }, 499 ) 500 resourceURLNotifier.mu.RLock() 501 rqi := resourceURLNotifier.resourceQueues[roomName] 502 resourceURLNotifier.mu.RUnlock() 503 require.NotNil(t, rqi) 504 require.NotNil(t, rqi.resourceQueue) 505 require.NotSame(t, rqi.resourceQueue, rq) 506 rq = rqi.resourceQueue 507 time.Sleep(10 * time.Millisecond) 508 509 _ = resourceURLNotifier.QueueNotify( 510 context.Background(), 511 &livekit.WebhookEvent{ 512 Event: EventParticipantJoined, 513 Room: &livekit.Room{ 514 Name: roomName, 515 }, 516 }, 517 ) 518 resourceURLNotifier.mu.RLock() 519 rqi = resourceURLNotifier.resourceQueues[roomName] 520 resourceURLNotifier.mu.RUnlock() 521 require.NotNil(t, rqi) 522 require.NotNil(t, rqi.resourceQueue) 523 require.NotSame(t, rqi.resourceQueue, rq) 524 rq = rqi.resourceQueue 525 time.Sleep(10 * time.Millisecond) 526 527 _ = resourceURLNotifier.QueueNotify( 528 context.Background(), 529 &livekit.WebhookEvent{ 530 Event: EventParticipantLeft, 531 Room: &livekit.Room{ 532 Name: roomName, 533 }, 534 }, 535 ) 536 resourceURLNotifier.mu.RLock() 537 rqi = resourceURLNotifier.resourceQueues[roomName] 538 resourceURLNotifier.mu.RUnlock() 539 require.NotNil(t, rqi) 540 require.NotNil(t, rqi.resourceQueue) 541 require.NotSame(t, rqi.resourceQueue, rq) 542 rq = rqi.resourceQueue 543 time.Sleep(10 * time.Millisecond) 544 } 545 546 time.Sleep(webhookCheckInterval) 547 548 require.Equal(t, int32(9), totalReceived.Load()) 549 }) 550 } 551 552 func TestResourceURLNotifierLifecycle(t *testing.T) { 553 s := newServer(testAddr) 554 require.NoError(t, s.Start()) 555 defer s.Stop() 556 557 t.Run("start/stop without use", func(t *testing.T) { 558 resourceURLNotifier := newTestResourceNotifier(time.Minute, 200*time.Millisecond, 50) 559 resourceURLNotifier.Stop(false) 560 }) 561 562 t.Run("sweeper", func(t *testing.T) { 563 resourceURLNotifier := newTestResourceNotifier(200*time.Millisecond, 200*time.Millisecond, 50) 564 numCalled := atomic.Int32{} 565 s.handler = func(w http.ResponseWriter, r *http.Request) { 566 numCalled.Inc() 567 } 568 for i := 0; i < 10; i++ { 569 roomName := fmt.Sprintf("room%d", i) 570 _ = resourceURLNotifier.QueueNotify( 571 context.Background(), 572 &livekit.WebhookEvent{ 573 Event: EventRoomStarted, 574 Room: &livekit.Room{ 575 Name: roomName, 576 }, 577 }, 578 ) 579 580 _ = resourceURLNotifier.QueueNotify( 581 context.Background(), 582 &livekit.WebhookEvent{ 583 Event: EventRoomFinished, 584 Room: &livekit.Room{ 585 Name: roomName, 586 }, 587 }, 588 ) 589 } 590 591 resourceURLNotifier.mu.RLock() 592 require.Equal(t, 10, len(resourceURLNotifier.resourceQueues)) 593 resourceURLNotifier.mu.RUnlock() 594 595 time.Sleep(time.Second) 596 597 // should have reaped after some time 598 resourceURLNotifier.mu.RLock() 599 require.Equal(t, 0, len(resourceURLNotifier.resourceQueues)) 600 resourceURLNotifier.mu.RUnlock() 601 602 require.Equal(t, int32(20), numCalled.Load()) 603 }) 604 605 t.Run("stop allowing to drain", func(t *testing.T) { 606 resourceURLNotifier := newTestResourceNotifier(time.Minute, 200*time.Millisecond, 50) 607 numCalled := atomic.Int32{} 608 s.handler = func(w http.ResponseWriter, r *http.Request) { 609 numCalled.Inc() 610 } 611 for i := 0; i < 10; i++ { 612 _ = resourceURLNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomStarted}) 613 _ = resourceURLNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomFinished}) 614 } 615 resourceURLNotifier.Stop(false) 616 require.Eventually(t, func() bool { return numCalled.Load() == 20 }, 5*time.Second, webhookCheckInterval) 617 }) 618 619 t.Run("force stop", func(t *testing.T) { 620 resourceURLNotifier := newTestResourceNotifier(time.Minute, 200*time.Millisecond, 50) 621 numCalled := atomic.Int32{} 622 s.handler = func(w http.ResponseWriter, r *http.Request) { 623 numCalled.Inc() 624 } 625 for i := 0; i < 10; i++ { 626 _ = resourceURLNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomStarted}) 627 _ = resourceURLNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomFinished}) 628 } 629 resourceURLNotifier.Stop(true) 630 time.Sleep(time.Second) 631 require.Greater(t, int32(20), numCalled.Load()) 632 }) 633 634 t.Run("times out after accepting connection", func(t *testing.T) { 635 params := ResourceURLNotifierParams{ 636 URL: testUrl, 637 APIKey: testAPIKey, 638 APISecret: testAPISecret, 639 Config: ResourceURLNotifierConfig{ 640 MaxAge: 200 * time.Millisecond, 641 MaxDepth: 50, 642 }, 643 HTTPClientParams: HTTPClientParams{ 644 RetryWaitMax: time.Millisecond, 645 MaxRetries: 1, 646 ClientTimeout: 100 * time.Millisecond, 647 }, 648 } 649 650 resourceURLNotifier := NewResourceURLNotifier(params) 651 652 numCalled := atomic.Int32{} 653 s.handler = func(w http.ResponseWriter, r *http.Request) { 654 w.WriteHeader(200) 655 w.Write([]byte("ok")) 656 657 // delay the request to cause it to fail 658 time.Sleep(time.Second) 659 if r.Context().Err() == nil { 660 // inc if not canceled 661 numCalled.Inc() 662 } 663 } 664 defer resourceURLNotifier.Stop(false) 665 666 err := resourceURLNotifier.send(&livekit.WebhookEvent{Event: EventRoomStarted}, ¶ms) 667 require.Error(t, err) 668 }) 669 670 t.Run("times out before connection", func(t *testing.T) { 671 ln, err := net.Listen("tcp", ":9987") 672 require.NoError(t, err) 673 defer ln.Close() 674 675 params := ResourceURLNotifierParams{ 676 URL: "http://localhost:9987", 677 APIKey: testAPIKey, 678 APISecret: testAPISecret, 679 Config: ResourceURLNotifierConfig{ 680 MaxAge: 200 * time.Millisecond, 681 MaxDepth: 50, 682 }, 683 HTTPClientParams: HTTPClientParams{ 684 RetryWaitMax: time.Millisecond, 685 MaxRetries: 1, 686 ClientTimeout: 100 * time.Millisecond, 687 }, 688 } 689 690 resourceURLNotifier := NewResourceURLNotifier(params) 691 defer resourceURLNotifier.Stop(false) 692 693 startedAt := time.Now() 694 err = resourceURLNotifier.send(&livekit.WebhookEvent{Event: EventRoomStarted}, ¶ms) 695 require.Error(t, err) 696 require.Less(t, time.Since(startedAt).Seconds(), float64(2)) 697 }) 698 } 699 700 func TestResourceURLNotifierFilter(t *testing.T) { 701 s := newServer(testAddr) 702 require.NoError(t, s.Start()) 703 defer s.Stop() 704 705 t.Run("none", func(t *testing.T) { 706 resourceURLNotifier := NewResourceURLNotifier(ResourceURLNotifierParams{ 707 URL: testUrl, 708 APIKey: testAPIKey, 709 APISecret: testAPISecret, 710 Config: ResourceURLNotifierConfig{ 711 MaxAge: 200 * time.Millisecond, 712 MaxDepth: 50, 713 }, 714 FilterParams: FilterParams{}, 715 }) 716 defer resourceURLNotifier.Stop(false) 717 718 numCalled := atomic.Int32{} 719 s.handler = func(w http.ResponseWriter, r *http.Request) { 720 numCalled.Inc() 721 } 722 723 _ = resourceURLNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomStarted}) 724 _ = resourceURLNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomFinished}) 725 require.Eventually( 726 t, 727 func() bool { 728 return numCalled.Load() == 2 729 }, 730 5*time.Second, 731 webhookCheckInterval, 732 ) 733 }) 734 735 t.Run("includes", func(t *testing.T) { 736 resourceURLNotifier := NewResourceURLNotifier(ResourceURLNotifierParams{ 737 URL: testUrl, 738 APIKey: testAPIKey, 739 APISecret: testAPISecret, 740 Config: ResourceURLNotifierConfig{ 741 MaxAge: 200 * time.Millisecond, 742 MaxDepth: 50, 743 }, 744 FilterParams: FilterParams{ 745 IncludeEvents: []string{EventRoomStarted}, 746 }, 747 }) 748 defer resourceURLNotifier.Stop(false) 749 750 numCalled := atomic.Int32{} 751 s.handler = func(w http.ResponseWriter, r *http.Request) { 752 numCalled.Inc() 753 } 754 755 _ = resourceURLNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomStarted}) 756 _ = resourceURLNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomFinished}) 757 require.Eventually( 758 t, 759 func() bool { 760 return numCalled.Load() == 1 761 }, 762 5*time.Second, 763 webhookCheckInterval, 764 ) 765 }) 766 767 t.Run("excludes", func(t *testing.T) { 768 resourceURLNotifier := NewResourceURLNotifier(ResourceURLNotifierParams{ 769 URL: testUrl, 770 APIKey: testAPIKey, 771 APISecret: testAPISecret, 772 Config: ResourceURLNotifierConfig{ 773 MaxAge: 200 * time.Millisecond, 774 MaxDepth: 50, 775 }, 776 FilterParams: FilterParams{ 777 ExcludeEvents: []string{EventRoomStarted}, 778 }, 779 }) 780 defer resourceURLNotifier.Stop(false) 781 782 numCalled := atomic.Int32{} 783 s.handler = func(w http.ResponseWriter, r *http.Request) { 784 numCalled.Inc() 785 } 786 787 _ = resourceURLNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomStarted}) 788 _ = resourceURLNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomFinished}) 789 require.Eventually( 790 t, 791 func() bool { 792 return numCalled.Load() == 1 793 }, 794 5*time.Second, 795 webhookCheckInterval, 796 ) 797 }) 798 799 t.Run("includes + excludes", func(t *testing.T) { 800 resourceURLNotifier := NewResourceURLNotifier(ResourceURLNotifierParams{ 801 URL: testUrl, 802 APIKey: testAPIKey, 803 APISecret: testAPISecret, 804 Config: ResourceURLNotifierConfig{ 805 MaxAge: 200 * time.Millisecond, 806 MaxDepth: 50, 807 }, 808 FilterParams: FilterParams{ 809 IncludeEvents: []string{EventRoomStarted}, 810 ExcludeEvents: []string{EventRoomStarted, EventRoomFinished}, 811 }, 812 }) 813 defer resourceURLNotifier.Stop(false) 814 815 numCalled := atomic.Int32{} 816 s.handler = func(w http.ResponseWriter, r *http.Request) { 817 numCalled.Inc() 818 } 819 820 // EventRoomStarted should be allowed as IncludeEvents take precedence 821 _ = resourceURLNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomStarted}) 822 _ = resourceURLNotifier.QueueNotify(context.Background(), &livekit.WebhookEvent{Event: EventRoomFinished}) 823 require.Eventually( 824 t, 825 func() bool { 826 return numCalled.Load() == 1 827 }, 828 5*time.Second, 829 webhookCheckInterval, 830 ) 831 }) 832 } 833 834 func newTestResourceNotifier(timeout time.Duration, maxAge time.Duration, maxDepth int) *ResourceURLNotifier { 835 return NewResourceURLNotifier(ResourceURLNotifierParams{ 836 URL: testUrl, 837 APIKey: testAPIKey, 838 APISecret: testAPISecret, 839 Timeout: timeout, 840 Config: ResourceURLNotifierConfig{ 841 MaxAge: maxAge, 842 MaxDepth: maxDepth, 843 }, 844 }) 845 } 846 847 // --------------------------------------- 848 849 type testServer struct { 850 handler func(w http.ResponseWriter, r *http.Request) 851 server *http.Server 852 } 853 854 func newServer(addr string) *testServer { 855 s := &testServer{} 856 s.server = &http.Server{ 857 Addr: addr, 858 Handler: s, 859 } 860 return s 861 } 862 863 func (s *testServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { 864 if s.handler != nil { 865 s.handler(w, r) 866 } 867 } 868 869 func (s *testServer) Start() error { 870 l, err := net.Listen("tcp", s.server.Addr) 871 if err != nil { 872 return err 873 } 874 go s.server.Serve(l) 875 return nil 876 } 877 878 func (s *testServer) Stop() { 879 _ = s.server.Shutdown(context.Background()) 880 }