first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,787 @@
|
||||
import { mockDeep } from 'jest-mock-extended';
|
||||
import type { IExecuteFunctions, ILoadOptionsFunctions, INode } from 'n8n-workflow';
|
||||
import { NodeApiError } from 'n8n-workflow';
|
||||
|
||||
import * as GenericFunctions from '../GenericFunctions';
|
||||
|
||||
describe('Google Contacts GenericFunctions', () => {
|
||||
let mockExecuteFunctions: jest.Mocked<IExecuteFunctions>;
|
||||
let mockLoadOptionsFunctions: jest.Mocked<ILoadOptionsFunctions>;
|
||||
let mockNode: INode;
|
||||
|
||||
beforeEach(() => {
|
||||
mockExecuteFunctions = mockDeep<IExecuteFunctions>();
|
||||
mockLoadOptionsFunctions = mockDeep<ILoadOptionsFunctions>();
|
||||
mockNode = {
|
||||
id: 'test-node-id',
|
||||
name: 'Google Contacts Test',
|
||||
type: 'n8n-nodes-base.googleContacts',
|
||||
typeVersion: 1,
|
||||
position: [0, 0],
|
||||
parameters: {},
|
||||
};
|
||||
|
||||
jest.clearAllMocks();
|
||||
mockExecuteFunctions.getNode.mockReturnValue(mockNode);
|
||||
mockLoadOptionsFunctions.getNode.mockReturnValue(mockNode);
|
||||
|
||||
// Properly mock the requestOAuth2 helper
|
||||
(mockExecuteFunctions.helpers.requestOAuth2 as jest.Mock) = jest.fn();
|
||||
(mockLoadOptionsFunctions.helpers.requestOAuth2 as jest.Mock) = jest.fn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('googleApiRequest', () => {
|
||||
it('should make successful API request with default parameters', async () => {
|
||||
const mockResponse = { id: 'person123', names: [{ displayName: 'John Doe' }] };
|
||||
(mockExecuteFunctions.helpers.requestOAuth2 as jest.Mock).mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await GenericFunctions.googleApiRequest.call(
|
||||
mockExecuteFunctions,
|
||||
'GET',
|
||||
'/people/me',
|
||||
);
|
||||
|
||||
expect(mockExecuteFunctions.helpers.requestOAuth2).toHaveBeenCalledWith(
|
||||
'googleContactsOAuth2Api',
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'GET',
|
||||
qs: {},
|
||||
uri: 'https://people.googleapis.com/v1/people/me',
|
||||
json: true,
|
||||
},
|
||||
);
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
it('should make API request with custom URI', async () => {
|
||||
const customUri = 'https://custom.api.com/endpoint';
|
||||
const mockResponse = { data: 'custom response' };
|
||||
(mockExecuteFunctions.helpers.requestOAuth2 as jest.Mock).mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await GenericFunctions.googleApiRequest.call(
|
||||
mockExecuteFunctions,
|
||||
'POST',
|
||||
'/custom',
|
||||
{ data: 'test' },
|
||||
{ param: 'value' },
|
||||
customUri,
|
||||
);
|
||||
|
||||
expect(mockExecuteFunctions.helpers.requestOAuth2).toHaveBeenCalledWith(
|
||||
'googleContactsOAuth2Api',
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'POST',
|
||||
body: { data: 'test' },
|
||||
qs: { param: 'value' },
|
||||
uri: customUri,
|
||||
json: true,
|
||||
},
|
||||
);
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
it('should include custom headers when provided', async () => {
|
||||
const customHeaders = { 'X-Custom-Header': 'custom-value' };
|
||||
const mockResponse = { success: true };
|
||||
(mockExecuteFunctions.helpers.requestOAuth2 as jest.Mock).mockResolvedValue(mockResponse);
|
||||
|
||||
await GenericFunctions.googleApiRequest.call(
|
||||
mockExecuteFunctions,
|
||||
'PUT',
|
||||
'/people/123',
|
||||
{ name: 'Updated Name' },
|
||||
{},
|
||||
undefined,
|
||||
customHeaders,
|
||||
);
|
||||
|
||||
expect(mockExecuteFunctions.helpers.requestOAuth2).toHaveBeenCalledWith(
|
||||
'googleContactsOAuth2Api',
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Custom-Header': 'custom-value',
|
||||
},
|
||||
method: 'PUT',
|
||||
body: { name: 'Updated Name' },
|
||||
qs: {},
|
||||
uri: 'https://people.googleapis.com/v1/people/123',
|
||||
json: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should remove empty body from request options', async () => {
|
||||
const mockResponse = { data: 'response' };
|
||||
(mockExecuteFunctions.helpers.requestOAuth2 as jest.Mock).mockResolvedValue(mockResponse);
|
||||
|
||||
await GenericFunctions.googleApiRequest.call(mockExecuteFunctions, 'GET', '/people', {});
|
||||
|
||||
expect(mockExecuteFunctions.helpers.requestOAuth2).toHaveBeenCalledWith(
|
||||
'googleContactsOAuth2Api',
|
||||
expect.objectContaining({
|
||||
method: 'GET',
|
||||
qs: {},
|
||||
uri: 'https://people.googleapis.com/v1/people',
|
||||
json: true,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const callArgs = (mockExecuteFunctions.helpers.requestOAuth2 as jest.Mock).mock.calls[0][1];
|
||||
expect(callArgs).not.toHaveProperty('body');
|
||||
});
|
||||
|
||||
it('should work with ILoadOptionsFunctions context', async () => {
|
||||
const mockResponse = { connections: [] };
|
||||
(mockLoadOptionsFunctions.helpers.requestOAuth2 as jest.Mock).mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await GenericFunctions.googleApiRequest.call(
|
||||
mockLoadOptionsFunctions,
|
||||
'GET',
|
||||
'/people/connections',
|
||||
);
|
||||
|
||||
expect(mockLoadOptionsFunctions.helpers.requestOAuth2).toHaveBeenCalledWith(
|
||||
'googleContactsOAuth2Api',
|
||||
expect.objectContaining({
|
||||
method: 'GET',
|
||||
uri: 'https://people.googleapis.com/v1/people/connections',
|
||||
}),
|
||||
);
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
it('should throw NodeApiError on request failure', async () => {
|
||||
const apiError = new Error('API Error');
|
||||
(mockExecuteFunctions.helpers.requestOAuth2 as jest.Mock).mockRejectedValue(apiError);
|
||||
|
||||
await expect(
|
||||
GenericFunctions.googleApiRequest.call(mockExecuteFunctions, 'GET', '/people/invalid'),
|
||||
).rejects.toThrow(NodeApiError);
|
||||
|
||||
expect(mockExecuteFunctions.getNode).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle authentication errors', async () => {
|
||||
const authError = { code: 401, message: 'Unauthorized' };
|
||||
(mockExecuteFunctions.helpers.requestOAuth2 as jest.Mock).mockRejectedValue(authError);
|
||||
|
||||
await expect(
|
||||
GenericFunctions.googleApiRequest.call(mockExecuteFunctions, 'POST', '/people'),
|
||||
).rejects.toThrow(NodeApiError);
|
||||
});
|
||||
|
||||
it('should handle network errors', async () => {
|
||||
const networkError = { code: 'ECONNREFUSED', message: 'Connection refused' };
|
||||
(mockExecuteFunctions.helpers.requestOAuth2 as jest.Mock).mockRejectedValue(networkError);
|
||||
|
||||
await expect(
|
||||
GenericFunctions.googleApiRequest.call(mockExecuteFunctions, 'GET', '/people'),
|
||||
).rejects.toThrow(NodeApiError);
|
||||
});
|
||||
|
||||
it('should preserve query string parameters', async () => {
|
||||
const mockResponse = { data: 'filtered results' };
|
||||
const queryParams = { personFields: 'names,emailAddresses', pageSize: '50' };
|
||||
(mockExecuteFunctions.helpers.requestOAuth2 as jest.Mock).mockResolvedValue(mockResponse);
|
||||
|
||||
await GenericFunctions.googleApiRequest.call(
|
||||
mockExecuteFunctions,
|
||||
'GET',
|
||||
'/people/connections',
|
||||
{},
|
||||
queryParams,
|
||||
);
|
||||
|
||||
expect(mockExecuteFunctions.helpers.requestOAuth2).toHaveBeenCalledWith(
|
||||
'googleContactsOAuth2Api',
|
||||
expect.objectContaining({
|
||||
qs: queryParams,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('googleApiRequestAllItems', () => {
|
||||
it('should fetch all items with pagination', async () => {
|
||||
// Mock the requestOAuth2 helper to simulate pagination responses
|
||||
let callCount = 0;
|
||||
(mockExecuteFunctions.helpers.requestOAuth2 as jest.Mock).mockImplementation(
|
||||
async (_, options) => {
|
||||
callCount++;
|
||||
if (callCount === 1) {
|
||||
// First call should not have pageToken
|
||||
expect(options.qs.pageToken).toBeUndefined();
|
||||
return {
|
||||
connections: [
|
||||
{ resourceName: 'people/1', names: [{ displayName: 'John' }] },
|
||||
{ resourceName: 'people/2', names: [{ displayName: 'Jane' }] },
|
||||
],
|
||||
nextPageToken: 'token123',
|
||||
};
|
||||
} else {
|
||||
// Second call should have pageToken from first response
|
||||
expect(options.qs.pageToken).toBe('token123');
|
||||
return {
|
||||
connections: [{ resourceName: 'people/3', names: [{ displayName: 'Bob' }] }],
|
||||
nextPageToken: '',
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const result = await GenericFunctions.googleApiRequestAllItems.call(
|
||||
mockExecuteFunctions,
|
||||
'connections',
|
||||
'GET',
|
||||
'/people/connections',
|
||||
{},
|
||||
{ personFields: 'names' },
|
||||
);
|
||||
|
||||
expect(result).toHaveLength(3);
|
||||
expect(result[0].resourceName).toBe('people/1');
|
||||
expect(result[1].resourceName).toBe('people/2');
|
||||
expect(result[2].resourceName).toBe('people/3');
|
||||
|
||||
expect(mockExecuteFunctions.helpers.requestOAuth2).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should handle single page response with no pagination', async () => {
|
||||
const singlePageResponse = {
|
||||
connections: [{ resourceName: 'people/1', names: [{ displayName: 'Only Contact' }] }],
|
||||
};
|
||||
|
||||
(mockExecuteFunctions.helpers.requestOAuth2 as jest.Mock).mockResolvedValue(
|
||||
singlePageResponse,
|
||||
);
|
||||
|
||||
const result = await GenericFunctions.googleApiRequestAllItems.call(
|
||||
mockExecuteFunctions,
|
||||
'connections',
|
||||
'GET',
|
||||
'/people/connections',
|
||||
);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].resourceName).toBe('people/1');
|
||||
expect(mockExecuteFunctions.helpers.requestOAuth2).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle empty response', async () => {
|
||||
const emptyResponse = {
|
||||
connections: [],
|
||||
};
|
||||
|
||||
(mockExecuteFunctions.helpers.requestOAuth2 as jest.Mock).mockResolvedValue(emptyResponse);
|
||||
|
||||
const result = await GenericFunctions.googleApiRequestAllItems.call(
|
||||
mockExecuteFunctions,
|
||||
'connections',
|
||||
'GET',
|
||||
'/people/connections',
|
||||
);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
expect(mockExecuteFunctions.helpers.requestOAuth2).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should work with ILoadOptionsFunctions context', async () => {
|
||||
const mockResponse = {
|
||||
items: [
|
||||
{ id: '1', name: 'Item 1' },
|
||||
{ id: '2', name: 'Item 2' },
|
||||
],
|
||||
};
|
||||
|
||||
(mockLoadOptionsFunctions.helpers.requestOAuth2 as jest.Mock).mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await GenericFunctions.googleApiRequestAllItems.call(
|
||||
mockLoadOptionsFunctions,
|
||||
'items',
|
||||
'GET',
|
||||
'/custom/endpoint',
|
||||
{ filter: 'active' },
|
||||
{ sortBy: 'name' },
|
||||
);
|
||||
|
||||
expect(result).toHaveLength(2);
|
||||
expect(mockLoadOptionsFunctions.helpers.requestOAuth2).toHaveBeenCalledWith(
|
||||
'googleContactsOAuth2Api',
|
||||
expect.objectContaining({
|
||||
body: { filter: 'active' },
|
||||
qs: expect.objectContaining({
|
||||
sortBy: 'name',
|
||||
pageSize: 100,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle pagination with undefined nextPageToken', async () => {
|
||||
const responseWithUndefinedToken = {
|
||||
connections: [{ resourceName: 'people/1', names: [{ displayName: 'Contact' }] }],
|
||||
nextPageToken: undefined,
|
||||
};
|
||||
|
||||
(mockExecuteFunctions.helpers.requestOAuth2 as jest.Mock).mockResolvedValue(
|
||||
responseWithUndefinedToken,
|
||||
);
|
||||
|
||||
const result = await GenericFunctions.googleApiRequestAllItems.call(
|
||||
mockExecuteFunctions,
|
||||
'connections',
|
||||
'GET',
|
||||
'/people/connections',
|
||||
);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(mockExecuteFunctions.helpers.requestOAuth2).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle large datasets with multiple pages', async () => {
|
||||
const createResponse = (pageNum: number, hasNext: boolean) => ({
|
||||
connections: Array.from({ length: 10 }, (_, i) => ({
|
||||
resourceName: `people/${pageNum * 10 + i + 1}`,
|
||||
names: [{ displayName: `Contact ${pageNum * 10 + i + 1}` }],
|
||||
})),
|
||||
nextPageToken: hasNext ? `token${pageNum + 1}` : '',
|
||||
});
|
||||
|
||||
(mockExecuteFunctions.helpers.requestOAuth2 as jest.Mock)
|
||||
.mockResolvedValueOnce(createResponse(0, true))
|
||||
.mockResolvedValueOnce(createResponse(1, true))
|
||||
.mockResolvedValueOnce(createResponse(2, false));
|
||||
|
||||
const result = await GenericFunctions.googleApiRequestAllItems.call(
|
||||
mockExecuteFunctions,
|
||||
'connections',
|
||||
'GET',
|
||||
'/people/connections',
|
||||
);
|
||||
|
||||
expect(result).toHaveLength(30);
|
||||
expect(mockExecuteFunctions.helpers.requestOAuth2).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('should propagate errors from underlying API requests', async () => {
|
||||
const apiError = new Error('Rate limit exceeded');
|
||||
(mockExecuteFunctions.helpers.requestOAuth2 as jest.Mock).mockRejectedValue(apiError);
|
||||
|
||||
await expect(
|
||||
GenericFunctions.googleApiRequestAllItems.call(
|
||||
mockExecuteFunctions,
|
||||
'connections',
|
||||
'GET',
|
||||
'/people/connections',
|
||||
),
|
||||
).rejects.toThrow('Rate limit exceeded');
|
||||
});
|
||||
});
|
||||
|
||||
describe('allFields', () => {
|
||||
it('should contain all expected field names', () => {
|
||||
const expectedFields = [
|
||||
'addresses',
|
||||
'biographies',
|
||||
'birthdays',
|
||||
'coverPhotos',
|
||||
'emailAddresses',
|
||||
'events',
|
||||
'genders',
|
||||
'imClients',
|
||||
'interests',
|
||||
'locales',
|
||||
'memberships',
|
||||
'metadata',
|
||||
'names',
|
||||
'nicknames',
|
||||
'occupations',
|
||||
'organizations',
|
||||
'phoneNumbers',
|
||||
'photos',
|
||||
'relations',
|
||||
'residences',
|
||||
'sipAddresses',
|
||||
'skills',
|
||||
'urls',
|
||||
'userDefined',
|
||||
];
|
||||
|
||||
expect(GenericFunctions.allFields).toEqual(expectedFields);
|
||||
});
|
||||
|
||||
it('should be a read-only array', () => {
|
||||
expect(Array.isArray(GenericFunctions.allFields)).toBe(true);
|
||||
expect(GenericFunctions.allFields).toHaveLength(24);
|
||||
});
|
||||
|
||||
it('should contain unique field names', () => {
|
||||
const uniqueFields = [...new Set(GenericFunctions.allFields)];
|
||||
expect(uniqueFields).toHaveLength(GenericFunctions.allFields.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe('cleanData', () => {
|
||||
it('should clean single contact data correctly', () => {
|
||||
const rawData = {
|
||||
resourceName: 'people/123',
|
||||
metadata: {
|
||||
sources: [{ type: 'CONTACT', id: '123' }],
|
||||
},
|
||||
names: [
|
||||
{
|
||||
metadata: { primary: true, verified: true },
|
||||
displayName: 'John Doe',
|
||||
familyName: 'Doe',
|
||||
givenName: 'John',
|
||||
},
|
||||
],
|
||||
emailAddresses: [
|
||||
{
|
||||
metadata: { primary: true },
|
||||
value: 'john@example.com',
|
||||
type: 'work',
|
||||
},
|
||||
{
|
||||
metadata: { primary: false },
|
||||
value: 'john.personal@example.com',
|
||||
type: 'home',
|
||||
},
|
||||
],
|
||||
phoneNumbers: [
|
||||
{
|
||||
metadata: { primary: true },
|
||||
value: '+1234567890',
|
||||
type: 'mobile',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = GenericFunctions.cleanData(rawData);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).not.toHaveProperty('metadata');
|
||||
expect(result[0].names).toEqual({
|
||||
displayName: 'John Doe',
|
||||
familyName: 'Doe',
|
||||
givenName: 'John',
|
||||
});
|
||||
expect(result[0].emailAddresses).toEqual({
|
||||
work: ['john@example.com'],
|
||||
home: ['john.personal@example.com'],
|
||||
});
|
||||
expect(result[0].phoneNumbers).toEqual({
|
||||
mobile: ['+1234567890'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle array input', () => {
|
||||
const rawData = [
|
||||
{
|
||||
resourceName: 'people/1',
|
||||
names: [{ displayName: 'Person 1', metadata: { primary: true } }],
|
||||
},
|
||||
{
|
||||
resourceName: 'people/2',
|
||||
names: [{ displayName: 'Person 2', metadata: { primary: true } }],
|
||||
},
|
||||
];
|
||||
|
||||
const result = GenericFunctions.cleanData(rawData);
|
||||
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0].names.displayName).toBe('Person 1');
|
||||
expect(result[1].names.displayName).toBe('Person 2');
|
||||
});
|
||||
|
||||
it('should clean photos field correctly', () => {
|
||||
const rawData = {
|
||||
resourceName: 'people/123',
|
||||
photos: [
|
||||
{ url: 'https://example.com/photo1.jpg', metadata: { primary: true } },
|
||||
{ url: 'https://example.com/photo2.jpg', metadata: { primary: false } },
|
||||
],
|
||||
};
|
||||
|
||||
const result = GenericFunctions.cleanData(rawData);
|
||||
|
||||
expect(result[0].photos).toEqual([
|
||||
'https://example.com/photo1.jpg',
|
||||
'https://example.com/photo2.jpg',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should clean memberships field correctly', () => {
|
||||
const rawData = {
|
||||
resourceName: 'people/123',
|
||||
memberships: [
|
||||
{
|
||||
metadata: {
|
||||
source: { type: 'CONTACT', id: 'group1' },
|
||||
},
|
||||
contactGroupMembership: { contactGroupId: 'group1' },
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
source: { type: 'CONTACT', id: 'group2' },
|
||||
},
|
||||
contactGroupMembership: { contactGroupId: 'group2' },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = GenericFunctions.cleanData(rawData);
|
||||
|
||||
expect(result[0].memberships).toEqual(['group1', 'group2']);
|
||||
});
|
||||
|
||||
it('should clean birthdays field correctly', () => {
|
||||
const rawData = {
|
||||
resourceName: 'people/123',
|
||||
birthdays: [
|
||||
{
|
||||
metadata: { primary: true },
|
||||
date: { year: 1990, month: 6, day: 15 },
|
||||
},
|
||||
{
|
||||
metadata: { primary: false },
|
||||
date: { year: 1985, month: 12, day: 25 },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = GenericFunctions.cleanData(rawData);
|
||||
|
||||
expect(result[0].birthdays).toBe('6/15/1990');
|
||||
});
|
||||
|
||||
it('should clean userDefined, organizations, and biographies fields', () => {
|
||||
const rawData = {
|
||||
resourceName: 'people/123',
|
||||
userDefined: [
|
||||
{
|
||||
metadata: { primary: true },
|
||||
key: 'customField',
|
||||
value: 'customValue',
|
||||
},
|
||||
],
|
||||
organizations: [
|
||||
{
|
||||
metadata: { primary: true },
|
||||
name: 'Company Name',
|
||||
title: 'Job Title',
|
||||
},
|
||||
],
|
||||
biographies: [
|
||||
{
|
||||
metadata: { primary: true },
|
||||
value: 'This is a biography',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = GenericFunctions.cleanData(rawData);
|
||||
|
||||
expect(result[0].userDefined[0]).not.toHaveProperty('metadata');
|
||||
expect(result[0].organizations[0]).not.toHaveProperty('metadata');
|
||||
expect(result[0].biographies[0]).not.toHaveProperty('metadata');
|
||||
expect(result[0].userDefined[0].key).toBe('customField');
|
||||
expect(result[0].organizations[0].name).toBe('Company Name');
|
||||
expect(result[0].biographies[0].value).toBe('This is a biography');
|
||||
});
|
||||
|
||||
it('should handle relations field correctly', () => {
|
||||
const rawData = {
|
||||
resourceName: 'people/123',
|
||||
relations: [
|
||||
{
|
||||
metadata: { primary: true },
|
||||
person: 'Jane Doe',
|
||||
type: 'spouse',
|
||||
},
|
||||
{
|
||||
metadata: { primary: false },
|
||||
person: 'John Doe Sr.',
|
||||
type: 'father',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = GenericFunctions.cleanData(rawData);
|
||||
|
||||
expect(result[0].relations).toEqual({
|
||||
spouse: ['Jane Doe'],
|
||||
father: ['John Doe Sr.'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle events field correctly', () => {
|
||||
const rawData = {
|
||||
resourceName: 'people/123',
|
||||
events: [
|
||||
{
|
||||
metadata: { primary: true },
|
||||
date: { year: 2020, month: 6, day: 15 },
|
||||
type: 'anniversary',
|
||||
},
|
||||
{
|
||||
metadata: { primary: false },
|
||||
date: { year: 2021, month: 12, day: 25 },
|
||||
type: 'anniversary',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = GenericFunctions.cleanData(rawData);
|
||||
|
||||
expect(result[0].events).toEqual({
|
||||
anniversary: ['6/15/2020', '12/25/2021'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle addresses field correctly', () => {
|
||||
const rawData = {
|
||||
resourceName: 'people/123',
|
||||
addresses: [
|
||||
{
|
||||
metadata: { primary: true },
|
||||
formattedValue: '123 Main St, City, State 12345',
|
||||
type: 'home',
|
||||
streetAddress: '123 Main St',
|
||||
city: 'City',
|
||||
region: 'State',
|
||||
postalCode: '12345',
|
||||
},
|
||||
{
|
||||
metadata: { primary: false },
|
||||
formattedValue: '456 Work Ave, Work City, Work State 67890',
|
||||
type: 'work',
|
||||
streetAddress: '456 Work Ave',
|
||||
city: 'Work City',
|
||||
region: 'Work State',
|
||||
postalCode: '67890',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = GenericFunctions.cleanData(rawData);
|
||||
|
||||
expect(result[0].addresses.home).toHaveLength(1);
|
||||
expect(result[0].addresses.work).toHaveLength(1);
|
||||
expect(result[0].addresses.home[0]).not.toHaveProperty('metadata');
|
||||
expect(result[0].addresses.home[0].formattedValue).toBe('123 Main St, City, State 12345');
|
||||
expect(result[0].addresses.work[0].streetAddress).toBe('456 Work Ave');
|
||||
});
|
||||
|
||||
it('should handle empty data gracefully', () => {
|
||||
const rawData = {
|
||||
resourceName: 'people/123',
|
||||
metadata: { sources: [] },
|
||||
};
|
||||
|
||||
const result = GenericFunctions.cleanData(rawData);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual({ resourceName: 'people/123' });
|
||||
});
|
||||
|
||||
it('should handle missing fields gracefully', () => {
|
||||
const rawData = {
|
||||
resourceName: 'people/123',
|
||||
someOtherField: 'value',
|
||||
};
|
||||
|
||||
const result = GenericFunctions.cleanData(rawData);
|
||||
|
||||
expect(result[0]).toEqual({
|
||||
resourceName: 'people/123',
|
||||
someOtherField: 'value',
|
||||
});
|
||||
});
|
||||
|
||||
it('should skip fields that result in empty objects', () => {
|
||||
const rawData = {
|
||||
resourceName: 'people/123',
|
||||
emailAddresses: [], // Empty array should not create emailAddresses field
|
||||
phoneNumbers: [
|
||||
{
|
||||
metadata: { primary: true },
|
||||
value: '+1234567890',
|
||||
type: 'mobile',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = GenericFunctions.cleanData(rawData);
|
||||
|
||||
expect(result[0]).not.toHaveProperty('emailAddresses');
|
||||
expect(result[0]).toHaveProperty('phoneNumbers');
|
||||
});
|
||||
|
||||
it('should handle complex nested data structures', () => {
|
||||
const rawData = {
|
||||
resourceName: 'people/123',
|
||||
names: [
|
||||
{
|
||||
metadata: { primary: true, verified: true, source: { type: 'CONTACT' } },
|
||||
displayName: 'John Doe',
|
||||
familyName: 'Doe',
|
||||
givenName: 'John',
|
||||
middleName: 'Michael',
|
||||
honorificPrefix: 'Mr.',
|
||||
honorificSuffix: 'Jr.',
|
||||
},
|
||||
],
|
||||
emailAddresses: [
|
||||
{
|
||||
metadata: { primary: true, verified: true },
|
||||
value: 'john@example.com',
|
||||
type: 'work',
|
||||
displayName: 'John Work Email',
|
||||
},
|
||||
],
|
||||
addresses: [
|
||||
{
|
||||
metadata: { primary: true },
|
||||
type: 'home',
|
||||
formattedValue: '123 Main St\nAnytown, ST 12345\nUSA',
|
||||
streetAddress: '123 Main St',
|
||||
city: 'Anytown',
|
||||
region: 'ST',
|
||||
postalCode: '12345',
|
||||
country: 'USA',
|
||||
countryCode: 'US',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = GenericFunctions.cleanData(rawData);
|
||||
|
||||
expect(result[0].names).toEqual({
|
||||
displayName: 'John Doe',
|
||||
familyName: 'Doe',
|
||||
givenName: 'John',
|
||||
middleName: 'Michael',
|
||||
honorificPrefix: 'Mr.',
|
||||
honorificSuffix: 'Jr.',
|
||||
});
|
||||
expect(result[0].emailAddresses.work[0]).toBe('john@example.com');
|
||||
expect(result[0].addresses.home[0]).not.toHaveProperty('metadata');
|
||||
expect(result[0].addresses.home[0].country).toBe('USA');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,437 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import { jsonParse } from 'n8n-workflow';
|
||||
import nock from 'nock';
|
||||
|
||||
describe('Google Contacts', () => {
|
||||
const credentials = {
|
||||
googleContactsOAuth2Api: {
|
||||
scope: 'https://www.googleapis.com/auth/contacts',
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('Contact Create Operation', () => {
|
||||
beforeAll(() => {
|
||||
const mock = nock('https://people.googleapis.com/v1');
|
||||
|
||||
// Mock successful contact creation - handle all variations
|
||||
mock
|
||||
.post('/people:createContact')
|
||||
.reply(function (_, requestBody: any) {
|
||||
const parsedBody = typeof requestBody === 'string' ? jsonParse(requestBody) : requestBody;
|
||||
if (parsedBody.names && parsedBody.names[0].givenName === 'John') {
|
||||
return [
|
||||
201,
|
||||
{
|
||||
resourceName: 'people/c123456789',
|
||||
etag: '%EgYBAgMEBQYHCCESBCAFIAI=.',
|
||||
names: [
|
||||
{
|
||||
metadata: {
|
||||
primary: true,
|
||||
source: { type: 'CONTACT', id: 'c123456789' },
|
||||
},
|
||||
displayName: 'John Doe',
|
||||
familyName: 'Doe',
|
||||
givenName: 'John',
|
||||
displayNameLastFirst: 'Doe, John',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
// For any other create request (Jane or any other contact)
|
||||
return [
|
||||
201,
|
||||
{
|
||||
resourceName: 'people/c987654321',
|
||||
etag: '%EgYBAgMEBQYHCCESBCAFIAI=.',
|
||||
names: [
|
||||
{
|
||||
metadata: {
|
||||
primary: true,
|
||||
source: { type: 'CONTACT', id: 'c987654321' },
|
||||
},
|
||||
displayName: 'Dr. Jane Marie Smith Jr.',
|
||||
familyName: 'Smith',
|
||||
givenName: 'Jane',
|
||||
middleName: 'Marie',
|
||||
honorificPrefix: 'Dr.',
|
||||
honorificSuffix: 'Jr.',
|
||||
},
|
||||
],
|
||||
emailAddresses: [
|
||||
{
|
||||
metadata: { primary: true, source: { type: 'CONTACT', id: 'c987654321' } },
|
||||
value: 'jane@example.com',
|
||||
type: 'work',
|
||||
},
|
||||
],
|
||||
phoneNumbers: [
|
||||
{
|
||||
metadata: { primary: true, source: { type: 'CONTACT', id: 'c987654321' } },
|
||||
value: '+1234567890',
|
||||
type: 'mobile',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
})
|
||||
.persist();
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['contact-create-basic.workflow.json', 'contact-create-full.workflow.json'],
|
||||
});
|
||||
});
|
||||
|
||||
describe('Contact Delete Operation', () => {
|
||||
beforeAll(() => {
|
||||
const mock = nock('https://people.googleapis.com/v1');
|
||||
|
||||
// Mock successful contact deletion
|
||||
mock.delete('/people/c123456789:deleteContact').reply(200, {});
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['contact-delete.workflow.json'],
|
||||
});
|
||||
});
|
||||
|
||||
describe('Contact Get Operation', () => {
|
||||
beforeAll(() => {
|
||||
const mock = nock('https://people.googleapis.com/v1');
|
||||
|
||||
// Mock get contact with specific fields
|
||||
mock
|
||||
.get('/people/c123456789')
|
||||
.query({
|
||||
personFields: 'names,emailAddresses,phoneNumbers',
|
||||
})
|
||||
.reply(200, {
|
||||
resourceName: 'people/c123456789',
|
||||
etag: '%EgYBAgMEBQYHCCESBCAFIAI=.',
|
||||
names: [
|
||||
{
|
||||
metadata: {
|
||||
primary: true,
|
||||
source: { type: 'CONTACT', id: 'c123456789' },
|
||||
},
|
||||
displayName: 'John Doe',
|
||||
familyName: 'Doe',
|
||||
givenName: 'John',
|
||||
},
|
||||
],
|
||||
emailAddresses: [
|
||||
{
|
||||
metadata: { primary: true, source: { type: 'CONTACT', id: 'c123456789' } },
|
||||
value: 'john@example.com',
|
||||
type: 'work',
|
||||
},
|
||||
],
|
||||
phoneNumbers: [
|
||||
{
|
||||
metadata: { primary: true, source: { type: 'CONTACT', id: 'c123456789' } },
|
||||
value: '+1234567890',
|
||||
type: 'mobile',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Mock get contact with all fields
|
||||
mock
|
||||
.get('/people/c123456789')
|
||||
.query({
|
||||
personFields:
|
||||
'addresses,biographies,birthdays,coverPhotos,emailAddresses,events,genders,imClients,interests,locales,memberships,metadata,names,nicknames,occupations,organizations,phoneNumbers,photos,relations,residences,sipAddresses,skills,urls,userDefined',
|
||||
})
|
||||
.reply(200, {
|
||||
resourceName: 'people/c123456789',
|
||||
etag: '%EgYBAgMEBQYHCCESBCAFIAI=.',
|
||||
names: [
|
||||
{
|
||||
metadata: {
|
||||
primary: true,
|
||||
source: { type: 'CONTACT', id: 'c123456789' },
|
||||
},
|
||||
displayName: 'John Doe',
|
||||
familyName: 'Doe',
|
||||
givenName: 'John',
|
||||
},
|
||||
],
|
||||
emailAddresses: [
|
||||
{
|
||||
metadata: { primary: true, source: { type: 'CONTACT', id: 'c123456789' } },
|
||||
value: 'john@example.com',
|
||||
type: 'work',
|
||||
},
|
||||
],
|
||||
phoneNumbers: [
|
||||
{
|
||||
metadata: { primary: true, source: { type: 'CONTACT', id: 'c123456789' } },
|
||||
value: '+1234567890',
|
||||
type: 'mobile',
|
||||
},
|
||||
],
|
||||
addresses: [
|
||||
{
|
||||
metadata: { primary: true, source: { type: 'CONTACT', id: 'c123456789' } },
|
||||
type: 'home',
|
||||
formattedValue: '123 Main St\nAnytown, CA 12345',
|
||||
streetAddress: '123 Main St',
|
||||
city: 'Anytown',
|
||||
region: 'CA',
|
||||
postalCode: '12345',
|
||||
},
|
||||
],
|
||||
organizations: [
|
||||
{
|
||||
metadata: { primary: true, source: { type: 'CONTACT', id: 'c123456789' } },
|
||||
name: 'Example Corp',
|
||||
title: 'Software Engineer',
|
||||
current: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['contact-get-fields.workflow.json', 'contact-get-all.workflow.json'],
|
||||
});
|
||||
});
|
||||
|
||||
describe('Contact Get All Operation', () => {
|
||||
beforeAll(() => {
|
||||
const mock = nock('https://people.googleapis.com/v1');
|
||||
|
||||
// Mock cache warmup requests with specific empty query
|
||||
mock
|
||||
.get('/people:searchContacts')
|
||||
.query((actualQuery) => actualQuery.query === '')
|
||||
.reply(200, {
|
||||
results: [],
|
||||
})
|
||||
.persist();
|
||||
|
||||
mock
|
||||
.get('/people/me/connections')
|
||||
.query(true) // Match any query parameters
|
||||
.reply(function (uri) {
|
||||
const url = new URL(uri, 'https://people.googleapis.com');
|
||||
const params = url.searchParams;
|
||||
|
||||
// Handle different scenarios based on query params
|
||||
if (params.get('pageSize') === '1') {
|
||||
// Limited results case
|
||||
return [
|
||||
200,
|
||||
{
|
||||
connections: [
|
||||
{
|
||||
resourceName: 'people/c123456789',
|
||||
etag: '%EgYBAgMEBQYHCCESBCAFIAI=.',
|
||||
names: [
|
||||
{
|
||||
metadata: {
|
||||
primary: true,
|
||||
source: { type: 'CONTACT', id: 'c123456789' },
|
||||
},
|
||||
displayName: 'John Doe',
|
||||
familyName: 'Doe',
|
||||
givenName: 'John',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
nextPageToken: '',
|
||||
},
|
||||
];
|
||||
} else if (params.get('personFields') === 'names,emailAddresses') {
|
||||
// Full results case with email addresses
|
||||
return [
|
||||
200,
|
||||
{
|
||||
connections: [
|
||||
{
|
||||
resourceName: 'people/c123456789',
|
||||
etag: '%EgYBAgMEBQYHCCESBCAFIAI=.',
|
||||
names: [
|
||||
{
|
||||
metadata: {
|
||||
primary: true,
|
||||
source: { type: 'CONTACT', id: 'c123456789' },
|
||||
},
|
||||
displayName: 'John Doe',
|
||||
familyName: 'Doe',
|
||||
givenName: 'John',
|
||||
},
|
||||
],
|
||||
emailAddresses: [
|
||||
{
|
||||
metadata: { primary: true, source: { type: 'CONTACT', id: 'c123456789' } },
|
||||
value: 'john@example.com',
|
||||
type: 'work',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
resourceName: 'people/c987654321',
|
||||
etag: '%EgYBAgMEBQYHCCESBCAFIAI=.',
|
||||
names: [
|
||||
{
|
||||
metadata: {
|
||||
primary: true,
|
||||
source: { type: 'CONTACT', id: 'c987654321' },
|
||||
},
|
||||
displayName: 'Jane Smith',
|
||||
familyName: 'Smith',
|
||||
givenName: 'Jane',
|
||||
},
|
||||
],
|
||||
emailAddresses: [
|
||||
{
|
||||
metadata: { primary: true, source: { type: 'CONTACT', id: 'c987654321' } },
|
||||
value: 'jane@example.com',
|
||||
type: 'work',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
nextPageToken: '',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// Default response for cache warmup and other requests
|
||||
return [200, { connections: [] }];
|
||||
})
|
||||
.persist();
|
||||
|
||||
// Mock search contacts using query with function to better handle
|
||||
mock
|
||||
.get('/people:searchContacts')
|
||||
.query(true)
|
||||
.reply(function (uri) {
|
||||
const url = new URL(uri, 'https://people.googleapis.com');
|
||||
const params = url.searchParams;
|
||||
|
||||
// Handle search query specifically
|
||||
if (params.get('query') === 'John' && params.get('readMask')) {
|
||||
return [
|
||||
200,
|
||||
{
|
||||
results: [
|
||||
{
|
||||
person: {
|
||||
resourceName: 'people/c123456789',
|
||||
etag: '%EgYBAgMEBQYHCCESBCAFIAI=.',
|
||||
names: [
|
||||
{
|
||||
metadata: {
|
||||
primary: true,
|
||||
source: { type: 'CONTACT', id: 'c123456789' },
|
||||
},
|
||||
displayName: 'John Doe',
|
||||
familyName: 'Doe',
|
||||
givenName: 'John',
|
||||
},
|
||||
],
|
||||
emailAddresses: [
|
||||
{
|
||||
metadata: {
|
||||
primary: true,
|
||||
source: { type: 'CONTACT', id: 'c123456789' },
|
||||
},
|
||||
value: 'john@example.com',
|
||||
type: 'work',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
nextPageToken: '',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// Default response for cache warmup
|
||||
return [200, { results: [] }];
|
||||
})
|
||||
.persist();
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: [
|
||||
'contact-getall-connections.workflow.json',
|
||||
'contact-getall-search.workflow.json',
|
||||
'contact-getall-limit.workflow.json',
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
describe('Contact Update Operation', () => {
|
||||
beforeAll(() => {
|
||||
const mock = nock('https://people.googleapis.com/v1');
|
||||
|
||||
// Mock etag fetch for update
|
||||
mock
|
||||
.get('/people/c123456789')
|
||||
.query({
|
||||
personFields: 'Names',
|
||||
})
|
||||
.reply(200, {
|
||||
resourceName: 'people/c123456789',
|
||||
etag: '%EgYBAgMEBQYHCCESBCAFIAI=.',
|
||||
names: [
|
||||
{
|
||||
metadata: {
|
||||
primary: true,
|
||||
source: { type: 'CONTACT', id: 'c123456789' },
|
||||
},
|
||||
displayName: 'John Doe',
|
||||
familyName: 'Doe',
|
||||
givenName: 'John',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Mock contact update with more flexible query matching
|
||||
mock
|
||||
.patch('/people/c123456789:updateContact')
|
||||
.query(true) // Match any query parameters
|
||||
.reply(200, {
|
||||
resourceName: 'people/c123456789',
|
||||
etag: '%EgYBAgMEBQYHCCESBCAFIAI=.',
|
||||
names: [
|
||||
{
|
||||
metadata: {
|
||||
primary: true,
|
||||
source: { type: 'CONTACT', id: 'c123456789' },
|
||||
},
|
||||
displayName: 'John Updated Doe',
|
||||
familyName: 'Doe',
|
||||
givenName: 'John Updated',
|
||||
},
|
||||
],
|
||||
emailAddresses: [
|
||||
{
|
||||
metadata: { primary: true, source: { type: 'CONTACT', id: 'c123456789' } },
|
||||
value: 'john.updated@example.com',
|
||||
type: 'work',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['contact-update.workflow.json'],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"name": "Google Contacts Create Basic Test",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [0, 0],
|
||||
"id": "trigger-id",
|
||||
"name": "When clicking 'Execute Workflow'"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "contact",
|
||||
"operation": "create",
|
||||
"familyName": "Doe",
|
||||
"givenName": "John"
|
||||
},
|
||||
"type": "n8n-nodes-base.googleContacts",
|
||||
"typeVersion": 1,
|
||||
"position": [200, 0],
|
||||
"id": "contact-create-basic",
|
||||
"name": "Create Contact Basic",
|
||||
"credentials": {
|
||||
"googleContactsOAuth2Api": {
|
||||
"id": "test-credential-id",
|
||||
"name": "Test Google Contacts OAuth2"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Create Contact Basic": [
|
||||
{
|
||||
"json": {
|
||||
"resourceName": "people/c123456789",
|
||||
"etag": "%EgYBAgMEBQYHCCESBCAFIAI=.",
|
||||
"names": [
|
||||
{
|
||||
"metadata": {
|
||||
"primary": true,
|
||||
"source": { "type": "CONTACT", "id": "c123456789" }
|
||||
},
|
||||
"displayName": "John Doe",
|
||||
"familyName": "Doe",
|
||||
"givenName": "John",
|
||||
"displayNameLastFirst": "Doe, John"
|
||||
}
|
||||
],
|
||||
"contactId": "c123456789"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking 'Execute Workflow'": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Create Contact Basic",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
{
|
||||
"name": "Google Contacts Create Full Test",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [0, 0],
|
||||
"id": "trigger-id",
|
||||
"name": "When clicking 'Execute Workflow'"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "contact",
|
||||
"operation": "create",
|
||||
"familyName": "Smith",
|
||||
"givenName": "Jane",
|
||||
"additionalFields": {
|
||||
"middleName": "Marie",
|
||||
"honorificPrefix": "Dr.",
|
||||
"honorificSuffix": "Jr.",
|
||||
"emailsUi": {
|
||||
"emailsValues": [
|
||||
{
|
||||
"value": "jane@example.com",
|
||||
"type": "work"
|
||||
}
|
||||
]
|
||||
},
|
||||
"phoneUi": {
|
||||
"phoneValues": [
|
||||
{
|
||||
"value": "+1234567890",
|
||||
"type": "mobile"
|
||||
}
|
||||
]
|
||||
},
|
||||
"addressesUi": {
|
||||
"addressesValues": [
|
||||
{
|
||||
"streetAddress": "123 Main St",
|
||||
"city": "Anytown",
|
||||
"region": "CA",
|
||||
"postalCode": "12345",
|
||||
"countryCode": "US",
|
||||
"type": "home"
|
||||
}
|
||||
]
|
||||
},
|
||||
"companyUi": {
|
||||
"companyValues": [
|
||||
{
|
||||
"name": "Test Company",
|
||||
"title": "Software Engineer",
|
||||
"current": true,
|
||||
"domain": "testcompany.com"
|
||||
}
|
||||
]
|
||||
},
|
||||
"birthday": "1990-06-15T00:00:00.000Z",
|
||||
"biographies": "Test biography",
|
||||
"relationsUi": {
|
||||
"relationsValues": [
|
||||
{
|
||||
"person": "John Smith",
|
||||
"type": "spouse"
|
||||
}
|
||||
]
|
||||
},
|
||||
"eventsUi": {
|
||||
"eventsValues": [
|
||||
{
|
||||
"date": "2020-06-15T00:00:00.000Z",
|
||||
"type": "anniversary"
|
||||
}
|
||||
]
|
||||
},
|
||||
"customFieldsUi": {
|
||||
"customFieldsValues": [
|
||||
{
|
||||
"key": "customField",
|
||||
"value": "customValue"
|
||||
}
|
||||
]
|
||||
},
|
||||
"group": ["contactGroups/group123"]
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.googleContacts",
|
||||
"typeVersion": 1,
|
||||
"position": [200, 0],
|
||||
"id": "contact-create-full",
|
||||
"name": "Create Contact Full",
|
||||
"credentials": {
|
||||
"googleContactsOAuth2Api": {
|
||||
"id": "test-credential-id",
|
||||
"name": "Test Google Contacts OAuth2"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Create Contact Full": [
|
||||
{
|
||||
"json": {
|
||||
"resourceName": "people/c987654321",
|
||||
"etag": "%EgYBAgMEBQYHCCESBCAFIAI=.",
|
||||
"names": [
|
||||
{
|
||||
"metadata": {
|
||||
"primary": true,
|
||||
"source": { "type": "CONTACT", "id": "c987654321" }
|
||||
},
|
||||
"displayName": "Dr. Jane Marie Smith Jr.",
|
||||
"familyName": "Smith",
|
||||
"givenName": "Jane",
|
||||
"middleName": "Marie",
|
||||
"honorificPrefix": "Dr.",
|
||||
"honorificSuffix": "Jr."
|
||||
}
|
||||
],
|
||||
"emailAddresses": [
|
||||
{
|
||||
"metadata": { "primary": true, "source": { "type": "CONTACT", "id": "c987654321" } },
|
||||
"value": "jane@example.com",
|
||||
"type": "work"
|
||||
}
|
||||
],
|
||||
"phoneNumbers": [
|
||||
{
|
||||
"metadata": { "primary": true, "source": { "type": "CONTACT", "id": "c987654321" } },
|
||||
"value": "+1234567890",
|
||||
"type": "mobile"
|
||||
}
|
||||
],
|
||||
"contactId": "c987654321"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking 'Execute Workflow'": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Create Contact Full",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "Google Contacts Delete Test",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [0, 0],
|
||||
"id": "trigger-id",
|
||||
"name": "When clicking 'Execute Workflow'"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "contact",
|
||||
"operation": "delete",
|
||||
"contactId": "c123456789"
|
||||
},
|
||||
"type": "n8n-nodes-base.googleContacts",
|
||||
"typeVersion": 1,
|
||||
"position": [200, 0],
|
||||
"id": "contact-delete",
|
||||
"name": "Delete Contact",
|
||||
"credentials": {
|
||||
"googleContactsOAuth2Api": {
|
||||
"id": "test-credential-id",
|
||||
"name": "Test Google Contacts OAuth2"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Delete Contact": [
|
||||
{
|
||||
"json": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking 'Execute Workflow'": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Delete Contact",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"name": "Google Contacts Get All Fields Test",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [0, 0],
|
||||
"id": "trigger-id",
|
||||
"name": "When clicking 'Execute Workflow'"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "contact",
|
||||
"operation": "get",
|
||||
"contactId": "c123456789",
|
||||
"fields": ["*"],
|
||||
"rawData": false
|
||||
},
|
||||
"type": "n8n-nodes-base.googleContacts",
|
||||
"typeVersion": 1,
|
||||
"position": [200, 0],
|
||||
"id": "contact-get-all",
|
||||
"name": "Get Contact All Fields",
|
||||
"credentials": {
|
||||
"googleContactsOAuth2Api": {
|
||||
"id": "test-credential-id",
|
||||
"name": "Test Google Contacts OAuth2"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Get Contact All Fields": [
|
||||
{
|
||||
"json": {
|
||||
"resourceName": "people/c123456789",
|
||||
"etag": "%EgYBAgMEBQYHCCESBCAFIAI=.",
|
||||
"names": {
|
||||
"displayName": "John Doe",
|
||||
"familyName": "Doe",
|
||||
"givenName": "John"
|
||||
},
|
||||
"emailAddresses": {
|
||||
"work": ["john@example.com"]
|
||||
},
|
||||
"phoneNumbers": {
|
||||
"mobile": ["+1234567890"]
|
||||
},
|
||||
"addresses": {
|
||||
"home": [
|
||||
{
|
||||
"formattedValue": "123 Main St\nAnytown, CA 12345",
|
||||
"streetAddress": "123 Main St",
|
||||
"city": "Anytown",
|
||||
"region": "CA",
|
||||
"postalCode": "12345"
|
||||
}
|
||||
]
|
||||
},
|
||||
"organizations": [
|
||||
{
|
||||
"name": "Example Corp",
|
||||
"title": "Software Engineer",
|
||||
"current": true
|
||||
}
|
||||
],
|
||||
"contactId": "c123456789"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking 'Execute Workflow'": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get Contact All Fields",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"name": "Google Contacts Get Fields Test",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [0, 0],
|
||||
"id": "trigger-id",
|
||||
"name": "When clicking 'Execute Workflow'"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "contact",
|
||||
"operation": "get",
|
||||
"contactId": "c123456789",
|
||||
"fields": ["names", "emailAddresses", "phoneNumbers"],
|
||||
"rawData": false
|
||||
},
|
||||
"type": "n8n-nodes-base.googleContacts",
|
||||
"typeVersion": 1,
|
||||
"position": [200, 0],
|
||||
"id": "contact-get-fields",
|
||||
"name": "Get Contact Fields",
|
||||
"credentials": {
|
||||
"googleContactsOAuth2Api": {
|
||||
"id": "test-credential-id",
|
||||
"name": "Test Google Contacts OAuth2"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Get Contact Fields": [
|
||||
{
|
||||
"json": {
|
||||
"resourceName": "people/c123456789",
|
||||
"etag": "%EgYBAgMEBQYHCCESBCAFIAI=.",
|
||||
"names": {
|
||||
"displayName": "John Doe",
|
||||
"familyName": "Doe",
|
||||
"givenName": "John"
|
||||
},
|
||||
"emailAddresses": {
|
||||
"work": ["john@example.com"]
|
||||
},
|
||||
"phoneNumbers": {
|
||||
"mobile": ["+1234567890"]
|
||||
},
|
||||
"contactId": "c123456789"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking 'Execute Workflow'": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get Contact Fields",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"name": "Google Contacts Get All Connections Test",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [0, 0],
|
||||
"id": "trigger-id",
|
||||
"name": "When clicking 'Execute Workflow'"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "contact",
|
||||
"operation": "getAll",
|
||||
"returnAll": true,
|
||||
"fields": ["names", "emailAddresses"],
|
||||
"useQuery": false,
|
||||
"rawData": false
|
||||
},
|
||||
"type": "n8n-nodes-base.googleContacts",
|
||||
"typeVersion": 1,
|
||||
"position": [200, 0],
|
||||
"id": "contact-getall-connections",
|
||||
"name": "Get All Contacts Connections",
|
||||
"credentials": {
|
||||
"googleContactsOAuth2Api": {
|
||||
"id": "test-credential-id",
|
||||
"name": "Test Google Contacts OAuth2"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Get All Contacts Connections": [
|
||||
{
|
||||
"json": {
|
||||
"resourceName": "people/c123456789",
|
||||
"etag": "%EgYBAgMEBQYHCCESBCAFIAI=.",
|
||||
"names": {
|
||||
"displayName": "John Doe",
|
||||
"familyName": "Doe",
|
||||
"givenName": "John"
|
||||
},
|
||||
"emailAddresses": {
|
||||
"work": ["john@example.com"]
|
||||
},
|
||||
"contactId": "c123456789"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"resourceName": "people/c987654321",
|
||||
"etag": "%EgYBAgMEBQYHCCESBCAFIAI=.",
|
||||
"names": {
|
||||
"displayName": "Jane Smith",
|
||||
"familyName": "Smith",
|
||||
"givenName": "Jane"
|
||||
},
|
||||
"emailAddresses": {
|
||||
"work": ["jane@example.com"]
|
||||
},
|
||||
"contactId": "c987654321"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking 'Execute Workflow'": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get All Contacts Connections",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"name": "Google Contacts Get All Limit Test",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [0, 0],
|
||||
"id": "trigger-id",
|
||||
"name": "When clicking 'Execute Workflow'"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "contact",
|
||||
"operation": "getAll",
|
||||
"returnAll": false,
|
||||
"limit": 1,
|
||||
"fields": ["names"],
|
||||
"useQuery": false,
|
||||
"rawData": false
|
||||
},
|
||||
"type": "n8n-nodes-base.googleContacts",
|
||||
"typeVersion": 1,
|
||||
"position": [200, 0],
|
||||
"id": "contact-getall-limit",
|
||||
"name": "Get All Contacts Limit",
|
||||
"credentials": {
|
||||
"googleContactsOAuth2Api": {
|
||||
"id": "test-credential-id",
|
||||
"name": "Test Google Contacts OAuth2"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Get All Contacts Limit": [
|
||||
{
|
||||
"json": {
|
||||
"resourceName": "people/c123456789",
|
||||
"etag": "%EgYBAgMEBQYHCCESBCAFIAI=.",
|
||||
"names": {
|
||||
"displayName": "John Doe",
|
||||
"familyName": "Doe",
|
||||
"givenName": "John"
|
||||
},
|
||||
"contactId": "c123456789"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking 'Execute Workflow'": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get All Contacts Limit",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"name": "Google Contacts Get All Search Test",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [0, 0],
|
||||
"id": "trigger-id",
|
||||
"name": "When clicking 'Execute Workflow'"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "contact",
|
||||
"operation": "getAll",
|
||||
"returnAll": true,
|
||||
"fields": ["names", "emailAddresses"],
|
||||
"useQuery": true,
|
||||
"query": "John",
|
||||
"rawData": false
|
||||
},
|
||||
"type": "n8n-nodes-base.googleContacts",
|
||||
"typeVersion": 1,
|
||||
"position": [200, 0],
|
||||
"id": "contact-getall-search",
|
||||
"name": "Get All Contacts Search",
|
||||
"credentials": {
|
||||
"googleContactsOAuth2Api": {
|
||||
"id": "test-credential-id",
|
||||
"name": "Test Google Contacts OAuth2"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Get All Contacts Search": [
|
||||
{
|
||||
"json": {
|
||||
"resourceName": "people/c123456789",
|
||||
"etag": "%EgYBAgMEBQYHCCESBCAFIAI=.",
|
||||
"names": {
|
||||
"displayName": "John Doe",
|
||||
"familyName": "Doe",
|
||||
"givenName": "John"
|
||||
},
|
||||
"emailAddresses": {
|
||||
"work": ["john@example.com"]
|
||||
},
|
||||
"contactId": "c123456789"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking 'Execute Workflow'": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get All Contacts Search",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"name": "Google Contacts Update Test",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [0, 0],
|
||||
"id": "trigger-id",
|
||||
"name": "When clicking 'Execute Workflow'"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "contact",
|
||||
"operation": "update",
|
||||
"contactId": "c123456789",
|
||||
"fields": ["names", "emailAddresses"],
|
||||
"updateFields": {
|
||||
"givenName": "John Updated",
|
||||
"emailsUi": {
|
||||
"emailsValues": [
|
||||
{
|
||||
"value": "john.updated@example.com",
|
||||
"type": "work"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.googleContacts",
|
||||
"typeVersion": 1,
|
||||
"position": [200, 0],
|
||||
"id": "contact-update",
|
||||
"name": "Update Contact",
|
||||
"credentials": {
|
||||
"googleContactsOAuth2Api": {
|
||||
"id": "test-credential-id",
|
||||
"name": "Test Google Contacts OAuth2"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Update Contact": [
|
||||
{
|
||||
"json": {
|
||||
"resourceName": "people/c123456789",
|
||||
"etag": "%EgYBAgMEBQYHCCESBCAFIAI=.",
|
||||
"names": [
|
||||
{
|
||||
"metadata": {
|
||||
"primary": true,
|
||||
"source": { "type": "CONTACT", "id": "c123456789" }
|
||||
},
|
||||
"displayName": "John Updated Doe",
|
||||
"familyName": "Doe",
|
||||
"givenName": "John Updated"
|
||||
}
|
||||
],
|
||||
"emailAddresses": [
|
||||
{
|
||||
"metadata": { "primary": true, "source": { "type": "CONTACT", "id": "c123456789" } },
|
||||
"value": "john.updated@example.com",
|
||||
"type": "work"
|
||||
}
|
||||
],
|
||||
"contactId": "c123456789"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking 'Execute Workflow'": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Update Contact",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user