/**
* mysql-mcp - Stats Tools Unit Tests
*
* Tests for stats tool definitions, annotations, and handler execution.
*/
import { describe, it, expect, vi, beforeEach } from "vitest";
import { getStatsTools } from "../stats/index.js";
import type { MySQLAdapter } from "../../MySQLAdapter.js";
import {
createMockMySQLAdapter,
createMockRequestContext,
createMockQueryResult,
} from "../../../../__tests__/mocks/index.js";
describe("getStatsTools", () => {
let tools: ReturnType<typeof getStatsTools>;
beforeEach(() => {
vi.clearAllMocks();
tools = getStatsTools(createMockMySQLAdapter() as unknown as MySQLAdapter);
});
it("should return 8 stats tools", () => {
expect(tools).toHaveLength(8);
});
it("should have stats group for all tools", () => {
for (const tool of tools) {
expect(tool.group).toBe("stats");
}
});
it("should have handler functions for all tools", () => {
for (const tool of tools) {
expect(typeof tool.handler).toBe("function");
}
});
it("should have inputSchema for all tools", () => {
for (const tool of tools) {
expect(tool.inputSchema).toBeDefined();
}
});
it("should include expected tool names", () => {
const names = tools.map((t) => t.name);
expect(names).toContain("mysql_stats_descriptive");
expect(names).toContain("mysql_stats_percentiles");
expect(names).toContain("mysql_stats_correlation");
expect(names).toContain("mysql_stats_distribution");
expect(names).toContain("mysql_stats_time_series");
expect(names).toContain("mysql_stats_regression");
expect(names).toContain("mysql_stats_sampling");
expect(names).toContain("mysql_stats_histogram");
});
});
describe("Handler Execution", () => {
let mockAdapter: ReturnType<typeof createMockMySQLAdapter>;
let tools: ReturnType<typeof getStatsTools>;
let mockContext: ReturnType<typeof createMockRequestContext>;
beforeEach(() => {
vi.clearAllMocks();
mockAdapter = createMockMySQLAdapter();
tools = getStatsTools(mockAdapter as unknown as MySQLAdapter);
mockContext = createMockRequestContext();
});
describe("mysql_stats_descriptive", () => {
it("should calculate descriptive statistics", async () => {
mockAdapter.executeQuery.mockImplementation(async (query) => {
if (query.includes("COUNT(*) as count")) {
return createMockQueryResult([{ count: 100 }]);
}
if (query.includes("AVG(val) as median")) {
return createMockQueryResult([{ median: 50 }]);
}
return createMockQueryResult([
{
count: 100,
mean: 50.5,
min: 1,
max: 100,
range: 99,
stddev: 10,
variance: 100,
sum: 5050,
},
]);
});
const tool = tools.find((t) => t.name === "mysql_stats_descriptive")!;
const result = await tool.handler(
{ table: "orders", column: "total" },
mockContext,
);
expect(mockAdapter.executeQuery).toHaveBeenCalledTimes(3);
// Verify all queries were made
const calls = mockAdapter.executeQuery.mock.calls.map(
(c) => c[0] as string,
);
expect(calls.some((c) => c.includes("COUNT(*)"))).toBe(true);
expect(calls.some((c) => c.includes("OFFSET"))).toBe(true);
expect(calls.some((c) => c.includes("as `range`"))).toBe(true);
// Returns column, count, mean, median, stddev, etc
expect(result).toHaveProperty("column", "total");
expect(result).toHaveProperty("count", 100);
expect(result).toHaveProperty("median", 50);
expect(result).toHaveProperty("range", 99);
});
it("should include where clause in all queries", async () => {
mockAdapter.executeQuery.mockResolvedValue(
createMockQueryResult([{ count: 10 }]),
);
const tool = tools.find((t) => t.name === "mysql_stats_descriptive")!;
await tool.handler(
{ table: "orders", column: "total", where: 'status = "completed"' },
mockContext,
);
// Check that WHERE clause was present in all queries
const calls = mockAdapter.executeQuery.mock.calls.map(
(c) => c[0] as string,
);
expect(calls.length).toBeGreaterThan(0);
for (const call of calls) {
expect(call).toContain('WHERE status = "completed"');
}
});
it("should return nulls when count is 0", async () => {
mockAdapter.executeQuery.mockImplementation(async (query) => {
if (query.includes("COUNT(*) as count")) {
return createMockQueryResult([{ count: 0 }]);
}
return createMockQueryResult([]);
});
const tool = tools.find((t) => t.name === "mysql_stats_descriptive")!;
const result = await tool.handler(
{ table: "orders", column: "total" },
mockContext,
);
expect(result).toHaveProperty("count", 0);
expect(result).toHaveProperty("mean", null);
expect(result).toHaveProperty("median", null);
});
});
describe("mysql_stats_percentiles", () => {
it("should calculate percentiles", async () => {
mockAdapter.executeQuery.mockResolvedValue(
createMockQueryResult([{ cnt: 100 }]),
);
const tool = tools.find((t) => t.name === "mysql_stats_percentiles")!;
const result = await tool.handler(
{ table: "orders", column: "total" },
mockContext,
);
expect(mockAdapter.executeQuery).toHaveBeenCalled();
// Returns column, totalCount, percentiles
expect(result).toHaveProperty("totalCount");
expect(result).toHaveProperty("percentiles");
});
it("should use custom percentiles", async () => {
mockAdapter.executeQuery.mockResolvedValue(
createMockQueryResult([{ cnt: 100 }]),
);
const tool = tools.find((t) => t.name === "mysql_stats_percentiles")!;
await tool.handler(
{
table: "orders",
column: "total",
percentiles: [10, 50, 90, 99],
},
mockContext,
);
expect(mockAdapter.executeQuery).toHaveBeenCalled();
});
});
describe("mysql_stats_correlation", () => {
it("should calculate correlation coefficient", async () => {
mockAdapter.executeQuery.mockResolvedValue(
createMockQueryResult([
{
correlation: 0.85,
sample_size: 100,
mean_x: 50,
mean_y: 60,
std_x: 10,
std_y: 12,
},
]),
);
const tool = tools.find((t) => t.name === "mysql_stats_correlation")!;
const result = await tool.handler(
{
table: "data",
column1: "height",
column2: "weight",
},
mockContext,
);
expect(mockAdapter.executeQuery).toHaveBeenCalled();
expect(result).toHaveProperty("column1", "height");
expect(result).toHaveProperty("column2", "weight");
expect(result).toHaveProperty("correlation");
});
});
describe("mysql_stats_distribution", () => {
it("should analyze data distribution", async () => {
mockAdapter.executeQuery.mockResolvedValue(
createMockQueryResult([{ min_val: 0, max_val: 100 }]),
);
const tool = tools.find((t) => t.name === "mysql_stats_distribution")!;
const result = await tool.handler(
{
table: "orders",
column: "amount",
buckets: 10,
},
mockContext,
);
expect(mockAdapter.executeQuery).toHaveBeenCalled();
expect(result).toHaveProperty("distribution");
});
});
describe("mysql_stats_time_series", () => {
it("should compute time series with moving average", async () => {
mockAdapter.executeQuery.mockResolvedValue(
createMockQueryResult([{ period: "2024-01", value: 100 }]),
);
const tool = tools.find((t) => t.name === "mysql_stats_time_series")!;
const result = await tool.handler(
{
table: "sales",
timeColumn: "sale_date",
valueColumn: "amount",
interval: "month",
},
mockContext,
);
expect(mockAdapter.executeQuery).toHaveBeenCalled();
expect(result).toHaveProperty("dataPoints");
});
});
describe("mysql_stats_regression", () => {
it("should perform linear regression", async () => {
mockAdapter.executeQuery.mockResolvedValue(
createMockQueryResult([
{
n: 100,
avg_x: 50,
avg_y: 60,
sum_x: 5000,
sum_y: 6000,
sum_xy: 310000,
sum_x2: 260000,
sum_y2: 370000,
},
]),
);
const tool = tools.find((t) => t.name === "mysql_stats_regression")!;
const result = await tool.handler(
{
table: "data",
xColumn: "x",
yColumn: "y",
},
mockContext,
);
expect(mockAdapter.executeQuery).toHaveBeenCalled();
expect(result).toHaveProperty("slope");
expect(result).toHaveProperty("intercept");
});
});
describe("mysql_stats_sampling", () => {
it("should return random sample", async () => {
mockAdapter.executeQuery.mockResolvedValue(
createMockQueryResult([{ id: 1 }, { id: 5 }, { id: 10 }]),
);
const tool = tools.find((t) => t.name === "mysql_stats_sampling")!;
const result = await tool.handler(
{
table: "users",
sampleSize: 100,
},
mockContext,
);
expect(mockAdapter.executeQuery).toHaveBeenCalled();
const call = mockAdapter.executeQuery.mock.calls[0][0] as string;
expect(call).toContain("RAND()");
expect(result).toHaveProperty("sample");
});
it("should accept specific columns", async () => {
mockAdapter.executeQuery.mockResolvedValue(createMockQueryResult([]));
const tool = tools.find((t) => t.name === "mysql_stats_sampling")!;
await tool.handler(
{
table: "users",
sampleSize: 10,
columns: ["id", "name"],
},
mockContext,
);
const call = mockAdapter.executeQuery.mock.calls[0][0] as string;
expect(call).toContain("`id`");
expect(call).toContain("`name`");
});
});
describe("mysql_stats_histogram", () => {
it("should query histogram data", async () => {
mockAdapter.executeQuery.mockResolvedValue(createMockQueryResult([]));
const tool = tools.find((t) => t.name === "mysql_stats_histogram")!;
const result = await tool.handler(
{
table: "orders",
column: "total",
},
mockContext,
);
expect(mockAdapter.executeQuery).toHaveBeenCalled();
expect(result).toBeDefined();
});
it("should create histogram when update is true", async () => {
mockAdapter.executeQuery.mockResolvedValue(
createMockQueryResult([{ TABLE_NAME: "orders" }]),
);
const tool = tools.find((t) => t.name === "mysql_stats_histogram")!;
await tool.handler(
{
table: "orders",
column: "total",
update: true,
buckets: 32,
},
mockContext,
);
// calls[0] is table existence check, calls[1] is ANALYZE TABLE
const calls = mockAdapter.executeQuery.mock.calls.map(
(c) => c[0] as string,
);
expect(calls.some((c) => c.includes("ANALYZE TABLE"))).toBe(true);
});
it("should include warning when buckets exceed 1024", async () => {
mockAdapter.executeQuery.mockResolvedValue(
createMockQueryResult([{ TABLE_NAME: "orders" }]),
);
const tool = tools.find((t) => t.name === "mysql_stats_histogram")!;
const result = (await tool.handler(
{ table: "orders", column: "total", update: true, buckets: 2000 },
mockContext,
)) as { warning?: string };
// Verify ANALYZE TABLE used clamped value
const calls = mockAdapter.executeQuery.mock.calls.map(
(c) => c[0] as string,
);
const analyzeCall = calls.find((c) => c.includes("ANALYZE TABLE"));
expect(analyzeCall).toContain("1024 BUCKETS");
expect(result.warning).toBe(
"Requested 2000 buckets; clamped to max 1024",
);
});
it("should return histogram metadata when exists", async () => {
mockAdapter.executeQuery.mockResolvedValue(
createMockQueryResult([
{
schemaName: "test",
tableName: "orders",
columnName: "total",
histogramType: "singleton",
},
]),
);
const tool = tools.find((t) => t.name === "mysql_stats_histogram")!;
const result = (await tool.handler(
{
table: "orders",
column: "total",
},
mockContext,
)) as { exists: boolean };
expect(result.exists).toBe(true);
});
});
});
describe("Stats Validation Errors", () => {
let mockAdapter: ReturnType<typeof createMockMySQLAdapter>;
let tools: ReturnType<typeof getStatsTools>;
let mockContext: ReturnType<typeof createMockRequestContext>;
beforeEach(() => {
vi.clearAllMocks();
mockAdapter = createMockMySQLAdapter();
tools = getStatsTools(mockAdapter as unknown as MySQLAdapter);
mockContext = createMockRequestContext();
});
describe("mysql_stats_descriptive", () => {
it("should reject invalid table name", async () => {
const tool = tools.find((t) => t.name === "mysql_stats_descriptive")!;
const result = (await tool.handler(
{
table: "123-invalid",
column: "total",
},
mockContext,
)) as { success: boolean; error: string };
expect(result.success).toBe(false);
expect(result.error).toContain("Invalid table name");
});
it("should reject invalid column name", async () => {
const tool = tools.find((t) => t.name === "mysql_stats_descriptive")!;
const result = (await tool.handler(
{
table: "orders",
column: "123-bad",
},
mockContext,
)) as { success: boolean; error: string };
expect(result.success).toBe(false);
expect(result.error).toContain("Invalid column name");
});
});
describe("mysql_stats_percentiles", () => {
it("should reject invalid table name", async () => {
const tool = tools.find((t) => t.name === "mysql_stats_percentiles")!;
const result = (await tool.handler(
{
table: "123-invalid",
column: "amount",
},
mockContext,
)) as { success: boolean; error: string };
expect(result.success).toBe(false);
expect(result.error).toContain("Invalid table name");
});
it("should reject invalid column name", async () => {
const tool = tools.find((t) => t.name === "mysql_stats_percentiles")!;
const result = (await tool.handler(
{
table: "orders",
column: "bad-col",
},
mockContext,
)) as { success: boolean; error: string };
expect(result.success).toBe(false);
expect(result.error).toContain("Invalid column name");
});
it("should return empty percentiles when table is empty", async () => {
mockAdapter.executeQuery.mockResolvedValue(
createMockQueryResult([{ cnt: 0 }]),
);
const tool = tools.find((t) => t.name === "mysql_stats_percentiles")!;
const result = (await tool.handler(
{
table: "empty_table",
column: "value",
},
mockContext,
)) as { totalCount: number };
expect(result.totalCount).toBe(0);
});
});
describe("mysql_stats_correlation", () => {
it("should reject invalid table name", async () => {
const tool = tools.find((t) => t.name === "mysql_stats_correlation")!;
const result = (await tool.handler(
{
table: "123-bad",
column1: "x",
column2: "y",
},
mockContext,
)) as { success: boolean; error: string };
expect(result.success).toBe(false);
expect(result.error).toContain("Invalid table name");
});
it("should reject invalid column names", async () => {
const tool = tools.find((t) => t.name === "mysql_stats_correlation")!;
const result = (await tool.handler(
{
table: "data",
column1: "bad-col",
column2: "y",
},
mockContext,
)) as { success: boolean; error: string };
expect(result.success).toBe(false);
expect(result.error).toContain("Invalid column name");
});
});
describe("mysql_stats_distribution", () => {
it("should reject invalid table name", async () => {
const tool = tools.find((t) => t.name === "mysql_stats_distribution")!;
const result = (await tool.handler(
{
table: "123-bad",
column: "amount",
},
mockContext,
)) as { success: boolean; error: string };
expect(result.success).toBe(false);
expect(result.error).toContain("Invalid table name");
});
it("should reject invalid column name", async () => {
const tool = tools.find((t) => t.name === "mysql_stats_distribution")!;
const result = (await tool.handler(
{
table: "orders",
column: "bad-col",
},
mockContext,
)) as { success: boolean; error: string };
expect(result.success).toBe(false);
expect(result.error).toContain("Invalid column name");
});
it("should handle same min/max values", async () => {
mockAdapter.executeQuery.mockResolvedValue(
createMockQueryResult([{ min_val: 50, max_val: 50 }]),
);
const tool = tools.find((t) => t.name === "mysql_stats_distribution")!;
const result = (await tool.handler(
{
table: "constant",
column: "val",
},
mockContext,
)) as { bucketCount: number };
expect(result.bucketCount).toBe(1);
});
});
describe("mysql_stats_time_series", () => {
it("should reject invalid table name", async () => {
const tool = tools.find((t) => t.name === "mysql_stats_time_series")!;
const result = (await tool.handler(
{
table: "123-bad",
valueColumn: "amount",
timeColumn: "created_at",
},
mockContext,
)) as { success: boolean; error: string };
expect(result.success).toBe(false);
expect(result.error).toContain("Invalid table name");
});
it("should reject invalid column names", async () => {
const tool = tools.find((t) => t.name === "mysql_stats_time_series")!;
const result = (await tool.handler(
{
table: "sales",
valueColumn: "bad-col",
timeColumn: "created_at",
},
mockContext,
)) as { success: boolean; error: string };
expect(result.success).toBe(false);
expect(result.error).toContain("Invalid column name");
});
it("should use different interval formats", async () => {
mockAdapter.executeQuery.mockResolvedValue(createMockQueryResult([]));
const tool = tools.find((t) => t.name === "mysql_stats_time_series")!;
await tool.handler(
{
table: "metrics",
valueColumn: "value",
timeColumn: "ts",
interval: "minute",
},
mockContext,
);
const call = mockAdapter.executeQuery.mock.calls[0][0] as string;
expect(call).toContain("%H:%i");
});
});
describe("mysql_stats_regression", () => {
it("should reject invalid table name", async () => {
const tool = tools.find((t) => t.name === "mysql_stats_regression")!;
const result = (await tool.handler(
{
table: "123-bad",
xColumn: "x",
yColumn: "y",
},
mockContext,
)) as { success: boolean; error: string };
expect(result.success).toBe(false);
expect(result.error).toContain("Invalid table name");
});
it("should reject invalid column names", async () => {
const tool = tools.find((t) => t.name === "mysql_stats_regression")!;
const result = (await tool.handler(
{
table: "data",
xColumn: "bad-x",
yColumn: "y",
},
mockContext,
)) as { success: boolean; error: string };
expect(result.success).toBe(false);
expect(result.error).toContain("Invalid column name");
});
it("should return error for insufficient data points", async () => {
mockAdapter.executeQuery.mockResolvedValue(
createMockQueryResult([
{
n: 1,
sum_x: 1,
sum_y: 1,
},
]),
);
const tool = tools.find((t) => t.name === "mysql_stats_regression")!;
const result = (await tool.handler(
{
table: "small_data",
xColumn: "x",
yColumn: "y",
},
mockContext,
)) as { success: boolean; error: string };
expect(result.success).toBe(false);
expect(result.error).toContain("Insufficient");
});
});
describe("mysql_stats_sampling", () => {
it("should reject invalid table name", async () => {
const tool = tools.find((t) => t.name === "mysql_stats_sampling")!;
const result = (await tool.handler(
{
table: "123-bad",
sampleSize: 10,
},
mockContext,
)) as { success: boolean; error: string };
expect(result.success).toBe(false);
expect(result.error).toContain("Invalid table name");
});
it("should reject invalid column names", async () => {
const tool = tools.find((t) => t.name === "mysql_stats_sampling")!;
const result = (await tool.handler(
{
table: "users",
sampleSize: 10,
columns: ["valid", "bad-column"],
},
mockContext,
)) as { success: boolean; error: string };
expect(result.success).toBe(false);
expect(result.error).toContain("Invalid column name");
});
it("should use seed for reproducibility", async () => {
mockAdapter.executeQuery.mockResolvedValue(createMockQueryResult([]));
const tool = tools.find((t) => t.name === "mysql_stats_sampling")!;
await tool.handler(
{
table: "users",
sampleSize: 10,
seed: 42,
},
mockContext,
);
const call = mockAdapter.executeQuery.mock.calls[0][0] as string;
expect(call).toContain("RAND(42)");
});
});
describe("mysql_stats_histogram", () => {
it("should reject invalid table name", async () => {
const tool = tools.find((t) => t.name === "mysql_stats_histogram")!;
const result = (await tool.handler(
{
table: "123-bad",
column: "amount",
},
mockContext,
)) as { success: boolean; error: string };
expect(result.success).toBe(false);
expect(result.error).toContain("Invalid table name");
});
it("should reject invalid column name", async () => {
const tool = tools.find((t) => t.name === "mysql_stats_histogram")!;
const result = (await tool.handler(
{
table: "orders",
column: "bad-col",
},
mockContext,
)) as { success: boolean; error: string };
expect(result.success).toBe(false);
expect(result.error).toContain("Invalid column name");
});
});
});
describe("Stats Nonexistent Table Handling", () => {
let mockAdapter: ReturnType<typeof createMockMySQLAdapter>;
let tools: ReturnType<typeof getStatsTools>;
let mockContext: ReturnType<typeof createMockRequestContext>;
beforeEach(() => {
vi.clearAllMocks();
mockAdapter = createMockMySQLAdapter();
tools = getStatsTools(mockAdapter as unknown as MySQLAdapter);
mockContext = createMockRequestContext();
});
const tableDoesNotExistError = new Error(
"Table 'testdb.nonexistent' doesn't exist",
);
it("mysql_stats_descriptive returns exists:false for nonexistent table", async () => {
mockAdapter.executeQuery.mockRejectedValue(tableDoesNotExistError);
const tool = tools.find((t) => t.name === "mysql_stats_descriptive")!;
const result = (await tool.handler(
{ table: "nonexistent", column: "val" },
mockContext,
)) as { exists: boolean; table: string };
expect(result.exists).toBe(false);
expect(result.table).toBe("nonexistent");
});
it("mysql_stats_percentiles returns exists:false for nonexistent table", async () => {
mockAdapter.executeQuery.mockRejectedValue(tableDoesNotExistError);
const tool = tools.find((t) => t.name === "mysql_stats_percentiles")!;
const result = (await tool.handler(
{ table: "nonexistent", column: "val" },
mockContext,
)) as { exists: boolean; table: string };
expect(result.exists).toBe(false);
expect(result.table).toBe("nonexistent");
});
it("mysql_stats_correlation returns exists:false for nonexistent table", async () => {
mockAdapter.executeQuery.mockRejectedValue(tableDoesNotExistError);
const tool = tools.find((t) => t.name === "mysql_stats_correlation")!;
const result = (await tool.handler(
{ table: "nonexistent", column1: "x", column2: "y" },
mockContext,
)) as { exists: boolean; table: string };
expect(result.exists).toBe(false);
expect(result.table).toBe("nonexistent");
});
it("mysql_stats_distribution returns exists:false for nonexistent table", async () => {
mockAdapter.executeQuery.mockRejectedValue(tableDoesNotExistError);
const tool = tools.find((t) => t.name === "mysql_stats_distribution")!;
const result = (await tool.handler(
{ table: "nonexistent", column: "val" },
mockContext,
)) as { exists: boolean; table: string };
expect(result.exists).toBe(false);
expect(result.table).toBe("nonexistent");
});
it("mysql_stats_time_series returns exists:false for nonexistent table", async () => {
mockAdapter.executeQuery.mockRejectedValue(tableDoesNotExistError);
const tool = tools.find((t) => t.name === "mysql_stats_time_series")!;
const result = (await tool.handler(
{
table: "nonexistent",
valueColumn: "val",
timeColumn: "ts",
},
mockContext,
)) as { exists: boolean; table: string };
expect(result.exists).toBe(false);
expect(result.table).toBe("nonexistent");
});
it("mysql_stats_regression returns exists:false for nonexistent table", async () => {
mockAdapter.executeQuery.mockRejectedValue(tableDoesNotExistError);
const tool = tools.find((t) => t.name === "mysql_stats_regression")!;
const result = (await tool.handler(
{ table: "nonexistent", xColumn: "x", yColumn: "y" },
mockContext,
)) as { exists: boolean; table: string };
expect(result.exists).toBe(false);
expect(result.table).toBe("nonexistent");
});
it("mysql_stats_sampling returns exists:false for nonexistent table", async () => {
mockAdapter.executeQuery.mockRejectedValue(tableDoesNotExistError);
const tool = tools.find((t) => t.name === "mysql_stats_sampling")!;
const result = (await tool.handler(
{ table: "nonexistent", sampleSize: 10 },
mockContext,
)) as { exists: boolean; table: string };
expect(result.exists).toBe(false);
expect(result.table).toBe("nonexistent");
});
it("mysql_stats_histogram returns exists:false for nonexistent table", async () => {
mockAdapter.executeQuery.mockResolvedValue(createMockQueryResult([]));
const tool = tools.find((t) => t.name === "mysql_stats_histogram")!;
const result = (await tool.handler(
{ table: "nonexistent", column: "val" },
mockContext,
)) as { exists: boolean; table: string };
expect(result.exists).toBe(false);
expect(result.table).toBe("nonexistent");
});
it("mysql_stats_descriptive returns success:false for unknown column", async () => {
mockAdapter.executeQuery.mockRejectedValue(
new Error("Unknown column 'bad_col' in 'field list'"),
);
const tool = tools.find((t) => t.name === "mysql_stats_descriptive")!;
const result = (await tool.handler(
{ table: "orders", column: "bad_col" },
mockContext,
)) as { success: boolean; error: string };
expect(result.success).toBe(false);
expect(result.error).toContain("Unknown column");
});
});
describe("Stats Zod Validation Guards", () => {
let tools: ReturnType<typeof getStatsTools>;
let mockContext: ReturnType<typeof createMockRequestContext>;
beforeEach(() => {
vi.clearAllMocks();
const mockAdapter = createMockMySQLAdapter();
tools = getStatsTools(mockAdapter as unknown as MySQLAdapter);
mockContext = createMockRequestContext();
});
it("mysql_stats_time_series returns structured error for invalid interval", async () => {
const tool = tools.find((t) => t.name === "mysql_stats_time_series")!;
const result = (await tool.handler(
{
table: "sales",
valueColumn: "amount",
timeColumn: "created_at",
interval: "invalid_interval",
},
mockContext,
)) as { success: boolean; error: string };
expect(result.success).toBe(false);
expect(result.error).toContain("Invalid interval");
expect(result.error).toContain("invalid_interval");
});
it("mysql_stats_time_series returns structured error for invalid aggregation", async () => {
const tool = tools.find((t) => t.name === "mysql_stats_time_series")!;
const result = (await tool.handler(
{
table: "sales",
valueColumn: "amount",
timeColumn: "created_at",
aggregation: "invalid_agg",
},
mockContext,
)) as { success: boolean; error: string };
expect(result.success).toBe(false);
expect(result.error).toContain("Invalid aggregation");
expect(result.error).toContain("invalid_agg");
});
it("mysql_stats_sampling returns structured error for negative sampleSize", async () => {
const tool = tools.find((t) => t.name === "mysql_stats_sampling")!;
const result = (await tool.handler(
{
table: "users",
sampleSize: -1,
},
mockContext,
)) as { success: boolean; error: string };
expect(result.success).toBe(false);
expect(result.error).toContain("sampleSize");
});
it("mysql_stats_correlation returns structured error for missing required fields", async () => {
const tool = tools.find((t) => t.name === "mysql_stats_correlation")!;
const result = (await tool.handler({}, mockContext)) as {
success: boolean;
error: string;
};
expect(result.success).toBe(false);
expect(result.error).toBeDefined();
});
it("mysql_stats_regression returns structured error for missing required fields", async () => {
const tool = tools.find((t) => t.name === "mysql_stats_regression")!;
const result = (await tool.handler({}, mockContext)) as {
success: boolean;
error: string;
};
expect(result.success).toBe(false);
expect(result.error).toBeDefined();
});
it("mysql_stats_histogram returns structured error for missing required fields", async () => {
const tool = tools.find((t) => t.name === "mysql_stats_histogram")!;
const result = (await tool.handler({}, mockContext)) as {
success: boolean;
error: string;
};
expect(result.success).toBe(false);
expect(result.error).toBeDefined();
});
});