The product was materialized from the template at `4dc033c` and has stayed on it through 43 template commits, so it was missing all three rounds of adapter remediation — including files it never had, such as the shared `abortable-operation` primitive and the `exact-snapshot` decoder that later fixes are written against. Taking only the newest round was not possible for that reason: the delta is coherent only as a whole. The product had not touched `src/adapters` at all since materialization, so the 140-file delta applied with a three-way merge and no conflicts. `package.json` was the single overlap and merged cleanly: the product owns `name`, the template contributed `check:adapter-inventory`, `check:remediation-ledger` and the image-resolve-signal type fixture. All 24 product-owned files — README, index.html, CI workflow, i18n catalog, home page, generated schemas, evidence scripts, component and visual snapshots — are byte-identical to `main`. `template.lock.json` now pins the synced revision and tree. Verified in this repository, not inherited from the template: six type projects, lint, nine gates (adapter inventory, remediation ledger, registries, diagnostics, realtime boundaries, architecture, browser file/storage boundaries, optional recipes, documentation), the production build, and 2,054 of 2,073 tests. The 19 failures are all in `tests/unit/ci-artifact-contract.test.ts` and are the same pre-existing sandbox RLIMIT, EMFILE, umask and `/tmp` permission behaviour the template records; four suites that failed once under parallel load pass in isolation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
266 lines
7.9 KiB
TypeScript
266 lines
7.9 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import type { ResumableUploadCheckpoint } from "../../src/application/ports/browser-transfer/resumable-upload.ts";
|
|
import { RESUMABLE_UPLOAD_PROTOCOL } from "../../src/application/ports/browser-transfer/resumable-upload.ts";
|
|
import {
|
|
createIndexedDbResumableUploadCheckpointRuntime,
|
|
createIndexedDbResumableUploadCheckpointStore,
|
|
uploadCheckpointDatabaseName,
|
|
} from "../../src/adapters/browser-transfer/resumable-upload/indexeddb-checkpoint-store.ts";
|
|
import { MemoryIndexedDbFactory } from "../helpers/memory-indexeddb.ts";
|
|
|
|
const scope = Object.freeze({
|
|
authorityToken: "authority_token_01",
|
|
namespaceToken: "namespace_token_01",
|
|
partitionToken: "partition_token_01",
|
|
});
|
|
|
|
function checkpoint(
|
|
revision: number,
|
|
overrides: Partial<ResumableUploadCheckpoint> = {},
|
|
): ResumableUploadCheckpoint {
|
|
return {
|
|
schemaVersion: 1,
|
|
protocol: RESUMABLE_UPLOAD_PROTOCOL,
|
|
revision,
|
|
state: "ACTIVE",
|
|
uploadKey: "upload_key_01",
|
|
requestBindingSha256: "a".repeat(64),
|
|
fingerprint: {
|
|
algorithm: "SHA-256-PARTS-V1",
|
|
digestHex: "b".repeat(64),
|
|
byteLength: 4,
|
|
partSizeBytes: 4,
|
|
partCount: 1,
|
|
},
|
|
sessionId: "session_01",
|
|
sessionExpiresAtEpochMs: 5_000,
|
|
sessionMaxConcurrency: 1,
|
|
acceptedParts: [],
|
|
updatedAtEpochMs: 1_000,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function deletingFactory(
|
|
memory: MemoryIndexedDbFactory,
|
|
mode: "SUCCESS" | "BLOCKED",
|
|
): IDBFactory {
|
|
return {
|
|
open: memory.factory.open.bind(memory.factory),
|
|
cmp: memory.factory.cmp.bind(memory.factory),
|
|
deleteDatabase: () => {
|
|
const request = {
|
|
result: undefined,
|
|
error: null,
|
|
transaction: null,
|
|
source: null,
|
|
readyState: "pending",
|
|
onsuccess: null,
|
|
onerror: null,
|
|
onblocked: null,
|
|
onupgradeneeded: null,
|
|
addEventListener() {},
|
|
removeEventListener() {},
|
|
dispatchEvent: () => true,
|
|
} as unknown as IDBOpenDBRequest;
|
|
queueMicrotask(() => {
|
|
if (mode === "SUCCESS") {
|
|
request.onsuccess?.(new Event("success"));
|
|
} else {
|
|
request.onblocked?.({
|
|
oldVersion: 1,
|
|
newVersion: null,
|
|
} as IDBVersionChangeEvent);
|
|
}
|
|
});
|
|
return request;
|
|
},
|
|
databases: async () => [],
|
|
} as IDBFactory;
|
|
}
|
|
|
|
describe("IndexedDB resumable upload checkpoint", () => {
|
|
it("length-prefixes scope tuples so delimiter placement cannot collide", () => {
|
|
const first = uploadCheckpointDatabaseName({
|
|
authorityToken: "aaaaaaaa-bbbbbbbb",
|
|
namespaceToken: "cccccccc",
|
|
partitionToken: "dddddddd",
|
|
});
|
|
const second = uploadCheckpointDatabaseName({
|
|
authorityToken: "aaaaaaaa",
|
|
namespaceToken: "bbbbbbbb-cccccccc",
|
|
partitionToken: "dddddddd",
|
|
});
|
|
|
|
expect(first).not.toBe(second);
|
|
expect(first).toContain("17:aaaaaaaa-bbbbbbbb");
|
|
expect(second).toContain("8:aaaaaaaa");
|
|
});
|
|
|
|
it("commits CAS only at transaction completion and rejects stale revisions", async () => {
|
|
const memory = new MemoryIndexedDbFactory();
|
|
const store = createIndexedDbResumableUploadCheckpointStore({
|
|
scope,
|
|
factory: memory.factory,
|
|
});
|
|
const first = checkpoint(1);
|
|
expect(
|
|
await store.compareAndSwap({
|
|
expectedRevision: null,
|
|
checkpoint: first,
|
|
}),
|
|
).toEqual({ ok: true, value: first });
|
|
expect(await store.read(first.uploadKey)).toEqual({
|
|
ok: true,
|
|
value: first,
|
|
});
|
|
|
|
memory.failNextWriteCommit(
|
|
new DOMException("commit failed", "UnknownError"),
|
|
);
|
|
const failed = await store.compareAndSwap({
|
|
expectedRevision: 1,
|
|
checkpoint: checkpoint(2, { state: "ABORT_PENDING" }),
|
|
});
|
|
expect(failed).toMatchObject({
|
|
ok: false,
|
|
error: { code: "UNAVAILABLE" },
|
|
});
|
|
expect(await store.read(first.uploadKey)).toEqual({
|
|
ok: true,
|
|
value: first,
|
|
});
|
|
|
|
expect(
|
|
await store.compareAndSwap({
|
|
expectedRevision: 2,
|
|
checkpoint: checkpoint(3),
|
|
}),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: { code: "CONFLICT", recovery: "RECONCILE" },
|
|
});
|
|
});
|
|
|
|
it("rejects unknown persisted fields so URLs and credentials cannot enter a checkpoint", async () => {
|
|
const memory = new MemoryIndexedDbFactory();
|
|
const store = createIndexedDbResumableUploadCheckpointStore({
|
|
scope,
|
|
factory: memory.factory,
|
|
});
|
|
const smuggled = {
|
|
...checkpoint(1),
|
|
signedUrl: "https://object.invalid/secret?signature=value",
|
|
} as unknown as ResumableUploadCheckpoint;
|
|
expect(
|
|
await store.compareAndSwap({
|
|
expectedRevision: null,
|
|
checkpoint: smuggled,
|
|
}),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: { code: "INVALID_INPUT" },
|
|
});
|
|
expect(await store.read("upload_key_01")).toEqual({
|
|
ok: true,
|
|
value: null,
|
|
});
|
|
});
|
|
|
|
it("closes the bound partition before successful lifecycle deletion", async () => {
|
|
const memory = new MemoryIndexedDbFactory();
|
|
const runtime = createIndexedDbResumableUploadCheckpointRuntime({
|
|
scope,
|
|
factory: deletingFactory(memory, "SUCCESS"),
|
|
blockedTimeoutMs: 10,
|
|
});
|
|
expect(
|
|
await runtime.store.compareAndSwap({
|
|
expectedRevision: null,
|
|
checkpoint: checkpoint(1),
|
|
}),
|
|
).toMatchObject({ ok: true });
|
|
expect(await runtime.admin.deletePartition()).toEqual({
|
|
ok: true,
|
|
value: { state: "DELETED", effect: "APPLIED" },
|
|
});
|
|
expect(await runtime.store.read("upload_key_01")).toMatchObject({
|
|
ok: false,
|
|
error: { code: "UNAVAILABLE", recovery: "RESUME" },
|
|
});
|
|
});
|
|
|
|
it("returns PENDING UNKNOWN when deleteDatabase is still blocked", async () => {
|
|
const memory = new MemoryIndexedDbFactory();
|
|
const factory = deletingFactory(memory, "BLOCKED");
|
|
const runtime = createIndexedDbResumableUploadCheckpointRuntime({
|
|
scope,
|
|
factory,
|
|
blockedTimeoutMs: 1,
|
|
});
|
|
// BT-UP-03. The native request is still live, so the deadline is not
|
|
// evidence that nothing happened.
|
|
expect(await runtime.admin.deletePartition()).toEqual({
|
|
ok: true,
|
|
value: {
|
|
state: "PENDING",
|
|
effect: "UNKNOWN",
|
|
reason: "BLOCKED_DEADLINE",
|
|
},
|
|
});
|
|
});
|
|
|
|
it("keeps the checkpoint store closed until a pending delete is resolved externally", async () => {
|
|
const memory = new MemoryIndexedDbFactory();
|
|
const factory = deletingFactory(memory, "BLOCKED");
|
|
const runtime = createIndexedDbResumableUploadCheckpointRuntime({
|
|
scope,
|
|
factory,
|
|
blockedTimeoutMs: 1,
|
|
});
|
|
expect(await runtime.admin.deletePartition()).toMatchObject({
|
|
ok: true,
|
|
value: { state: "PENDING" },
|
|
});
|
|
|
|
// A second runtime over the same realm and database would race an unknown
|
|
// native effect.
|
|
expect(() =>
|
|
createIndexedDbResumableUploadCheckpointRuntime({
|
|
scope,
|
|
factory,
|
|
blockedTimeoutMs: 1,
|
|
}),
|
|
).toThrow(TypeError);
|
|
});
|
|
|
|
it("never reports a false abort after irreversible deleteDatabase dispatch", async () => {
|
|
const memory = new MemoryIndexedDbFactory();
|
|
const runtime = createIndexedDbResumableUploadCheckpointRuntime({
|
|
scope,
|
|
factory: deletingFactory(memory, "SUCCESS"),
|
|
});
|
|
const controller = new AbortController();
|
|
const deletion = runtime.admin.deletePartition(controller.signal);
|
|
controller.abort();
|
|
expect(await deletion).toEqual({
|
|
ok: true,
|
|
value: { state: "DELETED", effect: "APPLIED" },
|
|
});
|
|
|
|
const preAborted = new AbortController();
|
|
preAborted.abort();
|
|
const second = createIndexedDbResumableUploadCheckpointRuntime({
|
|
scope: { ...scope, partitionToken: "partition_token_02" },
|
|
factory: deletingFactory(new MemoryIndexedDbFactory(), "SUCCESS"),
|
|
});
|
|
expect(
|
|
await second.admin.deletePartition(preAborted.signal),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: { code: "ABORTED" },
|
|
});
|
|
});
|
|
});
|