History

fix: reconcile main's round-18 features after the rebase

Adopts the round's main features into the v2 architecture: the #9023 media rework (streamed videos, media-file assets, protocol-relative links), #9098 shared live-activity row folded into the v2 working and thinking rows, the #9084/#9078 Claude model catalog for v2 consumers, a native #9005 OpenCode child-session abort in the v2 adapter, #9013's landed LegendList patch, and per-environment sidebar provider entries. For #8600 the server-side pieces land, but auto-settle evaluation stays client-side (reading the new server-owned settings) until the v2 orchestrator grows its own settlement reactor; main's v1-only reactor and coalescer additions are dropped with the rest of the v1 path.

Julius Marminge committed 2 months ago

Commit 7b13bca1 parent 786a25847 files · +1855 −2616Browse files

Showing the changes to apps/server/src/http.test.ts (1 of 47 files). Show all

Unified · Split

apps/server/src/http.test.ts+177−3
@@ -1,12 +1,13 @@
11 import { expect, it } from "@effect/vitest";
2+import { describe, vi } from "vite-plus/test";
3+import * as NodeHttpPlatform from "@effect/platform-node/NodeHttpPlatform";
4+import * as NodeServices from "@effect/platform-node/NodeServices";
25 import * as Effect from "effect/Effect";
36 import * as FileSystem from "effect/FileSystem";
47 import * as Layer from "effect/Layer";
58 import * as Path from "effect/Path";
69 import { HttpRouter, HttpServerResponse } from "effect/unstable/http";
7−import { describe } from "vite-plus/test";
8−import * as NodeHttpPlatform from "@effect/platform-node/NodeHttpPlatform";
9−import * as NodeServices from "@effect/platform-node/NodeServices";
10+import { openMediaFile } from "./assets/MediaFile.ts";
1011
1112 import { ORCHESTRATION_PROTOCOL_HEADER } from "@t3tools/contracts";
1213
@@ -70,6 +71,179 @@ describe("browser API CORS", () => { · 57 unmodified lines
7071 const fileResponseLayer = Layer.mergeAll(NodeHttpPlatform.layer, NodeServices.layer);
7172
7273 describe("video asset byte ranges", () => {
74+ it.effect("uses current descriptor metadata after an in-place truncate or extension", () =>
75+ Effect.gen(function* () {
76+ const fs = yield* FileSystem.FileSystem;
77+ const path = yield* Path.Path;
78+ const directory = yield* fs.makeTempDirectoryScoped({ prefix: "t3-guarded-current-stat-" });
79+ const filePath = path.join(directory, "clip.mp4");
80+ for (const [contents, range, method, expected, status, contentRange] of [
81+ ["1234", undefined, "GET", "1234", 200, null],
82+ ["0123456789abcdef", undefined, "GET", "0123456789abcdef", 200, null],
83+ ["1234", "bytes=4-", "GET", "", 416, "bytes */4"],
84+ ["1234", "bytes=1-20", "GET", "234", 206, "bytes 1-3/4"],
85+ ["0123456789abcdef", "bytes=10-", "GET", "abcdef", 206, "bytes 10-15/16"],
86+ ["0123456789abcdef", undefined, "HEAD", "", 200, null],
87+ ["", undefined, "GET", "", 200, null],
88+ ["", "bytes=0-1", "GET", "", 416, "bytes */0"],
89+ ] as const) {
90+ yield* fs.writeFileString(filePath, "0123456789");
91+ const canonicalPath = yield* fs.realPath(filePath);
92+ const file = yield* openMediaFile(canonicalPath);
93+ if (!file) throw new Error("Expected an opened media file");
94+ yield* fs.writeFileString(filePath, contents);
95+ const response = HttpServerResponse.toWeb(
96+ yield* assetFileResponse(
97+ { path: canonicalPath, file, mimeType: "video/mp4" },
98+ range,
99+ undefined,
100+ method,
101+ ),
102+ );
103+ expect(response.status).toBe(status);
104+ expect(response.headers.get("content-range")).toBe(contentRange);
105+ if (status !== 416) {
106+ expect(response.headers.get("content-length")).toBe(
107+ String(method === "HEAD" ? contents.length : expected.length),
108+ );
109+ }
110+ expect(yield* Effect.promise(() => response.text())).toBe(expected);
111+ }
112+ }).pipe(Effect.provide(fileResponseLayer)),
113+ );
114+
115+ it.effect(
116+ "rejects unaddressable ranges before streaming and preserves small ranges on large files",
117+ () =>
118+ Effect.gen(function* () {
119+ const fs = yield* FileSystem.FileSystem;
120+ const path = yield* Path.Path;
121+ const directory = yield* fs.makeTempDirectoryScoped({ prefix: "t3-guarded-offset-limit-" });
122+ const filePath = path.join(directory, "clip.mp4");
123+ yield* fs.writeFileString(filePath, "0123456789");
124+ const canonicalPath = yield* fs.realPath(filePath);
125+ const unsafeOffset = BigInt(Number.MAX_SAFE_INTEGER) + 1n;
126+ const size = unsafeOffset + 32n;
127+ for (const [range, status] of [
128+ [`bytes=${unsafeOffset}-${unsafeOffset}`, 416],
129+ [`bytes=0-${unsafeOffset}`, 416],
130+ ["bytes=-1", 416],
131+ [undefined, 413],
132+ ["bytes=0-1", 206],
133+ ] as const) {
134+ const file = yield* openMediaFile(canonicalPath);
135+ if (!file) throw new Error("Expected an opened media file");
136+ // Model a sparse file beyond the native stream's numeric addressing limit.
137+ const info = yield* Effect.promise(() => file.handle.stat({ bigint: true }));
138+ info.size = size;
139+ const statSpy = vi.spyOn(file.handle, "stat").mockResolvedValue(info);
140+ yield* Effect.addFinalizer(() => Effect.sync(() => statSpy.mockRestore()));
141+ const response = HttpServerResponse.toWeb(
142+ yield* assetFileResponse({ path: canonicalPath, file, mimeType: "video/mp4" }, range),
143+ );
144+ expect(response.status).toBe(status);
145+ if (status === 416) {
146+ expect(response.headers.get("content-range")).toBe(`bytes */${size}`);
147+ expect(yield* Effect.promise(() => response.text())).toBe("");
148+ } else if (status === 206) {
149+ expect(response.headers.get("content-range")).toBe(`bytes 0-1/${size}`);
150+ expect(yield* Effect.promise(() => response.text())).toBe("01");
151+ } else {
152+ expect(yield* Effect.promise(() => response.text())).toBe(
153+ "File is too large to preview.",
154+ );
155+ }
156+ }
157+ }).pipe(Effect.provide(fileResponseLayer)),
158+ );
159+
160+ it.effect("streams guarded file ranges, including suffixes and conditional requests", () =>
161+ Effect.gen(function* () {
162+ const fs = yield* FileSystem.FileSystem;
163+ const path = yield* Path.Path;
164+ const directory = yield* fs.makeTempDirectoryScoped({ prefix: "t3-guarded-range-" });
165+ const filePath = path.join(directory, "clip.mp4");
166+ yield* fs.writeFileString(filePath, "0123456789");
167+ const canonicalPath = yield* fs.realPath(filePath);
168+ for (const [range, ifRange, expected, status, contentRange] of [
169+ [undefined, undefined, "0123456789", 200, null],
170+ ["bytes=0-1", undefined, "01", 206, "bytes 0-1/10"],
171+ ["bytes=4-", undefined, "456789", 206, "bytes 4-9/10"],
172+ ["bytes=-3", undefined, "789", 206, "bytes 7-9/10"],
173+ ["bytes=-999999999999999999999999", undefined, "0123456789", 206, "bytes 0-9/10"],
174+ ["bytes=10-", undefined, "", 416, "bytes */10"],
175+ ["bytes=0-1", '"old-etag"', "0123456789", 200, null],
176+ ["bytes=0-1", "", "0123456789", 200, null],
177+ ] as const) {
178+ const file = yield* openMediaFile(canonicalPath);
179+ if (!file) throw new Error("Expected an opened media file");
180+ const response = HttpServerResponse.toWeb(
181+ yield* assetFileResponse(
182+ { path: canonicalPath, file, mimeType: "video/mp4" },
183+ range,
184+ ifRange,
185+ ),
186+ );
187+ expect(response.status).toBe(status);
188+ expect(response.headers.get("accept-ranges")).toBe("bytes");
189+ expect(response.headers.get("content-range")).toBe(contentRange);
190+ expect(response.headers.get("cache-control")).toBe("private, no-store");
191+ expect(response.headers.get("etag")).toBeNull();
192+ expect(response.headers.get("last-modified")).toBeNull();
193+ if (status !== 416)
194+ expect(response.headers.get("content-length")).toBe(String(expected.length));
195+ expect(yield* Effect.promise(() => response.text())).toBe(expected);
196+ }
197+ }).pipe(Effect.provide(fileResponseLayer)),
198+ );
199+
200+ it.effect("closes guarded descriptors after full, HEAD, rejected, and cancelled responses", () =>
201+ Effect.gen(function* () {
202+ const fs = yield* FileSystem.FileSystem;
203+ const path = yield* Path.Path;
204+ const directory = yield* fs.makeTempDirectoryScoped({ prefix: "t3-guarded-cleanup-" });
205+ const filePath = path.join(directory, "clip.mp4");
206+ const bytes = new Uint8Array(1024 * 1024).fill(42);
207+ yield* fs.writeFile(filePath, bytes);
208+ const canonicalPath = yield* fs.realPath(filePath);
209+ for (const mode of ["full", "HEAD", "rejected", "cancelled"] as const) {
210+ const file = yield* Effect.scoped(
211+ Effect.gen(function* () {
212+ const file = yield* openMediaFile(canonicalPath);
213+ if (!file) throw new Error("Expected an opened media file");
214+ const response = HttpServerResponse.toWeb(
215+ yield* assetFileResponse(
216+ { path: canonicalPath, file, mimeType: "video/mp4" },
217+ mode === "rejected" ? `bytes=${bytes.length}-` : "bytes=0-",
218+ undefined,
219+ mode === "HEAD" ? "HEAD" : "GET",
220+ ),
221+ );
222+ if (mode === "HEAD") {
223+ expect(response.status).toBe(200);
224+ expect(response.headers.get("content-length")).toBe(String(bytes.length));
225+ expect(response.headers.get("content-range")).toBeNull();
226+ expect(yield* Effect.promise(() => response.text())).toBe("");
227+ } else if (mode === "rejected") {
228+ expect(response.status).toBe(416);
229+ expect(yield* Effect.promise(() => response.text())).toBe("");
230+ } else if (mode === "cancelled") {
231+ const reader = response.body!.getReader();
232+ const first = yield* Effect.promise(() => reader.read());
233+ expect(first.done).toBe(false);
234+ expect(first.value!.byteLength).toBeLessThan(bytes.length);
235+ yield* Effect.promise(() => reader.cancel());
236+ } else {
237+ expect(yield* Effect.promise(() => response.arrayBuffer())).toEqual(bytes.buffer);
238+ }
239+ return file;
240+ }),
241+ );
242+ expect(file.handle.fd).toBe(-1);
243+ }
244+ }).pipe(Effect.provide(fileResponseLayer)),
245+ );
246+
73247 it.effect("streams exactly the requested bytes and leaves full downloads intact", () =>
74248 Effect.gen(function* () {
75249 const fs = yield* FileSystem.FileSystem;

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t