TestChatIdleWatchdogResetByFrames pins that an alive-but-slow stream is NOT aborted: frames spaced under the idle window each reset the watchdog, so a stream whose total span exceeds one window still completes. Guards against a regression where onFrame() stops resetting the timer.
(t *testing.T)
| 898 | t.Fatalf("error should name the stall: %v", gotErr) |
| 899 | } |
| 900 | if elapsed := time.Since(start); elapsed > 2*time.Second { |
| 901 | t.Fatalf("watchdog fired too late (%v) - should be ~IdleTimeout", elapsed) |
| 902 | } |
| 903 | } |
| 904 | |
| 905 | // TestChatIdleWatchdogResetByFrames pins that an alive-but-slow stream is NOT |
| 906 | // aborted: frames spaced under the idle window each reset the watchdog, so a |
| 907 | // stream whose total span exceeds one window still completes. Guards against a |
| 908 | // regression where onFrame() stops resetting the timer. |
| 909 | func TestChatIdleWatchdogResetByFrames(t *testing.T) { |
| 910 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { |
| 911 | w.Header().Set("Content-Type", "text/event-stream") |
| 912 | flush := w.(http.Flusher) |
| 913 | for _, c := range []string{ |
| 914 | `{"choices":[{"delta":{"content":"A"}}]}`, |
| 915 | `{"choices":[{"delta":{"content":"B"}}]}`, |
| 916 | } { |
| 917 | fmt.Fprintf(w, "data: %s\n\n", c) |
| 918 | flush.Flush() |
| 919 | time.Sleep(250 * time.Millisecond) // < IdleTimeout, so the watchdog resets |
| 920 | } |
| 921 | fmt.Fprint(w, "data: [DONE]\n\n") |
| 922 | flush.Flush() |
| 923 | })) |
| 924 | defer srv.Close() |
| 925 | |
| 926 | c := New(srv.URL, "m", "") |
| 927 | c.IdleTimeout = 400 * time.Millisecond // > each 250ms gap, < ~500ms total span |
| 928 | |
| 929 | var content strings.Builder |
| 930 | for _, e := range collect(c.Chat(context.Background(), nil, nil)) { |
| 931 | switch e.Kind { |
| 932 | case EventContent: |
| 933 | content.WriteString(e.Content) |
| 934 | case EventError: |
| 935 | t.Fatalf("watchdog aborted a live stream: %v", e.Err) |