Advance Blender 5.2 web parity through M12-03D
This commit is contained in:
118
web/tests/e2e/asset-preview-quarantine.spec.ts
Normal file
118
web/tests/e2e/asset-preview-quarantine.spec.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const catalog = JSON.parse(fs.readFileSync(path.resolve(import.meta.dirname, "../../../tests/golden/M12-01D/catalog-v2.json"), "utf8")) as Record<string, unknown>;
|
||||
const identity = JSON.parse(fs.readFileSync(path.resolve(import.meta.dirname, "../../../tests/golden/M12-02B/identity.json"), "utf8")) as Record<string, unknown>;
|
||||
const preview = fs.readFileSync(path.resolve(import.meta.dirname, "../../../tests/golden/M12-02B/preview.png"));
|
||||
|
||||
test("M12-02F quarantines corrupt preview bytes while preserving catalog metadata", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
const result = await page.evaluate(async ({ sourceCatalog, sourceIdentity, bytes }) => {
|
||||
const commit = await import("/src/storage/asset-preview-opfs-commit.ts");
|
||||
const quarantine = await import("/src/storage/asset-preview-quarantine.ts");
|
||||
const encoded = new Uint8Array(bytes).buffer;
|
||||
const base = await commit.parseAssetCatalogManifestV2({
|
||||
...sourceCatalog,
|
||||
assets: (sourceCatalog.assets as Array<Record<string, unknown>>).map((asset) => ({ ...asset, preview: null })),
|
||||
});
|
||||
const target = base.assets[0];
|
||||
const identityInput = { ...sourceIdentity, assetId: target.assetId } as Record<string, unknown>;
|
||||
delete identityInput.identitySha256;
|
||||
const boundIdentity = await commit.createAssetPreviewIdentity(identityInput as never);
|
||||
const databaseName = `m12-02f-${crypto.randomUUID()}`;
|
||||
const projectId = `m12-02f-${crypto.randomUUID()}`;
|
||||
const open = (): Promise<IDBDatabase> => new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open(databaseName, 1);
|
||||
request.onupgradeneeded = () => request.result.createObjectStore("setting", { keyPath: "id" });
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
const complete = (transaction: IDBTransaction): Promise<void> => new Promise((resolve, reject) => {
|
||||
transaction.oncomplete = () => resolve();
|
||||
transaction.onerror = () => reject(transaction.error);
|
||||
transaction.onabort = () => reject(transaction.error);
|
||||
});
|
||||
let database = await open();
|
||||
const seed = database.transaction("setting", "readwrite");
|
||||
seed.objectStore("setting").put({ id: "asset-catalog:index:v2", value: base });
|
||||
await complete(seed);
|
||||
const committed = await commit.commitAssetPreviewToOPFS(database, projectId, 7, boundIdentity, encoded.slice(0));
|
||||
const ready = await quarantine.inspectAssetPreviewWithQuarantine(database, projectId, target.assetId);
|
||||
|
||||
const root = await navigator.storage.getDirectory();
|
||||
let contentDirectory = root;
|
||||
for (const segment of ["projects", projectId, "assets", "sha256", boundIdentity.content.sha256.slice(0, 2)]) {
|
||||
contentDirectory = await contentDirectory.getDirectoryHandle(segment);
|
||||
}
|
||||
const fileHandle = await contentDirectory.getFileHandle(boundIdentity.content.sha256);
|
||||
const corrupt = new Uint8Array(await (await fileHandle.getFile()).arrayBuffer());
|
||||
corrupt[corrupt.byteLength - 1] ^= 0xff;
|
||||
const writer = await fileHandle.createWritable();
|
||||
await writer.write(corrupt);
|
||||
await writer.close();
|
||||
|
||||
const quarantined = await quarantine.inspectAssetPreviewWithQuarantine(database, projectId, target.assetId);
|
||||
let contentExists = true;
|
||||
try { await contentDirectory.getFileHandle(boundIdentity.content.sha256); }
|
||||
catch { contentExists = false; }
|
||||
database.close();
|
||||
database = await open();
|
||||
const reopened = await quarantine.inspectAssetPreviewWithQuarantine(database, projectId, target.assetId);
|
||||
const catalogTransaction = database.transaction("setting", "readonly");
|
||||
const catalogRow = await new Promise<Record<string, unknown>>((resolve, reject) => {
|
||||
const request = catalogTransaction.objectStore("setting").get("asset-catalog:index:v2");
|
||||
request.onsuccess = () => resolve(request.result as Record<string, unknown>);
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
await complete(catalogTransaction);
|
||||
const storedCatalog = await commit.parseAssetCatalogManifestV2(catalogRow.value);
|
||||
const quarantinePath = quarantined.status === "QUARANTINED" ? quarantined.receipt.quarantinePath : null;
|
||||
let quarantineFileBytes = 0;
|
||||
if (quarantinePath) {
|
||||
let current = root;
|
||||
const segments = quarantinePath.split("/");
|
||||
for (const segment of segments.slice(0, -1)) current = await current.getDirectoryHandle(segment);
|
||||
quarantineFileBytes = (await (await current.getFileHandle(segments.at(-1)!)).getFile()).size;
|
||||
}
|
||||
const value = {
|
||||
baseMetadata: target,
|
||||
ready: { status: ready.status, bytes: ready.status === "READY" ? ready.data.byteLength : 0 },
|
||||
quarantined,
|
||||
reopened,
|
||||
contentExists,
|
||||
quarantineFileBytes,
|
||||
storedAsset: storedCatalog.assets[0],
|
||||
storedRevision: storedCatalog.revision,
|
||||
committedRevision: committed.manifest.revision,
|
||||
otherAsset: storedCatalog.assets[1],
|
||||
baseOtherAsset: base.assets[1],
|
||||
};
|
||||
database.close();
|
||||
indexedDB.deleteDatabase(databaseName);
|
||||
const projects = await root.getDirectoryHandle("projects");
|
||||
await projects.removeEntry(projectId, { recursive: true });
|
||||
return value;
|
||||
}, { sourceCatalog: catalog, sourceIdentity: identity, bytes: [...preview] });
|
||||
|
||||
expect(result.ready).toEqual({ status: "READY", bytes: 513 });
|
||||
expect(result.quarantined.status).toBe("QUARANTINED");
|
||||
expect(result.quarantined.preview).toBeNull();
|
||||
expect(result.quarantined.data).toBeNull();
|
||||
expect(result.quarantined.receipt).toMatchObject({
|
||||
task: "M12-02F",
|
||||
status: "QUARANTINED",
|
||||
catalogRevision: 8,
|
||||
actualByteLength: 513,
|
||||
code: "ASSET_SOURCE_HASH_MISMATCH",
|
||||
});
|
||||
expect(result.quarantined.receipt.actualSha256).not.toBe(result.quarantined.receipt.expected.sha256);
|
||||
expect(result.quarantined.receipt.quarantinePath).toContain("/assets/quarantine/");
|
||||
expect(result.reopened).toEqual(result.quarantined);
|
||||
expect(result.contentExists).toBe(false);
|
||||
expect(result.quarantineFileBytes).toBe(513);
|
||||
expect(result.storedRevision).toBe(result.committedRevision);
|
||||
expect(result.storedAsset).toEqual(result.quarantined.asset);
|
||||
expect({ ...result.storedAsset, preview: null }).toEqual(result.baseMetadata);
|
||||
expect(result.otherAsset).toEqual(result.baseOtherAsset);
|
||||
});
|
||||
Reference in New Issue
Block a user