syncable-entity-testing

Syncable Entity: Integration Testing (Step 6/6 - MANDATORY)

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "syncable-entity-testing" with this command: npx skills add twentyhq/twenty/twentyhq-twenty-syncable-entity-testing

Syncable Entity: Integration Testing (Step 6/6 - MANDATORY)

Purpose: Create comprehensive test suite covering all validation scenarios, input transpilation exceptions, and successful use cases.

When to use: After completing Steps 1-5. Integration tests are REQUIRED for all syncable entities.

Quick Start

Tests must cover:

  • Failing scenarios - All validator exceptions and input transpilation errors

  • Successful scenarios - All CRUD operations and edge cases

  • Test utilities - Reusable query factories and helper functions

Test pattern: Two-file pattern (query factory + wrapper) for each operation.

Step 1: Create Test Utilities

Pattern: Query Factory

File: test/integration/metadata/suites/my-entity/utils/create-my-entity-query-factory.util.ts

import gql from 'graphql-tag'; import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; import { type CreateMyEntityInput } from 'src/engine/metadata-modules/my-entity/dtos/create-my-entity.input';

export type CreateMyEntityFactoryInput = CreateMyEntityInput;

const DEFAULT_MY_ENTITY_GQL_FIELDS = id name label description isCustom createdAt updatedAt;

export const createMyEntityQueryFactory = ({ input, gqlFields = DEFAULT_MY_ENTITY_GQL_FIELDS, }: PerformMetadataQueryParams<CreateMyEntityFactoryInput>) => ({ query: gql mutation CreateMyEntity($input: CreateMyEntityInput!) { createMyEntity(input: $input) { ${gqlFields} } } , variables: { input, }, });

Pattern: Wrapper Utility

File: test/integration/metadata/suites/my-entity/utils/create-my-entity.util.ts

import { type CreateMyEntityFactoryInput, createMyEntityQueryFactory, } from 'test/integration/metadata/suites/my-entity/utils/create-my-entity-query-factory.util'; import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type'; import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util'; import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util'; import { type MyEntityDto } from 'src/engine/metadata-modules/my-entity/dtos/my-entity.dto';

export const createMyEntity = async ({ input, gqlFields, expectToFail = false, token, }: PerformMetadataQueryParams<CreateMyEntityFactoryInput>): CommonResponseBody<{ createMyEntity: MyEntityDto; }> => { const graphqlOperation = createMyEntityQueryFactory({ input, gqlFields, });

const response = await makeMetadataAPIRequest(graphqlOperation, token);

if (expectToFail === true) { warnIfNoErrorButExpectedToFail({ response, errorMessage: 'My entity creation should have failed but did not', }); }

if (expectToFail === false) { warnIfErrorButNotExpectedToFail({ response, errorMessage: 'My entity creation has failed but should not', }); }

return { data: response.body.data, errors: response.body.errors }; };

Required utilities (follow same pattern):

  • update-my-entity-query-factory.util.ts
  • update-my-entity.util.ts
  • delete-my-entity-query-factory.util.ts
  • delete-my-entity.util.ts

Step 2: Failing Creation Tests

File: test/integration/metadata/suites/my-entity/failing-my-entity-creation.integration-spec.ts

import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util'; import { createMyEntity } from 'test/integration/metadata/suites/my-entity/utils/create-my-entity.util'; import { deleteMyEntity } from 'test/integration/metadata/suites/my-entity/utils/delete-my-entity.util'; import { eachTestingContextFilter, type EachTestingContext, } from 'twenty-shared/testing'; import { isDefined } from 'twenty-shared/utils'; import { type CreateMyEntityInput } from 'src/engine/metadata-modules/my-entity/dtos/create-my-entity.input';

type TestContext = { input: CreateMyEntityInput; };

type GlobalTestContext = { existingEntityLabel: string; existingEntityName: string; };

const globalTestContext: GlobalTestContext = { existingEntityLabel: 'Existing Test Entity', existingEntityName: 'existingTestEntity', };

type CreateMyEntityTestingContext = EachTestingContext<TestContext>[];

describe('My entity creation should fail', () => { let existingEntityId: string | undefined;

beforeAll(async () => { // Setup: Create entity for uniqueness tests const { data } = await createMyEntity({ expectToFail: false, input: { name: globalTestContext.existingEntityName, label: globalTestContext.existingEntityLabel, }, });

existingEntityId = data.createMyEntity.id;

});

afterAll(async () => { // Cleanup if (isDefined(existingEntityId)) { await deleteMyEntity({ expectToFail: false, input: { id: existingEntityId }, }); } });

const failingMyEntityCreationTestCases: CreateMyEntityTestingContext = [ // Input transpilation validation { title: 'when name is missing', context: { input: { label: 'Entity Missing Name', } as CreateMyEntityInput, }, }, { title: 'when label is missing', context: { input: { name: 'entityMissingLabel', } as CreateMyEntityInput, }, }, { title: 'when name is empty string', context: { input: { name: '', label: 'Empty Name Entity', }, }, },

// Validator business logic
{
  title: 'when name already exists (uniqueness)',
  context: {
    input: {
      name: globalTestContext.existingEntityName,
      label: 'Duplicate Name Entity',
    },
  },
},
{
  title: 'when trying to create standard entity',
  context: {
    input: {
      name: 'myEntity',
      label: 'Standard Entity',
      isCustom: false,
    } as CreateMyEntityInput,
  },
},

// Foreign key validation
{
  title: 'when parentEntityId does not exist',
  context: {
    input: {
      name: 'invalidParentEntity',
      label: 'Invalid Parent Entity',
      parentEntityId: '00000000-0000-0000-0000-000000000000',
    },
  },
},

];

it.each(eachTestingContextFilter(failingMyEntityCreationTestCases))( '$title', async ({ context }) => { const { errors } = await createMyEntity({ expectToFail: true, input: context.input, });

  expectOneNotInternalServerErrorSnapshot({
    errors,
  });
},

); });

Test coverage requirements:

  • ✅ Missing required fields

  • ✅ Empty strings

  • ✅ Invalid format

  • ✅ Uniqueness violations

  • ✅ Standard entity protection

  • ✅ Foreign key validation

Step 3: Successful Creation Tests

File: test/integration/metadata/suites/my-entity/successful-my-entity-creation.integration-spec.ts

import { createMyEntity } from 'test/integration/metadata/suites/my-entity/utils/create-my-entity.util'; import { deleteMyEntity } from 'test/integration/metadata/suites/my-entity/utils/delete-my-entity.util'; import { type CreateMyEntityInput } from 'src/engine/metadata-modules/my-entity/dtos/create-my-entity.input';

describe('My entity creation should succeed', () => { let createdEntityId: string;

afterEach(async () => { if (createdEntityId) { await deleteMyEntity({ expectToFail: false, input: { id: createdEntityId }, }); } });

it('should create entity with minimal required input', async () => { const { data } = await createMyEntity({ expectToFail: false, input: { name: 'minimalEntity', label: 'Minimal Entity', }, });

createdEntityId = data?.createMyEntity?.id;

expect(data.createMyEntity).toMatchObject({
  id: expect.any(String),
  name: 'minimalEntity',
  label: 'Minimal Entity',
  description: null,
  isCustom: true,
  createdAt: expect.any(String),
  updatedAt: expect.any(String),
});

});

it('should create entity with all optional fields', async () => { const input = { name: 'fullEntity', label: 'Full Entity', description: 'Entity with all fields specified', } as const satisfies CreateMyEntityInput;

const { data } = await createMyEntity({
  expectToFail: false,
  input,
});

createdEntityId = data?.createMyEntity?.id;

expect(data.createMyEntity).toMatchObject({
  id: expect.any(String),
  name: 'fullEntity',
  label: 'Full Entity',
  description: 'Entity with all fields specified',
  isCustom: true,
});

});

it('should sanitize input by trimming whitespace', async () => { const { data } = await createMyEntity({ expectToFail: false, input: { name: ' entityWithSpaces ', label: ' Entity With Spaces ', description: ' Description with spaces ', }, });

createdEntityId = data?.createMyEntity?.id;

expect(data.createMyEntity).toMatchObject({
  id: expect.any(String),
  name: 'entityWithSpaces',
  label: 'Entity With Spaces',
  description: 'Description with spaces',
});

});

it('should handle long text content', async () => { const longDescription = 'A'.repeat(1000);

const { data } = await createMyEntity({
  expectToFail: false,
  input: {
    name: 'longDescEntity',
    label: 'Long Description Entity',
    description: longDescription,
  },
});

createdEntityId = data?.createMyEntity?.id;

expect(data.createMyEntity).toMatchObject({
  id: expect.any(String),
  description: longDescription,
});

}); });

Test coverage requirements:

  • ✅ Minimal required input

  • ✅ All optional fields

  • ✅ Input sanitization

  • ✅ Long text content

  • ✅ Special characters

Step 4: Update and Delete Tests

Create similar test files for update and delete operations:

Required files:

  • failing-my-entity-update.integration-spec.ts

  • successful-my-entity-update.integration-spec.ts

  • failing-my-entity-deletion.integration-spec.ts

  • successful-my-entity-deletion.integration-spec.ts

Testing Best Practices

Pattern: Cleanup

afterEach(async () => { if (createdEntityId) { await deleteMyEntity({ expectToFail: false, input: { id: createdEntityId }, }); } });

Pattern: Type-Safe Inputs

const input = { name: 'myEntity', label: 'My Entity', } as const satisfies CreateMyEntityInput;

Pattern: Snapshot Testing

expectOneNotInternalServerErrorSnapshot({ errors, });

Running Tests

Run all entity tests

npx jest test/integration/metadata/suites/my-entity --config=packages/twenty-server/jest.config.mjs

Run specific test file

npx jest test/integration/metadata/suites/my-entity/failing-my-entity-creation.integration-spec.ts --config=packages/twenty-server/jest.config.mjs

Update snapshots

npx jest test/integration/metadata/suites/my-entity --updateSnapshot --config=packages/twenty-server/jest.config.mjs

Complete Test Checklist

Test Utilities

  • create-my-entity-query-factory.util.ts created

  • create-my-entity.util.ts created

  • update-my-entity-query-factory.util.ts created

  • update-my-entity.util.ts created

  • delete-my-entity-query-factory.util.ts created

  • delete-my-entity.util.ts created

Failing Tests Coverage

  • Missing required fields

  • Empty string validation

  • Uniqueness violations

  • Standard entity protection

  • Foreign key validation

  • JSONB property validation (if applicable)

Successful Tests Coverage

  • Create with minimal input

  • Create with all optional fields

  • Input sanitization (whitespace)

  • Long text content

  • Update single field

  • Update multiple fields

  • Successful deletion

Snapshot Tests

  • All failing tests use expectOneNotInternalServerErrorSnapshot

  • Snapshots committed to snapshots/ directory

Success Criteria

Your integration tests are complete when:

✅ All test utilities created (minimum 6 files) ✅ Failing creation tests cover all validators ✅ Failing update tests cover business rules ✅ Failing deletion tests cover protection rules ✅ Successful tests cover all use cases ✅ All snapshots generated and committed ✅ All tests pass consistently ✅ Test coverage meets requirements (>80%)

Final Step

✅ Step 6 Complete! → Your syncable entity is fully tested and production-ready!

Congratulations! You've successfully created a new syncable entity in Twenty's workspace migration system.

For complete workflow, see @creating-syncable-entity rule.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

syncable-entity-cache-and-transform

No summary provided by upstream source.

Repository SourceNeeds Review
General

syncable-entity-types-and-constants

No summary provided by upstream source.

Repository SourceNeeds Review
General

syncable-entity-runner-and-actions

No summary provided by upstream source.

Repository SourceNeeds Review
General

syncable-entity-builder-and-validation

No summary provided by upstream source.

Repository SourceNeeds Review