| 441 | } |
| 442 | |
| 443 | func Test_SSE_InterruptedClientClosesStream(t *testing.T) { |
| 444 | t.Parallel() |
| 445 | |
| 446 | const ( |
| 447 | testTimeout = time.Second |
| 448 | disconnectObservationTimeout = 2 * time.Second |
| 449 | ) |
| 450 | |
| 451 | app := fiber.New() |
| 452 | handlerDone := make(chan error, 1) |
| 453 | onCloseDone := make(chan error, 1) |
| 454 | |
| 455 | app.Get("/events", New(Config{ |
| 456 | HeartbeatInterval: 100 * time.Millisecond, |
| 457 | Handler: func(_ fiber.Ctx, stream *Stream) error { |
| 458 | if err := stream.Event(Event{Data: "ready"}); err != nil { |
| 459 | handlerDone <- err |
| 460 | return err |
| 461 | } |
| 462 | |
| 463 | <-stream.Done() |
| 464 | err := stream.Err() |
| 465 | handlerDone <- err |
| 466 | return err |
| 467 | }, |
| 468 | OnClose: func(_ fiber.Ctx, err error) { |
| 469 | onCloseDone <- err |
| 470 | }, |
| 471 | })) |
| 472 | |
| 473 | resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/events", http.NoBody), fiber.TestConfig{ |
| 474 | FailOnTimeout: false, |
| 475 | Timeout: testTimeout, |
| 476 | }) |
| 477 | require.NoError(t, err) |
| 478 | require.Equal(t, fiber.StatusOK, resp.StatusCode) |
| 479 | |
| 480 | body, err := io.ReadAll(resp.Body) |
| 481 | require.True(t, err == nil || errors.Is(err, io.ErrUnexpectedEOF)) |
| 482 | require.Contains(t, string(body), "data: ready\n\n") |
| 483 | |
| 484 | select { |
| 485 | case err := <-handlerDone: |
| 486 | require.Error(t, err) |
| 487 | case <-time.After(disconnectObservationTimeout): |
| 488 | t.Fatal("handler did not observe the interrupted client") |
| 489 | } |
| 490 | |
| 491 | select { |
| 492 | case err := <-onCloseDone: |
| 493 | require.Error(t, err) |
| 494 | case <-time.After(disconnectObservationTimeout): |
| 495 | t.Fatal("OnClose was not called after the interrupted client") |
| 496 | } |
| 497 | } |
| 498 | |
| 499 | func Test_SSE_HandlerErrorCallsOnClose(t *testing.T) { |
| 500 | t.Parallel() |