Skip to content

Commit 4ad9aba

Browse files
author
CodeBuddy Attribution Bot
committed
fix(attribution): MCP 工具:MySQL 开通 READY 后 SQL 操作仍超时 - 缺乏数据库就绪检测 (issue_mnry7ddi_rn8emg)
1 parent 65c585d commit 4ad9aba

2 files changed

Lines changed: 381 additions & 108 deletions

File tree

mcp/src/tools/databaseSQL.test.ts

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,126 @@ describe("SQL database tools", () => {
220220
});
221221
});
222222

223+
it("querySqlDatabase(runQuery) waits for SQL endpoint after control plane reports ready", async () => {
224+
let runSqlAttempt = 0;
225+
mockCommonServiceCall.mockImplementation(async ({ Action, Param }: { Action: string; Param: any }) => {
226+
if (Action === "RunSql") {
227+
runSqlAttempt += 1;
228+
if (runSqlAttempt === 1) {
229+
throw Object.assign(new Error("connect ETIMEDOUT"), {
230+
code: "ETIMEDOUT",
231+
});
232+
}
233+
234+
return {
235+
RequestId: `req-run-${runSqlAttempt}`,
236+
RowsAffected: 0,
237+
Items: ['{"ready":true}'],
238+
Infos: ['{"Field":"ready"}'],
239+
};
240+
}
241+
242+
if (Action === "DescribeCreateMySQLResult") {
243+
return {
244+
RequestId: "req-create",
245+
Data: {
246+
Status: "success",
247+
},
248+
};
249+
}
250+
251+
if (Action === "DescribeMySQLClusterDetail") {
252+
return {
253+
RequestId: "req-cluster",
254+
Data: {
255+
DbInfo: {
256+
ClusterStatus: "running",
257+
},
258+
},
259+
};
260+
}
261+
262+
throw new Error(`unexpected action: ${Action}`);
263+
});
264+
265+
const { tools } = createMockServer();
266+
const result = await tools.querySqlDatabase.handler({
267+
action: "runQuery",
268+
sql: "SELECT * FROM demo",
269+
});
270+
const payload = JSON.parse(result.content[0].text);
271+
272+
expect(payload).toMatchObject({
273+
success: true,
274+
data: {
275+
rows: [{ ready: true }],
276+
},
277+
});
278+
expect(runSqlAttempt).toBe(3);
279+
});
280+
281+
it("manageSqlDatabase(runStatement) returns MYSQL_NOT_READY when SQL endpoint never warms up", async () => {
282+
mockCommonServiceCall.mockImplementation(async ({ Action }: { Action: string }) => {
283+
if (Action === "RunSql") {
284+
throw Object.assign(new Error("connect ETIMEDOUT"), {
285+
code: "ETIMEDOUT",
286+
});
287+
}
288+
289+
if (Action === "DescribeCreateMySQLResult") {
290+
return {
291+
RequestId: "req-create",
292+
Data: {
293+
Status: "success",
294+
},
295+
};
296+
}
297+
298+
if (Action === "DescribeMySQLClusterDetail") {
299+
return {
300+
RequestId: "req-cluster",
301+
Data: {
302+
DbInfo: {
303+
ClusterStatus: "running",
304+
},
305+
},
306+
};
307+
}
308+
309+
throw new Error(`unexpected action: ${Action}`);
310+
});
311+
312+
const originalNow = Date.now;
313+
let now = 0;
314+
const dateNowSpy = vi.spyOn(Date, "now").mockImplementation(() => now);
315+
const setTimeoutSpy = vi
316+
.spyOn(globalThis, "setTimeout")
317+
.mockImplementation(((fn: (...args: any[]) => void, ms?: number, ...args: any[]) => {
318+
now += typeof ms === "number" ? ms : 0;
319+
fn(...args);
320+
return 0 as any;
321+
}) as typeof setTimeout);
322+
323+
const { tools } = createMockServer();
324+
const result = await tools.manageSqlDatabase.handler({
325+
action: "runStatement",
326+
sql: "CREATE TABLE demo(id INT)",
327+
});
328+
const payload = JSON.parse(result.content[0].text);
329+
330+
expect(payload).toMatchObject({
331+
success: false,
332+
errorCode: "MYSQL_NOT_READY",
333+
data: {
334+
probeSql: "SELECT 1",
335+
},
336+
});
337+
338+
setTimeoutSpy.mockRestore();
339+
dateNowSpy.mockRestore();
340+
Date.now = originalNow;
341+
});
342+
223343
it("querySqlDatabase(describeTaskStatus) maps success to READY", async () => {
224344
mockCommonServiceCall.mockImplementation(async ({ Action }: { Action: string }) => {
225345
if (Action === "DescribeMySQLTaskStatus") {

0 commit comments

Comments
 (0)