Skip to content

Commit d7244d6

Browse files
committed
fix(tests/spanner): implement resource cleanup for graphs and tables
1 parent 4baf758 commit d7244d6

File tree

2 files changed

+89
-59
lines changed

2 files changed

+89
-59
lines changed

tests/common.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ import (
2626

2727
"cloud.google.com/go/bigquery"
2828
"cloud.google.com/go/bigtable"
29+
"cloud.google.com/go/spanner"
30+
database "cloud.google.com/go/spanner/admin/database/apiv1"
31+
"cloud.google.com/go/spanner/admin/database/apiv1/databasepb"
2932
"github.com/google/go-cmp/cmp"
3033
"github.com/googleapis/genai-toolbox/internal/server"
3134
"github.com/googleapis/genai-toolbox/internal/sources/cloudsqlmysql"
@@ -1124,3 +1127,61 @@ func CleanupBigtableTables(t *testing.T, ctx context.Context, adminClient *bigta
11241127
}
11251128
}
11261129
}
1130+
1131+
func CleanupSpannerResources(t *testing.T, ctx context.Context, adminClient *database.DatabaseAdminClient, dataClient *spanner.Client, dbString string, uniqueID string) {
1132+
t.Logf("DEBUG: Starting Spanner cleanup for uniqueID: %s", uniqueID)
1133+
1134+
var ddlStatements []string
1135+
pattern := "%" + uniqueID + "%"
1136+
1137+
//Identify Property Graphs
1138+
graphQuery := `SELECT property_graph_name FROM INFORMATION_SCHEMA.PROPERTY_GRAPHS WHERE property_graph_name LIKE @pattern`
1139+
gIter := dataClient.Single().Query(ctx, spanner.Statement{
1140+
SQL: graphQuery,
1141+
Params: map[string]any{"pattern": pattern},
1142+
})
1143+
if err := gIter.Do(func(row *spanner.Row) error {
1144+
var name string
1145+
if err := row.Column(0, &name); err == nil {
1146+
ddlStatements = append(ddlStatements, fmt.Sprintf("DROP PROPERTY GRAPH `%s` ", name))
1147+
}
1148+
return nil
1149+
}); err != nil {
1150+
t.Errorf("Cleanup: failed to iterate graphs: %v", err)
1151+
}
1152+
1153+
//Identify Tables
1154+
tableQuery := `SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = '' AND table_name LIKE @pattern`
1155+
tIter := dataClient.Single().Query(ctx, spanner.Statement{
1156+
SQL: tableQuery,
1157+
Params: map[string]any{"pattern": pattern},
1158+
})
1159+
if err := tIter.Do(func(row *spanner.Row) error {
1160+
var name string
1161+
if err := row.Column(0, &name); err == nil {
1162+
ddlStatements = append(ddlStatements, fmt.Sprintf("DROP TABLE `%s` ", name))
1163+
}
1164+
return nil
1165+
}); err != nil {
1166+
t.Errorf("Cleanup: failed to iterate tables: %v", err)
1167+
}
1168+
1169+
if len(ddlStatements) > 0 {
1170+
t.Logf("DEBUG: SPANNER CLEANUP: Dropping %d stale resources: %v", len(ddlStatements), ddlStatements)
1171+
op, err := adminClient.UpdateDatabaseDdl(ctx, &databasepb.UpdateDatabaseDdlRequest{
1172+
Database: dbString,
1173+
Statements: ddlStatements,
1174+
})
1175+
if err != nil {
1176+
t.Errorf("DEBUG: Failed to start cleanup operation: %v", err)
1177+
return
1178+
}
1179+
if err := op.Wait(ctx); err != nil {
1180+
t.Errorf("DEBUG: Cleanup operation failed to complete: %v", err)
1181+
} else {
1182+
t.Logf("DEBUG: SPANNER CLEANUP SUCCESS for ID: %s", uniqueID)
1183+
}
1184+
} else {
1185+
t.Logf("DEBUG: SPANNER CLEANUP: No resources found for ID: %s", uniqueID)
1186+
}
1187+
}

tests/spanner/spanner_integration_test.go

Lines changed: 28 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -94,39 +94,43 @@ func TestSpannerToolEndpoints(t *testing.T) {
9494
t.Fatalf("unable to create Spanner client: %s", err)
9595
}
9696

97-
// create table name with UUID
98-
tableNameParam := "param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
99-
tableNameAuth := "auth_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
100-
tableNameTemplateParam := "template_param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
101-
102-
// set up data for param tool
103-
createParamTableStmt, insertParamTableStmt, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, paramTestParams := getSpannerParamToolInfo(tableNameParam)
97+
uniqueID := strings.ReplaceAll(uuid.New().String(), "-", "")
10498
dbString := fmt.Sprintf(
10599
"projects/%s/instances/%s/databases/%s",
106100
SpannerProject,
107101
SpannerInstance,
108102
SpannerDatabase,
109103
)
110-
teardownTable1 := setupSpannerTable(t, ctx, adminClient, dataClient, createParamTableStmt, insertParamTableStmt, tableNameParam, dbString, paramTestParams)
111-
defer teardownTable1(t)
104+
105+
t.Cleanup(func() {
106+
tests.CleanupSpannerResources(t, context.Background(), adminClient, dataClient, dbString, uniqueID)
107+
})
108+
109+
t.Logf("DEBUG: Starting test run with isolated ID: %s", uniqueID)
110+
111+
// create table name with UUID
112+
tableNameParam := "param_table_" + uniqueID
113+
tableNameAuth := "auth_table_" + uniqueID
114+
tableNameTemplateParam := "template_param_table_" + uniqueID
115+
116+
// set up data for param tool
117+
createParamTableStmt, insertParamTableStmt, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, paramTestParams := getSpannerParamToolInfo(tableNameParam)
118+
setupSpannerTable(t, ctx, adminClient, dataClient, createParamTableStmt, insertParamTableStmt, tableNameParam, dbString, paramTestParams)
112119

113120
// set up data for auth tool
114121
createAuthTableStmt, insertAuthTableStmt, authToolStmt, authTestParams := getSpannerAuthToolInfo(tableNameAuth)
115-
teardownTable2 := setupSpannerTable(t, ctx, adminClient, dataClient, createAuthTableStmt, insertAuthTableStmt, tableNameAuth, dbString, authTestParams)
116-
defer teardownTable2(t)
122+
setupSpannerTable(t, ctx, adminClient, dataClient, createAuthTableStmt, insertAuthTableStmt, tableNameAuth, dbString, authTestParams)
117123

118124
// set up data for template param tool
119125
createStatementTmpl := fmt.Sprintf("CREATE TABLE %s (id INT64, name STRING(MAX), age INT64) PRIMARY KEY (id)", tableNameTemplateParam)
120-
teardownTableTmpl := setupSpannerTable(t, ctx, adminClient, dataClient, createStatementTmpl, "", tableNameTemplateParam, dbString, nil)
121-
defer teardownTableTmpl(t)
126+
setupSpannerTable(t, ctx, adminClient, dataClient, createStatementTmpl, "", tableNameTemplateParam, dbString, nil)
122127

123128
// set up for graph tool
124-
nodeTableName := "node_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
129+
nodeTableName := "node_table_" + uniqueID
125130
createNodeStatementTmpl := fmt.Sprintf("CREATE TABLE %s (id INT64 NOT NULL) PRIMARY KEY (id)", nodeTableName)
126-
teardownNodeTableTmpl := setupSpannerTable(t, ctx, adminClient, dataClient, createNodeStatementTmpl, "", nodeTableName, dbString, nil)
127-
defer teardownNodeTableTmpl(t)
131+
setupSpannerTable(t, ctx, adminClient, dataClient, createNodeStatementTmpl, "", nodeTableName, dbString, nil)
128132

129-
edgeTableName := "edge_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
133+
edgeTableName := "edge_table_" + uniqueID
130134
createEdgeStatementTmpl := fmt.Sprintf(`
131135
CREATE TABLE %[1]s (
132136
id INT64 NOT NULL,
@@ -135,10 +139,9 @@ func TestSpannerToolEndpoints(t *testing.T) {
135139
) PRIMARY KEY (id, target_id),
136140
INTERLEAVE IN PARENT %[2]s ON DELETE CASCADE
137141
`, edgeTableName, nodeTableName)
138-
teardownEdgeTableTmpl := setupSpannerTable(t, ctx, adminClient, dataClient, createEdgeStatementTmpl, "", edgeTableName, dbString, nil)
139-
defer teardownEdgeTableTmpl(t)
142+
setupSpannerTable(t, ctx, adminClient, dataClient, createEdgeStatementTmpl, "", edgeTableName, dbString, nil)
140143

141-
graphName := "graph_" + strings.ReplaceAll(uuid.New().String(), "-", "")
144+
graphName := "graph_" + uniqueID
142145
createGraphStmt := fmt.Sprintf(`
143146
CREATE PROPERTY GRAPH %[3]s
144147
NODE TABLES (
@@ -151,8 +154,7 @@ func TestSpannerToolEndpoints(t *testing.T) {
151154
LABEL EDGE
152155
)
153156
`, nodeTableName, edgeTableName, graphName)
154-
teardownGraph := setupSpannerGraph(t, ctx, adminClient, createGraphStmt, graphName, dbString)
155-
defer teardownGraph(t)
157+
setupSpannerGraph(t, ctx, adminClient, createGraphStmt, graphName, dbString)
156158

157159
// Write config into a file and pass it to command
158160
toolsFile := tests.GetToolsConfig(sourceConfig, SpannerToolType, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, authToolStmt)
@@ -181,7 +183,7 @@ func TestSpannerToolEndpoints(t *testing.T) {
181183
invokeParamWant := "[{\"id\":\"1\",\"name\":\"Alice\"},{\"id\":\"3\",\"name\":\"Sid\"}]"
182184
accessSchemaWant := "[{\"schema_name\":\"INFORMATION_SCHEMA\"}]"
183185
toolInvokeMyToolById4Want := `[{"id":"4","name":null}]`
184-
mcpMyFailToolWant := `"jsonrpc":"2.0","id":"invoke-fail-tool","result":{"content":[{"type":"text","text":"unable to execute client: unable to parse row: spanner: code = \"InvalidArgument\", desc = \"Syntax error: Unexpected identifier \\\\\\\"SELEC\\\\\\\" [at 1:1]\\\\nSELEC 1;\\\\n^\"`
186+
mcpMyFailToolWant := `"jsonrpc":"2.0","id":"invoke-fail-tool","result":{"content":[{"type":"text","text":"error processing GCP request: unable to parse row: spanner: code = \"InvalidArgument\", desc = \"Syntax error: Unexpected identifier \\\\\\\"SELEC\\\\\\\" [at 1:1]\\\\nSELEC 1;\\\\n^\"`
185187
mcpMyToolId3NameAliceWant := `{"jsonrpc":"2.0","id":"my-tool","result":{"content":[{"type":"text","text":"{\"id\":\"1\",\"name\":\"Alice\"}"},{"type":"text","text":"{\"id\":\"3\",\"name\":\"Sid\"}"}]}}`
186188
mcpSelect1Want := `{"jsonrpc":"2.0","id":"invoke my-auth-required-tool","result":{"content":[{"type":"text","text":"{\"\":\"1\"}"}]}}`
187189
tmplSelectAllWwant := "[{\"age\":\"21\",\"id\":\"1\",\"name\":\"Alex\"},{\"age\":\"100\",\"id\":\"2\",\"name\":\"Alice\"}]"
@@ -235,7 +237,7 @@ func getSpannerAuthToolInfo(tableName string) (string, string, string, map[strin
235237

236238
// setupSpannerTable creates and inserts data into a table of tool
237239
// compatible with spanner-sql tool
238-
func setupSpannerTable(t *testing.T, ctx context.Context, adminClient *database.DatabaseAdminClient, dataClient *spanner.Client, createStatement, insertStatement, tableName, dbString string, params map[string]any) func(*testing.T) {
240+
func setupSpannerTable(t *testing.T, ctx context.Context, adminClient *database.DatabaseAdminClient, dataClient *spanner.Client, createStatement, insertStatement, tableName, dbString string, params map[string]any) {
239241

240242
// Create table
241243
op, err := adminClient.UpdateDatabaseDdl(ctx, &databasepb.UpdateDatabaseDdlRequest{
@@ -264,27 +266,10 @@ func setupSpannerTable(t *testing.T, ctx context.Context, adminClient *database.
264266
t.Fatalf("unable to insert test data: %s", err)
265267
}
266268
}
267-
268-
return func(t *testing.T) {
269-
// tear down test
270-
op, err = adminClient.UpdateDatabaseDdl(ctx, &databasepb.UpdateDatabaseDdlRequest{
271-
Database: dbString,
272-
Statements: []string{fmt.Sprintf("DROP TABLE IF EXISTS %s", tableName)},
273-
})
274-
if err != nil {
275-
t.Errorf("unable to start drop %s operation: %s", tableName, err)
276-
return
277-
}
278-
279-
opErr := op.Wait(ctx)
280-
if opErr != nil {
281-
t.Errorf("Teardown failed: %s", opErr)
282-
}
283-
}
284269
}
285270

286271
// setupSpannerGraph creates a graph and inserts data into it.
287-
func setupSpannerGraph(t *testing.T, ctx context.Context, adminClient *database.DatabaseAdminClient, createStatement, graphName, dbString string) func(*testing.T) {
272+
func setupSpannerGraph(t *testing.T, ctx context.Context, adminClient *database.DatabaseAdminClient, createStatement, graphName, dbString string) {
288273
// Create graph
289274
op, err := adminClient.UpdateDatabaseDdl(ctx, &databasepb.UpdateDatabaseDdlRequest{
290275
Database: dbString,
@@ -297,23 +282,6 @@ func setupSpannerGraph(t *testing.T, ctx context.Context, adminClient *database.
297282
if err != nil {
298283
t.Fatalf("unable to create test graph %s: %s", graphName, err)
299284
}
300-
301-
return func(t *testing.T) {
302-
// tear down test
303-
op, err = adminClient.UpdateDatabaseDdl(ctx, &databasepb.UpdateDatabaseDdlRequest{
304-
Database: dbString,
305-
Statements: []string{fmt.Sprintf("DROP PROPERTY GRAPH IF EXISTS %s", graphName)},
306-
})
307-
if err != nil {
308-
t.Errorf("unable to start drop %s operation: %s", graphName, err)
309-
return
310-
}
311-
312-
opErr := op.Wait(ctx)
313-
if opErr != nil {
314-
t.Errorf("Teardown failed: %s", opErr)
315-
}
316-
}
317285
}
318286

319287
// addSpannerExecuteSqlConfig gets the tools config for `spanner-execute-sql`
@@ -362,6 +330,7 @@ func addSpannerReadOnlyConfig(t *testing.T, config map[string]any) map[string]an
362330
"source": "my-instance",
363331
"description": "Tool to access information schema.",
364332
"statement": "SELECT schema_name FROM `INFORMATION_SCHEMA`.SCHEMATA WHERE schema_name='INFORMATION_SCHEMA';",
333+
"readOnly": true,
365334
}
366335
config["tools"] = tools
367336
return config

0 commit comments

Comments
 (0)