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,162 @@
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import pgPromise from 'pg-promise';
|
||||
|
||||
import * as PostgresFun from '../v1/genericFunctions';
|
||||
import type { PgpDatabase } from '../v2/helpers/interfaces';
|
||||
|
||||
type NodeParams = Record<string, string | {}>;
|
||||
|
||||
const pgp = pgPromise();
|
||||
const db = mock<PgpDatabase>();
|
||||
|
||||
describe('pgUpdate', () => {
|
||||
it('runs query to update db', async () => {
|
||||
const updateItem = { id: 1234, name: 'test' };
|
||||
const nodeParams: NodeParams = {
|
||||
table: 'mytable',
|
||||
schema: 'myschema',
|
||||
updateKey: 'id',
|
||||
columns: 'id,name',
|
||||
additionalFields: {},
|
||||
returnFields: '*',
|
||||
};
|
||||
const getNodeParam = (key: string) => nodeParams[key];
|
||||
|
||||
const items = [
|
||||
{
|
||||
json: updateItem,
|
||||
},
|
||||
];
|
||||
|
||||
await PostgresFun.pgUpdate(getNodeParam, pgp, db, items);
|
||||
|
||||
expect(db.any).toHaveBeenCalledWith(
|
||||
'update "myschema"."mytable" as t set "id"=v."id","name"=v."name" from (values(1234,\'test\')) as v("id","name") WHERE v."id" = t."id" RETURNING *',
|
||||
);
|
||||
});
|
||||
|
||||
it('runs query to update db if updateKey is not in columns', async () => {
|
||||
const updateItem = { id: 1234, name: 'test' };
|
||||
const nodeParams: NodeParams = {
|
||||
table: 'mytable',
|
||||
schema: 'myschema',
|
||||
updateKey: 'id',
|
||||
columns: 'name',
|
||||
additionalFields: {},
|
||||
returnFields: '*',
|
||||
};
|
||||
const getNodeParam = (key: string) => nodeParams[key];
|
||||
|
||||
const items = [
|
||||
{
|
||||
json: updateItem,
|
||||
},
|
||||
];
|
||||
|
||||
await PostgresFun.pgUpdate(getNodeParam, pgp, db, items);
|
||||
|
||||
expect(db.any).toHaveBeenCalledWith(
|
||||
'update "myschema"."mytable" as t set "id"=v."id","name"=v."name" from (values(1234,\'test\')) as v("id","name") WHERE v."id" = t."id" RETURNING *',
|
||||
);
|
||||
});
|
||||
|
||||
it('runs query to update db with cast as updateKey', async () => {
|
||||
const updateItem = { id: '1234', name: 'test' };
|
||||
const nodeParams: NodeParams = {
|
||||
table: 'mytable',
|
||||
schema: 'myschema',
|
||||
updateKey: 'id:uuid',
|
||||
columns: 'name',
|
||||
additionalFields: {},
|
||||
returnFields: '*',
|
||||
};
|
||||
const getNodeParam = (key: string) => nodeParams[key];
|
||||
|
||||
const items = [
|
||||
{
|
||||
json: updateItem,
|
||||
},
|
||||
];
|
||||
|
||||
await PostgresFun.pgUpdate(getNodeParam, pgp, db, items);
|
||||
|
||||
expect(db.any).toHaveBeenCalledWith(
|
||||
'update "myschema"."mytable" as t set "id"=v."id","name"=v."name" from (values(\'1234\'::uuid,\'test\')) as v("id","name") WHERE v."id" = t."id" RETURNING *',
|
||||
);
|
||||
});
|
||||
|
||||
it('runs query to update db with cast in target columns', async () => {
|
||||
const updateItem = { id: '1234', name: 'test' };
|
||||
const nodeParams: NodeParams = {
|
||||
table: 'mytable',
|
||||
schema: 'myschema',
|
||||
updateKey: 'id',
|
||||
columns: 'id:uuid,name',
|
||||
additionalFields: {},
|
||||
returnFields: '*',
|
||||
};
|
||||
const getNodeParam = (key: string) => nodeParams[key];
|
||||
|
||||
const items = [
|
||||
{
|
||||
json: updateItem,
|
||||
},
|
||||
];
|
||||
|
||||
await PostgresFun.pgUpdate(getNodeParam, pgp, db, items);
|
||||
|
||||
expect(db.any).toHaveBeenCalledWith(
|
||||
'update "myschema"."mytable" as t set "id"=v."id","name"=v."name" from (values(\'1234\'::uuid,\'test\')) as v("id","name") WHERE v."id" = t."id" RETURNING *',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pgInsert', () => {
|
||||
it('runs query to insert', async () => {
|
||||
const insertItem = { id: 1234, name: 'test', age: 34 };
|
||||
const nodeParams: NodeParams = {
|
||||
table: 'mytable',
|
||||
schema: 'myschema',
|
||||
columns: 'id,name,age',
|
||||
returnFields: '*',
|
||||
additionalFields: {},
|
||||
};
|
||||
const getNodeParam = (key: string) => nodeParams[key];
|
||||
|
||||
const items = [
|
||||
{
|
||||
json: insertItem,
|
||||
},
|
||||
];
|
||||
|
||||
await PostgresFun.pgInsert(getNodeParam, pgp, db, items, false);
|
||||
|
||||
expect(db.any).toHaveBeenCalledWith(
|
||||
'insert into "myschema"."mytable"("id","name","age") values(1234,\'test\',34) RETURNING *',
|
||||
);
|
||||
});
|
||||
|
||||
it('runs query to insert with type casting', async () => {
|
||||
const insertItem = { id: 1234, name: 'test', age: 34 };
|
||||
const nodeParams: NodeParams = {
|
||||
table: 'mytable',
|
||||
schema: 'myschema',
|
||||
columns: 'id:int,name:text,age',
|
||||
returnFields: '*',
|
||||
additionalFields: {},
|
||||
};
|
||||
const getNodeParam = (key: string) => nodeParams[key];
|
||||
|
||||
const items = [
|
||||
{
|
||||
json: insertItem,
|
||||
},
|
||||
];
|
||||
|
||||
await PostgresFun.pgInsert(getNodeParam, pgp, db, items, false);
|
||||
|
||||
expect(db.any).toHaveBeenCalledWith(
|
||||
'insert into "myschema"."mytable"("id","name","age") values(1234::int,\'test\'::text,34) RETURNING *',
|
||||
);
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,216 @@
|
||||
import type { MockProxy } from 'jest-mock-extended';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import type { ILoadOptionsFunctions } from 'n8n-workflow';
|
||||
|
||||
import type { ColumnInfo } from '../../v2/helpers/interfaces';
|
||||
import { getEnums, getEnumValues, getTableSchema } from '../../v2/helpers/utils';
|
||||
import { getMappingColumns } from '../../v2/methods/resourceMapping';
|
||||
|
||||
jest.mock('../../transport', () => {
|
||||
const originalModule = jest.requireActual('../../transport');
|
||||
return {
|
||||
...originalModule,
|
||||
configurePostgres: jest.fn(async () => ({ db: {} })),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../../v2/helpers/utils', () => {
|
||||
const originalModule = jest.requireActual('../../v2/helpers/utils');
|
||||
return {
|
||||
...originalModule,
|
||||
getEnums: jest.fn(() => new Map()),
|
||||
getEnumValues: jest.fn(),
|
||||
getTableSchema: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('Postgres, resourceMapping', () => {
|
||||
let loadOptionsFunctions: MockProxy<ILoadOptionsFunctions>;
|
||||
|
||||
const createColumnData = (
|
||||
columnName: string,
|
||||
dataType: string,
|
||||
overrides: Partial<ColumnInfo> = {},
|
||||
): ColumnInfo => ({
|
||||
column_name: columnName,
|
||||
data_type: dataType,
|
||||
is_nullable: 'NO',
|
||||
udt_name: dataType,
|
||||
column_default: null,
|
||||
is_generated: 'NEVER',
|
||||
...overrides,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
loadOptionsFunctions = mock<ILoadOptionsFunctions>();
|
||||
loadOptionsFunctions.getCredentials.mockResolvedValue({});
|
||||
loadOptionsFunctions.getNodeParameter.mockReturnValueOnce('public');
|
||||
loadOptionsFunctions.getNodeParameter.mockReturnValueOnce('test_table');
|
||||
loadOptionsFunctions.getNodeParameter.mockReturnValueOnce('insert');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test.each([
|
||||
{
|
||||
name: 'should mark id as not required if identity_generation is "BY_DEFAULT"',
|
||||
columnData: createColumnData('id', 'bigint', {
|
||||
udt_name: 'int8',
|
||||
identity_generation: 'BY DEFAULT',
|
||||
}),
|
||||
expectedType: 'number',
|
||||
expectedRequired: false,
|
||||
expectedDefaultMatch: true,
|
||||
},
|
||||
{
|
||||
name: 'should map citext to string type',
|
||||
columnData: createColumnData('email', 'citext'),
|
||||
expectedType: 'string',
|
||||
expectedRequired: true,
|
||||
expectedDefaultMatch: false,
|
||||
},
|
||||
{
|
||||
name: 'should map varchar to string type',
|
||||
columnData: createColumnData('name', 'varchar', { is_nullable: 'YES' }),
|
||||
expectedType: 'string',
|
||||
expectedRequired: false,
|
||||
expectedDefaultMatch: false,
|
||||
},
|
||||
{
|
||||
name: 'should map integer to number type',
|
||||
columnData: createColumnData('count', 'integer', { udt_name: 'int4' }),
|
||||
expectedType: 'number',
|
||||
expectedRequired: true,
|
||||
expectedDefaultMatch: false,
|
||||
},
|
||||
{
|
||||
name: 'should map boolean to boolean type',
|
||||
columnData: createColumnData('is_active', 'boolean', {
|
||||
udt_name: 'bool',
|
||||
column_default: 'false',
|
||||
}),
|
||||
expectedType: 'boolean',
|
||||
expectedRequired: false, // has default
|
||||
expectedDefaultMatch: false,
|
||||
},
|
||||
{
|
||||
name: 'should map timestamp to dateTime type',
|
||||
columnData: createColumnData('created_at', 'timestamp'),
|
||||
expectedType: 'dateTime',
|
||||
expectedRequired: true,
|
||||
expectedDefaultMatch: false,
|
||||
},
|
||||
{
|
||||
name: 'should map json to object type',
|
||||
columnData: createColumnData('metadata', 'json', { is_nullable: 'YES' }),
|
||||
expectedType: 'object',
|
||||
expectedRequired: false,
|
||||
expectedDefaultMatch: false,
|
||||
},
|
||||
{
|
||||
name: 'should map USER-DEFINED enum to options type',
|
||||
columnData: createColumnData('status', 'USER-DEFINED', { udt_name: 'status_enum' }),
|
||||
expectedType: 'options',
|
||||
expectedRequired: true,
|
||||
expectedDefaultMatch: false,
|
||||
isEnum: true,
|
||||
expectedOptions: [
|
||||
{ name: 'Active', value: 'active' },
|
||||
{ name: 'Inactive', value: 'inactive' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'should map unknown USER-DEFINED type to string by default',
|
||||
columnData: createColumnData('custom_field', 'USER-DEFINED', { udt_name: 'unknown_type' }),
|
||||
expectedType: 'string',
|
||||
expectedRequired: true,
|
||||
expectedDefaultMatch: false,
|
||||
},
|
||||
{
|
||||
name: 'should map uuid to string type',
|
||||
columnData: createColumnData('user_id', 'uuid'),
|
||||
expectedType: 'string',
|
||||
expectedRequired: true,
|
||||
expectedDefaultMatch: false,
|
||||
},
|
||||
{
|
||||
name: 'should map PostGIS geometry to string type',
|
||||
columnData: createColumnData('location', 'USER-DEFINED', { udt_name: 'geometry' }),
|
||||
expectedType: 'string',
|
||||
expectedRequired: true,
|
||||
expectedDefaultMatch: false,
|
||||
},
|
||||
{
|
||||
name: 'should map inet address to string type',
|
||||
columnData: createColumnData('ip_address', 'inet'),
|
||||
expectedType: 'string',
|
||||
expectedRequired: true,
|
||||
expectedDefaultMatch: false,
|
||||
},
|
||||
{
|
||||
name: 'should map hstore to object type',
|
||||
columnData: createColumnData('attributes', 'USER-DEFINED', { udt_name: 'hstore' }),
|
||||
expectedType: 'object',
|
||||
expectedRequired: true,
|
||||
expectedDefaultMatch: false,
|
||||
},
|
||||
{
|
||||
name: 'should map range types to string type',
|
||||
columnData: createColumnData('price_range', 'USER-DEFINED', { udt_name: 'numrange' }),
|
||||
expectedType: 'string',
|
||||
expectedRequired: true,
|
||||
expectedDefaultMatch: false,
|
||||
},
|
||||
{
|
||||
name: 'should map unknown data type to string by default',
|
||||
columnData: createColumnData('unknown_field', 'unknown_postgres_type', {
|
||||
udt_name: 'unknown',
|
||||
}),
|
||||
expectedType: 'string',
|
||||
expectedRequired: true,
|
||||
expectedDefaultMatch: false,
|
||||
},
|
||||
])(
|
||||
'$name',
|
||||
async ({
|
||||
columnData,
|
||||
expectedType,
|
||||
expectedRequired,
|
||||
expectedDefaultMatch,
|
||||
isEnum,
|
||||
expectedOptions,
|
||||
}) => {
|
||||
jest.mocked(getTableSchema).mockResolvedValueOnce([columnData]);
|
||||
|
||||
// Mock enum data if this test case represents an enum
|
||||
if (isEnum && columnData.udt_name) {
|
||||
jest
|
||||
.mocked(getEnums)
|
||||
.mockResolvedValueOnce(new Map([[columnData.udt_name, ['active', 'inactive']]]));
|
||||
jest.mocked(getEnumValues).mockReturnValueOnce([
|
||||
{ name: 'Active', value: 'active' },
|
||||
{ name: 'Inactive', value: 'inactive' },
|
||||
]);
|
||||
}
|
||||
|
||||
const fields = await getMappingColumns.call(loadOptionsFunctions);
|
||||
|
||||
expect(fields).toEqual({
|
||||
fields: [
|
||||
{
|
||||
canBeUsedToMatch: true,
|
||||
defaultMatch: expectedDefaultMatch,
|
||||
display: true,
|
||||
displayName: columnData.column_name,
|
||||
id: columnData.column_name,
|
||||
options: expectedOptions ?? undefined,
|
||||
required: expectedRequired,
|
||||
type: expectedType,
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import type { IDataObject, IExecuteFunctions, INode } from 'n8n-workflow';
|
||||
import pgPromise from 'pg-promise';
|
||||
|
||||
import type { PgpDatabase } from '../../v2/helpers/interfaces';
|
||||
import { configureQueryRunner } from '../../v2/helpers/utils';
|
||||
|
||||
const node: INode = {
|
||||
id: '1',
|
||||
name: 'Postgres node',
|
||||
typeVersion: 2,
|
||||
type: 'n8n-nodes-base.postgres',
|
||||
position: [60, 760],
|
||||
parameters: {
|
||||
operation: 'executeQuery',
|
||||
},
|
||||
};
|
||||
|
||||
const createMockDb = (returnData: IDataObject | IDataObject[]) => {
|
||||
return {
|
||||
async any() {
|
||||
return returnData;
|
||||
},
|
||||
async multi() {
|
||||
return returnData;
|
||||
},
|
||||
async tx() {
|
||||
return returnData;
|
||||
},
|
||||
async task() {
|
||||
return returnData;
|
||||
},
|
||||
} as unknown as PgpDatabase;
|
||||
};
|
||||
|
||||
describe('Test PostgresV2, runQueries', () => {
|
||||
it('should execute, should return success true', async () => {
|
||||
const pgp = pgPromise();
|
||||
const db = createMockDb([]);
|
||||
|
||||
const dbMultiSpy = jest.spyOn(db, 'multi');
|
||||
|
||||
const thisArg = mock<IExecuteFunctions>();
|
||||
const runQueries = configureQueryRunner.call(thisArg, node, false, pgp, db);
|
||||
|
||||
const result = await runQueries([{ query: 'SELECT * FROM table', values: [] }], {
|
||||
nodeVersion: 2.2,
|
||||
});
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result).toEqual([{ json: { success: true }, pairedItem: [{ item: 0 }] }]);
|
||||
expect(dbMultiSpy).toHaveBeenCalledWith('SELECT * FROM table');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,861 @@
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import type { IExecuteFunctions, INode, INodeExecutionData, IPairedItemData } from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
import pgPromise from 'pg-promise';
|
||||
|
||||
import type {
|
||||
ColumnInfo,
|
||||
PostgresNodeOptions,
|
||||
QueriesRunner,
|
||||
QueryMode,
|
||||
QueryWithValues,
|
||||
} from '../../v2/helpers/interfaces';
|
||||
import {
|
||||
addSortRules,
|
||||
addReturning,
|
||||
addWhereClauses,
|
||||
checkItemAgainstSchema,
|
||||
parsePostgresError,
|
||||
prepareErrorItem,
|
||||
prepareItem,
|
||||
replaceEmptyStringsByNulls,
|
||||
wrapData,
|
||||
convertArraysToPostgresFormat,
|
||||
isJSON,
|
||||
convertValuesToJsonWithPgp,
|
||||
hasJsonDataTypeInSchema,
|
||||
evaluateExpression,
|
||||
isWhereClause,
|
||||
getWhereClauses,
|
||||
runQueriesAndHandleErrors,
|
||||
} from '../../v2/helpers/utils';
|
||||
|
||||
const node: INode = {
|
||||
id: '1',
|
||||
name: 'Postgres node',
|
||||
typeVersion: 2,
|
||||
type: 'n8n-nodes-base.postgres',
|
||||
position: [60, 760],
|
||||
parameters: {
|
||||
operation: 'executeQuery',
|
||||
},
|
||||
};
|
||||
|
||||
describe('Test PostgresV2, isJSON', () => {
|
||||
it('should return true for valid JSON', () => {
|
||||
expect(isJSON('{"key": "value"}')).toEqual(true);
|
||||
});
|
||||
it('should return false for invalid JSON', () => {
|
||||
expect(isJSON('{"key": "value"')).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test PostgresV2, evaluateExpression', () => {
|
||||
it('should evaluate undefined to an empty string', () => {
|
||||
expect(evaluateExpression(undefined)).toEqual('');
|
||||
});
|
||||
it('should evaluate null to a string with value null', () => {
|
||||
expect(evaluateExpression(null)).toEqual('null');
|
||||
});
|
||||
it('should evaluate object to a string', () => {
|
||||
expect(evaluateExpression({ key: '' })).toEqual('{"key":""}');
|
||||
expect(evaluateExpression([])).toEqual('[]');
|
||||
expect(evaluateExpression([1, 2, 4])).toEqual('[1,2,4]');
|
||||
});
|
||||
it('should evaluate everything else to a string', () => {
|
||||
expect(evaluateExpression(1)).toEqual('1');
|
||||
expect(evaluateExpression('string')).toEqual('string');
|
||||
expect(evaluateExpression(true)).toEqual('true');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test PostgresV2, wrapData', () => {
|
||||
it('should wrap object in json', () => {
|
||||
const data = {
|
||||
id: 1,
|
||||
name: 'Name',
|
||||
};
|
||||
const wrappedData = wrapData(data);
|
||||
expect(wrappedData).toBeDefined();
|
||||
expect(wrappedData).toEqual([{ json: data }]);
|
||||
});
|
||||
it('should wrap each object in array in json', () => {
|
||||
const data = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Name',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Name 2',
|
||||
},
|
||||
];
|
||||
const wrappedData = wrapData(data);
|
||||
expect(wrappedData).toBeDefined();
|
||||
expect(wrappedData).toEqual([{ json: data[0] }, { json: data[1] }]);
|
||||
});
|
||||
it('json key from source should be inside json', () => {
|
||||
const data = {
|
||||
json: {
|
||||
id: 1,
|
||||
name: 'Name',
|
||||
},
|
||||
};
|
||||
const wrappedData = wrapData(data);
|
||||
expect(wrappedData).toBeDefined();
|
||||
expect(wrappedData).toEqual([{ json: data }]);
|
||||
expect(Object.keys(wrappedData[0].json)).toContain('json');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test PostgresV2, prepareErrorItem', () => {
|
||||
it('should return error info item', () => {
|
||||
const error = new Error('Test error');
|
||||
const item = prepareErrorItem(error, 1);
|
||||
expect(item).toBeDefined();
|
||||
|
||||
expect((item.pairedItem as IPairedItemData).item).toEqual(1);
|
||||
expect(item.json.error).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test PostgresV2, parsePostgresError', () => {
|
||||
it('should return NodeOperationError', () => {
|
||||
const error = new Error('Test error');
|
||||
|
||||
const parsedError = parsePostgresError(node, error, [], 1);
|
||||
expect(parsedError).toBeDefined();
|
||||
expect(parsedError.message).toEqual('Test error');
|
||||
expect(parsedError instanceof NodeOperationError).toEqual(true);
|
||||
});
|
||||
|
||||
it('should update message that includes ECONNREFUSED', () => {
|
||||
const error = new Error('ECONNREFUSED');
|
||||
|
||||
const parsedError = parsePostgresError(node, error, [], 1);
|
||||
expect(parsedError).toBeDefined();
|
||||
expect(parsedError.message).toEqual('Connection refused');
|
||||
expect(parsedError instanceof NodeOperationError).toEqual(true);
|
||||
});
|
||||
|
||||
it('should update message with syntax error', () => {
|
||||
// eslint-disable-next-line n8n-local-rules/no-unneeded-backticks
|
||||
const errorMessage = String.raw`syntax error at or near "select"`;
|
||||
const error = new Error();
|
||||
error.message = errorMessage;
|
||||
|
||||
const parsedError = parsePostgresError(node, error, [
|
||||
{ query: 'select * from my_table', values: [] },
|
||||
]);
|
||||
expect(parsedError).toBeDefined();
|
||||
expect(parsedError.message).toEqual('Syntax error at line 1 near "select"');
|
||||
expect(parsedError instanceof NodeOperationError).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test PostgresV2, addWhereClauses', () => {
|
||||
it('should add where clauses to query', () => {
|
||||
const query = 'SELECT * FROM $1:name.$2:name';
|
||||
const values = ['public', 'my_table'];
|
||||
const whereClauses = [{ column: 'id', condition: 'equal', value: '1' }];
|
||||
|
||||
const [updatedQuery, updatedValues] = addWhereClauses(
|
||||
node,
|
||||
0,
|
||||
query,
|
||||
whereClauses,
|
||||
values,
|
||||
'AND',
|
||||
);
|
||||
|
||||
expect(updatedQuery).toEqual('SELECT * FROM $1:name.$2:name WHERE $3:name = $4');
|
||||
expect(updatedValues).toEqual(['public', 'my_table', 'id', '1']);
|
||||
});
|
||||
|
||||
it('should combine where clauses by OR', () => {
|
||||
const query = 'SELECT * FROM $1:name.$2:name';
|
||||
const values = ['public', 'my_table'];
|
||||
const whereClauses = [
|
||||
{ column: 'id', condition: 'equal', value: '1' },
|
||||
{ column: 'foo', condition: 'equal', value: 'select 2' },
|
||||
];
|
||||
|
||||
const [updatedQuery, updatedValues] = addWhereClauses(
|
||||
node,
|
||||
0,
|
||||
query,
|
||||
whereClauses,
|
||||
values,
|
||||
'OR',
|
||||
);
|
||||
|
||||
expect(updatedQuery).toEqual(
|
||||
'SELECT * FROM $1:name.$2:name WHERE $3:name = $4 OR $5:name = $6',
|
||||
);
|
||||
expect(updatedValues).toEqual(['public', 'my_table', 'id', '1', 'foo', 'select 2']);
|
||||
});
|
||||
|
||||
it('should ignore incorrect combine condition ad use AND', () => {
|
||||
const query = 'SELECT * FROM $1:name.$2:name';
|
||||
const values = ['public', 'my_table'];
|
||||
const whereClauses = [
|
||||
{ column: 'id', condition: 'equal', value: '1' },
|
||||
{ column: 'foo', condition: 'equal', value: 'select 2' },
|
||||
];
|
||||
|
||||
const [updatedQuery, updatedValues] = addWhereClauses(
|
||||
node,
|
||||
0,
|
||||
query,
|
||||
whereClauses,
|
||||
values,
|
||||
'SELECT * FROM my_table',
|
||||
);
|
||||
|
||||
expect(updatedQuery).toEqual(
|
||||
'SELECT * FROM $1:name.$2:name WHERE $3:name = $4 AND $5:name = $6',
|
||||
);
|
||||
expect(updatedValues).toEqual(['public', 'my_table', 'id', '1', 'foo', 'select 2']);
|
||||
});
|
||||
|
||||
it('should handle numeric comparison operators', () => {
|
||||
const query = 'SELECT * FROM $1:name.$2:name';
|
||||
const values = ['public', 'my_table'];
|
||||
const whereClauses = [
|
||||
{ column: 'age', condition: '>', value: '25' },
|
||||
{ column: 'salary', condition: '>=', value: '50000' },
|
||||
];
|
||||
|
||||
const [updatedQuery, updatedValues] = addWhereClauses(
|
||||
node,
|
||||
0,
|
||||
query,
|
||||
whereClauses,
|
||||
values,
|
||||
'AND',
|
||||
);
|
||||
|
||||
expect(updatedQuery).toEqual(
|
||||
'SELECT * FROM $1:name.$2:name WHERE $3:name > $4 AND $5:name >= $6',
|
||||
);
|
||||
// Values should be converted to numbers
|
||||
expect(updatedValues).toEqual(['public', 'my_table', 'age', 25, 'salary', 50000]);
|
||||
});
|
||||
|
||||
it('should handle date comparison operators', () => {
|
||||
const query = 'SELECT * FROM $1:name.$2:name';
|
||||
const values = ['public', 'my_table'];
|
||||
const whereClauses = [
|
||||
{ column: 'created_at', condition: '>=', value: '2025-04-28T00:00:00.000Z' },
|
||||
{ column: 'updated_at', condition: '<', value: '2025-05-01' },
|
||||
];
|
||||
|
||||
const [updatedQuery, updatedValues] = addWhereClauses(
|
||||
node,
|
||||
0,
|
||||
query,
|
||||
whereClauses,
|
||||
values,
|
||||
'AND',
|
||||
);
|
||||
|
||||
expect(updatedQuery).toEqual(
|
||||
'SELECT * FROM $1:name.$2:name WHERE $3:name >= $4 AND $5:name < $6',
|
||||
);
|
||||
// Date strings should remain as strings
|
||||
expect(updatedValues).toEqual([
|
||||
'public',
|
||||
'my_table',
|
||||
'created_at',
|
||||
'2025-04-28T00:00:00.000Z',
|
||||
'updated_at',
|
||||
'2025-05-01',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle string comparison operators', () => {
|
||||
const query = 'SELECT * FROM $1:name.$2:name';
|
||||
const values = ['public', 'my_table'];
|
||||
const whereClauses = [
|
||||
{ column: 'name', condition: '>', value: 'M' },
|
||||
{ column: 'category', condition: '<=', value: 'Electronics' },
|
||||
];
|
||||
|
||||
const [updatedQuery, updatedValues] = addWhereClauses(
|
||||
node,
|
||||
0,
|
||||
query,
|
||||
whereClauses,
|
||||
values,
|
||||
'AND',
|
||||
);
|
||||
|
||||
expect(updatedQuery).toEqual(
|
||||
'SELECT * FROM $1:name.$2:name WHERE $3:name > $4 AND $5:name <= $6',
|
||||
);
|
||||
// Text strings should remain as strings
|
||||
expect(updatedValues).toEqual(['public', 'my_table', 'name', 'M', 'category', 'Electronics']);
|
||||
});
|
||||
|
||||
it('should not convert empty strings or whitespace-only strings to numbers', () => {
|
||||
const query = 'SELECT * FROM $1:name.$2:name';
|
||||
const values = ['public', 'my_table'];
|
||||
const whereClauses = [
|
||||
{ column: 'empty_field', condition: '>', value: '' },
|
||||
{ column: 'whitespace_field', condition: '>=', value: ' ' },
|
||||
];
|
||||
|
||||
const [updatedQuery, updatedValues] = addWhereClauses(
|
||||
node,
|
||||
0,
|
||||
query,
|
||||
whereClauses,
|
||||
values,
|
||||
'AND',
|
||||
);
|
||||
|
||||
expect(updatedQuery).toEqual(
|
||||
'SELECT * FROM $1:name.$2:name WHERE $3:name > $4 AND $5:name >= $6',
|
||||
);
|
||||
// These should NOT be converted to numbers
|
||||
expect(updatedValues).toEqual([
|
||||
'public',
|
||||
'my_table',
|
||||
'empty_field',
|
||||
'',
|
||||
'whitespace_field',
|
||||
' ',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test PostgresV2, addSortRules', () => {
|
||||
it('should ORDER BY ASC', () => {
|
||||
const query = 'SELECT * FROM $1:name.$2:name';
|
||||
const values = ['public', 'my_table'];
|
||||
const sortRules = [{ column: 'id', direction: 'ASC' }];
|
||||
|
||||
const [updatedQuery, updatedValues] = addSortRules(query, sortRules, values);
|
||||
|
||||
expect(updatedQuery).toEqual('SELECT * FROM $1:name.$2:name ORDER BY $3:name ASC');
|
||||
expect(updatedValues).toEqual(['public', 'my_table', 'id']);
|
||||
});
|
||||
it('should ORDER BY DESC', () => {
|
||||
const query = 'SELECT * FROM $1:name.$2:name';
|
||||
const values = ['public', 'my_table'];
|
||||
const sortRules = [{ column: 'id', direction: 'DESC' }];
|
||||
|
||||
const [updatedQuery, updatedValues] = addSortRules(query, sortRules, values);
|
||||
|
||||
expect(updatedQuery).toEqual('SELECT * FROM $1:name.$2:name ORDER BY $3:name DESC');
|
||||
expect(updatedValues).toEqual(['public', 'my_table', 'id']);
|
||||
});
|
||||
it('should ignore incorrect direction', () => {
|
||||
const query = 'SELECT * FROM $1:name.$2:name';
|
||||
const values = ['public', 'my_table'];
|
||||
const sortRules = [{ column: 'id', direction: 'SELECT * FROM my_table' }];
|
||||
|
||||
const [updatedQuery, updatedValues] = addSortRules(query, sortRules, values);
|
||||
|
||||
expect(updatedQuery).toEqual('SELECT * FROM $1:name.$2:name ORDER BY $3:name ASC');
|
||||
expect(updatedValues).toEqual(['public', 'my_table', 'id']);
|
||||
});
|
||||
it('should add multiple sort rules', () => {
|
||||
const query = 'SELECT * FROM $1:name.$2:name';
|
||||
const values = ['public', 'my_table'];
|
||||
const sortRules = [
|
||||
{ column: 'id', direction: 'ASC' },
|
||||
{ column: 'foo', direction: 'DESC' },
|
||||
];
|
||||
|
||||
const [updatedQuery, updatedValues] = addSortRules(query, sortRules, values);
|
||||
|
||||
expect(updatedQuery).toEqual(
|
||||
'SELECT * FROM $1:name.$2:name ORDER BY $3:name ASC, $4:name DESC',
|
||||
);
|
||||
expect(updatedValues).toEqual(['public', 'my_table', 'id', 'foo']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test PostgresV2, addReturning', () => {
|
||||
it('should add RETURNING', () => {
|
||||
const query = 'UPDATE $1:name.$2:name SET $5:name = $6 WHERE $3:name = $4';
|
||||
const values = ['public', 'my_table', 'id', '1', 'foo', 'updated'];
|
||||
const outputColumns = ['id', 'foo'];
|
||||
|
||||
const [updatedQuery, updatedValues] = addReturning(query, outputColumns, values);
|
||||
|
||||
expect(updatedQuery).toEqual(
|
||||
'UPDATE $1:name.$2:name SET $5:name = $6 WHERE $3:name = $4 RETURNING $7:name',
|
||||
);
|
||||
expect(updatedValues).toEqual([
|
||||
'public',
|
||||
'my_table',
|
||||
'id',
|
||||
'1',
|
||||
'foo',
|
||||
'updated',
|
||||
['id', 'foo'],
|
||||
]);
|
||||
});
|
||||
it('should add RETURNING *', () => {
|
||||
const query = 'UPDATE $1:name.$2:name SET $5:name = $6 WHERE $3:name = $4';
|
||||
const values = ['public', 'my_table', 'id', '1', 'foo', 'updated'];
|
||||
const outputColumns = ['id', 'foo', '*'];
|
||||
|
||||
const [updatedQuery, updatedValues] = addReturning(query, outputColumns, values);
|
||||
|
||||
expect(updatedQuery).toEqual(
|
||||
'UPDATE $1:name.$2:name SET $5:name = $6 WHERE $3:name = $4 RETURNING *',
|
||||
);
|
||||
expect(updatedValues).toEqual(['public', 'my_table', 'id', '1', 'foo', 'updated']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test PostgresV2, replaceEmptyStringsByNulls', () => {
|
||||
it('should replace empty string by null', () => {
|
||||
const items = [
|
||||
{ json: { foo: 'bar', bar: '', spam: undefined } },
|
||||
{ json: { foo: '', bar: '', spam: '' } },
|
||||
{ json: { foo: 0, bar: NaN, spam: false } },
|
||||
];
|
||||
|
||||
const updatedItems = replaceEmptyStringsByNulls(items, true);
|
||||
|
||||
expect(updatedItems).toBeDefined();
|
||||
expect(updatedItems).toEqual([
|
||||
{ json: { foo: 'bar', bar: null, spam: undefined } },
|
||||
{ json: { foo: null, bar: null, spam: null } },
|
||||
{ json: { foo: 0, bar: NaN, spam: false } },
|
||||
]);
|
||||
});
|
||||
it('should do nothing', () => {
|
||||
const items = [
|
||||
{ json: { foo: 'bar', bar: '', spam: undefined } },
|
||||
{ json: { foo: '', bar: '', spam: '' } },
|
||||
{ json: { foo: 0, bar: NaN, spam: false } },
|
||||
];
|
||||
|
||||
const updatedItems = replaceEmptyStringsByNulls(items);
|
||||
|
||||
expect(updatedItems).toBeDefined();
|
||||
expect(updatedItems).toEqual(items);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test PostgresV2, prepareItem', () => {
|
||||
it('should convert fixedCollection values to object', () => {
|
||||
const values = [
|
||||
{
|
||||
column: 'id',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
column: 'foo',
|
||||
value: 'bar',
|
||||
},
|
||||
{
|
||||
column: 'bar',
|
||||
value: 'foo',
|
||||
},
|
||||
];
|
||||
|
||||
const item = prepareItem(values);
|
||||
|
||||
expect(item).toBeDefined();
|
||||
expect(item).toEqual({
|
||||
id: '1',
|
||||
foo: 'bar',
|
||||
bar: 'foo',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test PostgresV2, checkItemAgainstSchema', () => {
|
||||
it('should not throw error', () => {
|
||||
const item = { foo: 'updated', id: 2 };
|
||||
const columnsInfo = [
|
||||
{ column_name: 'id', data_type: 'integer', is_nullable: 'NO' },
|
||||
{ column_name: 'json', data_type: 'json', is_nullable: 'NO' },
|
||||
{ column_name: 'foo', data_type: 'text', is_nullable: 'NO' },
|
||||
];
|
||||
|
||||
const result = checkItemAgainstSchema(node, item, columnsInfo, 0);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toEqual(item);
|
||||
});
|
||||
it('should throw error on not existing column', () => {
|
||||
const item = { foo: 'updated', bar: 'updated' };
|
||||
const columnsInfo = [
|
||||
{ column_name: 'id', data_type: 'integer', is_nullable: 'NO' },
|
||||
{ column_name: 'json', data_type: 'json', is_nullable: 'NO' },
|
||||
{ column_name: 'foo', data_type: 'text', is_nullable: 'NO' },
|
||||
];
|
||||
|
||||
try {
|
||||
checkItemAgainstSchema(node, item, columnsInfo, 0);
|
||||
} catch (error) {
|
||||
expect(error.message).toEqual("Column 'bar' does not exist in selected table");
|
||||
}
|
||||
});
|
||||
it('should throw error on not nullable column', () => {
|
||||
const item = { foo: null };
|
||||
const columnsInfo = [
|
||||
{ column_name: 'id', data_type: 'integer', is_nullable: 'NO' },
|
||||
{ column_name: 'foo', data_type: 'text', is_nullable: 'NO' },
|
||||
];
|
||||
|
||||
try {
|
||||
checkItemAgainstSchema(node, item, columnsInfo, 0);
|
||||
} catch (error) {
|
||||
expect(error.message).toEqual("Column 'foo' is not nullable");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test PostgresV2, hasJsonDataType', () => {
|
||||
it('returns true if there are columns which are of type json', () => {
|
||||
const schema: ColumnInfo[] = [
|
||||
{ column_name: 'data', data_type: 'json', is_nullable: 'YES' },
|
||||
{ column_name: 'id', data_type: 'integer', is_nullable: 'NO' },
|
||||
];
|
||||
|
||||
expect(hasJsonDataTypeInSchema(schema)).toEqual(true);
|
||||
});
|
||||
|
||||
it('returns false if there are columns which are of type json', () => {
|
||||
const schema: ColumnInfo[] = [{ column_name: 'id', data_type: 'integer', is_nullable: 'NO' }];
|
||||
|
||||
expect(hasJsonDataTypeInSchema(schema)).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test PostgresV2, convertValuesToJsonWithPgp', () => {
|
||||
const pgp = pgPromise();
|
||||
const pgpJsonSpy = jest.spyOn(pgp.as, 'json');
|
||||
const schema: ColumnInfo[] = [
|
||||
{ column_name: 'data', data_type: 'json', is_nullable: 'YES' },
|
||||
{ column_name: 'id', data_type: 'integer', is_nullable: 'NO' },
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
pgpJsonSpy.mockClear();
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
value: { data: [], id: 1 },
|
||||
expected: { data: '[]', id: 1 },
|
||||
},
|
||||
{
|
||||
value: { data: [0], id: 1 },
|
||||
expected: { data: '[0]', id: 1 },
|
||||
},
|
||||
{
|
||||
value: { data: { key: 2 }, id: 1 },
|
||||
expected: { data: '{"key":2}', id: 1 },
|
||||
},
|
||||
{
|
||||
value: { data: null, id: 1 },
|
||||
expected: { data: null, id: 1 },
|
||||
shouldSkipPgp: true,
|
||||
},
|
||||
{
|
||||
value: { data: undefined, id: 1 },
|
||||
expected: { data: undefined, id: 1 },
|
||||
shouldSkipPgp: true,
|
||||
},
|
||||
])('should convert $value.data to json correctly', ({ value, expected, shouldSkipPgp }) => {
|
||||
const data = value.data;
|
||||
expect(convertValuesToJsonWithPgp(pgp, schema, value)).toEqual(expected);
|
||||
expect(value).toEqual(expected);
|
||||
if (!shouldSkipPgp) {
|
||||
expect(pgpJsonSpy).toHaveBeenCalledWith(data, true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test PostgresV2, convertArraysToPostgresFormat', () => {
|
||||
it('should convert js arrays to postgres format', () => {
|
||||
const item = {
|
||||
jsonb_array: [
|
||||
{
|
||||
key: 'value44',
|
||||
},
|
||||
],
|
||||
json_array: [
|
||||
{
|
||||
key: 'value54',
|
||||
},
|
||||
],
|
||||
int_array: [1, 2, 5],
|
||||
text_array: ['one', 't"w"o'],
|
||||
bool_array: [true, false],
|
||||
};
|
||||
|
||||
const schema: ColumnInfo[] = [
|
||||
{
|
||||
column_name: 'id',
|
||||
data_type: 'integer',
|
||||
is_nullable: 'NO',
|
||||
udt_name: 'int4',
|
||||
column_default: "nextval('test_data_array_id_seq'::regclass)",
|
||||
},
|
||||
{
|
||||
column_name: 'jsonb_array',
|
||||
data_type: 'ARRAY',
|
||||
is_nullable: 'YES',
|
||||
udt_name: '_jsonb',
|
||||
column_default: null,
|
||||
},
|
||||
{
|
||||
column_name: 'json_array',
|
||||
data_type: 'ARRAY',
|
||||
is_nullable: 'YES',
|
||||
udt_name: '_json',
|
||||
column_default: null,
|
||||
},
|
||||
{
|
||||
column_name: 'int_array',
|
||||
data_type: 'ARRAY',
|
||||
is_nullable: 'YES',
|
||||
udt_name: '_int4',
|
||||
column_default: null,
|
||||
},
|
||||
{
|
||||
column_name: 'bool_array',
|
||||
data_type: 'ARRAY',
|
||||
is_nullable: 'YES',
|
||||
udt_name: '_bool',
|
||||
column_default: null,
|
||||
},
|
||||
{
|
||||
column_name: 'text_array',
|
||||
data_type: 'ARRAY',
|
||||
is_nullable: 'YES',
|
||||
udt_name: '_text',
|
||||
column_default: null,
|
||||
},
|
||||
];
|
||||
|
||||
const result = convertArraysToPostgresFormat(item, schema, node, 0);
|
||||
|
||||
expect(result).toEqual({
|
||||
jsonb_array: '{"{\\"key\\":\\"value44\\"}"}',
|
||||
json_array: '{"{\\"key\\":\\"value54\\"}"}',
|
||||
int_array: '{1,2,5}',
|
||||
text_array: '{"one","t\\"w\\"o"}',
|
||||
bool_array: '{"true","false"}',
|
||||
});
|
||||
});
|
||||
|
||||
it('should not modify the original data object', () => {
|
||||
const referenceItem = {
|
||||
arr: [1, 2, 3],
|
||||
};
|
||||
const item = {
|
||||
arr: [1, 2, 3],
|
||||
};
|
||||
const schema: ColumnInfo[] = [
|
||||
{
|
||||
column_name: 'arr',
|
||||
data_type: 'ARRAY',
|
||||
is_nullable: 'YES',
|
||||
udt_name: '_int4',
|
||||
column_default: null,
|
||||
},
|
||||
];
|
||||
|
||||
const result = convertArraysToPostgresFormat(item, schema, node, 0);
|
||||
|
||||
expect(result).toEqual({
|
||||
arr: '{1,2,3}',
|
||||
});
|
||||
expect(item).toEqual(referenceItem);
|
||||
});
|
||||
|
||||
describe('where clause handling', () => {
|
||||
const validOperations = [
|
||||
'equal',
|
||||
'=',
|
||||
'!=',
|
||||
'LIKE',
|
||||
'>',
|
||||
'<',
|
||||
'>=',
|
||||
'<=',
|
||||
'IS NULL',
|
||||
'IS NOT NULL',
|
||||
];
|
||||
const invalidOperations = ['=1 or 1--', '=>', ''];
|
||||
|
||||
test.each(validOperations)('isWhereClause returns true for "%s" operation', (operation) => {
|
||||
expect(
|
||||
isWhereClause({
|
||||
column: 'id',
|
||||
condition: operation,
|
||||
value: '1',
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test.each(invalidOperations)('isWhereClause returns false for "%s" operation', (operation) => {
|
||||
expect(
|
||||
isWhereClause({
|
||||
column: 'name',
|
||||
condition: operation,
|
||||
value: 'ok',
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
test('isWhereClause returns false for when column is missing', () => {
|
||||
expect(
|
||||
isWhereClause({
|
||||
condition: 'equal',
|
||||
value: 'ok',
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
test('isWhereClause returns false for when condition is missing', () => {
|
||||
expect(
|
||||
isWhereClause({
|
||||
column: 'id',
|
||||
value: 'ok',
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
test.each(invalidOperations)(
|
||||
'getWhereClauses throws an exception for "%s" operation',
|
||||
(operation) => {
|
||||
const getNodeParameterMock = jest.fn().mockReturnValue({
|
||||
values: [
|
||||
{
|
||||
column: 'test',
|
||||
condition: '=',
|
||||
value: '3',
|
||||
},
|
||||
{
|
||||
column: 'id',
|
||||
condition: operation,
|
||||
value: '1',
|
||||
},
|
||||
],
|
||||
});
|
||||
const ctx = mock<IExecuteFunctions>({ getNodeParameter: getNodeParameterMock });
|
||||
expect(() => getWhereClauses(ctx, 0)).toThrow();
|
||||
},
|
||||
);
|
||||
|
||||
test.each(validOperations)(
|
||||
'getWhereClauses returns valid clauses for "%s" operation',
|
||||
(operation) => {
|
||||
const clauses = [
|
||||
{
|
||||
column: 'name',
|
||||
condition: 'LIKE',
|
||||
value: 'Wohn Jick',
|
||||
},
|
||||
{
|
||||
column: 'id',
|
||||
condition: operation,
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
column: 'condition',
|
||||
condition: 'equal',
|
||||
value: 'angry',
|
||||
},
|
||||
];
|
||||
const getNodeParameterMock = jest.fn().mockReturnValue({
|
||||
values: clauses,
|
||||
});
|
||||
const ctx = mock<IExecuteFunctions>({ getNodeParameter: getNodeParameterMock });
|
||||
expect(getWhereClauses(ctx, 0)).toBe(clauses);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test PostgresV2, runQueriesAndHandleErrors', () => {
|
||||
it.each([['single'], ['transaction']] as QueryMode[][])(
|
||||
'should return errors without running queries when batching is %s',
|
||||
async (batching) => {
|
||||
const runQueries: QueriesRunner = jest.fn().mockResolvedValue([]);
|
||||
const queries: QueryWithValues[] = [
|
||||
{ query: 'INSERT INTO my_table (id) VALUES (1)', values: [] },
|
||||
];
|
||||
const nodeOptions: PostgresNodeOptions = { queryBatching: batching };
|
||||
const errorItemsMap: Map<number, INodeExecutionData> = new Map();
|
||||
errorItemsMap.set(1, { json: { error: new Error('Test error') }, pairedItem: { item: 1 } });
|
||||
|
||||
const result = await runQueriesAndHandleErrors(
|
||||
runQueries,
|
||||
queries,
|
||||
nodeOptions,
|
||||
errorItemsMap,
|
||||
);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ json: { error: new Error('Test error') }, pairedItem: { item: 1 } },
|
||||
]);
|
||||
expect(runQueries).not.toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
|
||||
it('should run queries and return errors when batching is independently', async () => {
|
||||
const runQueries: QueriesRunner = jest.fn().mockResolvedValue([
|
||||
{ json: { id: 1 }, pairedItem: { item: 0 } },
|
||||
{ json: { id: 3 }, pairedItem: { item: 2 } },
|
||||
]);
|
||||
const queries: QueryWithValues[] = [
|
||||
{ query: 'INSERT INTO my_table (id) VALUES (1)', values: [] },
|
||||
{ query: 'INSERT INTO my_table (id) VALUES (3)', values: [] },
|
||||
];
|
||||
const nodeOptions: PostgresNodeOptions = { queryBatching: 'independently' };
|
||||
const errorItemsMap: Map<number, INodeExecutionData> = new Map();
|
||||
errorItemsMap.set(1, { json: { error: new Error('Test error') }, pairedItem: { item: 1 } });
|
||||
|
||||
const result = await runQueriesAndHandleErrors(runQueries, queries, nodeOptions, errorItemsMap);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ json: { id: 1 }, pairedItem: { item: 0 } },
|
||||
{ json: { error: new Error('Test error') }, pairedItem: { item: 1 } },
|
||||
{ json: { id: 3 }, pairedItem: { item: 2 } },
|
||||
]);
|
||||
});
|
||||
|
||||
it.each([['single'], ['transaction'], ['independently']] as QueryMode[][])(
|
||||
'should run queries when batching is %s and there are no errors',
|
||||
async (batching) => {
|
||||
const runQueries: QueriesRunner = jest.fn().mockResolvedValue([
|
||||
{ json: { id: 1 }, pairedItem: { item: 0 } },
|
||||
{ json: { id: 2 }, pairedItem: { item: 1 } },
|
||||
{ json: { id: 3 }, pairedItem: { item: 2 } },
|
||||
]);
|
||||
const queries: QueryWithValues[] = [
|
||||
{ query: 'INSERT INTO my_table (id) VALUES (1)', values: [] },
|
||||
{ query: 'INSERT INTO my_table (id) VALUES (2)', values: [] },
|
||||
{ query: 'INSERT INTO my_table (id) VALUES (3)', values: [] },
|
||||
];
|
||||
const nodeOptions: PostgresNodeOptions = { queryBatching: batching };
|
||||
const errorItemsMap: Map<number, INodeExecutionData> = new Map();
|
||||
|
||||
const result = await runQueriesAndHandleErrors(
|
||||
runQueries,
|
||||
queries,
|
||||
nodeOptions,
|
||||
errorItemsMap,
|
||||
);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ json: { id: 1 }, pairedItem: { item: 0 } },
|
||||
{ json: { id: 2 }, pairedItem: { item: 1 } },
|
||||
{ json: { id: 3 }, pairedItem: { item: 2 } },
|
||||
]);
|
||||
},
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user