diff --git a/src/backend/drivers/util/fileInput.test.ts b/src/backend/drivers/util/fileInput.test.ts
new file mode 100644
index 000000000..5b8d152f2
--- /dev/null
+++ b/src/backend/drivers/util/fileInput.test.ts
@@ -0,0 +1,377 @@
+/**
+ * Copyright (C) 2024-present Puter Technologies Inc.
+ *
+ * This file is part of Puter.
+ *
+ * Puter is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { afterAll, beforeAll, describe, expect, it } from 'vitest';
+import { v4 as uuidv4 } from 'uuid';
+import type { Actor } from '../../core/actor.js';
+import { runWithContext } from '../../core/context.js';
+import { PuterServer } from '../../server.js';
+import { setupTestServer } from '../../testUtil.js';
+import { generateDefaultFsentries } from '../../util/userProvisioning.js';
+import { inferFilenameFromUrlOrPath, loadFileInput } from './fileInput.js';
+
+// ── Test harness ────────────────────────────────────────────────────
+//
+// Boots one real PuterServer (in-memory sqlite + dynamo + s3 + mock
+// redis) and exercises `loadFileInput` against the live wired stores
+// and FSService. Each test makes its own user via `makeUser` and,
+// where the FS-resolution path is being exercised, writes a real
+// file through FSService.write so there's an actual fsentry +
+// in-memory S3 object behind the path / uuid we hand to loadFileInput.
+
+let server: PuterServer;
+
+beforeAll(async () => {
+ server = await setupTestServer();
+});
+
+afterAll(async () => {
+ await server?.shutdown();
+});
+
+const makeUser = async (): Promise<{ actor: Actor; userId: number }> => {
+ const username = `fic-${Math.random().toString(36).slice(2, 10)}`;
+ const created = await server.stores.user.create({
+ username,
+ uuid: uuidv4(),
+ password: null,
+ email: `${username}@test.local`,
+ free_storage: 100 * 1024 * 1024,
+ requires_email_confirmation: false,
+ });
+ await generateDefaultFsentries(
+ server.clients.db,
+ server.stores.user,
+ created,
+ );
+ const refreshed = (await server.stores.user.getById(created.id))!;
+ return {
+ userId: refreshed.id,
+ actor: {
+ user: {
+ id: refreshed.id,
+ uuid: refreshed.uuid,
+ username: refreshed.username,
+ email: refreshed.email ?? null,
+ email_confirmed: true,
+ } as Actor['user'],
+ },
+ };
+};
+
+const withActor = async (actor: Actor, fn: () => Promise): Promise =>
+ runWithContext({ actor }, fn);
+
+const writeFile = async (
+ userId: number,
+ path: string,
+ body: Buffer,
+ contentType = 'application/octet-stream',
+) => {
+ const result = await server.services.fs.write(userId, {
+ fileMetadata: {
+ path,
+ size: body.byteLength,
+ contentType,
+ },
+ fileContent: body,
+ });
+ return result.fsEntry;
+};
+
+const callLoadFileInput = (
+ actor: Actor,
+ input: unknown,
+ options?: Parameters[4],
+) =>
+ withActor(actor, () =>
+ loadFileInput(
+ server.stores,
+ server.services.fs,
+ actor,
+ input,
+ options,
+ ),
+ );
+
+// ── inferFilenameFromUrlOrPath ─────────────────────────────────────
+
+describe('inferFilenameFromUrlOrPath', () => {
+ it('returns the basename of a URL pathname', () => {
+ expect(inferFilenameFromUrlOrPath('https://cdn.test/a/b/photo.png')).toBe(
+ 'photo.png',
+ );
+ });
+
+ it('returns the basename of a posix-style path string', () => {
+ expect(inferFilenameFromUrlOrPath('/alice/Music/song.mp3')).toBe(
+ 'song.mp3',
+ );
+ });
+
+ it('falls back to the supplied default when there is no basename', () => {
+ // Empty string has no URL form and no posix basename, so the
+ // explicit fallback wins.
+ expect(inferFilenameFromUrlOrPath('', 'fallback-name')).toBe(
+ 'fallback-name',
+ );
+ });
+
+ it('uses the literal `input` as fallback when no override is passed', () => {
+ expect(inferFilenameFromUrlOrPath('')).toBe('input');
+ });
+
+ it('handles bare filenames (no slashes, not a URL)', () => {
+ expect(inferFilenameFromUrlOrPath('plain.txt')).toBe('plain.txt');
+ });
+});
+
+// ── loadFileInput — argument validation ─────────────────────────────
+
+describe('loadFileInput validation', () => {
+ it('throws 400 when input is empty/falsy', async () => {
+ const { actor } = await makeUser();
+ await expect(callLoadFileInput(actor, undefined)).rejects.toMatchObject(
+ { statusCode: 400 },
+ );
+ await expect(callLoadFileInput(actor, null)).rejects.toMatchObject({
+ statusCode: 400,
+ });
+ await expect(callLoadFileInput(actor, '')).rejects.toMatchObject({
+ statusCode: 400,
+ });
+ });
+
+ it('throws 401 when actor.user.id is not a finite number', async () => {
+ // Missing actor entirely
+ await expect(
+ loadFileInput(
+ server.stores,
+ server.services.fs,
+ undefined as unknown as Actor,
+ 'data:text/plain,hi',
+ ),
+ ).rejects.toMatchObject({ statusCode: 401 });
+ // Actor with no user
+ await expect(
+ loadFileInput(
+ server.stores,
+ server.services.fs,
+ {} as Actor,
+ 'data:text/plain,hi',
+ ),
+ ).rejects.toMatchObject({ statusCode: 401 });
+ // Actor with non-numeric user.id
+ await expect(
+ loadFileInput(
+ server.stores,
+ server.services.fs,
+ { user: { id: 'nope' } } as unknown as Actor,
+ 'data:text/plain,hi',
+ ),
+ ).rejects.toMatchObject({ statusCode: 401 });
+ });
+});
+
+// ── loadFileInput — data URL path ───────────────────────────────────
+
+describe('loadFileInput data URL', () => {
+ it('decodes a base64 data URL and reports the declared MIME', async () => {
+ const { actor } = await makeUser();
+ const payload = Buffer.from('hello world');
+ const dataUrl = `data:text/plain;base64,${payload.toString('base64')}`;
+
+ const result = await callLoadFileInput(actor, dataUrl);
+
+ expect(result.buffer.equals(payload)).toBe(true);
+ expect(result.mimeType).toBe('text/plain');
+ expect(result.fsEntry).toBeNull();
+ // Filename derives from the MIME subtype.
+ expect(result.filename).toBe('input.plain');
+ });
+
+ it('decodes a non-base64 (URL-encoded) data URL', async () => {
+ const { actor } = await makeUser();
+ // Plain (no `;base64`) → URL-decoded payload.
+ const result = await callLoadFileInput(
+ actor,
+ 'data:text/plain,hello%20world',
+ );
+
+ expect(result.buffer.toString('utf8')).toBe('hello world');
+ expect(result.mimeType).toBe('text/plain');
+ });
+
+ it('infers MIME-derived filename for compound types like svg+xml', async () => {
+ const { actor } = await makeUser();
+ const result = await callLoadFileInput(
+ actor,
+ 'data:image/svg+xml;base64,PHN2Zy8+',
+ );
+ // `image/svg+xml` → input.svg (subtype, before the `+`).
+ expect(result.filename).toBe('input.svg');
+ expect(result.mimeType).toBe('image/svg+xml');
+ });
+
+ it('defaults MIME to application/octet-stream when omitted', async () => {
+ const { actor } = await makeUser();
+ const result = await callLoadFileInput(actor, 'data:;base64,QUJD');
+ expect(result.mimeType).toBe('application/octet-stream');
+ expect(result.buffer.toString('utf8')).toBe('ABC');
+ });
+
+ it('throws 400 on a malformed data URL', async () => {
+ const { actor } = await makeUser();
+ // Missing comma → DATA_URL_PATTERN.exec returns null.
+ await expect(
+ callLoadFileInput(actor, 'data:not-a-url'),
+ ).rejects.toMatchObject({ statusCode: 400 });
+ });
+
+ it('rejects an oversize data URL with 413 + storage_limit_reached', async () => {
+ const { actor } = await makeUser();
+ const payload = Buffer.alloc(64);
+ const dataUrl = `data:application/octet-stream;base64,${payload.toString('base64')}`;
+
+ await expect(
+ callLoadFileInput(actor, dataUrl, { maxBytes: 32 }),
+ ).rejects.toMatchObject({
+ statusCode: 413,
+ legacyCode: 'storage_limit_reached',
+ });
+ });
+
+ it('accepts a data URL exactly at the maxBytes threshold', async () => {
+ const { actor } = await makeUser();
+ const payload = Buffer.alloc(16, 0x41); // 16 bytes of 'A'
+ const dataUrl = `data:application/octet-stream;base64,${payload.toString('base64')}`;
+
+ const result = await callLoadFileInput(actor, dataUrl, {
+ maxBytes: 16,
+ });
+ expect(result.buffer.byteLength).toBe(16);
+ });
+});
+
+// ── loadFileInput — FS path ─────────────────────────────────────────
+
+describe('loadFileInput FS path', () => {
+ it('reads bytes back through a real fsentry written via FSService', async () => {
+ const { actor, userId } = await makeUser();
+ const username = actor.user!.username!;
+ const path = `/${username}/Documents/sample.txt`;
+ const body = Buffer.from('hello from sql + s3');
+ const entry = await withActor(actor, () =>
+ writeFile(userId, path, body, 'text/plain'),
+ );
+
+ const result = await callLoadFileInput(actor, path);
+
+ expect(result.buffer.equals(body)).toBe(true);
+ expect(result.filename).toBe('sample.txt');
+ // FSService stamps the contentType into S3 metadata; loadFileInput
+ // returns it on the way out.
+ expect(result.mimeType).toBe('text/plain');
+ expect(result.fsEntry?.uuid).toBe(entry.uuid);
+ expect(result.fsEntry?.path).toBe(path);
+ });
+
+ it('also accepts a `{ uuid }` object reference', async () => {
+ const { actor, userId } = await makeUser();
+ const username = actor.user!.username!;
+ const path = `/${username}/Documents/by-uuid.bin`;
+ const body = Buffer.from([0x01, 0x02, 0x03, 0x04]);
+ const entry = await withActor(actor, () =>
+ writeFile(userId, path, body),
+ );
+
+ const result = await callLoadFileInput(actor, { uuid: entry.uuid });
+ expect(result.buffer.equals(body)).toBe(true);
+ expect(result.fsEntry?.uuid).toBe(entry.uuid);
+ });
+
+ it('expands `~/...` paths against the actor home before resolving', async () => {
+ const { actor, userId } = await makeUser();
+ const username = actor.user!.username!;
+ const body = Buffer.from('tilde');
+ await withActor(actor, () =>
+ writeFile(userId, `/${username}/Documents/tilde.txt`, body),
+ );
+
+ const result = await callLoadFileInput(actor, '~/Documents/tilde.txt');
+ expect(result.buffer.equals(body)).toBe(true);
+ });
+
+ it('throws 404 when the fsentry cannot be resolved', async () => {
+ const { actor } = await makeUser();
+ await expect(
+ callLoadFileInput(actor, {
+ uuid: '00000000-0000-0000-0000-000000000000',
+ }),
+ ).rejects.toMatchObject({ statusCode: 404 });
+ });
+
+ it('rejects directory entries with 400', async () => {
+ const { actor } = await makeUser();
+ const username = actor.user!.username!;
+ // The Documents folder is a real fsentry directory created by
+ // generateDefaultFsentries.
+ await expect(
+ callLoadFileInput(actor, `/${username}/Documents`),
+ ).rejects.toMatchObject({ statusCode: 400 });
+ });
+
+ it("rejects when the FS access check denies the actor", async () => {
+ // Owner writes a file; intruder tries to read it. The real
+ // FSService.checkFSAccess walks the ACL and refuses.
+ const owner = await makeUser();
+ const intruder = await makeUser();
+ const ownerName = owner.actor.user!.username!;
+ const path = `/${ownerName}/Documents/private.txt`;
+ await withActor(owner.actor, () =>
+ writeFile(owner.userId, path, Buffer.from('owned')),
+ );
+
+ const err = await callLoadFileInput(intruder.actor, path).then(
+ () => null,
+ (e: unknown) => e,
+ );
+ const status = (err as { statusCode?: number } | null)?.statusCode;
+ // Access denied lands as 403; "can't see" lands as 404 — both
+ // are valid refusals from ACLService.getSafeAclError.
+ expect([403, 404]).toContain(status);
+ });
+
+ it('rejects up-front when contentLength exceeds maxBytes', async () => {
+ const { actor, userId } = await makeUser();
+ const username = actor.user!.username!;
+ const path = `/${username}/Documents/big.bin`;
+ const body = Buffer.alloc(2048, 0x42);
+ await withActor(actor, () =>
+ writeFile(userId, path, body, 'application/octet-stream'),
+ );
+
+ await expect(
+ callLoadFileInput(actor, path, { maxBytes: 64 }),
+ ).rejects.toMatchObject({
+ statusCode: 413,
+ legacyCode: 'storage_limit_reached',
+ });
+ });
+});