package web import ( "net/http" "net/http/httptest" "strconv" "testing" "time" ) // deadlineRecorder 冒充 http.response:只有实现了 SetWriteDeadline, // 才看得出 http.NewResponseController 有没有被中间件挡住。 type deadlineRecorder struct { *httptest.ResponseRecorder deadline time.Time got bool } func (d *deadlineRecorder) SetWriteDeadline(t time.Time) error { d.deadline, d.got = t, true return nil } // P4 回归:gzipWriter 必须实现 Unwrap()。少了它,被 gzip 中间件包住的请求调 // http.NewResponseController(...).SetWriteDeadline/Flush 只会拿到 ErrNotSupported。 func TestGzipWriterUnwrapsToUnderlyingWriter(t *testing.T) { rec := &deadlineRecorder{ResponseRecorder: httptest.NewRecorder()} gw := &gzipWriter{ResponseWriter: rec} if err := http.NewResponseController(gw).SetWriteDeadline(time.Now().Add(time.Minute)); err != nil { t.Fatalf("ResponseController 没能穿过 gzipWriter: %v", err) } if !rec.got { t.Error("SetWriteDeadline 没有落到真正的 ResponseWriter 上") } } // P4 回归:导出是「一次性大响应」,得带上 Content-Length(下载进度条、 // 也顺带锁住「不再依赖隐式 200」),并把写死线放宽到全站 120s 之外。 func TestExportSetsContentLengthAndExtendsWriteDeadline(t *testing.T) { f := newMovementFixture(t) rec := &deadlineRecorder{ResponseRecorder: httptest.NewRecorder()} req := httptest.NewRequest("GET", "/admin/export/rondbackup", nil).WithContext(f.ctx) f.srv.exportRondbackup(rec, req) if rec.Code != http.StatusOK { t.Fatalf("导出返回 %d:%s", rec.Code, firstLine(rec.Body.String())) } if cl := rec.Header().Get("Content-Length"); cl != strconv.Itoa(rec.Body.Len()) { t.Errorf("Content-Length=%q,实际写出 %d 字节", cl, rec.Body.Len()) } if ct := rec.Header().Get("Content-Type"); ct != "application/octet-stream" { t.Errorf("Content-Type 不对: %q", ct) } if !rec.got { t.Error("导出没有延长写死线(慢链路下会被全站 120s 截断)") } }