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,660 @@
|
||||
import {
|
||||
type INode,
|
||||
NodeOperationError,
|
||||
type IDataTableProjectService,
|
||||
type IExecuteFunctions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import type { FieldEntry } from '../../common/constants';
|
||||
import { ANY_CONDITION, ALL_CONDITIONS } from '../../common/constants';
|
||||
import { DATA_TABLE_ID_FIELD } from '../../common/fields';
|
||||
import { executeSelectMany, getSelectFilter } from '../../common/selectMany';
|
||||
|
||||
describe('selectMany utils', () => {
|
||||
let mockExecuteFunctions: IExecuteFunctions;
|
||||
const getManyRowsAndCount = jest.fn();
|
||||
const dataTableProxy = jest.mocked<IDataTableProjectService>({
|
||||
getManyRowsAndCount,
|
||||
} as unknown as IDataTableProjectService);
|
||||
const dataTableId = 2345;
|
||||
let filters: FieldEntry[];
|
||||
const node = { id: 1 } as unknown as INode;
|
||||
|
||||
beforeEach(() => {
|
||||
filters = [
|
||||
{
|
||||
condition: 'eq',
|
||||
keyName: 'id',
|
||||
keyValue: 1,
|
||||
},
|
||||
];
|
||||
|
||||
const mockDataTableProxy = {
|
||||
getColumns: jest.fn().mockResolvedValue([
|
||||
{ name: 'name', type: 'string' },
|
||||
{ name: 'age', type: 'number' },
|
||||
{ name: 'status', type: 'string' },
|
||||
]),
|
||||
};
|
||||
|
||||
mockExecuteFunctions = {
|
||||
getNode: jest.fn().mockReturnValue(node),
|
||||
getNodeParameter: jest.fn().mockImplementation((field) => {
|
||||
switch (field) {
|
||||
case DATA_TABLE_ID_FIELD:
|
||||
return dataTableId;
|
||||
case 'filters.conditions':
|
||||
return filters;
|
||||
case 'matchType':
|
||||
return ANY_CONDITION;
|
||||
}
|
||||
}),
|
||||
helpers: {
|
||||
getDataTableProxy: jest.fn().mockResolvedValue(mockDataTableProxy),
|
||||
},
|
||||
} as unknown as IExecuteFunctions;
|
||||
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('executeSelectMany', () => {
|
||||
it('should get a few rows', async () => {
|
||||
// ARRANGE
|
||||
getManyRowsAndCount.mockReturnValue({ data: [{ id: 1 }], count: 1 });
|
||||
|
||||
// ACT
|
||||
const result = await executeSelectMany(mockExecuteFunctions, 0, dataTableProxy);
|
||||
|
||||
// ASSERT
|
||||
expect(result).toEqual([{ json: { id: 1 } }]);
|
||||
});
|
||||
|
||||
it('should get a paginated amount of rows', async () => {
|
||||
// ARRANGE
|
||||
getManyRowsAndCount.mockReturnValueOnce({
|
||||
data: Array.from({ length: 1000 }, (_, k) => ({ id: k })),
|
||||
count: 2345,
|
||||
});
|
||||
getManyRowsAndCount.mockReturnValueOnce({
|
||||
data: Array.from({ length: 1000 }, (_, k) => ({ id: k + 1000 })),
|
||||
count: 2345,
|
||||
});
|
||||
|
||||
getManyRowsAndCount.mockReturnValueOnce({
|
||||
data: Array.from({ length: 345 }, (_, k) => ({ id: k + 2000 })),
|
||||
count: 2345,
|
||||
});
|
||||
|
||||
filters = [];
|
||||
|
||||
// ACT
|
||||
const result = await executeSelectMany(mockExecuteFunctions, 0, dataTableProxy);
|
||||
|
||||
// ASSERT
|
||||
expect(result.length).toBe(2345);
|
||||
expect(result[0]).toEqual({ json: { id: 0 } });
|
||||
expect(result[2344]).toEqual({ json: { id: 2344 } });
|
||||
});
|
||||
|
||||
it('should pass null through correctly', async () => {
|
||||
// ARRANGE
|
||||
getManyRowsAndCount.mockReturnValue({ data: [{ id: 1, colA: null }], count: 1 });
|
||||
|
||||
// ACT
|
||||
const result = await executeSelectMany(mockExecuteFunctions, 0, dataTableProxy);
|
||||
|
||||
// ASSERT
|
||||
expect(result).toEqual([{ json: { id: 1, colA: null } }]);
|
||||
});
|
||||
|
||||
it('should panic if pagination gets out of sync', async () => {
|
||||
// ARRANGE
|
||||
getManyRowsAndCount.mockReturnValueOnce({
|
||||
data: Array.from({ length: 1000 }, (_, k) => ({ id: k })),
|
||||
count: 2345,
|
||||
});
|
||||
getManyRowsAndCount.mockReturnValueOnce({
|
||||
data: Array.from({ length: 1000 }, (_, k) => ({ id: k + 1000 })),
|
||||
count: 2344,
|
||||
});
|
||||
|
||||
filters = [];
|
||||
|
||||
// ACT ASSERT
|
||||
await expect(executeSelectMany(mockExecuteFunctions, 0, dataTableProxy)).rejects.toEqual(
|
||||
new NodeOperationError(
|
||||
node,
|
||||
'synchronization error: result count changed during pagination',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
describe('filter conditions', () => {
|
||||
it('should handle "eq" condition', async () => {
|
||||
// ARRANGE
|
||||
filters = [{ condition: 'eq', keyName: 'name', keyValue: 'John' }];
|
||||
getManyRowsAndCount.mockReturnValue({ data: [{ id: 1, name: 'John' }], count: 1 });
|
||||
|
||||
// ACT
|
||||
const result = await executeSelectMany(mockExecuteFunctions, 0, dataTableProxy);
|
||||
|
||||
// ASSERT
|
||||
expect(result).toEqual([{ json: { id: 1, name: 'John' } }]);
|
||||
});
|
||||
|
||||
it('should handle "neq" condition', async () => {
|
||||
// ARRANGE
|
||||
filters = [{ condition: 'neq', keyName: 'name', keyValue: 'John' }];
|
||||
getManyRowsAndCount.mockReturnValue({ data: [{ id: 1, name: 'Jane' }], count: 1 });
|
||||
|
||||
// ACT
|
||||
const result = await executeSelectMany(mockExecuteFunctions, 0, dataTableProxy);
|
||||
|
||||
// ASSERT
|
||||
expect(result).toEqual([{ json: { id: 1, name: 'Jane' } }]);
|
||||
});
|
||||
|
||||
it('should handle "gt" condition with numbers', async () => {
|
||||
// ARRANGE
|
||||
filters = [{ condition: 'gt', keyName: 'age', keyValue: 25 }];
|
||||
getManyRowsAndCount.mockReturnValue({ data: [{ id: 1, age: 30 }], count: 1 });
|
||||
|
||||
// ACT
|
||||
const result = await executeSelectMany(mockExecuteFunctions, 0, dataTableProxy);
|
||||
|
||||
// ASSERT
|
||||
expect(result).toEqual([{ json: { id: 1, age: 30 } }]);
|
||||
});
|
||||
|
||||
it('should handle "gte" condition with numbers', async () => {
|
||||
// ARRANGE
|
||||
filters = [{ condition: 'gte', keyName: 'age', keyValue: 25 }];
|
||||
getManyRowsAndCount.mockReturnValue({
|
||||
data: [
|
||||
{ id: 1, age: 25 },
|
||||
{ id: 2, age: 30 },
|
||||
],
|
||||
count: 2,
|
||||
});
|
||||
|
||||
// ACT
|
||||
const result = await executeSelectMany(mockExecuteFunctions, 0, dataTableProxy);
|
||||
|
||||
// ASSERT
|
||||
expect(result).toEqual([{ json: { id: 1, age: 25 } }, { json: { id: 2, age: 30 } }]);
|
||||
});
|
||||
|
||||
it('should handle "lt" condition with numbers', async () => {
|
||||
// ARRANGE
|
||||
filters = [{ condition: 'lt', keyName: 'age', keyValue: 30 }];
|
||||
getManyRowsAndCount.mockReturnValue({ data: [{ id: 1, age: 25 }], count: 1 });
|
||||
|
||||
// ACT
|
||||
const result = await executeSelectMany(mockExecuteFunctions, 0, dataTableProxy);
|
||||
|
||||
// ASSERT
|
||||
expect(result).toEqual([{ json: { id: 1, age: 25 } }]);
|
||||
});
|
||||
|
||||
it('should handle "lte" condition with numbers', async () => {
|
||||
// ARRANGE
|
||||
filters = [{ condition: 'lte', keyName: 'age', keyValue: 30 }];
|
||||
getManyRowsAndCount.mockReturnValue({
|
||||
data: [
|
||||
{ id: 1, age: 25 },
|
||||
{ id: 2, age: 30 },
|
||||
],
|
||||
count: 2,
|
||||
});
|
||||
|
||||
// ACT
|
||||
const result = await executeSelectMany(mockExecuteFunctions, 0, dataTableProxy);
|
||||
|
||||
// ASSERT
|
||||
expect(result).toEqual([{ json: { id: 1, age: 25 } }, { json: { id: 2, age: 30 } }]);
|
||||
});
|
||||
|
||||
it('should handle "like" condition with pattern matching', async () => {
|
||||
// ARRANGE
|
||||
filters = [{ condition: 'like', keyName: 'name', keyValue: '%Mar%' }];
|
||||
getManyRowsAndCount.mockReturnValue({ data: [{ id: 1, name: 'Anne-Marie' }], count: 1 });
|
||||
|
||||
// ACT
|
||||
const result = await executeSelectMany(mockExecuteFunctions, 0, dataTableProxy);
|
||||
|
||||
// ASSERT
|
||||
expect(result).toEqual([{ json: { id: 1, name: 'Anne-Marie' } }]);
|
||||
});
|
||||
|
||||
it('should handle "ilike" condition with case-insensitive pattern matching', async () => {
|
||||
// ARRANGE
|
||||
filters = [{ condition: 'ilike', keyName: 'name', keyValue: '%mar%' }];
|
||||
getManyRowsAndCount.mockReturnValue({ data: [{ id: 1, name: 'Anne-Marie' }], count: 1 });
|
||||
|
||||
// ACT
|
||||
const result = await executeSelectMany(mockExecuteFunctions, 0, dataTableProxy);
|
||||
|
||||
// ASSERT
|
||||
expect(result).toEqual([{ json: { id: 1, name: 'Anne-Marie' } }]);
|
||||
});
|
||||
|
||||
it('should handle multiple conditions with ANY_CONDITION (OR logic - matches records satisfying either condition)', async () => {
|
||||
// ARRANGE
|
||||
filters = [
|
||||
{ condition: 'eq', keyName: 'status', keyValue: 'active' },
|
||||
{ condition: 'gt', keyName: 'age', keyValue: 50 },
|
||||
];
|
||||
getManyRowsAndCount.mockReturnValue({
|
||||
data: [{ id: 1, status: 'active', age: 25 }],
|
||||
count: 1,
|
||||
});
|
||||
|
||||
// ACT
|
||||
const result = await executeSelectMany(mockExecuteFunctions, 0, dataTableProxy);
|
||||
|
||||
// ASSERT
|
||||
expect(result).toEqual([{ json: { id: 1, status: 'active', age: 25 } }]);
|
||||
});
|
||||
|
||||
it('should handle multiple conditions with ALL_CONDITIONS (AND logic - matches records satisfying all conditions)', async () => {
|
||||
// ARRANGE
|
||||
filters = [
|
||||
{ condition: 'eq', keyName: 'status', keyValue: 'active' },
|
||||
{ condition: 'gte', keyName: 'age', keyValue: 21 },
|
||||
];
|
||||
mockExecuteFunctions.getNodeParameter = jest.fn().mockImplementation((field) => {
|
||||
switch (field) {
|
||||
case DATA_TABLE_ID_FIELD:
|
||||
return dataTableId;
|
||||
case 'filters.conditions':
|
||||
return filters;
|
||||
case 'matchType':
|
||||
return ALL_CONDITIONS;
|
||||
}
|
||||
});
|
||||
getManyRowsAndCount.mockReturnValue({
|
||||
data: [{ id: 1, status: 'active', age: 25 }],
|
||||
count: 1,
|
||||
});
|
||||
|
||||
// ACT
|
||||
const result = await executeSelectMany(mockExecuteFunctions, 0, dataTableProxy);
|
||||
|
||||
// ASSERT
|
||||
expect(result).toEqual([{ json: { id: 1, status: 'active', age: 25 } }]);
|
||||
});
|
||||
|
||||
it('should handle ALL_CONDITIONS excluding records that match only one condition (proves AND logic)', async () => {
|
||||
// ARRANGE
|
||||
filters = [
|
||||
{ condition: 'eq', keyName: 'status', keyValue: 'inactive' },
|
||||
{ condition: 'gte', keyName: 'age', keyValue: 21 },
|
||||
];
|
||||
mockExecuteFunctions.getNodeParameter = jest.fn().mockImplementation((field) => {
|
||||
switch (field) {
|
||||
case DATA_TABLE_ID_FIELD:
|
||||
return dataTableId;
|
||||
case 'filters.conditions':
|
||||
return filters;
|
||||
case 'matchType':
|
||||
return ALL_CONDITIONS;
|
||||
}
|
||||
});
|
||||
getManyRowsAndCount.mockReturnValue({
|
||||
data: [],
|
||||
count: 0,
|
||||
});
|
||||
|
||||
// ACT
|
||||
const result = await executeSelectMany(mockExecuteFunctions, 0, dataTableProxy);
|
||||
|
||||
// ASSERT
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle ANY_CONDITION including records that match only one condition (proves OR logic)', async () => {
|
||||
// ARRANGE
|
||||
filters = [
|
||||
{ condition: 'eq', keyName: 'status', keyValue: 'inactive' },
|
||||
{ condition: 'gte', keyName: 'age', keyValue: 21 },
|
||||
];
|
||||
mockExecuteFunctions.getNodeParameter = jest.fn().mockImplementation((field) => {
|
||||
switch (field) {
|
||||
case DATA_TABLE_ID_FIELD:
|
||||
return dataTableId;
|
||||
case 'filters.conditions':
|
||||
return filters;
|
||||
case 'matchType':
|
||||
return ANY_CONDITION;
|
||||
}
|
||||
});
|
||||
getManyRowsAndCount.mockReturnValue({
|
||||
data: [{ id: 1, status: 'active', age: 25 }],
|
||||
count: 1,
|
||||
});
|
||||
|
||||
// ACT
|
||||
const result = await executeSelectMany(mockExecuteFunctions, 0, dataTableProxy);
|
||||
|
||||
// ASSERT
|
||||
expect(result).toEqual([{ json: { id: 1, status: 'active', age: 25 } }]);
|
||||
});
|
||||
|
||||
it('should convert Date objects to ISO strings in output (v1.1+)', async () => {
|
||||
// ARRANGE
|
||||
const testDate = new Date('2025-12-11T10:30:59.000Z');
|
||||
const testUpdatedDate = new Date('2025-12-12T11:16:53.385Z');
|
||||
filters = [];
|
||||
mockExecuteFunctions.getNodeParameter = jest.fn().mockImplementation((field) => {
|
||||
switch (field) {
|
||||
case DATA_TABLE_ID_FIELD:
|
||||
return dataTableId;
|
||||
case 'filters.conditions':
|
||||
return filters;
|
||||
case 'matchType':
|
||||
return ANY_CONDITION;
|
||||
case 'returnAll':
|
||||
return true;
|
||||
}
|
||||
});
|
||||
mockExecuteFunctions.getNode = jest.fn().mockReturnValue({ ...node, typeVersion: 1.1 });
|
||||
getManyRowsAndCount.mockReturnValue({
|
||||
data: [
|
||||
{
|
||||
id: 1,
|
||||
completedDate: testDate,
|
||||
createdAt: testDate,
|
||||
updatedAt: testUpdatedDate,
|
||||
status: 'active',
|
||||
},
|
||||
],
|
||||
count: 1,
|
||||
});
|
||||
|
||||
// ACT
|
||||
const result = await executeSelectMany(mockExecuteFunctions, 0, dataTableProxy);
|
||||
|
||||
// ASSERT
|
||||
// Dates should be converted to ISO strings, not Date objects
|
||||
expect(result).toEqual([
|
||||
{
|
||||
json: {
|
||||
id: 1,
|
||||
completedDate: '2025-12-11T10:30:59.000Z',
|
||||
createdAt: '2025-12-11T10:30:59.000Z',
|
||||
updatedAt: '2025-12-12T11:16:53.385Z',
|
||||
status: 'active',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should keep Date objects in output (v1.0 - legacy behavior)', async () => {
|
||||
// ARRANGE
|
||||
const testDate = new Date('2025-12-11T10:30:59.000Z');
|
||||
const testUpdatedDate = new Date('2025-12-12T11:16:53.385Z');
|
||||
filters = [];
|
||||
mockExecuteFunctions.getNodeParameter = jest.fn().mockImplementation((field) => {
|
||||
switch (field) {
|
||||
case DATA_TABLE_ID_FIELD:
|
||||
return dataTableId;
|
||||
case 'filters.conditions':
|
||||
return filters;
|
||||
case 'matchType':
|
||||
return ANY_CONDITION;
|
||||
case 'returnAll':
|
||||
return true;
|
||||
}
|
||||
});
|
||||
mockExecuteFunctions.getNode = jest.fn().mockReturnValue({ ...node, typeVersion: 1 });
|
||||
getManyRowsAndCount.mockReturnValue({
|
||||
data: [
|
||||
{
|
||||
id: 1,
|
||||
completedDate: testDate,
|
||||
createdAt: testDate,
|
||||
updatedAt: testUpdatedDate,
|
||||
status: 'active',
|
||||
},
|
||||
],
|
||||
count: 1,
|
||||
});
|
||||
|
||||
// ACT
|
||||
const result = await executeSelectMany(mockExecuteFunctions, 0, dataTableProxy);
|
||||
|
||||
// ASSERT
|
||||
expect(result).toEqual([
|
||||
{
|
||||
json: {
|
||||
id: 1,
|
||||
completedDate: testDate,
|
||||
createdAt: testDate,
|
||||
updatedAt: testUpdatedDate,
|
||||
status: 'active',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sorting', () => {
|
||||
it('should pass sortBy parameter to getManyRowsAndCount with ASC direction', async () => {
|
||||
// ARRANGE
|
||||
filters = [];
|
||||
const sortBy: [string, 'ASC' | 'DESC'] = ['name', 'ASC'];
|
||||
getManyRowsAndCount.mockReturnValue({
|
||||
data: [
|
||||
{ id: 1, name: 'Alice' },
|
||||
{ id: 2, name: 'Bob' },
|
||||
],
|
||||
count: 2,
|
||||
});
|
||||
|
||||
// ACT
|
||||
await executeSelectMany(mockExecuteFunctions, 0, dataTableProxy, false, undefined, sortBy);
|
||||
|
||||
// ASSERT
|
||||
expect(getManyRowsAndCount).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sortBy: ['name', 'ASC'],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should pass sortBy parameter to getManyRowsAndCount with DESC direction', async () => {
|
||||
// ARRANGE
|
||||
filters = [];
|
||||
const sortBy: [string, 'ASC' | 'DESC'] = ['id', 'DESC'];
|
||||
getManyRowsAndCount.mockReturnValue({
|
||||
data: [
|
||||
{ id: 3, name: 'Charlie' },
|
||||
{ id: 2, name: 'Bob' },
|
||||
{ id: 1, name: 'Alice' },
|
||||
],
|
||||
count: 3,
|
||||
});
|
||||
|
||||
// ACT
|
||||
await executeSelectMany(mockExecuteFunctions, 0, dataTableProxy, false, undefined, sortBy);
|
||||
|
||||
// ASSERT
|
||||
expect(getManyRowsAndCount).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sortBy: ['id', 'DESC'],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should work with undefined sortBy', async () => {
|
||||
// ARRANGE
|
||||
filters = [];
|
||||
getManyRowsAndCount.mockReturnValue({
|
||||
data: [{ id: 1, name: 'Alice' }],
|
||||
count: 1,
|
||||
});
|
||||
|
||||
// ACT
|
||||
await executeSelectMany(
|
||||
mockExecuteFunctions,
|
||||
0,
|
||||
dataTableProxy,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
|
||||
// ASSERT
|
||||
expect(getManyRowsAndCount).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sortBy: undefined,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should combine sortBy with filter conditions', async () => {
|
||||
// ARRANGE
|
||||
filters = [{ condition: 'eq', keyName: 'status', keyValue: 'active' }];
|
||||
const sortBy: [string, 'ASC' | 'DESC'] = ['name', 'ASC'];
|
||||
getManyRowsAndCount.mockReturnValue({
|
||||
data: [
|
||||
{ id: 1, name: 'Alice', status: 'active' },
|
||||
{ id: 2, name: 'Bob', status: 'active' },
|
||||
],
|
||||
count: 2,
|
||||
});
|
||||
|
||||
// ACT
|
||||
await executeSelectMany(mockExecuteFunctions, 0, dataTableProxy, false, undefined, sortBy);
|
||||
|
||||
// ASSERT
|
||||
expect(getManyRowsAndCount).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sortBy: ['name', 'ASC'],
|
||||
filter: expect.objectContaining({
|
||||
type: 'or',
|
||||
filters: [
|
||||
{
|
||||
columnName: 'status',
|
||||
condition: 'eq',
|
||||
value: 'active',
|
||||
},
|
||||
],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should maintain sortBy across paginated requests', async () => {
|
||||
// ARRANGE
|
||||
filters = [];
|
||||
const sortBy: [string, 'ASC' | 'DESC'] = ['id', 'ASC'];
|
||||
getManyRowsAndCount.mockReturnValueOnce({
|
||||
data: Array.from({ length: 1000 }, (_, k) => ({ id: k })),
|
||||
count: 1500,
|
||||
});
|
||||
getManyRowsAndCount.mockReturnValueOnce({
|
||||
data: Array.from({ length: 500 }, (_, k) => ({ id: k + 1000 })),
|
||||
count: 1500,
|
||||
});
|
||||
|
||||
// ACT
|
||||
await executeSelectMany(mockExecuteFunctions, 0, dataTableProxy, false, undefined, sortBy);
|
||||
|
||||
// ASSERT
|
||||
expect(getManyRowsAndCount).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({
|
||||
sortBy: ['id', 'ASC'],
|
||||
}),
|
||||
);
|
||||
expect(getManyRowsAndCount).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
expect.objectContaining({
|
||||
sortBy: ['id', 'ASC'],
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSelectFilter', () => {
|
||||
it('should validate filter conditions against table schema', async () => {
|
||||
// ARRANGE
|
||||
filters = [
|
||||
{ condition: 'eq', keyName: 'name', keyValue: 'John' }, // Valid column
|
||||
{ condition: 'eq', keyName: 'invalid_column', keyValue: 'test' }, // Invalid column
|
||||
];
|
||||
|
||||
// ACT & ASSERT
|
||||
await expect(getSelectFilter(mockExecuteFunctions, 0)).rejects.toEqual(
|
||||
new NodeOperationError(
|
||||
node,
|
||||
'Filter validation failed: Column(s) "invalid_column" do not exist in the selected table. ' +
|
||||
'This often happens when switching between tables with different schemas. ' +
|
||||
'Please update your filter conditions.',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should allow system columns in filter conditions', async () => {
|
||||
// ARRANGE
|
||||
filters = [
|
||||
{ condition: 'eq', keyName: 'id', keyValue: 1 }, // System column
|
||||
{ condition: 'neq', keyName: 'createdAt', keyValue: null }, // System column
|
||||
];
|
||||
|
||||
// ACT
|
||||
const result = await getSelectFilter(mockExecuteFunctions, 0);
|
||||
|
||||
// ASSERT
|
||||
expect(result).toBeDefined();
|
||||
expect(result.filters).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should allow combination of system and custom columns', async () => {
|
||||
// ARRANGE
|
||||
filters = [
|
||||
{ condition: 'eq', keyName: 'id', keyValue: 1 }, // System column
|
||||
{ condition: 'eq', keyName: 'name', keyValue: 'John' }, // Custom column
|
||||
];
|
||||
|
||||
// ACT
|
||||
const result = await getSelectFilter(mockExecuteFunctions, 0);
|
||||
|
||||
// ASSERT
|
||||
expect(result).toBeDefined();
|
||||
expect(result.filters).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should pass validation when no filters are provided', async () => {
|
||||
// ARRANGE
|
||||
filters = [];
|
||||
|
||||
// ACT
|
||||
const result = await getSelectFilter(mockExecuteFunctions, 0);
|
||||
|
||||
// ASSERT
|
||||
expect(result).toBeDefined();
|
||||
expect(result.filters).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should report multiple invalid columns in error message', async () => {
|
||||
// ARRANGE
|
||||
filters = [
|
||||
{ condition: 'eq', keyName: 'invalid1', keyValue: 'test1' },
|
||||
{ condition: 'eq', keyName: 'invalid2', keyValue: 'test2' },
|
||||
];
|
||||
|
||||
// ACT & ASSERT
|
||||
await expect(getSelectFilter(mockExecuteFunctions, 0)).rejects.toEqual(
|
||||
new NodeOperationError(
|
||||
node,
|
||||
'Filter validation failed: Column(s) "invalid1, invalid2" do not exist in the selected table. ' +
|
||||
'This often happens when switching between tables with different schemas. ' +
|
||||
'Please update your filter conditions.',
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user