1import * as DenoFileSystem from "@effect/platform-deno/DenoFileSystem"
2import * as DenoHttpPlatform from "@effect/platform-deno/DenoHttpPlatform"
3import { assert, describe, it } from "@effect/vitest"
4import * as ByteSize from "effect/ByteSize"
5import * as Cause from "effect/Cause"
6import * as Effect from "effect/Effect"
7import * as FileSystem from "effect/FileSystem"
8import * as Etag from "effect/http/Etag"
9import type * as HttpBody from "effect/http/HttpBody"
10import * as HttpPlatform from "effect/http/HttpPlatform"
11
12const fixture = `${import.meta.dirname}/fixtures/text.txt`
13
14const readStream = (stream: ReadableStream<Uint8Array>) => Effect.promise(() => new globalThis.Response(stream).text())
15
16const readBody = (body: HttpBody.HttpBody) => {
17 assert.strictEqual(body._tag, "Raw")
18 return Effect.promise(() => new Response((body as HttpBody.Raw).body as BodyInit).text())
19}
20
21describe("DenoHttpPlatform", () => {
22 it.effect("fileWebResponse preserves the requested content type", () =>
23 Effect.gen(function*() {
24 const platform = yield* HttpPlatform.HttpPlatform
25 const response = yield* platform.fileWebResponse(new File([], "script.js", { type: "text/plain" }), {
26 contentType: "text/javascript"
27 })
28 assert.strictEqual(response.headers["content-type"], "text/javascript")
29 }).pipe(Effect.provide(DenoHttpPlatform.layer)))
30
31 for (const [name, expected] of [["image.png", "image/png"], ["file", "application/octet-stream"]]) {
32 it.effect(`fileWebResponse resolves an empty File.type for ${name}`, () =>
33 Effect.gen(function*() {
34 const platform = yield* HttpPlatform.HttpPlatform
35 const response = yield* platform.fileWebResponse(new File([], name))
36 assert.strictEqual(response.headers["content-type"], expected)
37 }).pipe(Effect.provide(DenoHttpPlatform.layer)))
38 }
39
40 for (
41 const { name, offset, bytesToRead, expected } of [
42 { name: "clamps bytesToRead beyond EOF", offset: 1, bytesToRead: 10, expected: "bcd" },
43 { name: "returns an empty body at EOF", offset: 4, bytesToRead: undefined, expected: "" },
44 { name: "returns an empty body past EOF", offset: 9, bytesToRead: undefined, expected: "" },
45 { name: "clamps bytesToRead at EOF", offset: 4, bytesToRead: 10, expected: "" },
46 { name: "clamps bytesToRead past EOF", offset: 9, bytesToRead: 10, expected: "" }
47 ]
48 ) {
49 it.effect(`fileWebResponse ${name}`, () =>
50 Effect.gen(function*() {
51 const platform = yield* HttpPlatform.HttpPlatform
52 const file = new File(["abcd"], "file.txt", { type: "text/plain", lastModified: 0 })
53 const response = yield* platform.fileWebResponse(file, { offset, bytesToRead })
54 const text = yield* readBody(response.body)
55
56 assert.deepStrictEqual(
57 { contentLength: response.headers["content-length"], body: text },
58 { contentLength: String(expected.length), body: expected }
59 )
60 }).pipe(Effect.provide(DenoHttpPlatform.layer)))
61 }
62
63 it.effect("fileResponse preserves open failures as defects after stat succeeds", () =>
64 Effect.gen(function*() {
65 const fs = yield* FileSystem.FileSystem
66 const directory = yield* fs.makeTempDirectoryScoped()
67 const info = yield* fs.stat(fixture)
68 const platform = yield* DenoHttpPlatform.make.pipe(
69 Effect.provideService(
70 FileSystem.FileSystem,
71 FileSystem.makeNoop({
72 stat: () => Effect.succeed(info)
73 })
74 ),
75 Effect.provide(Etag.layer)
76 )
77
78 const exit = yield* Effect.exit(platform.fileResponse(`${directory}/missing.txt`))
79 assert.strictEqual(exit._tag, "Failure")
80 if (exit._tag === "Failure") {
81 assert.isTrue(Cause.hasDies(exit.cause))
82 assert.isFalse(Cause.hasFails(exit.cause))
83 }
84 }).pipe(Effect.scoped, Effect.provide(DenoFileSystem.layer)))
85
86 it.effect("fileWebResponse honors offset and bytesToRead including zero", () =>
87 Effect.gen(function*() {
88 const platform = yield* HttpPlatform.HttpPlatform
89 const file = new File(["abcd"], "file.txt", { type: "text/plain", lastModified: 0 })
90 const sliced = yield* platform.fileWebResponse(file, { offset: 1, bytesToRead: 2 })
91 const empty = yield* platform.fileWebResponse(file, { offset: 1, bytesToRead: 0 })
92
93 assert.deepStrictEqual(
94 {
95 slicedLength: sliced.headers["content-length"],
96 slicedBody: yield* readBody(sliced.body),
97 emptyLength: empty.headers["content-length"],
98 emptyBody: yield* readBody(empty.body)
99 },
100 { slicedLength: "2", slicedBody: "bc", emptyLength: "0", emptyBody: "" }
101 )
102 }).pipe(Effect.provide(DenoHttpPlatform.layer)))
103
104 it.effect("fileResponse reads exact bytesToRead", () =>
105 Effect.gen(function*() {
106 const platform = yield* HttpPlatform.HttpPlatform
107 const response = yield* platform.fileResponse(fixture, {
108 offset: ByteSize.bytes(6),
109 bytesToRead: ByteSize.bytes(5)
110 })
111
112 assert.strictEqual(response.headers["content-length"], "5")
113 assert.strictEqual(response.body._tag, "Raw")
114 const body = (response.body as HttpBody.Raw).body
115 assert(body instanceof ReadableStream)
116
117 const text = yield* readStream(body)
118 assert.strictEqual(text, "ipsum")
119 }).pipe(Effect.provide(DenoHttpPlatform.layer)))
120
121 for (
122 const { name, offset, bytesToRead, expected } of [
123 { name: "clamps bytesToRead beyond EOF", offset: 22, bytesToRead: 100, expected: "amet\n" },
124 { name: "returns an empty body at EOF", offset: 27, bytesToRead: undefined, expected: "" },
125 { name: "returns an empty body past EOF", offset: 50, bytesToRead: undefined, expected: "" },
126 { name: "clamps bytesToRead at EOF", offset: 27, bytesToRead: 100, expected: "" },
127 { name: "clamps bytesToRead past EOF", offset: 50, bytesToRead: 100, expected: "" }
128 ]
129 ) {
130 it.effect(`fileResponse ${name}`, () =>
131 Effect.gen(function*() {
132 const platform = yield* HttpPlatform.HttpPlatform
133 const response = yield* platform.fileResponse(fixture, {
134 offset: ByteSize.bytes(offset),
135 bytesToRead: bytesToRead === undefined ? undefined : ByteSize.bytes(bytesToRead)
136 })
137
138 assert.strictEqual(response.body._tag, "Raw")
139 const text = yield* readStream((response.body as HttpBody.Raw).body as ReadableStream<Uint8Array>)
140 assert.deepStrictEqual(
141 { contentLength: response.headers["content-length"], body: text },
142 { contentLength: String(expected.length), body: expected }
143 )
144 }).pipe(Effect.provide(DenoHttpPlatform.layer)))
145 }
146
147 it.effect("fileResponse supports zero bytesToRead", () =>
148 Effect.gen(function*() {
149 const platform = yield* HttpPlatform.HttpPlatform
150 const response = yield* platform.fileResponse(fixture, {
151 offset: ByteSize.bytes(6),
152 bytesToRead: ByteSize.zero
153 })
154
155 assert.strictEqual(response.headers["content-length"], "0")
156 assert.strictEqual(response.body._tag, "Raw")
157 const body = (response.body as HttpBody.Raw).body
158 assert(body instanceof ReadableStream)
159
160 const text = yield* readStream(body)
161 assert.strictEqual(text, "")
162 }).pipe(Effect.provide(DenoHttpPlatform.layer)))
163})