TestChat401DrainsBodyForConnReuse: a 401 carrying a body must have that body drained before close, or Go's transport discards the TCP connection instead of returning it to the keep-alive pool. With the same client issuing two sequential 401s, a drained body reuses one connection (one RemoteAddr); an
(t *testing.T)
| 461 | // undrained one forces a fresh connection on the second request (two). The 402 |
| 462 | // and default error branches already drain; this pins the 401 branch to match. |
| 463 | func TestChat401DrainsBodyForConnReuse(t *testing.T) { |
| 464 | var mu sync.Mutex |
| 465 | conns := map[string]bool{} |
| 466 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 467 | mu.Lock() |
| 468 | conns[r.RemoteAddr] = true |
| 469 | mu.Unlock() |
| 470 | w.WriteHeader(http.StatusUnauthorized) |
| 471 | // Non-empty body: an empty 401 is reusable regardless and would hide the |
| 472 | // regression. A real backend's 401 carries an error JSON like this. |
| 473 | fmt.Fprint(w, `{"error":{"message":"invalid api key"}}`) |
| 474 | })) |
| 475 | defer srv.Close() |
| 476 | |
| 477 | c := New(srv.URL, "m", "") |
| 478 | for i := 0; i < 2; i++ { |
| 479 | evs := collect(c.Chat(context.Background(), nil, nil)) |
| 480 | if len(evs) != 1 || !errors.Is(evs[0].Err, cloud.ErrUnauthorized) { |
| 481 | t.Fatalf("request %d: want ErrUnauthorized, got %+v", i, evs) |
| 482 | } |
| 483 | } |
| 484 | mu.Lock() |
| 485 | n := len(conns) |
| 486 | mu.Unlock() |
| 487 | if n != 1 { |
| 488 | t.Fatalf("401 body not drained: server saw %d connections across 2 sequential requests, want 1 (keep-alive reuse defeated)", n) |
| 489 | } |
| 490 | } |
| 491 | |
| 492 | // TestChat402: budget exhaustion surfaces as a typed error with the snapshot |
| 493 | // reporting zero remaining, so the UI paints the depleted state immediately. |