Skip to content

Commit 4b94c00

Browse files
committed
test(source/alloydbpg): create MCP integration tests
1 parent a8b0611 commit 4b94c00

File tree

3 files changed

+1937
-90
lines changed

3 files changed

+1937
-90
lines changed

tests/alloydbpg/alloydb_pg_integration_test.go

Lines changed: 1 addition & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -16,106 +16,17 @@ package alloydbpg
1616

1717
import (
1818
"context"
19-
"fmt"
20-
"net"
21-
"os"
2219
"regexp"
2320
"strings"
2421
"testing"
2522
"time"
2623

27-
"cloud.google.com/go/alloydbconn"
2824
"github.com/google/uuid"
2925
"github.com/googleapis/genai-toolbox/internal/testutils"
3026
"github.com/googleapis/genai-toolbox/tests"
31-
"github.com/jackc/pgx/v5/pgxpool"
3227
)
3328

34-
var (
35-
AlloyDBPostgresSourceType = "alloydb-postgres"
36-
AlloyDBPostgresToolType = "postgres-sql"
37-
AlloyDBPostgresProject = os.Getenv("ALLOYDB_POSTGRES_PROJECT")
38-
AlloyDBPostgresRegion = os.Getenv("ALLOYDB_POSTGRES_REGION")
39-
AlloyDBPostgresCluster = os.Getenv("ALLOYDB_POSTGRES_CLUSTER")
40-
AlloyDBPostgresInstance = os.Getenv("ALLOYDB_POSTGRES_INSTANCE")
41-
AlloyDBPostgresDatabase = os.Getenv("ALLOYDB_POSTGRES_DATABASE")
42-
AlloyDBPostgresUser = os.Getenv("ALLOYDB_POSTGRES_USER")
43-
AlloyDBPostgresPass = os.Getenv("ALLOYDB_POSTGRES_PASSWORD")
44-
)
45-
46-
func getAlloyDBPgVars(t *testing.T) map[string]any {
47-
switch "" {
48-
case AlloyDBPostgresProject:
49-
t.Fatal("'ALLOYDB_POSTGRES_PROJECT' not set")
50-
case AlloyDBPostgresRegion:
51-
t.Fatal("'ALLOYDB_POSTGRES_REGION' not set")
52-
case AlloyDBPostgresCluster:
53-
t.Fatal("'ALLOYDB_POSTGRES_CLUSTER' not set")
54-
case AlloyDBPostgresInstance:
55-
t.Fatal("'ALLOYDB_POSTGRES_INSTANCE' not set")
56-
case AlloyDBPostgresDatabase:
57-
t.Fatal("'ALLOYDB_POSTGRES_DATABASE' not set")
58-
case AlloyDBPostgresUser:
59-
t.Fatal("'ALLOYDB_POSTGRES_USER' not set")
60-
case AlloyDBPostgresPass:
61-
t.Fatal("'ALLOYDB_POSTGRES_PASSWORD' not set")
62-
}
63-
return map[string]any{
64-
"type": AlloyDBPostgresSourceType,
65-
"project": AlloyDBPostgresProject,
66-
"cluster": AlloyDBPostgresCluster,
67-
"instance": AlloyDBPostgresInstance,
68-
"region": AlloyDBPostgresRegion,
69-
"database": AlloyDBPostgresDatabase,
70-
"user": AlloyDBPostgresUser,
71-
"password": AlloyDBPostgresPass,
72-
}
73-
}
74-
75-
// Copied over from alloydb_pg.go
76-
func getAlloyDBDialOpts(ipType string) ([]alloydbconn.DialOption, error) {
77-
switch strings.ToLower(ipType) {
78-
case "private":
79-
return []alloydbconn.DialOption{alloydbconn.WithPrivateIP()}, nil
80-
case "public":
81-
return []alloydbconn.DialOption{alloydbconn.WithPublicIP()}, nil
82-
default:
83-
return nil, fmt.Errorf("invalid ipType %s", ipType)
84-
}
85-
}
86-
87-
// Copied over from alloydb_pg.go
88-
func initAlloyDBPgConnectionPool(project, region, cluster, instance, ipType, user, pass, dbname string) (*pgxpool.Pool, error) {
89-
// Configure the driver to connect to the database
90-
dsn := fmt.Sprintf("user=%s password=%s dbname=%s sslmode=disable", user, pass, dbname)
91-
config, err := pgxpool.ParseConfig(dsn)
92-
if err != nil {
93-
return nil, fmt.Errorf("unable to parse connection uri: %w", err)
94-
}
95-
96-
// Create a new dialer with options
97-
dialOpts, err := getAlloyDBDialOpts(ipType)
98-
if err != nil {
99-
return nil, err
100-
}
101-
d, err := alloydbconn.NewDialer(context.Background(), alloydbconn.WithDefaultDialOptions(dialOpts...))
102-
if err != nil {
103-
return nil, fmt.Errorf("unable to parse connection uri: %w", err)
104-
}
105-
106-
// Tell the driver to use the AlloyDB Go Connector to create connections
107-
i := fmt.Sprintf("projects/%s/locations/%s/clusters/%s/instances/%s", project, region, cluster, instance)
108-
config.ConnConfig.DialFunc = func(ctx context.Context, _ string, instance string) (net.Conn, error) {
109-
return d.Dial(ctx, i)
110-
}
111-
112-
// Interact with the driver directly as you normally would
113-
pool, err := pgxpool.NewWithConfig(context.Background(), config)
114-
if err != nil {
115-
return nil, err
116-
}
117-
return pool, nil
118-
}
29+
// Variables and connection helpers moved to alloydb_pg_mcp_test.go
11930

12031
func TestAlloyDBPgToolEndpoints(t *testing.T) {
12132
sourceConfig := getAlloyDBPgVars(t)
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package alloydbpg
16+
17+
import (
18+
"context"
19+
"fmt"
20+
"net"
21+
"os"
22+
"regexp"
23+
"strings"
24+
"testing"
25+
"time"
26+
27+
"cloud.google.com/go/alloydbconn"
28+
"github.com/google/uuid"
29+
"github.com/googleapis/genai-toolbox/internal/testutils"
30+
"github.com/googleapis/genai-toolbox/tests"
31+
"github.com/jackc/pgx/v5/pgxpool"
32+
)
33+
34+
var (
35+
AlloyDBPostgresSourceType = "alloydb-postgres"
36+
AlloyDBPostgresToolType = "postgres-sql"
37+
AlloyDBPostgresProject = os.Getenv("ALLOYDB_POSTGRES_PROJECT")
38+
AlloyDBPostgresRegion = os.Getenv("ALLOYDB_POSTGRES_REGION")
39+
AlloyDBPostgresCluster = os.Getenv("ALLOYDB_POSTGRES_CLUSTER")
40+
AlloyDBPostgresInstance = os.Getenv("ALLOYDB_POSTGRES_INSTANCE")
41+
AlloyDBPostgresDatabase = os.Getenv("ALLOYDB_POSTGRES_DATABASE")
42+
AlloyDBPostgresUser = os.Getenv("ALLOYDB_POSTGRES_USER")
43+
AlloyDBPostgresPass = os.Getenv("ALLOYDB_POSTGRES_PASSWORD")
44+
)
45+
46+
func getAlloyDBPgVars(t *testing.T) map[string]any {
47+
switch "" {
48+
case AlloyDBPostgresProject:
49+
t.Fatal("'ALLOYDB_POSTGRES_PROJECT' not set")
50+
case AlloyDBPostgresRegion:
51+
t.Fatal("'ALLOYDB_POSTGRES_REGION' not set")
52+
case AlloyDBPostgresCluster:
53+
t.Fatal("'ALLOYDB_POSTGRES_CLUSTER' not set")
54+
case AlloyDBPostgresInstance:
55+
t.Fatal("'ALLOYDB_POSTGRES_INSTANCE' not set")
56+
case AlloyDBPostgresDatabase:
57+
t.Fatal("'ALLOYDB_POSTGRES_DATABASE' not set")
58+
case AlloyDBPostgresUser:
59+
t.Fatal("'ALLOYDB_POSTGRES_USER' not set")
60+
case AlloyDBPostgresPass:
61+
t.Fatal("'ALLOYDB_POSTGRES_PASSWORD' not set")
62+
}
63+
return map[string]any{
64+
"type": AlloyDBPostgresSourceType,
65+
"project": AlloyDBPostgresProject,
66+
"cluster": AlloyDBPostgresCluster,
67+
"instance": AlloyDBPostgresInstance,
68+
"region": AlloyDBPostgresRegion,
69+
"database": AlloyDBPostgresDatabase,
70+
"user": AlloyDBPostgresUser,
71+
"password": AlloyDBPostgresPass,
72+
}
73+
}
74+
75+
func getAlloyDBDialOpts(ipType string) ([]alloydbconn.DialOption, error) {
76+
switch strings.ToLower(ipType) {
77+
case "private":
78+
return []alloydbconn.DialOption{alloydbconn.WithPrivateIP()}, nil
79+
case "public":
80+
return []alloydbconn.DialOption{alloydbconn.WithPublicIP()}, nil
81+
default:
82+
return nil, fmt.Errorf("invalid ipType %s", ipType)
83+
}
84+
}
85+
86+
func initAlloyDBPgConnectionPool(project, region, cluster, instance, ipType, user, pass, dbname string) (*pgxpool.Pool, error) {
87+
dsn := fmt.Sprintf("user=%s password=%s dbname=%s sslmode=disable", user, pass, dbname)
88+
config, err := pgxpool.ParseConfig(dsn)
89+
if err != nil {
90+
return nil, fmt.Errorf("unable to parse connection uri: %w", err)
91+
}
92+
93+
dialOpts, err := getAlloyDBDialOpts(ipType)
94+
if err != nil {
95+
return nil, err
96+
}
97+
d, err := alloydbconn.NewDialer(context.Background(), alloydbconn.WithDefaultDialOptions(dialOpts...))
98+
if err != nil {
99+
return nil, fmt.Errorf("unable to parse connection uri: %w", err)
100+
}
101+
102+
i := fmt.Sprintf("projects/%s/locations/%s/clusters/%s/instances/%s", project, region, cluster, instance)
103+
config.ConnConfig.DialFunc = func(ctx context.Context, _ string, instance string) (net.Conn, error) {
104+
return d.Dial(ctx, i)
105+
}
106+
107+
pool, err := pgxpool.NewWithConfig(context.Background(), config)
108+
if err != nil {
109+
return nil, err
110+
}
111+
return pool, nil
112+
}
113+
114+
func TestAlloyDBPgListTools(t *testing.T) {
115+
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
116+
defer cancel()
117+
118+
args := []string{"--prebuilt", "alloydb-postgres"}
119+
120+
cmd, cleanup, err := tests.StartCmd(ctx, map[string]any{}, args...)
121+
if err != nil {
122+
t.Fatalf("command initialization returned an error: %v", err)
123+
}
124+
defer cleanup()
125+
126+
waitCtx, cancelWait := context.WithTimeout(ctx, 10*time.Second)
127+
defer cancelWait()
128+
out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out)
129+
if err != nil {
130+
t.Logf("toolbox command logs: \n%s", out)
131+
t.Fatalf("toolbox didn't start successfully: %v", err)
132+
}
133+
134+
// We expect standard Postgres tools to be listed
135+
// This is a subset check, full list validation can be added if needed
136+
_, tools, err := tests.GetMCPToolsList(t, nil)
137+
if err != nil {
138+
t.Fatalf("failed to get tools list: %v", err)
139+
}
140+
141+
if len(tools) == 0 {
142+
t.Errorf("expected tools to be listed, got none")
143+
}
144+
}
145+
146+
func TestAlloyDBPgCallTool(t *testing.T) {
147+
getAlloyDBPgVars(t)
148+
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
149+
defer cancel()
150+
151+
pool, err := initAlloyDBPgConnectionPool(AlloyDBPostgresProject, AlloyDBPostgresRegion, AlloyDBPostgresCluster, AlloyDBPostgresInstance, "public", AlloyDBPostgresUser, AlloyDBPostgresPass, AlloyDBPostgresDatabase)
152+
if err != nil {
153+
t.Fatalf("unable to create AlloyDB connection pool: %s", err)
154+
}
155+
defer pool.Close()
156+
157+
uniqueID := strings.ReplaceAll(uuid.New().String(), "-", "")
158+
159+
args := []string{"--prebuilt", "alloydb-postgres"}
160+
161+
cmd, cleanup, err := tests.StartCmd(ctx, map[string]any{}, args...)
162+
if err != nil {
163+
t.Fatalf("command initialization returned an error: %v", err)
164+
}
165+
defer cleanup()
166+
167+
waitCtx, cancelWait := context.WithTimeout(ctx, 10*time.Second)
168+
defer cancelWait()
169+
out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out)
170+
if err != nil {
171+
t.Logf("toolbox command logs: \n%s", out)
172+
t.Fatalf("toolbox didn't start successfully: %v", err)
173+
}
174+
175+
// Run shared Postgres tests
176+
tests.RunMCPPostgresListViewsTest(t, ctx, pool)
177+
tests.RunMCPPostgresListSchemasTest(t, ctx, pool, AlloyDBPostgresUser, uniqueID)
178+
tests.RunMCPPostgresListActiveQueriesTest(t, ctx, pool)
179+
tests.RunMCPPostgresListAvailableExtensionsTest(t)
180+
tests.RunMCPPostgresListInstalledExtensionsTest(t)
181+
tests.RunMCPPostgresDatabaseOverviewTest(t, ctx, pool)
182+
tests.RunMCPPostgresListTriggersTest(t, ctx, pool)
183+
tests.RunMCPPostgresListIndexesTest(t, ctx, pool)
184+
tests.RunMCPPostgresListSequencesTest(t, ctx, pool)
185+
tests.RunMCPPostgresLongRunningTransactionsTest(t, ctx, pool)
186+
tests.RunMCPPostgresListLocksTest(t, ctx, pool)
187+
tests.RunMCPPostgresReplicationStatsTest(t, ctx, pool)
188+
tests.RunMCPPostgresGetColumnCardinalityTest(t, ctx, pool)
189+
tests.RunMCPPostgresListTableStatsTest(t, ctx, pool)
190+
tests.RunMCPPostgresListPublicationTablesTest(t, ctx, pool)
191+
tests.RunMCPPostgresListTableSpacesTest(t)
192+
tests.RunMCPPostgresListPgSettingsTest(t, ctx, pool)
193+
tests.RunMCPPostgresListDatabaseStatsTest(t, ctx, pool)
194+
tests.RunMCPPostgresListRolesTest(t, ctx, pool)
195+
tests.RunMCPPostgresListStoredProcedureTest(t, ctx, pool)
196+
197+
toolsToTest := map[string]string{
198+
"list_autovacuum_configurations": `{}`,
199+
"list_memory_configurations": `{}`,
200+
"list_top_bloated_tables": `{"limit": 10}`,
201+
"list_replication_slots": `{}`,
202+
"list_invalid_indexes": `{}`,
203+
"get_query_plan": `{"query": "SELECT 1"}`,
204+
}
205+
tests.RunMCPStatementToolsTest(t, toolsToTest)
206+
}

0 commit comments

Comments
 (0)