Skip to main content
Glama
cesarvarela

PostgreSQL MCP Server

by cesarvarela
getSchema.test.ts21.5 kB
import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { getSchema, getSchemaSchema } from '../../../tools/getSchema'; import { cleanTestData, getTestPool, insertTestData } from '../../setup/test-setup'; import { executeTestQuery, expectMcpError, expectValidMcpResponse, extractJsonFromMcpResponse, createTestTable, dropTestTable } from '../../setup/test-helpers'; describe('getSchema Tool', () => { beforeEach(async () => { await cleanTestData(); await insertTestData(); // Ensure we have data and table structure }); describe('Schema Validation', () => { it('should validate minimal valid parameters', () => { const params = {}; const result = getSchemaSchema.parse(params); expect(result.schema_name).toBe('public'); // default expect(result.include_columns).toBe(true); // default expect(result.include_constraints).toBe(false); // default expect(result.table_pattern).toBeUndefined(); // optional }); it('should validate complete parameters', () => { const params = { schema_name: 'public', table_pattern: 'user%', include_columns: true, include_constraints: true }; const result = getSchemaSchema.parse(params); expect(result).toEqual(params); }); it('should apply default values correctly', () => { const params = { schema_name: 'test' }; const result = getSchemaSchema.parse(params); expect(result.schema_name).toBe('test'); expect(result.include_columns).toBe(true); expect(result.include_constraints).toBe(false); }); }); describe('Basic Schema Discovery', () => { it('should get schema information successfully', async () => { const response = await getSchema({}); expectValidMcpResponse(response); const data = extractJsonFromMcpResponse(response); expect(data.schema).toBe('public'); expect(typeof data.table_count).toBe('number'); expect(data.table_count).toBeGreaterThan(0); expect(Array.isArray(data.tables)).toBe(true); expect(data.tables.length).toBe(data.table_count); expect(data.generated_at).toBeDefined(); }); it('should list available tables', async () => { const response = await getSchema({ schema_name: 'public' }); expectValidMcpResponse(response); const data = extractJsonFromMcpResponse(response); expect(data.tables.length).toBeGreaterThan(0); // Should include the users table const usersTable = data.tables.find((table: any) => table.table_name === 'users'); expect(usersTable).toBeDefined(); expect(usersTable.table_schema).toBe('public'); expect(usersTable.table_type).toBe('BASE TABLE'); }); it('should include table types', async () => { const response = await getSchema({}); expectValidMcpResponse(response); const data = extractJsonFromMcpResponse(response); data.tables.forEach((table: any) => { expect(table.table_name).toBeDefined(); expect(table.table_schema).toBe('public'); expect(table.table_type).toMatch(/^(BASE TABLE|VIEW|MATERIALIZED VIEW)$/); }); }); }); describe('Table Pattern Filtering', () => { it('should filter tables by pattern', async () => { const response = await getSchema({ table_pattern: 'user%' }); expectValidMcpResponse(response); const data = extractJsonFromMcpResponse(response); // All returned tables should match the pattern data.tables.forEach((table: any) => { expect(table.table_name).toMatch(/^user/); }); // Should include users table const usersTable = data.tables.find((table: any) => table.table_name === 'users'); expect(usersTable).toBeDefined(); }); it('should handle exact table name pattern', async () => { const response = await getSchema({ table_pattern: 'users' }); expectValidMcpResponse(response); const data = extractJsonFromMcpResponse(response); expect(data.table_count).toBe(1); expect(data.tables[0].table_name).toBe('users'); }); it('should return empty result for non-matching pattern', async () => { const response = await getSchema({ table_pattern: 'nonexistent_%' }); expectValidMcpResponse(response); const data = extractJsonFromMcpResponse(response); expect(data.table_count).toBe(0); expect(data.tables).toEqual([]); }); it('should handle wildcard patterns', async () => { const response = await getSchema({ table_pattern: '%' }); expectValidMcpResponse(response); const data = extractJsonFromMcpResponse(response); // Should return all tables (same as no pattern) expect(data.table_count).toBeGreaterThan(0); }); }); describe('Column Information', () => { it('should include column information by default', async () => { const response = await getSchema({ table_pattern: 'users' }); expectValidMcpResponse(response); const data = extractJsonFromMcpResponse(response); const usersTable = data.tables[0]; expect(usersTable.columns).toBeDefined(); expect(Array.isArray(usersTable.columns)).toBe(true); expect(usersTable.columns.length).toBeGreaterThan(0); // Check for expected columns const columnNames = usersTable.columns.map((col: any) => col.column_name); expect(columnNames).toContain('id'); expect(columnNames).toContain('email'); expect(columnNames).toContain('name'); expect(columnNames).toContain('age'); expect(columnNames).toContain('is_active'); }); it('should exclude column information when requested', async () => { const response = await getSchema({ table_pattern: 'users', include_columns: false }); expectValidMcpResponse(response); const data = extractJsonFromMcpResponse(response); const usersTable = data.tables[0]; expect(usersTable.columns).toEqual([]); }); it('should provide detailed column information', async () => { const response = await getSchema({ table_pattern: 'users', include_columns: true }); expectValidMcpResponse(response); const data = extractJsonFromMcpResponse(response); const usersTable = data.tables[0]; usersTable.columns.forEach((column: any) => { expect(column.column_name).toBeDefined(); expect(column.data_type).toBeDefined(); expect(column.is_nullable).toMatch(/^(YES|NO)$/); // Optional fields if (column.character_maximum_length !== null) { expect(typeof column.character_maximum_length).toBe('number'); } if (column.numeric_precision !== null) { expect(typeof column.numeric_precision).toBe('number'); } if (column.numeric_scale !== null) { expect(typeof column.numeric_scale).toBe('number'); } }); // Verify specific column details const idColumn = usersTable.columns.find((col: any) => col.column_name === 'id'); expect(idColumn).toBeDefined(); expect(idColumn.data_type).toBe('integer'); expect(idColumn.is_nullable).toBe('NO'); const emailColumn = usersTable.columns.find((col: any) => col.column_name === 'email'); expect(emailColumn).toBeDefined(); expect(emailColumn.data_type).toBe('character varying'); expect(emailColumn.is_nullable).toBe('NO'); }); }); describe('Constraint Information', () => { it('should exclude constraint information by default', async () => { const response = await getSchema({ table_pattern: 'users' }); expectValidMcpResponse(response); const data = extractJsonFromMcpResponse(response); const usersTable = data.tables[0]; expect(usersTable.constraints).toEqual([]); }); it('should include constraint information when requested', async () => { const response = await getSchema({ table_pattern: 'users', include_constraints: true }); expectValidMcpResponse(response); const data = extractJsonFromMcpResponse(response); const usersTable = data.tables[0]; expect(usersTable.constraints).toBeDefined(); expect(Array.isArray(usersTable.constraints)).toBe(true); expect(usersTable.constraints.length).toBeGreaterThan(0); // Should have at least primary key constraint const constraintTypes = usersTable.constraints.map((c: any) => c.constraint_type); expect(constraintTypes).toContain('PRIMARY KEY'); }); it('should provide detailed constraint information', async () => { const response = await getSchema({ table_pattern: 'users', include_constraints: true }); expectValidMcpResponse(response); const data = extractJsonFromMcpResponse(response); const usersTable = data.tables[0]; usersTable.constraints.forEach((constraint: any) => { expect(constraint.constraint_name).toBeDefined(); expect(constraint.constraint_type).toBeDefined(); expect(constraint.column_name).toBeDefined(); // Foreign key constraints have additional fields if (constraint.constraint_type === 'FOREIGN KEY') { expect(constraint.foreign_table_name).toBeDefined(); expect(constraint.foreign_column_name).toBeDefined(); } }); }); }); describe('Multiple Tables', () => { beforeEach(async () => { // Create additional test tables await createTestTable('multi_products', ` id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, price DECIMAL(10,2), category_id INTEGER `); await createTestTable('multi_categories', ` id SERIAL PRIMARY KEY, name VARCHAR(255) UNIQUE NOT NULL, description TEXT `); }); afterEach(async () => { await dropTestTable('multi_products'); await dropTestTable('multi_categories'); }); it('should return multiple tables', async () => { const response = await getSchema({ table_pattern: 'multi_%' }); expectValidMcpResponse(response); const data = extractJsonFromMcpResponse(response); expect(data.table_count).toBe(2); expect(data.tables.length).toBe(2); const tableNames = data.tables.map((table: any) => table.table_name); expect(tableNames).toContain('multi_products'); expect(tableNames).toContain('multi_categories'); }); it('should include columns for all tables', async () => { const response = await getSchema({ table_pattern: 'multi_%', include_columns: true }); expectValidMcpResponse(response); const data = extractJsonFromMcpResponse(response); data.tables.forEach((table: any) => { expect(table.columns).toBeDefined(); expect(Array.isArray(table.columns)).toBe(true); expect(table.columns.length).toBeGreaterThan(0); }); const productsTable = data.tables.find((table: any) => table.table_name === 'multi_products'); const categoriesTable = data.tables.find((table: any) => table.table_name === 'multi_categories'); expect(productsTable.columns.length).toBe(4); // id, name, price, category_id expect(categoriesTable.columns.length).toBe(3); // id, name, description }); it('should include constraints for all tables', async () => { const response = await getSchema({ table_pattern: 'multi_%', include_constraints: true }); expectValidMcpResponse(response); const data = extractJsonFromMcpResponse(response); data.tables.forEach((table: any) => { expect(table.constraints).toBeDefined(); expect(Array.isArray(table.constraints)).toBe(true); }); const categoriesTable = data.tables.find((table: any) => table.table_name === 'multi_categories'); const constraintTypes = categoriesTable.constraints.map((c: any) => c.constraint_type); expect(constraintTypes).toContain('PRIMARY KEY'); expect(constraintTypes).toContain('UNIQUE'); }); }); describe('Different Schema Names', () => { it('should handle explicit public schema', async () => { const response = await getSchema({ schema_name: 'public' }); expectValidMcpResponse(response); const data = extractJsonFromMcpResponse(response); expect(data.schema).toBe('public'); expect(data.table_count).toBeGreaterThan(0); data.tables.forEach((table: any) => { expect(table.table_schema).toBe('public'); }); }); it('should handle non-existent schema', async () => { const response = await getSchema({ schema_name: 'nonexistent_schema' }); expectValidMcpResponse(response); const data = extractJsonFromMcpResponse(response); expect(data.schema).toBe('nonexistent_schema'); expect(data.table_count).toBe(0); expect(data.tables).toEqual([]); }); it('should handle information_schema', async () => { const response = await getSchema({ schema_name: 'information_schema', table_pattern: 'tables' }); expectValidMcpResponse(response); const data = extractJsonFromMcpResponse(response); expect(data.schema).toBe('information_schema'); expect(data.table_count).toBe(1); expect(data.tables[0].table_name).toBe('tables'); expect(data.tables[0].table_type).toBe('VIEW'); }); }); describe('Error Handling', () => { it('should handle invalid schema names gracefully', async () => { // This should not error, just return empty results const response = await getSchema({ schema_name: '123invalid' }); expectValidMcpResponse(response); const data = extractJsonFromMcpResponse(response); expect(data.table_count).toBe(0); expect(data.tables).toEqual([]); }); it('should handle special characters in patterns', async () => { const response = await getSchema({ table_pattern: '%_test_%' }); expectValidMcpResponse(response); const data = extractJsonFromMcpResponse(response); // Should handle the pattern without errors expect(typeof data.table_count).toBe('number'); expect(Array.isArray(data.tables)).toBe(true); }); }); describe('Views and Materialized Views', () => { beforeEach(async () => { // Create a test view await executeTestQuery(` CREATE VIEW users_summary AS SELECT id, email, name, is_active FROM users WHERE is_active = true `); }); afterEach(async () => { await executeTestQuery('DROP VIEW IF EXISTS users_summary'); }); it('should include views in schema discovery', async () => { const response = await getSchema({ table_pattern: 'users_%' }); expectValidMcpResponse(response); const data = extractJsonFromMcpResponse(response); const usersView = data.tables.find((table: any) => table.table_name === 'users_summary'); expect(usersView).toBeDefined(); expect(usersView.table_type).toBe('VIEW'); }); it('should include view columns', async () => { const response = await getSchema({ table_pattern: 'users_summary', include_columns: true }); expectValidMcpResponse(response); const data = extractJsonFromMcpResponse(response); expect(data.table_count).toBe(1); const viewTable = data.tables[0]; expect(viewTable.columns.length).toBe(4); // id, email, name, is_active const columnNames = viewTable.columns.map((col: any) => col.column_name); expect(columnNames).toEqual(['id', 'email', 'name', 'is_active']); }); }); describe('Performance', () => { it('should perform efficiently with columns', async () => { const startTime = Date.now(); const response = await getSchema({ include_columns: true }); const endTime = Date.now(); const duration = endTime - startTime; expectValidMcpResponse(response); const data = extractJsonFromMcpResponse(response); expect(data.table_count).toBeGreaterThan(0); expect(duration).toBeLessThan(3000); // Should complete within 3 seconds }); it('should be faster without columns and constraints', async () => { const startTime = Date.now(); const response = await getSchema({ include_columns: false, include_constraints: false }); const endTime = Date.now(); const duration = endTime - startTime; expectValidMcpResponse(response); expect(duration).toBeLessThan(1000); // Should be faster }); it('should handle large schemas efficiently', async () => { const response = await getSchema({ schema_name: 'information_schema', include_columns: false }); expectValidMcpResponse(response); const data = extractJsonFromMcpResponse(response); // information_schema has many tables/views expect(data.table_count).toBeGreaterThan(20); }); }); describe('Edge Cases', () => { it('should handle empty schema', async () => { const response = await getSchema({ schema_name: 'nonexistent' }); expectValidMcpResponse(response); const data = extractJsonFromMcpResponse(response); expect(data.schema).toBe('nonexistent'); expect(data.table_count).toBe(0); expect(data.tables).toEqual([]); expect(data.generated_at).toBeDefined(); }); it('should handle patterns with no matches', async () => { const response = await getSchema({ table_pattern: 'xyz_nonexistent_%' }); expectValidMcpResponse(response); const data = extractJsonFromMcpResponse(response); expect(data.table_count).toBe(0); expect(data.tables).toEqual([]); }); it('should maintain consistent response structure', async () => { const response = await getSchema({}); expectValidMcpResponse(response); const data = extractJsonFromMcpResponse(response); // Verify required fields expect(data).toHaveProperty('schema'); expect(data).toHaveProperty('table_count'); expect(data).toHaveProperty('tables'); expect(data).toHaveProperty('generated_at'); // Verify data types expect(typeof data.schema).toBe('string'); expect(typeof data.table_count).toBe('number'); expect(Array.isArray(data.tables)).toBe(true); expect(typeof data.generated_at).toBe('string'); // Verify timestamp format expect(new Date(data.generated_at).toISOString()).toBe(data.generated_at); }); }); describe('Combined Options', () => { it('should handle all options together', async () => { const response = await getSchema({ schema_name: 'public', table_pattern: 'users', include_columns: true, include_constraints: true }); expectValidMcpResponse(response); const data = extractJsonFromMcpResponse(response); expect(data.schema).toBe('public'); expect(data.table_count).toBe(1); expect(data.tables[0].table_name).toBe('users'); expect(data.tables[0].columns.length).toBeGreaterThan(0); expect(data.tables[0].constraints.length).toBeGreaterThan(0); }); it('should maintain data consistency across options', async () => { // Get basic info const basicResponse = await getSchema({ table_pattern: 'users', include_columns: false, include_constraints: false }); // Get detailed info const detailedResponse = await getSchema({ table_pattern: 'users', include_columns: true, include_constraints: true }); expectValidMcpResponse(basicResponse); expectValidMcpResponse(detailedResponse); const basicData = extractJsonFromMcpResponse(basicResponse); const detailedData = extractJsonFromMcpResponse(detailedResponse); // Basic table info should be the same expect(basicData.table_count).toBe(detailedData.table_count); expect(basicData.tables[0].table_name).toBe(detailedData.tables[0].table_name); expect(basicData.tables[0].table_schema).toBe(detailedData.tables[0].table_schema); expect(basicData.tables[0].table_type).toBe(detailedData.tables[0].table_type); // Extended info should be present in detailed response expect(basicData.tables[0].columns).toEqual([]); expect(basicData.tables[0].constraints).toEqual([]); expect(detailedData.tables[0].columns.length).toBeGreaterThan(0); expect(detailedData.tables[0].constraints.length).toBeGreaterThan(0); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/cesarvarela/postgres-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server