mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-29 19:10:43 +08:00
fix(MySql Node): Fix table name filtering (#28911)
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
/* eslint-disable n8n-nodes-base/node-param-display-name-miscased */
|
||||
import type { ILoadOptionsFunctions } from 'n8n-workflow';
|
||||
|
||||
import { searchTables } from '../../v2/methods/listSearch';
|
||||
|
||||
const mockRelease = jest.fn();
|
||||
const mockEnd = jest.fn().mockResolvedValue(undefined);
|
||||
|
||||
const createMockPool = (rows: object[]) => {
|
||||
const mockQuery = jest.fn().mockResolvedValue([rows]);
|
||||
const mockFormat = jest
|
||||
.fn()
|
||||
.mockImplementation((query: string, values: unknown[]) =>
|
||||
values.reduce<string>((q, v) => q.replace('?', String(v)), query),
|
||||
);
|
||||
const mockConnection = {
|
||||
format: mockFormat,
|
||||
query: mockQuery,
|
||||
release: mockRelease,
|
||||
};
|
||||
return {
|
||||
pool: { getConnection: jest.fn().mockResolvedValue(mockConnection), end: mockEnd },
|
||||
mockFormat,
|
||||
mockQuery,
|
||||
};
|
||||
};
|
||||
|
||||
const mockLoadOptionsFunctions = (database = 'test_db'): ILoadOptionsFunctions =>
|
||||
({
|
||||
getCredentials: jest.fn().mockResolvedValue({ database }),
|
||||
getNodeParameter: jest.fn().mockReturnValue({}),
|
||||
}) as unknown as ILoadOptionsFunctions;
|
||||
|
||||
jest.mock('../../v2/transport', () => ({ createPool: jest.fn() }));
|
||||
|
||||
import { createPool } from '../../v2/transport';
|
||||
|
||||
describe('MySQL v2 / listSearch / searchTables', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockEnd.mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
it('should return all tables when no filter is provided', async () => {
|
||||
const rows = [{ TABLE_NAME: 'users' }, { TABLE_NAME: 'orders' }];
|
||||
const { pool, mockFormat } = createMockPool(rows);
|
||||
(createPool as jest.Mock).mockResolvedValue(pool);
|
||||
|
||||
const result = await searchTables.call(mockLoadOptionsFunctions());
|
||||
|
||||
expect(result).toEqual({
|
||||
results: [
|
||||
{ name: 'users', value: 'users' },
|
||||
{ name: 'orders', value: 'orders' },
|
||||
],
|
||||
});
|
||||
|
||||
const [sql] = mockFormat.mock.calls[0];
|
||||
expect(sql).not.toContain('LIKE');
|
||||
});
|
||||
|
||||
it('should filter tables by name when filter is provided', async () => {
|
||||
const rows = [{ TABLE_NAME: 'users' }];
|
||||
const { pool, mockFormat } = createMockPool(rows);
|
||||
(createPool as jest.Mock).mockResolvedValue(pool);
|
||||
|
||||
const result = await searchTables.call(mockLoadOptionsFunctions(), 'user');
|
||||
|
||||
expect(result).toEqual({
|
||||
results: [{ name: 'users', value: 'users' }],
|
||||
});
|
||||
|
||||
const [sql, values] = mockFormat.mock.calls[0];
|
||||
expect(sql).toContain('LIKE ?');
|
||||
expect(values).toContain('%user%');
|
||||
});
|
||||
|
||||
it('should return empty results when no tables match filter', async () => {
|
||||
const { pool } = createMockPool([]);
|
||||
(createPool as jest.Mock).mockResolvedValue(pool);
|
||||
|
||||
const result = await searchTables.call(mockLoadOptionsFunctions(), 'nonexistent');
|
||||
|
||||
expect(result).toEqual({ results: [] });
|
||||
});
|
||||
|
||||
it('should always close the pool', async () => {
|
||||
const { pool } = createMockPool([]);
|
||||
(createPool as jest.Mock).mockResolvedValue(pool);
|
||||
|
||||
await searchTables.call(mockLoadOptionsFunctions());
|
||||
|
||||
expect(mockEnd).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should close the pool even when query throws', async () => {
|
||||
const mockConnection = {
|
||||
format: (_q: string, v: unknown[]) => v,
|
||||
query: jest.fn().mockRejectedValue(new Error('DB error')),
|
||||
release: mockRelease,
|
||||
};
|
||||
const pool = { getConnection: jest.fn().mockResolvedValue(mockConnection), end: mockEnd };
|
||||
(createPool as jest.Mock).mockResolvedValue(pool);
|
||||
|
||||
await expect(searchTables.call(mockLoadOptionsFunctions())).rejects.toThrow('DB error');
|
||||
expect(mockEnd).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -3,7 +3,10 @@ import type { IDataObject, ILoadOptionsFunctions, INodeListSearchResult } from '
|
||||
import type { MysqlNodeCredentials } from '../helpers/interfaces';
|
||||
import { createPool } from '../transport';
|
||||
|
||||
export async function searchTables(this: ILoadOptionsFunctions): Promise<INodeListSearchResult> {
|
||||
export async function searchTables(
|
||||
this: ILoadOptionsFunctions,
|
||||
filter?: string,
|
||||
): Promise<INodeListSearchResult> {
|
||||
const credentials = await this.getCredentials<MysqlNodeCredentials>('mySql');
|
||||
|
||||
const nodeOptions = this.getNodeParameter('options', 0) as IDataObject;
|
||||
@@ -13,8 +16,13 @@ export async function searchTables(this: ILoadOptionsFunctions): Promise<INodeLi
|
||||
try {
|
||||
const connection = await pool.getConnection();
|
||||
|
||||
const query = 'SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = ?';
|
||||
const values = [credentials.database];
|
||||
let query = 'SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = ?';
|
||||
const values: string[] = [credentials.database];
|
||||
|
||||
if (filter) {
|
||||
query += ' AND TABLE_NAME LIKE ?';
|
||||
values.push(`%${filter}%`);
|
||||
}
|
||||
|
||||
const formatedQuery = connection.format(query, values);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user