test(storybook): verify isolated file lifecycle fixtures

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Dotta 2026-09-08 08:35:26 -05:00
parent 28858075ef
commit 0bed8c643d
2 changed files with 112 additions and 1 deletions

View File

@ -0,0 +1,102 @@
import { describe, expect, it } from "vitest";
import type { WorkFile } from "@paperclipai/shared";
import { createWorkFolderFixture } from "./workFolders";
const root =
"http://storybook.test/api/companies/company-storybook/work-folders/task/issue-storybook-1";
type Fixture = ReturnType<typeof createWorkFolderFixture>;
function request(fixture: Fixture, suffix = "", init?: RequestInit) {
return fixture.handle(new Request(`${root}${suffix}`, init));
}
async function files(fixture: Fixture, trash = false): Promise<WorkFile[]> {
return (await (await request(fixture, `?trash=${trash}`))!.json()).files;
}
function operation(
fixture: Fixture,
action: string,
fields: Record<string, string>,
) {
return request(fixture, "/operations", {
method: "POST",
body: JSON.stringify({ action, ...fields }),
});
}
describe("work-folder Storybook fixtures", () => {
it("keeps uploads within one owner and resets them for the next story", async () => {
const fixture = createWorkFolderFixture("empty");
const bytes = new Uint8Array([0, 255, 128, 10]);
await request(fixture, "/content?path=nested%2Fsample.bin", {
method: "PUT",
body: bytes,
});
const downloaded = await request(
fixture,
"/content?path=nested%2Fsample.bin",
);
expect(new Uint8Array(await downloaded!.arrayBuffer())).toEqual(bytes);
expect(await files(fixture)).toHaveLength(1);
const otherOwner = await fixture.handle(
new Request(root.replace("/task/issue-storybook-1", "/user/user-board")),
);
expect((await otherOwner!.json()).files).toEqual([]);
expect(await files(createWorkFolderFixture("empty"))).toEqual([]);
expect(
await fixture.handle(
new Request(
"http://storybook.test/api/companies/real-company/work-folders/task/task-1",
),
),
).toBeNull();
});
it("restores a deleted directory and purges its content without affecting siblings", async () => {
const fixture = createWorkFolderFixture("empty");
await operation(fixture, "mkdir", { path: "notes" });
await request(fixture, "/content?path=notes%2Fempty.txt", {
method: "PUT",
body: "",
});
await request(fixture, "/content?path=notes-extra.txt", {
method: "PUT",
body: "Keep me",
});
await operation(fixture, "delete", { path: "notes" });
expect((await files(fixture)).map((file) => file.path)).toEqual([
"notes-extra.txt",
]);
const directory = (await files(fixture, true)).find(
(file) => file.kind === "directory",
)!;
await operation(fixture, "restore", { fileId: directory.id });
expect(await files(fixture, true)).toEqual([]);
expect(
await (await request(fixture, "/content?path=notes%2Fempty.txt"))!.text(),
).toBe("");
await operation(fixture, "delete", { path: "notes" });
await operation(fixture, "purge", { fileId: directory.id });
expect(await files(fixture, true)).toEqual([]);
expect(
(await request(fixture, "/content?path=notes%2Fempty.txt"))!.status,
).toBe(404);
expect(
await (await request(fixture, "/content?path=notes-extra.txt"))!.text(),
).toBe("Keep me");
});
it("preserves existing content when an upload failure is demonstrated", async () => {
const fixture = createWorkFolderFixture("uploadFailed");
const before = await (await request(
fixture,
"/content?path=README.md",
))!.text();
const response = await request(fixture, "/content?path=README.md", {
method: "PUT",
body: "replacement",
});
expect(response!.status).toBe(503);
expect(
await (await request(fixture, "/content?path=README.md"))!.text(),
).toBe(before);
});
});

View File

@ -180,6 +180,11 @@ export function createWorkFolderFixture(
}
const path = url.searchParams.get("path") ?? "";
if (action === "content" && request.method === "GET") {
if (
!state.files.some((file) => file.path === path && file.kind === "file")
) {
return fail("File not found", 404);
}
if (path === "assets/paperclip.png")
return fetch("/android-chrome-192x192.png", { signal: request.signal });
const blob = state.contents.get(path);
@ -245,8 +250,12 @@ export function createWorkFolderFixture(
state.files.forEach((f) => {
if (inDeletedTree(f)) f.deletedAt = null;
});
if (op.action === "purge")
if (op.action === "purge") {
for (const file of state.files.filter(inDeletedTree)) {
state.contents.delete(file.path);
}
state.files = state.files.filter((f) => !inDeletedTree(f));
}
} else return fail("Unsupported fixture operation", 405);
state.changed = true;
return Response.json({ ok: true });