Files
Endoleap/tests/canvas-assets.test.mjs
T

38 lines
3.6 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { prepareManagedImages, uploadCanvasImage, inlineImageCount, inlineImageBlob } from '../src/lib/canvas-assets.ts';
import { emptyCanvas, makeCanvasNode } from '../src/lib/canvas.ts';
import { managedImageSource } from '../src/lib/canvas-v2.ts';
const source='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB';
const id='77777777-7777-4777-8777-777777777777';
const asset={id,url:`https://api.foundry.entroia.com/projects/endoleap/workspaces/w/assets/${id}`,width:1,height:1,bytes:24,mime_type:'image/png'};
const fixture=()=>({...emptyCanvas(),schema_version:2,nodes:[makeCanvasNode('image',{x:15,y:20},source),makeCanvasNode('plain-image',{x:800,y:300},source)],edges:[]});
test('explicit legacy image migration uploads original bytes once and preserves graph geometry and identity',async()=>{
const document=fixture(),original=structuredClone(document);let calls=0;
const result=await prepareManagedImages(document,async(path,options)=>{
calls++;assert.equal(path,'workspaces/w/assets');assert.match(options.headers['Idempotency-Key'],/^[a-f0-9-]{36}$/);assert.equal(options.method,'POST');assert.ok(options.body instanceof FormData);
assert.deepEqual(Buffer.from(await options.body.get('image').arrayBuffer()),Buffer.from(source.split(',')[1],'base64'));return asset;
},'w');
assert.equal(calls,1);assert.equal(inlineImageCount(result),0);assert.deepEqual(document,original);
assert.equal(result.edges,document.edges);for(let i=0;i<2;i++){assert.equal(result.nodes[i].id,document.nodes[i].id);assert.deepEqual(result.nodes[i].position,document.nodes[i].position);assert.equal(result.nodes[i].width,document.nodes[i].width);assert.equal(result.nodes[i].data.src,asset.url);assert.equal(result.nodes[i].data.assetId,id);}
assert.equal(inlineImageCount(document),2);
});
test('failed upload or invalid response leaves the original document untouched',async()=>{
const document=fixture(),original=structuredClone(document);
await assert.rejects(prepareManagedImages(document,async()=>{throw new Error('forbidden');},'w'),/forbidden/);assert.deepEqual(document,original);
await assert.rejects(prepareManagedImages(document,async()=>({...asset,url:'https://invalid.example/a.png'}),'w'),/invalid_asset_response/);assert.deepEqual(document,original);
});
test('uncertain upload retries the exact bytes with the same idempotency key',async()=>{
let calls=0,key;
await uploadCanvasImage(async(_path,options)=>{calls++;if(!key)key=options.headers['Idempotency-Key'];assert.equal(options.headers['Idempotency-Key'],key);if(calls===1)throw new TypeError('connection lost');return asset;},'w',new Blob(['test'],{type:'image/png'}),'test.png');assert.equal(calls,2);
});
test('V1 uses managed URLs without unsupported V2 fields and clean documents cause no upload',async()=>{
const document=fixture();document.schema_version=1;let calls=0;
const result=await prepareManagedImages(document,async()=>{calls++;return asset;},'w');assert.equal(result.schema_version,1);assert.equal(result.nodes[0].data.assetId,undefined);
await prepareManagedImages(result,async()=>{throw new Error('must not upload');},'w');assert.equal(calls,1);
});
test('preview URLs are renderer-only and do not replace original managed references',()=>{
assert.equal(managedImageSource(asset.url),`/api/foundry/workspaces/w/assets/${id}`);assert.equal(managedImageSource(asset.url,true),`/api/foundry/workspaces/w/assets/${id}?variant=preview`);
assert.equal(managedImageSource('https://example.com/photo.png',true),'https://example.com/photo.png');assert.throws(()=>inlineImageBlob('data:image/svg+xml;base64,PHN2Zz4='),/invalid_asset/);
});