gongxiangp2/apps/detail-agent-pi/test/workflow-execute.test.mjs

69 lines
5.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
});