@@ -458,5 +458,151 @@ router.post('/:graphId/duplicate',
458458 }
459459) ;
460460
461+ router . post ( '/test-run/:graphId' ,
462+ authorize ( 'management:edit' ) ,
463+ async ( req , res ) => {
464+ const { botId, graphId } = req . params ;
465+ const { eventType = 'chat' , eventArgs = { } } = req . body || { } ;
466+
467+ try {
468+ const eventGraph = await prisma . eventGraph . findFirst ( {
469+ where : { id : parseInt ( graphId ) , botId : parseInt ( botId ) }
470+ } ) ;
471+ if ( ! eventGraph ) {
472+ return res . status ( 404 ) . json ( { error : 'Event graph not found' } ) ;
473+ }
474+ if ( ! eventGraph . graphJson ) {
475+ return res . status ( 400 ) . json ( { error : 'Graph has no data' } ) ;
476+ }
477+
478+ const parsed = JSON . parse ( eventGraph . graphJson ) ;
479+ const graph = {
480+ id : eventGraph . id ,
481+ name : eventGraph . name ,
482+ nodes : parsed . nodes || [ ] ,
483+ connections : parsed . connections || [ ] ,
484+ variables : parsed . variables || [ ]
485+ } ;
486+
487+ const nodeRegistry = require ( '../../core/NodeRegistry' ) ;
488+ const GraphExecutionEngine = require ( '../../core/GraphExecutionEngine' ) ;
489+ const { getGlobalDebugManager } = require ( '../../core/services/DebugSessionManager' ) ;
490+ const { buildTestContext } = require ( '../../core/services/TestModeContext' ) ;
491+
492+ const debugManager = getGlobalDebugManager ( ) ;
493+ const debugState = debugManager . getOrCreate ( parseInt ( botId ) , parseInt ( graphId ) ) ;
494+ debugState . enableTestMode ( { botId : parseInt ( botId ) , graphId : parseInt ( graphId ) , eventType, eventArgs } ) ;
495+
496+ const engine = new GraphExecutionEngine ( nodeRegistry , null ) ;
497+
498+ const context = buildTestContext ( {
499+ botId : parseInt ( botId ) ,
500+ graphId : parseInt ( graphId ) ,
501+ eventArgs
502+ } ) ;
503+
504+ engine . execute ( graph , context , eventType ) . catch ( err => {
505+ if ( ! err ) return ;
506+ if ( err . name === 'BreakLoopSignal' ) return ;
507+ if ( err . message === 'Execution stopped by debugger' ) return ;
508+ logger . error ( '[Test Run] Execution error:' , err . message ) ;
509+ } ) ;
510+
511+ res . json ( { success : true , message : 'Test run started in step mode' } ) ;
512+ } catch ( error ) {
513+ logger . error ( '[Test Run] Failed:' , error ) ;
514+ res . status ( 500 ) . json ( { error : error . message || 'Test run failed' } ) ;
515+ }
516+ }
517+ ) ;
518+
519+ router . post ( '/run-node/:graphId/:nodeId' ,
520+ authorize ( 'management:edit' ) ,
521+ async ( req , res ) => {
522+ const { botId, graphId, nodeId } = req . params ;
523+ const { inputs = { } , variables = { } } = req . body || { } ;
524+
525+ try {
526+ const eventGraph = await prisma . eventGraph . findFirst ( {
527+ where : { id : parseInt ( graphId ) , botId : parseInt ( botId ) }
528+ } ) ;
529+ if ( ! eventGraph ) {
530+ return res . status ( 404 ) . json ( { error : 'Event graph not found' } ) ;
531+ }
532+ const parsed = JSON . parse ( eventGraph . graphJson || '{}' ) ;
533+ const node = ( parsed . nodes || [ ] ) . find ( n => n . id === nodeId ) ;
534+ if ( ! node ) {
535+ return res . status ( 404 ) . json ( { error : 'Node not found in graph' } ) ;
536+ }
537+
538+ const nodeRegistry = require ( '../../core/NodeRegistry' ) ;
539+ const nodeConfig = nodeRegistry . getNodeConfig ( node . type ) ;
540+ if ( ! nodeConfig ) {
541+ return res . status ( 400 ) . json ( { error : `Unknown node type: ${ node . type } ` } ) ;
542+ }
543+
544+ const GraphExecutionEngine = require ( '../../core/GraphExecutionEngine' ) ;
545+ const { buildTestContext } = require ( '../../core/services/TestModeContext' ) ;
546+ const engine = new GraphExecutionEngine ( nodeRegistry , null ) ;
547+
548+ engine . activeGraph = {
549+ id : parseInt ( graphId ) ,
550+ nodes : parsed . nodes || [ ] ,
551+ connections : parsed . connections || [ ] ,
552+ variables : parsed . variables || [ ]
553+ } ;
554+ engine . context = {
555+ ...buildTestContext ( {
556+ botId : parseInt ( botId ) ,
557+ graphId : parseInt ( graphId ) ,
558+ eventArgs : inputs
559+ } ) ,
560+ variables
561+ } ;
562+
563+ for ( const [ pin , value ] of Object . entries ( inputs ) ) {
564+ engine . memo . set ( `__forced:${ node . id } :${ pin } ` , value ) ;
565+ }
566+ const originalResolve = engine . resolvePinValue . bind ( engine ) ;
567+ engine . resolvePinValue = async function ( targetNode , pinName ) {
568+ if ( targetNode . id === node . id && engine . memo . has ( `__forced:${ node . id } :${ pinName } ` ) ) {
569+ return engine . memo . get ( `__forced:${ node . id } :${ pinName } ` ) ;
570+ }
571+ return originalResolve ( targetNode , pinName ) ;
572+ } ;
573+
574+ const startedAt = Date . now ( ) ;
575+ const helpers = {
576+ resolvePinValue : engine . resolvePinValue . bind ( engine ) ,
577+ traverse : async ( ) => { } ,
578+ memo : engine . memo ,
579+ clearLoopBodyMemo : ( ) => { }
580+ } ;
581+
582+ let outputs = { } ;
583+ let errorMsg = null ;
584+ try {
585+ await nodeConfig . executor . call ( engine , node , engine . context , helpers ) ;
586+ outputs = await engine . _captureNodeOutputs ( node ) ;
587+ } catch ( e ) {
588+ errorMsg = e . message ;
589+ }
590+
591+ res . json ( {
592+ success : ! errorMsg ,
593+ nodeId : node . id ,
594+ nodeType : node . type ,
595+ executionTime : Date . now ( ) - startedAt ,
596+ outputs,
597+ error : errorMsg ,
598+ variables : engine . context . variables
599+ } ) ;
600+ } catch ( error ) {
601+ logger . error ( '[Run Node] Failed:' , error ) ;
602+ res . status ( 500 ) . json ( { error : error . message || 'Run node failed' } ) ;
603+ }
604+ }
605+ ) ;
606+
461607module . exports = router ;
462608
0 commit comments