























@@ -101,6 +101,28 @@ function stubFetchOk(): ReturnType<typeof vi.fn> {
101101return fetchWithSsrFGuardMock;
102102}
103103104+function cancelTrackedResponse(
105+text: string,
106+init: ResponseInit,
107+): {
108+response: Response;
109+wasCanceled: () => boolean;
110+} {
111+let canceled = false;
112+const stream = new ReadableStream<Uint8Array>({
113+start(controller) {
114+controller.enqueue(new TextEncoder().encode(text));
115+},
116+cancel() {
117+canceled = true;
118+},
119+});
120+return {
121+response: new Response(stream, init),
122+wasCanceled: () => canceled,
123+};
124+}
125+104126// ============ Tests ============
105127106128describe("media-chunked: UploadDailyLimitExceededError", () => {
@@ -259,6 +281,75 @@ describe("media-chunked: ChunkedMediaApi.uploadChunked", () => {
259281expect(last.totalBytes).toBe(FIXTURE_BUFFER.length);
260282});
261283284+it("bounds COS PUT error bodies without using response.text()", async () => {
285+const client = mockApiClient();
286+const tm = mockTokenManager();
287+const logger = { info: vi.fn(), error: vi.fn(), warn: vi.fn() };
288+client.request.mockImplementation(async (_token, _method, pathLocal) => {
289+if (pathLocal.endsWith("/upload_prepare")) {
290+return makePrepareResponse("uid-bounded", 1);
291+}
292+throw new Error(`unexpected path ${pathLocal}`);
293+});
294+295+const releases = [vi.fn(async () => {}), vi.fn(async () => {}), vi.fn(async () => {})];
296+const trackedResponses = releases.map((release) => {
297+const tracked = cancelTrackedResponse(`${"cos gateway unavailable ".repeat(1024)}tail`, {
298+status: 503,
299+statusText: "Service Unavailable",
300+headers: {
301+"content-type": "text/plain",
302+"x-cos-request-id": "req-bounded",
303+},
304+});
305+const textSpy = vi.spyOn(tracked.response, "text").mockRejectedValue(new Error("unbounded"));
306+return {
307+response: tracked.response,
308+wasCanceled: tracked.wasCanceled,
309+ release,
310+ textSpy,
311+};
312+});
313+const pendingResponses = [...trackedResponses];
314+315+fetchWithSsrFGuardMock.mockImplementation(async () => {
316+const next = pendingResponses.shift();
317+if (!next) {
318+throw new Error("unexpected extra COS PUT attempt");
319+}
320+return {
321+response: next.response,
322+release: next.release,
323+};
324+});
325+326+const api = new ChunkedMediaApi(client, tm, { logger });
327+let error: unknown;
328+try {
329+await api.uploadChunked({
330+scope: "group",
331+targetId: "g1",
332+fileType: MediaFileType.FILE,
333+source: { kind: "buffer", buffer: Buffer.from("01234567"), fileName: "blob.bin" },
334+creds: { appId: "a", clientSecret: "s" },
335+});
336+} catch (caught) {
337+error = caught;
338+}
339+340+expect(String(error)).toContain("COS PUT failed: 503 Service Unavailable");
341+expect(String(error)).toContain("cos gateway unavailable");
342+expect(String(error)).not.toContain("tail");
343+expect(fetchWithSsrFGuardMock).toHaveBeenCalledTimes(3);
344+for (const tracked of trackedResponses) {
345+expect(tracked.wasCanceled()).toBe(true);
346+expect(tracked.textSpy).not.toHaveBeenCalled();
347+expect(tracked.release).toHaveBeenCalledTimes(1);
348+}
349+expect(JSON.stringify(logger.error.mock.calls)).toContain("cos gateway unavailable");
350+expect(JSON.stringify(logger.error.mock.calls)).not.toContain("tail");
351+});
352+262353it("maps UPLOAD_PREPARE_FALLBACK_CODE to UploadDailyLimitExceededError", async () => {
263354const client = mockApiClient();
264355const tm = mockTokenManager();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。