chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user