Files
insforge--insforge/backend/tests/unit/sql-parser.test.ts
T
wehub-resource-sync 3a28426bf4
Lint and Format Check / lint-and-format (push) Failing after 0s
Check Migrations / Check for duplicate migration numbers (push) Failing after 1s
CI Pre-merge Check / CI Pre-merge Check (push) Failing after 2m17s
chore: import upstream snapshot with attribution
2026-07-13 12:23:40 +08:00

168 lines
6.0 KiB
TypeScript

import { beforeAll, describe, it, expect } from 'vitest';
import {
checkSqlExecutionGuards,
initSqlParser,
parseSQLStatements,
} from '../../src/utils/sql-parser';
beforeAll(async () => {
await initSqlParser();
});
describe('parseSQLStatements', () => {
it('splits multiple statements by semicolon', () => {
const sql = `
SELECT * FROM users;
INSERT INTO users (name) VALUES ('John');
DELETE FROM users WHERE id = 1;
`;
const result = parseSQLStatements(sql);
expect(result).toEqual([
'SELECT * FROM users',
"INSERT INTO users (name) VALUES ('John')",
'DELETE FROM users WHERE id = 1',
]);
});
it('ignores line comments', () => {
const sql = `
-- This is a comment
SELECT * FROM users; -- Inline comment
`;
const result = parseSQLStatements(sql);
expect(result).toHaveLength(1);
expect(result[0]).toContain('SELECT * FROM users');
});
it('ignores block comments', () => {
const sql = `
/* Block comment */
SELECT * FROM users;
/* Another comment */
`;
const result = parseSQLStatements(sql);
expect(result).toHaveLength(1);
expect(result[0]).toContain('SELECT * FROM users');
});
it('handles semicolons inside string literals', () => {
const sql = `INSERT INTO messages (text) VALUES ('Hello; World')`;
const result = parseSQLStatements(sql);
expect(result).toEqual([`INSERT INTO messages (text) VALUES ('Hello; World')`]);
});
it('throws error on empty input', () => {
expect(() => parseSQLStatements('')).toThrow();
});
it('returns empty array for comments-only SQL', () => {
const sql = `
-- Only comment
/* Another comment */
`;
const result = parseSQLStatements(sql);
expect(result).toEqual([]);
});
it('trims statements and removes empty results', () => {
const sql = `
SELECT * FROM users;
-- comment
INSERT INTO users (id) VALUES (1);
`;
const result = parseSQLStatements(sql);
expect(result.length).toBeGreaterThan(0);
expect(result[0]).toContain('SELECT * FROM users');
expect(result[result.length - 1] || result[0]).toContain('INSERT INTO users');
});
});
describe('SQL execution guards', () => {
it('blocks role and session authorization changes', () => {
expect(checkSqlExecutionGuards('SET ROLE postgres')).not.toBeNull();
expect(checkSqlExecutionGuards('SET LOCAL ROLE postgres')).not.toBeNull();
expect(checkSqlExecutionGuards('RESET ROLE')).not.toBeNull();
expect(checkSqlExecutionGuards('SET SESSION AUTHORIZATION postgres')).not.toBeNull();
expect(checkSqlExecutionGuards('RESET SESSION AUTHORIZATION')).not.toBeNull();
expect(checkSqlExecutionGuards("SELECT set_config('role', 'postgres', false)")).not.toBeNull();
expect(
checkSqlExecutionGuards("SELECT set_config('role'::text, 'postgres', false)")
).not.toBeNull();
expect(checkSqlExecutionGuards("SELECT set_config($1, 'postgres', false)")).not.toBeNull();
expect(
checkSqlExecutionGuards("SELECT set_config('ro' || 'le', 'postgres', false)")
).not.toBeNull();
expect(
checkSqlExecutionGuards("SELECT pg_catalog.set_config('role', 'postgres', false)")
).not.toBeNull();
expect(
checkSqlExecutionGuards("SELECT pg_catalog.\"set_config\"('role', 'postgres', false)")
).not.toBeNull();
expect(checkSqlExecutionGuards("SELECT set_config('app.safe', 'value', false)")).not.toBeNull();
});
it('blocks role management statements but allows object grants', () => {
expect(checkSqlExecutionGuards('CREATE ROLE app_owner')).not.toBeNull();
expect(
checkSqlExecutionGuards('ALTER ROLE project_admin SET search_path TO public')
).not.toBeNull();
expect(checkSqlExecutionGuards('DROP ROLE app_owner')).not.toBeNull();
expect(checkSqlExecutionGuards('GRANT authenticated TO project_admin')).not.toBeNull();
expect(checkSqlExecutionGuards('GRANT SELECT ON public.todos TO authenticated')).toBeNull();
});
it('blocks transaction control statements', () => {
expect(checkSqlExecutionGuards('BEGIN')).not.toBeNull();
expect(checkSqlExecutionGuards('COMMIT')).not.toBeNull();
expect(checkSqlExecutionGuards('ROLLBACK')).not.toBeNull();
expect(checkSqlExecutionGuards('SELECT 1')).toBeNull();
});
it('blocks search_path changes without matching comments or strings', () => {
expect(checkSqlExecutionGuards('SET search_path TO public')).not.toBeNull();
expect(
checkSqlExecutionGuards("SELECT set_config('search_path', 'public', false)")
).not.toBeNull();
expect(
checkSqlExecutionGuards("SELECT set_config('search_path'::text, 'public', false)")
).not.toBeNull();
expect(checkSqlExecutionGuards("SELECT 'SET search_path TO public'")).toBeNull();
expect(checkSqlExecutionGuards('-- SET search_path TO public\nSELECT 1')).toBeNull();
});
it('blocks reset-all and statement timeout changes', () => {
expect(checkSqlExecutionGuards('RESET ALL')).not.toBeNull();
expect(checkSqlExecutionGuards('SET statement_timeout = 0')).not.toBeNull();
expect(checkSqlExecutionGuards('RESET statement_timeout')).not.toBeNull();
expect(
checkSqlExecutionGuards("SELECT set_config('statement_timeout', '0', false)")
).not.toBeNull();
});
it('blocks set_config inside DO blocks without blocking ordinary DO blocks', () => {
expect(
checkSqlExecutionGuards(`
DO $$
BEGIN
PERFORM set_config('role', 'postgres', false);
END $$;
`)
).not.toBeNull();
expect(
checkSqlExecutionGuards(`
DO $$
BEGIN
RAISE NOTICE 'hello';
END $$;
`)
).toBeNull();
});
it('blocks database-level operations without matching comments or strings', () => {
expect(checkSqlExecutionGuards('DROP DATABASE customer_project')).not.toBeNull();
expect(checkSqlExecutionGuards("SELECT 'DROP DATABASE customer_project'")).toBeNull();
expect(checkSqlExecutionGuards('-- DROP DATABASE customer_project\nSELECT 1')).toBeNull();
});
});