13233 zapier update route to create workflow apikey etc (#13239)

Fix webhook creation utils and some tests
This commit is contained in:
martmull
2025-07-16 16:23:35 +02:00
committed by GitHub
parent 5ba98475d4
commit 1fc087aeac
7 changed files with 130 additions and 66 deletions

View File

@ -1,16 +1,8 @@
{
"name": "twenty-zapier",
"version": "2.0.1",
"version": "2.0.2",
"description": "Effortlessly sync Twenty with 3000+ apps. Automate tasks, boost productivity, and supercharge your customer relationships!",
"main": "src/index.ts",
"scripts": {
"nx": "NX_DEFAULT_PROJECT=twenty-zapier node ../../node_modules/nx/bin/nx.js",
"format": "prettier . --write \"!build\"",
"test": "yarn build && jest --testTimeout 10000 --rootDir ./lib/test",
"validate": "yarn build && zapier validate",
"versions": "yarn build && zapier versions",
"watch": "yarn clean && npx tsc --watch"
},
"engines": {
"node": "^22.12.0",
"npm": "please-use-yarn",
@ -21,8 +13,11 @@
"convertedByCLIVersion": "15.4.1"
},
"dependencies": {
"@sniptt/guards": "^0.2.0",
"dotenv": "^16.4.5",
"zapier-platform-core": "15.5.1"
"libphonenumber-js": "^1.10.26",
"zapier-platform-core": "15.5.1",
"zod": "3.23.8"
},
"devDependencies": {
"jest": "29.7.0",

View File

@ -12,6 +12,41 @@
},
"dependsOn": ["^build"]
},
"format": {
"executor": "nx:run-commands",
"options": {
"cwd": "{projectRoot}",
"commands": ["prettier . --write \"!build\""]
}
},
"test": {
"executor": "nx:run-commands",
"options": {
"cwd": "{projectRoot}",
"commands": ["NODE_ENV=test && nx run twenty-zapier:build && jest --testTimeout 10000 --rootDir ./lib/test"]
}
},
"validate": {
"executor": "nx:run-commands",
"options": {
"cwd": "{projectRoot}",
"commands": ["nx run twenty-zapier:build && zapier validate"]
}
},
"versions": {
"executor": "nx:run-commands",
"options": {
"cwd": "{projectRoot}",
"commands": ["nx run twenty-zapier:build && zapier versions"]
}
},
"watch":{
"executor": "nx:run-commands",
"options": {
"cwd": "{projectRoot}",
"commands": ["nx run twenty-zapier:clean && npx tsc --watch"]
}
},
"clean": {
"executor": "nx:run-commands",
"options": {

View File

@ -10,56 +10,68 @@ const appTester = createAppTester(App);
describe('triggers.trigger_record.created', () => {
test('should succeed to subscribe', async () => {
const bundle = getBundle({});
bundle.inputData.nameSingular = 'company';
bundle.inputData.operation = DatabaseEventAction.CREATED;
bundle.targetUrl = 'https://test.com';
const result = await appTester(
App.triggers[triggerRecordKey].operation.performSubscribe,
bundle,
);
expect(result).toBeDefined();
expect(result.id).toBeDefined();
const checkDbResult = await appTester(
(z: ZObject, bundle: Bundle) =>
requestDb(
z,
bundle,
`query webhook {webhooks(filter: {id: {eq: "${result.id}"}}){edges {node {id operations}}}}`,
`query webhook {webhook(input: {id: "${result.id}"}){id operations}}`,
),
bundle,
);
expect(checkDbResult.data.webhooks.edges[0].node.operations[0]).toEqual(
'company.created',
);
expect(checkDbResult.data.webhook.operations[0]).toEqual('company.created');
});
test('should succeed to unsubscribe', async () => {
const bundle = getBundle({});
bundle.inputData.nameSingular = 'company';
bundle.inputData.operation = DatabaseEventAction.CREATED;
bundle.targetUrl = 'https://test.com';
const result = await appTester(
App.triggers[triggerRecordKey].operation.performSubscribe,
bundle,
);
const unsubscribeBundle = getBundle({});
unsubscribeBundle.subscribeData = { id: result.id };
const unsubscribeResult = await appTester(
App.triggers[triggerRecordKey].operation.performUnsubscribe,
unsubscribeBundle,
);
expect(unsubscribeResult).toBeDefined();
expect(unsubscribeResult.id).toEqual(result.id);
const checkDbResult = await appTester(
(z: ZObject, bundle: Bundle) =>
requestDb(
z,
bundle,
`query webhook {webhooks(filter: {id: {eq: "${result.id}"}}){edges {node {id operations}}}}`,
),
requestDb(z, bundle, `query webhook {webhooks {id}}`),
bundle,
);
expect(checkDbResult.data.webhooks.edges.length).toEqual(0);
expect(
// @ts-expect-error legacy noImplicitAny
checkDbResult.data.webhooks.filter((webhook) => webhook.id === result.id)
.length,
).toEqual(0);
});
test('should load company from webhook', async () => {
const bundle = {
cleanedRequest: {
@ -77,24 +89,33 @@ describe('triggers.trigger_record.created', () => {
},
},
};
const results = await appTester(
App.triggers[triggerRecordKey].operation.perform,
bundle,
);
expect(results.length).toEqual(1);
const company = results[0];
expect(company.record.id).toEqual('d6ccb1d1-a90b-4822-a992-a0dd946592c9');
});
it('should load companies from list', async () => {
const bundle = getBundle({});
bundle.inputData.nameSingular = 'company';
bundle.inputData.operation = DatabaseEventAction.CREATED;
const results = await appTester(
App.triggers[triggerRecordKey].operation.performList,
bundle,
);
expect(results.length).toBeGreaterThan(1);
const firstCompany = results[0];
expect(firstCompany.record).toBeDefined();
});
});
@ -102,66 +123,83 @@ describe('triggers.trigger_record.created', () => {
describe('triggers.trigger_record.update', () => {
test('should succeed to subscribe', async () => {
const bundle = getBundle({});
bundle.inputData.nameSingular = 'company';
bundle.inputData.operation = DatabaseEventAction.UPDATED;
bundle.targetUrl = 'https://test.com';
const result = await appTester(
App.triggers[triggerRecordKey].operation.performSubscribe,
bundle,
);
expect(result).toBeDefined();
expect(result.id).toBeDefined();
const checkDbResult = await appTester(
(z: ZObject, bundle: Bundle) =>
requestDb(
z,
bundle,
`query webhook {webhooks(filter: {id: {eq: "${result.id}"}}){edges {node {id operations}}}}`,
`query webhook {webhook(input: {id: "${result.id}"}){id operations}}`,
),
bundle,
);
expect(checkDbResult.data.webhooks.edges[0].node.operations[0]).toEqual(
expect(checkDbResult.data.webhooks.operations[0]).toEqual(
'company.updated',
);
});
test('should succeed to unsubscribe', async () => {
const bundle = getBundle({});
bundle.inputData.nameSingular = 'company';
bundle.inputData.operation = DatabaseEventAction.UPDATED;
bundle.targetUrl = 'https://test.com';
const result = await appTester(
App.triggers[triggerRecordKey].operation.performSubscribe,
bundle,
);
const unsubscribeBundle = getBundle({});
unsubscribeBundle.subscribeData = { id: result.id };
const unsubscribeResult = await appTester(
App.triggers[triggerRecordKey].operation.performUnsubscribe,
unsubscribeBundle,
);
expect(unsubscribeResult).toBeDefined();
expect(unsubscribeResult.id).toEqual(result.id);
const checkDbResult = await appTester(
(z: ZObject, bundle: Bundle) =>
requestDb(
z,
bundle,
`query webhook {webhooks(filter: {id: {eq: "${result.id}"}}){edges {node {id operations}}}}`,
),
requestDb(z, bundle, `query webhook {webhooks {id}}`),
bundle,
);
expect(checkDbResult.data.webhooks.edges.length).toEqual(0);
expect(
// @ts-expect-error legacy noImplicitAny
checkDbResult.data.webhooks.filter((webhook) => webhook.id === result.id)
.length,
).toEqual(0);
});
it('should load companies from list', async () => {
const bundle = getBundle({});
bundle.inputData.nameSingular = 'company';
bundle.inputData.operation = DatabaseEventAction.UPDATED;
const results = await appTester(
App.triggers[triggerRecordKey].operation.performList,
bundle,
);
expect(results.length).toBeGreaterThan(1);
const firstCompany = results[0];
expect(firstCompany.record).toBeDefined();
expect(firstCompany.updatedFields).toBeDefined();
});
@ -170,66 +208,83 @@ describe('triggers.trigger_record.update', () => {
describe('triggers.trigger_record.delete', () => {
test('should succeed to subscribe', async () => {
const bundle = getBundle({});
bundle.inputData.nameSingular = 'company';
bundle.inputData.operation = DatabaseEventAction.DELETED;
bundle.targetUrl = 'https://test.com';
const result = await appTester(
App.triggers[triggerRecordKey].operation.performSubscribe,
bundle,
);
expect(result).toBeDefined();
expect(result.id).toBeDefined();
const checkDbResult = await appTester(
(z: ZObject, bundle: Bundle) =>
requestDb(
z,
bundle,
`query webhook {webhooks(filter: {id: {eq: "${result.id}"}}){edges {node {id operations}}}}`,
`query webhook {webhook(input: {id: "${result.id}"}){id operations}}`,
),
bundle,
);
expect(checkDbResult.data.webhooks.edges[0].node.operations[0]).toEqual(
expect(checkDbResult.data.webhooks.operations[0]).toEqual(
'company.deleted',
);
});
test('should succeed to unsubscribe', async () => {
const bundle = getBundle({});
bundle.inputData.nameSingular = 'company';
bundle.inputData.operation = DatabaseEventAction.DELETED;
bundle.targetUrl = 'https://test.com';
const result = await appTester(
App.triggers[triggerRecordKey].operation.performSubscribe,
bundle,
);
const unsubscribeBundle = getBundle({});
unsubscribeBundle.subscribeData = { id: result.id };
const unsubscribeResult = await appTester(
App.triggers[triggerRecordKey].operation.performUnsubscribe,
unsubscribeBundle,
);
expect(unsubscribeResult).toBeDefined();
expect(unsubscribeResult.id).toEqual(result.id);
const checkDbResult = await appTester(
(z: ZObject, bundle: Bundle) =>
requestDb(
z,
bundle,
`query webhook {webhooks(filter: {id: {eq: "${result.id}"}}){edges {node {id operations}}}}`,
),
requestDb(z, bundle, `query webhook {webhooks {id}}`),
bundle,
);
expect(checkDbResult.data.webhooks.edges.length).toEqual(0);
expect(
// @ts-expect-error legacy noImplicitAny
checkDbResult.data.webhooks.filter((webhook) => webhook.id === result.id)
.length,
).toEqual(0);
});
it('should load companies from list', async () => {
const bundle = getBundle({});
bundle.inputData.nameSingular = 'company';
bundle.inputData.operation = DatabaseEventAction.DELETED;
const results = await appTester(
App.triggers[triggerRecordKey].operation.performList,
bundle,
);
expect(results.length).toBeGreaterThan(1);
const firstCompany = results[0];
expect(firstCompany).toBeDefined();
expect(firstCompany.record.id).toBeDefined();
expect(Object.keys(firstCompany).length).toEqual(1);

View File

@ -179,24 +179,6 @@ describe('computeInputFields', () => {
list: false,
placeholder: undefined,
},
{
key: 'xLink__url',
label: 'X: Url',
type: 'string',
helpText: 'Contacts X/Twitter account: Link Url',
required: false,
list: false,
placeholder: undefined,
},
{
key: 'xLink__label',
label: 'X: Label',
type: 'string',
helpText: 'Contacts X/Twitter account: Link Label',
required: false,
list: false,
placeholder: undefined,
},
{
key: 'whatsapp__primaryLinkLabel',
label: 'Whatsapp: Primary Link Label',
@ -224,15 +206,6 @@ describe('computeInputFields', () => {
list: true,
placeholder: '{ url: "", label: "" }',
},
{
key: 'email',
label: 'Email',
type: 'string',
helpText: 'Contacts Email',
required: false,
list: false,
placeholder: undefined,
},
{
key: 'companyId',
label: 'Company id (foreign key)',

View File

@ -45,7 +45,7 @@ describe('utils.handleQueryParams', () => {
'linkedinUrl: {url: "/linkedin_url", label: "Test linkedinUrl"}, ' +
'whatsapp: {primaryLinkUrl: "/whatsapp_url", primaryLinkLabel: "Whatsapp Link", secondaryLinks: [{url: \'/secondary_whatsapp_url\',label: \'Secondary Whatsapp Link\'}]}, ' +
'emails: {primaryEmail: "primary@email.com", additionalEmails: ["secondary@email.com"]}, ' +
'phones: {primaryPhoneNumber: "322110011", primaryPhoneCountryCode: "FR", primaryPhoneCallingCode: "+33", additionalPhones: [{ phoneNumber: \'322110012\', countryCode: \'+33\' }]}, ' +
'phones: {primaryPhoneNumber: "322110011", primaryPhoneCountryCode: "FR", primaryPhoneCallingCode: "+33", additionalPhones: [{ phoneNumber: \'322110012\', countryCode: \'FR\', callingCode: \'+33\' }]}, ' +
'xUrl: {url: "/x_url", label: "Test xUrl"}, ' +
'annualRecurringRevenue: 100000, ' +
'idealCustomerProfile: true, ' +

View File

@ -18,13 +18,15 @@ export const performSubscribe = async (z: ZObject, bundle: Bundle) => {
operations: [
`${bundle.inputData.nameSingular}.${bundle.inputData.operation}`,
],
secret: '',
};
const result = await requestDb(
z,
bundle,
`mutation createWebhook {createWebhook(data:{${handleQueryParams(
`mutation createWebhook {createWebhook(input:{${handleQueryParams(
data,
)}}) {id}}`,
'metadata',
);
return result.data.createWebhook;
};
@ -34,7 +36,8 @@ export const performUnsubscribe = async (z: ZObject, bundle: Bundle) => {
const result = await requestDb(
z,
bundle,
`mutation deleteWebhook {deleteWebhook(${handleQueryParams(data)}) {id}}`,
`mutation deleteWebhook {deleteWebhook(${handleQueryParams(data)})}`,
'metadata',
);
return result.data.deleteWebhook;
};

View File

@ -57174,11 +57174,14 @@ __metadata:
version: 0.0.0-use.local
resolution: "twenty-zapier@workspace:packages/twenty-zapier"
dependencies:
"@sniptt/guards": "npm:^0.2.0"
dotenv: "npm:^16.4.5"
jest: "npm:29.7.0"
libphonenumber-js: "npm:^1.10.26"
rimraf: "npm:^3.0.2"
zapier-platform-cli: "npm:^15.4.1"
zapier-platform-core: "npm:15.5.1"
zod: "npm:3.23.8"
languageName: unknown
linkType: soft