Skip to content

Commit 5e2de5d

Browse files
committed
Use postgres template to create databases faster
1 parent 6d3d020 commit 5e2de5d

File tree

1 file changed

+60
-13
lines changed

1 file changed

+60
-13
lines changed

internal/database/testutil.go

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,85 @@ import (
44
"context"
55
"crypto/rand"
66
"encoding/binary"
7+
"errors"
78
"fmt"
89
"testing"
910
"time"
1011

1112
"github.com/jackc/pgx/v5"
13+
"github.com/jackc/pgx/v5/pgconn"
1214
"github.com/stretchr/testify/require"
1315
)
1416

15-
// NewTestDB creates an isolated PostgreSQL database for each test.
16-
// Each test gets its own database with a random name, eliminating all coordination issues.
17+
const templateDBName = "mcp_registry_test_template"
18+
19+
// ensureTemplateDB creates a template database with migrations applied
20+
// Multiple processes may call this, so we handle race conditions
21+
func ensureTemplateDB(ctx context.Context, adminConn *pgx.Conn) error {
22+
// Check if template exists
23+
var exists bool
24+
err := adminConn.QueryRow(ctx, "SELECT EXISTS(SELECT 1 FROM pg_database WHERE datname = $1)", templateDBName).Scan(&exists)
25+
if err != nil {
26+
return fmt.Errorf("failed to check template database: %w", err)
27+
}
28+
29+
if exists {
30+
// Template already exists
31+
return nil
32+
}
33+
34+
// Create template database
35+
_, err = adminConn.Exec(ctx, fmt.Sprintf("CREATE DATABASE %s", templateDBName))
36+
if err != nil {
37+
// Ignore duplicate error - another process created it concurrently
38+
var pgErr *pgconn.PgError
39+
if errors.As(err, &pgErr) && pgErr.Code == "23505" {
40+
return nil
41+
}
42+
return fmt.Errorf("failed to create template database: %w", err)
43+
}
44+
45+
// Connect to template and run migrations
46+
templateURI := fmt.Sprintf("postgres://mcpregistry:mcpregistry@localhost:5432/%s?sslmode=disable", templateDBName)
47+
templateDB, err := NewPostgreSQL(ctx, templateURI)
48+
if err != nil {
49+
return fmt.Errorf("failed to connect to template database: %w", err)
50+
}
51+
defer templateDB.Close()
52+
53+
// Migrations run automatically in NewPostgreSQL
54+
return nil
55+
}
56+
57+
// NewTestDB creates an isolated PostgreSQL database for each test by copying a template.
58+
// The template database has migrations pre-applied, so each test is fast.
1759
// Requires PostgreSQL to be running on localhost:5432 (e.g., via docker-compose).
1860
func NewTestDB(t *testing.T) Database {
1961
t.Helper()
2062

2163
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
2264
defer cancel()
2365

66+
// Connect to postgres database
67+
adminURI := "postgres://mcpregistry:mcpregistry@localhost:5432/postgres?sslmode=disable"
68+
adminConn, err := pgx.Connect(ctx, adminURI)
69+
require.NoError(t, err, "Failed to connect to PostgreSQL. Make sure PostgreSQL is running via: docker-compose up -d postgres")
70+
defer adminConn.Close(ctx)
71+
72+
// Ensure template database exists with migrations
73+
err = ensureTemplateDB(ctx, adminConn)
74+
require.NoError(t, err, "Failed to initialize template database")
75+
2476
// Generate unique database name for this test
2577
var randomBytes [8]byte
26-
_, err := rand.Read(randomBytes[:])
78+
_, err = rand.Read(randomBytes[:])
2779
require.NoError(t, err, "Failed to generate random database id")
2880
randomInt := binary.BigEndian.Uint64(randomBytes[:])
2981
dbName := fmt.Sprintf("test_%d", randomInt)
3082

31-
// Connect to postgres database to create test database
32-
adminURI := "postgres://mcpregistry:mcpregistry@localhost:5432/postgres?sslmode=disable"
33-
adminConn, err := pgx.Connect(ctx, adminURI)
34-
require.NoError(t, err, "Failed to connect to PostgreSQL. Make sure PostgreSQL is running via: docker-compose up -d postgres")
35-
defer adminConn.Close(ctx)
36-
37-
// Create test database
38-
_, err = adminConn.Exec(ctx, fmt.Sprintf("CREATE DATABASE %s", dbName))
39-
require.NoError(t, err, "Failed to create test database")
83+
// Create test database from template (fast - just copies files)
84+
_, err = adminConn.Exec(ctx, fmt.Sprintf("CREATE DATABASE %s TEMPLATE %s", dbName, templateDBName))
85+
require.NoError(t, err, "Failed to create test database from template")
4086

4187
// Register cleanup to drop database
4288
t.Cleanup(func() {
@@ -53,8 +99,9 @@ func NewTestDB(t *testing.T) Database {
5399
_, _ = adminConn.Exec(cleanupCtx, fmt.Sprintf("DROP DATABASE IF EXISTS %s", dbName))
54100
})
55101

56-
// Connect to test database
102+
// Connect to test database (no migrations needed - copied from template)
57103
testURI := fmt.Sprintf("postgres://mcpregistry:mcpregistry@localhost:5432/%s?sslmode=disable", dbName)
104+
58105
db, err := NewPostgreSQL(ctx, testURI)
59106
require.NoError(t, err, "Failed to connect to test database")
60107

0 commit comments

Comments
 (0)