|
1 | 1 | // Verifies provider HTTP error parsing, redaction, and response-size limits. |
2 | | -import { describe, expect, it } from "vitest"; |
| 2 | +import { describe, expect, it, vi } from "vitest"; |
3 | 3 | import { |
4 | 4 | assertOkOrThrowProviderError, |
5 | 5 | assertOkOrThrowHttpError, |
@@ -9,6 +9,7 @@ import {
|
9 | 9 | ProviderHttpError, |
10 | 10 | readProviderBinaryResponse, |
11 | 11 | readProviderJsonResponse, |
| 12 | +readResponseTextLimited, |
12 | 13 | } from "./provider-http-errors.js"; |
13 | 14 | |
14 | 15 | function createStreamingBinaryResponse(params: { |
@@ -108,6 +109,46 @@ describe("provider error utils", () => {
|
108 | 109 | } satisfies Partial<ProviderHttpError>); |
109 | 110 | }); |
110 | 111 | |
| 112 | +it("releases provider error body reader locks after bounded reads complete", async () => { |
| 113 | +const releaseLock = vi.fn(); |
| 114 | +const cancel = vi.fn(async () => undefined); |
| 115 | +const chunks: Array<ReadableStreamReadResult<Uint8Array>> = [ |
| 116 | +{ done: false, value: new TextEncoder().encode("provider error") }, |
| 117 | +{ done: true, value: undefined }, |
| 118 | +]; |
| 119 | +const response = { |
| 120 | +body: { |
| 121 | +getReader: () => ({ |
| 122 | +read: async () => chunks.shift() ?? { done: true, value: undefined }, |
| 123 | + cancel, |
| 124 | + releaseLock, |
| 125 | +}), |
| 126 | +}, |
| 127 | +} as unknown as Response; |
| 128 | + |
| 129 | +await expect(readResponseTextLimited(response, 64)).resolves.toBe("provider error"); |
| 130 | +expect(cancel).not.toHaveBeenCalled(); |
| 131 | +expect(releaseLock).toHaveBeenCalledTimes(1); |
| 132 | +}); |
| 133 | + |
| 134 | +it("cancels and releases provider error body readers after diagnostic truncation", async () => { |
| 135 | +const releaseLock = vi.fn(); |
| 136 | +const cancel = vi.fn(async () => undefined); |
| 137 | +const response = { |
| 138 | +body: { |
| 139 | +getReader: () => ({ |
| 140 | +read: async () => ({ done: false, value: new TextEncoder().encode("provider error") }), |
| 141 | + cancel, |
| 142 | + releaseLock, |
| 143 | +}), |
| 144 | +}, |
| 145 | +} as unknown as Response; |
| 146 | + |
| 147 | +await expect(readResponseTextLimited(response, 8)).resolves.toBe("provider"); |
| 148 | +expect(cancel).toHaveBeenCalledTimes(1); |
| 149 | +expect(releaseLock).toHaveBeenCalledTimes(1); |
| 150 | +}); |
| 151 | + |
111 | 152 | it("attaches structured provider error metadata", async () => { |
112 | 153 | // API-key-like substrings must be redacted from stored error bodies. |
113 | 154 | const response = new Response( |
|