src/lib/documents/server-storage.test.ts
Linked repository file from this build.
import { describe, expect, test, spyOn } from "bun:test";
import {
S3Client,
GetObjectCommand,
PutObjectCommand,
DeleteObjectCommand,
} from "@aws-sdk/client-s3";
import { rm } from "node:fs/promises";
import { join } from "node:path";
import {
attachableDocumentSize,
orphanedObjects,
createNeonDocumentStorage,
putLocalDocument,
storageConfig,
uploadOwner,
} from "./server-storage";
import { MAX_PDF_BYTES } from "./storage";
function client() {
return new S3Client({
region: "us-east-2",
endpoint: "https://storage.example.com",
forcePathStyle: true,
credentials: { accessKeyId: "test", secretAccessKey: "test-secret" },
});
}
const id = "11111111-1111-4111-8111-111111111111";
describe("document storage configuration", () => {
test("production requires storage credentials and development keeps local files", () => {
expect(storageConfig({ NODE_ENV: "development" })).toEqual({ mode: "local" });
expect(() => storageConfig({ NODE_ENV: "production" })).toThrow("not configured");
expect(storageConfig({ NODE_ENV: "production", DOCUMENT_STORAGE: "local" })).toEqual({
mode: "local",
});
expect(() => storageConfig({ NODE_ENV: "production", DOCUMENT_STORAGE: "typo" })).toThrow(
"Invalid",
);
});
});
test("signed form limits key, size, content type, owner, and expiry", async () => {
const storage = createNeonDocumentStorage(client(), "private-documents");
const upload = await storage.prepare({ name: "test.pdf", size: 8 }, "owner");
const policy = JSON.parse(Buffer.from(upload.fields.Policy, "base64").toString());
expect(upload.fields.key).toBe(`pending/${upload.uploadId}`);
expect(policy.conditions).toContainEqual(["content-length-range", 8, 8]);
expect(policy.conditions).toContainEqual(["eq", "$Content-Type", "application/pdf"]);
expect(policy.conditions).toContainEqual(["eq", "$x-amz-meta-owner", "owner"]);
expect(new Date(policy.expiration).getTime() - Date.now()).toBeLessThanOrEqual(300000);
await expect(
storage.prepare({ name: "x.pdf", size: MAX_PDF_BYTES + 1 }, "owner"),
).rejects.toThrow();
});
test("completion checks the bytes then writes a separate final key", async () => {
const s3 = client();
const bytes = new TextEncoder().encode("%PDF-1.4");
const send = spyOn(s3, "send")
.mockImplementationOnce(async () => ({
Metadata: { owner: "owner" },
ContentLength: bytes.length,
ContentType: "application/pdf",
Body: { transformToByteArray: async () => bytes },
}))
.mockImplementation(async () => ({}));
const key = await createNeonDocumentStorage(s3, "docs").complete(id, "owner");
expect(key).not.toBe(id);
expect(send.mock.calls[0][0]).toBeInstanceOf(GetObjectCommand);
expect(send.mock.calls[1][0]).toBeInstanceOf(PutObjectCommand);
expect(send.mock.calls[1][0].input).toMatchObject({
Key: key,
Body: bytes,
Metadata: { owner: "owner" },
});
expect(send.mock.calls[2][0]).toBeInstanceOf(DeleteObjectCommand);
});
test("completion rejects another uploader, oversized objects, and disguised PDFs", async () => {
for (const [owner, size, content, error] of [
["other", 8, "%PDF-1.4", "another user"],
["owner", MAX_PDF_BYTES + 1, "%PDF-1.4", "10 MB"],
["owner", 8, "not pdf!", "header"],
] as const) {
const s3 = client();
const send = spyOn(s3, "send").mockImplementation(async () => ({
Metadata: { owner },
ContentLength: size,
ContentType: "application/pdf",
Body: { transformToByteArray: async () => new TextEncoder().encode(content) },
}));
await expect(createNeonDocumentStorage(s3, "docs").complete(id, "owner")).rejects.toThrow(
error,
);
expect(send).toHaveBeenCalledTimes(1);
}
});
test("download URLs expire quickly and owner IDs include the program", async () => {
const url = new URL(await createNeonDocumentStorage(client(), "docs").url(id));
expect(url.searchParams.get("X-Amz-Expires")).toBe("60");
expect(uploadOwner("snap", "101")).not.toBe(uploadOwner("other", "101"));
});
test("describe reports size and uploader, and a missing object as undefined", async () => {
const s3 = client();
spyOn(s3, "send")
.mockImplementationOnce(async () => ({ ContentLength: 42, Metadata: { owner: "owner" } }))
.mockImplementationOnce(async () => {
throw Object.assign(new Error("missing"), { name: "NotFound" });
});
const storage = createNeonDocumentStorage(s3, "docs");
expect(await storage.describe(id)).toEqual({ size: 42, owner: "owner" });
expect(await storage.describe(id)).toBeUndefined();
});
test("a local upload can only be attached by its uploader, with its real size", async () => {
const pdf = new File(["%PDF-1.4 local"], "local.pdf", { type: "application/pdf" });
const key = await putLocalDocument(pdf, "owner");
try {
expect(await attachableDocumentSize(key, "owner")).toBe(pdf.size);
await expect(attachableDocumentSize(key, "other")).rejects.toThrow("Upload the PDF again");
await expect(attachableDocumentSize(crypto.randomUUID(), "owner")).rejects.toThrow(
"Upload the PDF again",
);
} finally {
await rm(join(process.cwd(), ".demo-documents", key), { force: true });
await rm(join(process.cwd(), ".demo-documents", `${key}.owner`), { force: true });
}
});
test("only old, unreferenced objects are orphans", () => {
const now = new Date("2026-10-02T12:00:00Z");
const old = new Date("2026-09-30T12:00:00Z");
const recent = new Date("2026-10-02T11:00:00Z");
const objects = [
{ key: "attached", modifiedAt: old },
{ key: "pending/abandoned", modifiedAt: old },
{ key: "unused", modifiedAt: old },
{ key: "uploading", modifiedAt: recent },
];
expect(orphanedObjects(objects, new Set(["attached"]), now).map((object) => object.key)).toEqual([
"pending/abandoned",
"unused",
]);
});