@@ -32,7 +32,13 @@ import type { Scope } from '@n8n/permissions';
3232import { WorkflowValidationService } from '@/workflows/workflow-validation.service' ;
3333import { createFolder } from '@test-integration/db/folders' ;
3434import { DateTime } from 'luxon' ;
35- import { PROJECT_ROOT , type INode , type IPinData , type IWorkflowBase } from 'n8n-workflow' ;
35+ import {
36+ PROJECT_ROOT ,
37+ calculateWorkflowChecksum ,
38+ type INode ,
39+ type IPinData ,
40+ type IWorkflowBase ,
41+ } from 'n8n-workflow' ;
3642import { v4 as uuid } from 'uuid' ;
3743
3844import { saveCredential } from '../shared/db/credentials' ;
@@ -3953,6 +3959,53 @@ describe('POST /workflows/:workflowId/deactivate', () => {
39533959 expect ( updatedWorkflow ?. activeVersionId ) . toBeNull ( ) ;
39543960 expect ( updatedWorkflow ?. activeVersion ) . toBeNull ( ) ;
39553961 } ) ;
3962+
3963+ test ( 'should block deactivation when expectedChecksum does not match' , async ( ) => {
3964+ const workflow = await createActiveWorkflow ( { } , owner ) ;
3965+ await setActiveVersion ( workflow . id , workflow . versionId ) ;
3966+
3967+ // Simulate another user updating the workflow
3968+ await authOwnerAgent . patch ( `/workflows/${ workflow . id } ` ) . send ( {
3969+ name : 'Updated by another user' ,
3970+ versionId : workflow . versionId ,
3971+ } ) ;
3972+
3973+ // Try to deactivate with outdated checksum
3974+ const outdatedChecksum = await calculateWorkflowChecksum ( workflow ) ;
3975+ const response = await authOwnerAgent
3976+ . post ( `/workflows/${ workflow . id } /deactivate` )
3977+ . send ( { expectedChecksum : outdatedChecksum } ) ;
3978+
3979+ expect ( response . statusCode ) . toBe ( 400 ) ;
3980+ expect ( response . body . code ) . toBe ( 100 ) ;
3981+ } ) ;
3982+
3983+ test ( 'should allow deactivation when expectedChecksum matches' , async ( ) => {
3984+ const workflow = await createActiveWorkflow ( { } , owner ) ;
3985+ await setActiveVersion ( workflow . id , workflow . versionId ) ;
3986+
3987+ // Get the current workflow to compute correct checksum
3988+ const getResponse = await authOwnerAgent . get ( `/workflows/${ workflow . id } ` ) ;
3989+ const currentChecksum = await calculateWorkflowChecksum ( getResponse . body . data ) ;
3990+
3991+ const response = await authOwnerAgent
3992+ . post ( `/workflows/${ workflow . id } /deactivate` )
3993+ . send ( { expectedChecksum : currentChecksum } ) ;
3994+
3995+ expect ( response . statusCode ) . toBe ( 200 ) ;
3996+ expect ( response . body . data . active ) . toBe ( false ) ;
3997+ expect ( response . body . data . activeVersionId ) . toBeNull ( ) ;
3998+ } ) ;
3999+
4000+ test ( 'should allow deactivation without expectedChecksum (backward compatible)' , async ( ) => {
4001+ const workflow = await createActiveWorkflow ( { } , owner ) ;
4002+ await setActiveVersion ( workflow . id , workflow . versionId ) ;
4003+
4004+ const response = await authOwnerAgent . post ( `/workflows/${ workflow . id } /deactivate` ) . send ( { } ) ;
4005+
4006+ expect ( response . statusCode ) . toBe ( 200 ) ;
4007+ expect ( response . body . data . active ) . toBe ( false ) ;
4008+ } ) ;
39564009} ) ;
39574010
39584011describe ( 'POST /workflows/:workflowId/run' , ( ) => {
@@ -4130,6 +4183,49 @@ describe('POST /workflows/:workflowId/archive', () => {
41304183 expect ( historyRecord ! . nodes ) . toEqual ( workflow . nodes ) ;
41314184 expect ( historyRecord ! . connections ) . toEqual ( workflow . connections ) ;
41324185 } ) ;
4186+
4187+ test ( 'should block archive when expectedChecksum does not match' , async ( ) => {
4188+ const workflow = await createWorkflow ( { } , owner ) ;
4189+
4190+ // Simulate another user updating the workflow
4191+ await authOwnerAgent . patch ( `/workflows/${ workflow . id } ` ) . send ( {
4192+ name : 'Updated by another user' ,
4193+ versionId : workflow . versionId ,
4194+ } ) ;
4195+
4196+ // Try to archive with outdated checksum
4197+ const outdatedChecksum = await calculateWorkflowChecksum ( workflow ) ;
4198+ const response = await authOwnerAgent
4199+ . post ( `/workflows/${ workflow . id } /archive` )
4200+ . send ( { expectedChecksum : outdatedChecksum } ) ;
4201+
4202+ expect ( response . statusCode ) . toBe ( 400 ) ;
4203+ expect ( response . body . code ) . toBe ( 100 ) ;
4204+ } ) ;
4205+
4206+ test ( 'should allow archive when expectedChecksum matches' , async ( ) => {
4207+ const workflow = await createWorkflow ( { } , owner ) ;
4208+
4209+ // Get the current workflow to compute correct checksum
4210+ const getResponse = await authOwnerAgent . get ( `/workflows/${ workflow . id } ` ) ;
4211+ const currentChecksum = await calculateWorkflowChecksum ( getResponse . body . data ) ;
4212+
4213+ const response = await authOwnerAgent
4214+ . post ( `/workflows/${ workflow . id } /archive` )
4215+ . send ( { expectedChecksum : currentChecksum } ) ;
4216+
4217+ expect ( response . statusCode ) . toBe ( 200 ) ;
4218+ expect ( response . body . data . isArchived ) . toBe ( true ) ;
4219+ } ) ;
4220+
4221+ test ( 'should allow archive without expectedChecksum (backward compatible)' , async ( ) => {
4222+ const workflow = await createWorkflow ( { } , owner ) ;
4223+
4224+ const response = await authOwnerAgent . post ( `/workflows/${ workflow . id } /archive` ) . send ( { } ) ;
4225+
4226+ expect ( response . statusCode ) . toBe ( 200 ) ;
4227+ expect ( response . body . data . isArchived ) . toBe ( true ) ;
4228+ } ) ;
41334229} ) ;
41344230
41354231describe ( 'POST /workflows/:workflowId/unarchive' , ( ) => {
0 commit comments