chore: initialize from frontend template 4dc033c
This commit is contained in:
@@ -0,0 +1,304 @@
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import type {
|
||||
FileVerificationReceipt,
|
||||
LocalFileRef,
|
||||
} from "../../src/application/ports/browser-file-storage/file.ts";
|
||||
import {
|
||||
browserFilePolicyReference,
|
||||
createBrowserFileRuntime,
|
||||
type BrowserFilePolicyProfile,
|
||||
} from "../../src/adapters/browser-files/index.ts";
|
||||
|
||||
const filePolicy = browserFilePolicyReference(
|
||||
"runtime-file",
|
||||
"select-and-preview",
|
||||
);
|
||||
const downloadPolicy = browserFilePolicyReference(
|
||||
"runtime-download",
|
||||
"bounded-generated-bin",
|
||||
);
|
||||
const policies: readonly BrowserFilePolicyProfile[] = [
|
||||
{
|
||||
reference: filePolicy,
|
||||
selection: {
|
||||
policyId: "attachment-v1",
|
||||
purpose: "attachment",
|
||||
classification: "PERSONAL",
|
||||
multiple: false,
|
||||
maxCount: 1,
|
||||
maxFileBytes: 8,
|
||||
maxTotalBytes: 8,
|
||||
allowEmpty: false,
|
||||
accept: [{ mediaType: "text/plain", extensions: [".txt"] }],
|
||||
},
|
||||
inspection: {
|
||||
policyId: "preview-v1",
|
||||
maxInspectionBytes: 8,
|
||||
acceptedSignatures: [
|
||||
{
|
||||
mediaType: "image/png",
|
||||
extensions: [".png"],
|
||||
patterns: [{ offset: 0, bytes: [0x89] }],
|
||||
},
|
||||
],
|
||||
},
|
||||
preview: {
|
||||
allowedMediaTypes: ["image/png"],
|
||||
maxPreviewBytes: 8,
|
||||
},
|
||||
},
|
||||
{
|
||||
reference: downloadPolicy,
|
||||
download: {
|
||||
strategy: "BOUNDED_OBJECT_URL",
|
||||
mediaType: "application/octet-stream",
|
||||
safeExtension: ".bin",
|
||||
maxTransferBytes: 8,
|
||||
maxBufferedBytes: 8,
|
||||
integrity: "OPTIONAL",
|
||||
},
|
||||
},
|
||||
];
|
||||
const unusedBrowserManagedCapabilities = {
|
||||
resolve() {
|
||||
return {
|
||||
ok: false as const,
|
||||
error: {
|
||||
code: "POLICY_REJECTED" as const,
|
||||
operation: "DOWNLOAD" as const,
|
||||
retryable: false,
|
||||
recovery: "NONE" as const,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
function fileInput(): HTMLInputElement {
|
||||
const label = document.createElement("label");
|
||||
label.textContent = "Attachment";
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
label.append(input);
|
||||
document.body.append(label);
|
||||
return input;
|
||||
}
|
||||
|
||||
describe("browser file runtime hard limits and disposal", () => {
|
||||
it("revokes active download leases immediately on runtime dispose", async () => {
|
||||
const revoked: string[] = [];
|
||||
let scheduled: (() => void) | undefined;
|
||||
const runtime = createBrowserFileRuntime({
|
||||
input: fileInput(),
|
||||
policies,
|
||||
limits: {
|
||||
hardMaxPreviewBytes: 8,
|
||||
hardMaxObjectUrlBytes: 8,
|
||||
hardMaxTransferBytes: 16,
|
||||
},
|
||||
objectUrlApi: {
|
||||
createObjectURL: () => "blob:runtime-download",
|
||||
revokeObjectURL: (url) => revoked.push(url),
|
||||
},
|
||||
download: {
|
||||
host: { handoff: vi.fn() },
|
||||
browserManagedCapabilities:
|
||||
unusedBrowserManagedCapabilities,
|
||||
scheduler: {
|
||||
setTimeout(callback) {
|
||||
scheduled = callback;
|
||||
return 1;
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const delivery = {
|
||||
policy: downloadPolicy,
|
||||
source: {
|
||||
kind: "GENERATED" as const,
|
||||
bytes: {
|
||||
byteLength: 3,
|
||||
async *stream() {
|
||||
yield {
|
||||
ok: true as const,
|
||||
value: new Uint8Array([1, 2, 3]),
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
suggestedFileName: "artifact",
|
||||
maxTransferBytes: 8,
|
||||
maxBufferedBytes: 8,
|
||||
signal: new AbortController().signal,
|
||||
onProgress() {},
|
||||
};
|
||||
expect(await runtime.downloads.deliver(delivery)).toMatchObject({
|
||||
ok: true,
|
||||
value: { kind: "BROWSER_HANDOFF" },
|
||||
});
|
||||
expect(revoked).toEqual([]);
|
||||
|
||||
runtime.dispose();
|
||||
expect(revoked).toEqual(["blob:runtime-download"]);
|
||||
expect(await runtime.downloads.deliver(delivery)).toMatchObject({
|
||||
ok: false,
|
||||
error: { code: "UNAVAILABLE" },
|
||||
});
|
||||
expect(
|
||||
await runtime.content.readRange({
|
||||
ref: "file:disposed" as LocalFileRef,
|
||||
offset: 0,
|
||||
length: 0,
|
||||
signal: new AbortController().signal,
|
||||
}),
|
||||
).toMatchObject({
|
||||
ok: false,
|
||||
error: { code: "UNAVAILABLE" },
|
||||
});
|
||||
expect(
|
||||
await runtime.previews.create({
|
||||
ref: "file:disposed" as LocalFileRef,
|
||||
verificationReceipt:
|
||||
"verification:disposed" as FileVerificationReceipt,
|
||||
policy: filePolicy,
|
||||
maxPreviewBytes: 1,
|
||||
signal: new AbortController().signal,
|
||||
}),
|
||||
).toMatchObject({
|
||||
ok: false,
|
||||
error: { code: "UNAVAILABLE" },
|
||||
});
|
||||
scheduled?.();
|
||||
runtime.dispose();
|
||||
expect(revoked).toEqual(["blob:runtime-download"]);
|
||||
});
|
||||
|
||||
it("aborts a pending native picker and prevents event resurrection", async () => {
|
||||
const input = fileInput();
|
||||
Object.defineProperty(input, "showPicker", {
|
||||
configurable: true,
|
||||
value: vi.fn(),
|
||||
});
|
||||
const runtime = createBrowserFileRuntime({
|
||||
input,
|
||||
policies,
|
||||
limits: {
|
||||
hardMaxPreviewBytes: 8,
|
||||
hardMaxObjectUrlBytes: 8,
|
||||
hardMaxTransferBytes: 16,
|
||||
},
|
||||
vault: {
|
||||
createReference: () => "file:must-not-exist",
|
||||
},
|
||||
download: {
|
||||
host: { handoff() {} },
|
||||
browserManagedCapabilities:
|
||||
unusedBrowserManagedCapabilities,
|
||||
},
|
||||
});
|
||||
const pending = runtime.baselinePicker.select({
|
||||
policy: filePolicy,
|
||||
});
|
||||
|
||||
runtime.dispose();
|
||||
expect(await pending).toMatchObject({
|
||||
ok: false,
|
||||
error: { code: "ABORTED" },
|
||||
});
|
||||
Object.defineProperty(input, "files", {
|
||||
configurable: true,
|
||||
value: [
|
||||
new File(["late"], "late.txt", {
|
||||
type: "text/plain",
|
||||
lastModified: 1,
|
||||
}),
|
||||
],
|
||||
});
|
||||
input.dispatchEvent(new Event("change"));
|
||||
expect(
|
||||
await runtime.baselinePicker.select({
|
||||
policy: filePolicy,
|
||||
}),
|
||||
).toMatchObject({
|
||||
ok: false,
|
||||
error: { code: "UNAVAILABLE" },
|
||||
});
|
||||
});
|
||||
|
||||
it("aborts a generated download whose producer ignores cancellation", async () => {
|
||||
let reportStarted: (() => void) | undefined;
|
||||
const started = new Promise<void>((resolve) => {
|
||||
reportStarted = resolve;
|
||||
});
|
||||
let unblockProducer: (() => void) | undefined;
|
||||
const producerBlock = new Promise<void>((resolve) => {
|
||||
unblockProducer = resolve;
|
||||
});
|
||||
const runtime = createBrowserFileRuntime({
|
||||
input: fileInput(),
|
||||
policies,
|
||||
limits: {
|
||||
hardMaxPreviewBytes: 8,
|
||||
hardMaxObjectUrlBytes: 8,
|
||||
hardMaxTransferBytes: 16,
|
||||
},
|
||||
download: {
|
||||
host: { handoff() {} },
|
||||
browserManagedCapabilities:
|
||||
unusedBrowserManagedCapabilities,
|
||||
},
|
||||
});
|
||||
const pending = runtime.downloads.deliver({
|
||||
policy: downloadPolicy,
|
||||
source: {
|
||||
kind: "GENERATED",
|
||||
bytes: {
|
||||
byteLength: 1,
|
||||
async *stream() {
|
||||
reportStarted?.();
|
||||
await producerBlock;
|
||||
yield {
|
||||
ok: true as const,
|
||||
value: new Uint8Array([1]),
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
suggestedFileName: "blocked",
|
||||
maxTransferBytes: 8,
|
||||
maxBufferedBytes: 8,
|
||||
signal: new AbortController().signal,
|
||||
onProgress() {},
|
||||
});
|
||||
|
||||
await started;
|
||||
runtime.dispose();
|
||||
expect(await pending).toMatchObject({
|
||||
ok: false,
|
||||
error: { code: "ABORTED", operation: "DOWNLOAD" },
|
||||
});
|
||||
unblockProducer?.();
|
||||
});
|
||||
|
||||
it("rejects inconsistent absolute limits during composition", () => {
|
||||
expect(() =>
|
||||
createBrowserFileRuntime({
|
||||
input: fileInput(),
|
||||
policies,
|
||||
limits: {
|
||||
hardMaxPreviewBytes: 8,
|
||||
hardMaxObjectUrlBytes: 17,
|
||||
hardMaxTransferBytes: 16,
|
||||
},
|
||||
download: {
|
||||
host: { handoff() {} },
|
||||
browserManagedCapabilities:
|
||||
unusedBrowserManagedCapabilities,
|
||||
},
|
||||
}),
|
||||
).toThrowError("Browser file runtime hard limits are invalid.");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user