import { describe, test, expect, vi, beforeEach } from 'vitest'; import { DatabaseTableService } from '../../src/services/database/database-table.service'; // Create a single mock query function to track all database calls const sharedMockQuery = vi.fn(); // Mock DatabaseManager vi.mock('../../src/infra/database/database.manager', () => { return { DatabaseManager: { getInstance: vi.fn(() => ({ getPool: vi.fn(() => ({ connect: vi.fn().mockResolvedValue({ query: sharedMockQuery, release: vi.fn(), }), query: sharedMockQuery, })), })), }, }; }); describe('DatabaseTableService - SQL Injection Verification (Fixed)', () => { let service: DatabaseTableService; beforeEach(() => { vi.clearAllMocks(); service = DatabaseTableService.getInstance(); // Setup sequential responses to satisfy code path logic sharedMockQuery .mockResolvedValueOnce({ rows: [{ column_name: 'id', data_type: 'uuid', udt_name: 'uuid', is_nullable: 'NO' }], }) // 1. Columns schema .mockResolvedValueOnce({ rows: [] }) // 2. Fkey constraints (pooled query) .mockResolvedValueOnce({ rows: [{ column_name: 'id' }] }) // 3. Primary keys .mockResolvedValueOnce({ rows: [] }) // 4. Unique columns .mockResolvedValueOnce({ rows: [{ row_count: 0 }] }); // 5. COUNT(*) query }); test('getTableSchema correctly escapes table names to prevent SQL injection', async () => { // Malicious table name keeps SQL metacharacters but avoids quotes so it reaches the SQL builder. const maliciousTableName = 'users; DROP TABLE secrets; --'; try { // Execute the method await service.getTableSchema('public', maliciousTableName); } catch { // Ignore logical errors, we only care about the captured SQL } // Find the binary query that contains our malicious payload (the COUNT query) const injectionQueryCall = sharedMockQuery.mock.calls.find( (call) => typeof call[0] === 'string' && call[0].includes('COUNT(*)') ); expect(injectionQueryCall).toBeDefined(); const capturedQuery = injectionQueryCall![0]; console.log('Captured SQL Query:', capturedQuery); // Assert that the table name stays inside a schema-qualified quoted identifier. expect(capturedQuery).toContain(`FROM "public"."users; DROP TABLE secrets; --"`); // This proves that the DROP TABLE command is now safely inside the identifier // string and will NOT be executed as a separate SQL command. expect(capturedQuery).not.toMatch(/FROM "public"\."[^"]*"; DROP TABLE/); }); });