/**
* postgres-mcp - Admin Tools Unit Tests
*
* Tests for PostgreSQL admin tools with focus on
* VACUUM, ANALYZE, REINDEX, and configuration operations.
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { getAdminTools } from '../admin.js';
import type { PostgresAdapter } from '../../PostgresAdapter.js';
import {
createMockPostgresAdapter,
createMockRequestContext
} from '../../../../__tests__/mocks/index.js';
describe('getAdminTools', () => {
let adapter: PostgresAdapter;
let tools: ReturnType<typeof getAdminTools>;
beforeEach(() => {
vi.clearAllMocks();
adapter = createMockPostgresAdapter() as unknown as PostgresAdapter;
tools = getAdminTools(adapter);
});
it('should return 10 admin tools', () => {
expect(tools).toHaveLength(10);
});
it('should have all expected tool names', () => {
const toolNames = tools.map(t => t.name);
expect(toolNames).toContain('pg_vacuum');
expect(toolNames).toContain('pg_vacuum_analyze');
expect(toolNames).toContain('pg_analyze');
expect(toolNames).toContain('pg_reindex');
expect(toolNames).toContain('pg_terminate_backend');
expect(toolNames).toContain('pg_cancel_backend');
expect(toolNames).toContain('pg_reload_conf');
expect(toolNames).toContain('pg_set_config');
expect(toolNames).toContain('pg_reset_stats');
expect(toolNames).toContain('pg_cluster');
});
it('should have group set to admin for all tools', () => {
for (const tool of tools) {
expect(tool.group).toBe('admin');
}
});
it('should have handler function for all tools', () => {
for (const tool of tools) {
expect(typeof tool.handler).toBe('function');
}
});
});
describe('pg_vacuum', () => {
let mockAdapter: ReturnType<typeof createMockPostgresAdapter>;
let tools: ReturnType<typeof getAdminTools>;
let mockContext: ReturnType<typeof createMockRequestContext>;
beforeEach(() => {
vi.clearAllMocks();
mockAdapter = createMockPostgresAdapter();
tools = getAdminTools(mockAdapter as unknown as PostgresAdapter);
mockContext = createMockRequestContext();
});
it('should vacuum all tables when no params', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_vacuum')!;
const result = await tool.handler({}, mockContext) as {
success: boolean;
message: string;
};
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('VACUUM ');
expect(result.success).toBe(true);
expect(result.message).toContain('completed');
});
it('should vacuum specific table', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_vacuum')!;
const result = await tool.handler({ table: 'users' }, mockContext) as {
success: boolean;
};
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('VACUUM "users"');
expect(result.success).toBe(true);
});
it('should vacuum with schema qualified table', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_vacuum')!;
await tool.handler({ table: 'users', schema: 'public' }, mockContext);
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('VACUUM "public"."users"');
});
it('should run VACUUM FULL when full=true', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_vacuum')!;
const result = await tool.handler({ full: true }, mockContext) as {
success: boolean;
message: string;
};
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('VACUUM FULL ');
expect(result.message).toContain('FULL');
});
it('should run VACUUM VERBOSE when verbose=true', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_vacuum')!;
await tool.handler({ verbose: true }, mockContext);
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('VACUUM VERBOSE ');
});
it('should accept undefined (no args) and vacuum all tables', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_vacuum')!;
const result = await tool.handler(undefined, mockContext) as {
success: boolean;
message: string;
};
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('VACUUM ');
expect(result.success).toBe(true);
});
it('should reflect analyze flag in message', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_vacuum')!;
const result = await tool.handler({ analyze: true }, mockContext) as {
success: boolean;
message: string;
};
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('VACUUM ANALYZE ');
expect(result.message).toBe('VACUUM ANALYZE completed');
});
it('should reflect both FULL and ANALYZE in message', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_vacuum')!;
const result = await tool.handler({ full: true, analyze: true }, mockContext) as {
success: boolean;
message: string;
};
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('VACUUM FULL ANALYZE ');
expect(result.message).toBe('VACUUM FULL ANALYZE completed');
});
it('should include verbose hint when verbose=true', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_vacuum')!;
const result = await tool.handler({ verbose: true }, mockContext) as {
success: boolean;
hint: string;
};
expect(result.hint).toBe('Verbose output written to PostgreSQL server logs');
});
it('should echo back table and schema in response', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_vacuum')!;
const result = await tool.handler({ table: 'users', schema: 'public' }, mockContext) as {
success: boolean;
table: string;
schema: string;
};
expect(result.table).toBe('users');
expect(result.schema).toBe('public');
});
it('should expose parameters in schema for MCP visibility', () => {
const tool = tools.find(t => t.name === 'pg_vacuum')!;
const schema = tool.inputSchema as { shape?: Record<string, unknown> };
expect(schema.shape).toBeDefined();
expect(schema.shape?.['table']).toBeDefined();
expect(schema.shape?.['schema']).toBeDefined();
expect(schema.shape?.['full']).toBeDefined();
expect(schema.shape?.['verbose']).toBeDefined();
});
});
describe('pg_vacuum_analyze', () => {
let mockAdapter: ReturnType<typeof createMockPostgresAdapter>;
let tools: ReturnType<typeof getAdminTools>;
let mockContext: ReturnType<typeof createMockRequestContext>;
beforeEach(() => {
vi.clearAllMocks();
mockAdapter = createMockPostgresAdapter();
tools = getAdminTools(mockAdapter as unknown as PostgresAdapter);
mockContext = createMockRequestContext();
});
it('should run VACUUM ANALYZE on all tables', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_vacuum_analyze')!;
const result = await tool.handler({}, mockContext) as {
success: boolean;
message: string;
};
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('VACUUM ANALYZE ');
expect(result.success).toBe(true);
expect(result.message).toBe('VACUUM ANALYZE completed');
});
it('should run VACUUM ANALYZE on specific table', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_vacuum_analyze')!;
await tool.handler({ table: 'orders' }, mockContext);
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('VACUUM ANALYZE "orders"');
});
it('should run VACUUM VERBOSE ANALYZE', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_vacuum_analyze')!;
await tool.handler({ verbose: true, table: 'users' }, mockContext);
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('VACUUM VERBOSE ANALYZE "users"');
});
it('should accept undefined (no args) and vacuum analyze all tables', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_vacuum_analyze')!;
const result = await tool.handler(undefined, mockContext) as {
success: boolean;
message: string;
};
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('VACUUM ANALYZE ');
expect(result.success).toBe(true);
});
it('should run VACUUM FULL ANALYZE when full=true', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_vacuum_analyze')!;
const result = await tool.handler({ full: true }, mockContext) as {
success: boolean;
message: string;
};
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('VACUUM FULL ANALYZE ');
expect(result.message).toBe('VACUUM FULL ANALYZE completed');
});
});
describe('pg_analyze', () => {
let mockAdapter: ReturnType<typeof createMockPostgresAdapter>;
let tools: ReturnType<typeof getAdminTools>;
let mockContext: ReturnType<typeof createMockRequestContext>;
beforeEach(() => {
vi.clearAllMocks();
mockAdapter = createMockPostgresAdapter();
tools = getAdminTools(mockAdapter as unknown as PostgresAdapter);
mockContext = createMockRequestContext();
});
it('should analyze all tables', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_analyze')!;
const result = await tool.handler({}, mockContext) as {
success: boolean;
message: string;
};
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('ANALYZE ');
expect(result.success).toBe(true);
expect(result.message).toBe('ANALYZE completed');
});
it('should analyze specific table', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_analyze')!;
await tool.handler({ table: 'products' }, mockContext);
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('ANALYZE "products"');
});
it('should analyze specific columns', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_analyze')!;
await tool.handler({ table: 'users', columns: ['email', 'created_at'] }, mockContext);
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('ANALYZE "users"("email", "created_at")');
});
it('should accept undefined (no args) and analyze all tables', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_analyze')!;
const result = await tool.handler(undefined, mockContext) as {
success: boolean;
message: string;
};
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('ANALYZE ');
expect(result.success).toBe(true);
});
it('should expose parameters in schema for MCP visibility', () => {
const tool = tools.find(t => t.name === 'pg_analyze')!;
const schema = tool.inputSchema as { shape?: Record<string, unknown> };
expect(schema.shape).toBeDefined();
expect(schema.shape?.['table']).toBeDefined();
expect(schema.shape?.['schema']).toBeDefined();
expect(schema.shape?.['columns']).toBeDefined();
});
it('should throw error when columns specified without table', async () => {
const tool = tools.find(t => t.name === 'pg_analyze')!;
await expect(tool.handler({ columns: ['email'] }, mockContext))
.rejects.toThrow('table is required when columns is specified');
});
});
describe('pg_reindex', () => {
let mockAdapter: ReturnType<typeof createMockPostgresAdapter>;
let tools: ReturnType<typeof getAdminTools>;
let mockContext: ReturnType<typeof createMockRequestContext>;
beforeEach(() => {
vi.clearAllMocks();
mockAdapter = createMockPostgresAdapter();
tools = getAdminTools(mockAdapter as unknown as PostgresAdapter);
mockContext = createMockRequestContext();
});
it('should reindex a table', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_reindex')!;
const result = await tool.handler({ target: 'table', name: 'users' }, mockContext) as {
success: boolean;
message: string;
};
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('REINDEX TABLE "users"');
expect(result.success).toBe(true);
expect(result.message).toBe('Reindexed table: users');
});
it('should reindex an index', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_reindex')!;
await tool.handler({ target: 'index', name: 'idx_users_email' }, mockContext);
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('REINDEX INDEX "idx_users_email"');
});
it('should reindex concurrently', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_reindex')!;
await tool.handler({ target: 'table', name: 'users', concurrently: true }, mockContext);
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('REINDEX TABLE CONCURRENTLY "users"');
});
it('should reindex database with auto-default to current database', async () => {
// First call returns current database name
mockAdapter.executeQuery.mockResolvedValueOnce({
rows: [{ current_database: 'testdb' }]
});
// Second call is the actual REINDEX
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_reindex')!;
const result = await tool.handler({ target: 'database' }, mockContext) as {
success: boolean;
message: string;
};
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('SELECT current_database()');
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('REINDEX DATABASE "testdb"');
expect(result.success).toBe(true);
expect(result.message).toBe('Reindexed database: testdb');
});
it('should expose parameters in schema for MCP visibility', () => {
const tool = tools.find(t => t.name === 'pg_reindex')!;
const schema = tool.inputSchema as { shape?: Record<string, unknown> };
expect(schema.shape).toBeDefined();
expect(schema.shape?.['target']).toBeDefined();
expect(schema.shape?.['name']).toBeDefined();
expect(schema.shape?.['concurrently']).toBeDefined();
});
});
describe('pg_terminate_backend', () => {
let mockAdapter: ReturnType<typeof createMockPostgresAdapter>;
let tools: ReturnType<typeof getAdminTools>;
let mockContext: ReturnType<typeof createMockRequestContext>;
beforeEach(() => {
vi.clearAllMocks();
mockAdapter = createMockPostgresAdapter();
tools = getAdminTools(mockAdapter as unknown as PostgresAdapter);
mockContext = createMockRequestContext();
});
it('should terminate a backend', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({
rows: [{ pg_terminate_backend: true }]
});
const tool = tools.find(t => t.name === 'pg_terminate_backend')!;
const result = await tool.handler({ pid: 12345 }, mockContext) as {
success: boolean;
pid: number;
message: string;
};
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('SELECT pg_terminate_backend($1)', [12345]);
expect(result.success).toBe(true);
expect(result.pid).toBe(12345);
expect(result.message).toBe('Backend terminated');
});
it('should report failure when backend cannot be terminated', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({
rows: [{ pg_terminate_backend: false }]
});
const tool = tools.find(t => t.name === 'pg_terminate_backend')!;
const result = await tool.handler({ pid: 99999 }, mockContext) as {
success: boolean;
message: string;
};
expect(result.success).toBe(false);
expect(result.message).toBe('Failed to terminate');
});
it('should expose parameters in schema for MCP visibility', () => {
const tool = tools.find(t => t.name === 'pg_terminate_backend')!;
const schema = tool.inputSchema as { shape?: Record<string, unknown> };
expect(schema.shape).toBeDefined();
expect(schema.shape?.['pid']).toBeDefined();
});
});
describe('pg_cancel_backend', () => {
let mockAdapter: ReturnType<typeof createMockPostgresAdapter>;
let tools: ReturnType<typeof getAdminTools>;
let mockContext: ReturnType<typeof createMockRequestContext>;
beforeEach(() => {
vi.clearAllMocks();
mockAdapter = createMockPostgresAdapter();
tools = getAdminTools(mockAdapter as unknown as PostgresAdapter);
mockContext = createMockRequestContext();
});
it('should cancel a backend query', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({
rows: [{ pg_cancel_backend: true }]
});
const tool = tools.find(t => t.name === 'pg_cancel_backend')!;
const result = await tool.handler({ pid: 12345 }, mockContext) as {
success: boolean;
pid: number;
message: string;
};
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('SELECT pg_cancel_backend($1)', [12345]);
expect(result.success).toBe(true);
expect(result.message).toBe('Query cancelled');
});
it('should report failure when query cannot be cancelled', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({
rows: [{ pg_cancel_backend: false }]
});
const tool = tools.find(t => t.name === 'pg_cancel_backend')!;
const result = await tool.handler({ pid: 99999 }, mockContext) as {
success: boolean;
message: string;
};
expect(result.success).toBe(false);
expect(result.message).toBe('Failed to cancel');
});
it('should expose parameters in schema for MCP visibility', () => {
const tool = tools.find(t => t.name === 'pg_cancel_backend')!;
const schema = tool.inputSchema as { shape?: Record<string, unknown> };
expect(schema.shape).toBeDefined();
expect(schema.shape?.['pid']).toBeDefined();
});
});
describe('pg_reload_conf', () => {
let mockAdapter: ReturnType<typeof createMockPostgresAdapter>;
let tools: ReturnType<typeof getAdminTools>;
let mockContext: ReturnType<typeof createMockRequestContext>;
beforeEach(() => {
vi.clearAllMocks();
mockAdapter = createMockPostgresAdapter();
tools = getAdminTools(mockAdapter as unknown as PostgresAdapter);
mockContext = createMockRequestContext();
});
it('should reload configuration', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({
rows: [{ pg_reload_conf: true }]
});
const tool = tools.find(t => t.name === 'pg_reload_conf')!;
const result = await tool.handler({}, mockContext) as {
success: boolean;
message: string;
};
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('SELECT pg_reload_conf()');
expect(result.success).toBe(true);
expect(result.message).toBe('Configuration reloaded');
});
});
describe('pg_set_config', () => {
let mockAdapter: ReturnType<typeof createMockPostgresAdapter>;
let tools: ReturnType<typeof getAdminTools>;
let mockContext: ReturnType<typeof createMockRequestContext>;
beforeEach(() => {
vi.clearAllMocks();
mockAdapter = createMockPostgresAdapter();
tools = getAdminTools(mockAdapter as unknown as PostgresAdapter);
mockContext = createMockRequestContext();
});
it('should set a configuration parameter', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({
rows: [{ set_config: '100MB' }]
});
const tool = tools.find(t => t.name === 'pg_set_config')!;
const result = await tool.handler({
name: 'work_mem',
value: '100MB'
}, mockContext) as {
success: boolean;
parameter: string;
value: string;
};
expect(mockAdapter.executeQuery).toHaveBeenCalledWith(
'SELECT set_config($1, $2, $3)',
['work_mem', '100MB', false]
);
expect(result.success).toBe(true);
expect(result.parameter).toBe('work_mem');
expect(result.value).toBe('100MB');
});
it('should set config locally when isLocal=true', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({
rows: [{ set_config: 'on' }]
});
const tool = tools.find(t => t.name === 'pg_set_config')!;
await tool.handler({
name: 'enable_seqscan',
value: 'on',
isLocal: true
}, mockContext);
expect(mockAdapter.executeQuery).toHaveBeenCalledWith(
'SELECT set_config($1, $2, $3)',
['enable_seqscan', 'on', true]
);
});
it('should expose parameters in schema for MCP visibility', () => {
const tool = tools.find(t => t.name === 'pg_set_config')!;
const schema = tool.inputSchema as { shape?: Record<string, unknown> };
expect(schema.shape).toBeDefined();
expect(schema.shape?.['name']).toBeDefined();
expect(schema.shape?.['value']).toBeDefined();
expect(schema.shape?.['isLocal']).toBeDefined();
});
});
describe('pg_reset_stats', () => {
let mockAdapter: ReturnType<typeof createMockPostgresAdapter>;
let tools: ReturnType<typeof getAdminTools>;
let mockContext: ReturnType<typeof createMockRequestContext>;
beforeEach(() => {
vi.clearAllMocks();
mockAdapter = createMockPostgresAdapter();
tools = getAdminTools(mockAdapter as unknown as PostgresAdapter);
mockContext = createMockRequestContext();
});
it('should reset statistics', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_reset_stats')!;
const result = await tool.handler({}, mockContext) as {
success: boolean;
message: string;
};
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('SELECT pg_stat_reset()');
expect(result.success).toBe(true);
expect(result.message).toBe('Statistics reset');
});
it('should reset all statistics when type=all', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_reset_stats')!;
await tool.handler({ type: 'all' }, mockContext);
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('SELECT pg_stat_reset()');
});
});
describe('pg_cluster', () => {
let mockAdapter: ReturnType<typeof createMockPostgresAdapter>;
let tools: ReturnType<typeof getAdminTools>;
let mockContext: ReturnType<typeof createMockRequestContext>;
beforeEach(() => {
vi.clearAllMocks();
mockAdapter = createMockPostgresAdapter();
tools = getAdminTools(mockAdapter as unknown as PostgresAdapter);
mockContext = createMockRequestContext();
});
it('should cluster a table on an index', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_cluster')!;
const result = await tool.handler({
table: 'users',
index: 'idx_users_created'
}, mockContext) as {
success: boolean;
table: string;
index: string;
};
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('CLUSTER "users" USING "idx_users_created"');
expect(result.success).toBe(true);
expect(result.table).toBe('users');
expect(result.index).toBe('idx_users_created');
});
it('should cluster with schema qualified table', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_cluster')!;
await tool.handler({
table: 'users',
schema: 'app',
index: 'idx_users_email'
}, mockContext);
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('CLUSTER "app"."users" USING "idx_users_email"');
});
it('should re-cluster all previously-clustered tables when no args', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_cluster')!;
const result = await tool.handler({}, mockContext) as {
success: boolean;
message: string;
};
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('CLUSTER');
expect(result.success).toBe(true);
expect(result.message).toBe('Re-clustered all previously-clustered tables');
});
it('should accept undefined (no args) and re-cluster all tables', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_cluster')!;
const result = await tool.handler(undefined, mockContext) as {
success: boolean;
message: string;
};
expect(mockAdapter.executeQuery).toHaveBeenCalledWith('CLUSTER');
expect(result.success).toBe(true);
});
it('should expose parameters in schema for MCP visibility', () => {
const tool = tools.find(t => t.name === 'pg_cluster')!;
const schema = tool.inputSchema as { shape?: Record<string, unknown> };
expect(schema.shape).toBeDefined();
expect(schema.shape?.['table']).toBeDefined();
expect(schema.shape?.['index']).toBeDefined();
expect(schema.shape?.['schema']).toBeDefined();
});
it('should throw error when index specified without table', async () => {
const tool = tools.find(t => t.name === 'pg_cluster')!;
await expect(tool.handler({ index: 'idx_users_email' }, mockContext))
.rejects.toThrow('table and index must both be specified together');
});
it('should throw error when table specified without index', async () => {
const tool = tools.find(t => t.name === 'pg_cluster')!;
await expect(tool.handler({ table: 'users' }, mockContext))
.rejects.toThrow('table and index must both be specified together');
});
});