snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
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

This commit is contained in:
upstream-mirror
2026-09-17 23:28:13 +05:00
commit e7da97194a
32688 changed files with 4208681 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`calendarEventsResolver (e2e) should find many calendarEvents 1`] = `
{
"createdAt": Any<String>,
"deletedAt": null,
"description": Any<String>,
"endsAt": Any<String>,
"id": "20202020-0001-4e7c-8001-123456789cde",
"startsAt": Any<String>,
"title": Any<String>,
"updatedAt": Any<String>,
}
`;
exports[`calendarEventsResolver (e2e) should find one calendarEvent 1`] = `
{
"createdAt": Any<String>,
"deletedAt": null,
"description": Any<String>,
"endsAt": Any<String>,
"id": "20202020-0001-4e7c-8001-123456789cde",
"startsAt": Any<String>,
"title": Any<String>,
"updatedAt": Any<String>,
}
`;

View File

@@ -0,0 +1,39 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`messagesResolver (e2e) should find many messages 1`] = `
{
"createdAt": Any<String>,
"deletedAt": null,
"id": "20202020-0001-4e7c-8001-123456789bcd",
"subject": "Meeting Request",
"text": "Hello,
I hope this email finds you well. I am writing to request a meeting. I believe it would be beneficial for both parties to collaborate and explore potential opportunities.
Would you be available for a meeting sometime next week? Please let me know your availability, and I will arrange a suitable time.
Looking forward to your response.
Best regards",
"updatedAt": Any<String>,
}
`;
exports[`messagesResolver (e2e) should find one message 1`] = `
{
"createdAt": Any<String>,
"deletedAt": null,
"id": "20202020-0001-4e7c-8001-123456789bcd",
"subject": "Meeting Request",
"text": "Hello,
I hope this email finds you well. I am writing to request a meeting. I believe it would be beneficial for both parties to collaborate and explore potential opportunities.
Would you be available for a meeting sometime next week? Please let me know your availability, and I will arrange a suitable time.
Looking forward to your response.
Best regards",
"updatedAt": Any<String>,
}
`;

View File

@@ -0,0 +1,69 @@
import request from 'supertest';
const client = request(`http://localhost:${APP_PORT}`);
describe('attachmentsResolver (e2e)', () => {
it('should find many attachments', () => {
const queryData = {
query: `
query attachments {
attachments {
edges {
node {
name
fullPath
id
createdAt
updatedAt
deletedAt
targetTaskId
targetNoteId
targetPersonId
targetCompanyId
targetOpportunityId
targetPetId
targetSurveyResultId
}
}
}
}
`,
};
return client
.post('/graphql')
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
.send(queryData)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeDefined();
expect(res.body.errors).toBeUndefined();
})
.expect((res) => {
const data = res.body.data.attachments;
expect(data).toBeDefined();
expect(Array.isArray(data.edges)).toBe(true);
const edges = data.edges;
if (edges.length > 0) {
const attachments = edges[0].node;
expect(attachments).toHaveProperty('name');
expect(attachments).toHaveProperty('fullPath');
expect(attachments).toHaveProperty('id');
expect(attachments).toHaveProperty('createdAt');
expect(attachments).toHaveProperty('updatedAt');
expect(attachments).toHaveProperty('deletedAt');
expect(attachments).toHaveProperty('targetTaskId');
expect(attachments).toHaveProperty('targetNoteId');
expect(attachments).toHaveProperty('targetPersonId');
expect(attachments).toHaveProperty('targetCompanyId');
expect(attachments).toHaveProperty('targetOpportunityId');
expect(attachments).toHaveProperty('targetPetId');
expect(attachments).toHaveProperty('targetSurveyResultId');
}
});
});
});

View File

@@ -0,0 +1,57 @@
import request from 'supertest';
const client = request(`http://localhost:${APP_PORT}`);
describe('blocklistsResolver (e2e)', () => {
it('should find many blocklists', () => {
const queryData = {
query: `
query blocklists {
blocklists {
edges {
node {
handle
id
createdAt
updatedAt
deletedAt
scope
workspaceMemberId
}
}
}
}
`,
};
return client
.post('/graphql')
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
.send(queryData)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeDefined();
expect(res.body.errors).toBeUndefined();
})
.expect((res) => {
const data = res.body.data.blocklists;
expect(data).toBeDefined();
expect(Array.isArray(data.edges)).toBe(true);
const edges = data.edges;
if (edges.length > 0) {
const blocklists = edges[0].node;
expect(blocklists).toHaveProperty('handle');
expect(blocklists).toHaveProperty('id');
expect(blocklists).toHaveProperty('createdAt');
expect(blocklists).toHaveProperty('updatedAt');
expect(blocklists).toHaveProperty('deletedAt');
expect(blocklists).toHaveProperty('scope');
expect(blocklists).toHaveProperty('workspaceMemberId');
}
});
});
});

View File

@@ -0,0 +1,67 @@
import request from 'supertest';
const client = request(`http://localhost:${APP_PORT}`);
describe('calendarChannelEventAssociationsResolver (e2e)', () => {
it('should find many calendarChannelEventAssociations', () => {
const queryData = {
query: `
query calendarChannelEventAssociations {
calendarChannelEventAssociations {
edges {
node {
eventExternalId
recurringEventExternalId
id
createdAt
updatedAt
deletedAt
calendarChannelId
calendarEventId
}
}
}
}
`,
};
return client
.post('/graphql')
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
.send(queryData)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeDefined();
expect(res.body.errors).toBeUndefined();
})
.expect((res) => {
const data = res.body.data.calendarChannelEventAssociations;
expect(data).toBeDefined();
expect(Array.isArray(data.edges)).toBe(true);
const edges = data.edges;
if (edges.length > 0) {
const calendarChannelEventAssociations = edges[0].node;
expect(calendarChannelEventAssociations).toHaveProperty(
'eventExternalId',
);
expect(calendarChannelEventAssociations).toHaveProperty(
'recurringEventExternalId',
);
expect(calendarChannelEventAssociations).toHaveProperty('id');
expect(calendarChannelEventAssociations).toHaveProperty('createdAt');
expect(calendarChannelEventAssociations).toHaveProperty('updatedAt');
expect(calendarChannelEventAssociations).toHaveProperty('deletedAt');
expect(calendarChannelEventAssociations).toHaveProperty(
'calendarChannelId',
);
expect(calendarChannelEventAssociations).toHaveProperty(
'calendarEventId',
);
}
});
});
});

View File

@@ -0,0 +1,65 @@
import request from 'supertest';
const client = request(`http://localhost:${APP_PORT}`);
describe('calendarEventParticipantsResolver (e2e)', () => {
it('should find many calendarEventParticipants', () => {
const queryData = {
query: `
query calendarEventParticipants {
calendarEventParticipants {
edges {
node {
handle
displayName
isOrganizer
responseStatus
id
createdAt
updatedAt
deletedAt
calendarEventId
personId
workspaceMemberId
}
}
}
}
`,
};
return client
.post('/graphql')
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
.send(queryData)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeDefined();
expect(res.body.errors).toBeUndefined();
})
.expect((res) => {
const data = res.body.data.calendarEventParticipants;
expect(data).toBeDefined();
expect(Array.isArray(data.edges)).toBe(true);
const edges = data.edges;
if (edges.length > 0) {
const calendarEventParticipants = edges[0].node;
expect(calendarEventParticipants).toHaveProperty('handle');
expect(calendarEventParticipants).toHaveProperty('displayName');
expect(calendarEventParticipants).toHaveProperty('isOrganizer');
expect(calendarEventParticipants).toHaveProperty('responseStatus');
expect(calendarEventParticipants).toHaveProperty('id');
expect(calendarEventParticipants).toHaveProperty('createdAt');
expect(calendarEventParticipants).toHaveProperty('updatedAt');
expect(calendarEventParticipants).toHaveProperty('deletedAt');
expect(calendarEventParticipants).toHaveProperty('calendarEventId');
expect(calendarEventParticipants).toHaveProperty('personId');
expect(calendarEventParticipants).toHaveProperty('workspaceMemberId');
}
});
});
});

View File

@@ -0,0 +1,61 @@
import { CALENDAR_EVENT_GQL_FIELDS } from 'test/integration/constants/calendar-event-gql-fields.constants';
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 { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { CALENDAR_EVENT_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/calendar-event-data-seeds.constant';
describe('calendarEventsResolver (e2e)', () => {
it('should find many calendarEvents', async () => {
const graphqlOperation = findManyOperationFactory({
objectMetadataSingularName: 'calendarEvent',
objectMetadataPluralName: 'calendarEvents',
gqlFields: CALENDAR_EVENT_GQL_FIELDS,
});
const response = await makeGraphqlAPIRequest(graphqlOperation);
const data = response.body.data.calendarEvents;
expect(data).toBeDefined();
expect(Array.isArray(data.edges)).toBe(true);
const edges = data.edges;
expect(edges.length).toEqual(QUERY_MAX_RECORDS);
const calendarEvent = edges[0].node;
expect(calendarEvent).toMatchSnapshot({
createdAt: expect.any(String),
endsAt: expect.any(String),
startsAt: expect.any(String),
updatedAt: expect.any(String),
title: expect.any(String),
description: expect.any(String),
});
});
it('should find one calendarEvent', async () => {
const graphqlOperation = findOneOperationFactory({
objectMetadataSingularName: 'calendarEvent',
filter: { id: { eq: CALENDAR_EVENT_DATA_SEED_IDS.ID_1 } },
gqlFields: CALENDAR_EVENT_GQL_FIELDS,
});
const response = await makeGraphqlAPIRequest(graphqlOperation);
const data = response.body.data.calendarEvent;
expect(data).toBeDefined();
expect(data).toMatchSnapshot({
createdAt: expect.any(String),
endsAt: expect.any(String),
startsAt: expect.any(String),
updatedAt: expect.any(String),
title: expect.any(String),
description: expect.any(String),
});
});
});

View File

@@ -0,0 +1,510 @@
import { COMPANY_GQL_FIELDS } from 'test/integration/constants/company-gql-fields.constants';
import { createManyOperationFactory } from 'test/integration/graphql/utils/create-many-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 { waitForTimelineActivities } from 'test/integration/utils/wait-for-timeline-activities.util';
describe('companies merge resolvers (integration)', () => {
let createdCompanyIds: string[] = [];
afterEach(async () => {
if (createdCompanyIds.length > 0) {
await deleteRecordsByIds('company', createdCompanyIds);
createdCompanyIds = [];
}
});
describe('merging links composite fields', () => {
it('should merge linkedinLink composite field correctly', async () => {
const createCompaniesOperation = createManyOperationFactory({
objectMetadataSingularName: 'company',
objectMetadataPluralName: 'companies',
gqlFields: COMPANY_GQL_FIELDS,
data: [
{
name: 'Company A',
linkedinLink: {
primaryLinkUrl: 'https://linkedin.com/company/company-a',
primaryLinkLabel: 'Main LinkedIn',
secondaryLinks: [
{
url: 'linkedin.com/company/subsidiary-a1',
label: 'Subsidiary A1',
},
{
url: 'linkedin.com/company/subsidiary-a2',
label: 'Subsidiary A2',
},
],
},
},
{
name: 'Company B',
linkedinLink: {
primaryLinkUrl: 'https://linkedin.com/company/company-b',
primaryLinkLabel: 'Main LinkedIn',
secondaryLinks: [
{
url: 'linkedin.com/company/subsidiary-b1',
label: 'Subsidiary B1',
},
{
url: 'linkedin.com/company/subsidiary-b2',
label: 'Subsidiary B2',
},
],
},
},
],
});
const createResponse = await makeGraphqlAPIRequest(
createCompaniesOperation,
);
expect(createResponse.body.data.createCompanies).toHaveLength(2);
const company1Id = createResponse.body.data.createCompanies[0].id;
const company2Id = createResponse.body.data.createCompanies[1].id;
createdCompanyIds.push(company1Id, company2Id);
await waitForTimelineActivities('targetCompanyId', [
company1Id,
company2Id,
]);
const mergeOperation = mergeManyOperationFactory({
objectMetadataPluralName: 'companies',
gqlFields: COMPANY_GQL_FIELDS,
ids: [company1Id, company2Id],
conflictPriorityIndex: 0,
});
const mergeResponse = await makeGraphqlAPIRequest(mergeOperation);
expect(mergeResponse.body.errors).toBeUndefined();
const mergedCompany = mergeResponse.body.data.mergeCompanies;
expect(mergedCompany.linkedinLink.primaryLinkUrl).toBe(
'https://linkedin.com/company/company-a',
);
expect(mergedCompany.linkedinLink.primaryLinkLabel).toBe('Main LinkedIn');
expect(mergedCompany.linkedinLink.secondaryLinks).toEqual(
expect.arrayContaining([
expect.objectContaining({
url: 'https://linkedin.com/company/company-b',
}),
expect.objectContaining({
url: 'linkedin.com/company/subsidiary-a1',
}),
expect.objectContaining({
url: 'linkedin.com/company/subsidiary-a2',
}),
expect.objectContaining({
url: 'linkedin.com/company/subsidiary-b1',
}),
expect.objectContaining({
url: 'linkedin.com/company/subsidiary-b2',
}),
]),
);
expect(mergedCompany.linkedinLink.secondaryLinks).toHaveLength(5);
});
it('should merge links with deduplication', async () => {
const createCompaniesOperation = createManyOperationFactory({
objectMetadataSingularName: 'company',
objectMetadataPluralName: 'companies',
gqlFields: COMPANY_GQL_FIELDS,
data: [
{
name: 'Tech Corp',
linkedinLink: {
primaryLinkUrl: 'https://linkedin.com/company/tech-corp',
primaryLinkLabel: '',
secondaryLinks: [
{
url: 'linkedin.com/company/shared-subsidiary',
label: 'Shared Sub',
},
{
url: 'linkedin.com/company/tech-division',
label: 'Tech Division',
},
],
},
},
{
name: 'Corp Tech',
linkedinLink: {
primaryLinkUrl: 'https://linkedin.com/company/corp-tech',
primaryLinkLabel: '',
secondaryLinks: [
{
url: 'linkedin.com/company/shared-subsidiary',
label: 'Shared Sub Different Label',
},
{
url: 'linkedin.com/company/corp-division',
label: 'Corp Division',
},
],
},
},
],
});
const createResponse = await makeGraphqlAPIRequest(
createCompaniesOperation,
);
const company1Id = createResponse.body.data.createCompanies[0].id;
const company2Id = createResponse.body.data.createCompanies[1].id;
createdCompanyIds.push(company1Id, company2Id);
await waitForTimelineActivities('targetCompanyId', [
company1Id,
company2Id,
]);
const mergeOperation = mergeManyOperationFactory({
objectMetadataPluralName: 'companies',
gqlFields: COMPANY_GQL_FIELDS,
ids: [company1Id, company2Id],
conflictPriorityIndex: 0,
});
const mergeResponse = await makeGraphqlAPIRequest(mergeOperation);
const mergedCompany = mergeResponse.body.data.mergeCompanies;
expect(mergedCompany.linkedinLink.primaryLinkUrl).toBe(
'https://linkedin.com/company/tech-corp',
);
const secondaryLinks = mergedCompany.linkedinLink.secondaryLinks;
expect(secondaryLinks).toHaveLength(4);
const urls = secondaryLinks.map((link: { url: string }) => link.url);
expect(urls).toEqual(
expect.arrayContaining([
'https://linkedin.com/company/corp-tech',
'linkedin.com/company/shared-subsidiary',
'linkedin.com/company/tech-division',
'linkedin.com/company/corp-division',
]),
);
const duplicateCount = urls.filter(
(url: string) => url === 'linkedin.com/company/shared-subsidiary',
).length;
expect(duplicateCount).toBe(1);
});
it('should respect priority index for links unique constraint', async () => {
const createCompaniesOperation = createManyOperationFactory({
objectMetadataSingularName: 'company',
objectMetadataPluralName: 'companies',
gqlFields: COMPANY_GQL_FIELDS,
data: [
{
name: 'Priority First',
linkedinLink: {
primaryLinkUrl: 'https://linkedin.com/company/first-priority',
primaryLinkLabel: 'First Label',
secondaryLinks: [
{ url: 'linkedin.com/company/first-sub', label: 'First Sub' },
],
},
},
{
name: 'Priority Second',
linkedinLink: {
primaryLinkUrl: 'https://linkedin.com/company/second-priority',
primaryLinkLabel: 'Second Label',
secondaryLinks: [
{ url: 'linkedin.com/company/second-sub', label: 'Second Sub' },
],
},
},
],
});
const createResponse = await makeGraphqlAPIRequest(
createCompaniesOperation,
);
const company1Id = createResponse.body.data.createCompanies[0].id;
const company2Id = createResponse.body.data.createCompanies[1].id;
createdCompanyIds.push(company1Id, company2Id);
await waitForTimelineActivities('targetCompanyId', [
company1Id,
company2Id,
]);
const mergeWithPriority1 = mergeManyOperationFactory({
objectMetadataPluralName: 'companies',
gqlFields: COMPANY_GQL_FIELDS,
ids: [company1Id, company2Id],
conflictPriorityIndex: 1,
});
const mergeResponse = await makeGraphqlAPIRequest(mergeWithPriority1);
const mergedCompany = mergeResponse.body.data.mergeCompanies;
expect(mergedCompany.linkedinLink.primaryLinkUrl).toBe(
'https://linkedin.com/company/second-priority',
);
expect(mergedCompany.linkedinLink.primaryLinkLabel).toBe('Second Label');
expect(mergedCompany.linkedinLink.secondaryLinks).toEqual(
expect.arrayContaining([
expect.objectContaining({
url: 'https://linkedin.com/company/first-priority',
}),
expect.objectContaining({ url: 'linkedin.com/company/first-sub' }),
expect.objectContaining({ url: 'linkedin.com/company/second-sub' }),
]),
);
});
});
describe('migrating related records', () => {
let createdPersonIds: string[] = [];
afterEach(async () => {
if (createdPersonIds.length > 0) {
await deleteRecordsByIds('person', createdPersonIds);
createdPersonIds = [];
}
});
it('should re-parent related records onto the survivor and delete the duplicate', async () => {
const createCompaniesOperation = createManyOperationFactory({
objectMetadataSingularName: 'company',
objectMetadataPluralName: 'companies',
gqlFields: COMPANY_GQL_FIELDS,
data: [{ name: 'Survivor Inc' }, { name: 'Duplicate Inc' }],
});
const createCompaniesResponse = await makeGraphqlAPIRequest(
createCompaniesOperation,
);
const survivorCompanyId =
createCompaniesResponse.body.data.createCompanies[0].id;
const duplicateCompanyId =
createCompaniesResponse.body.data.createCompanies[1].id;
createdCompanyIds.push(survivorCompanyId, duplicateCompanyId);
const createPeopleOperation = createManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: `
id
company {
id
}
`,
data: [
{
name: { firstName: 'Related', lastName: 'Contact' },
companyId: duplicateCompanyId,
},
],
});
const createPeopleResponse = await makeGraphqlAPIRequest(
createPeopleOperation,
);
const relatedPerson = createPeopleResponse.body.data.createPeople[0];
createdPersonIds.push(relatedPerson.id);
expect(relatedPerson.company.id).toBe(duplicateCompanyId);
await waitForTimelineActivities('targetCompanyId', [
survivorCompanyId,
duplicateCompanyId,
]);
await waitForTimelineActivities('targetPersonId', [relatedPerson.id]);
const mergeOperation = mergeManyOperationFactory({
objectMetadataPluralName: 'companies',
gqlFields: COMPANY_GQL_FIELDS,
ids: [survivorCompanyId, duplicateCompanyId],
conflictPriorityIndex: 0,
});
const mergeResponse = await makeGraphqlAPIRequest(mergeOperation);
expect(mergeResponse.body.errors).toBeUndefined();
expect(mergeResponse.body.data.mergeCompanies.id).toBe(survivorCompanyId);
const findPersonOperation = findOneOperationFactory({
objectMetadataSingularName: 'person',
gqlFields: `
id
company {
id
}
`,
filter: { id: { eq: relatedPerson.id } },
});
const findPersonResponse =
await makeGraphqlAPIRequest(findPersonOperation);
expect(findPersonResponse.body.data.person).not.toBeNull();
expect(findPersonResponse.body.data.person.company.id).toBe(
survivorCompanyId,
);
const findDuplicateOperation = findOneOperationFactory({
objectMetadataSingularName: 'company',
gqlFields: `
id
`,
filter: { id: { eq: duplicateCompanyId } },
});
const findDuplicateResponse = await makeGraphqlAPIRequest(
findDuplicateOperation,
);
expect(findDuplicateResponse.body.data.company).toBeNull();
});
it('should re-parent children from every duplicate onto the survivor', async () => {
const createCompaniesOperation = createManyOperationFactory({
objectMetadataSingularName: 'company',
objectMetadataPluralName: 'companies',
gqlFields: COMPANY_GQL_FIELDS,
data: [
{ name: 'Survivor Multi' },
{ name: 'Duplicate One' },
{ name: 'Duplicate Two' },
],
});
const createCompaniesResponse = await makeGraphqlAPIRequest(
createCompaniesOperation,
);
const survivorCompanyId =
createCompaniesResponse.body.data.createCompanies[0].id;
const duplicateOneId =
createCompaniesResponse.body.data.createCompanies[1].id;
const duplicateTwoId =
createCompaniesResponse.body.data.createCompanies[2].id;
createdCompanyIds.push(survivorCompanyId, duplicateOneId, duplicateTwoId);
const createPeopleOperation = createManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: `
id
company {
id
}
`,
data: [
{
name: { firstName: 'Already', lastName: 'OnSurvivor' },
companyId: survivorCompanyId,
},
{
name: { firstName: 'Child', lastName: 'OfDuplicateOne' },
companyId: duplicateOneId,
},
{
name: { firstName: 'Child', lastName: 'OfDuplicateTwo' },
companyId: duplicateTwoId,
},
],
});
const createPeopleResponse = await makeGraphqlAPIRequest(
createPeopleOperation,
);
const createdPeopleIds = createPeopleResponse.body.data.createPeople.map(
(person: { id: string }) => person.id,
);
createdPersonIds.push(...createdPeopleIds);
await waitForTimelineActivities('targetCompanyId', [
survivorCompanyId,
duplicateOneId,
duplicateTwoId,
]);
await waitForTimelineActivities('targetPersonId', createdPeopleIds);
const mergeOperation = mergeManyOperationFactory({
objectMetadataPluralName: 'companies',
gqlFields: COMPANY_GQL_FIELDS,
ids: [survivorCompanyId, duplicateOneId, duplicateTwoId],
conflictPriorityIndex: 0,
});
const mergeResponse = await makeGraphqlAPIRequest(mergeOperation);
expect(mergeResponse.body.errors).toBeUndefined();
expect(mergeResponse.body.data.mergeCompanies.id).toBe(survivorCompanyId);
const findPeopleOperation = findManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: `
id
company {
id
}
`,
filter: { id: { in: createdPeopleIds } },
});
const findPeopleResponse =
await makeGraphqlAPIRequest(findPeopleOperation);
const peopleAfterMerge = findPeopleResponse.body.data.people.edges;
expect(peopleAfterMerge).toHaveLength(3);
peopleAfterMerge.forEach(
(edge: { node: { company: { id: string } } }) => {
expect(edge.node.company.id).toBe(survivorCompanyId);
},
);
const findCompaniesOperation = findManyOperationFactory({
objectMetadataSingularName: 'company',
objectMetadataPluralName: 'companies',
gqlFields: `id`,
filter: {
id: { in: [survivorCompanyId, duplicateOneId, duplicateTwoId] },
},
});
const findCompaniesResponse = await makeGraphqlAPIRequest(
findCompaniesOperation,
);
const remainingCompanyIds =
findCompaniesResponse.body.data.companies.edges.map(
(edge: { node: { id: string } }) => edge.node.id,
);
expect(remainingCompanyIds).toEqual([survivorCompanyId]);
});
});
});

View File

@@ -0,0 +1,65 @@
import request from 'supertest';
const client = request(`http://localhost:${APP_PORT}`);
describe('companiesResolver (e2e)', () => {
it('should find many companies', () => {
const queryData = {
query: `
query companies {
companies {
edges {
node {
name
position
searchVector
id
createdAt
updatedAt
deletedAt
accountOwnerId
tagline
workPolicy
visaSponsorship
}
}
}
}
`,
};
return client
.post('/graphql')
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
.send(queryData)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeDefined();
expect(res.body.errors).toBeUndefined();
})
.expect((res) => {
const data = res.body.data.companies;
expect(data).toBeDefined();
expect(Array.isArray(data.edges)).toBe(true);
const edges = data.edges;
if (edges.length > 0) {
const companies = edges[0].node;
expect(companies).toHaveProperty('name');
expect(companies).toHaveProperty('position');
expect(companies).toHaveProperty('searchVector');
expect(companies).toHaveProperty('id');
expect(companies).toHaveProperty('createdAt');
expect(companies).toHaveProperty('updatedAt');
expect(companies).toHaveProperty('deletedAt');
expect(companies).toHaveProperty('accountOwnerId');
expect(companies).toHaveProperty('tagline');
expect(companies).toHaveProperty('workPolicy');
expect(companies).toHaveProperty('visaSponsorship');
}
});
});
});

View File

@@ -0,0 +1,49 @@
import request from 'supertest';
const client = request(`http://localhost:${APP_PORT}`);
describe('logicFunctionsResolver (e2e)', () => {
it('should find many logicFunctions', () => {
const queryData = {
query: `
query GetManyLogicFunctions {
findManyLogicFunctions {
id
name
description
runtime
createdAt
updatedAt
}
}
`,
};
return client
.post('/metadata')
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
.send(queryData)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeDefined();
expect(res.body.errors).toBeUndefined();
})
.expect((res) => {
const logicFunctions = res.body.data.findManyLogicFunctions;
expect(logicFunctions).toBeDefined();
expect(Array.isArray(logicFunctions)).toBe(true);
if (logicFunctions.length > 0) {
const logicFunction = logicFunctions[0];
expect(logicFunction).toHaveProperty('id');
expect(logicFunction).toHaveProperty('name');
expect(logicFunction).toHaveProperty('description');
expect(logicFunction).toHaveProperty('runtime');
expect(logicFunction).toHaveProperty('createdAt');
expect(logicFunction).toHaveProperty('updatedAt');
}
});
});
});

View File

@@ -0,0 +1,67 @@
import request from 'supertest';
const client = request(`http://localhost:${APP_PORT}`);
describe('messageChannelMessageAssociationsResolver (e2e)', () => {
it('should find many messageChannelMessageAssociations', () => {
const queryData = {
query: `
query messageChannelMessageAssociations {
messageChannelMessageAssociations {
edges {
node {
messageExternalId
messageThreadExternalId
direction
id
createdAt
updatedAt
deletedAt
messageChannelId
messageId
}
}
}
}
`,
};
return client
.post('/graphql')
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
.send(queryData)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeDefined();
expect(res.body.errors).toBeUndefined();
})
.expect((res) => {
const data = res.body.data.messageChannelMessageAssociations;
expect(data).toBeDefined();
expect(Array.isArray(data.edges)).toBe(true);
const edges = data.edges;
if (edges.length > 0) {
const messageChannelMessageAssociations = edges[0].node;
expect(messageChannelMessageAssociations).toHaveProperty(
'messageExternalId',
);
expect(messageChannelMessageAssociations).toHaveProperty(
'messageThreadExternalId',
);
expect(messageChannelMessageAssociations).toHaveProperty('direction');
expect(messageChannelMessageAssociations).toHaveProperty('id');
expect(messageChannelMessageAssociations).toHaveProperty('createdAt');
expect(messageChannelMessageAssociations).toHaveProperty('updatedAt');
expect(messageChannelMessageAssociations).toHaveProperty('deletedAt');
expect(messageChannelMessageAssociations).toHaveProperty(
'messageChannelId',
);
expect(messageChannelMessageAssociations).toHaveProperty('messageId');
}
});
});
});

View File

@@ -0,0 +1,63 @@
import request from 'supertest';
const client = request(`http://localhost:${APP_PORT}`);
describe('messageParticipantsResolver (e2e)', () => {
it('should find many messageParticipants', () => {
const queryData = {
query: `
query messageParticipants {
messageParticipants {
edges {
node {
role
handle
displayName
id
createdAt
updatedAt
deletedAt
messageId
personId
workspaceMemberId
}
}
}
}
`,
};
return client
.post('/graphql')
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
.send(queryData)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeDefined();
expect(res.body.errors).toBeUndefined();
})
.expect((res) => {
const data = res.body.data.messageParticipants;
expect(data).toBeDefined();
expect(Array.isArray(data.edges)).toBe(true);
const edges = data.edges;
if (edges.length > 0) {
const messageParticipants = edges[0].node;
expect(messageParticipants).toHaveProperty('role');
expect(messageParticipants).toHaveProperty('handle');
expect(messageParticipants).toHaveProperty('displayName');
expect(messageParticipants).toHaveProperty('id');
expect(messageParticipants).toHaveProperty('createdAt');
expect(messageParticipants).toHaveProperty('updatedAt');
expect(messageParticipants).toHaveProperty('deletedAt');
expect(messageParticipants).toHaveProperty('messageId');
expect(messageParticipants).toHaveProperty('personId');
expect(messageParticipants).toHaveProperty('workspaceMemberId');
}
});
});
});

View File

@@ -0,0 +1,51 @@
import request from 'supertest';
const client = request(`http://localhost:${APP_PORT}`);
describe('messageThreadsResolver (e2e)', () => {
it('should find many messageThreads', () => {
const queryData = {
query: `
query messageThreads {
messageThreads {
edges {
node {
id
createdAt
updatedAt
deletedAt
}
}
}
}
`,
};
return client
.post('/graphql')
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
.send(queryData)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeDefined();
expect(res.body.errors).toBeUndefined();
})
.expect((res) => {
const data = res.body.data.messageThreads;
expect(data).toBeDefined();
expect(Array.isArray(data.edges)).toBe(true);
const edges = data.edges;
if (edges.length > 0) {
const messageThreads = edges[0].node;
expect(messageThreads).toHaveProperty('id');
expect(messageThreads).toHaveProperty('createdAt');
expect(messageThreads).toHaveProperty('updatedAt');
expect(messageThreads).toHaveProperty('deletedAt');
}
});
});
});

View File

@@ -0,0 +1,53 @@
import { MESSAGE_GQL_FIELDS } from 'test/integration/constants/message-gql-fields.constants';
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 { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { MESSAGE_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/message-data-seeds.constant';
describe('messagesResolver (e2e)', () => {
it('should find many messages', async () => {
const graphqlOperation = findManyOperationFactory({
objectMetadataSingularName: 'message',
objectMetadataPluralName: 'messages',
gqlFields: MESSAGE_GQL_FIELDS,
});
const response = await makeGraphqlAPIRequest(graphqlOperation);
const data = response.body.data.messages;
expect(data).toBeDefined();
expect(Array.isArray(data.edges)).toBe(true);
const edges = data.edges;
expect(edges.length).toEqual(QUERY_MAX_RECORDS);
const message1 = edges[0].node;
expect(message1).toMatchSnapshot({
createdAt: expect.any(String),
updatedAt: expect.any(String),
});
});
it('should find one message', async () => {
const graphqlOperation = findOneOperationFactory({
objectMetadataSingularName: 'message',
filter: { id: { eq: MESSAGE_DATA_SEED_IDS.ID_1 } },
gqlFields: MESSAGE_GQL_FIELDS,
});
const response = await makeGraphqlAPIRequest(graphqlOperation);
const data = response.body.data.message;
expect(data).toBeDefined();
expect(data).toMatchSnapshot({
createdAt: expect.any(String),
updatedAt: expect.any(String),
});
});
});

View File

@@ -0,0 +1,63 @@
import request from 'supertest';
const client = request(`http://localhost:${APP_PORT}`);
describe('noteTargetsResolver (e2e)', () => {
it('should find many noteTargets', () => {
const queryData = {
query: `
query noteTargets {
noteTargets {
edges {
node {
id
createdAt
updatedAt
deletedAt
noteId
targetPersonId
targetCompanyId
targetOpportunityId
targetPetId
targetSurveyResultId
}
}
}
}
`,
};
return client
.post('/graphql')
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
.send(queryData)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeDefined();
expect(res.body.errors).toBeUndefined();
})
.expect((res) => {
const data = res.body.data.noteTargets;
expect(data).toBeDefined();
expect(Array.isArray(data.edges)).toBe(true);
const edges = data.edges;
if (edges.length > 0) {
const noteTargets = edges[0].node;
expect(noteTargets).toHaveProperty('id');
expect(noteTargets).toHaveProperty('createdAt');
expect(noteTargets).toHaveProperty('updatedAt');
expect(noteTargets).toHaveProperty('deletedAt');
expect(noteTargets).toHaveProperty('noteId');
expect(noteTargets).toHaveProperty('targetPersonId');
expect(noteTargets).toHaveProperty('targetCompanyId');
expect(noteTargets).toHaveProperty('targetOpportunityId');
expect(noteTargets).toHaveProperty('targetPetId');
expect(noteTargets).toHaveProperty('targetSurveyResultId');
}
});
});
});

View File

@@ -0,0 +1,60 @@
import request from 'supertest';
const client = request(`http://localhost:${APP_PORT}`);
describe('notesResolver (e2e)', () => {
it('should find many notes', () => {
const queryData = {
query: `
query notes {
notes {
edges {
node {
position
title
bodyV2 {
markdown
blocknote
}
id
createdAt
updatedAt
deletedAt
}
}
}
}
`,
};
return client
.post('/graphql')
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
.send(queryData)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeDefined();
expect(res.body.errors).toBeUndefined();
})
.expect((res) => {
const data = res.body.data.notes;
expect(data).toBeDefined();
expect(Array.isArray(data.edges)).toBe(true);
const edges = data.edges;
if (edges.length > 0) {
const notes = edges[0].node;
expect(notes).toHaveProperty('position');
expect(notes).toHaveProperty('title');
expect(notes).toHaveProperty('bodyV2');
expect(notes).toHaveProperty('id');
expect(notes).toHaveProperty('createdAt');
expect(notes).toHaveProperty('updatedAt');
expect(notes).toHaveProperty('deletedAt');
}
});
});
});

View File

@@ -0,0 +1,71 @@
import request from 'supertest';
const client = request(`http://localhost:${APP_PORT}`);
describe('objectsResolver (e2e)', () => {
it('should find many objects', () => {
const queryData = {
query: `
query objects {
objects {
edges {
node {
id
nameSingular
namePlural
labelSingular
labelPlural
description
icon
isRemote
isActive
isSystem
createdAt
updatedAt
labelIdentifierFieldMetadataId
imageIdentifierFieldMetadataId
}
}
}
}
`,
};
return client
.post('/metadata')
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
.send(queryData)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeDefined();
expect(res.body.errors).toBeUndefined();
})
.expect((res) => {
const data = res.body.data.objects;
expect(data).toBeDefined();
expect(Array.isArray(data.edges)).toBe(true);
const edges = data.edges;
if (edges.length > 0) {
const objects = edges[0].node;
expect(objects).toHaveProperty('id');
expect(objects).toHaveProperty('nameSingular');
expect(objects).toHaveProperty('namePlural');
expect(objects).toHaveProperty('labelSingular');
expect(objects).toHaveProperty('labelPlural');
expect(objects).toHaveProperty('description');
expect(objects).toHaveProperty('icon');
expect(objects).toHaveProperty('isRemote');
expect(objects).toHaveProperty('isActive');
expect(objects).toHaveProperty('isSystem');
expect(objects).toHaveProperty('createdAt');
expect(objects).toHaveProperty('updatedAt');
expect(objects).toHaveProperty('labelIdentifierFieldMetadataId');
expect(objects).toHaveProperty('imageIdentifierFieldMetadataId');
}
});
});
});

View File

@@ -0,0 +1,65 @@
import request from 'supertest';
const client = request(`http://localhost:${APP_PORT}`);
describe('opportunitiesResolver (e2e)', () => {
it('should find many opportunities', () => {
const queryData = {
query: `
query opportunities {
opportunities {
edges {
node {
name
closeDate
stage
position
searchVector
id
createdAt
updatedAt
deletedAt
pointOfContactId
companyId
}
}
}
}
`,
};
return client
.post('/graphql')
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
.send(queryData)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeDefined();
expect(res.body.errors).toBeUndefined();
})
.expect((res) => {
const data = res.body.data.opportunities;
expect(data).toBeDefined();
expect(Array.isArray(data.edges)).toBe(true);
const edges = data.edges;
if (edges.length > 0) {
const opportunities = edges[0].node;
expect(opportunities).toHaveProperty('name');
expect(opportunities).toHaveProperty('closeDate');
expect(opportunities).toHaveProperty('stage');
expect(opportunities).toHaveProperty('position');
expect(opportunities).toHaveProperty('searchVector');
expect(opportunities).toHaveProperty('id');
expect(opportunities).toHaveProperty('createdAt');
expect(opportunities).toHaveProperty('updatedAt');
expect(opportunities).toHaveProperty('deletedAt');
expect(opportunities).toHaveProperty('pointOfContactId');
expect(opportunities).toHaveProperty('companyId');
}
});
});
});

View File

@@ -0,0 +1,166 @@
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 { 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 { deleteAllRecords } from 'test/integration/utils/delete-all-records';
describe('people resolvers (integration)', () => {
let person2Id: string;
beforeAll(async () => {
await deleteAllRecords('person');
});
it('should create many people', async () => {
const graphqlOperation = createManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
data: [
{
name: {
firstName: 'John',
lastName: 'Doe',
},
jobTitle: 'Just Created',
emails: {
primaryEmail: 'john.doe@example.com',
},
},
{
name: {
firstName: 'Jane',
lastName: 'Smith',
},
jobTitle: 'Just Created',
emails: {
primaryEmail: 'jane.smith@example.com',
},
},
{
name: {
firstName: 'Tim',
lastName: 'Apple',
},
jobTitle: 'Just Created',
emails: {
primaryEmail: 'tim.apple@example.com',
},
},
],
upsert: true,
});
const response = await makeGraphqlAPIRequest(graphqlOperation);
expect(response.body.data.createPeople).toHaveLength(3);
expect(response.body.errors).toBeUndefined();
});
it('should update many people', async () => {
const findOneOperation = findOneOperationFactory({
objectMetadataSingularName: 'person',
gqlFields: PERSON_GQL_FIELDS,
filter: {
emails: {
primaryEmail: {
eq: 'jane.smith@example.com',
},
},
},
});
const findOneResponse = await makeGraphqlAPIRequest(findOneOperation);
person2Id = findOneResponse.body.data.person.id;
const graphqlOperation = createManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
data: [
{
emails: {
primaryEmail: 'john.doe@example.com',
},
jobTitle: 'Just Updated',
},
{
id: person2Id,
jobTitle: 'Just Updated',
},
],
upsert: true,
});
const response = await makeGraphqlAPIRequest(graphqlOperation);
const findAllOperation = findManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
});
const findAllResponse = await makeGraphqlAPIRequest(findAllOperation);
expect(findAllResponse.body.data.people.edges.length).toBe(3);
expect(response.body.data.createPeople).toHaveLength(2);
expect(response.body.errors).toBeUndefined();
response.body.data.createPeople.forEach((person: any) => {
expect(person.jobTitle).toEqual('Just Updated');
});
});
it('should update and create many people', async () => {
const graphqlOperation = createManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
data: [
{
emails: {
primaryEmail: 'tim.apple@example.com',
},
jobTitle: 'Just Updated',
},
{
jobTitle: 'Just Created',
emails: {
primaryEmail: 'paul.doe@example.com',
},
},
{
id: person2Id,
jobTitle: 'Email Just Updated',
emails: {
primaryEmail: 'jane.smith@updated.com',
},
},
],
upsert: true,
});
const response = await makeGraphqlAPIRequest(graphqlOperation);
const findAllOperation = findManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
});
const findAllResponse = await makeGraphqlAPIRequest(findAllOperation);
expect(findAllResponse.body.data.people.edges.length).toBe(4);
expect(response.body.data.createPeople).toHaveLength(3);
expect(
response.body.data.createPeople.find(
(person: any) => person.id === person2Id,
).emails.primaryEmail,
).toEqual('jane.smith@updated.com');
expect(response.body.errors).toBeUndefined();
});
});

View File

@@ -0,0 +1,784 @@
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);
});
});
});

View File

@@ -0,0 +1,74 @@
import request from 'supertest';
const client = request(`http://localhost:${APP_PORT}`);
describe('peopleResolver (e2e)', () => {
it('should find many people', () => {
const queryData = {
query: `
query people {
people {
edges {
node {
jobTitle
avatarUrl
avatarFile {
fileId
label
extension
url
}
position
searchVector
id
createdAt
updatedAt
deletedAt
companyId
intro
workPreference
performanceRating
}
}
}
}
`,
};
return client
.post('/graphql')
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
.send(queryData)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeDefined();
expect(res.body.errors).toBeUndefined();
})
.expect((res) => {
const data = res.body.data.people;
expect(data).toBeDefined();
expect(Array.isArray(data.edges)).toBe(true);
const edges = data.edges;
if (edges.length > 0) {
const people = edges[0].node;
expect(people).toHaveProperty('jobTitle');
expect(people).toHaveProperty('avatarUrl');
expect(people).toHaveProperty('avatarFile');
expect(people).toHaveProperty('position');
expect(people).toHaveProperty('searchVector');
expect(people).toHaveProperty('id');
expect(people).toHaveProperty('createdAt');
expect(people).toHaveProperty('updatedAt');
expect(people).toHaveProperty('deletedAt');
expect(people).toHaveProperty('companyId');
expect(people).toHaveProperty('intro');
expect(people).toHaveProperty('workPreference');
expect(people).toHaveProperty('performanceRating');
}
});
});
});

View File

@@ -0,0 +1,57 @@
import request from 'supertest';
const client = request(`http://localhost:${APP_PORT}`);
describe('petsResolver (e2e)', () => {
it('should find many pets', () => {
const queryData = {
query: `
query pets {
pets {
edges {
node {
id
name
createdAt
updatedAt
deletedAt
position
searchVector
}
}
}
}
`,
};
return client
.post('/graphql')
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
.send(queryData)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeDefined();
expect(res.body.errors).toBeUndefined();
})
.expect((res) => {
const data = res.body.data.pets;
expect(data).toBeDefined();
expect(Array.isArray(data.edges)).toBe(true);
const edges = data.edges;
if (edges.length > 0) {
const pets = edges[0].node;
expect(pets).toHaveProperty('id');
expect(pets).toHaveProperty('name');
expect(pets).toHaveProperty('createdAt');
expect(pets).toHaveProperty('updatedAt');
expect(pets).toHaveProperty('deletedAt');
expect(pets).toHaveProperty('position');
expect(pets).toHaveProperty('searchVector');
}
});
});
});

View File

@@ -0,0 +1,63 @@
import request from 'supertest';
const client = request(`http://localhost:${APP_PORT}`);
describe('taskTargetsResolver (e2e)', () => {
it('should find many taskTargets', () => {
const queryData = {
query: `
query taskTargets {
taskTargets {
edges {
node {
id
createdAt
updatedAt
deletedAt
taskId
targetPersonId
targetCompanyId
targetOpportunityId
targetPetId
targetSurveyResultId
}
}
}
}
`,
};
return client
.post('/graphql')
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
.send(queryData)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeDefined();
expect(res.body.errors).toBeUndefined();
})
.expect((res) => {
const data = res.body.data.taskTargets;
expect(data).toBeDefined();
expect(Array.isArray(data.edges)).toBe(true);
const edges = data.edges;
if (edges.length > 0) {
const taskTargets = edges[0].node;
expect(taskTargets).toHaveProperty('id');
expect(taskTargets).toHaveProperty('createdAt');
expect(taskTargets).toHaveProperty('updatedAt');
expect(taskTargets).toHaveProperty('deletedAt');
expect(taskTargets).toHaveProperty('taskId');
expect(taskTargets).toHaveProperty('targetPersonId');
expect(taskTargets).toHaveProperty('targetCompanyId');
expect(taskTargets).toHaveProperty('targetOpportunityId');
expect(taskTargets).toHaveProperty('targetPetId');
expect(taskTargets).toHaveProperty('targetSurveyResultId');
}
});
});
});

View File

@@ -0,0 +1,66 @@
import request from 'supertest';
const client = request(`http://localhost:${APP_PORT}`);
describe('tasksResolver (e2e)', () => {
it('should find many tasks', () => {
const queryData = {
query: `
query tasks {
tasks {
edges {
node {
position
title
bodyV2 {
markdown
blocknote
}
dueAt
status
id
createdAt
updatedAt
deletedAt
assigneeId
}
}
}
}
`,
};
return client
.post('/graphql')
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
.send(queryData)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeDefined();
expect(res.body.errors).toBeUndefined();
})
.expect((res) => {
const data = res.body.data.tasks;
expect(data).toBeDefined();
expect(Array.isArray(data.edges)).toBe(true);
const edges = data.edges;
if (edges.length > 0) {
const tasks = edges[0].node;
expect(tasks).toHaveProperty('position');
expect(tasks).toHaveProperty('title');
expect(tasks).toHaveProperty('bodyV2');
expect(tasks).toHaveProperty('dueAt');
expect(tasks).toHaveProperty('status');
expect(tasks).toHaveProperty('id');
expect(tasks).toHaveProperty('createdAt');
expect(tasks).toHaveProperty('updatedAt');
expect(tasks).toHaveProperty('deletedAt');
expect(tasks).toHaveProperty('assigneeId');
}
});
});
});

View File

@@ -0,0 +1,85 @@
import request from 'supertest';
const client = request(`http://localhost:${APP_PORT}`);
describe('timelineActivitiesResolver (e2e)', () => {
it('should find many timelineActivities', () => {
const queryData = {
query: `
query timelineActivities {
timelineActivities {
edges {
node {
happensAt
name
properties
linkedRecordCachedName
linkedRecordId
linkedObjectMetadataId
id
createdAt
updatedAt
deletedAt
workspaceMemberId
targetPersonId
targetCompanyId
targetOpportunityId
targetNoteId
targetTaskId
targetWorkflowId
targetWorkflowVersionId
targetWorkflowRunId
targetPetId
targetSurveyResultId
}
}
}
}
`,
};
return client
.post('/graphql')
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
.send(queryData)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeDefined();
expect(res.body.errors).toBeUndefined();
})
.expect((res) => {
const data = res.body.data.timelineActivities;
expect(data).toBeDefined();
expect(Array.isArray(data.edges)).toBe(true);
const edges = data.edges;
if (edges.length > 0) {
const timelineActivities = edges[0].node;
expect(timelineActivities).toHaveProperty('happensAt');
expect(timelineActivities).toHaveProperty('name');
expect(timelineActivities).toHaveProperty('properties');
expect(timelineActivities).toHaveProperty('linkedRecordCachedName');
expect(timelineActivities).toHaveProperty('linkedRecordId');
expect(timelineActivities).toHaveProperty('linkedObjectMetadataId');
expect(timelineActivities).toHaveProperty('id');
expect(timelineActivities).toHaveProperty('createdAt');
expect(timelineActivities).toHaveProperty('updatedAt');
expect(timelineActivities).toHaveProperty('deletedAt');
expect(timelineActivities).toHaveProperty('workspaceMemberId');
expect(timelineActivities).toHaveProperty('targetPersonId');
expect(timelineActivities).toHaveProperty('targetCompanyId');
expect(timelineActivities).toHaveProperty('targetOpportunityId');
expect(timelineActivities).toHaveProperty('targetNoteId');
expect(timelineActivities).toHaveProperty('targetTaskId');
expect(timelineActivities).toHaveProperty('targetWorkflowId');
expect(timelineActivities).toHaveProperty('targetWorkflowVersionId');
expect(timelineActivities).toHaveProperty('targetWorkflowRunId');
expect(timelineActivities).toHaveProperty('targetPetId');
expect(timelineActivities).toHaveProperty('targetSurveyResultId');
}
});
});
});

View File

@@ -0,0 +1,57 @@
import request from 'supertest';
const client = request(`http://localhost:${APP_PORT}`);
describe('workflowAutomatedTriggersResolver (e2e)', () => {
it('should find many workflowAutomatedTriggers', () => {
const queryData = {
query: `
query workflowAutomatedTriggers {
workflowAutomatedTriggers {
edges {
node {
id
type
settings
createdAt
updatedAt
deletedAt
workflowId
}
}
}
}
`,
};
return client
.post('/graphql')
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
.send(queryData)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeDefined();
expect(res.body.errors).toBeUndefined();
})
.expect((res) => {
const data = res.body.data.workflowAutomatedTriggers;
expect(data).toBeDefined();
expect(Array.isArray(data.edges)).toBe(true);
const edges = data.edges;
if (edges.length > 0) {
const workflowAutomatedTriggers = edges[0].node;
expect(workflowAutomatedTriggers).toHaveProperty('id');
expect(workflowAutomatedTriggers).toHaveProperty('type');
expect(workflowAutomatedTriggers).toHaveProperty('settings');
expect(workflowAutomatedTriggers).toHaveProperty('createdAt');
expect(workflowAutomatedTriggers).toHaveProperty('updatedAt');
expect(workflowAutomatedTriggers).toHaveProperty('deletedAt');
expect(workflowAutomatedTriggers).toHaveProperty('workflowId');
}
});
});
});

View File

@@ -0,0 +1,63 @@
import request from 'supertest';
const client = request(`http://localhost:${APP_PORT}`);
describe('workflowVersionsResolver (e2e)', () => {
it('should find many workflowVersions', () => {
const queryData = {
query: `
query workflowVersions {
workflowVersions {
edges {
node {
name
trigger
steps
status
position
id
createdAt
updatedAt
deletedAt
workflowId
}
}
}
}
`,
};
return client
.post('/graphql')
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
.send(queryData)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeDefined();
expect(res.body.errors).toBeUndefined();
})
.expect((res) => {
const data = res.body.data.workflowVersions;
expect(data).toBeDefined();
expect(Array.isArray(data.edges)).toBe(true);
const edges = data.edges;
if (edges.length > 0) {
const workflowVersions = edges[0].node;
expect(workflowVersions).toHaveProperty('name');
expect(workflowVersions).toHaveProperty('trigger');
expect(workflowVersions).toHaveProperty('steps');
expect(workflowVersions).toHaveProperty('status');
expect(workflowVersions).toHaveProperty('position');
expect(workflowVersions).toHaveProperty('id');
expect(workflowVersions).toHaveProperty('createdAt');
expect(workflowVersions).toHaveProperty('updatedAt');
expect(workflowVersions).toHaveProperty('deletedAt');
expect(workflowVersions).toHaveProperty('workflowId');
}
});
});
});

View File

@@ -0,0 +1,59 @@
import request from 'supertest';
const client = request(`http://localhost:${APP_PORT}`);
describe('workflowsResolver (e2e)', () => {
it('should find many workflows', () => {
const queryData = {
query: `
query workflows {
workflows {
edges {
node {
name
lastPublishedVersionId
statuses
position
id
createdAt
updatedAt
deletedAt
}
}
}
}
`,
};
return client
.post('/graphql')
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
.send(queryData)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeDefined();
expect(res.body.errors).toBeUndefined();
})
.expect((res) => {
const data = res.body.data.workflows;
expect(data).toBeDefined();
expect(Array.isArray(data.edges)).toBe(true);
const edges = data.edges;
if (edges.length > 0) {
const workflows = edges[0].node;
expect(workflows).toHaveProperty('name');
expect(workflows).toHaveProperty('lastPublishedVersionId');
expect(workflows).toHaveProperty('statuses');
expect(workflows).toHaveProperty('position');
expect(workflows).toHaveProperty('id');
expect(workflows).toHaveProperty('createdAt');
expect(workflows).toHaveProperty('updatedAt');
expect(workflows).toHaveProperty('deletedAt');
}
});
});
});

View File

@@ -0,0 +1,67 @@
import request from 'supertest';
const client = request(`http://localhost:${APP_PORT}`);
describe('workspaceMembersResolver (e2e)', () => {
it('should find many workspaceMembers', () => {
const queryData = {
query: `
query workspaceMembers {
workspaceMembers {
edges {
node {
id
colorScheme
avatarUrl
locale
timeZone
dateFormat
timeFormat
userEmail
userId
createdAt
updatedAt
deletedAt
}
}
}
}
`,
};
return client
.post('/graphql')
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
.send(queryData)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeDefined();
expect(res.body.errors).toBeUndefined();
})
.expect((res) => {
const data = res.body.data.workspaceMembers;
expect(data).toBeDefined();
expect(Array.isArray(data.edges)).toBe(true);
const edges = data.edges;
if (edges.length > 0) {
const workspaceMembers = edges[0].node;
expect(workspaceMembers).toHaveProperty('id');
expect(workspaceMembers).toHaveProperty('colorScheme');
expect(workspaceMembers).toHaveProperty('avatarUrl');
expect(workspaceMembers).toHaveProperty('locale');
expect(workspaceMembers).toHaveProperty('timeZone');
expect(workspaceMembers).toHaveProperty('dateFormat');
expect(workspaceMembers).toHaveProperty('timeFormat');
expect(workspaceMembers).toHaveProperty('userEmail');
expect(workspaceMembers).toHaveProperty('userId');
expect(workspaceMembers).toHaveProperty('createdAt');
expect(workspaceMembers).toHaveProperty('updatedAt');
expect(workspaceMembers).toHaveProperty('deletedAt');
}
});
});
});