51 lines
4.4 KiB
JavaScript
51 lines
4.4 KiB
JavaScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { createFoundryAssets, createFoundryTransport } from '../../src/lib/canvas-service/index.ts';
|
|
const id = n => `10000000-0000-4000-8000-${String(n).padStart(12, '0')}`;
|
|
async function fixture() {
|
|
const blob = new Blob(['immutable image'], { type: 'image/png' });
|
|
const digest = [...new Uint8Array(await crypto.subtle.digest('SHA-256', await blob.arrayBuffer()))].map(value => value.toString(16).padStart(2, '0')).join('');
|
|
const resource = { id: id(1), operationId: id(2), name: 'picture.png', byteLength: blob.size, mimeType: blob.type, digest };
|
|
const request = { kind: 'asset.upload', protocolVersion: 2, id: id(3), operationIds: [resource.operationId], canvasIds: [], expectedVersions: {}, phase: 'frozen', outcome: 'unresolved', generation: 1, attempt: 0,
|
|
body: JSON.stringify({ protocol_version: 2, request_id: id(3), resource_id: resource.id, operation_id: resource.operationId, name: resource.name, mime_type: resource.mimeType, byte_length: resource.byteLength, sha256: resource.digest }) };
|
|
const calls = [], receipt = { protocol_version: 2, request_id: request.id, status: 'applied', asset: { id: resource.id, bytes: resource.byteLength, mime_type: 'image/png', width: 2, height: 1, url: `https://api.foundry.entroia.com/projects/endoleap/workspaces/${id(4)}/assets/${resource.id}` } };
|
|
let response = { status: 201, body: receipt };
|
|
const assets = createFoundryAssets({ workspaceId: id(4), transport: async input => { calls.push(input); return response; } });
|
|
return { blob, resource, request, calls, receipt, assets, respond: next => { response = next; } };
|
|
}
|
|
|
|
test('asset adapter preserves byte identity and headers and validates the scoped applied receipt', async () => {
|
|
const f = await fixture(), result = await f.assets.upload(f.request, f.blob, new AbortController().signal);
|
|
assert.equal(result.outcome, 'applied'); assert.equal(f.calls[0].requestId, f.request.id); assert.equal(f.calls[0].resourceId, f.resource.id);
|
|
assert.equal(f.calls[0].body.get('image').name, f.resource.name); assert.equal(await f.calls[0].body.get('image').text(), await f.blob.text());
|
|
assert.equal(f.assets.accept(f.request, result, f.resource).id, f.resource.id);
|
|
for (const patch of [{ id: id(99) }, { bytes: 100 }, { url: 'https://other.example/image' }, { width: 0 }, { width: 1000000, height: 1000000 }]) {
|
|
const altered = { ...result, canonical: { ...f.receipt, asset: { ...f.receipt.asset, ...patch } } };
|
|
assert.throws(() => f.assets.accept(f.request, altered, f.resource), /asset_receipt_mismatch/);
|
|
}
|
|
const changed = await f.assets.upload(f.request, new Blob(['different']), new AbortController().signal);
|
|
assert.equal(changed.outcome, 'unresolved'); assert.equal(changed.blocked, true); assert.equal(f.calls.length, 1);
|
|
});
|
|
|
|
test('missing and rejected upload receipts never become proof of zero writes', async () => {
|
|
const f = await fixture(), signal = new AbortController().signal;
|
|
f.respond({ status: 404, body: { protocol_version: 2, request_id: f.request.id, status: 'unknown' } });
|
|
const unknown = await f.assets.query(f.request, signal);
|
|
assert.equal(unknown.outcome, 'unresolved'); assert.equal(unknown.blocked, undefined);
|
|
assert.ok(f.calls[0].path.endsWith(`/assets/operations/${f.request.id}`));
|
|
f.respond({ status: 403, body: { error: 'forbidden' } });
|
|
assert.equal((await f.assets.query(f.request, signal)).blocked, true);
|
|
f.respond({ status: 201, body: { ...f.receipt, request_id: id(99) } });
|
|
assert.equal((await f.assets.upload(f.request, f.blob, signal)).outcome, 'unresolved');
|
|
});
|
|
|
|
test('multipart browser transport leaves the boundary to fetch and binds the expected account', async () => {
|
|
let captured;
|
|
const transport = createFoundryTransport({ accountId: id(5), isCurrentIdentity: () => true, fetch: async (url, init) => { captured = { url, init }; return Response.json({ status: 'applied' }); } });
|
|
const body = new FormData(); body.append('image', new Blob(['image']), 'a.png');
|
|
await transport({ method: 'POST', path: 'workspaces/example/assets', body, requestId: id(3), resourceId: id(1) });
|
|
const headers = new Headers(captured.init.headers);
|
|
assert.equal(headers.has('content-type'), false); assert.equal(headers.get('X-Expected-Account'), id(5));
|
|
assert.equal(headers.get('X-Resource-Id'), id(1)); assert.equal(headers.get('Idempotency-Key'), id(3)); assert.equal(captured.init.body, body);
|
|
});
|