




























@@ -11,6 +11,28 @@ vi.mock("../../../api.js", () => ({
11111212import { guardedJsonApiRequest } from "./guarded-json-api.js";
131314+function cancelTrackedTextResponse(
15+text: string,
16+init?: ResponseInit,
17+): {
18+response: Response;
19+wasCanceled: () => boolean;
20+} {
21+let canceled = false;
22+const stream = new ReadableStream<Uint8Array>({
23+start(controller) {
24+controller.enqueue(new TextEncoder().encode(text));
25+},
26+cancel() {
27+canceled = true;
28+},
29+});
30+return {
31+response: new Response(stream, init),
32+wasCanceled: () => canceled,
33+};
34+}
35+1436describe("guardedJsonApiRequest", () => {
1537beforeEach(() => {
1638vi.clearAllMocks();
@@ -66,8 +88,9 @@ describe("guardedJsonApiRequest", () => {
6688}),
6789).resolves.toBeUndefined();
689091+const missing = cancelTrackedTextResponse("missing", { status: 404 });
6992fetchWithSsrFGuardMock.mockResolvedValueOnce({
70-response: new Response("missing", { status: 404 }),
93+response: missing.response,
7194 release,
7295});
7396@@ -82,6 +105,9 @@ describe("guardedJsonApiRequest", () => {
82105errorPrefix: "request failed",
83106}),
84107).resolves.toBeUndefined();
108+109+expect(missing.wasCanceled()).toBe(true);
110+expect(release).toHaveBeenCalledTimes(2);
85111});
8611287113it("throws prefixed errors and still releases the response handle", async () => {
@@ -105,6 +131,35 @@ describe("guardedJsonApiRequest", () => {
105131expect(release).toHaveBeenCalledTimes(1);
106132});
107133134+it("bounds provider error bodies and cancels unread overflow", async () => {
135+const release = vi.fn(async () => {});
136+const tracked = cancelTrackedTextResponse("x".repeat(9 * 1024), { status: 500 });
137+fetchWithSsrFGuardMock.mockResolvedValue({
138+response: tracked.response,
139+ release,
140+});
141+142+let caught: Error | undefined;
143+try {
144+await guardedJsonApiRequest({
145+url: "https://api.example.com/v1/calls/3",
146+method: "DELETE",
147+headers: {},
148+allowedHostnames: ["api.example.com"],
149+auditContext: "voice-call:test",
150+errorPrefix: "provider error",
151+});
152+} catch (error) {
153+caught = error as Error;
154+}
155+156+expect(caught?.message).toContain("provider error: 500 ");
157+expect(caught?.message).toContain("... [truncated]");
158+expect(caught?.message.length).toBeLessThan(8_300);
159+expect(tracked.wasCanceled()).toBe(true);
160+expect(release).toHaveBeenCalledTimes(1);
161+});
162+108163it("throws prefixed errors for malformed json success responses", async () => {
109164const release = vi.fn(async () => {});
110165fetchWithSsrFGuardMock.mockResolvedValue({
@@ -125,4 +180,27 @@ describe("guardedJsonApiRequest", () => {
125180126181expect(release).toHaveBeenCalledTimes(1);
127182});
183+184+it("rejects oversized json success bodies and cancels unread overflow", async () => {
185+const release = vi.fn(async () => {});
186+const tracked = cancelTrackedTextResponse("x".repeat(1024 * 1024 + 1), { status: 200 });
187+fetchWithSsrFGuardMock.mockResolvedValue({
188+response: tracked.response,
189+ release,
190+});
191+192+await expect(
193+guardedJsonApiRequest({
194+url: "https://api.example.com/v1/calls/5",
195+method: "GET",
196+headers: {},
197+allowedHostnames: ["api.example.com"],
198+auditContext: "voice-call:test",
199+errorPrefix: "provider error",
200+}),
201+).rejects.toThrow("provider response body too large: 1048577 bytes (limit: 1048576 bytes)");
202+203+expect(tracked.wasCanceled()).toBe(true);
204+expect(release).toHaveBeenCalledTimes(1);
205+});
128206});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。