1import type * as BunFileSystem from "@effect/platform-bun/BunFileSystem"
2import * as BunHttpPlatform from "@effect/platform-bun/BunHttpPlatform"
3import { assert, describe, it } from "@effect/vitest"
4import * as ByteSize from "effect/ByteSize"
5import * as Effect from "effect/Effect"
6import type * as HttpBody from "effect/http/HttpBody"
7import * as HttpPlatform from "effect/http/HttpPlatform"
8import * as HttpServerResponse from "effect/http/HttpServerResponse"
9import { afterEach, beforeEach, vi } from "vitest"
10
11const readBody = (body: HttpBody.HttpBody) => {
12 assert.strictEqual(body._tag, "Raw")
13 return Effect.promise(() => new Response((body as HttpBody.Raw).body as BodyInit).text())
14}
15
16// Bun sets Content-Length when sending the raw file, so inspect the HTTP response.
17const readResponse = (response: HttpServerResponse.HttpServerResponse) =>
18 Effect.acquireUseRelease(
19 Effect.sync(() =>
20 Bun.serve({
21 hostname: "127.0.0.1",
22 port: 0,
23 fetch: () => HttpServerResponse.toWeb(response)
24 })
25 ),
26 (server) =>
27 Effect.promise(async (signal) => {
28 const response = await fetch(server.url, { signal })
29 return { contentLength: response.headers.get("content-length"), body: await response.text() }
30 }),
31 (server) =>
32 Effect.promise(async () => {
33 await server.stop(true)
34 })
35 )
36
37describe("BunHttpPlatform", { concurrent: false }, () => {
38 for (
39 const { name, offset, bytesToRead, expected } of [
40 { name: "clamps bytesToRead beyond EOF", offset: 1, bytesToRead: 10, expected: "bcd" },
41 { name: "returns an empty body at EOF", offset: 4, bytesToRead: undefined, expected: "" },
42 { name: "returns an empty body past EOF", offset: 9, bytesToRead: undefined, expected: "" },
43 { name: "clamps bytesToRead at EOF", offset: 4, bytesToRead: 10, expected: "" },
44 { name: "clamps bytesToRead past EOF", offset: 9, bytesToRead: 10, expected: "" }
45 ]
46 ) {
47 it.effect(`fileWebResponse ${name}`, () =>
48 Effect.gen(function*() {
49 const platform = yield* HttpPlatform.HttpPlatform
50 const file = new File(["abcd"], "file.txt", { type: "text/plain", lastModified: 0 })
51 const response = yield* platform.fileWebResponse(file, { offset, bytesToRead })
52 assert.deepStrictEqual(
53 yield* readResponse(response),
54 { contentLength: String(expected.length), body: expected }
55 )
56 }).pipe(Effect.provide(BunHttpPlatform.layer)))
57 }
58
59 for (
60 const { name, offsetFromEnd, bytesToRead } of [
61 { name: "clamps bytesToRead beyond EOF", offsetFromEnd: -5, bytesToRead: 100 },
62 { name: "returns an empty body at EOF", offsetFromEnd: 0, bytesToRead: undefined },
63 { name: "returns an empty body past EOF", offsetFromEnd: 5, bytesToRead: undefined },
64 { name: "clamps bytesToRead at EOF", offsetFromEnd: 0, bytesToRead: 100 },
65 { name: "clamps bytesToRead past EOF", offsetFromEnd: 5, bytesToRead: 100 }
66 ]
67 ) {
68 it.effect(`fileResponse ${name}`, () =>
69 Effect.gen(function*() {
70 const platform = yield* HttpPlatform.HttpPlatform
71 const file = Bun.file(import.meta.filename)
72 const contents = yield* Effect.promise(() => file.text())
73 const expected = offsetFromEnd < 0 ? contents.slice(offsetFromEnd) : ""
74 const response = yield* platform.fileResponse(import.meta.filename, {
75 offset: ByteSize.bytes(file.size + offsetFromEnd),
76 bytesToRead: bytesToRead === undefined ? undefined : ByteSize.bytes(bytesToRead)
77 })
78 assert.deepStrictEqual(
79 yield* readResponse(response),
80 { contentLength: String(expected.length), body: expected }
81 )
82 }).pipe(Effect.provide(BunHttpPlatform.layer)))
83 }
84
85 it.effect("fileWebResponse honors offset and bytesToRead including zero", () =>
86 Effect.gen(function*() {
87 const platform = yield* HttpPlatform.HttpPlatform
88 const file = new File(["abcd"], "file.txt", { type: "text/plain", lastModified: 0 })
89 const sliced = yield* platform.fileWebResponse(file, { offset: 1, bytesToRead: 2 })
90 const empty = yield* platform.fileWebResponse(file, { offset: 1, bytesToRead: 0 })
91
92 assert.deepStrictEqual(
93 { sliced: yield* readBody(sliced.body), empty: yield* readBody(empty.body) },
94 { sliced: "bc", empty: "" }
95 )
96 }).pipe(Effect.provide(BunHttpPlatform.layer)))
97
98 it.effect("fileResponse supports exact and empty ranges", () =>
99 Effect.gen(function*() {
100 const platform = yield* HttpPlatform.HttpPlatform
101 const exact = yield* platform.fileResponse(import.meta.filename, {
102 bytesToRead: ByteSize.bytes(6)
103 })
104 const empty = yield* platform.fileResponse(import.meta.filename, {
105 offset: ByteSize.bytes(1),
106 bytesToRead: ByteSize.zero
107 })
108
109 assert.deepStrictEqual(
110 { exact: yield* readBody(exact.body), empty: yield* readBody(empty.body) },
111 { exact: "import", empty: "" }
112 )
113 }).pipe(Effect.provide(BunHttpPlatform.layer)))
114})
115
116const precision = vi.hoisted(() => ({ enabled: false }))
117
118vi.mock("@effect/platform-bun/BunFileSystem", async (importOriginal) => {
119 const original = await importOriginal<typeof BunFileSystem>()
120 const { Layer, Effect } = await import("effect")
121 const { fileSystemLayer } = await import("../../node-shared/test/HttpPlatform.test-utils.ts")
122 return { layer: Layer.unwrap(Effect.sync(() => precision.enabled ? fileSystemLayer : original.layer)) }
123})
124
125describe("BunHttpPlatform precision", { concurrent: false }, () => {
126 const slice = vi.fn((_start?: number, _end?: number) => new Blob([]))
127
128 beforeEach(() => {
129 precision.enabled = true
130 slice.mockClear()
131 vi.spyOn(Bun, "file").mockReturnValue({ slice } as unknown as ReturnType<typeof Bun.file>)
132 })
133 afterEach(() => {
134 precision.enabled = false
135 vi.restoreAllMocks()
136 })
137
138 it.effect("passes a safe range to the runtime with the correct end bound", () =>
139 Effect.gen(function*() {
140 const maxSafe = Number.MAX_SAFE_INTEGER
141 const platform = yield* HttpPlatform.HttpPlatform
142 yield* platform.fileResponse("precision.bin", { offset: maxSafe - 1, bytesToRead: 1 })
143 assert.deepStrictEqual(slice.mock.calls, [[maxSafe - 1, maxSafe]])
144 }).pipe(Effect.provide(BunHttpPlatform.layer)))
145
146 it.effect("serves a whole oversized file without slicing it", () =>
147 Effect.gen(function*() {
148 const platform = yield* HttpPlatform.HttpPlatform
149 const response = yield* platform.fileResponse("precision.bin")
150 assert.strictEqual(response.status, 200)
151 assert.strictEqual(response.headers["content-length"], "9007199254740993")
152 assert.strictEqual(response.body._tag, "Raw")
153 if (response.body._tag === "Raw") {
154 assert.strictEqual(response.body.body, vi.mocked(Bun.file).mock.results[0].value)
155 }
156 assert.strictEqual(vi.mocked(Bun.file).mock.calls.length, 1)
157 assert.deepStrictEqual(slice.mock.calls, [])
158 }).pipe(Effect.provide(BunHttpPlatform.layer)))
159})