diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/__tests__/assert-form-step-is-valid.util.spec.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/__tests__/assert-form-step-is-valid.util.spec.ts new file mode 100644 index 000000000..5a8cbdba1 --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/__tests__/assert-form-step-is-valid.util.spec.ts @@ -0,0 +1,171 @@ +import { FieldMetadataType } from 'twenty-shared/types'; + +import { WorkflowFormActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/form/types/workflow-form-action-settings.type'; +import { WorkflowTriggerException } from 'src/modules/workflow/workflow-trigger/exceptions/workflow-trigger.exception'; +import { assertFormStepIsValid } from 'src/modules/workflow/workflow-trigger/utils/assert-form-step-is-valid.util'; + +const settings: WorkflowFormActionSettings = { + input: [ + { + id: 'af2009ca-f263-4bd7-9361-f3323eca4ef2', + name: 'text', + type: FieldMetadataType.TEXT, + label: 'Text', + }, + { + id: '3cc0d22a-e6e7-4c07-b45b-c4fbb83ba3bf', + name: 'record', + type: 'RECORD', + label: 'Record', + settings: { + objectName: 'company', + }, + placeholder: '', + }, + ], + outputSchema: { + text: { + type: FieldMetadataType.TEXT, + label: 'Text', + value: 'My text', + isLeaf: true, + }, + record: { + label: 'Record', + value: { + fields: { + id: { + icon: 'Icon123', + type: FieldMetadataType.UUID, + label: 'Id', + value: '123e4567-e89b-12d3-a456-426614174000', + isLeaf: true, + }, + }, + object: { + icon: 'IconBuildingSkyscraper', + label: 'Company', + value: 'A company', + isLeaf: true, + fieldIdName: 'id', + nameSingular: 'company', + }, + _outputSchemaType: 'RECORD', + }, + isLeaf: false, + }, + }, + errorHandlingOptions: { + retryOnFailure: { + value: false, + }, + continueOnFailure: { + value: false, + }, + }, +}; + +describe('assertFormStepIsValid', () => { + it('should throw an exception when input is not provided', () => { + const invalidSettings: WorkflowFormActionSettings = { + ...settings, + // @ts-expect-error Intentionally invalid input + input: undefined, + }; + + expect(() => assertFormStepIsValid(invalidSettings)).toThrow( + WorkflowTriggerException, + ); + expect(() => assertFormStepIsValid(invalidSettings)).toThrow( + 'No input provided in form step', + ); + }); + + it('should throw an exception when input array is empty', () => { + const invalidSettings: WorkflowFormActionSettings = { + ...settings, + input: [], + }; + + expect(() => assertFormStepIsValid(invalidSettings)).toThrow( + WorkflowTriggerException, + ); + expect(() => assertFormStepIsValid(invalidSettings)).toThrow( + 'Form action must have at least one field', + ); + }); + + it('should throw an exception when field names are not unique', () => { + const invalidSettings: WorkflowFormActionSettings = { + ...settings, + input: [ + { + id: 'af2009ca-f263-4bd7-9361-f3323eca4ef2', + name: 'text', + type: FieldMetadataType.TEXT, + label: 'Text', + }, + { + id: '3cc0d22a-e6e7-4c07-b45b-c4fbb83ba3bf', + name: 'text', + type: FieldMetadataType.TEXT, + label: 'Text Duplicate', + }, + ], + }; + + expect(() => assertFormStepIsValid(invalidSettings)).toThrow( + WorkflowTriggerException, + ); + expect(() => assertFormStepIsValid(invalidSettings)).toThrow( + 'Form action fields must have unique names', + ); + }); + + it('should throw an exception when field label is not defined', () => { + const invalidSettings: WorkflowFormActionSettings = { + ...settings, + input: [ + { + id: 'af2009ca-f263-4bd7-9361-f3323eca4ef2', + name: 'text', + type: FieldMetadataType.TEXT, + label: '', + }, + ], + }; + + expect(() => assertFormStepIsValid(invalidSettings)).toThrow( + WorkflowTriggerException, + ); + expect(() => assertFormStepIsValid(invalidSettings)).toThrow( + 'Form action fields must have a defined label and type', + ); + }); + + it('should throw an exception when field type is not defined', () => { + const invalidSettings: WorkflowFormActionSettings = { + ...settings, + input: [ + { + id: 'af2009ca-f263-4bd7-9361-f3323eca4ef2', + name: 'text', + // @ts-expect-error Intentionally invalid type + type: '', + label: 'Text', + }, + ], + }; + + expect(() => assertFormStepIsValid(invalidSettings)).toThrow( + WorkflowTriggerException, + ); + expect(() => assertFormStepIsValid(invalidSettings)).toThrow( + 'Form action fields must have a defined label and type', + ); + }); + + it('should not throw an exception for valid form settings', () => { + expect(() => assertFormStepIsValid(settings)).not.toThrow(); + }); +}); diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-form-step-is-valid.util.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-form-step-is-valid.util.ts new file mode 100644 index 000000000..4d8e2f453 --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-form-step-is-valid.util.ts @@ -0,0 +1,47 @@ +import { isNonEmptyString } from '@sniptt/guards'; + +import { WorkflowFormActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/form/types/workflow-form-action-settings.type'; +import { + WorkflowTriggerException, + WorkflowTriggerExceptionCode, +} from 'src/modules/workflow/workflow-trigger/exceptions/workflow-trigger.exception'; + +export function assertFormStepIsValid(settings: WorkflowFormActionSettings) { + if (!settings.input) { + throw new WorkflowTriggerException( + 'No input provided in form step', + WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER, + ); + } + + if (settings.input.length === 0) { + throw new WorkflowTriggerException( + 'Form action must have at least one field', + WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION, + ); + } + + // Check all fields have unique and defined names + const fieldNames = settings.input.map((fieldMetadata) => fieldMetadata.name); + const uniqueFieldNames = new Set(fieldNames); + + if (fieldNames.length !== uniqueFieldNames.size) { + throw new WorkflowTriggerException( + 'Form action fields must have unique names', + WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION, + ); + } + + // Check all fields have defined labels and types + settings.input.forEach((fieldMetadata) => { + if ( + !isNonEmptyString(fieldMetadata.label) || + !isNonEmptyString(fieldMetadata.type) + ) { + throw new WorkflowTriggerException( + 'Form action fields must have a defined label and type', + WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION, + ); + } + }); +} diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-version-can-be-activated.util.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-version-can-be-activated.util.ts index eb7cabb0c..0a61c6c11 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-version-can-be-activated.util.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-version-can-be-activated.util.ts @@ -1,11 +1,8 @@ -import { isNonEmptyString } from '@sniptt/guards'; - import { WorkflowVersionStatus, WorkflowVersionWorkspaceEntity, } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity'; -import { WorkflowFormActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/form/types/workflow-form-action-settings.type'; import { WorkflowAction, WorkflowActionType, @@ -15,6 +12,7 @@ import { WorkflowTriggerExceptionCode, } from 'src/modules/workflow/workflow-trigger/exceptions/workflow-trigger.exception'; import { WorkflowTriggerType } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type'; +import { assertFormStepIsValid } from 'src/modules/workflow/workflow-trigger/utils/assert-form-step-is-valid.util'; export function assertVersionCanBeActivated( workflowVersion: WorkflowVersionWorkspaceEntity, @@ -212,36 +210,3 @@ function assertStepIsValid(step: WorkflowAction) { break; } } - -function assertFormStepIsValid(settings: WorkflowFormActionSettings) { - if (!settings.input) { - throw new WorkflowTriggerException( - 'No input provided in form step', - WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER, - ); - } - - // Check all fields have unique and defined names - const fieldNames = settings.input.map((fieldMetadata) => fieldMetadata.name); - const uniqueFieldNames = new Set(fieldNames); - - if (fieldNames.length !== uniqueFieldNames.size) { - throw new WorkflowTriggerException( - 'Form action fields must have unique names', - WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION, - ); - } - - // Check all fields have defined labels and types - settings.input.forEach((fieldMetadata) => { - if ( - !isNonEmptyString(fieldMetadata.label) || - !isNonEmptyString(fieldMetadata.type) - ) { - throw new WorkflowTriggerException( - 'Form action fields must have a defined label and type', - WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION, - ); - } - }); -}