/**
* postgres-mcp - Partitioning Tools Unit Tests
*
* Tests for PostgreSQL partitioning tools with focus on
* partition management, creation, and attachment operations.
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { getPartitioningTools } from '../partitioning.js';
import type { PostgresAdapter } from '../../PostgresAdapter.js';
import {
createMockPostgresAdapter,
createMockRequestContext
} from '../../../../__tests__/mocks/index.js';
describe('getPartitioningTools', () => {
let adapter: PostgresAdapter;
let tools: ReturnType<typeof getPartitioningTools>;
beforeEach(() => {
vi.clearAllMocks();
adapter = createMockPostgresAdapter() as unknown as PostgresAdapter;
tools = getPartitioningTools(adapter);
});
it('should return 6 partitioning tools', () => {
expect(tools).toHaveLength(6);
});
it('should have all expected tool names', () => {
const toolNames = tools.map(t => t.name);
expect(toolNames).toContain('pg_list_partitions');
expect(toolNames).toContain('pg_create_partitioned_table');
expect(toolNames).toContain('pg_create_partition');
expect(toolNames).toContain('pg_attach_partition');
expect(toolNames).toContain('pg_detach_partition');
expect(toolNames).toContain('pg_partition_info');
});
it('should have group set to partitioning for all tools', () => {
for (const tool of tools) {
expect(tool.group).toBe('partitioning');
}
});
});
describe('pg_list_partitions', () => {
let mockAdapter: ReturnType<typeof createMockPostgresAdapter>;
let tools: ReturnType<typeof getPartitioningTools>;
let mockContext: ReturnType<typeof createMockRequestContext>;
beforeEach(() => {
vi.clearAllMocks();
mockAdapter = createMockPostgresAdapter();
tools = getPartitioningTools(mockAdapter as unknown as PostgresAdapter);
mockContext = createMockRequestContext();
});
it('should list partitions of a table', async () => {
// First call: checkTablePartitionStatus - partitioned table
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [{ relkind: 'p' }] });
// Second call: partition listing
mockAdapter.executeQuery.mockResolvedValueOnce({
rows: [
{ partition_name: 'events_2023', partition_bounds: "FOR VALUES FROM ('2023-01-01') TO ('2024-01-01')", size_bytes: 104857600 }
]
});
const tool = tools.find(t => t.name === 'pg_list_partitions')!;
const result = await tool.handler({
table: 'events'
}, mockContext) as {
partitions: { size: string }[];
count: number;
};
expect(mockAdapter.executeQuery).toHaveBeenNthCalledWith(
2,
expect.stringContaining('pg_inherits'),
['public', 'events']
);
expect(result.count).toBe(1);
expect(result.partitions).toHaveLength(1);
// Verify consistent size formatting
expect(result.partitions[0]?.size).toBe('100.0 MB');
});
it('should use specified schema', async () => {
// First call: checkTablePartitionStatus - partitioned table
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [{ relkind: 'p' }] });
// Second call: partition listing
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_list_partitions')!;
await tool.handler({
table: 'events',
schema: 'analytics'
}, mockContext);
expect(mockAdapter.executeQuery).toHaveBeenNthCalledWith(
1,
expect.stringContaining("relkind IN ('r', 'p')"),
['events', 'analytics']
);
});
it('should return warning for non-partitioned table', async () => {
// checkTablePartitionStatus returns regular table
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [{ relkind: 'r' }] });
const tool = tools.find(t => t.name === 'pg_list_partitions')!;
const result = await tool.handler({
table: 'regular_table'
}, mockContext) as {
partitions: unknown[];
count: number;
warning: string;
};
expect(result.count).toBe(0);
expect(result.partitions).toHaveLength(0);
expect(result.warning).toContain('exists but is not partitioned');
expect(result.warning).toContain('regular_table');
expect(mockAdapter.executeQuery).toHaveBeenCalledTimes(1);
});
it('should return warning for non-existent table', async () => {
// checkTablePartitionStatus returns not found
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_list_partitions')!;
const result = await tool.handler({
table: 'nonexistent_table'
}, mockContext) as {
partitions: unknown[];
count: number;
warning: string;
};
expect(result.count).toBe(0);
expect(result.partitions).toHaveLength(0);
expect(result.warning).toContain('does not exist');
expect(result.warning).toContain('nonexistent_table');
expect(mockAdapter.executeQuery).toHaveBeenCalledTimes(1);
});
});
describe('pg_create_partitioned_table', () => {
let mockAdapter: ReturnType<typeof createMockPostgresAdapter>;
let tools: ReturnType<typeof getPartitioningTools>;
let mockContext: ReturnType<typeof createMockRequestContext>;
beforeEach(() => {
vi.clearAllMocks();
mockAdapter = createMockPostgresAdapter();
tools = getPartitioningTools(mockAdapter as unknown as PostgresAdapter);
mockContext = createMockRequestContext();
});
it('should create a RANGE partitioned table', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_create_partitioned_table')!;
const result = await tool.handler({
name: 'events',
columns: [
{ name: 'id', type: 'bigint' },
{ name: 'event_date', type: 'date', nullable: false },
{ name: 'data', type: 'jsonb' }
],
partitionBy: 'range',
partitionKey: 'event_date'
}, mockContext) as {
success: boolean;
table: string;
partitionBy: string;
partitionKey: string;
};
const call = mockAdapter.executeQuery.mock.calls[0][0] as string;
expect(call).toContain('CREATE TABLE');
expect(call).toContain('PARTITION BY RANGE (event_date)');
expect(result.success).toBe(true);
expect(result.partitionBy).toBe('range');
});
it('should create a LIST partitioned table', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_create_partitioned_table')!;
await tool.handler({
name: 'orders',
columns: [
{ name: 'id', type: 'serial' },
{ name: 'region', type: 'varchar(50)' }
],
partitionBy: 'list',
partitionKey: 'region'
}, mockContext);
const call = mockAdapter.executeQuery.mock.calls[0][0] as string;
expect(call).toContain('PARTITION BY LIST (region)');
});
it('should handle NOT NULL columns', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_create_partitioned_table')!;
await tool.handler({
name: 'data',
columns: [
{ name: 'id', type: 'bigint', nullable: false }
],
partitionBy: 'hash',
partitionKey: 'id'
}, mockContext);
const call = mockAdapter.executeQuery.mock.calls[0][0] as string;
expect(call).toContain('NOT NULL');
});
it('should handle notNull: true column option', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_create_partitioned_table')!;
await tool.handler({
name: 'data',
columns: [
{ name: 'id', type: 'bigint', notNull: true } // notNull alias
],
partitionBy: 'hash',
partitionKey: 'id'
}, mockContext);
const call = mockAdapter.executeQuery.mock.calls[0][0] as string;
expect(call).toContain('NOT NULL');
});
it('should handle primaryKey column option', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_create_partitioned_table')!;
await tool.handler({
name: 'data',
columns: [
{ name: 'id', type: 'bigint', primaryKey: true }
],
partitionBy: 'hash',
partitionKey: 'id'
}, mockContext);
const call = mockAdapter.executeQuery.mock.calls[0][0] as string;
expect(call).toContain('PRIMARY KEY');
});
it('should handle unique column option', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_create_partitioned_table')!;
await tool.handler({
name: 'data',
columns: [
{ name: 'email', type: 'varchar(255)', unique: true }
],
partitionBy: 'hash',
partitionKey: 'id'
}, mockContext);
const call = mockAdapter.executeQuery.mock.calls[0][0] as string;
expect(call).toContain('UNIQUE');
});
it('should handle default column option', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_create_partitioned_table')!;
await tool.handler({
name: 'data',
columns: [
{ name: 'status', type: 'varchar(20)', default: 'active' }
],
partitionBy: 'hash',
partitionKey: 'id'
}, mockContext);
const call = mockAdapter.executeQuery.mock.calls[0][0] as string;
expect(call).toContain("DEFAULT 'active'");
});
it('should handle numeric default values', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_create_partitioned_table')!;
await tool.handler({
name: 'data',
columns: [
{ name: 'count', type: 'integer', default: 0 }
],
partitionBy: 'hash',
partitionKey: 'id'
}, mockContext);
const call = mockAdapter.executeQuery.mock.calls[0][0] as string;
expect(call).toContain('DEFAULT 0');
});
it('should strip outer quotes from string defaults (common mistake)', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_create_partitioned_table')!;
await tool.handler({
name: 'data',
columns: [
{ name: 'status', type: 'varchar(20)', default: "'pending'" } // User added quotes
],
partitionBy: 'hash',
partitionKey: 'id'
}, mockContext);
const call = mockAdapter.executeQuery.mock.calls[0][0] as string;
// Should produce DEFAULT 'pending' not DEFAULT ''pending''
expect(call).toContain("DEFAULT 'pending'");
expect(call).not.toContain("''pending''");
});
it('should escape quotes within string defaults', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_create_partitioned_table')!;
await tool.handler({
name: 'data',
columns: [
{ name: 'desc', type: 'text', default: "it's working" }
],
partitionBy: 'hash',
partitionKey: 'id'
}, mockContext);
const call = mockAdapter.executeQuery.mock.calls[0][0] as string;
// Single quote should be escaped to ''
expect(call).toContain("DEFAULT 'it''s working'");
});
});
describe('pg_create_partition', () => {
let mockAdapter: ReturnType<typeof createMockPostgresAdapter>;
let tools: ReturnType<typeof getPartitioningTools>;
let mockContext: ReturnType<typeof createMockRequestContext>;
beforeEach(() => {
vi.clearAllMocks();
mockAdapter = createMockPostgresAdapter();
tools = getPartitioningTools(mockAdapter as unknown as PostgresAdapter);
mockContext = createMockRequestContext();
});
it('should create a RANGE partition', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_create_partition')!;
const result = await tool.handler({
parent: 'events',
name: 'events_2024',
forValues: "FROM ('2024-01-01') TO ('2025-01-01')"
}, mockContext) as {
success: boolean;
partition: string;
bounds: string;
};
const call = mockAdapter.executeQuery.mock.calls[0][0] as string;
expect(call).toContain('PARTITION OF');
expect(call).toContain('FOR VALUES');
expect(result.success).toBe(true);
expect(result.partition).toContain('events_2024');
});
it('should create a LIST partition', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_create_partition')!;
await tool.handler({
parent: 'orders',
name: 'orders_us',
forValues: "IN ('US', 'CA')"
}, mockContext);
const call = mockAdapter.executeQuery.mock.calls[0][0] as string;
expect(call).toContain("IN ('US', 'CA')");
});
it('should create a DEFAULT partition', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_create_partition')!;
const result = await tool.handler({
parent: 'events',
name: 'events_other',
isDefault: true
}, mockContext) as {
success: boolean;
bounds: string;
};
const call = mockAdapter.executeQuery.mock.calls[0][0] as string;
expect(call).toContain('DEFAULT');
expect(call).not.toContain('FOR VALUES');
expect(result.success).toBe(true);
expect(result.bounds).toBe('DEFAULT');
});
it('should accept default: true as alias for isDefault', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_create_partition')!;
const result = await tool.handler({
parent: 'events',
name: 'events_other',
default: true // Alias for isDefault
}, mockContext) as {
success: boolean;
bounds: string;
};
const call = mockAdapter.executeQuery.mock.calls[0][0] as string;
expect(call).toContain('DEFAULT');
expect(result.success).toBe(true);
expect(result.bounds).toBe('DEFAULT');
});
it('should create a sub-partitionable partition', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_create_partition')!;
const result = await tool.handler({
parent: 'orders',
name: 'orders_2024',
from: '2024-01-01',
to: '2025-01-01',
subpartitionBy: 'list',
subpartitionKey: 'region'
}, mockContext) as {
success: boolean;
subpartitionBy: string;
subpartitionKey: string;
};
const call = mockAdapter.executeQuery.mock.calls[0][0] as string;
expect(call).toContain('FOR VALUES');
expect(call).toContain('PARTITION BY LIST (region)');
expect(result.success).toBe(true);
expect(result.subpartitionBy).toBe('list');
expect(result.subpartitionKey).toBe('region');
});
it('should require subpartitionKey when subpartitionBy is set', async () => {
const tool = tools.find(t => t.name === 'pg_create_partition')!;
await expect(tool.handler({
parent: 'orders',
name: 'orders_2024',
from: '2024-01-01',
to: '2025-01-01',
subpartitionBy: 'list'
// Missing subpartitionKey
}, mockContext)).rejects.toThrow('subpartitionKey is required');
});
it('should support DEFAULT partition with sub-partitioning', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_create_partition')!;
const result = await tool.handler({
parent: 'orders',
name: 'orders_other',
isDefault: true,
subpartitionBy: 'hash',
subpartitionKey: 'id'
}, mockContext) as {
success: boolean;
bounds: string;
subpartitionBy: string;
};
const call = mockAdapter.executeQuery.mock.calls[0][0] as string;
expect(call).toContain('DEFAULT');
expect(call).not.toContain('FOR VALUES');
expect(call).toContain('PARTITION BY HASH (id)');
expect(result.bounds).toBe('DEFAULT');
expect(result.subpartitionBy).toBe('hash');
});
it('should normalize uppercase subpartitionBy values', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_create_partition')!;
const result = await tool.handler({
parent: 'orders',
name: 'orders_2024',
from: '2024-01-01',
to: '2025-01-01',
subpartitionBy: 'LIST', // Uppercase - should be normalized
subpartitionKey: 'region'
}, mockContext) as {
success: boolean;
subpartitionBy: string;
};
const call = mockAdapter.executeQuery.mock.calls[0][0] as string;
expect(call).toContain('PARTITION BY LIST (region)');
expect(result.subpartitionBy).toBe('list'); // Normalized to lowercase
});
});
describe('pg_attach_partition', () => {
let mockAdapter: ReturnType<typeof createMockPostgresAdapter>;
let tools: ReturnType<typeof getPartitioningTools>;
let mockContext: ReturnType<typeof createMockRequestContext>;
beforeEach(() => {
vi.clearAllMocks();
mockAdapter = createMockPostgresAdapter();
tools = getPartitioningTools(mockAdapter as unknown as PostgresAdapter);
mockContext = createMockRequestContext();
});
it('should attach a partition', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_attach_partition')!;
const result = await tool.handler({
parent: 'events',
partition: 'legacy_events',
forValues: "FROM ('2020-01-01') TO ('2021-01-01')"
}, mockContext) as {
success: boolean;
parent: string;
partition: string;
};
const call = mockAdapter.executeQuery.mock.calls[0][0] as string;
expect(call).toContain('ALTER TABLE');
expect(call).toContain('ATTACH PARTITION');
expect(result.success).toBe(true);
expect(result.parent).toBe('events');
expect(result.partition).toBe('legacy_events');
});
});
describe('pg_detach_partition', () => {
let mockAdapter: ReturnType<typeof createMockPostgresAdapter>;
let tools: ReturnType<typeof getPartitioningTools>;
let mockContext: ReturnType<typeof createMockRequestContext>;
beforeEach(() => {
vi.clearAllMocks();
mockAdapter = createMockPostgresAdapter();
tools = getPartitioningTools(mockAdapter as unknown as PostgresAdapter);
mockContext = createMockRequestContext();
});
it('should detach a partition', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_detach_partition')!;
const result = await tool.handler({
parent: 'events',
partition: 'events_2020'
}, mockContext) as {
success: boolean;
parent: string;
detached: string;
};
const call = mockAdapter.executeQuery.mock.calls[0][0] as string;
expect(call).toContain('DETACH PARTITION');
expect(result.success).toBe(true);
expect(result.detached).toBe('events_2020');
});
it('should detach concurrently when specified', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_detach_partition')!;
await tool.handler({
parent: 'events',
partition: 'events_2020',
concurrently: true
}, mockContext);
const call = mockAdapter.executeQuery.mock.calls[0][0] as string;
expect(call).toContain('CONCURRENTLY');
});
});
describe('pg_partition_info', () => {
let mockAdapter: ReturnType<typeof createMockPostgresAdapter>;
let tools: ReturnType<typeof getPartitioningTools>;
let mockContext: ReturnType<typeof createMockRequestContext>;
beforeEach(() => {
vi.clearAllMocks();
mockAdapter = createMockPostgresAdapter();
tools = getPartitioningTools(mockAdapter as unknown as PostgresAdapter);
mockContext = createMockRequestContext();
});
it('should get partition info', async () => {
// First call: checkTablePartitionStatus - partitioned table
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [{ relkind: 'p' }] });
// Second call: partition info
mockAdapter.executeQuery.mockResolvedValueOnce({
rows: [{
table_name: 'events',
partition_strategy: 'RANGE',
partition_key: 'event_date',
partition_count: 4
}]
});
// Third call: partition details
mockAdapter.executeQuery.mockResolvedValueOnce({
rows: [
{ partition_name: 'events_2021', bounds: "FOR VALUES FROM ('2021-01-01') TO ('2022-01-01')", size_bytes: 52428800, approx_rows: 100000 },
{ partition_name: 'events_2022', bounds: "FOR VALUES FROM ('2022-01-01') TO ('2023-01-01')", size_bytes: 78643200, approx_rows: 150000 }
]
});
const tool = tools.find(t => t.name === 'pg_partition_info')!;
const result = await tool.handler({
table: 'events'
}, mockContext) as {
tableInfo: unknown;
partitions: { size: string; approx_rows: number }[];
totalSizeBytes: number;
};
expect(result.tableInfo).toHaveProperty('partition_strategy', 'RANGE');
expect(result.partitions).toHaveLength(2);
expect(result.totalSizeBytes).toBe(52428800 + 78643200);
// Verify consistent size formatting
expect(result.partitions[0]?.size).toBe('50.0 MB');
expect(result.partitions[1]?.size).toBe('75.0 MB');
});
it('should normalize approx_rows -1 to 0 for empty partitions', async () => {
// First call: checkTablePartitionStatus - partitioned table
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [{ relkind: 'p' }] });
// Second call: partition info
mockAdapter.executeQuery.mockResolvedValueOnce({
rows: [{ table_name: 'events', partition_strategy: 'RANGE', partition_key: 'event_date', partition_count: 1 }]
});
// Third call: partition details with 0 approx_rows
mockAdapter.executeQuery.mockResolvedValueOnce({
rows: [{ partition_name: 'events_empty', bounds: "FOR VALUES FROM ('2024-01-01') TO ('2025-01-01')", size_bytes: 8192, approx_rows: 0 }]
});
const tool = tools.find(t => t.name === 'pg_partition_info')!;
const result = await tool.handler({ table: 'events' }, mockContext) as {
partitions: { approx_rows: number }[];
};
// Should normalize -1 to 0 (handled by GREATEST(0, ...) in SQL)
expect(result.partitions[0]?.approx_rows).toBe(0);
});
it('should return warning for non-partitioned table', async () => {
// checkTablePartitionStatus returns regular table
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [{ relkind: 'r' }] });
const tool = tools.find(t => t.name === 'pg_partition_info')!;
const result = await tool.handler({
table: 'regular_table'
}, mockContext) as {
tableInfo: unknown;
partitions: unknown[];
totalSizeBytes: number;
warning: string;
};
expect(result.tableInfo).toBeNull();
expect(result.partitions).toHaveLength(0);
expect(result.totalSizeBytes).toBe(0);
expect(result.warning).toContain('exists but is not partitioned');
expect(result.warning).toContain('regular_table');
expect(mockAdapter.executeQuery).toHaveBeenCalledTimes(1);
});
it('should return warning for non-existent table', async () => {
// checkTablePartitionStatus returns not found
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_partition_info')!;
const result = await tool.handler({
table: 'nonexistent_table'
}, mockContext) as {
tableInfo: unknown;
partitions: unknown[];
totalSizeBytes: number;
warning: string;
};
expect(result.tableInfo).toBeNull();
expect(result.partitions).toHaveLength(0);
expect(result.totalSizeBytes).toBe(0);
expect(result.warning).toContain('does not exist');
expect(result.warning).toContain('nonexistent_table');
expect(mockAdapter.executeQuery).toHaveBeenCalledTimes(1);
});
});
/**
* Parameter Smoothing Tests
*
* These tests verify that common agent input mistakes are automatically corrected.
*/
describe('Parameter Smoothing', () => {
let mockAdapter: ReturnType<typeof createMockPostgresAdapter>;
let tools: ReturnType<typeof getPartitioningTools>;
let mockContext: ReturnType<typeof createMockRequestContext>;
beforeEach(() => {
vi.clearAllMocks();
mockAdapter = createMockPostgresAdapter();
tools = getPartitioningTools(mockAdapter as unknown as PostgresAdapter);
mockContext = createMockRequestContext();
});
describe('pg_create_partitioned_table - partitionBy case normalization', () => {
it('should accept uppercase RANGE and normalize to lowercase', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_create_partitioned_table')!;
const result = await tool.handler({
name: 'events',
columns: [{ name: 'id', type: 'bigint' }, { name: 'event_date', type: 'date' }],
partitionBy: 'RANGE', // Uppercase - should be normalized
partitionKey: 'event_date'
}, mockContext) as { success: boolean; partitionBy: string };
expect(result.success).toBe(true);
expect(result.partitionBy).toBe('range');
const call = mockAdapter.executeQuery.mock.calls[0][0] as string;
expect(call).toContain('PARTITION BY RANGE');
});
it('should accept uppercase LIST and normalize to lowercase', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_create_partitioned_table')!;
const result = await tool.handler({
name: 'orders',
columns: [{ name: 'id', type: 'bigint' }, { name: 'region', type: 'text' }],
partitionBy: 'LIST', // Uppercase - should be normalized
partitionKey: 'region'
}, mockContext) as { success: boolean; partitionBy: string };
expect(result.success).toBe(true);
expect(result.partitionBy).toBe('list');
});
it('should accept uppercase HASH and normalize to lowercase', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_create_partitioned_table')!;
const result = await tool.handler({
name: 'data',
columns: [{ name: 'id', type: 'bigint' }],
partitionBy: 'HASH', // Uppercase - should be normalized
partitionKey: 'id'
}, mockContext) as { success: boolean; partitionBy: string };
expect(result.success).toBe(true);
expect(result.partitionBy).toBe('hash');
});
});
describe('pg_create_partition - parameter aliasing', () => {
it('should accept parentTable as alias for parent', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_create_partition')!;
const result = await tool.handler({
parentTable: 'events', // Common alias - should be normalized to parent
name: 'events_2024',
forValues: "FROM ('2024-01-01') TO ('2025-01-01')"
}, mockContext) as { success: boolean; parent: string };
expect(result.success).toBe(true);
expect(result.parent).toBe('events');
});
it('should accept partitionName as alias for name', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_create_partition')!;
const result = await tool.handler({
parent: 'events',
partitionName: 'events_2024', // Common alias - should be normalized to name
forValues: "FROM ('2024-01-01') TO ('2025-01-01')"
}, mockContext) as { success: boolean; partition: string };
expect(result.success).toBe(true);
expect(result.partition).toContain('events_2024');
});
it('should accept from/to and build forValues', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_create_partition')!;
const result = await tool.handler({
parent: 'events',
name: 'events_2024',
from: '2024-01-01', // Common pattern - should be converted to forValues
to: '2025-01-01'
}, mockContext) as { success: boolean; bounds: string };
expect(result.success).toBe(true);
expect(result.bounds).toContain("FROM ('2024-01-01') TO ('2025-01-01')");
});
it('should accept parentTable with from/to combined', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_create_partition')!;
const result = await tool.handler({
parentTable: 'events', // Alias
name: 'events_q1_2024',
from: '2024-01-01', // Combined with to
to: '2024-04-01'
}, mockContext) as { success: boolean; parent: string };
expect(result.success).toBe(true);
expect(result.parent).toBe('events');
});
});
describe('pg_attach_partition - parameter aliasing', () => {
it('should accept parentTable as alias for parent', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_attach_partition')!;
const result = await tool.handler({
parentTable: 'events', // Common alias
partition: 'legacy_events',
forValues: "FROM ('2020-01-01') TO ('2021-01-01')"
}, mockContext) as { success: boolean; parent: string };
expect(result.success).toBe(true);
expect(result.parent).toBe('events');
});
it('should accept from/to and build forValues', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_attach_partition')!;
const result = await tool.handler({
parent: 'events',
partition: 'legacy_events',
from: '2020-01-01',
to: '2021-01-01'
}, mockContext) as { success: boolean; bounds: string };
expect(result.success).toBe(true);
expect(result.bounds).toContain("FROM ('2020-01-01') TO ('2021-01-01')");
});
it('should accept partitionTable as alias for partition', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_attach_partition')!;
const result = await tool.handler({
parent: 'events',
partitionTable: 'legacy_events', // Common alias
forValues: "FROM ('2020-01-01') TO ('2021-01-01')"
}, mockContext) as { success: boolean; partition: string };
expect(result.success).toBe(true);
expect(result.partition).toBe('legacy_events');
});
it('should accept partitionName as alias for partition', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_attach_partition')!;
const result = await tool.handler({
parent: 'events',
partitionName: 'legacy_events', // Common alias
forValues: "FROM ('2020-01-01') TO ('2021-01-01')"
}, mockContext) as { success: boolean; partition: string };
expect(result.success).toBe(true);
expect(result.partition).toBe('legacy_events');
});
it('should accept values array for LIST partitions', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_attach_partition')!;
const result = await tool.handler({
parent: 'events',
partition: 'events_status',
values: ['active', 'pending'] // Intuitive format
}, mockContext) as { success: boolean; bounds: string };
expect(result.success).toBe(true);
expect(result.bounds).toBe("IN ('active', 'pending')");
});
it('should accept modulus/remainder for HASH partitions', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_attach_partition')!;
const result = await tool.handler({
parent: 'events',
partition: 'events_h0',
modulus: 4,
remainder: 0
}, mockContext) as { success: boolean; bounds: string };
expect(result.success).toBe(true);
expect(result.bounds).toBe('WITH (MODULUS 4, REMAINDER 0)');
});
});
describe('pg_detach_partition - parameter aliasing', () => {
it('should accept parentTable as alias for parent', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_detach_partition')!;
const result = await tool.handler({
parentTable: 'events', // Common alias
partition: 'events_2020'
}, mockContext) as { success: boolean; parent: string };
expect(result.success).toBe(true);
expect(result.parent).toBe('events');
});
it('should accept partitionTable as alias for partition', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_detach_partition')!;
const result = await tool.handler({
parent: 'events',
partitionTable: 'events_2020' // Common alias
}, mockContext) as { success: boolean; detached: string };
expect(result.success).toBe(true);
expect(result.detached).toBe('events_2020');
});
it('should accept partitionName as alias for partition', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_detach_partition')!;
const result = await tool.handler({
parent: 'events',
partitionName: 'events_2020' // Common alias
}, mockContext) as { success: boolean; detached: string };
expect(result.success).toBe(true);
expect(result.detached).toBe('events_2020');
});
});
describe('pg_create_partition - LIST and HASH intuitive formats', () => {
it('should accept values array for LIST partitions', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_create_partition')!;
const result = await tool.handler({
parent: 'orders',
name: 'orders_us',
values: ['US', 'CA', 'MX'] // Intuitive format
}, mockContext) as { success: boolean; bounds: string };
expect(result.success).toBe(true);
expect(result.bounds).toBe("IN ('US', 'CA', 'MX')");
});
it('should accept modulus/remainder for HASH partitions', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_create_partition')!;
const result = await tool.handler({
parent: 'data',
name: 'data_h1',
modulus: 4,
remainder: 1
}, mockContext) as { success: boolean; bounds: string };
expect(result.success).toBe(true);
expect(result.bounds).toBe('WITH (MODULUS 4, REMAINDER 1)');
});
it('should accept rangeFrom/rangeTo for RANGE partitions', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_create_partition')!;
const result = await tool.handler({
parent: 'events',
name: 'events_2024',
rangeFrom: '2024-01-01', // Intuitive alias
rangeTo: '2025-01-01'
}, mockContext) as { success: boolean; bounds: string };
expect(result.success).toBe(true);
expect(result.bounds).toBe("FROM ('2024-01-01') TO ('2025-01-01')");
});
it('should accept listValues for LIST partitions', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_create_partition')!;
const result = await tool.handler({
parent: 'status',
name: 'status_active',
listValues: ['active', 'enabled'] // Intuitive alias
}, mockContext) as { success: boolean; bounds: string };
expect(result.success).toBe(true);
expect(result.bounds).toBe("IN ('active', 'enabled')");
});
it('should accept hashModulus/hashRemainder for HASH partitions', async () => {
mockAdapter.executeQuery.mockResolvedValueOnce({ rows: [] });
const tool = tools.find(t => t.name === 'pg_create_partition')!;
const result = await tool.handler({
parent: 'data',
name: 'data_h2',
hashModulus: 8, // Intuitive alias
hashRemainder: 3
}, mockContext) as { success: boolean; bounds: string };
expect(result.success).toBe(true);
expect(result.bounds).toBe('WITH (MODULUS 8, REMAINDER 3)');
});
});
});