Skip to main content
Glama
response-formatter.test.js17.3 kB
import { describe, it, expect, beforeEach } from 'vitest'; import { ResponseFormatter } from '../../lib/utils/response-formatter.js'; describe('ResponseFormatter', () => { let formatter; beforeEach(() => { // Reset environment variables before each test delete process.env.SQL_SERVER_RESPONSE_FORMAT; delete process.env.NODE_ENV; // Create a fresh formatter instance formatter = new ResponseFormatter(); }); describe('Constructor and Configuration', () => { it('should initialize with default configuration', () => { const config = formatter.getConfig(); expect(config.format).toBe('structured'); expect(config.includeMetadata).toBe(true); expect(config.includePerformance).toBe(false); expect(config.maxResponseSize).toBe(1000000); }); it('should use environment variable for format', () => { process.env.SQL_SERVER_RESPONSE_FORMAT = 'pretty-json'; const envFormatter = new ResponseFormatter(); expect(envFormatter.getConfig().format).toBe('pretty-json'); }); it('should override defaults with custom config', () => { const customFormatter = new ResponseFormatter({ format: 'json', includeMetadata: false, includePerformance: true, maxResponseSize: 500000 }); const config = customFormatter.getConfig(); expect(config.format).toBe('json'); expect(config.includeMetadata).toBe(false); expect(config.includePerformance).toBe(true); expect(config.maxResponseSize).toBe(500000); }); it('should update configuration', () => { formatter.updateConfig({ format: 'pretty-json', includePerformance: true }); const config = formatter.getConfig(); expect(config.format).toBe('pretty-json'); expect(config.includePerformance).toBe(true); expect(config.includeMetadata).toBe(true); // Should preserve existing values }); }); describe('formatQueryResult', () => { it('should format basic query result', () => { const mockResult = { recordset: [ { id: 1, name: 'John' }, { id: 2, name: 'Jane' } ], rowsAffected: [2] }; const context = { startTime: Date.now() - 100, query: 'SELECT * FROM users', database: 'testdb', toolName: 'execute_query' }; const response = formatter.formatQueryResult(mockResult, context); expect(response.content).toHaveLength(1); expect(response.content[0].type).toBe('text'); const data = JSON.parse(response.content[0].text); expect(data.success).toBe(true); expect(data.recordset).toEqual(mockResult.recordset); expect(data.rowsAffected).toEqual(mockResult.rowsAffected); expect(data.metadata.query).toBe(context.query); expect(data.metadata.database).toBe(context.database); expect(data.metadata.tool).toBe(context.toolName); }); it('should include performance metrics when enabled', () => { formatter.updateConfig({ includePerformance: true }); const mockResult = { recordset: [{ id: 1, name: 'test' }] }; const context = { startTime: Date.now() - 150 }; const response = formatter.formatQueryResult(mockResult, context); const data = JSON.parse(response.content[0].text); expect(data.performance).toBeDefined(); expect(data.performance.duration).toBeGreaterThanOrEqual(0); expect(data.performance.recordCount).toBe(1); }); it('should include security info when provided', () => { const mockResult = { recordset: [] }; const context = { securityConfig: { readOnlyMode: true, allowDestructiveOperations: false, allowSchemaChanges: false } }; const response = formatter.formatQueryResult(mockResult, context); const data = JSON.parse(response.content[0].text); expect(data.security).toBeDefined(); expect(data.security.readOnlyMode).toBe(true); expect(data.security.destructiveOperationsAllowed).toBe(false); expect(data.security.schemaChangesAllowed).toBe(false); }); it('should handle empty results', () => { const mockResult = {}; const response = formatter.formatQueryResult(mockResult); const data = JSON.parse(response.content[0].text); expect(data.success).toBe(true); expect(data.recordset).toEqual([]); expect(data.rowsAffected).toEqual([]); }); }); describe('formatTableData', () => { it('should format table data with metadata', () => { const mockResult = { recordset: [ { id: 1, name: 'Alice', age: 30 }, { id: 2, name: 'Bob', age: 25 } ] }; const context = { tableName: 'users', schema: 'public', database: 'mydb', limit: 100, whereClause: 'age > 18' }; const response = formatter.formatTableData(mockResult, context); const data = JSON.parse(response.content[0].text); expect(data.success).toBe(true); expect(data.table).toBe('public.users'); expect(data.rowCount).toBe(2); expect(data.data).toEqual(mockResult.recordset); expect(data.columns).toHaveLength(3); expect(data.metadata.database).toBe('mydb'); expect(data.metadata.schema).toBe('public'); expect(data.metadata.table).toBe('users'); expect(data.metadata.limit).toBe(100); expect(data.metadata.whereClause).toBe('age > 18'); }); it('should default schema to dbo', () => { const mockResult = { recordset: [] }; const context = { tableName: 'test_table' }; const response = formatter.formatTableData(mockResult, context); const data = JSON.parse(response.content[0].text); expect(data.table).toBe('dbo.test_table'); expect(data.metadata.schema).toBe('dbo'); }); }); describe('formatCsvExport', () => { it('should format CSV export data', () => { const csvData = 'id,name,age\\n1,Alice,30\\n2,Bob,25'; const context = { tableName: 'users', schema: 'dbo', database: 'testdb', rowCount: 2, columnCount: 3 }; const response = formatter.formatCsvExport(csvData, context); const data = JSON.parse(response.content[0].text); expect(data.success).toBe(true); expect(data.table).toBe('dbo.users'); expect(data.format).toBe('csv'); expect(data.csvData).toBe(csvData); expect(data.rowCount).toBe(2); expect(data.columnCount).toBe(3); expect(data.metadata.exportSize).toBe(csvData.length); }); }); describe('formatListResult', () => { it('should format list of items', () => { const items = [ { name: 'database1', size_mb: 100 }, { name: 'database2', size_mb: 250 } ]; const context = { listType: 'databases', database: 'master' }; const response = formatter.formatListResult(items, context); const data = JSON.parse(response.content[0].text); expect(data.success).toBe(true); expect(data.items).toEqual(items); expect(data.count).toBe(2); expect(data.metadata.listType).toBe('databases'); expect(data.metadata.database).toBe('master'); }); it('should handle empty list', () => { const response = formatter.formatListResult([]); const data = JSON.parse(response.content[0].text); expect(data.success).toBe(true); expect(data.items).toEqual([]); expect(data.count).toBe(0); }); it('should handle null items', () => { const response = formatter.formatListResult(null); const data = JSON.parse(response.content[0].text); expect(data.success).toBe(true); expect(data.items).toEqual([]); expect(data.count).toBe(0); }); }); describe('formatError', () => { it('should format error response', () => { const error = new Error('Test error message'); error.code = 'TEST_ERROR'; const context = { toolName: 'test_tool', query: 'SELECT * FROM invalid_table', database: 'testdb' }; const response = formatter.formatError(error, context); const data = JSON.parse(response.content[0].text); expect(data.success).toBe(false); expect(data.error.name).toBe('Error'); expect(data.error.message).toBe('Test error message'); expect(data.error.code).toBe('TEST_ERROR'); expect(data.metadata.tool).toBe('test_tool'); expect(data.metadata.query).toBe(context.query); expect(data.metadata.database).toBe('testdb'); }); it('should include stack trace in non-production environment', () => { process.env.NODE_ENV = 'development'; const error = new Error('Test error'); error.stack = 'Error: Test error\\n at test.js:10:5'; const response = formatter.formatError(error); const data = JSON.parse(response.content[0].text); expect(data.error.stack).toBeDefined(); expect(data.error.stack).toContain('Error: Test error'); }); it('should exclude stack trace in production environment', () => { process.env.NODE_ENV = 'production'; const error = new Error('Test error'); error.stack = 'Error: Test error\\n at test.js:10:5'; const response = formatter.formatError(error); const data = JSON.parse(response.content[0].text); expect(data.error.stack).toBeUndefined(); }); it('should handle errors without code', () => { const error = new Error('Simple error'); const response = formatter.formatError(error); const data = JSON.parse(response.content[0].text); expect(data.error.code).toBe('UNKNOWN_ERROR'); }); }); describe('Response Format Options', () => { it('should format as structured JSON', () => { formatter.updateConfig({ format: 'structured' }); const mockResult = { recordset: [{ id: 1 }] }; const response = formatter.formatQueryResult(mockResult); expect(response.content[0].type).toBe('text'); expect(() => JSON.parse(response.content[0].text)).not.toThrow(); }); it('should format as compact JSON', () => { formatter.updateConfig({ format: 'json' }); const mockResult = { recordset: [{ id: 1 }] }; const response = formatter.formatQueryResult(mockResult); const text = response.content[0].text; expect(text).not.toContain('\\n '); // No indentation for compact JSON expect(() => JSON.parse(text)).not.toThrow(); }); it('should format as pretty JSON', () => { formatter.updateConfig({ format: 'pretty-json' }); const mockResult = { recordset: [{ id: 1 }] }; const response = formatter.formatQueryResult(mockResult); const text = response.content[0].text; expect(text).toContain('\n'); // Should have newlines for pretty printing (single backslash) expect(() => JSON.parse(text)).not.toThrow(); }); it('should default to structured for unknown formats', () => { formatter.updateConfig({ format: 'invalid-format' }); const mockResult = { recordset: [{ id: 1 }] }; const response = formatter.formatQueryResult(mockResult); expect(() => JSON.parse(response.content[0].text)).not.toThrow(); }); }); describe('Column Type Inference', () => { it('should infer column types correctly', () => { const recordset = [ { id: 123, name: 'John Doe', salary: 75000.5, isActive: true, birthDate: new Date('1990-01-15'), createdAt: '2023-01-15T10:30:00Z', guid: '550e8400-e29b-41d4-a716-446655440000', notes: null } ]; const columns = formatter.extractColumnInfo(recordset); expect(columns).toEqual([ { name: 'id', type: 'integer' }, { name: 'name', type: 'string' }, { name: 'salary', type: 'decimal' }, { name: 'isActive', type: 'boolean' }, { name: 'birthDate', type: 'datetime' }, { name: 'createdAt', type: 'date' }, { name: 'guid', type: 'guid' }, { name: 'notes', type: 'null' } ]); }); it('should return empty array for empty recordset', () => { expect(formatter.extractColumnInfo([])).toEqual([]); expect(formatter.extractColumnInfo(null)).toEqual([]); expect(formatter.extractColumnInfo(undefined)).toEqual([]); }); it('should handle unknown types', () => { const recordset = [{ weird: Symbol('test') }]; const columns = formatter.extractColumnInfo(recordset); expect(columns[0].type).toBe('unknown'); }); }); describe('Response Truncation', () => { it('should truncate large responses', () => { formatter.updateConfig({ maxResponseSize: 1000 }); // Create a large dataset const largeRecordset = Array.from({ length: 100 }, (_, i) => ({ id: i, data: 'x'.repeat(50), // 50 characters per record moreData: 'very long string that makes the response quite large'.repeat(10) })); const mockResult = { recordset: largeRecordset }; const response = formatter.formatQueryResult(mockResult); const data = JSON.parse(response.content[0].text); expect(data.truncated).toBe(true); expect(data.originalSize).toBeGreaterThan(1000); expect(data.maxSize).toBe(1000); expect(data.truncatedRows).toBeGreaterThan(0); expect(data.recordset.length).toBeLessThan(largeRecordset.length); }); it('should truncate CSV data when it exceeds size limits', () => { // Test that the truncation mechanism works by directly calling the truncation method const largeCsvData = 'id,name\\n' + Array.from({ length: 100 }, (_, i) => `${i},${'very_long_name'.repeat(10)}_${i}`).join( '\\n' ); const mockData = { csvData: largeCsvData, success: true, table: 'test' }; formatter.updateConfig({ maxResponseSize: 1000 }); const currentSize = JSON.stringify(mockData).length; // Only test if the CSV is actually large enough to trigger truncation if (currentSize > 1000) { const truncated = formatter.truncateResponse(mockData, currentSize); expect(truncated.truncated).toBe(true); expect(truncated.originalSize).toBe(currentSize); expect(truncated.maxSize).toBe(1000); } else { // If not large enough, just verify the method exists and doesn't crash const result = formatter.truncateResponse(mockData, currentSize); expect(result).toBeDefined(); } }); it('should not truncate small responses', () => { formatter.updateConfig({ maxResponseSize: 10000 }); const smallResult = { recordset: [{ id: 1, name: 'test' }] }; const response = formatter.formatQueryResult(smallResult); const data = JSON.parse(response.content[0].text); expect(data.truncated).toBeUndefined(); }); }); describe('Query Truncation', () => { it('should truncate long queries for metadata', () => { const longQuery = 'SELECT * FROM users WHERE ' + 'condition AND '.repeat(50) + 'id = 1'; const truncated = formatter.truncateQuery(longQuery, 100); expect(truncated).toHaveLength(103); // 100 + '...' expect(truncated.endsWith('...')).toBe(true); }); it('should not truncate short queries', () => { const shortQuery = 'SELECT * FROM users'; const truncated = formatter.truncateQuery(shortQuery, 100); expect(truncated).toBe(shortQuery); }); it('should handle null/undefined queries', () => { expect(formatter.truncateQuery(null)).toBe(null); expect(formatter.truncateQuery(undefined)).toBe(undefined); expect(formatter.truncateQuery('')).toBe(''); }); }); describe('Utility Methods', () => { it('should calculate result size', () => { const data = { test: 'value', number: 123 }; const size = formatter.calculateResultSize(data); expect(size).toBe(JSON.stringify(data).length); }); it('should handle null data in size calculation', () => { expect(formatter.calculateResultSize(null)).toBeGreaterThan(0); expect(formatter.calculateResultSize(undefined)).toBeGreaterThan(0); }); }); describe('Metadata Control', () => { it('should exclude metadata when disabled', () => { formatter.updateConfig({ includeMetadata: false }); const mockResult = { recordset: [{ id: 1 }] }; const context = { query: 'SELECT * FROM test', toolName: 'test' }; const response = formatter.formatQueryResult(mockResult, context); const data = JSON.parse(response.content[0].text); expect(data.metadata).toBeUndefined(); }); it('should exclude performance when disabled', () => { formatter.updateConfig({ includePerformance: false }); const mockResult = { recordset: [{ id: 1 }] }; const response = formatter.formatQueryResult(mockResult); const data = JSON.parse(response.content[0].text); expect(data.performance).toBeUndefined(); }); }); });

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/egarcia74/warp-sql-server-mcp'

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