550 lines
17 KiB
TypeScript
550 lines
17 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
|
|
import type { BrowserDataObservation } from "../../src/application/ports/browser-file-storage/shared.ts";
|
|
import { sha256Hex } from "../../src/adapters/browser-transfer/presigned/incremental-sha256.ts";
|
|
import {
|
|
CHECKSUM_HEADER,
|
|
CONTROL_ENDPOINT,
|
|
DATA_ORIGIN,
|
|
DIGEST_HEADER,
|
|
DOWNLOAD_HREF,
|
|
DOWNLOAD_PATH,
|
|
NOW,
|
|
POLICY_HEADER,
|
|
REQUEST_BINDING_SHA256,
|
|
UPLOAD_SESSION_ID,
|
|
collect,
|
|
createHarness,
|
|
downloadCapabilityPayload,
|
|
downloadResponse,
|
|
jsonResponse,
|
|
responseWithUrl,
|
|
uploadCapabilityPayload,
|
|
} from "./presigned-transfer-fixture.ts";
|
|
|
|
describe("presigned upload part execution", () => {
|
|
it("snapshots, verifies and uploads a PUT part with a separate response receipt", async () => {
|
|
const original = new Uint8Array([9, 8, 7]);
|
|
const checksum = sha256Hex(original);
|
|
const payload = uploadCapabilityPayload({
|
|
bytes: original,
|
|
checksum,
|
|
});
|
|
let releaseDigest: (() => void) | undefined;
|
|
const digestGate = new Promise<void>((resolve) => {
|
|
releaseDigest = resolve;
|
|
});
|
|
const sentBodies: number[][] = [];
|
|
const dataCalls: RequestInit[] = [];
|
|
const fetcher = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
if (String(input) === CONTROL_ENDPOINT) return jsonResponse(payload);
|
|
dataCalls.push(init ?? {});
|
|
sentBodies.push([
|
|
...new Uint8Array(init?.body as ArrayBuffer),
|
|
]);
|
|
return responseWithUrl(
|
|
new Response(null, {
|
|
status: 200,
|
|
headers: {
|
|
[POLICY_HEADER]: "v1",
|
|
"Content-Length": "0",
|
|
ETag: "\"part-etag-1\"",
|
|
},
|
|
}),
|
|
String(payload.href),
|
|
);
|
|
}) as unknown as typeof fetch;
|
|
const { provider, executor } = createHarness({
|
|
fetcher,
|
|
digestBytes: async (bytes) => {
|
|
await digestGate;
|
|
return sha256Hex(bytes);
|
|
},
|
|
});
|
|
const issued = await provider.issueUploadPart({
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: original.byteLength,
|
|
checksumSha256: checksum,
|
|
mediaType: "application/octet-stream",
|
|
idempotencyKey: "part-attempt-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(issued.ok).toBe(true);
|
|
if (!issued.ok) return;
|
|
const request = {
|
|
capability: issued.value,
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: original.byteLength,
|
|
checksumSha256: checksum,
|
|
idempotencyKey: "part-attempt-1",
|
|
bytes: original,
|
|
signal: new AbortController().signal,
|
|
};
|
|
const pending = executor.uploadParts.put(request);
|
|
original.fill(0);
|
|
request.sessionId = "mutated-session";
|
|
request.requestBindingSha256 = "d".repeat(64);
|
|
request.checksumSha256 = "f".repeat(64);
|
|
releaseDigest?.();
|
|
|
|
expect(await pending).toEqual({
|
|
ok: true,
|
|
value: {
|
|
bytesWritten: 3,
|
|
checksumSha256: checksum,
|
|
receiptToken: "part-etag-1",
|
|
},
|
|
});
|
|
expect(sentBodies).toEqual([[9, 8, 7]]);
|
|
expect(dataCalls[0]).toMatchObject({
|
|
method: "PUT",
|
|
credentials: "omit",
|
|
redirect: "error",
|
|
referrerPolicy: "no-referrer",
|
|
});
|
|
expect(
|
|
(dataCalls[0]?.headers as Headers).get(CHECKSUM_HEADER),
|
|
).toBe(checksum);
|
|
});
|
|
|
|
it.each(["sessionId", "requestBindingSha256"] as const)(
|
|
"rejects an actual PUT whose %s differs from the capability",
|
|
async (field) => {
|
|
const bytes = new Uint8Array([3, 2, 1]);
|
|
const checksum = sha256Hex(bytes);
|
|
const payload = uploadCapabilityPayload({ bytes, checksum });
|
|
const fetcher = vi.fn(async () =>
|
|
jsonResponse(payload),
|
|
) as unknown as typeof fetch;
|
|
const { provider, executor } = createHarness({ fetcher });
|
|
const issued = await provider.issueUploadPart({
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: bytes.byteLength,
|
|
checksumSha256: checksum,
|
|
mediaType: "application/octet-stream",
|
|
idempotencyKey: "part-attempt-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(issued.ok).toBe(true);
|
|
if (!issued.ok) return;
|
|
expect(
|
|
await executor.uploadParts.put({
|
|
capability: issued.value,
|
|
sessionId:
|
|
field === "sessionId"
|
|
? "different-session"
|
|
: UPLOAD_SESSION_ID,
|
|
requestBindingSha256:
|
|
field === "requestBindingSha256"
|
|
? "d".repeat(64)
|
|
: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: bytes.byteLength,
|
|
checksumSha256: checksum,
|
|
idempotencyKey: "part-attempt-1",
|
|
bytes,
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: { code: "POLICY_REJECTED" },
|
|
});
|
|
expect(fetcher).toHaveBeenCalledTimes(1);
|
|
},
|
|
);
|
|
|
|
it("rejects URL-shaped upload receipts", async () => {
|
|
const bytes = new Uint8Array([4, 5, 6]);
|
|
const checksum = sha256Hex(bytes);
|
|
const payload = uploadCapabilityPayload({ bytes, checksum });
|
|
const fetcher = vi.fn(async (input: RequestInfo | URL) => {
|
|
if (String(input) === CONTROL_ENDPOINT) {
|
|
return jsonResponse(payload);
|
|
}
|
|
return responseWithUrl(
|
|
new Response(null, {
|
|
status: 200,
|
|
headers: {
|
|
[POLICY_HEADER]: "v1",
|
|
"Content-Length": "0",
|
|
ETag: "\"https://objects.example/authorizing-token\"",
|
|
},
|
|
}),
|
|
String(payload.href),
|
|
);
|
|
}) as unknown as typeof fetch;
|
|
const { provider, executor } = createHarness({ fetcher });
|
|
const issued = await provider.issueUploadPart({
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: bytes.byteLength,
|
|
checksumSha256: checksum,
|
|
mediaType: "application/octet-stream",
|
|
idempotencyKey: "part-attempt-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(issued.ok).toBe(true);
|
|
if (!issued.ok) return;
|
|
expect(
|
|
await executor.uploadParts.put({
|
|
capability: issued.value,
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: bytes.byteLength,
|
|
checksumSha256: checksum,
|
|
idempotencyKey: "part-attempt-1",
|
|
bytes,
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: { code: "INTEGRITY_FAILED" },
|
|
});
|
|
});
|
|
|
|
it("drains a bounded successful PUT acknowledgement without cancelling it", async () => {
|
|
const bytes = new Uint8Array([4, 5, 6]);
|
|
const checksum = sha256Hex(bytes);
|
|
const payload = uploadCapabilityPayload(
|
|
{ bytes, checksum },
|
|
{ expectedResponseByteLength: 2 },
|
|
);
|
|
let cancelled = false;
|
|
const fetcher = vi.fn(async (input: RequestInfo | URL) => {
|
|
if (String(input) === CONTROL_ENDPOINT) {
|
|
return jsonResponse(payload);
|
|
}
|
|
const body = new ReadableStream<Uint8Array>({
|
|
start(controller) {
|
|
controller.enqueue(new Uint8Array([8, 9]));
|
|
controller.close();
|
|
},
|
|
cancel() {
|
|
cancelled = true;
|
|
},
|
|
});
|
|
return responseWithUrl(
|
|
new Response(body, {
|
|
status: 200,
|
|
headers: {
|
|
[POLICY_HEADER]: "v1",
|
|
"Content-Length": "2",
|
|
ETag: "\"part-etag-1\"",
|
|
},
|
|
}),
|
|
String(payload.href),
|
|
);
|
|
}) as unknown as typeof fetch;
|
|
const { provider, executor } = createHarness({ fetcher });
|
|
const issued = await provider.issueUploadPart({
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: bytes.byteLength,
|
|
checksumSha256: checksum,
|
|
mediaType: "application/octet-stream",
|
|
idempotencyKey: "part-attempt-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(issued.ok).toBe(true);
|
|
if (!issued.ok) return;
|
|
expect(
|
|
await executor.uploadParts.put({
|
|
capability: issued.value,
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: bytes.byteLength,
|
|
checksumSha256: checksum,
|
|
idempotencyKey: "part-attempt-1",
|
|
bytes,
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).toMatchObject({
|
|
ok: true,
|
|
value: { receiptToken: "part-etag-1" },
|
|
});
|
|
expect(cancelled).toBe(false);
|
|
});
|
|
|
|
it("accepts an empty 204 PUT acknowledgement", async () => {
|
|
const bytes = new Uint8Array([4, 5, 6]);
|
|
const checksum = sha256Hex(bytes);
|
|
const payload = uploadCapabilityPayload(
|
|
{ bytes, checksum },
|
|
{
|
|
expectedStatus: 204,
|
|
expectedResponseByteLength: 0,
|
|
},
|
|
);
|
|
const fetcher = vi.fn(async (input: RequestInfo | URL) =>
|
|
String(input) === CONTROL_ENDPOINT
|
|
? jsonResponse(payload)
|
|
: responseWithUrl(
|
|
new Response(null, {
|
|
status: 204,
|
|
headers: {
|
|
[POLICY_HEADER]: "v1",
|
|
ETag: "\"part-etag-204\"",
|
|
},
|
|
}),
|
|
String(payload.href),
|
|
),
|
|
) as unknown as typeof fetch;
|
|
const { provider, executor } = createHarness({ fetcher });
|
|
const issued = await provider.issueUploadPart({
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: bytes.byteLength,
|
|
checksumSha256: checksum,
|
|
mediaType: "application/octet-stream",
|
|
idempotencyKey: "part-attempt-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(issued.ok).toBe(true);
|
|
if (!issued.ok) return;
|
|
expect(
|
|
await executor.uploadParts.put({
|
|
capability: issued.value,
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: bytes.byteLength,
|
|
checksumSha256: checksum,
|
|
idempotencyKey: "part-attempt-1",
|
|
bytes,
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).toMatchObject({
|
|
ok: true,
|
|
value: { receiptToken: "part-etag-204" },
|
|
});
|
|
});
|
|
|
|
it("cancels a PUT acknowledgement whose declared length violates its binding", async () => {
|
|
const bytes = new Uint8Array([4, 5, 6]);
|
|
const checksum = sha256Hex(bytes);
|
|
const payload = uploadCapabilityPayload({ bytes, checksum });
|
|
let cancelled = false;
|
|
const fetcher = vi.fn(async (input: RequestInfo | URL) => {
|
|
if (String(input) === CONTROL_ENDPOINT) {
|
|
return jsonResponse(payload);
|
|
}
|
|
const body = new ReadableStream<Uint8Array>({
|
|
pull() {},
|
|
cancel() {
|
|
cancelled = true;
|
|
},
|
|
});
|
|
return responseWithUrl(
|
|
new Response(body, {
|
|
status: 200,
|
|
headers: {
|
|
[POLICY_HEADER]: "v1",
|
|
"Content-Length": "1",
|
|
ETag: "\"part-etag-1\"",
|
|
},
|
|
}),
|
|
String(payload.href),
|
|
);
|
|
}) as unknown as typeof fetch;
|
|
const { provider, executor } = createHarness({ fetcher });
|
|
const issued = await provider.issueUploadPart({
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: bytes.byteLength,
|
|
checksumSha256: checksum,
|
|
mediaType: "application/octet-stream",
|
|
idempotencyKey: "part-attempt-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(issued.ok).toBe(true);
|
|
if (!issued.ok) return;
|
|
expect(
|
|
await executor.uploadParts.put({
|
|
capability: issued.value,
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: bytes.byteLength,
|
|
checksumSha256: checksum,
|
|
idempotencyKey: "part-attempt-1",
|
|
bytes,
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: { code: "POLICY_REJECTED" },
|
|
});
|
|
expect(cancelled).toBe(true);
|
|
});
|
|
|
|
it("observes only safe operation, outcome, failure and byte buckets", async () => {
|
|
const bytes = new Uint8Array([7, 8, 9]);
|
|
const downloadPayload = downloadCapabilityPayload(bytes);
|
|
const checksum = sha256Hex(bytes);
|
|
const uploadPayload = uploadCapabilityPayload({ bytes, checksum });
|
|
const observations: BrowserDataObservation[] = [];
|
|
const fetcher = vi.fn(
|
|
async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
if (String(input) === CONTROL_ENDPOINT) {
|
|
const request = JSON.parse(String(init?.body)) as {
|
|
method: string;
|
|
};
|
|
return jsonResponse(
|
|
request.method === "GET"
|
|
? downloadPayload
|
|
: uploadPayload,
|
|
);
|
|
}
|
|
if (String(input) === DOWNLOAD_HREF) {
|
|
return downloadResponse(
|
|
bytes.slice().buffer,
|
|
downloadPayload,
|
|
);
|
|
}
|
|
return responseWithUrl(
|
|
new Response(null, {
|
|
status: 200,
|
|
headers: {
|
|
[POLICY_HEADER]: "v1",
|
|
"Content-Length": "0",
|
|
ETag: "\"part-etag-secret\"",
|
|
},
|
|
}),
|
|
String(uploadPayload.href),
|
|
);
|
|
},
|
|
) as unknown as typeof fetch;
|
|
const { provider, executor } = createHarness({
|
|
fetcher,
|
|
observer: {
|
|
record(observation) {
|
|
observations.push(observation);
|
|
},
|
|
},
|
|
});
|
|
const issuedDownload = await provider.issueDownload({
|
|
resourceId: "resource-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(issuedDownload.ok).toBe(true);
|
|
if (!issuedDownload.ok) return;
|
|
const opened = await executor.downloadSources.open({
|
|
resourceId: "resource-1",
|
|
capability: issuedDownload.value,
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(opened.ok).toBe(true);
|
|
if (!opened.ok) return;
|
|
expect((await collect(opened.value)).every((result) => result.ok)).toBe(
|
|
true,
|
|
);
|
|
|
|
const issuedUpload = await provider.issueUploadPart({
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: bytes.byteLength,
|
|
checksumSha256: checksum,
|
|
mediaType: "application/octet-stream",
|
|
idempotencyKey: "part-attempt-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(issuedUpload.ok).toBe(true);
|
|
if (!issuedUpload.ok) return;
|
|
expect(
|
|
await executor.uploadParts.put({
|
|
capability: issuedUpload.value,
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: bytes.byteLength,
|
|
checksumSha256: checksum,
|
|
idempotencyKey: "part-attempt-1",
|
|
bytes,
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).toMatchObject({ ok: true });
|
|
|
|
expect(observations).toEqual([
|
|
{
|
|
operation: "PRESIGNED_TRANSFER",
|
|
outcome: "SUCCEEDED",
|
|
byteBucket: "LT1MIB",
|
|
},
|
|
{
|
|
operation: "PRESIGNED_TRANSFER",
|
|
outcome: "SUCCEEDED",
|
|
byteBucket: "LT1MIB",
|
|
},
|
|
{
|
|
operation: "DOWNLOAD",
|
|
outcome: "SUCCEEDED",
|
|
byteBucket: "LT1MIB",
|
|
},
|
|
{
|
|
operation: "PRESIGNED_TRANSFER",
|
|
outcome: "SUCCEEDED",
|
|
byteBucket: "LT1MIB",
|
|
},
|
|
{
|
|
operation: "UPLOAD_PART",
|
|
outcome: "SUCCEEDED",
|
|
byteBucket: "LT1MIB",
|
|
},
|
|
]);
|
|
const serialized = JSON.stringify(observations);
|
|
for (const secret of [
|
|
DOWNLOAD_HREF,
|
|
"do-not-log-this",
|
|
checksum,
|
|
"capability-download-1",
|
|
"capability-upload-1",
|
|
"part-etag-secret",
|
|
"resource-1",
|
|
]) {
|
|
expect(serialized).not.toContain(secret);
|
|
}
|
|
});
|
|
|
|
});
|