Files
twenty/packages/twenty-server/test/integration/graphql/suites/object-generated/people-merge-many.integration-spec.ts
T

423 lines
13 KiB
TypeScript

import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants';
import { createManyOperationFactory } from 'test/integration/graphql/utils/create-many-operation-factory.util';
import { findOneOperationFactory } from 'test/integration/graphql/utils/find-one-operation-factory.util';
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
import { mergeManyOperationFactory } from 'test/integration/graphql/utils/merge-many-operation-factory.util';
import { deleteRecordsByIds } from 'test/integration/utils/delete-records-by-ids';
import { type PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
describe('people merge resolvers (integration)', () => {
let createdPersonIdsForCleaning: string[] = [];
afterEach(async () => {
if (createdPersonIdsForCleaning.length > 0) {
await deleteRecordsByIds('person', createdPersonIdsForCleaning);
createdPersonIdsForCleaning = [];
}
});
describe('merging composite fields', () => {
it('should merge emails composite field correctly', async () => {
const createPersonsOperation = createManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
data: [
{
name: {
firstName: 'John',
lastName: 'Doe',
},
emails: {
primaryEmail: 'john@example.com',
additionalEmails: [
'john.alt@example.com',
'john.work@example.com',
],
},
},
{
name: {
firstName: 'Jane',
lastName: 'Doe',
},
emails: {
primaryEmail: 'jane@example.com',
additionalEmails: [
'jane.alt@example.com',
'jane.personal@example.com',
],
},
},
],
});
const createResponse = await makeGraphqlAPIRequest(
createPersonsOperation,
);
expect(createResponse.body.data.createPeople).toHaveLength(2);
const createdPersonIds = [...createResponse.body.data.createPeople].map(
({ id }: { id: string }) => id,
);
createdPersonIdsForCleaning.push(...createdPersonIds);
const mergeOperation = mergeManyOperationFactory({
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
ids: createdPersonIds,
conflictPriorityIndex: 0,
});
const mergeResponse = await makeGraphqlAPIRequest(mergeOperation);
expect(mergeResponse.body.errors).toBeUndefined();
const mergedPerson = mergeResponse.body.data.mergePeople;
expect(mergedPerson.emails.primaryEmail).toBe('john@example.com');
expect(mergedPerson.emails.additionalEmails).toEqual(
expect.arrayContaining([
'john.alt@example.com',
'john.work@example.com',
'jane.alt@example.com',
'jane.personal@example.com',
]),
);
expect(mergedPerson.emails.additionalEmails).toHaveLength(4);
});
it('should merge emails with deduplication', async () => {
const createPersonsOperation = createManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
data: [
{
name: {
firstName: 'Alice',
lastName: 'Smith',
},
emails: {
primaryEmail: 'alice@example.com',
additionalEmails: [
'shared@example.com',
'alice.work@example.com',
],
},
},
{
name: {
firstName: 'Bob',
lastName: 'Smith',
},
emails: {
primaryEmail: 'bob@example.com',
additionalEmails: ['shared@example.com', 'bob.work@example.com'],
},
},
],
});
const createResponse = await makeGraphqlAPIRequest(
createPersonsOperation,
);
const createdPersonIds = createResponse.body.data.createPeople.map(
({ id }: { id: string }) => id,
);
createdPersonIdsForCleaning.push(...createdPersonIds);
const mergeOperation = mergeManyOperationFactory({
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
ids: createdPersonIds,
conflictPriorityIndex: 0,
});
const mergeResponse = await makeGraphqlAPIRequest(mergeOperation);
const mergedPerson = mergeResponse.body.data.mergePeople;
expect(mergedPerson.emails.primaryEmail).toBe('alice@example.com');
const additionalEmails = mergedPerson.emails.additionalEmails;
expect(additionalEmails).toHaveLength(3);
expect(additionalEmails).toEqual(
expect.arrayContaining([
'shared@example.com',
'alice.work@example.com',
'bob.work@example.com',
]),
);
const duplicateCount = additionalEmails.filter(
(email: string) => email === 'shared@example.com',
).length;
expect(duplicateCount).toBe(1);
});
it('should respect priority index for unique constraint fields', async () => {
const createPersonsOperation = createManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
data: [
{
name: {
firstName: 'First',
lastName: 'Person',
},
emails: {
primaryEmail: 'first@example.com',
additionalEmails: ['first.extra@example.com'],
},
},
{
name: {
firstName: 'Second',
lastName: 'Person',
},
emails: {
primaryEmail: 'second@example.com',
additionalEmails: ['second.extra@example.com'],
},
},
],
});
const createResponse = await makeGraphqlAPIRequest(
createPersonsOperation,
);
const createdPersonIds = createResponse.body.data.createPeople.map(
({ id }: { id: string }) => id,
);
createdPersonIdsForCleaning.push(...createdPersonIds);
const mergeWithPriority1 = mergeManyOperationFactory({
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
ids: createdPersonIds,
conflictPriorityIndex: 1,
});
const mergeResponse = await makeGraphqlAPIRequest(mergeWithPriority1);
const mergedPerson = mergeResponse.body.data.mergePeople;
expect(mergedPerson.emails.primaryEmail).toBe('second@example.com');
expect(mergedPerson.emails.additionalEmails).toEqual(
expect.arrayContaining([
'first.extra@example.com',
'second.extra@example.com',
]),
);
});
it('should handle dry run mode', async () => {
const createPersonsOperation = createManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
data: [
{
name: {
firstName: 'Test1',
lastName: 'User',
},
emails: {
primaryEmail: 'test1@example.com',
additionalEmails: ['test1.extra@example.com'],
},
},
{
name: {
firstName: 'Test2',
lastName: 'User',
},
emails: {
primaryEmail: 'test2@example.com',
additionalEmails: ['test2.extra@example.com'],
},
},
],
});
const createResponse = await makeGraphqlAPIRequest(
createPersonsOperation,
);
const person1Id = createResponse.body.data.createPeople.find(
(people: PersonWorkspaceEntity) =>
people.emails.primaryEmail === 'test1@example.com',
).id;
const person2Id = createResponse.body.data.createPeople.find(
(people: PersonWorkspaceEntity) =>
people.emails.primaryEmail === 'test2@example.com',
).id;
createdPersonIdsForCleaning.push(person1Id, person2Id);
const dryRunMergeOperation = mergeManyOperationFactory({
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
ids: [person1Id, person2Id],
conflictPriorityIndex: 0,
dryRun: true,
});
const dryRunResponse = await makeGraphqlAPIRequest(dryRunMergeOperation);
expect(dryRunResponse.body.errors).toBeUndefined();
const dryRunResult = dryRunResponse.body.data.mergePeople;
expect(dryRunResult.emails.primaryEmail).toBe('test1@example.com');
expect(dryRunResult.emails.additionalEmails).toEqual(
expect.arrayContaining([
'test1.extra@example.com',
'test2.extra@example.com',
]),
);
const findOriginalPersons = findOneOperationFactory({
objectMetadataSingularName: 'person',
gqlFields: PERSON_GQL_FIELDS,
filter: {
id: {
eq: person2Id,
},
},
});
const findResponse = await makeGraphqlAPIRequest(findOriginalPersons);
expect(findResponse.body.data.person).toBeTruthy();
expect(findResponse.body.data.person.emails.primaryEmail).toBe(
'test2@example.com',
);
});
it('should merge phones and whatsapp composite fields correctly', async () => {
const createPersonsOperation = createManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
data: [
{
name: {
firstName: 'Alice',
lastName: 'Johnson',
},
phones: {
primaryPhoneNumber: '5551234567',
primaryPhoneCountryCode: 'US',
primaryPhoneCallingCode: '+1',
additionalPhones: [
{
number: '5559876543',
callingCode: '+1',
countryCode: 'US',
},
],
},
whatsapp: {
primaryPhoneNumber: '810407803',
primaryPhoneCountryCode: 'FR',
primaryPhoneCallingCode: '+33',
additionalPhones: [
{
number: '8104078034',
callingCode: '+91',
countryCode: 'IN',
},
],
},
},
{
name: {
firstName: 'Bob',
lastName: 'Johnson',
},
phones: {
primaryPhoneNumber: '4445556789',
primaryPhoneCountryCode: 'US',
primaryPhoneCallingCode: '+1',
additionalPhones: [
{
number: '4441112222',
callingCode: '+1',
countryCode: 'US',
},
],
},
whatsapp: {
primaryPhoneNumber: '987654321',
primaryPhoneCountryCode: 'FR',
primaryPhoneCallingCode: '+33',
additionalPhones: [
{
number: '123456789',
callingCode: '+44',
countryCode: 'GB',
},
],
},
},
],
});
const createResponse = await makeGraphqlAPIRequest(
createPersonsOperation,
);
expect(createResponse.body.data.createPeople).toHaveLength(2);
const createdPersonIds = createResponse.body.data.createPeople.map(
({ id }: { id: string }) => id,
);
createdPersonIdsForCleaning.push(...createdPersonIds);
const mergeOperation = mergeManyOperationFactory({
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
ids: createdPersonIds,
conflictPriorityIndex: 0,
});
const mergeResponse = await makeGraphqlAPIRequest(mergeOperation);
expect(mergeResponse.body.errors).toBeUndefined();
const mergedPerson = mergeResponse.body.data.mergePeople;
expect(mergedPerson.phones.primaryPhoneNumber).toBe('5551234567');
expect(mergedPerson.phones.primaryPhoneCountryCode).toBe('US');
expect(mergedPerson.phones.primaryPhoneCallingCode).toBe('+1');
expect(mergedPerson.phones.additionalPhones).toEqual(
expect.arrayContaining([
expect.objectContaining({ number: '5559876543' }),
expect.objectContaining({ number: '4441112222' }),
]),
);
expect(mergedPerson.phones.additionalPhones).toHaveLength(2);
expect(mergedPerson.whatsapp.primaryPhoneNumber).toBe('810407803');
expect(mergedPerson.whatsapp.primaryPhoneCountryCode).toBe('FR');
expect(mergedPerson.whatsapp.primaryPhoneCallingCode).toBe('+33');
expect(mergedPerson.whatsapp.additionalPhones).toEqual(
expect.arrayContaining([
expect.objectContaining({ number: '8104078034' }),
expect.objectContaining({ number: '123456789' }),
]),
);
expect(mergedPerson.whatsapp.additionalPhones).toHaveLength(2);
});
});
});