Baptiste Devessier
2025-06-12 12:18:38 +02:00
committed by GitHub
parent cb5a895963
commit 6b0517943f
3 changed files with 219 additions and 36 deletions

View File

@ -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();
});
});

View File

@ -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,
);
}
});
}

View File

@ -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,
);
}
});
}