71 lines
7.9 KiB
JavaScript
71 lines
7.9 KiB
JavaScript
/** Remote authenticated HTTP/DB integration. Only the model output is a fixture. */
|
|
import assert from 'node:assert/strict';
|
|
import {randomUUID,createHash} from 'node:crypto';
|
|
|
|
export async function verifyFoundryAI({open,transport,rootId,imageId,assets,calls,advance,loseAdmission,loseAction}){
|
|
const path=`workspaces/${process.env.FOUNDRY_TEST_WORKSPACE}/materials/${rootId}/canvas/ai`;
|
|
const control=async action=>{const response=await transport({method:'POST',path:path+'/fixture/'+action,signal:AbortSignal.timeout(15_000)});assert.equal(response.status,200,action+': '+JSON.stringify(response.body));return response.body;};
|
|
const posts=suffix=>calls.filter(call=>call.method==='POST'&&call.path.endsWith(suffix));
|
|
let engine=await open();const signal=new AbortController().signal;
|
|
const reopen=async()=>{await engine.close();advance();engine=await open();await engine.settle();};
|
|
const poll=async()=>{await engine.retry();await engine.settle();};
|
|
try{
|
|
const catalog=await engine.models(rootId,signal);assert.ok(catalog.models.some(model=>model.id==='fixture-text'));assert.ok(catalog.models.some(model=>model.id==='fixture-image'));
|
|
const target={id:randomUUID(),incarnation:1};
|
|
assert.equal(engine.editor.dispatch({type:'element.create',element:{id:target.id,incarnation:1,ownerCanvasId:rootId,componentId:'text',dataVersion:1,structure:'node',data:{title:'Real AI adapter',text:'before generation',extension:{keep:true}},geometry:{x:0,y:0,width:100,height:80,rotation:0}}}).status,'applied');await engine.settle();
|
|
const input={target,boardId:rootId,modelId:'fixture-text',prompt:'Fixture generation',parameters:{},propagate:false};
|
|
const begin=async value=>{const result=engine.startRun(value);assert.equal(result.status,'applied');await engine.settle();return result;};
|
|
const byOperation=id=>[...engine.getSnapshot().runtime.runs.values()].find(run=>run.operationId===id);
|
|
const initialStarts=posts('/ai/runs').length,initialUploads=posts('/assets').length;
|
|
|
|
// A committed admission whose response was lost must be queried after reopen.
|
|
loseAdmission();const first=await begin(input),admission=[...engine.getSnapshot().runtime.ledger.requests.values()].find(request=>request.kind==='run.start'&&request.operationIds.includes(first.operationIds[0]));
|
|
assert.equal(admission.phase,'waiting');const admissionCount=posts('/ai/runs').length;
|
|
await reopen();assert.equal(posts('/ai/runs').length,admissionCount);assert.equal(engine.getSnapshot().runtime.ledger.requests.get(admission.id).body,admission.body);
|
|
assert.ok(calls.some(call=>call.method==='GET'&&call.path.endsWith('/ai/operations/'+admission.id)));
|
|
const firstRun=byOperation(first.operationIds[0]);assert.equal(firstRun.status,'queued');
|
|
assert.equal(engine.editor.dispatch({type:'element.geometry',target,patch:{x:77}}).status,'applied');await engine.settle();
|
|
await control('execute');await poll();
|
|
assert.equal(engine.getSnapshot().runtime.runs.get(firstRun.id).status,'succeeded');
|
|
assert.equal(engine.getSnapshot().document.elements.get(target.id).data.text,'headless fixture output');assert.equal(engine.getSnapshot().document.elements.get(target.id).geometry.x,77);
|
|
assert.deepEqual(engine.getSnapshot().document.elements.get(target.id).data.extension,{keep:true});
|
|
|
|
// Queued cancellation is durable and does not execute the generation transport.
|
|
const second=await begin({...input,prompt:'Fixture cancellation'}),secondRun=byOperation(second.operationIds[0]),beforeCancel=(await control('stats')).executions;
|
|
loseAction();assert.equal(engine.cancelRun(secondRun.id).status,'applied');await engine.settle();
|
|
const cancellation=[...engine.getSnapshot().runtime.ledger.requests.values()].find(request=>request.kind==='run.cancel'&&request.runId===secondRun.id);assert.equal(cancellation.phase,'waiting');const cancellationCount=posts('/cancel').length;
|
|
await reopen();assert.equal(posts('/cancel').length,cancellationCount);assert.equal(engine.getSnapshot().runtime.ledger.requests.get(cancellation.id).body,cancellation.body);assert.equal(engine.getSnapshot().runtime.runs.get(secondRun.id).status,'cancelled');
|
|
assert.equal((await control('stats')).executions,beforeCancel);
|
|
|
|
// Edit while the provider is held, then retain and explicitly apply its output.
|
|
const third=await begin({...input,prompt:'Fixture conflict'}),thirdRun=byOperation(third.operationIds[0]);await control('arm');
|
|
const executing=control('execute').then(value=>({value}),error=>({error}));
|
|
try{
|
|
await control('wait');
|
|
assert.equal(engine.editor.dispatch({type:'element.data',target,patch:{text:'user edit while executing'}}).status,'applied');await engine.settle();
|
|
}finally{await control('release');}
|
|
const completed=await executing;if(completed.error)throw completed.error;await poll();
|
|
const conflict=engine.getSnapshot().runtime.runs.get(thirdRun.id);assert.equal(conflict.status,'conflict');assert.equal(engine.getSnapshot().document.elements.get(target.id).data.text,'user edit while executing');
|
|
const outputId=conflict.outputEvents[0]?.output.output_id;assert.equal(typeof outputId,'string');
|
|
loseAction();assert.equal(engine.applyRunOutput(conflict.id,target,outputId).status,'applied');await engine.settle();
|
|
const application=[...engine.getSnapshot().runtime.ledger.requests.values()].find(request=>request.kind==='run.apply'&&request.runId===conflict.id);assert.equal(application.phase,'waiting');const applyCount=posts('/apply').length;
|
|
assert.equal(engine.editor.history.undo().status,'applied');await engine.settle();await reopen();
|
|
assert.equal(engine.getSnapshot().document.elements.get(target.id).data.text,'user edit while executing');assert.equal(posts('/apply').length,applyCount);assert.equal(engine.getSnapshot().runtime.ledger.requests.get(application.id).body,application.body);
|
|
assert.ok(engine.getSnapshot().runtime.runs.get(conflict.id).appliedOutputIds.includes(outputId));
|
|
assert.equal(engine.editor.history.redo().status,'applied');await engine.settle();assert.equal(engine.getSnapshot().document.elements.get(target.id).data.text,'headless fixture output');
|
|
assert.equal(posts('/apply').length,applyCount);
|
|
|
|
// Managed image results are readable, and copying shares bytes without a run.
|
|
const image=engine.getSnapshot().document.elements.get(imageId),oldAsset=image.data.assetId;
|
|
const fourth=await begin({target:{id:image.id,incarnation:image.incarnation},boardId:rootId,modelId:'fixture-image',prompt:'Fixture image',parameters:{},propagate:false});
|
|
const fourthRun=byOperation(fourth.operationIds[0]);await control('execute');await poll();
|
|
assert.equal(engine.getSnapshot().runtime.runs.get(fourthRun.id).status,'succeeded');const generated=engine.getSnapshot().document.elements.get(imageId);assert.notEqual(generated.data.assetId,oldAsset);
|
|
const metadata=await assets.authority.resolve(generated.data.assetId,signal),display=await engine.displayResource(generated.data.assetId);
|
|
try{const bytes=Buffer.from(await(await fetch(display.url)).arrayBuffer());assert.deepEqual([...bytes.subarray(0,8)],[137,80,78,71,13,10,26,10]);assert.equal(createHash('sha256').update(bytes).digest('hex'),metadata.digest);assert.equal(metadata.asset.width,3);assert.equal(metadata.asset.height,2);}finally{display.release();}
|
|
const runCount=engine.getSnapshot().runtime.runs.size,copyId=await engine.imports.duplicate([{id:generated.id,incarnation:generated.incarnation}],{viewCanvasId:rootId});await engine.settle();
|
|
const copy=engine.imports.getJobs().get(copyId);assert.equal(copy.status,'completed');assert.equal(engine.getSnapshot().document.elements.get(copy.stableIds['element:'+generated.id]).data.assetId,generated.data.assetId);
|
|
assert.equal(engine.getSnapshot().runtime.runs.size,runCount);assert.equal(posts('/ai/runs').length,initialStarts+4);assert.equal(posts('/assets').length,initialUploads);assert.equal((await control('stats')).executions,3);
|
|
return {runtimeAI:true,aiLostAdmission:true,aiCancelReconciled:true,aiConflictApplyUndo:true,aiManagedImage:true,aiCopyNoRun:true,aiExecutionCount:3};
|
|
}finally{await engine.close();}
|
|
}
|