69 lines
5.9 KiB
JavaScript
69 lines
5.9 KiB
JavaScript
import test from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
import { executeWorkflow } from '../src/workflow-execute.mjs';
|
||
import { StdioCanvas } from '../src/stdio-canvas.mjs';
|
||
const args={version:1,intent:'generate_candidate',outputId:'detail',slotId:'hero',productVariantId:'walnut'};
|
||
function setup(phases) {
|
||
const calls=[], events=[]; let id;
|
||
const host={canvas:{run:'r',async workflowExecute(input){ calls.push(['execute',input]);id=input.operationId;return {operationId:id,phase:'submitted'}; },async workflowPoll(op){calls.push(['poll',op]);return {operationId:id,phase:phases.shift()||'submitted'};}},store:{async appendEvent(v){events.push(v)}}, async readWorkflow(input){calls.push(['read',input]);return {version:2,section:'slots'};}};
|
||
return {host,calls,events};
|
||
}
|
||
test('late prior-turn workflow receipt cannot stop the new turn and retains unknown operation identity',async()=>{
|
||
const x=setup([]);x.host.turnEpoch=1;
|
||
let release;
|
||
x.host.canvas.workflowExecute=input=>{x.calls.push(['execute',input]);return new Promise(resolve=>{release=()=>resolve({operationId:input.operationId,phase:'submitted'});});};
|
||
const task=executeWorkflow(x.host,args,undefined,{pause:async()=>{}});
|
||
await new Promise(resolve=>setImmediate(resolve));
|
||
const operationId=x.calls[0][1].operationId;
|
||
x.host.turnEpoch=2;x.host.canvas.run='new-run';x.host.canvasWritesStopped=false;
|
||
release();await assert.rejects(task,/原回合已结束/);
|
||
assert.equal(x.host.canvasWritesStopped,false);
|
||
assert.deepEqual(x.host.nativeUnresolved,{kind:'workflow',operationId,run:'r',documentId:undefined,identity:{scope:undefined,project:undefined,accountId:undefined,origin:undefined},epoch:1});
|
||
await assert.rejects(executeWorkflow(x.host,args),/先核查原请求/);
|
||
assert.equal(x.calls.length,1);
|
||
});
|
||
test('one dispatch polls matching operation and refreshes after complete',async()=>{
|
||
const x=setup(['submitted','complete']);const result=await executeWorkflow(x.host,args,undefined,{pause:async()=>{}});
|
||
assert.deepEqual(x.calls.map(c=>c[0]),['execute','poll','poll','read']);assert.equal(result.operation.phase,'complete');assert.equal(result.workflow.version,2);
|
||
assert.equal(x.calls[0][1].operationId,x.calls[1][1]);assert.equal(x.host.canvasWritesStopped,undefined);
|
||
});
|
||
test('unknown, failed and invalid receipt never redispatch and stop writes',async()=>{
|
||
for (const phase of ['unknown','failed','bad']) {const x=setup([phase]);await assert.rejects(executeWorkflow(x.host,args,undefined,{pause:async()=>{}}));assert.equal(x.calls.filter(c=>c[0]==='execute').length,1);assert.equal(x.host.canvasWritesStopped,true);assert.equal(x.calls.some(c=>c[0]==='read'),false);}
|
||
});
|
||
|
||
test('an upstream failure keeps its real reason instead of describing a user handover',async()=>{
|
||
const x=setup([]);
|
||
x.host.canvas.workflowPoll=async operationId=>({operationId,phase:'unknown',candidateId:'original',message:'图片服务暂时不可用(503),原请求待核查;不会重发'});
|
||
await assert.rejects(executeWorkflow(x.host,args,undefined,{pause:async()=>{}}),/503/);
|
||
assert.match(x.host.workflowStopReason,/503/);
|
||
assert.equal(x.host.canvasWritesStopped,true);
|
||
assert.equal(x.calls.filter(c=>c[0]==='execute').length,1);
|
||
await assert.rejects(executeWorkflow(x.host,args),/原操作需核查/);
|
||
});
|
||
test('cancellation and broken poll preserve operation and block further dispatch',async()=>{
|
||
const x=setup([]); x.host.canvas.workflowPoll=async()=>{throw Error('pipe closed')};
|
||
await assert.rejects(executeWorkflow(x.host,args,undefined,{pause:async()=>{}}));await assert.rejects(executeWorkflow(x.host,args));assert.equal(x.calls.filter(c=>c[0]==='execute').length,1);
|
||
const y=setup([]), controller=new AbortController();controller.abort();await assert.rejects(executeWorkflow(y.host,args,controller.signal));assert.equal(y.calls.length,0);
|
||
});
|
||
test('stdio transport carries session identity and operation id',async()=>{
|
||
const sent=[];const c=new StdioCanvas(async (action,input)=>{sent.push({action,input});},7);c.run='run';await c.workflowExecute({...args,operationId:'op'});await c.workflowPoll('op');assert.deepEqual(sent.map(v=>v.action),['pi-workflow-execute','pi-workflow-poll']);assert.equal(sent[1].input.run,'run');assert.equal(sent[1].input.documentId,7);assert.equal(sent[1].input.operationId,'op');
|
||
});
|
||
test('adoption obtains current revision write permission before dispatch',async()=>{
|
||
const x=setup(['complete']); x.host.canvas.read=async()=>{x.calls.push(['canvas-read']);return {revision:17,conversationActive:true,writeBlocked:false};};
|
||
x.host.canvas.requestWrite=async revision=>{x.calls.push(['permission',revision]);return {allowed:true,revision};};
|
||
await executeWorkflow(x.host,{...args,intent:'adopt_candidate',candidateId:'chosen'},undefined,{pause:async()=>{}});
|
||
assert.deepEqual(x.calls.map(c=>c[0]),['canvas-read','permission','execute','poll','read']); assert.equal(x.calls[1][1],17); assert.equal(x.host.canvasWriteRequested,true);
|
||
});
|
||
test('failed or stale adoption permission never dispatches',async()=>{
|
||
for (const permission of [{allowed:false,revision:17},{allowed:true,revision:18},null]) {
|
||
const x=setup(['complete']);x.host.canvas.read=async()=>({revision:17});x.host.canvas.requestWrite=async()=>permission;
|
||
await assert.rejects(executeWorkflow(x.host,{...args,intent:'adopt_candidate',candidateId:'chosen'}));
|
||
assert.equal(x.calls.length,0); assert.equal(x.host.canvasWritesStopped,true);
|
||
}
|
||
const x=setup([]);await assert.rejects(executeWorkflow(x.host,{...args,intent:'adopt_candidate',candidateId:'chosen'}));assert.equal(x.calls.length,0);
|
||
});
|
||
test('generation never requests a write lease',async()=>{
|
||
const x=setup(['complete']);x.host.canvas.read=async()=>{throw Error('unexpected canvas read')};x.host.canvas.requestWrite=async()=>{throw Error('unexpected permission')};
|
||
await executeWorkflow(x.host,args,undefined,{pause:async()=>{}});assert.equal(x.host.canvasWriteRequested,undefined);
|
||
});
|