@@ -2120,4 +2120,63 @@ describe('HttpDispatcher — MCP action bridge (list_actions / run_action)', ()
21202120 const { bridge } = makeBridge ( { userId : 'u1' , systemPermissions : [ ] } ) ;
21212121 await expect ( bridge . runAction ( 'defer_task' , { recordId : 't1' } ) ) . rejects . toThrow ( / c a n n o t b e i n v o k e d / i) ;
21222122 } ) ;
2123+
2124+ // ── flow dispatch (type:'flow' → automation flow runner) ──
2125+ const flowAction = {
2126+ name : 'escalate_ticket' ,
2127+ label : 'Escalate' ,
2128+ objectName : 'todo_task' ,
2129+ type : 'flow' ,
2130+ target : 'escalation_flow' ,
2131+ locations : [ 'record_header' ] ,
2132+ } ;
2133+ const makeFlowBridge = ( execCtx : any , automation : any ) => {
2134+ const flowObject = { ...{ name : 'todo_task' , label : 'Task' , fields : { } } , actions : [ flowAction ] } ;
2135+ const ql : any = {
2136+ executeAction : vi . fn ( ) ,
2137+ registry : { getObject : ( ) => flowObject } ,
2138+ find : vi . fn ( async ( ) => [ ] ) ,
2139+ insert : vi . fn ( ) ,
2140+ update : vi . fn ( ) ,
2141+ delete : vi . fn ( ) ,
2142+ } ;
2143+ const metadata : any = {
2144+ listObjects : vi . fn ( async ( ) => [ flowObject ] ) ,
2145+ getObject : vi . fn ( async ( ) => flowObject ) ,
2146+ } ;
2147+ const kernel : any = {
2148+ context : {
2149+ getService : ( n : string ) =>
2150+ n === 'objectql' || n === 'data' ? ql : n === 'metadata' ? metadata : n === 'automation' ? automation : null ,
2151+ } ,
2152+ } ;
2153+ const dispatcher = new HttpDispatcher ( kernel ) ;
2154+ const ctx : any = { request : { } , environmentId : 'platform' , executionContext : execCtx } ;
2155+ return { bridge : ( dispatcher as any ) . buildMcpBridge ( ctx ) , ql } ;
2156+ } ;
2157+
2158+ it ( 'list_actions includes a flow action only when an automation service is present' , async ( ) => {
2159+ const withAuto = makeFlowBridge ( { userId : 'u1' , systemPermissions : [ ] } , { execute : vi . fn ( ) } ) ;
2160+ expect ( ( await withAuto . bridge . listActions ( ) ) . map ( ( a : any ) => a . name ) ) . toContain ( 'escalate_ticket' ) ;
2161+ const noAuto = makeFlowBridge ( { userId : 'u1' , systemPermissions : [ ] } , null ) ;
2162+ expect ( ( await noAuto . bridge . listActions ( ) ) . map ( ( a : any ) => a . name ) ) . not . toContain ( 'escalate_ticket' ) ;
2163+ } ) ;
2164+
2165+ it ( 'run_action dispatches a flow action through the automation flow runner' , async ( ) => {
2166+ const execute = vi . fn ( async ( ) => ( { success : true , output : { escalated : true } } ) ) ;
2167+ const { bridge, ql } = makeFlowBridge ( { userId : 'u1' , systemPermissions : [ ] } , { execute } ) ;
2168+ const res = await bridge . runAction ( 'escalate_ticket' , { recordId : 't1' , params : { reason : 'sla' } } ) ;
2169+ expect ( res . ok ) . toBe ( true ) ;
2170+ expect ( ql . executeAction ) . not . toHaveBeenCalled ( ) ; // flow path, not executeAction
2171+ expect ( execute ) . toHaveBeenCalledWith (
2172+ 'escalation_flow' ,
2173+ expect . objectContaining ( { triggerData : expect . objectContaining ( { action : 'escalate_ticket' , params : { reason : 'sla' } } ) } ) ,
2174+ ) ;
2175+ } ) ;
2176+
2177+ it ( 'run_action surfaces a flow failure as an error' , async ( ) => {
2178+ const execute = vi . fn ( async ( ) => ( { success : false , error : 'boom' } ) ) ;
2179+ const { bridge } = makeFlowBridge ( { userId : 'u1' , systemPermissions : [ ] } , { execute } ) ;
2180+ await expect ( bridge . runAction ( 'escalate_ticket' , { } ) ) . rejects . toThrow ( / b o o m / i) ;
2181+ } ) ;
21232182} ) ;
0 commit comments