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,34 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import type { WorkflowTestData } from 'n8n-workflow';
|
||||
import nock from 'nock';
|
||||
|
||||
const record = {
|
||||
id: 'rec2BWBoyS5QsS7pT',
|
||||
name: 'Tim',
|
||||
email: 'tim@email.com',
|
||||
createdTime: '2022-08-25T08:22:34.000Z',
|
||||
};
|
||||
|
||||
describe('Execute Airtable Node', () => {
|
||||
const testHarness = new NodeTestHarness();
|
||||
|
||||
beforeEach(() => {
|
||||
nock('https://api.airtable.com/v0')
|
||||
.get('/appIaXXdDqS5ORr4V/tbljyBEdYzCPF0NDh/rec2BWBoyS5QsS7pT')
|
||||
.reply(200, record);
|
||||
});
|
||||
|
||||
const testData: WorkflowTestData = {
|
||||
description: 'List Airtable Records',
|
||||
input: {
|
||||
workflowData: testHarness.readWorkflowJSON('workflow.json'),
|
||||
},
|
||||
output: {
|
||||
nodeData: {
|
||||
Airtable: [[{ json: record }]],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
testHarness.setupTest(testData, { credentials: { airtableTokenApi: {} } });
|
||||
});
|
||||
@@ -0,0 +1,112 @@
|
||||
import * as getMany from '../../../../v2/actions/base/getMany.operation';
|
||||
import * as transport from '../../../../v2/transport';
|
||||
import { createMockExecuteFunction } from '../helpers';
|
||||
|
||||
const bases = [
|
||||
{
|
||||
id: 'appXXX',
|
||||
name: 'base 1',
|
||||
permissionLevel: 'create',
|
||||
},
|
||||
{
|
||||
id: 'appYYY',
|
||||
name: 'base 2',
|
||||
permissionLevel: 'edit',
|
||||
},
|
||||
{
|
||||
id: 'appZZZ',
|
||||
name: 'base 3',
|
||||
permissionLevel: 'create',
|
||||
},
|
||||
];
|
||||
|
||||
jest.mock('../../../../v2/transport', () => {
|
||||
const originalModule = jest.requireActual('../../../../v2/transport');
|
||||
return {
|
||||
...originalModule,
|
||||
apiRequest: jest.fn(async function () {
|
||||
return { bases };
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe('Test AirtableV2, base => getMany', () => {
|
||||
it('should return all bases', async () => {
|
||||
const nodeParameters = {
|
||||
resource: 'base',
|
||||
returnAll: true,
|
||||
options: {},
|
||||
};
|
||||
|
||||
const response = await getMany.execute.call(createMockExecuteFunction(nodeParameters));
|
||||
|
||||
expect(transport.apiRequest).toHaveBeenCalledWith('GET', 'meta/bases');
|
||||
|
||||
expect(response).toEqual([
|
||||
{
|
||||
json: {
|
||||
id: 'appXXX',
|
||||
name: 'base 1',
|
||||
permissionLevel: 'create',
|
||||
},
|
||||
pairedItem: [
|
||||
{
|
||||
item: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
json: {
|
||||
id: 'appYYY',
|
||||
name: 'base 2',
|
||||
permissionLevel: 'edit',
|
||||
},
|
||||
pairedItem: [
|
||||
{
|
||||
item: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
json: {
|
||||
id: 'appZZZ',
|
||||
name: 'base 3',
|
||||
permissionLevel: 'create',
|
||||
},
|
||||
pairedItem: [
|
||||
{
|
||||
item: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return one base with edit permission', async () => {
|
||||
const nodeParameters = {
|
||||
resource: 'base',
|
||||
returnAll: false,
|
||||
limit: 2,
|
||||
options: { permissionLevel: ['edit'] },
|
||||
};
|
||||
|
||||
const response = await getMany.execute.call(createMockExecuteFunction(nodeParameters));
|
||||
|
||||
expect(transport.apiRequest).toHaveBeenCalledWith('GET', 'meta/bases');
|
||||
|
||||
expect(response).toEqual([
|
||||
{
|
||||
json: {
|
||||
id: 'appYYY',
|
||||
name: 'base 2',
|
||||
permissionLevel: 'edit',
|
||||
},
|
||||
pairedItem: [
|
||||
{
|
||||
item: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
import { mockDeep } from 'jest-mock-extended';
|
||||
import get from 'lodash/get';
|
||||
import type { IExecuteFunctions } from 'n8n-workflow';
|
||||
|
||||
import * as getSchema from '../../../../v2/actions/base/getSchema.operation';
|
||||
import * as transport from '../../../../v2/transport';
|
||||
|
||||
jest.mock('../../../../v2/transport', () => {
|
||||
const originalModule = jest.requireActual('../../../../v2/transport');
|
||||
return {
|
||||
...originalModule,
|
||||
apiRequest: jest.fn(async function () {
|
||||
return { tables: [] };
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe('Test AirtableV2, base => getSchema', () => {
|
||||
it('should return all bases', async () => {
|
||||
const nodeParameters = {
|
||||
resource: 'base',
|
||||
operation: 'getSchema',
|
||||
base: {
|
||||
value: '={{$json.id}}',
|
||||
},
|
||||
};
|
||||
|
||||
const items = [
|
||||
{
|
||||
json: { id: 'appYobase1' },
|
||||
},
|
||||
{
|
||||
json: { id: 'appYobase2' },
|
||||
},
|
||||
];
|
||||
|
||||
await getSchema.execute.call(
|
||||
mockDeep<IExecuteFunctions>({
|
||||
getInputData: jest.fn(() => items),
|
||||
getNodeParameter: jest.fn((param: string, itemIndex: number) => {
|
||||
if (param === 'base') {
|
||||
return items[itemIndex].json.id;
|
||||
}
|
||||
|
||||
return get(nodeParameters, param);
|
||||
}),
|
||||
}),
|
||||
items,
|
||||
);
|
||||
|
||||
expect(transport.apiRequest).toBeCalledTimes(2);
|
||||
expect(transport.apiRequest).toHaveBeenCalledWith('GET', 'meta/bases/appYobase1/tables');
|
||||
expect(transport.apiRequest).toHaveBeenCalledWith('GET', 'meta/bases/appYobase2/tables');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import get from 'lodash/get';
|
||||
import { constructExecutionMetaData } from 'n8n-core';
|
||||
import type { IDataObject, IExecuteFunctions, IGetNodeParameterOptions, INode } from 'n8n-workflow';
|
||||
|
||||
export const node: INode = {
|
||||
id: '11',
|
||||
name: 'Airtable node',
|
||||
typeVersion: 2,
|
||||
type: 'n8n-nodes-base.airtable',
|
||||
position: [42, 42],
|
||||
parameters: {
|
||||
operation: 'create',
|
||||
},
|
||||
};
|
||||
|
||||
export const createMockExecuteFunction = (nodeParameters: IDataObject) => {
|
||||
const fakeExecuteFunction = {
|
||||
getInputData: jest.fn(() => {
|
||||
return [{ json: {} }];
|
||||
}),
|
||||
getNodeParameter: jest.fn(
|
||||
(
|
||||
parameterName: string,
|
||||
_itemIndex: number,
|
||||
fallbackValue?: IDataObject,
|
||||
options?: IGetNodeParameterOptions,
|
||||
) => {
|
||||
const parameter = options?.extractValue ? `${parameterName}.value` : parameterName;
|
||||
return get(nodeParameters, parameter, fallbackValue);
|
||||
},
|
||||
),
|
||||
getNode: jest.fn(() => {
|
||||
return node;
|
||||
}),
|
||||
helpers: { constructExecutionMetaData: jest.fn(constructExecutionMetaData) },
|
||||
continueOnFail: jest.fn(() => false),
|
||||
} as unknown as IExecuteFunctions;
|
||||
return fakeExecuteFunction;
|
||||
};
|
||||
@@ -0,0 +1,208 @@
|
||||
import * as create from '../../../../v2/actions/record/create.operation';
|
||||
import * as transport from '../../../../v2/transport';
|
||||
import { createMockExecuteFunction } from '../helpers';
|
||||
|
||||
jest.mock('../../../../v2/transport', () => {
|
||||
const originalModule = jest.requireActual('../../../../v2/transport');
|
||||
return {
|
||||
...originalModule,
|
||||
apiRequest: jest.fn(async function () {
|
||||
return {};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe('Test AirtableV2, create operation', () => {
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
it('should create a record, autoMapInputData', async () => {
|
||||
const nodeParameters = {
|
||||
operation: 'create',
|
||||
columns: {
|
||||
mappingMode: 'autoMapInputData',
|
||||
value: {
|
||||
bar: 'bar 1',
|
||||
foo: 'foo 1',
|
||||
spam: 'eggs',
|
||||
},
|
||||
matchingColumns: [],
|
||||
schema: [
|
||||
{
|
||||
id: 'foo',
|
||||
displayName: 'foo',
|
||||
required: false,
|
||||
defaultMatch: false,
|
||||
display: true,
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
id: 'bar',
|
||||
displayName: 'bar',
|
||||
required: false,
|
||||
defaultMatch: false,
|
||||
display: true,
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
id: 'spam',
|
||||
displayName: 'spam',
|
||||
required: false,
|
||||
defaultMatch: false,
|
||||
display: true,
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
typecast: true,
|
||||
ignoreFields: 'spam',
|
||||
},
|
||||
};
|
||||
|
||||
const items = [
|
||||
{
|
||||
json: {
|
||||
foo: 'foo 1',
|
||||
spam: 'eggs',
|
||||
bar: 'bar 1',
|
||||
},
|
||||
},
|
||||
{
|
||||
json: {
|
||||
foo: 'foo 2',
|
||||
spam: 'eggs',
|
||||
bar: 'bar 2',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
await create.execute.call(
|
||||
createMockExecuteFunction(nodeParameters),
|
||||
items,
|
||||
'appYoLbase',
|
||||
'tblltable',
|
||||
);
|
||||
|
||||
expect(transport.apiRequest).toHaveBeenCalledTimes(2);
|
||||
expect(transport.apiRequest).toHaveBeenCalledWith('POST', 'appYoLbase/tblltable', {
|
||||
fields: {
|
||||
foo: 'foo 1',
|
||||
bar: 'bar 1',
|
||||
},
|
||||
typecast: true,
|
||||
});
|
||||
expect(transport.apiRequest).toHaveBeenCalledWith('POST', 'appYoLbase/tblltable', {
|
||||
fields: {
|
||||
foo: 'foo 2',
|
||||
bar: 'bar 2',
|
||||
},
|
||||
typecast: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a record, defineBelow', async () => {
|
||||
const nodeParameters = {
|
||||
operation: 'create',
|
||||
columns: {
|
||||
mappingMode: 'defineBelow',
|
||||
value: {
|
||||
bar: 'bar 1',
|
||||
foo: 'foo 1',
|
||||
},
|
||||
matchingColumns: [],
|
||||
schema: [
|
||||
{
|
||||
id: 'foo',
|
||||
displayName: 'foo',
|
||||
required: false,
|
||||
defaultMatch: false,
|
||||
display: true,
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
id: 'bar',
|
||||
displayName: 'bar',
|
||||
required: false,
|
||||
defaultMatch: false,
|
||||
display: true,
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {},
|
||||
};
|
||||
|
||||
const items = [
|
||||
{
|
||||
json: {},
|
||||
},
|
||||
];
|
||||
|
||||
await create.execute.call(
|
||||
createMockExecuteFunction(nodeParameters),
|
||||
items,
|
||||
'appYoLbase',
|
||||
'tblltable',
|
||||
);
|
||||
|
||||
expect(transport.apiRequest).toHaveBeenCalledTimes(1);
|
||||
expect(transport.apiRequest).toHaveBeenCalledWith('POST', 'appYoLbase/tblltable', {
|
||||
fields: {
|
||||
foo: 'foo 1',
|
||||
bar: 'bar 1',
|
||||
},
|
||||
typecast: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should skip validation if typecast option is true', async () => {
|
||||
const nodeParameters = {
|
||||
operation: 'create',
|
||||
columns: {
|
||||
mappingMode: 'defineBelow',
|
||||
value: {
|
||||
bar: 'bar 1',
|
||||
foo: 'foo 1',
|
||||
},
|
||||
matchingColumns: [],
|
||||
schema: [
|
||||
{
|
||||
id: 'foo',
|
||||
displayName: 'foo',
|
||||
required: false,
|
||||
defaultMatch: false,
|
||||
display: true,
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
id: 'bar',
|
||||
displayName: 'bar',
|
||||
required: false,
|
||||
defaultMatch: false,
|
||||
display: true,
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
typecast: true,
|
||||
},
|
||||
};
|
||||
|
||||
const mockExecuteFunctions = createMockExecuteFunction(nodeParameters);
|
||||
await create.execute.call(mockExecuteFunctions, [{ json: {} }], 'appYoLbase', 'tblltable');
|
||||
|
||||
expect(mockExecuteFunctions.getNodeParameter).toHaveBeenCalledWith('columns.value', 0, [], {
|
||||
skipValidation: true,
|
||||
});
|
||||
|
||||
expect(transport.apiRequest).toHaveBeenCalledTimes(1);
|
||||
expect(transport.apiRequest).toHaveBeenCalledWith('POST', 'appYoLbase/tblltable', {
|
||||
fields: {
|
||||
foo: 'foo 1',
|
||||
bar: 'bar 1',
|
||||
},
|
||||
typecast: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import * as deleteRecord from '../../../../v2/actions/record/deleteRecord.operation';
|
||||
import * as transport from '../../../../v2/transport';
|
||||
import { createMockExecuteFunction } from '../helpers';
|
||||
|
||||
jest.mock('../../../../v2/transport', () => {
|
||||
const originalModule = jest.requireActual('../../../../v2/transport');
|
||||
return {
|
||||
...originalModule,
|
||||
apiRequest: jest.fn(async function () {
|
||||
return {};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe('Test AirtableV2, deleteRecord operation', () => {
|
||||
it('should delete a record', async () => {
|
||||
const nodeParameters = {
|
||||
operation: 'deleteRecord',
|
||||
id: 'recXXX',
|
||||
};
|
||||
|
||||
const items = [
|
||||
{
|
||||
json: {},
|
||||
},
|
||||
];
|
||||
|
||||
await deleteRecord.execute.call(
|
||||
createMockExecuteFunction(nodeParameters),
|
||||
items,
|
||||
'appYoLbase',
|
||||
'tblltable',
|
||||
);
|
||||
|
||||
expect(transport.apiRequest).toHaveBeenCalledTimes(1);
|
||||
expect(transport.apiRequest).toHaveBeenCalledWith('DELETE', 'appYoLbase/tblltable/recXXX');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
import * as get from '../../../../v2/actions/record/get.operation';
|
||||
import * as transport from '../../../../v2/transport';
|
||||
import { createMockExecuteFunction } from '../helpers';
|
||||
|
||||
jest.mock('../../../../v2/transport', () => {
|
||||
const originalModule = jest.requireActual('../../../../v2/transport');
|
||||
return {
|
||||
...originalModule,
|
||||
apiRequest: jest.fn(async function (method: string) {
|
||||
if (method === 'GET') {
|
||||
return {
|
||||
id: 'recXXX',
|
||||
fields: {
|
||||
foo: 'foo 1',
|
||||
bar: 'bar 1',
|
||||
},
|
||||
};
|
||||
}
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe('Test AirtableV2, create operation', () => {
|
||||
it('should create a record, autoMapInputData', async () => {
|
||||
const nodeParameters = {
|
||||
operation: 'get',
|
||||
id: 'recXXX',
|
||||
options: {},
|
||||
};
|
||||
|
||||
const items = [
|
||||
{
|
||||
json: {},
|
||||
},
|
||||
];
|
||||
|
||||
const responce = await get.execute.call(
|
||||
createMockExecuteFunction(nodeParameters),
|
||||
items,
|
||||
'appYoLbase',
|
||||
'tblltable',
|
||||
);
|
||||
|
||||
expect(transport.apiRequest).toHaveBeenCalledTimes(1);
|
||||
expect(transport.apiRequest).toHaveBeenCalledWith('GET', 'appYoLbase/tblltable/recXXX');
|
||||
|
||||
expect(responce).toEqual([
|
||||
{
|
||||
json: {
|
||||
id: 'recXXX',
|
||||
foo: 'foo 1',
|
||||
bar: 'bar 1',
|
||||
},
|
||||
pairedItem: {
|
||||
item: 0,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,152 @@
|
||||
import * as search from '../../../../v2/actions/record/search.operation';
|
||||
import * as transport from '../../../../v2/transport';
|
||||
import { createMockExecuteFunction } from '../helpers';
|
||||
|
||||
jest.mock('../../../../v2/transport', () => {
|
||||
const originalModule = jest.requireActual('../../../../v2/transport');
|
||||
return {
|
||||
...originalModule,
|
||||
apiRequest: jest.fn(async function (method: string) {
|
||||
if (method === 'GET') {
|
||||
return {
|
||||
records: [
|
||||
{
|
||||
id: 'recYYY',
|
||||
fields: {
|
||||
foo: 'foo 2',
|
||||
bar: 'bar 2',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}),
|
||||
apiRequestAllItems: jest.fn(async function (method: string) {
|
||||
if (method === 'GET') {
|
||||
return {
|
||||
records: [
|
||||
{
|
||||
id: 'recYYY',
|
||||
fields: {
|
||||
foo: 'foo 2',
|
||||
bar: 'bar 2',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'recXXX',
|
||||
fields: {
|
||||
foo: 'foo 1',
|
||||
bar: 'bar 1',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe('Test AirtableV2, search operation', () => {
|
||||
it('should return all records', async () => {
|
||||
const nodeParameters = {
|
||||
operation: 'search',
|
||||
filterByFormula: 'foo',
|
||||
returnAll: true,
|
||||
options: {
|
||||
fields: ['foo', 'bar'],
|
||||
view: {
|
||||
value: 'viwView',
|
||||
mode: 'list',
|
||||
},
|
||||
},
|
||||
sort: {
|
||||
property: [
|
||||
{
|
||||
field: 'bar',
|
||||
direction: 'desc',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const items = [
|
||||
{
|
||||
json: {},
|
||||
},
|
||||
];
|
||||
|
||||
const result = await search.execute.call(
|
||||
createMockExecuteFunction(nodeParameters),
|
||||
items,
|
||||
'appYoLbase',
|
||||
'tblltable',
|
||||
);
|
||||
|
||||
expect(transport.apiRequestAllItems).toHaveBeenCalledTimes(1);
|
||||
expect(transport.apiRequestAllItems).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
'appYoLbase/tblltable',
|
||||
{},
|
||||
{
|
||||
fields: ['foo', 'bar'],
|
||||
filterByFormula: 'foo',
|
||||
sort: [{ direction: 'desc', field: 'bar' }],
|
||||
view: 'viwView',
|
||||
},
|
||||
);
|
||||
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0]).toEqual({
|
||||
json: { id: 'recYYY', foo: 'foo 2', bar: 'bar 2' },
|
||||
pairedItem: [
|
||||
{
|
||||
item: 0,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should return all records', async () => {
|
||||
const nodeParameters = {
|
||||
operation: 'search',
|
||||
filterByFormula: 'foo',
|
||||
returnAll: false,
|
||||
limit: 1,
|
||||
options: {
|
||||
fields: ['foo', 'bar'],
|
||||
},
|
||||
sort: {},
|
||||
};
|
||||
|
||||
const items = [
|
||||
{
|
||||
json: {},
|
||||
},
|
||||
];
|
||||
|
||||
const result = await search.execute.call(
|
||||
createMockExecuteFunction(nodeParameters),
|
||||
items,
|
||||
'appYoLbase',
|
||||
'tblltable',
|
||||
);
|
||||
|
||||
expect(transport.apiRequest).toHaveBeenCalledTimes(1);
|
||||
expect(transport.apiRequest).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
'appYoLbase/tblltable',
|
||||
{},
|
||||
{ fields: ['foo', 'bar'], filterByFormula: 'foo', maxRecords: 1 },
|
||||
);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual({
|
||||
json: { id: 'recYYY', foo: 'foo 2', bar: 'bar 2' },
|
||||
pairedItem: [
|
||||
{
|
||||
item: 0,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,150 @@
|
||||
import type { MockProxy } from 'jest-mock-extended';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import type { IExecuteFunctions } from 'n8n-workflow';
|
||||
|
||||
import * as update from '../../../../v2/actions/record/update.operation';
|
||||
import * as transport from '../../../../v2/transport';
|
||||
import { createMockExecuteFunction } from '../helpers';
|
||||
|
||||
jest.mock('../../../../v2/transport', () => {
|
||||
const originalModule = jest.requireActual('../../../../v2/transport');
|
||||
return {
|
||||
...originalModule,
|
||||
apiRequest: jest.fn(async function () {
|
||||
return {};
|
||||
}),
|
||||
batchUpdate: jest.fn(async function () {
|
||||
return {};
|
||||
}),
|
||||
apiRequestAllItems: jest.fn(async function (method: string) {
|
||||
if (method === 'GET') {
|
||||
return {
|
||||
records: [
|
||||
{
|
||||
id: 'recYYY',
|
||||
fields: {
|
||||
foo: 'foo 2',
|
||||
bar: 'bar 2',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'recXXX',
|
||||
fields: {
|
||||
foo: 'foo 1',
|
||||
bar: 'bar 1',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe('Test AirtableV2, update operation', () => {
|
||||
let mockExecuteFunctions: MockProxy<IExecuteFunctions>;
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should skip validation if typecast option is true', async () => {
|
||||
mockExecuteFunctions = mock<IExecuteFunctions>();
|
||||
mockExecuteFunctions.helpers.constructExecutionMetaData = jest.fn(() => []);
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((key: string) => {
|
||||
if (key === 'columns.mappingMode') {
|
||||
return 'defineBelow';
|
||||
}
|
||||
if (key === 'columns.matchingColumns') {
|
||||
return ['id'];
|
||||
}
|
||||
if (key === 'options') {
|
||||
return {
|
||||
typecast: true,
|
||||
};
|
||||
}
|
||||
if (key === 'columns.value') {
|
||||
return {
|
||||
id: 'recXXX',
|
||||
field1: 'foo 1',
|
||||
field2: 'bar 1',
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
await update.execute.call(mockExecuteFunctions, [{ json: {} }], 'base', 'table');
|
||||
|
||||
expect(mockExecuteFunctions.getNodeParameter).toHaveBeenCalledWith('columns.value', 0, [], {
|
||||
skipValidation: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should update a record by id, autoMapInputData', async () => {
|
||||
const nodeParameters = {
|
||||
operation: 'update',
|
||||
columns: {
|
||||
mappingMode: 'autoMapInputData',
|
||||
matchingColumns: ['id'],
|
||||
},
|
||||
options: {},
|
||||
};
|
||||
|
||||
const items = [
|
||||
{
|
||||
json: {
|
||||
id: 'recXXX',
|
||||
foo: 'foo 1',
|
||||
bar: 'bar 1',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
await update.execute.call(
|
||||
createMockExecuteFunction(nodeParameters),
|
||||
items,
|
||||
'appYoLbase',
|
||||
'tblltable',
|
||||
);
|
||||
|
||||
expect(transport.batchUpdate).toHaveBeenCalledWith(
|
||||
'appYoLbase/tblltable',
|
||||
{ typecast: false },
|
||||
[{ fields: { bar: 'bar 1', foo: 'foo 1' }, id: 'recXXX' }],
|
||||
);
|
||||
});
|
||||
|
||||
it('should update a record by field name, autoMapInputData', async () => {
|
||||
const nodeParameters = {
|
||||
operation: 'update',
|
||||
columns: {
|
||||
mappingMode: 'autoMapInputData',
|
||||
matchingColumns: ['foo'],
|
||||
},
|
||||
options: {},
|
||||
};
|
||||
|
||||
const items = [
|
||||
{
|
||||
json: {
|
||||
id: 'recXXX',
|
||||
foo: 'foo 1',
|
||||
bar: 'bar 1',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
await update.execute.call(
|
||||
createMockExecuteFunction(nodeParameters),
|
||||
items,
|
||||
'appYoLbase',
|
||||
'tblltable',
|
||||
);
|
||||
|
||||
expect(transport.batchUpdate).toHaveBeenCalledWith(
|
||||
'appYoLbase/tblltable',
|
||||
{ typecast: false },
|
||||
[{ fields: { bar: 'bar 1', foo: 'foo 1', id: 'recXXX' }, id: 'recXXX' }],
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,125 @@
|
||||
import { findMatches, removeIgnored } from '../../v2/helpers/utils';
|
||||
|
||||
describe('test AirtableV2, removeIgnored', () => {
|
||||
it('should remove ignored fields', () => {
|
||||
const data = {
|
||||
foo: 'foo',
|
||||
baz: 'baz',
|
||||
spam: 'spam',
|
||||
};
|
||||
|
||||
const ignore = 'baz,spam';
|
||||
|
||||
const result = removeIgnored(data, ignore);
|
||||
|
||||
expect(result).toEqual({
|
||||
foo: 'foo',
|
||||
});
|
||||
});
|
||||
it('should return the same data if ignore field does not present', () => {
|
||||
const data = {
|
||||
foo: 'foo',
|
||||
};
|
||||
|
||||
const ignore = 'bar';
|
||||
|
||||
const result = removeIgnored(data, ignore);
|
||||
|
||||
expect(result).toEqual(data);
|
||||
});
|
||||
it('should return the same data if empty string', () => {
|
||||
const data = {
|
||||
foo: 'foo',
|
||||
};
|
||||
|
||||
const ignore = '';
|
||||
|
||||
const result = removeIgnored(data, ignore);
|
||||
|
||||
expect(result).toEqual(data);
|
||||
});
|
||||
});
|
||||
|
||||
describe('test AirtableV2, findMatches', () => {
|
||||
it('should find match', () => {
|
||||
const data = [
|
||||
{
|
||||
fields: {
|
||||
id: 'rec123',
|
||||
data: 'data 1',
|
||||
},
|
||||
},
|
||||
{
|
||||
fields: {
|
||||
id: 'rec456',
|
||||
data: 'data 2',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const key = 'id';
|
||||
|
||||
const result = findMatches(data, [key], {
|
||||
id: 'rec123',
|
||||
data: 'data 1',
|
||||
});
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
fields: {
|
||||
id: 'rec123',
|
||||
data: 'data 1',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
it('should find all matches', () => {
|
||||
const data = [
|
||||
{
|
||||
fields: {
|
||||
id: 'rec123',
|
||||
data: 'data 1',
|
||||
},
|
||||
},
|
||||
{
|
||||
fields: {
|
||||
id: 'rec456',
|
||||
data: 'data 2',
|
||||
},
|
||||
},
|
||||
{
|
||||
fields: {
|
||||
id: 'rec123',
|
||||
data: 'data 3',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const key = 'id';
|
||||
|
||||
const result = findMatches(
|
||||
data,
|
||||
[key],
|
||||
{
|
||||
id: 'rec123',
|
||||
data: 'data 1',
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
fields: {
|
||||
id: 'rec123',
|
||||
data: 'data 1',
|
||||
},
|
||||
},
|
||||
{
|
||||
fields: {
|
||||
id: 'rec123',
|
||||
data: 'data 3',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"meta": {
|
||||
"instanceId": "104a4d08d8897b8bdeb38aaca515021075e0bd8544c983c2bb8c86e6a8e6081c"
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "f857c37f-36c1-4c9c-9b5f-f6ef49db67e3",
|
||||
"name": "On clicking 'execute'",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [820, 380]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"base": "appIaXXdDqS5ORr4V",
|
||||
"resource": "record",
|
||||
"operation": "get",
|
||||
"id": "rec2BWBoyS5QsS7pT",
|
||||
"application": {
|
||||
"__rl": true,
|
||||
"value": "https://airtable.com/appIaXXdDqS5ORr4V/tbljyBEdYzCPF0NDh/rec2BWBoyS5QsS7pT",
|
||||
"mode": "url",
|
||||
"__regex": "https://airtable.com/([a-zA-Z0-9]{2,})"
|
||||
},
|
||||
"table": {
|
||||
"__rl": true,
|
||||
"value": "https://airtable.com/appIaXXdDqS5ORr4V/tbljyBEdYzCPF0NDh/rec2BWBoyS5QsS7pT",
|
||||
"mode": "url",
|
||||
"__regex": "https://airtable.com/[a-zA-Z0-9]{2,}/([a-zA-Z0-9]{2,})"
|
||||
},
|
||||
"additionalOptions": {}
|
||||
},
|
||||
"id": "5654d3b3-fe83-4988-889b-94f107d41807",
|
||||
"name": "Airtable",
|
||||
"type": "n8n-nodes-base.airtable",
|
||||
"typeVersion": 2,
|
||||
"position": [1020, 380],
|
||||
"credentials": {
|
||||
"airtableTokenApi": {
|
||||
"id": "20",
|
||||
"name": "Airtable account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"On clicking 'execute'": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Airtable",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user