From 07883c08e0824d52354ae83b1fbe31b8a7ebeb61 Mon Sep 17 00:00:00 2001 From: wangxiangzhu88-droid <258966752+wangxiangzhu88-droid@users.noreply.github.com> Date: Sun, 21 Jun 2026 14:39:05 +0800 Subject: [PATCH] test: cover sql macros plugin --- plugins/sql-macros/index.test.ts | 90 ++++++++++++++++++++++++++++++++ plugins/sql-macros/index.ts | 3 +- 2 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 plugins/sql-macros/index.test.ts diff --git a/plugins/sql-macros/index.test.ts b/plugins/sql-macros/index.test.ts new file mode 100644 index 0000000..8d92377 --- /dev/null +++ b/plugins/sql-macros/index.test.ts @@ -0,0 +1,90 @@ +import { describe, expect, it, vi } from 'vitest' +import { SqlMacrosPlugin } from './index' +import { DataSource } from '../../src/types' + +function createInternalDataSource(columns: string[] = []): DataSource { + return { + source: 'internal', + rpc: { + executeQuery: vi.fn().mockResolvedValue( + columns.map((column_name) => ({ + column_name, + })) + ), + }, + } as unknown as DataSource +} + +describe('SqlMacrosPlugin', () => { + it('leaves SQL unchanged when no data source is provided', async () => { + const plugin = new SqlMacrosPlugin({ preventSelectStar: true }) + const sql = 'SELECT * FROM users' + + const result = await plugin.beforeQuery({ sql, params: [1] }) + + expect(result).toEqual({ sql, params: [1] }) + }) + + it('expands $_exclude into explicit internal table columns', async () => { + const plugin = new SqlMacrosPlugin() + const dataSource = createInternalDataSource(['id', 'email', 'password']) + + const result = await plugin.beforeQuery({ + sql: 'SELECT $_exclude(password) FROM users', + dataSource, + }) + + expect(result.params).toBeUndefined() + expect(result.sql).toContain('SELECT') + expect(result.sql).toContain('id') + expect(result.sql).toContain('email') + expect(result.sql).not.toContain('password') + expect(dataSource.rpc.executeQuery).toHaveBeenCalledWith({ + sql: expect.stringContaining("FROM pragma_table_info('users')"), + }) + }) + + it('does not expand $_exclude for external data sources', async () => { + const plugin = new SqlMacrosPlugin() + const dataSource = { + source: 'external', + external: { dialect: 'postgresql' }, + rpc: { + executeQuery: vi.fn(), + }, + } as unknown as DataSource + const sql = 'SELECT $_exclude(password) FROM users' + + const result = await plugin.beforeQuery({ sql, dataSource }) + + expect(result.sql).toBe(sql) + expect(dataSource.rpc.executeQuery).not.toHaveBeenCalled() + }) + + it('rejects SELECT * for non-admin users when enabled', async () => { + const plugin = new SqlMacrosPlugin({ preventSelectStar: true }) + plugin['config'] = { role: 'user' } as any + + await expect( + plugin.beforeQuery({ + sql: 'SELECT * FROM users', + dataSource: createInternalDataSource(), + }) + ).rejects.toThrow( + 'SELECT * is not allowed. Please specify explicit columns.' + ) + }) + + it('allows SELECT * for admin users when prevention is enabled', async () => { + const plugin = new SqlMacrosPlugin({ preventSelectStar: true }) + plugin['config'] = { role: 'admin' } as any + const sql = 'SELECT * FROM users' + + const result = await plugin.beforeQuery({ + sql, + dataSource: createInternalDataSource(), + }) + + expect(result.sql).toBe(sql) + }) +}) diff --git a/plugins/sql-macros/index.ts b/plugins/sql-macros/index.ts index 9dfbb85..8a90de7 100644 --- a/plugins/sql-macros/index.ts +++ b/plugins/sql-macros/index.ts @@ -59,7 +59,8 @@ export class SqlMacrosPlugin extends StarbasePlugin { private checkSelectStar(sql: string, params?: unknown[]): string { try { - const ast = parser.astify(sql)[0] + const parsed = parser.astify(sql) + const ast = Array.isArray(parsed) ? parsed[0] : parsed // Only check SELECT statements if (ast.type === 'select') {