Files
tech-log-frontend/tests/recipes/browser-file-storage-contracts.test.ts
T

674 lines
19 KiB
TypeScript

import { describe, expect, expectTypeOf, it } from "vitest";
import {
MemoryDurableObjectStore,
MemoryBrowserFilePolicyRegistry,
MemoryExampleQuarantinedUploadAdapter,
MemoryFileSelectionAdapter,
MemoryPublicResponseCache,
MemoryStorageDurabilityAdapter,
MemoryStructuredOfflineStore,
MemoryTransientPreviewAdapter,
RecordingDownloadDeliveryAdapter,
asByteCount,
asBrowserManagedDownloadCapabilityReceipt,
asDurableObjectId,
asLocalFileRef,
asObjectGeneration,
browserFilePolicyReference,
failure,
sanitizeDownloadFileName,
} from "../../recipes/frontend-capabilities/index.ts";
import type {
BrowserFileComposition,
BrowserPersistencePolicy,
DurableObjectStorageComposition,
ExampleBackendUploadComposition,
PublicResponseCacheComposition,
StorageDurabilityComposition,
StructuredOfflineStorageComposition,
} from "../../recipes/frontend-capabilities/index.ts";
async function* byteChunks(
...values: ReadonlyArray<string>
): AsyncIterable<Readonly<{ ok: true; value: Uint8Array }>> {
for (const value of values) {
yield {
ok: true,
value: new TextEncoder().encode(value),
};
}
}
async function* failedByteChunks() {
yield failure(
"NOT_READABLE",
true,
"The fixture source became unreadable.",
);
}
describe("production browser file and storage recipe contracts", () => {
it("separates picker dismissal, metadata policy and bounded content reads", async () => {
const ref = asLocalFileRef("opaque-local-1");
const selectionPolicy = {
policyId: "support-attachment-v1",
purpose: "support-attachment",
classification: "PERSONAL" as const,
multiple: false,
maxCount: 1,
maxFileBytes: asByteCount(10),
maxTotalBytes: asByteCount(10),
allowEmpty: false,
accept: [{ mediaType: "text/plain", extensions: [".txt"] }],
};
const filePolicy = browserFilePolicyReference(
"support-attachment",
"select-inspect-preview-text",
);
const policies = new MemoryBrowserFilePolicyRegistry([
{
reference: filePolicy,
selection: selectionPolicy,
inspection: {
policyId: selectionPolicy.policyId,
maxInspectionBytes: asByteCount(1_445),
acceptedSignatures: [
{
mediaType: "text/plain",
extensions: [".txt"],
patterns: [
{
offset: asByteCount(0),
bytes: [104],
},
],
},
],
},
preview: {
allowedMediaTypes: ["text/plain"],
maxPreviewBytes: asByteCount(10),
},
},
]);
const adapter = new MemoryFileSelectionAdapter(
[
{
ref,
displayName: "notes.txt",
bytes: new TextEncoder().encode("hello"),
reportedMediaType: "text/plain",
detectedMediaType: "text/plain",
signature: "MATCHED",
},
],
policies,
);
adapter.setNextOutcome("DISMISSED");
expect(await adapter.select({ policy: filePolicy })).toEqual({
ok: true,
value: { kind: "DISMISSED" },
});
const selected = await adapter.select({ policy: filePolicy });
expect(selected.ok && selected.value.kind).toBe("SELECTED");
expect(
await adapter.select({
policy: browserFilePolicyReference(
"support-attachment",
"select-inspect-preview-text",
),
}),
).toMatchObject({
ok: false,
failure: { code: "POLICY_REJECTED" },
});
const inspected = await adapter.inspect({
ref,
policy: filePolicy,
signal: new AbortController().signal,
});
expect(inspected).toMatchObject({
ok: true,
value: {
detectedMediaType: "text/plain",
signature: "MATCHED",
},
});
if (!inspected.ok || !inspected.value.verificationReceipt) return;
expect(
await adapter.readRange({
ref,
offset: asByteCount(1),
length: asByteCount(3),
signal: new AbortController().signal,
}),
).toEqual({
ok: true,
value: new TextEncoder().encode("ell"),
});
const previews = new MemoryTransientPreviewAdapter(
adapter,
policies,
);
const preview = await previews.create({
ref,
verificationReceipt: inspected.value.verificationReceipt,
policy: filePolicy,
maxPreviewBytes: asByteCount(10),
signal: new AbortController().signal,
});
expect(preview.ok).toBe(true);
expect(previews.activeLeaseCount).toBe(1);
if (preview.ok) {
preview.value.release();
preview.value.release();
}
expect(previews.activeLeaseCount).toBe(0);
previews.dispose();
expect(
await previews.create({
ref,
verificationReceipt: inspected.value.verificationReceipt,
policy: filePolicy,
maxPreviewBytes: asByteCount(10),
signal: new AbortController().signal,
}),
).toMatchObject({
ok: false,
failure: { code: "PROVIDER_UNAVAILABLE" },
});
adapter.release(ref);
expect(
(
await adapter.readRange({
ref,
offset: asByteCount(0),
length: asByteCount(1),
signal: new AbortController().signal,
})
).ok,
).toBe(false);
});
it("keeps the optional backend upload example resumable and quarantined", async () => {
const adapter = new MemoryExampleQuarantinedUploadAdapter(
asByteCount(16),
asByteCount(4),
() => Date.parse("2026-07-27T00:00:00.000Z"),
);
const signal = new AbortController().signal;
const created = await adapter.create({
purpose: "avatar",
byteLength: asByteCount(4),
detectedMediaType: "image/png",
signal,
});
expect(created.ok).toBe(true);
if (!created.ok) return;
const part = await adapter.uploadPart({
sessionId: created.value.sessionId,
partNumber: 1,
offset: asByteCount(0),
bytes: new Uint8Array([1, 2, 3, 4]),
checksumSha256: "part-checksum",
idempotencyKey: "part-one-attempt",
signal,
});
expect(part.ok).toBe(true);
const replay = await adapter.uploadPart({
sessionId: created.value.sessionId,
partNumber: 1,
offset: asByteCount(0),
bytes: new Uint8Array([1, 2, 3, 4]),
checksumSha256: "part-checksum",
idempotencyKey: "part-one-attempt",
signal,
});
expect(replay).toEqual(part);
expect(
await adapter.uploadPart({
sessionId: created.value.sessionId,
partNumber: 1,
offset: asByteCount(0),
bytes: new Uint8Array([9, 9, 9, 9]),
checksumSha256: "different-request-body",
idempotencyKey: "part-one-attempt",
signal,
}),
).toMatchObject({ ok: false, failure: { code: "CONFLICT" } });
if (!part.ok) return;
expect(
await adapter.complete({
sessionId: created.value.sessionId,
parts: [part.value],
signal,
}),
).toMatchObject({
ok: true,
value: { state: "QUARANTINED" },
});
});
it("keeps each browser technology independently composable", () => {
expectTypeOf<keyof BrowserFileComposition>().toEqualTypeOf<
"picker" | "content" | "previews" | "downloads"
>();
expectTypeOf<
keyof StructuredOfflineStorageComposition<unknown>
>().toEqualTypeOf<"store" | "maintenance">();
expectTypeOf<
keyof StorageDurabilityComposition
>().toEqualTypeOf<"durability">();
expectTypeOf<
keyof DurableObjectStorageComposition
>().toEqualTypeOf<"store" | "maintenance">();
expectTypeOf<
keyof PublicResponseCacheComposition
>().toEqualTypeOf<"cache">();
expectTypeOf<
keyof ExampleBackendUploadComposition
>().toEqualTypeOf<"upload">();
expectTypeOf<
BrowserPersistencePolicy["logoutAction"]
>().toEqualTypeOf<
"KEEP_ORIGIN_SHARED" | "PURGE_PARTITION" | "EXPORT_THEN_PURGE"
>();
expectTypeOf<
BrowserPersistencePolicy["accountDeletionAction"]
>().toEqualTypeOf<"KEEP_ORIGIN_SHARED" | "PURGE_PARTITION">();
expectTypeOf<
BrowserPersistencePolicy["pressureAction"]
>().toEqualTypeOf<"EVICT_RECONSTRUCTABLE" | "RETAIN">();
});
it("distinguishes browser handoff from a verified streamed save", async () => {
const browserPolicy = browserFilePolicyReference(
"reports",
"browser-managed-pdf",
);
const streamPolicy = browserFilePolicyReference(
"exports",
"stream-text",
);
const policies = new MemoryBrowserFilePolicyRegistry([
{
reference: browserPolicy,
download: {
strategy: "BROWSER_MANAGED",
mediaType: "application/pdf",
safeExtension: ".pdf",
maxTransferBytes: asByteCount(10),
maxBufferedBytes: asByteCount(10),
integrity: "OPTIONAL",
},
},
{
reference: streamPolicy,
download: {
strategy: "PROMPT_AND_STREAM",
mediaType: "text/plain",
safeExtension: ".txt",
maxTransferBytes: asByteCount(10),
maxBufferedBytes: asByteCount(10),
integrity: "REQUIRED",
},
},
]);
const capabilityReceipt =
asBrowserManagedDownloadCapabilityReceipt(
"capability:report",
);
const adapter = new RecordingDownloadDeliveryAdapter(
policies,
{
resolve(input) {
return {
ok: true,
value: {
capabilityReceipt,
href: "/downloads/report",
resourceId: input.resourceId,
mediaType: "application/pdf",
safeExtension: ".pdf",
maxBytes: asByteCount(10),
expiresAtEpochMs: 2_000,
},
};
},
},
() => 1_000,
);
const progress: number[] = [];
expect(sanitizeDownloadFileName("../CON\u202e.exe")).toBe("download.bin");
const handedOff = await adapter.deliver({
policy: browserPolicy,
source: {
kind: "BROWSER_MANAGED_RESOURCE",
resourceId: "opaque-resource",
capabilityReceipt,
},
suggestedFileName: "report.pdf",
maxTransferBytes: asByteCount(10),
maxBufferedBytes: asByteCount(10),
signal: new AbortController().signal,
onProgress() {},
});
expect(handedOff).toMatchObject({
ok: true,
value: { kind: "BROWSER_HANDOFF" },
});
expect(
await adapter.deliver({
policy: browserPolicy,
source: {
kind: "BROWSER_MANAGED_RESOURCE",
resourceId: "opaque-resource",
capabilityReceipt:
asBrowserManagedDownloadCapabilityReceipt(
"capability:forged",
),
},
suggestedFileName: "report.pdf",
signal: new AbortController().signal,
onProgress() {},
}),
).toMatchObject({
ok: false,
failure: { code: "POLICY_REJECTED" },
});
const saved = await adapter.deliver({
policy: streamPolicy,
source: {
kind: "GENERATED",
bytes: {
byteLength: asByteCount(4),
stream: () => byteChunks("ab", "cd"),
},
expectedSha256:
"88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589",
},
suggestedFileName: "safe.txt",
maxTransferBytes: asByteCount(10),
maxBufferedBytes: asByteCount(10),
signal: new AbortController().signal,
onProgress(value) {
progress.push(value.transferredBytes);
},
});
expect(saved).toMatchObject({
ok: true,
value: {
kind: "SAVED",
bytesWritten: 4,
integrity: "VERIFIED",
},
});
expect(progress).toEqual([2, 4, 4]);
});
it("commits IndexedDB-like mutations atomically with CAS and idempotency", async () => {
const store = new MemoryStructuredOfflineStore<{ title: string }>({
maxBytes: 10_000,
now: () => 1_000,
});
expect(
(
await store.open({
partitionKey: "opaque-account-partition",
onLifecycle() {},
})
).ok,
).toBe(true);
const createMutation = {
kind: "PUT",
id: "one",
value: { title: "first" },
payloadVersion: 1,
expiresAtEpochMs: null,
revision: { kind: "MUST_NOT_EXIST" },
} as const;
const committed = await store.commit({
idempotencyKey: "create-one",
mutations: [createMutation],
});
expect(committed).toMatchObject({
ok: true,
value: { replayed: false, revisions: { one: 1 } },
});
expect(await store.commit({
idempotencyKey: "create-one",
mutations: [createMutation],
})).toMatchObject({ ok: true, value: { replayed: true } });
expect(
await store.commit({
idempotencyKey: "create-one",
mutations: [
{
kind: "DELETE",
id: "one",
revision: { kind: "ANY" },
},
],
}),
).toMatchObject({ ok: false, failure: { code: "CONFLICT" } });
const rejectedBatch = await store.commit({
idempotencyKey: "atomic-conflict",
mutations: [
{
kind: "PUT",
id: "two",
value: { title: "must-not-commit" },
payloadVersion: 1,
expiresAtEpochMs: null,
revision: { kind: "MUST_NOT_EXIST" },
},
{
kind: "PUT",
id: "one",
value: { title: "stale" },
payloadVersion: 1,
expiresAtEpochMs: null,
revision: { kind: "MATCH", revision: 999 },
},
],
});
expect(rejectedBatch).toMatchObject({
ok: false,
failure: { code: "CONFLICT" },
});
expect(await store.read("two")).toEqual({ ok: true, value: null });
});
it("makes quota and persistence downgrade explicit", async () => {
const store = new MemoryStructuredOfflineStore<{ value: string }>({
maxBytes: 16,
});
await store.open({ partitionKey: "partition", onLifecycle() {} });
expect(
await store.commit({
idempotencyKey: "too-large",
mutations: [
{
kind: "PUT",
id: "one",
value: { value: "far larger than the approved storage budget" },
payloadVersion: 1,
expiresAtEpochMs: null,
revision: { kind: "MUST_NOT_EXIST" },
},
],
}),
).toMatchObject({
ok: false,
failure: { code: "QUOTA_EXCEEDED" },
});
const durability = new MemoryStorageDurabilityAdapter(
70,
100,
"DENIED",
);
expect(await durability.inspect()).toMatchObject({
ok: true,
value: { pressure: "PRESSURE" },
});
expect(
await durability.requestPersistence({
reason: "PROTECT_UNSYNCED_USER_DATA",
userInitiated: true,
}),
).toEqual({ ok: true, value: "DENIED" });
});
it("keeps partially written OPFS objects invisible until logical commit", async () => {
const store = new MemoryDurableObjectStore(asByteCount(32));
const id = asDurableObjectId("opaque-object");
store.failNextPutAfter("FILES_READY");
const failed = await store.put({
id,
expectedGeneration: null,
source: byteChunks("ab", "cd"),
declaredByteLength: asByteCount(4),
mediaType: "application/octet-stream",
dataClass: "RECONSTRUCTABLE",
retention: { kind: "EXPIRES", expiresAt: "2026-08-01T00:00:00.000Z" },
signal: new AbortController().signal,
onProgress() {},
});
expect(failed).toMatchObject({
ok: false,
failure: { code: "PROVIDER_UNAVAILABLE" },
});
expect(await store.open(id)).toMatchObject({
ok: false,
failure: { code: "NOT_FOUND" },
});
expect(
await store.reconcile({ timeBudgetMs: 100, maxEntries: 10 }),
).toMatchObject({ ok: true, value: { purgedCount: 1 } });
expect(
await store.put({
id,
expectedGeneration: null,
source: failedByteChunks(),
declaredByteLength: asByteCount(4),
mediaType: null,
dataClass: "RECONSTRUCTABLE",
retention: { kind: "EXPLICIT_DELETE" },
signal: new AbortController().signal,
onProgress() {},
}),
).toMatchObject({
ok: false,
failure: { code: "NOT_READABLE" },
});
expect(
await store.reconcile({ timeBudgetMs: 100, maxEntries: 10 }),
).toMatchObject({ ok: true, value: { purgedCount: 1 } });
const committed = await store.put({
id,
expectedGeneration: null,
source: byteChunks("abcd"),
declaredByteLength: asByteCount(4),
mediaType: null,
dataClass: "RECONSTRUCTABLE",
retention: { kind: "EXPLICIT_DELETE" },
signal: new AbortController().signal,
onProgress() {},
});
expect(committed).toMatchObject({
ok: true,
value: {
generation: 1,
integrity: { algorithm: "SHA-256-TREE-V1" },
},
});
const opened = await store.open(id);
expect(opened.ok).toBe(true);
if (opened.ok) {
const chunks: number[] = [];
for await (const chunkResult of opened.value.chunks) {
expect(chunkResult.ok).toBe(true);
if (chunkResult.ok) chunks.push(...chunkResult.value);
}
expect(new TextDecoder().decode(new Uint8Array(chunks))).toBe(
"abcd",
);
}
expect(
await store.remove({
id,
expectedGeneration: asObjectGeneration(2),
}),
).toMatchObject({ ok: false, failure: { code: "CONFLICT" } });
});
it("stages and activates only exact public Cache Storage representations", async () => {
const cache = new MemoryPublicResponseCache({
origin: "https://app.example.test",
maxEntryBytes: 1_000,
allowedMediaTypes: new Set(["text/javascript"]),
});
const entry = {
url: "https://app.example.test/assets/app.js?v=1#ignored",
byteLength: asByteCount(100),
mediaType: "text/javascript",
integritySha256: "sha256-entry",
requestCredentials: "OMIT" as const,
dataClass: "PUBLIC" as const,
};
expect(
await cache.stageRelease({
releaseId: "release-1",
manifestDigest: "manifest-1",
entries: [entry],
signal: new AbortController().signal,
}),
).toEqual({ ok: true, value: undefined });
expect(
await cache.activateRelease({
releaseId: "release-1",
manifestDigest: "manifest-1",
expectedPreviousReleaseId: null,
}),
).toEqual({ ok: true, value: undefined });
expect(
await cache.lookup({
url: "https://app.example.test/assets/app.js?v=1",
requestCredentials: "OMIT",
hasAuthorization: false,
}),
).toMatchObject({ ok: true, value: { kind: "HIT" } });
expect(
await cache.lookup({
url: "https://app.example.test/assets/app.js?v=2",
requestCredentials: "OMIT",
hasAuthorization: false,
}),
).toEqual({
ok: true,
value: { kind: "MISS", reason: "NOT_FOUND" },
});
expect(
await cache.stageRelease({
releaseId: "poisoned",
manifestDigest: "bad",
entries: [{ ...entry, url: "https://app.example.test/config.json" }],
signal: new AbortController().signal,
}),
).toMatchObject({
ok: false,
failure: { code: "POLICY_REJECTED" },
});
});
});