Files
upstream-mirror e7da97194a
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s
snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
2026-09-17 23:28:13 +05:00

785 lines
25 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 { createOneOperationFactory } from 'test/integration/graphql/utils/create-one-operation-factory.util';
import { findManyOperationFactory } from 'test/integration/graphql/utils/find-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 { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
import { gql } from 'graphql-tag';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { isDefined } from 'twenty-shared/utils';
import { type PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
const FIND_CREATED_TIMELINE_ACTIVITY_TYPE = gql`
query FindCreatedTimelineActivityType {
timelineActivityTypes {
id
action
objectUniversalIdentifier
}
}
`;
describe('people merge resolvers (integration)', () => {
let createdPersonIdsForCleaning: string[] = [];
let createdMessageThreadIdsForCleaning: string[] = [];
let createdMessageThreadTargetIdsForCleaning: string[] = [];
let createdTimelineActivityIdsForCleaning: string[] = [];
let createdTimelineActivityTypeId: string;
const createPeoplePair = async (namePrefix: string) => {
const createPersonsResponse = await makeGraphqlAPIRequest(
createManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: 'id',
data: [
{ name: { firstName: namePrefix, lastName: 'Priority' } },
{ name: { firstName: namePrefix, lastName: 'Duplicate' } },
],
}),
);
expect(createPersonsResponse.body.errors).toBeUndefined();
const [priorityPerson, duplicatePerson] =
createPersonsResponse.body.data.createPeople;
createdPersonIdsForCleaning.push(priorityPerson.id, duplicatePerson.id);
return {
priorityPersonId: priorityPerson.id,
duplicatePersonId: duplicatePerson.id,
};
};
beforeAll(async () => {
const response = await makeMetadataAPIRequest({
query: FIND_CREATED_TIMELINE_ACTIVITY_TYPE,
});
expect(response.body.errors).toBeUndefined();
const createdTimelineActivityType =
response.body.data.timelineActivityTypes.find(
({
action,
objectUniversalIdentifier,
}: {
action: string | null;
objectUniversalIdentifier: string | null;
}) => action === 'created' && !isDefined(objectUniversalIdentifier),
);
if (!isDefined(createdTimelineActivityType)) {
throw new Error('Shared created timeline activity type is not installed');
}
createdTimelineActivityTypeId = createdTimelineActivityType.id;
});
afterEach(async () => {
await deleteRecordsByIds(
'messageThreadTarget',
createdMessageThreadTargetIdsForCleaning,
);
createdMessageThreadTargetIdsForCleaning = [];
await deleteRecordsByIds(
'messageThread',
createdMessageThreadIdsForCleaning,
);
createdMessageThreadIdsForCleaning = [];
await deleteRecordsByIds(
'timelineActivity',
createdTimelineActivityIdsForCleaning,
);
createdTimelineActivityIdsForCleaning = [];
await deleteRecordsByIds('person', createdPersonIdsForCleaning);
createdPersonIdsForCleaning = [];
});
describe('migrating related records', () => {
it('should migrate timeline activities targeting the merged person', async () => {
const createPersonsOperation = createManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
data: [
{
name: {
firstName: 'Priority',
lastName: 'Person',
},
},
{
name: {
firstName: 'Duplicate',
lastName: 'Person',
},
},
],
});
const createPersonsResponse = await makeGraphqlAPIRequest(
createPersonsOperation,
);
const [priorityPerson, duplicatePerson] =
createPersonsResponse.body.data.createPeople;
createdPersonIdsForCleaning.push(priorityPerson.id, duplicatePerson.id);
const createTimelineActivityResponse = await makeGraphqlAPIRequest(
createOneOperationFactory({
objectMetadataSingularName: 'timelineActivity',
gqlFields: 'id targetPersonId',
data: {
happensAt: new Date().toISOString(),
timelineActivityTypeId: createdTimelineActivityTypeId,
targetPersonId: duplicatePerson.id,
},
}),
);
expect(createTimelineActivityResponse.body.errors).toBeUndefined();
const timelineActivity =
createTimelineActivityResponse.body.data.createTimelineActivity;
const mergeResponse = await makeGraphqlAPIRequest(
mergeManyOperationFactory({
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
ids: [priorityPerson.id, duplicatePerson.id],
conflictPriorityIndex: 0,
}),
);
expect(mergeResponse.body.errors).toBeUndefined();
const findTimelineActivityResponse = await makeGraphqlAPIRequest(
findOneOperationFactory({
objectMetadataSingularName: 'timelineActivity',
gqlFields: 'id targetPersonId',
filter: {
id: {
eq: timelineActivity.id,
},
},
}),
);
expect(findTimelineActivityResponse.body.errors).toBeUndefined();
expect(
findTimelineActivityResponse.body.data.timelineActivity.targetPersonId,
).toBe(priorityPerson.id);
});
});
describe('migrating related records that carry a unique constraint', () => {
const createMessageThread = async () => {
const response = await makeGraphqlAPIRequest(
createOneOperationFactory({
objectMetadataSingularName: 'messageThread',
gqlFields: 'id',
data: {},
}),
);
expect(response.body.errors).toBeUndefined();
const messageThreadId = response.body.data.createMessageThread.id;
createdMessageThreadIdsForCleaning.push(messageThreadId);
return messageThreadId;
};
const attachPersonToMessageThread = async ({
messageThreadId,
targetPersonId,
}: {
messageThreadId: string;
targetPersonId: string;
}) => {
const response = await makeGraphqlAPIRequest(
createOneOperationFactory({
objectMetadataSingularName: 'messageThreadTarget',
gqlFields: 'id',
data: { messageThreadId, targetPersonId },
}),
);
expect(response.body.errors).toBeUndefined();
createdMessageThreadTargetIdsForCleaning.push(
response.body.data.createMessageThreadTarget.id,
);
};
const findMessageThreadTargetsOfPerson = async (targetPersonId: string) => {
const response = await makeGraphqlAPIRequest(
findManyOperationFactory({
objectMetadataSingularName: 'messageThreadTarget',
objectMetadataPluralName: 'messageThreadTargets',
gqlFields: 'id messageThreadId targetPersonId',
filter: { targetPersonId: { eq: targetPersonId } },
}),
);
expect(response.body.errors).toBeUndefined();
return response.body.data.messageThreadTargets.edges.map(
({ node }: { node: { messageThreadId: string } }) => node,
);
};
it('should merge two people attached to the same message thread', async () => {
const { priorityPersonId, duplicatePersonId } =
await createPeoplePair('Shared thread');
const sharedMessageThreadId = await createMessageThread();
const duplicateOnlyMessageThreadId = await createMessageThread();
await attachPersonToMessageThread({
messageThreadId: sharedMessageThreadId,
targetPersonId: priorityPersonId,
});
await attachPersonToMessageThread({
messageThreadId: sharedMessageThreadId,
targetPersonId: duplicatePersonId,
});
await attachPersonToMessageThread({
messageThreadId: duplicateOnlyMessageThreadId,
targetPersonId: duplicatePersonId,
});
const mergeResponse = await makeGraphqlAPIRequest(
mergeManyOperationFactory({
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
ids: [priorityPersonId, duplicatePersonId],
conflictPriorityIndex: 0,
}),
);
expect(mergeResponse.body.errors).toBeUndefined();
expect(mergeResponse.body.data.mergePeople.id).toBe(priorityPersonId);
const survivingTargets =
await findMessageThreadTargetsOfPerson(priorityPersonId);
expect(
survivingTargets.map(
({ messageThreadId }: { messageThreadId: string }) => messageThreadId,
),
).toEqual(
expect.arrayContaining([
sharedMessageThreadId,
duplicateOnlyMessageThreadId,
]),
);
expect(survivingTargets).toHaveLength(2);
expect(
await findMessageThreadTargetsOfPerson(duplicatePersonId),
).toHaveLength(0);
});
});
describe('migrating more related records than the mutation batch limit', () => {
it('should merge a person carrying more related records than a single batch allows', async () => {
const { priorityPersonId, duplicatePersonId } =
await createPeoplePair('Over batch limit');
const timelineActivityCount = QUERY_MAX_RECORDS + 1;
const timelineActivityPayloads = Array.from(
{ length: timelineActivityCount },
() => ({
happensAt: new Date().toISOString(),
timelineActivityTypeId: createdTimelineActivityTypeId,
targetPersonId: duplicatePersonId,
}),
);
for (
let batchStart = 0;
batchStart < timelineActivityCount;
batchStart += QUERY_MAX_RECORDS
) {
const createResponse = await makeGraphqlAPIRequest(
createManyOperationFactory({
objectMetadataSingularName: 'timelineActivity',
objectMetadataPluralName: 'timelineActivities',
gqlFields: 'id',
data: timelineActivityPayloads.slice(
batchStart,
batchStart + QUERY_MAX_RECORDS,
),
}),
);
expect(createResponse.body.errors).toBeUndefined();
createdTimelineActivityIdsForCleaning.push(
...createResponse.body.data.createTimelineActivities.map(
({ id }: { id: string }) => id,
),
);
}
expect(createdTimelineActivityIdsForCleaning).toHaveLength(
timelineActivityCount,
);
const mergeResponse = await makeGraphqlAPIRequest(
mergeManyOperationFactory({
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
ids: [priorityPersonId, duplicatePersonId],
conflictPriorityIndex: 0,
}),
);
expect(mergeResponse.body.errors).toBeUndefined();
const findMigratedResponse = await makeGraphqlAPIRequest(
findManyOperationFactory({
objectMetadataSingularName: 'timelineActivity',
objectMetadataPluralName: 'timelineActivities',
gqlFields: 'id targetPersonId',
filter: { id: { in: createdTimelineActivityIdsForCleaning } },
first: timelineActivityCount,
}),
);
expect(findMigratedResponse.body.errors).toBeUndefined();
const migratedTargetPersonIds =
findMigratedResponse.body.data.timelineActivities.edges.map(
({ node }: { node: { targetPersonId: string } }) =>
node.targetPersonId,
);
expect(migratedTargetPersonIds).toHaveLength(timelineActivityCount);
expect(new Set(migratedTargetPersonIds)).toEqual(
new Set([priorityPersonId]),
);
});
});
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([
'jane@example.com',
'john.alt@example.com',
'john.work@example.com',
'jane.alt@example.com',
'jane.personal@example.com',
]),
);
expect(mergedPerson.emails.additionalEmails).toHaveLength(5);
});
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(4);
expect(additionalEmails).toEqual(
expect.arrayContaining([
'bob@example.com',
'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@example.com',
'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([
'test2@example.com',
'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: '4445556789' }),
expect.objectContaining({ number: '5559876543' }),
expect.objectContaining({ number: '4441112222' }),
]),
);
expect(mergedPerson.phones.additionalPhones).toHaveLength(3);
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: '987654321' }),
expect.objectContaining({ number: '8104078034' }),
expect.objectContaining({ number: '123456789' }),
]),
);
expect(mergedPerson.whatsapp.additionalPhones).toHaveLength(3);
});
});
});