Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/firefox-bidi/__tests__/lib.test.ts

Raw
import assert from "node:assert/strict";
import { mkdtemp, readdir, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { test } from "vitest";
import {
	assemblePayload,
	interpolate,
	ledgerUpdate,
	MAX_SPOOL,
	resolveScreencastDestinations,
	runBatch,
	trimById,
} from "../lib.ts";

const res = (id: number, result: unknown) => ({ id, type: "success", result });

test("interpolate resolves primitive refs from earlier responses", () => {
	const byId = new Map([[2, res(2, { context: "abc-1" })]]);
	assert.equal(interpolate("{{2.result.context}}", byId), "abc-1");
});

test("interpolate resolves deep paths inside nested params", () => {
	const byId = new Map([[4, res(4, { screencast: "sc-9" })]]);
	const params = {
		context: "{{2.result.context}}",
		nested: { a: ["{{4.result.screencast}}", "plain"] },
	};
	const out = interpolate(
		params,
		new Map([
			[2, res(2, { context: "ctx-7" })],
			[4, byId.get(4)],
		]),
	);
	assert.deepEqual(out, { context: "ctx-7", nested: { a: ["sc-9", "plain"] } });
});

test("interpolate leaves strings without refs untouched", () => {
	assert.equal(
		interpolate("data:text/html,<h1>hi</h1>", new Map()),
		"data:text/html,<h1>hi</h1>",
	);
});

test("interpolate throws on unknown frame id", () => {
	assert.throws(
		() => interpolate("{{9.result.x}}", new Map()),
		/no successful response/,
	);
});

test("interpolate keeps whole-value primitive types and escapes literal refs", () => {
	const byId = new Map([[3, res(3, { n: 7, yes: false, text: "ok" })]]);
	assert.equal(interpolate("{{3.result.n}}", byId), 7);
	assert.equal(interpolate("{{3.result.yes}}", byId), false);
	assert.equal(interpolate("n={{3.result.n}}", byId), "n=7");
	assert.equal(interpolate("{{{{3.result.text}}}}", byId), "{{3.result.text}}");
	assert.equal(
		interpolate("text={{{{3.result.text}}}}", byId),
		"text={{3.result.text}}",
	);
	assert.equal(
		interpolate("\u00000\u0000 {{{{3.result.text}}}}", byId),
		"\u00000\u0000 {{3.result.text}}",
	);
	assert.throws(
		() => interpolate("{{3.result.constructor.name}}", byId),
		/path ends/,
	);
});

test("whole references preserve objects, arrays and null; embedded ones reject them", () => {
	const byId = new Map([[1, res(1, { tree: { a: [1, null] }, empty: null })]]);
	assert.deepEqual(interpolate("{{1.result.tree}}", byId), { a: [1, null] });
	assert.deepEqual(interpolate("{{1.result.tree.a}}", byId), [1, null]);
	assert.equal(interpolate("{{1.result.empty}}", byId), null);
	assert.throws(
		() => interpolate("prefix {{1.result.tree}}", byId),
		/primitive/,
	);
	assert.throws(
		() => interpolate("prefix {{1.result.empty}}", byId),
		/primitive/,
	);
});

test("resolveScreencastDestinations creates missing dir and injects explicit param", async () => {
	const dir = join(await mkdtemp(join(tmpdir(), "ffx-test-")), "videos");
	const frames = [
		{
			method: "browsingContext.startScreencast",
			params: { context: "c", destinationFolder: dir },
		},
	];
	await resolveScreencastDestinations(frames);
	assert.equal(frames[0].params?.destinationFolder, dir);
	assert.equal((await readdir(dir)).length, 0);
});

test("resolveScreencastDestinations falls back to configured callback when param absent", async () => {
	const dir = join(await mkdtemp(join(tmpdir(), "ffx-test-")), "from-settings");
	const frames = [
		{ method: "browsingContext.startScreencast", params: { context: "c" } },
	];
	await resolveScreencastDestinations(frames, () => dir);
	assert.equal(frames[0].params?.destinationFolder, dir);
	assert.ok(await readdir(dir));
});

test("resolveScreencastDestinations explicit param beats configured", async () => {
	const dir = join(await mkdtemp(join(tmpdir(), "ffx-test-")), "explicit");
	const frames = [
		{
			method: "browsingContext.startScreencast",
			params: { context: "c", destinationFolder: dir },
		},
	];
	await resolveScreencastDestinations(frames, () => "/from-settings");
	assert.equal(frames[0].params?.destinationFolder, dir);
});

test("resolveScreencastDestinations leaves params untouched when nothing configured", async () => {
	const frames = [
		{ method: "browsingContext.startScreencast", params: { context: "c" } },
	];
	await resolveScreencastDestinations(frames);
	assert.equal(frames[0].params?.destinationFolder, undefined);
});

test("resolveScreencastDestinations ignores other methods", async () => {
	const frames = [{ method: "browsingContext.navigate", params: {} }];
	await resolveScreencastDestinations(frames);
	assert.deepEqual(frames[0].params, {});
});

interface Ctx {
	pending: Map<
		number,
		(r: { id: number; type?: string; result?: unknown }) => void
	>;
	ledger: {
		subscriptions: string[];
		intercepts: string[];
		preloadScripts: string[];
	};
	spoolDir: string;
	spoolBudget?: { bytes: number };
}

async function ctx(): Promise<Ctx> {
	return {
		pending: new Map(),
		ledger: { subscriptions: [], intercepts: [], preloadScripts: [] },
		spoolDir: await mkdtemp(join(tmpdir(), "ffx-rb-")),
	};
}

test("runBatch resolves frames in order and tracks ledger + byId", async () => {
	const c = await ctx();
	const replies = new Map<number, unknown>([
		[1, {}],
		[2, { intercept: "int-1" }],
	]);
	const pending = new Map<number, (r: any) => void>();
	const ws = {
		send: (s: string) => {
			const f = JSON.parse(s);
			queueMicrotask(() =>
				pending.get(f.id)?.({
					id: f.id,
					type: "success",
					result: replies.get(f.id),
				}),
			);
		},
	};
	const b = { pending, ledger: c.ledger, spoolDir: c.spoolDir };
	const byId = new Map();
	const frames = [
		{ id: 1, method: "session.subscribe", params: { events: ["network"] } },
		{ id: 2, method: "network.addIntercept", params: { phases: ["request"] } },
	];
	const { responses } = await runBatch(
		ws as unknown as WebSocket,
		b as never,
		frames as never,
		byId,
		5000,
	);
	assert.equal(responses.length, 2);
	assert.deepEqual(
		responses.map((r) => r.id),
		[1, 2],
	);
	assert.ok(responses.every((r) => r.type === "success"));
	assert.deepEqual(c.ledger.subscriptions, ["network"]);
	assert.deepEqual(c.ledger.intercepts, ["int-1"]);
	assert.ok(byId.get(2));
});

test("runBatch reports every completed frame including failed and skipped frames", async () => {
	const b = await ctx();
	const updates: string[] = [];
	const ws = {
		send: (s: string) => {
			const frame = JSON.parse(s);
			queueMicrotask(() =>
				b.pending.get(frame.id)?.({
					id: frame.id,
					type: "success",
					result: {},
				}),
			);
		},
	};
	const { responses } = await runBatch(
		ws,
		b,
		[
			{ id: 1, method: "session.status" },
			{
				id: 2,
				method: "script.evaluate",
				params: { expression: "{{9.result.x}}" },
			},
			{ id: 3, method: "browsingContext.getTree" },
		],
		new Map(),
		5000,
		(response, completed) =>
			updates.push(
				`${completed}:${response.method}:${response.skipped ? "skipped" : response.type}`,
			),
	);
	assert.equal(responses.length, 3);
	assert.deepEqual(updates, [
		"1:session.status:success",
		"2:script.evaluate:error",
		"3:browsingContext.getTree:skipped",
	]);
});

test("runBatch cleans pending on timeout, abort, and send error; ignores late replies", async () => {
	const b = await ctx();
	const sent: number[] = [];
	const ws = {
		send: (text: string) => {
			sent.push(JSON.parse(text).id);
		},
	};
	const byId = new Map();
	const frames = [
		{ id: 1, method: "session.status" },
		{ id: 2, method: "session.status" },
	];
	const { responses } = await runBatch(ws, b, frames, byId, 5);
	assert.deepEqual(sent, [1]);
	assert.equal(responses[0].type, "timeout");
	assert.equal(responses[1].skipped, true);
	assert.equal(b.pending.size, 0);
	assert.equal(b.pending.get(1), undefined);
	const controller = new AbortController();
	const pendingRun = runBatch(
		ws,
		b,
		[{ id: 3, method: "session.status" }],
		byId,
		5000,
		undefined,
		controller.signal,
	);
	controller.abort();
	assert.equal((await pendingRun).responses[0].type, "aborted");
	assert.equal(b.pending.size, 0);
	const fail = await runBatch(
		{
			send: () => {
				throw Error("socket closed");
			},
		},
		b,
		[{ id: 4, method: "session.status" }],
		byId,
		5000,
	);
	assert.equal(fail.responses[0].type, "send-error");
	assert.equal(b.pending.size, 0);
});

test("runBatch spooled result remains available to later frame references", async () => {
	const b = await ctx();
	const byId = new Map();
	const sent: unknown[] = [];
	const ws = {
		send: (text: string) => {
			const frame = JSON.parse(text);
			sent.push(frame);
			queueMicrotask(() =>
				b.pending.get(frame.id)?.({
					id: frame.id,
					type: "success",
					result:
						frame.id === 1 ? { big: "x".repeat(300 * 1024), value: 13 } : {},
				}),
			);
		},
	};
	const first = await runBatch(
		ws,
		b,
		[{ id: 1, method: "script.evaluate" }],
		byId,
		1000,
	);
	assert.ok((first.responses[0].result as { spooled: string }).spooled);
	await runBatch(
		ws,
		b,
		[
			{
				id: 2,
				method: "script.evaluate",
				params: { value: "{{1.result.value}}" },
			},
		],
		byId,
		1000,
	);
	assert.equal((sent[1] as { params: { value: number } }).params.value, 13);
});

test("runBatch rejects multibyte params and expanded references by UTF-8 bytes", async () => {
	const b = await ctx();
	const sent: unknown[] = [];
	const byId = new Map([[1, res(1, { data: "🚀".repeat(280_000) })]]);
	const result = await runBatch(
		{ send: (s: string) => sent.push(JSON.parse(s)) },
		b,
		[
			{
				id: 2,
				method: "script.evaluate",
				params: { data: "🚀".repeat(280_000) },
			},
		],
		byId,
		100,
	);
	assert.equal(result.responses[0].type, "error");
	assert.equal(sent.length, 0);
	const expanded = await runBatch(
		{ send: (s: string) => sent.push(JSON.parse(s)) },
		b,
		[
			{
				id: 3,
				method: "script.evaluate",
				params: { data: "{{1.result.data}}" },
			},
		],
		byId,
		100,
	);
	assert.equal(expanded.responses[0].type, "send-error");
	assert.equal(sent.length, 0);
});

test("runBatch bounds oversized requests and error payloads", async () => {
	const b = await ctx();
	let sent = 0;
	const huge = await runBatch(
		{
			send: () => {
				sent++;
			},
		},
		b,
		[
			{
				id: 1,
				method: "session.status",
				params: { data: "x".repeat(1024 * 1024 + 1) },
			},
			{ id: 2, method: "session.status" },
		],
		new Map(),
		1000,
	);
	assert.equal(sent, 0);
	assert.equal(huge.responses[0].type, "error");
	assert.equal(huge.responses[1].skipped, true);
	const ws = {
		send: (text: string) => {
			const frame = JSON.parse(text);
			queueMicrotask(() =>
				b.pending.get(frame.id)?.({
					id: frame.id,
					type: "error",
					message: "x".repeat(300 * 1024),
				}),
			);
		},
	};
	const response = await runBatch(
		ws,
		b,
		[{ id: 3, method: "session.status" }],
		new Map(),
		1000,
	);
	assert.match(response.texts[0], /payload budget exceeded/);
	assert.ok(response.texts[0].length < 1024);
});

test("runBatch + assemblePayload preserve 64 outcomes within the response budget", async () => {
	const b = await ctx();
	const frames = Array.from({ length: 64 }, (_, index) => ({
		id: index + 1,
		method: "session.status",
	}));
	const responseText = (id: number, data: string) =>
		JSON.stringify({
			id,
			type: "success",
			result: { data },
			method: "session.status",
		});
	const fixed = frames.map((frame) =>
		frame.id === 1
			? JSON.stringify({
					id: frame.id,
					type: "error",
					error: "synthetic failure",
					method: frame.method,
				})
			: responseText(frame.id, frame.id === 64 ? "" : "x".repeat(14_000)),
	);
	const finalDataBytes =
		896 * 1024 - fixed.reduce((sum, text) => sum + Buffer.byteLength(text), 0);
	assert.ok(finalDataBytes > 0);
	const sent: number[] = [];
	const ws = {
		send: (text: string) => {
			const frame = JSON.parse(text);
			sent.push(frame.id);
			queueMicrotask(() =>
				b.pending.get(frame.id)?.(
					frame.id === 1
						? { id: 1, type: "error", error: "synthetic failure" }
						: {
								id: frame.id,
								type: "success",
								result: {
									data:
										frame.id === 64
											? "x".repeat(finalDataBytes)
											: "x".repeat(14_000),
								},
							},
				),
			);
		},
	};
	const result = await runBatch(ws, b, frames, new Map(), 1000);
	assert.equal(result.responses.length, 64);
	assert.deepEqual(
		sent,
		frames.map((frame) => frame.id),
	);
	assert.deepEqual(
		result.responses.map((response) => response.id),
		frames.map((frame) => frame.id),
	);
	assert.equal(result.responses[0].error, "synthetic failure");
	assert.equal(result.responses.at(-1)?.error, "pi payload budget exceeded");
	assert.ok(
		result.texts.reduce(
			(sum, text, index) => sum + Buffer.byteLength(text) + (index ? 1 : 0),
			0,
		) <=
			896 * 1024,
	);
	const payload = assemblePayload(
		{ sessionId: "s", port: 9222, capabilities: {} },
		result.texts,
		[],
		0,
	);
	assert.ok(Buffer.byteLength(payload) <= 1024 * 1024);
	assert.deepEqual(JSON.parse(payload).responses, result.responses);
});

test("runBatch aborts remaining frames after interpolation error", async () => {
	const pending = new Map<number, (r: any) => void>();
	const ws = {
		send: (s: string) => {
			const f = JSON.parse(s);
			queueMicrotask(() =>
				pending.get(f.id)?.({ id: f.id, type: "success", result: {} }),
			);
		},
	};
	const b = {
		pending,
		ledger: { subscriptions: [], intercepts: [], preloadScripts: [] },
		spoolDir: await mkdtemp(join(tmpdir(), "ffx-rb-")),
	};
	const byId = new Map();
	const frames = [
		{ id: 1, method: "browsingContext.create", params: {} },
		{
			id: 2,
			method: "browsingContext.navigate",
			params: { context: "{{9.result.context}}" },
		},
		{ id: 3, method: "script.evaluate", params: {} },
	];
	const { responses } = await runBatch(
		ws as unknown as WebSocket,
		b as never,
		frames as never,
		byId,
		5000,
	);
	assert.equal(responses[1].type, "error");
	assert.equal(responses[2].skipped, true);
});

test("runBatch spools by UTF-8 byte size, with byte-accurate marker", async () => {
	const b = await ctx();
	const byId = new Map();
	const data = "🚀".repeat(80_000);
	const result = await runBatch(
		{
			send: (s: string) => {
				const frame = JSON.parse(s);
				queueMicrotask(() =>
					b.pending.get(frame.id)?.({
						id: frame.id,
						type: "success",
						result: { data },
					}),
				);
			},
		},
		b,
		[{ id: 1, method: "script.evaluate" }],
		byId,
		1000,
	);
	const marker = result.responses[0].result as {
		spooled: string;
		size: number;
	};
	assert.ok(marker.spooled);
	assert.equal(
		marker.size,
		Buffer.byteLength(
			JSON.stringify({
				id: 1,
				type: "success",
				result: { data },
				method: "script.evaluate",
			}),
		),
	);
	assert.equal(JSON.parse(await readFile(marker.spooled, "utf8")).data, data);
});

test("runBatch spools oversized results and replaces them inline", async () => {
	const dir = await mkdtemp(join(tmpdir(), "ffx-rb-"));
	const pending = new Map<number, (r: any) => void>();
	const ws = {
		send: (s: string) => {
			const f = JSON.parse(s);
			queueMicrotask(() =>
				pending.get(f.id)?.({
					id: f.id,
					type: "success",
					result: { data: "A".repeat(300 * 1024) },
				}),
			);
		},
	};
	const b = {
		pending,
		ledger: { subscriptions: [], intercepts: [], preloadScripts: [] },
		spoolDir: dir,
	};
	const { responses } = await runBatch(
		ws as unknown as WebSocket,
		b as never,
		[
			{ id: 1, method: "browsingContext.captureScreenshot", params: {} },
		] as never,
		new Map(),
		5000,
	);
	const spooled = responses[0].result as { spooled: string; size: number };
	assert.ok(spooled.spooled.startsWith(dir));
	assert.equal((await readdir(dir)).length, 1);
	const file = await readFile(spooled.spooled, "utf8");
	assert.equal(JSON.parse(file).data.length, 300 * 1024);
	assert.equal(
		spooled.size,
		JSON.stringify({
			id: 1,
			type: "success",
			result: { data: "A".repeat(300 * 1024) },
			method: "browsingContext.captureScreenshot",
		}).length,
		"marker.size equals the unspooled full-frame length",
	);
});

test("runBatch caps spool across retained browser lifetimes", async () => {
	const budget = { bytes: 0 };
	const first = await ctx();
	const second = await ctx();
	first.spoolBudget = budget;
	second.spoolBudget = budget;
	const runLarge = (b: Ctx, id: number, bytes: number) =>
		runBatch(
			{
				send: (text: string) => {
					const frame = JSON.parse(text);
					queueMicrotask(() =>
						b.pending.get(frame.id)?.({
							id: frame.id,
							type: "success",
							result: { data: "x".repeat(bytes) },
						}),
					);
				},
			},
			b,
			[{ id, method: "script.evaluate" }],
			new Map(),
			1000,
		);
	try {
		const retained = await runLarge(first, 1, 20 * 1024 * 1024);
		assert.ok((retained.responses[0].result as { spooled: string }).spooled);
		assert.equal((await readdir(first.spoolDir)).length, 1);
		assert.ok(budget.bytes < MAX_SPOOL);

		const exhausted = await runLarge(second, 2, 13 * 1024 * 1024);
		assert.equal(exhausted.responses[0].error, "pi spool budget exhausted");
		assert.equal((await readdir(second.spoolDir)).length, 0);
		assert.equal(budget.bytes, first.spoolBytes);
	} finally {
		await rm(first.spoolDir, { recursive: true, force: true });
		await rm(second.spoolDir, { recursive: true, force: true });
	}
});

test("runBatch drains early spool failure and never exposes broken references", async () => {
	const b = await ctx();
	b.spoolDir = join(b.spoolDir, "missing");
	const byId = new Map();
	const ws = {
		send: (s: string) => {
			const frame = JSON.parse(s);
			setTimeout(
				() =>
					b.pending.get(frame.id)?.({
						id: frame.id,
						type: "success",
						result: { data: "x".repeat(300 * 1024) },
					}),
				30,
			);
		},
	};
	const result = await runBatch(
		ws,
		b,
		[
			{ id: 1, method: "script.evaluate" },
			{
				id: 2,
				method: "script.evaluate",
				params: { value: "{{1.result.data}}" },
			},
		],
		byId,
		1000,
	);
	assert.equal(result.responses[0].error, "pi spool write failed");
	assert.equal(result.responses[1].type, "error");
	assert.match(result.responses[1].message ?? "", /spool write failed/);
	assert.equal(byId.get(1)?.error, "pi spool write failed");
	assert.equal(b.spoolBytes, 0);
	const payload = assemblePayload(
		{ sessionId: "s", port: 9222, capabilities: {} },
		result.texts,
		[],
		0,
	);
	assert.equal(JSON.parse(payload).responses[0].error, "pi spool write failed");
});

test("runBatch flushes deferred spool writes before returning", async () => {
	const dir = await mkdtemp(join(tmpdir(), "ffx-rb-"));
	const pending = new Map<number, (r: any) => void>();
	const ws = {
		send: (s: string) => {
			const f = JSON.parse(s);
			queueMicrotask(() =>
				pending.get(f.id)?.({
					id: f.id,
					type: "success",
					result: { data: "A".repeat(300 * 1024) },
				}),
			);
		},
	};
	const b = {
		pending,
		ledger: { subscriptions: [], intercepts: [], preloadScripts: [] },
		spoolDir: dir,
	};
	const { responses } = await runBatch(
		ws as unknown as WebSocket,
		b as never,
		[
			{ id: 1, method: "browsingContext.captureScreenshot", params: {} },
			{ id: 2, method: "browsingContext.captureScreenshot", params: {} },
		] as never,
		new Map(),
		5000,
	);
	assert.equal(responses.length, 2);
	assert.equal(
		(await readdir(dir)).length,
		2,
		"both spool files flushed before return",
	);
});

test("assemblePayload caps bytes across events, session and wait", () => {
	const events = Array.from({ length: 200 }, (_, i) => ({
		type: "event",
		method: "e",
		params: { i, data: "a".repeat(7000) },
	}));
	const payload = assemblePayload(
		{ capabilities: "🦊".repeat(40_000) },
		[],
		events,
		200,
		{ data: "🚀".repeat(100_000) },
	);
	assert.ok(Buffer.byteLength(payload) <= 1024 * 1024);
	const result = JSON.parse(payload);
	assert.ok(result.events.length < 200);
	assert.equal(result.note, "event buffer capped or payload budget reached");
	assert.deepEqual(result.session.capabilities, { truncated: true });
	assert.deepEqual(result.wait, { truncated: true });
	assert.equal(result.events.at(-1).params.i, 199);
});

test("assemblePayload produces JSON identical to object stringify", () => {
	const session = {
		sessionId: "s",
		port: 9222,
		capabilities: { browserName: "firefox" },
	};
	const responses = [
		{
			id: 1,
			method: "session.new",
			type: "success",
			result: { sessionId: "s" },
		},
		{
			id: 2,
			method: "browsingContext.create",
			type: "error",
			error: "x",
			message: "y",
		},
	];
	const events = [
		{ type: "event", method: "log.entryAdded", params: { a: 1 } },
	];
	const wait = { timedOut: true, method: "x" };
	const got = assemblePayload(
		session,
		responses.map((r) => JSON.stringify(r)),
		events,
		200,
		wait,
	);
	const want = JSON.stringify({ session, responses, events, wait });
	assert.equal(JSON.parse(got).responses[0].result.sessionId, "s");
	assert.deepEqual(JSON.parse(got), JSON.parse(want));
});

test("ledgerUpdate removes intercepts and preload scripts", () => {
	const ledger = {
		subscriptions: [],
		intercepts: ["i1", "i2"],
		preloadScripts: ["p1"],
	};
	ledgerUpdate(
		{ pending: new Map(), ledger, spoolDir: "" } as never,
		"network.removeIntercept",
		{ intercepts: ["i1"] },
		{ id: 1, type: "success" },
	);
	ledgerUpdate(
		{ pending: new Map(), ledger, spoolDir: "" } as never,
		"script.removePreloadScript",
		{ id: "p1" },
		{ id: 2, type: "success" },
	);
	assert.deepEqual(ledger.intercepts, ["i2"]);
	assert.deepEqual(ledger.preloadScripts, []);
});

test("trimById passes small maps through unchanged", () => {
	const byId = new Map([
		[1, res(1, { a: 1 })],
		[2, res(2, { b: 2 })],
	]);
	assert.equal(trimById(byId, 500), byId);
});

test("trimById preserves insertion chronology across repeated byte-limited trims", () => {
	const large = "x".repeat(1100 * 1024);
	let entries = new Map<number, ReturnType<typeof res>>([
		[100, res(100, {})],
		[101, res(101, {})],
		[1, res(1, { large })],
		[2, res(2, { large })],
	]);
	entries = trimById(entries, 3);
	assert.deepEqual([...entries.keys()], [100, 101, 2]);
	entries.set(3, res(3, {}));
	entries = trimById(entries, 3);
	assert.deepEqual([...entries.keys()], [101, 2, 3]);
});

test("trimById keeps the most recent keep ids by frame id", () => {
	const byId = new Map<number, ReturnType<typeof res>>();
	for (let id = 1; id <= 600; id++) byId.set(id, res(id, { n: id }));
	const trimmed = trimById(byId, 500);
	assert.equal(trimmed.size, 500);
	assert.equal(trimmed.has(100), false);
	assert.equal(trimmed.get(101)?.id, 101);
	assert.equal(trimmed.get(600)?.id, 600);
});