From 38b83f0866b614b65b2bc52e73611d9ae28b9810 Mon Sep 17 00:00:00 2001 From: Baptiste Devessier Date: Thu, 28 Nov 2024 17:43:15 +0100 Subject: [PATCH] Write more tests (#8799) --- .../__tests__/getTriggerStepName.test.ts | 53 ++++++++ .../utils/getTriggerStepName.ts | 8 +- .../utils/__tests__/assertUnreachable.test.ts | 13 ++ ...orkflowWithCurrentVersionIsDefined.test.ts | 16 +++ .../getManualTriggerDefaultSettings.test.ts | 26 ++++ .../getStepDefaultDefinition.test.ts | 120 ++++++++++++++++++ .../getTriggerDefaultDefinition.test.ts | 50 ++++++++ .../isWorkflowRecordCreateAction.test.ts | 79 ++++++++++++ .../src/utils/__tests__/castToString.test.ts | 25 ++++ packages/twenty-front/src/utils/isAnObject.ts | 3 - .../src/utils/promise-to-observable.ts | 16 --- 11 files changed, 387 insertions(+), 22 deletions(-) create mode 100644 packages/twenty-front/src/modules/workflow/search-variables/utils/__tests__/getTriggerStepName.test.ts create mode 100644 packages/twenty-front/src/modules/workflow/utils/__tests__/assertUnreachable.test.ts create mode 100644 packages/twenty-front/src/modules/workflow/utils/__tests__/assertWorkflowWithCurrentVersionIsDefined.test.ts create mode 100644 packages/twenty-front/src/modules/workflow/utils/__tests__/getManualTriggerDefaultSettings.test.ts create mode 100644 packages/twenty-front/src/modules/workflow/utils/__tests__/getStepDefaultDefinition.test.ts create mode 100644 packages/twenty-front/src/modules/workflow/utils/__tests__/getTriggerDefaultDefinition.test.ts create mode 100644 packages/twenty-front/src/modules/workflow/utils/__tests__/isWorkflowRecordCreateAction.test.ts create mode 100644 packages/twenty-front/src/utils/__tests__/castToString.test.ts delete mode 100644 packages/twenty-front/src/utils/isAnObject.ts delete mode 100644 packages/twenty-front/src/utils/promise-to-observable.ts diff --git a/packages/twenty-front/src/modules/workflow/search-variables/utils/__tests__/getTriggerStepName.test.ts b/packages/twenty-front/src/modules/workflow/search-variables/utils/__tests__/getTriggerStepName.test.ts new file mode 100644 index 000000000..f5cfa2680 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/search-variables/utils/__tests__/getTriggerStepName.test.ts @@ -0,0 +1,53 @@ +import { getTriggerStepName } from '../getTriggerStepName'; + +it('returns the expected name for a DATABASE_EVENT trigger', () => { + expect( + getTriggerStepName({ + type: 'DATABASE_EVENT', + name: '', + settings: { + eventName: 'company.created', + outputSchema: {}, + }, + }), + ).toBe('Company is Created'); +}); + +it('returns the expected name for a MANUAL trigger without a defined objectType', () => { + expect( + getTriggerStepName({ + type: 'MANUAL', + name: '', + settings: { + objectType: undefined, + outputSchema: {}, + }, + }), + ).toBe('Manual trigger'); +}); + +it('returns the expected name for a MANUAL trigger with a defined objectType', () => { + expect( + getTriggerStepName({ + type: 'MANUAL', + name: '', + settings: { + objectType: 'company', + outputSchema: {}, + }, + }), + ).toBe('Manual trigger for Company'); +}); + +it('throws when an unknown trigger type is provided', () => { + expect(() => { + getTriggerStepName({ + type: 'unknown' as any, + name: '', + settings: { + objectType: 'company', + outputSchema: {}, + }, + }); + }).toThrow(); +}); diff --git a/packages/twenty-front/src/modules/workflow/search-variables/utils/getTriggerStepName.ts b/packages/twenty-front/src/modules/workflow/search-variables/utils/getTriggerStepName.ts index ae9c4a8e4..abe000c66 100644 --- a/packages/twenty-front/src/modules/workflow/search-variables/utils/getTriggerStepName.ts +++ b/packages/twenty-front/src/modules/workflow/search-variables/utils/getTriggerStepName.ts @@ -2,6 +2,8 @@ import { WorkflowDatabaseEventTrigger, WorkflowTrigger, } from '@/workflow/types/Workflow'; +import { assertUnreachable } from '@/workflow/utils/assertUnreachable'; +import { isDefined } from 'twenty-ui'; import { capitalize } from '~/utils/string/capitalize'; export const getTriggerStepName = (trigger: WorkflowTrigger): string => { @@ -9,14 +11,14 @@ export const getTriggerStepName = (trigger: WorkflowTrigger): string => { case 'DATABASE_EVENT': return getDatabaseEventTriggerStepName(trigger); case 'MANUAL': - if (!trigger.settings.objectType) { + if (!isDefined(trigger.settings.objectType)) { return 'Manual trigger'; } return 'Manual trigger for ' + capitalize(trigger.settings.objectType); - default: - return ''; } + + return assertUnreachable(trigger); }; const getDatabaseEventTriggerStepName = ( diff --git a/packages/twenty-front/src/modules/workflow/utils/__tests__/assertUnreachable.test.ts b/packages/twenty-front/src/modules/workflow/utils/__tests__/assertUnreachable.test.ts new file mode 100644 index 000000000..7c800a247 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/utils/__tests__/assertUnreachable.test.ts @@ -0,0 +1,13 @@ +import { assertUnreachable } from '@/workflow/utils/assertUnreachable'; + +it('throws when argument is not never', () => { + expect(() => { + assertUnreachable(42 as never); + }).toThrow(); +}); + +it('throws with the provided error message when argument is not never', () => { + expect(() => { + assertUnreachable(42 as never, 'Custom error!'); + }).toThrow('Custom error!'); +}); diff --git a/packages/twenty-front/src/modules/workflow/utils/__tests__/assertWorkflowWithCurrentVersionIsDefined.test.ts b/packages/twenty-front/src/modules/workflow/utils/__tests__/assertWorkflowWithCurrentVersionIsDefined.test.ts new file mode 100644 index 000000000..3c13ed535 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/utils/__tests__/assertWorkflowWithCurrentVersionIsDefined.test.ts @@ -0,0 +1,16 @@ +import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow'; +import { assertWorkflowWithCurrentVersionIsDefined } from '../assertWorkflowWithCurrentVersionIsDefined'; + +it('throws when provided workflow is undefined', () => { + expect(() => { + assertWorkflowWithCurrentVersionIsDefined(undefined); + }).toThrow(); +}); + +it("throws when provided workflow's current version is undefined", () => { + expect(() => { + assertWorkflowWithCurrentVersionIsDefined( + {} as unknown as WorkflowWithCurrentVersion, + ); + }).toThrow(); +}); diff --git a/packages/twenty-front/src/modules/workflow/utils/__tests__/getManualTriggerDefaultSettings.test.ts b/packages/twenty-front/src/modules/workflow/utils/__tests__/getManualTriggerDefaultSettings.test.ts new file mode 100644 index 000000000..0673e0e6a --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/utils/__tests__/getManualTriggerDefaultSettings.test.ts @@ -0,0 +1,26 @@ +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; +import { getManualTriggerDefaultSettings } from '../getManualTriggerDefaultSettings'; + +it('returns settings for a manual trigger that can be activated from any where', () => { + expect( + getManualTriggerDefaultSettings({ + availability: 'EVERYWHERE', + activeObjectMetadataItems: generatedMockObjectMetadataItems, + }), + ).toStrictEqual({ + objectType: undefined, + outputSchema: {}, + }); +}); + +it('returns settings for a manual trigger that can be activated from any where', () => { + expect( + getManualTriggerDefaultSettings({ + availability: 'WHEN_RECORD_SELECTED', + activeObjectMetadataItems: generatedMockObjectMetadataItems, + }), + ).toStrictEqual({ + objectType: generatedMockObjectMetadataItems[0].nameSingular, + outputSchema: {}, + }); +}); diff --git a/packages/twenty-front/src/modules/workflow/utils/__tests__/getStepDefaultDefinition.test.ts b/packages/twenty-front/src/modules/workflow/utils/__tests__/getStepDefaultDefinition.test.ts new file mode 100644 index 000000000..8d8515e65 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/utils/__tests__/getStepDefaultDefinition.test.ts @@ -0,0 +1,120 @@ +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; +import { getStepDefaultDefinition } from '../getStepDefaultDefinition'; + +it('returns a valid definition for CODE actions', () => { + expect( + getStepDefaultDefinition({ + type: 'CODE', + activeObjectMetadataItems: generatedMockObjectMetadataItems, + }), + ).toStrictEqual({ + id: expect.any(String), + name: 'Code', + type: 'CODE', + valid: false, + settings: { + input: { + serverlessFunctionId: '', + serverlessFunctionVersion: '', + serverlessFunctionInput: {}, + }, + outputSchema: {}, + errorHandlingOptions: { + continueOnFailure: { + value: false, + }, + retryOnFailure: { + value: false, + }, + }, + }, + }); +}); + +it('returns a valid definition for SEND_EMAIL actions', () => { + expect( + getStepDefaultDefinition({ + type: 'SEND_EMAIL', + activeObjectMetadataItems: generatedMockObjectMetadataItems, + }), + ).toStrictEqual({ + id: expect.any(String), + name: 'Send Email', + type: 'SEND_EMAIL', + valid: false, + settings: { + input: { + connectedAccountId: '', + email: '', + subject: '', + body: '', + }, + outputSchema: {}, + errorHandlingOptions: { + continueOnFailure: { + value: false, + }, + retryOnFailure: { + value: false, + }, + }, + }, + }); +}); + +it('returns a valid definition for RECORD_CRUD.CREATE actions', () => { + expect( + getStepDefaultDefinition({ + type: 'RECORD_CRUD.CREATE', + activeObjectMetadataItems: generatedMockObjectMetadataItems, + }), + ).toStrictEqual({ + id: expect.any(String), + name: 'Create Record', + type: 'RECORD_CRUD', + valid: false, + settings: { + input: { + type: 'CREATE', + objectName: generatedMockObjectMetadataItems[0].nameSingular, + objectRecord: {}, + }, + outputSchema: {}, + errorHandlingOptions: { + continueOnFailure: { + value: false, + }, + retryOnFailure: { + value: false, + }, + }, + }, + }); +}); + +it("throws for RECORD_CRUD.DELETE actions as it's not implemented yet", () => { + expect(() => { + getStepDefaultDefinition({ + type: 'RECORD_CRUD.DELETE', + activeObjectMetadataItems: generatedMockObjectMetadataItems, + }); + }).toThrow('Not implemented yet'); +}); + +it("throws for RECORD_CRUD.UPDATE actions as it's not implemented yet", () => { + expect(() => { + getStepDefaultDefinition({ + type: 'RECORD_CRUD.UPDATE', + activeObjectMetadataItems: generatedMockObjectMetadataItems, + }); + }).toThrow('Not implemented yet'); +}); + +it('throws when providing an unknown type', () => { + expect(() => { + getStepDefaultDefinition({ + type: 'unknown' as any, + activeObjectMetadataItems: generatedMockObjectMetadataItems, + }); + }).toThrow('Unknown type: unknown'); +}); diff --git a/packages/twenty-front/src/modules/workflow/utils/__tests__/getTriggerDefaultDefinition.test.ts b/packages/twenty-front/src/modules/workflow/utils/__tests__/getTriggerDefaultDefinition.test.ts new file mode 100644 index 000000000..8e9244acf --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/utils/__tests__/getTriggerDefaultDefinition.test.ts @@ -0,0 +1,50 @@ +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; +import { getTriggerDefaultDefinition } from '../getTriggerDefaultDefinition'; + +it('throws if the activeObjectMetadataItems list is empty', () => { + expect(() => { + getTriggerDefaultDefinition({ + type: 'DATABASE_EVENT', + activeObjectMetadataItems: [], + }); + }).toThrow(); +}); + +it('returns a valid configuration for DATABASE_EVENT trigger type', () => { + expect( + getTriggerDefaultDefinition({ + type: 'DATABASE_EVENT', + activeObjectMetadataItems: generatedMockObjectMetadataItems, + }), + ).toStrictEqual({ + type: 'DATABASE_EVENT', + settings: { + eventName: `${generatedMockObjectMetadataItems[0].nameSingular}.created`, + outputSchema: {}, + }, + }); +}); + +it('returns a valid configuration for MANUAL trigger type', () => { + expect( + getTriggerDefaultDefinition({ + type: 'MANUAL', + activeObjectMetadataItems: generatedMockObjectMetadataItems, + }), + ).toStrictEqual({ + type: 'MANUAL', + settings: { + objectType: generatedMockObjectMetadataItems[0].nameSingular, + outputSchema: {}, + }, + }); +}); + +it('throws when providing an unknown trigger type', () => { + expect(() => { + getTriggerDefaultDefinition({ + type: 'unknown' as any, + activeObjectMetadataItems: generatedMockObjectMetadataItems, + }); + }).toThrow('Unknown type: unknown'); +}); diff --git a/packages/twenty-front/src/modules/workflow/utils/__tests__/isWorkflowRecordCreateAction.test.ts b/packages/twenty-front/src/modules/workflow/utils/__tests__/isWorkflowRecordCreateAction.test.ts new file mode 100644 index 000000000..8a4e3721f --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/utils/__tests__/isWorkflowRecordCreateAction.test.ts @@ -0,0 +1,79 @@ +import { + WorkflowCodeAction, + WorkflowRecordCRUDAction, +} from '@/workflow/types/Workflow'; +import { isWorkflowRecordCreateAction } from '../isWorkflowRecordCreateAction'; + +it('returns false when providing an action that is not Record Create', () => { + const codeAction: WorkflowCodeAction = { + type: 'CODE', + id: '', + name: '', + settings: { + errorHandlingOptions: { + continueOnFailure: { + value: false, + }, + retryOnFailure: { + value: false, + }, + }, + input: { + serverlessFunctionId: '', + serverlessFunctionVersion: '', + serverlessFunctionInput: {}, + }, + outputSchema: {}, + }, + valid: true, + }; + + expect(isWorkflowRecordCreateAction(codeAction)).toBe(false); +}); + +it('returns false for Record Update', () => { + const codeAction: WorkflowRecordCRUDAction = { + type: 'RECORD_CRUD', + id: '', + name: '', + settings: { + errorHandlingOptions: { + continueOnFailure: { value: false }, + retryOnFailure: { value: false }, + }, + input: { + type: 'UPDATE', + objectName: '', + objectRecord: {}, + objectRecordId: '', + }, + outputSchema: {}, + }, + valid: true, + }; + + expect(isWorkflowRecordCreateAction(codeAction)).toBe(false); +}); + +it('returns true for Record Create', () => { + const codeAction: WorkflowRecordCRUDAction = { + type: 'RECORD_CRUD', + id: '', + name: '', + settings: { + errorHandlingOptions: { + continueOnFailure: { value: false }, + retryOnFailure: { value: false }, + }, + input: { + type: 'CREATE', + objectName: '', + objectRecord: {}, + }, + outputSchema: {}, + }, + valid: true, + }; + + expect(isWorkflowRecordCreateAction(codeAction)).toBe(true); +}); diff --git a/packages/twenty-front/src/utils/__tests__/castToString.test.ts b/packages/twenty-front/src/utils/__tests__/castToString.test.ts new file mode 100644 index 000000000..fc93ea5c9 --- /dev/null +++ b/packages/twenty-front/src/utils/__tests__/castToString.test.ts @@ -0,0 +1,25 @@ +import { castToString } from '../castToString'; + +it('returns an empty string when undefined is provided', () => { + expect(castToString(undefined)).toBe(''); +}); + +it('returns an empty string when null is provided', () => { + expect(castToString(undefined)).toBe(''); +}); + +it('returns an empty string when an empty string is provided', () => { + expect(castToString('')).toBe(''); +}); + +it('preserves strings', () => { + expect(castToString('test')).toBe('test'); +}); + +it('casts numbers to strings', () => { + expect(castToString(21)).toBe('21'); +}); + +it('casts booleans to strings', () => { + expect(castToString(true)).toBe('true'); +}); diff --git a/packages/twenty-front/src/utils/isAnObject.ts b/packages/twenty-front/src/utils/isAnObject.ts deleted file mode 100644 index 019e7ca20..000000000 --- a/packages/twenty-front/src/utils/isAnObject.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const isAnObject = (obj: any): obj is object => { - return typeof obj === 'object' && obj !== null && Object.keys(obj).length > 0; -}; diff --git a/packages/twenty-front/src/utils/promise-to-observable.ts b/packages/twenty-front/src/utils/promise-to-observable.ts deleted file mode 100644 index 41eac9e85..000000000 --- a/packages/twenty-front/src/utils/promise-to-observable.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Observable } from '@apollo/client'; - -export const promiseToObservable = (promise: Promise) => - new Observable((subscriber) => { - promise.then( - (value) => { - if (subscriber.closed) { - return; - } - - subscriber.next(value); - subscriber.complete(); - }, - (err) => subscriber.error(err), - ); - });