Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion cmd/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,8 @@ var (
Use: "test [path] ...",
Short: "Tests local database with pgTAP",
RunE: func(cmd *cobra.Command, args []string) error {
return test.Run(cmd.Context(), args, flags.DbConfig, afero.NewOsFs())
useShadow, _ := cmd.Flags().GetBool("use-shadow-db")
return test.Run(cmd.Context(), args, flags.DbConfig, useShadow, afero.NewOsFs())
},
}
)
Expand Down Expand Up @@ -349,6 +350,7 @@ func init() {
testFlags.String("db-url", "", "Tests the database specified by the connection string (must be percent-encoded).")
testFlags.Bool("linked", false, "Runs pgTAP tests on the linked project.")
testFlags.Bool("local", true, "Runs pgTAP tests on the local database.")
testFlags.Bool("use-shadow-db", false, "Creates a temporary database from migrations for running tests in isolation.")
dbTestCmd.MarkFlagsMutuallyExclusive("db-url", "linked", "local")
rootCmd.AddCommand(dbCmd)
}
1 change: 1 addition & 0 deletions cmd/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func init() {
dbFlags.String("db-url", "", "Tests the database specified by the connection string (must be percent-encoded).")
dbFlags.Bool("linked", false, "Runs pgTAP tests on the linked project.")
dbFlags.Bool("local", true, "Runs pgTAP tests on the local database.")
dbFlags.Bool("use-shadow-db", false, "Creates a temporary database from migrations for running tests in isolation.")
testDbCmd.MarkFlagsMutuallyExclusive("db-url", "linked", "local")
testCmd.AddCommand(testDbCmd)
// Build new command
Expand Down
13 changes: 13 additions & 0 deletions docs/supabase/test/db.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,16 @@ Requires the local development stack to be started by running `supabase start`.
Runs `pg_prove` in a container with unit test files volume mounted from `supabase/tests` directory. The test file can be suffixed by either `.sql` or `.pg` extension.

Since each test is wrapped in its own transaction, it will be individually rolled back regardless of success or failure.

## Running tests against a shadow database

Pass `--use-shadow-db` to run tests against an ephemeral shadow database instead of the local dev database. When this flag is set, the CLI:

1. Spins up a temporary Postgres container
2. Replays all local migrations from `supabase/migrations`
3. Runs the pgTAP tests against this clean database
4. Destroys the container when finished

Your local dev database is never touched, making this ideal for CI pipelines and ensuring tests always run against a clean, migration-defined schema.

The shadow database uses the `shadow_port` configured in `config.toml` (default `54320`) — the same port used by `db diff`. Because they share this port, you cannot run `db diff` and `db test --use-shadow-db` simultaneously.
11 changes: 11 additions & 0 deletions docs/templates/examples.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,17 @@ supabase-test-db:
All tests successful.
Files=2, Tests=2, 6 wallclock secs ( 0.03 usr 0.01 sys + 0.05 cusr 0.02 csys = 0.11 CPU)
Result: PASS
- id: shadow-db
name: Run tests against an isolated shadow database
code: supabase test db --use-shadow-db
response: |
Creating shadow database...
Applying migration 20220810154537_create_employees_table.sql...
/tmp/supabase/tests/nested/order_test.pg .. ok
/tmp/supabase/tests/pet_test.sql .......... ok
All tests successful.
Files=2, Tests=2, 6 wallclock secs ( 0.03 usr 0.01 sys + 0.05 cusr 0.02 csys = 0.11 CPU)
Result: PASS
# TODO: use actual cli response for sso commands
supabase-sso-show:
- id: basic-usage
Expand Down
34 changes: 32 additions & 2 deletions internal/db/test/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
"github.com/jackc/pgx/v4"
"github.com/spf13/afero"
"github.com/spf13/viper"
"github.com/supabase/cli/internal/db/diff"
"github.com/supabase/cli/internal/db/start"
"github.com/supabase/cli/internal/utils"
cliConfig "github.com/supabase/cli/pkg/config"
)
Expand All @@ -25,7 +27,31 @@ const (
DISABLE_PGTAP = "drop extension if exists pgtap"
)

func Run(ctx context.Context, testFiles []string, config pgconn.Config, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error {
func Run(ctx context.Context, testFiles []string, config pgconn.Config, useShadowDb bool, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error {
// Create and migrate shadow database if requested
if useShadowDb {
fmt.Fprintln(os.Stderr, "Creating shadow database for testing...")
shadow, err := diff.CreateShadowDatabase(ctx, utils.Config.Db.ShadowPort)
if err != nil {
return err
}
defer utils.DockerRemove(shadow)
if err := start.WaitForHealthyService(ctx, utils.Config.Db.HealthTimeout, shadow); err != nil {
return err
}
if err := diff.MigrateShadowDatabase(ctx, shadow, fsys, options...); err != nil {
return err
}
// Override config to point at shadow DB
config = pgconn.Config{
Host: utils.Config.Hostname,
Port: utils.Config.Db.ShadowPort,
User: "postgres",
Password: utils.Config.Db.Password,
Database: "postgres",
}
fmt.Fprintln(os.Stderr, "Shadow database ready. Running tests...")
}
// Build test command
if len(testFiles) == 0 {
absTestsDir, err := filepath.Abs(utils.DbTestsDir)
Expand Down Expand Up @@ -79,7 +105,11 @@ func Run(ctx context.Context, testFiles []string, config pgconn.Config, fsys afe
// Use custom network when connecting to local database
// disable selinux via security-opt to allow pg-tap to work properly
hostConfig := container.HostConfig{Binds: binds, SecurityOpt: []string{"label:disable"}}
if utils.IsLocalDatabase(config) {
if useShadowDb {
// Shadow container has no Docker DNS alias; use host networking
// so pg_prove reaches it via 127.0.0.1:<ShadowPort>
hostConfig.NetworkMode = network.NetworkHost
} else if utils.IsLocalDatabase(config) {
config.Host = utils.DbAliases[0]
config.Port = 5432
} else {
Expand Down
8 changes: 4 additions & 4 deletions internal/db/test/test_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func TestRunCommand(t *testing.T) {
apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(config.Images.PgProve), containerId)
require.NoError(t, apitest.MockDockerLogs(utils.Docker, containerId, "Result: SUCCESS"))
// Run test
err := Run(context.Background(), []string{"nested"}, dbConfig, fsys, conn.Intercept)
err := Run(context.Background(), []string{"nested"}, dbConfig, false, fsys, conn.Intercept)
// Check error
assert.NoError(t, err)
})
Expand All @@ -54,7 +54,7 @@ func TestRunCommand(t *testing.T) {
fsys := afero.NewMemMapFs()
require.NoError(t, utils.WriteConfig(fsys, false))
// Run test
err := Run(context.Background(), nil, dbConfig, fsys)
err := Run(context.Background(), nil, dbConfig, false, fsys)
// Check error
assert.ErrorContains(t, err, "failed to connect to postgres")
})
Expand All @@ -69,7 +69,7 @@ func TestRunCommand(t *testing.T) {
conn.Query(ENABLE_PGTAP).
ReplyError(pgerrcode.DuplicateObject, `extension "pgtap" already exists, skipping`)
// Run test
err := Run(context.Background(), nil, dbConfig, fsys, conn.Intercept)
err := Run(context.Background(), nil, dbConfig, false, fsys, conn.Intercept)
// Check error
assert.ErrorContains(t, err, "failed to enable pgTAP")
})
Expand All @@ -93,7 +93,7 @@ func TestRunCommand(t *testing.T) {
Get("/v" + utils.Docker.ClientVersion() + "/images/" + utils.GetRegistryImageUrl(config.Images.PgProve) + "/json").
ReplyError(errNetwork)
// Run test
err := Run(context.Background(), nil, dbConfig, fsys, conn.Intercept)
err := Run(context.Background(), nil, dbConfig, false, fsys, conn.Intercept)
// Check error
assert.ErrorIs(t, err, errNetwork)
assert.Empty(t, apitest.ListUnmatchedRequests())
Expand Down
3 changes: 2 additions & 1 deletion pkg/api/types.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pkg/config/templates/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ enabled = false
[db]
# Port to use for the local database URL.
port = 54322
# Port used by db diff command to initialize the shadow database.
# Port used by db diff and test db commands to initialize the shadow database.
shadow_port = 54320
# Maximum amount of time to wait for health check when starting the local database.
health_timeout = "2m"
Expand Down
2 changes: 1 addition & 1 deletion pkg/config/testdata/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ key_path = "../certs/my-key.pem"
[db]
# Port to use for the local database URL.
port = 54322
# Port used by db diff command to initialize the shadow database.
# Port used by db diff and test db commands to initialize the shadow database.
shadow_port = 54320
# Maximum amount of time to wait for health check when starting the local database.
health_timeout = "2m"
Expand Down
Loading