Files
tech-log-frontend/tests/unit/browser-file-policy-registry.test.ts

282 lines
7.3 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import {
BrowserFilePolicyRegistry,
browserFilePolicyReference,
} from "../../src/adapters/browser-files/browser-file-policy-registry.ts";
import { BrowserFileVault } from "../../src/adapters/browser-files/browser-file-vault.ts";
import {
BrowserTransientPreview,
ObjectUrlLeaseRegistry,
} from "../../src/adapters/browser-files/object-url-lease.ts";
const hardLimits = Object.freeze({
maxInspectionBytes: 64,
maxRetainedFileBytes: 1_024,
maxPreviewBytes: 1_024,
maxObjectUrlBytes: 1_024,
maxTransferBytes: 1_024,
});
describe("browser file composition policy registry", () => {
it("deep-snapshots every policy axis and permits only limit reductions", () => {
const reference = browserFilePolicyReference(
"documents",
"select-preview-download-pdf",
);
const selection = {
policyId: "documents-v1",
purpose: "document",
classification: "PERSONAL" as const,
multiple: false,
maxCount: 1,
maxFileBytes: 100,
maxTotalBytes: 100,
allowEmpty: false,
accept: [
{ mediaType: "application/pdf", extensions: [".pdf"] },
],
};
const inspection = {
policyId: "documents-v1",
maxInspectionBytes: 8,
acceptedSignatures: [
{
mediaType: "application/pdf",
extensions: [".pdf"],
patterns: [{ offset: 0, bytes: [0x25, 0x50] }],
},
],
};
const preview = {
allowedMediaTypes: ["application/pdf"],
maxPreviewBytes: 100,
};
const download = {
strategy: "PROMPT_AND_STREAM" as const,
mediaType: "application/pdf",
safeExtension: ".pdf",
maxTransferBytes: 100,
maxBufferedBytes: 50,
integrity: "REQUIRED" as const,
};
const registry = new BrowserFilePolicyRegistry({
profiles: [
{
reference,
selection,
inspection,
preview,
download,
},
],
hardLimits,
});
selection.maxFileBytes = 1;
selection.accept[0]!.extensions[0] = ".exe";
inspection.acceptedSignatures[0]!.patterns[0]!.bytes[0] = 0;
preview.allowedMediaTypes[0] = "text/html";
(
download as { strategy: string }
).strategy = "BROWSER_MANAGED";
download.safeExtension = ".exe";
expect(
registry.resolveSelection(reference, {
maxFileBytes: 80,
}),
).toMatchObject({
ok: true,
value: {
maxFileBytes: 80,
accept: [{ extensions: [".pdf"] }],
},
});
expect(
registry.resolveSelection(reference, {
maxFileBytes: 101,
}),
).toMatchObject({
ok: false,
error: { code: "LIMIT_EXCEEDED" },
});
expect(registry.resolveInspection(reference)).toMatchObject({
ok: true,
value: {
acceptedSignatures: [
{ patterns: [{ bytes: [0x25, 0x50] }] },
],
},
});
const resolvedPreview = registry.resolvePreview(reference);
expect(resolvedPreview.ok).toBe(true);
if (resolvedPreview.ok) {
expect(
resolvedPreview.value.allowedMediaTypes.has(
"application/pdf",
),
).toBe(true);
expect(
resolvedPreview.value.allowedMediaTypes.has("text/html"),
).toBe(false);
(
resolvedPreview.value.allowedMediaTypes as Set<string>
).clear();
expect(
registry.resolvePreview(reference),
).toMatchObject({
ok: true,
value: {
allowedMediaTypes: new Set(["application/pdf"]),
},
});
}
expect(
registry.resolveDownload(reference, {}),
).toMatchObject({
ok: true,
value: {
strategy: "PROMPT_AND_STREAM",
safeExtension: ".pdf",
},
});
expect(
registry.resolveDownload(
browserFilePolicyReference("unknown", "unknown"),
{},
),
).toMatchObject({
ok: false,
error: { code: "POLICY_REJECTED" },
});
expect(
registry.resolveSelection(
browserFilePolicyReference(
"documents",
"select-preview-download-pdf",
),
),
).toMatchObject({
ok: false,
error: { code: "POLICY_REJECTED" },
});
expect(
registry.resolveSelection(
{
policyKey: "documents",
intention: "select-preview-download-pdf",
} as typeof reference,
),
).toMatchObject({
ok: false,
error: { code: "POLICY_REJECTED" },
});
});
it("binds verification receipts to the exact profile, not a reused policyId", async () => {
const profileA = browserFilePolicyReference(
"images-a",
"preview-avatar",
);
const profileB = browserFilePolicyReference(
"images-b",
"preview-banner",
);
const inspection = {
policyId: "shared-png-v1",
maxInspectionBytes: 8,
acceptedSignatures: [
{
mediaType: "image/png",
extensions: [".png"],
patterns: [{ offset: 0, bytes: [0x89, 0x50] }],
},
],
};
const policies = new BrowserFilePolicyRegistry({
profiles: [profileA, profileB].map((reference) => ({
reference,
inspection,
preview: {
allowedMediaTypes: ["image/png"],
maxPreviewBytes: 16,
},
})),
hardLimits,
});
const vault = new BrowserFileVault({
policies,
createReference: () => "file:profile-bound",
createVerificationReceipt: () =>
"verification:profile-bound",
});
const captured = vault.captureFiles(
[
new File(
[new Uint8Array([0x89, 0x50, 1])],
"avatar.png",
{ type: "image/png", lastModified: 1 },
),
],
{
policyId: "capture-v1",
purpose: "capture",
classification: "PERSONAL",
multiple: false,
maxCount: 1,
maxFileBytes: 16,
maxTotalBytes: 16,
allowEmpty: false,
accept: [
{ mediaType: "image/png", extensions: [".png"] },
],
},
);
expect(captured.ok).toBe(true);
if (!captured.ok || !captured.value[0]) return;
const inspected = await vault.inspect({
ref: captured.value[0].ref,
policy: profileA,
signal: new AbortController().signal,
});
expect(inspected.ok).toBe(true);
if (!inspected.ok || !inspected.value.verificationReceipt) {
return;
}
const createObjectURL = vi.fn(() => "blob:profile-bound");
const previews = new BrowserTransientPreview({
files: vault,
policies,
hardMaxPreviewBytes: 16,
leases: new ObjectUrlLeaseRegistry({
createObjectURL,
revokeObjectURL() {},
}),
});
expect(
await previews.create({
ref: captured.value[0].ref,
verificationReceipt:
inspected.value.verificationReceipt,
policy: profileB,
signal: new AbortController().signal,
}),
).toMatchObject({
ok: false,
error: { code: "POLICY_REJECTED" },
});
expect(createObjectURL).not.toHaveBeenCalled();
expect(
await previews.create({
ref: captured.value[0].ref,
verificationReceipt:
inspected.value.verificationReceipt,
policy: profileA,
signal: new AbortController().signal,
}),
).toMatchObject({ ok: true });
});
});