Skip to content

Commit 99b5c62

Browse files
committed
feat(db): add --use-shadow-db flag to test db command
Runs pgTAP tests against an ephemeral shadow database built from migrations, keeping the local dev database untouched. Reuses the existing CreateShadowDatabase/MigrateShadowDatabase machinery from db diff. Uses host networking so pg_prove can reach the shadow container via 127.0.0.1:<shadow_port>.
1 parent 562fced commit 99b5c62

5 files changed

Lines changed: 41 additions & 8 deletions

File tree

cmd/db.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,8 @@ var (
238238
Use: "test [path] ...",
239239
Short: "Tests local database with pgTAP",
240240
RunE: func(cmd *cobra.Command, args []string) error {
241-
return test.Run(cmd.Context(), args, flags.DbConfig, afero.NewOsFs())
241+
useShadow, _ := cmd.Flags().GetBool("use-shadow-db")
242+
return test.Run(cmd.Context(), args, flags.DbConfig, useShadow, afero.NewOsFs())
242243
},
243244
}
244245
)
@@ -349,6 +350,7 @@ func init() {
349350
testFlags.String("db-url", "", "Tests the database specified by the connection string (must be percent-encoded).")
350351
testFlags.Bool("linked", false, "Runs pgTAP tests on the linked project.")
351352
testFlags.Bool("local", true, "Runs pgTAP tests on the local database.")
353+
testFlags.Bool("use-shadow-db", false, "Creates a temporary database from migrations for running tests in isolation.")
352354
dbTestCmd.MarkFlagsMutuallyExclusive("db-url", "linked", "local")
353355
rootCmd.AddCommand(dbCmd)
354356
}

cmd/test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ func init() {
4141
dbFlags.String("db-url", "", "Tests the database specified by the connection string (must be percent-encoded).")
4242
dbFlags.Bool("linked", false, "Runs pgTAP tests on the linked project.")
4343
dbFlags.Bool("local", true, "Runs pgTAP tests on the local database.")
44+
dbFlags.Bool("use-shadow-db", false, "Creates a temporary database from migrations for running tests in isolation.")
4445
testDbCmd.MarkFlagsMutuallyExclusive("db-url", "linked", "local")
4546
testCmd.AddCommand(testDbCmd)
4647
// Build new command

internal/db/test/test.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import (
1616
"github.com/jackc/pgx/v4"
1717
"github.com/spf13/afero"
1818
"github.com/spf13/viper"
19+
"github.com/supabase/cli/internal/db/diff"
20+
"github.com/supabase/cli/internal/db/start"
1921
"github.com/supabase/cli/internal/utils"
2022
cliConfig "github.com/supabase/cli/pkg/config"
2123
)
@@ -25,7 +27,31 @@ const (
2527
DISABLE_PGTAP = "drop extension if exists pgtap"
2628
)
2729

28-
func Run(ctx context.Context, testFiles []string, config pgconn.Config, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error {
30+
func Run(ctx context.Context, testFiles []string, config pgconn.Config, useShadowDb bool, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error {
31+
// Create and migrate shadow database if requested
32+
if useShadowDb {
33+
fmt.Fprintln(os.Stderr, "Creating shadow database for testing...")
34+
shadow, err := diff.CreateShadowDatabase(ctx, utils.Config.Db.ShadowPort)
35+
if err != nil {
36+
return err
37+
}
38+
defer utils.DockerRemove(shadow)
39+
if err := start.WaitForHealthyService(ctx, utils.Config.Db.HealthTimeout, shadow); err != nil {
40+
return err
41+
}
42+
if err := diff.MigrateShadowDatabase(ctx, shadow, fsys, options...); err != nil {
43+
return err
44+
}
45+
// Override config to point at shadow DB
46+
config = pgconn.Config{
47+
Host: utils.Config.Hostname,
48+
Port: utils.Config.Db.ShadowPort,
49+
User: "postgres",
50+
Password: utils.Config.Db.Password,
51+
Database: "postgres",
52+
}
53+
fmt.Fprintln(os.Stderr, "Shadow database ready. Running tests...")
54+
}
2955
// Build test command
3056
if len(testFiles) == 0 {
3157
absTestsDir, err := filepath.Abs(utils.DbTestsDir)
@@ -79,7 +105,11 @@ func Run(ctx context.Context, testFiles []string, config pgconn.Config, fsys afe
79105
// Use custom network when connecting to local database
80106
// disable selinux via security-opt to allow pg-tap to work properly
81107
hostConfig := container.HostConfig{Binds: binds, SecurityOpt: []string{"label:disable"}}
82-
if utils.IsLocalDatabase(config) {
108+
if useShadowDb {
109+
// Shadow container has no Docker DNS alias; use host networking
110+
// so pg_prove reaches it via 127.0.0.1:<ShadowPort>
111+
hostConfig.NetworkMode = network.NetworkHost
112+
} else if utils.IsLocalDatabase(config) {
83113
config.Host = utils.DbAliases[0]
84114
config.Port = 5432
85115
} else {

internal/db/test/test_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func TestRunCommand(t *testing.T) {
4444
apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(config.Images.PgProve), containerId)
4545
require.NoError(t, apitest.MockDockerLogs(utils.Docker, containerId, "Result: SUCCESS"))
4646
// Run test
47-
err := Run(context.Background(), []string{"nested"}, dbConfig, fsys, conn.Intercept)
47+
err := Run(context.Background(), []string{"nested"}, dbConfig, false, fsys, conn.Intercept)
4848
// Check error
4949
assert.NoError(t, err)
5050
})
@@ -54,7 +54,7 @@ func TestRunCommand(t *testing.T) {
5454
fsys := afero.NewMemMapFs()
5555
require.NoError(t, utils.WriteConfig(fsys, false))
5656
// Run test
57-
err := Run(context.Background(), nil, dbConfig, fsys)
57+
err := Run(context.Background(), nil, dbConfig, false, fsys)
5858
// Check error
5959
assert.ErrorContains(t, err, "failed to connect to postgres")
6060
})
@@ -69,7 +69,7 @@ func TestRunCommand(t *testing.T) {
6969
conn.Query(ENABLE_PGTAP).
7070
ReplyError(pgerrcode.DuplicateObject, `extension "pgtap" already exists, skipping`)
7171
// Run test
72-
err := Run(context.Background(), nil, dbConfig, fsys, conn.Intercept)
72+
err := Run(context.Background(), nil, dbConfig, false, fsys, conn.Intercept)
7373
// Check error
7474
assert.ErrorContains(t, err, "failed to enable pgTAP")
7575
})
@@ -93,7 +93,7 @@ func TestRunCommand(t *testing.T) {
9393
Get("/v" + utils.Docker.ClientVersion() + "/images/" + utils.GetRegistryImageUrl(config.Images.PgProve) + "/json").
9494
ReplyError(errNetwork)
9595
// Run test
96-
err := Run(context.Background(), nil, dbConfig, fsys, conn.Intercept)
96+
err := Run(context.Background(), nil, dbConfig, false, fsys, conn.Intercept)
9797
// Check error
9898
assert.ErrorIs(t, err, errNetwork)
9999
assert.Empty(t, apitest.ListUnmatchedRequests())

pkg/config/templates/config.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ enabled = false
2727
[db]
2828
# Port to use for the local database URL.
2929
port = 54322
30-
# Port used by db diff command to initialize the shadow database.
30+
# Port used by db diff and test db commands to initialize the shadow database.
3131
shadow_port = 54320
3232
# Maximum amount of time to wait for health check when starting the local database.
3333
health_timeout = "2m"

0 commit comments

Comments
 (0)