|
1 | 1 | import assert from "assert"; |
2 | 2 | import { ReadableStream, ReadableStreamBYOBReadResult } from "stream/web"; |
3 | | -import { ArrayBufferViewConstructor } from "@miniflare/core"; |
4 | | -import test from "ava"; |
5 | | - |
6 | | -import "@miniflare/core"; |
| 3 | +import { |
| 4 | + ArrayBufferViewConstructor, |
| 5 | + FixedLengthStream, |
| 6 | + Request, |
| 7 | + Response, |
| 8 | +} from "@miniflare/core"; |
| 9 | +import { utf8Encode } from "@miniflare/shared-test"; |
| 10 | +import test, { ThrowsExpectation } from "ava"; |
7 | 11 |
|
8 | 12 | function chunkedStream(chunks: number[][]): ReadableStream<Uint8Array> { |
9 | 13 | return new ReadableStream({ |
@@ -176,3 +180,142 @@ test("ReadableStreamBYOBReader: readAtLeast: throws if minimum number of bytes e |
176 | 180 | message: "Minimum bytes to read (4) exceeds size of buffer (3).", |
177 | 181 | }); |
178 | 182 | }); |
| 183 | + |
| 184 | +test("FixedLengthStream: requires non-negative integer expected length", (t) => { |
| 185 | + const expectations: ThrowsExpectation = { |
| 186 | + instanceOf: TypeError, |
| 187 | + message: |
| 188 | + "FixedLengthStream requires a non-negative integer expected length.", |
| 189 | + }; |
| 190 | + // @ts-expect-error intentionally testing incorrect types |
| 191 | + t.throws(() => new FixedLengthStream(), expectations); |
| 192 | + t.throws(() => new FixedLengthStream(-42), expectations); |
| 193 | + new FixedLengthStream(0); |
| 194 | +}); |
| 195 | +test("FixedLengthStream: throws if too many bytes written", async (t) => { |
| 196 | + const { readable, writable } = new FixedLengthStream(3); |
| 197 | + const writer = writable.getWriter(); |
| 198 | + // noinspection ES6MissingAwait |
| 199 | + void writer.write(new Uint8Array([1, 2])); |
| 200 | + // noinspection ES6MissingAwait |
| 201 | + void writer.write(new Uint8Array([3, 4])); |
| 202 | + |
| 203 | + const reader = readable.getReader(); |
| 204 | + t.deepEqual((await reader.read()).value, new Uint8Array([1, 2])); |
| 205 | + await t.throwsAsync(reader.read(), { |
| 206 | + instanceOf: TypeError, |
| 207 | + message: "Attempt to write too many bytes through a FixedLengthStream.", |
| 208 | + }); |
| 209 | +}); |
| 210 | +test("FixedLengthStream: throws if too few bytes written", async (t) => { |
| 211 | + const { readable, writable } = new FixedLengthStream(3); |
| 212 | + const writer = writable.getWriter(); |
| 213 | + // noinspection ES6MissingAwait |
| 214 | + void writer.write(new Uint8Array([1, 2])); |
| 215 | + // noinspection ES6MissingAwait |
| 216 | + const closePromise = writer.close(); |
| 217 | + |
| 218 | + const reader = readable.getReader(); |
| 219 | + t.deepEqual((await reader.read()).value, new Uint8Array([1, 2])); |
| 220 | + await t.throwsAsync(closePromise, { |
| 221 | + instanceOf: TypeError, |
| 222 | + message: "FixedLengthStream did not see all expected bytes before close().", |
| 223 | + }); |
| 224 | +}); |
| 225 | +test("FixedLengthStream: behaves as identity transform if just right number of bytes written", async (t) => { |
| 226 | + const { readable, writable } = new FixedLengthStream(3); |
| 227 | + const writer = writable.getWriter(); |
| 228 | + // noinspection ES6MissingAwait |
| 229 | + void writer.write(new Uint8Array([1, 2])); |
| 230 | + // noinspection ES6MissingAwait |
| 231 | + void writer.write(new Uint8Array([3])); |
| 232 | + // noinspection ES6MissingAwait |
| 233 | + void writer.close(); |
| 234 | + |
| 235 | + const reader = readable.getReader(); |
| 236 | + t.deepEqual((await reader.read()).value, new Uint8Array([1, 2])); |
| 237 | + t.deepEqual((await reader.read()).value, new Uint8Array([3])); |
| 238 | + t.true((await reader.read()).done); |
| 239 | +}); |
| 240 | +test("FixedLengthStream: throws on string chunks", async (t) => { |
| 241 | + const { readable, writable } = new FixedLengthStream(5); |
| 242 | + const writer = writable.getWriter(); |
| 243 | + // noinspection ES6MissingAwait |
| 244 | + void writer.write( |
| 245 | + // @ts-expect-error intentionally testing incorrect types |
| 246 | + "how much chunk would a chunk-chuck chuck if a chunk-chuck could chuck chunk?" |
| 247 | + ); |
| 248 | + |
| 249 | + const reader = readable.getReader(); |
| 250 | + await t.throwsAsync(reader.read(), { |
| 251 | + instanceOf: TypeError, |
| 252 | + message: |
| 253 | + "This TransformStream is being used as a byte stream, " + |
| 254 | + "but received a string on its writable side. " + |
| 255 | + "If you wish to write a string, you'll probably want to " + |
| 256 | + "explicitly UTF-8-encode it with TextEncoder.", |
| 257 | + }); |
| 258 | +}); |
| 259 | +test("FixedLengthStream: throws on non-ArrayBuffer/ArrayBufferView chunks", async (t) => { |
| 260 | + const { readable, writable } = new FixedLengthStream(5); |
| 261 | + const writer = writable.getWriter(); |
| 262 | + // @ts-expect-error intentionally testing incorrect types |
| 263 | + // noinspection ES6MissingAwait |
| 264 | + void writer.write(42); |
| 265 | + |
| 266 | + const reader = readable.getReader(); |
| 267 | + await t.throwsAsync(reader.read(), { |
| 268 | + instanceOf: TypeError, |
| 269 | + message: |
| 270 | + "This TransformStream is being used as a byte stream, " + |
| 271 | + "but received an object of non-ArrayBuffer/ArrayBufferView " + |
| 272 | + "type on its writable side.", |
| 273 | + }); |
| 274 | +}); |
| 275 | +function buildFixedLengthReadableStream(length: number) { |
| 276 | + const { readable, writable } = new FixedLengthStream(length); |
| 277 | + const writer = writable.getWriter(); |
| 278 | + if (length > 0) void writer.write(utf8Encode("".padStart(length, "x"))); |
| 279 | + void writer.close(); |
| 280 | + return readable; |
| 281 | +} |
| 282 | +test("FixedLengthStream: sets Content-Length header on Request", async (t) => { |
| 283 | + let body = buildFixedLengthReadableStream(3); |
| 284 | + let req = new Request("http://localhost", { method: "POST", body }); |
| 285 | + t.is(req.headers.get("Content-Length"), "3"); |
| 286 | + t.is(await req.text(), "xxx"); |
| 287 | + |
| 288 | + // Check overrides existing Content-Length header |
| 289 | + body = buildFixedLengthReadableStream(3); |
| 290 | + req = new Request("http://localhost", { |
| 291 | + method: "POST", |
| 292 | + body, |
| 293 | + headers: { "Content-Length": "2" }, |
| 294 | + }); |
| 295 | + t.is(req.headers.get("Content-Length"), "3"); |
| 296 | + t.is(await req.text(), "xxx"); |
| 297 | + |
| 298 | + // Check still includes header with 0 expected length |
| 299 | + body = buildFixedLengthReadableStream(0); |
| 300 | + req = new Request("http://localhost", { method: "POST", body }); |
| 301 | + t.is(req.headers.get("Content-Length"), "0"); |
| 302 | + t.is(await req.text(), ""); |
| 303 | +}); |
| 304 | +test("FixedLengthStream: sets Content-Length header on Response", async (t) => { |
| 305 | + let body = buildFixedLengthReadableStream(3); |
| 306 | + let res = new Response(body); |
| 307 | + t.is(res.headers.get("Content-Length"), "3"); |
| 308 | + t.is(await res.text(), "xxx"); |
| 309 | + |
| 310 | + // Check overrides existing Content-Length header |
| 311 | + body = buildFixedLengthReadableStream(3); |
| 312 | + res = new Response(body, { headers: { "Content-Length": "2" } }); |
| 313 | + t.is(res.headers.get("Content-Length"), "3"); |
| 314 | + t.is(await res.text(), "xxx"); |
| 315 | + |
| 316 | + // Check still includes header with 0 expected length |
| 317 | + body = buildFixedLengthReadableStream(0); |
| 318 | + res = new Response(body); |
| 319 | + t.is(res.headers.get("Content-Length"), "0"); |
| 320 | + t.is(await res.text(), ""); |
| 321 | +}); |
0 commit comments