@@ -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