@@ -2364,6 +2364,261 @@ describe('hasWorkflowChanged', () => {
23642364 } )
23652365 } )
23662366
2367+ describe ( 'Trigger Config Normalization (False Positive Prevention)' , ( ) => {
2368+ it . concurrent (
2369+ 'should not detect change when deployed has null fields but current has values from triggerConfig' ,
2370+ ( ) => {
2371+ // Core scenario: deployed state has null individual fields, current state has
2372+ // values populated from triggerConfig at runtime by populateTriggerFieldsFromConfig
2373+ const deployedState = createWorkflowState ( {
2374+ blocks : {
2375+ block1 : createBlock ( 'block1' , {
2376+ type : 'starter' ,
2377+ subBlocks : {
2378+ signingSecret : { id : 'signingSecret' , type : 'short-input' , value : null } ,
2379+ botToken : { id : 'botToken' , type : 'short-input' , value : null } ,
2380+ triggerConfig : {
2381+ id : 'triggerConfig' ,
2382+ type : 'short-input' ,
2383+ value : { signingSecret : 'secret123' , botToken : 'token456' } ,
2384+ } ,
2385+ } ,
2386+ } ) ,
2387+ } ,
2388+ } )
2389+
2390+ const currentState = createWorkflowState ( {
2391+ blocks : {
2392+ block1 : createBlock ( 'block1' , {
2393+ type : 'starter' ,
2394+ subBlocks : {
2395+ signingSecret : { id : 'signingSecret' , type : 'short-input' , value : 'secret123' } ,
2396+ botToken : { id : 'botToken' , type : 'short-input' , value : 'token456' } ,
2397+ triggerConfig : {
2398+ id : 'triggerConfig' ,
2399+ type : 'short-input' ,
2400+ value : { signingSecret : 'secret123' , botToken : 'token456' } ,
2401+ } ,
2402+ } ,
2403+ } ) ,
2404+ } ,
2405+ } )
2406+
2407+ expect ( hasWorkflowChanged ( currentState , deployedState ) ) . toBe ( false )
2408+ }
2409+ )
2410+
2411+ it . concurrent (
2412+ 'should detect change when user edits a trigger field to a different value' ,
2413+ ( ) => {
2414+ const deployedState = createWorkflowState ( {
2415+ blocks : {
2416+ block1 : createBlock ( 'block1' , {
2417+ type : 'starter' ,
2418+ subBlocks : {
2419+ signingSecret : { id : 'signingSecret' , type : 'short-input' , value : null } ,
2420+ triggerConfig : {
2421+ id : 'triggerConfig' ,
2422+ type : 'short-input' ,
2423+ value : { signingSecret : 'old-secret' } ,
2424+ } ,
2425+ } ,
2426+ } ) ,
2427+ } ,
2428+ } )
2429+
2430+ const currentState = createWorkflowState ( {
2431+ blocks : {
2432+ block1 : createBlock ( 'block1' , {
2433+ type : 'starter' ,
2434+ subBlocks : {
2435+ signingSecret : { id : 'signingSecret' , type : 'short-input' , value : 'new-secret' } ,
2436+ triggerConfig : {
2437+ id : 'triggerConfig' ,
2438+ type : 'short-input' ,
2439+ value : { signingSecret : 'old-secret' } ,
2440+ } ,
2441+ } ,
2442+ } ) ,
2443+ } ,
2444+ } )
2445+
2446+ expect ( hasWorkflowChanged ( currentState , deployedState ) ) . toBe ( true )
2447+ }
2448+ )
2449+
2450+ it . concurrent ( 'should not detect change when both sides have no triggerConfig' , ( ) => {
2451+ const deployedState = createWorkflowState ( {
2452+ blocks : {
2453+ block1 : createBlock ( 'block1' , {
2454+ type : 'starter' ,
2455+ subBlocks : {
2456+ signingSecret : { id : 'signingSecret' , type : 'short-input' , value : null } ,
2457+ } ,
2458+ } ) ,
2459+ } ,
2460+ } )
2461+
2462+ const currentState = createWorkflowState ( {
2463+ blocks : {
2464+ block1 : createBlock ( 'block1' , {
2465+ type : 'starter' ,
2466+ subBlocks : {
2467+ signingSecret : { id : 'signingSecret' , type : 'short-input' , value : null } ,
2468+ } ,
2469+ } ) ,
2470+ } ,
2471+ } )
2472+
2473+ expect ( hasWorkflowChanged ( currentState , deployedState ) ) . toBe ( false )
2474+ } )
2475+
2476+ it . concurrent (
2477+ 'should not detect change when deployed has empty fields and triggerConfig populates them' ,
2478+ ( ) => {
2479+ // Empty string is also treated as "empty" by normalizeTriggerConfigValues
2480+ const deployedState = createWorkflowState ( {
2481+ blocks : {
2482+ block1 : createBlock ( 'block1' , {
2483+ type : 'starter' ,
2484+ subBlocks : {
2485+ signingSecret : { id : 'signingSecret' , type : 'short-input' , value : '' } ,
2486+ triggerConfig : {
2487+ id : 'triggerConfig' ,
2488+ type : 'short-input' ,
2489+ value : { signingSecret : 'secret123' } ,
2490+ } ,
2491+ } ,
2492+ } ) ,
2493+ } ,
2494+ } )
2495+
2496+ const currentState = createWorkflowState ( {
2497+ blocks : {
2498+ block1 : createBlock ( 'block1' , {
2499+ type : 'starter' ,
2500+ subBlocks : {
2501+ signingSecret : { id : 'signingSecret' , type : 'short-input' , value : 'secret123' } ,
2502+ triggerConfig : {
2503+ id : 'triggerConfig' ,
2504+ type : 'short-input' ,
2505+ value : { signingSecret : 'secret123' } ,
2506+ } ,
2507+ } ,
2508+ } ) ,
2509+ } ,
2510+ } )
2511+
2512+ expect ( hasWorkflowChanged ( currentState , deployedState ) ) . toBe ( false )
2513+ }
2514+ )
2515+
2516+ it . concurrent ( 'should not detect change when triggerId differs' , ( ) => {
2517+ const deployedState = createWorkflowState ( {
2518+ blocks : {
2519+ block1 : createBlock ( 'block1' , {
2520+ type : 'starter' ,
2521+ subBlocks : {
2522+ model : { value : 'gpt-4' } ,
2523+ triggerId : { value : null } ,
2524+ } ,
2525+ } ) ,
2526+ } ,
2527+ } )
2528+
2529+ const currentState = createWorkflowState ( {
2530+ blocks : {
2531+ block1 : createBlock ( 'block1' , {
2532+ type : 'starter' ,
2533+ subBlocks : {
2534+ model : { value : 'gpt-4' } ,
2535+ triggerId : { value : 'slack_webhook' } ,
2536+ } ,
2537+ } ) ,
2538+ } ,
2539+ } )
2540+
2541+ expect ( hasWorkflowChanged ( currentState , deployedState ) ) . toBe ( false )
2542+ } )
2543+
2544+ it . concurrent (
2545+ 'should not detect change for namespaced system subBlock IDs like samplePayload_slack_webhook' ,
2546+ ( ) => {
2547+ const deployedState = createWorkflowState ( {
2548+ blocks : {
2549+ block1 : createBlock ( 'block1' , {
2550+ type : 'starter' ,
2551+ subBlocks : {
2552+ model : { value : 'gpt-4' } ,
2553+ samplePayload_slack_webhook : { value : 'old payload' } ,
2554+ triggerInstructions_slack_webhook : { value : 'old instructions' } ,
2555+ } ,
2556+ } ) ,
2557+ } ,
2558+ } )
2559+
2560+ const currentState = createWorkflowState ( {
2561+ blocks : {
2562+ block1 : createBlock ( 'block1' , {
2563+ type : 'starter' ,
2564+ subBlocks : {
2565+ model : { value : 'gpt-4' } ,
2566+ samplePayload_slack_webhook : { value : 'new payload' } ,
2567+ triggerInstructions_slack_webhook : { value : 'new instructions' } ,
2568+ } ,
2569+ } ) ,
2570+ } ,
2571+ } )
2572+
2573+ expect ( hasWorkflowChanged ( currentState , deployedState ) ) . toBe ( false )
2574+ }
2575+ )
2576+
2577+ it . concurrent (
2578+ 'should handle mixed scenario: some fields from triggerConfig, some user-edited' ,
2579+ ( ) => {
2580+ const deployedState = createWorkflowState ( {
2581+ blocks : {
2582+ block1 : createBlock ( 'block1' , {
2583+ type : 'starter' ,
2584+ subBlocks : {
2585+ signingSecret : { id : 'signingSecret' , type : 'short-input' , value : null } ,
2586+ botToken : { id : 'botToken' , type : 'short-input' , value : null } ,
2587+ includeFiles : { id : 'includeFiles' , type : 'switch' , value : false } ,
2588+ triggerConfig : {
2589+ id : 'triggerConfig' ,
2590+ type : 'short-input' ,
2591+ value : { signingSecret : 'secret123' , botToken : 'token456' } ,
2592+ } ,
2593+ } ,
2594+ } ) ,
2595+ } ,
2596+ } )
2597+
2598+ const currentState = createWorkflowState ( {
2599+ blocks : {
2600+ block1 : createBlock ( 'block1' , {
2601+ type : 'starter' ,
2602+ subBlocks : {
2603+ signingSecret : { id : 'signingSecret' , type : 'short-input' , value : 'secret123' } ,
2604+ botToken : { id : 'botToken' , type : 'short-input' , value : 'token456' } ,
2605+ includeFiles : { id : 'includeFiles' , type : 'switch' , value : true } ,
2606+ triggerConfig : {
2607+ id : 'triggerConfig' ,
2608+ type : 'short-input' ,
2609+ value : { signingSecret : 'secret123' , botToken : 'token456' } ,
2610+ } ,
2611+ } ,
2612+ } ) ,
2613+ } ,
2614+ } )
2615+
2616+ // includeFiles changed from false to true — this IS a real change
2617+ expect ( hasWorkflowChanged ( currentState , deployedState ) ) . toBe ( true )
2618+ }
2619+ )
2620+ } )
2621+
23672622 describe ( 'Trigger Runtime Metadata (Should Not Trigger Change)' , ( ) => {
23682623 it . concurrent ( 'should not detect change when webhookId differs' , ( ) => {
23692624 const deployedState = createWorkflowState ( {
0 commit comments