@@ -57,6 +57,51 @@ test("inspects and hashes a local file in an immutable module worker", async ({
|
||||
expect(external).toEqual([]);
|
||||
});
|
||||
|
||||
test("automatically inspects every file in a bounded batch", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/deep/nested/file/");
|
||||
await page
|
||||
.locator('input[type="file"]')
|
||||
.first()
|
||||
.setInputFiles([
|
||||
"tests/fixtures/hello.txt",
|
||||
"tests/fixtures/second.txt",
|
||||
"tests/fixtures/third.csv",
|
||||
]);
|
||||
await expect(
|
||||
page.locator(".file-row small", { hasText: "inspected" }),
|
||||
).toHaveCount(3, {
|
||||
timeout: 30_000,
|
||||
});
|
||||
await expect(page.getByText("Inspecting 3 of 3 files")).toBeHidden();
|
||||
await page.getByRole("button", { name: "third.csv" }).click();
|
||||
await expect(
|
||||
page.getByText("text/plain (.txt)", { exact: true }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("previews collision-safe batch renames and exposes split/hash workflows", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/deep/nested/file/");
|
||||
await page
|
||||
.locator('input[type="file"]')
|
||||
.first()
|
||||
.setInputFiles(["tests/fixtures/hello.txt", "tests/fixtures/second.txt"]);
|
||||
await page.getByLabel("Template").fill("same.{ext}");
|
||||
await page.getByRole("button", { name: "Plan rename" }).click();
|
||||
await expect(page.getByRole("list", { name: "Rename plan" })).toContainText(
|
||||
"same (2).txt",
|
||||
);
|
||||
await expect(
|
||||
page.getByRole("button", { name: "Download parts + manifest" }),
|
||||
).toBeEnabled();
|
||||
await expect(
|
||||
page.getByRole("button", { name: "Download SHA256SUMS" }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("serves the release identity and hardened headers", async ({
|
||||
request,
|
||||
}) => {
|
||||
@@ -69,7 +114,7 @@ test("serves the release identity and hardened headers", async ({
|
||||
const manifest = await request.get("/deep/nested/file/toolbox-app.json");
|
||||
await expect(manifest.json()).resolves.toMatchObject({
|
||||
id: "de.add-ideas.file-tools",
|
||||
version: "0.1.0",
|
||||
version: "0.2.0",
|
||||
entry: "./",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test("keeps the primary workspace inside a narrow viewport", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/deep/nested/file/");
|
||||
await expect(page.locator("main").first()).toBeVisible();
|
||||
await expect(
|
||||
page.locator("main .loading, main .workbench-loading"),
|
||||
).toHaveCount(0);
|
||||
|
||||
const widths = await page.evaluate(() => ({
|
||||
content: document.documentElement.scrollWidth,
|
||||
viewport: document.documentElement.clientWidth,
|
||||
}));
|
||||
expect(widths.viewport).toBeLessThanOrEqual(430);
|
||||
expect(widths.content).toBeLessThanOrEqual(widths.viewport + 1);
|
||||
});
|
||||
@@ -1,5 +1,11 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { manifestCsv, manifestJson } from "../../src/file/manifest";
|
||||
import {
|
||||
checksumManifest,
|
||||
joinBlobs,
|
||||
planBatchRename,
|
||||
splitBlob,
|
||||
} from "../../src/file/operations";
|
||||
import {
|
||||
detectKnownSignature,
|
||||
extensionMismatch,
|
||||
@@ -52,4 +58,102 @@ describe("manifests", () => {
|
||||
expect(manifestJson(records)).toBe(manifestJson(records)));
|
||||
it("neutralises spreadsheet-leading formulas", () =>
|
||||
expect(manifestCsv(records)).toContain("'=formula"));
|
||||
it("reports truthful inspection coverage and per-file failures", () => {
|
||||
const text = manifestJson([
|
||||
{
|
||||
...records[0]!,
|
||||
inspectionStatus: "inspected",
|
||||
inspection: {
|
||||
evidence: [],
|
||||
entropy: 0,
|
||||
sampleBytes: 0,
|
||||
strings: [],
|
||||
findings: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
...records[0]!,
|
||||
path: "broken.bin",
|
||||
inspectionStatus: "error",
|
||||
inspectionError: "Timed out",
|
||||
},
|
||||
]);
|
||||
const manifest = JSON.parse(text) as {
|
||||
inspectionCoverage: { inspected: number; failed: number };
|
||||
files: Array<{
|
||||
inspectionStatus: string;
|
||||
inspectionError: string | null;
|
||||
}>;
|
||||
};
|
||||
expect(manifest.inspectionCoverage).toMatchObject({
|
||||
inspected: 1,
|
||||
failed: 1,
|
||||
});
|
||||
expect(
|
||||
manifest.files.find((file) => file.inspectionStatus === "error"),
|
||||
).toMatchObject({ inspectionError: "Timed out" });
|
||||
expect(
|
||||
manifestCsv([{ ...records[0]!, inspectionStatus: "cancelled" }]),
|
||||
).toContain('"cancelled"');
|
||||
});
|
||||
});
|
||||
|
||||
describe("bounded file operations", () => {
|
||||
it("plans sanitized collision-safe rename downloads deterministically", () => {
|
||||
const files = [
|
||||
{ name: "first.txt", blob: new Blob(["one"]) },
|
||||
{ name: "second.txt", blob: new Blob(["two"]) },
|
||||
];
|
||||
const plan = planBatchRename(files, "same.{ext}");
|
||||
expect(plan.map((item) => item.filename)).toEqual([
|
||||
"same.txt",
|
||||
"same (2).txt",
|
||||
]);
|
||||
expect(plan.map((item) => item.originalName)).toEqual([
|
||||
"first.txt",
|
||||
"second.txt",
|
||||
]);
|
||||
});
|
||||
|
||||
it("splits and rejoins byte-exact Blob parts with an offset manifest", async () => {
|
||||
const source = new Blob([new Uint8Array(150_000).map((_, index) => index)]);
|
||||
const split = splitBlob(source, "payload.bin", 65_536);
|
||||
expect(split.parts.map((part) => part.size)).toEqual([
|
||||
65_536, 65_536, 18_928,
|
||||
]);
|
||||
expect(split.manifest.parts[1]).toMatchObject({ offset: 65_536, index: 2 });
|
||||
const joined = joinBlobs(split.parts.map((part) => part.blob));
|
||||
expect(new Uint8Array(await joined.arrayBuffer())).toEqual(
|
||||
new Uint8Array(await source.arrayBuffer()),
|
||||
);
|
||||
});
|
||||
|
||||
it("refuses unbounded split/join and emits sorted checksum files", () => {
|
||||
expect(() => splitBlob(new Blob(["x"]), "x", 1)).toThrow(/Chunk size/u);
|
||||
expect(() =>
|
||||
joinBlobs([new Blob(["abc"]), new Blob(["def"])], "", 5),
|
||||
).toThrow(/exceeds/u);
|
||||
const output = checksumManifest(
|
||||
[
|
||||
{
|
||||
name: "b",
|
||||
path: "b",
|
||||
size: 0,
|
||||
type: "",
|
||||
lastModified: 0,
|
||||
sha256: "b".repeat(64),
|
||||
},
|
||||
{
|
||||
name: "a",
|
||||
path: "a",
|
||||
size: 0,
|
||||
type: "",
|
||||
lastModified: 0,
|
||||
sha256: "a".repeat(64),
|
||||
},
|
||||
],
|
||||
"sha256",
|
||||
);
|
||||
expect(output.split("\n")[0]).toBe(`${"a".repeat(64)} *a`);
|
||||
});
|
||||
});
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
Second batch fixture.
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
name,value
|
||||
batch,3
|
||||
|
Reference in New Issue
Block a user