Skip to content

Commit a5f1756

Browse files
Rodriguespnclaude
andcommitted
feat(db): add --db-url flag to db advisors command
Adds the --db-url flag to db advisors, matching the pattern used by other db commands (lint, diff, dump, etc.). Switches from a dedicated bool to the standard flag.Changed detection for --linked routing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2f42279 commit a5f1756

2 files changed

Lines changed: 81 additions & 6 deletions

File tree

cmd/db.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -258,14 +258,12 @@ var (
258258
Value: "none",
259259
}
260260

261-
advisorLinked bool
262-
263261
dbAdvisorsCmd = &cobra.Command{
264262
Use: "advisors",
265263
Short: "Checks database for security and performance issues",
266264
Long: "Inspects the database for common security and performance issues such as missing RLS policies, unindexed foreign keys, exposed auth.users, and more.",
267265
PreRunE: func(cmd *cobra.Command, args []string) error {
268-
if advisorLinked {
266+
if flag := cmd.Flags().Lookup("linked"); flag != nil && flag.Changed {
269267
fsys := afero.NewOsFs()
270268
if _, err := utils.LoadAccessTokenFS(fsys); err != nil {
271269
utils.CmdSuggestion = fmt.Sprintf("Run %s first.", utils.Aqua("supabase login"))
@@ -276,7 +274,7 @@ var (
276274
return nil
277275
},
278276
RunE: func(cmd *cobra.Command, args []string) error {
279-
if advisorLinked {
277+
if flag := cmd.Flags().Lookup("linked"); flag != nil && flag.Changed {
280278
return advisors.RunLinked(cmd.Context(), advisorType.Value, advisorLevel.Value, advisorFailOn.Value, flags.ProjectRef)
281279
}
282280
return advisors.RunLocal(cmd.Context(), advisorType.Value, advisorLevel.Value, advisorFailOn.Value, flags.DbConfig)
@@ -393,9 +391,10 @@ func init() {
393391
dbTestCmd.MarkFlagsMutuallyExclusive("db-url", "linked", "local")
394392
// Build advisors command
395393
advisorsFlags := dbAdvisorsCmd.Flags()
396-
advisorsFlags.BoolVar(&advisorLinked, "linked", false, "Checks the linked project for issues.")
394+
advisorsFlags.String("db-url", "", "Checks the database specified by the connection string (must be percent-encoded).")
395+
advisorsFlags.Bool("linked", false, "Checks the linked project for issues.")
397396
advisorsFlags.Bool("local", true, "Checks the local database for issues.")
398-
dbAdvisorsCmd.MarkFlagsMutuallyExclusive("linked", "local")
397+
dbAdvisorsCmd.MarkFlagsMutuallyExclusive("db-url", "linked", "local")
399398
advisorsFlags.Var(&advisorType, "type", "Type of advisors to check: all, security, performance.")
400399
advisorsFlags.Var(&advisorLevel, "level", "Minimum issue level to display: info, warn, error.")
401400
advisorsFlags.Var(&advisorFailOn, "fail-on", "Issue level to exit with non-zero status: none, info, warn, error.")

internal/db/advisors/advisors_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"testing"
99

1010
"github.com/h2non/gock"
11+
"github.com/jackc/pgconn"
1112
"github.com/stretchr/testify/assert"
1213
"github.com/stretchr/testify/require"
1314
"github.com/supabase/cli/internal/testing/apitest"
@@ -16,6 +17,14 @@ import (
1617
"github.com/supabase/cli/pkg/pgtest"
1718
)
1819

20+
var dbConfig = pgconn.Config{
21+
Host: "127.0.0.1",
22+
Port: 5432,
23+
User: "admin",
24+
Password: "password",
25+
Database: "postgres",
26+
}
27+
1928
func TestQueryLints(t *testing.T) {
2029
t.Run("parses lint results from local database", func(t *testing.T) {
2130
utils.Config.Hostname = "127.0.0.1"
@@ -299,3 +308,70 @@ func TestFetchLinkedAdvisors(t *testing.T) {
299308
assert.Error(t, err)
300309
})
301310
}
311+
312+
func TestRunLocalWithDbUrl(t *testing.T) {
313+
t.Run("runs advisors against custom db-url", func(t *testing.T) {
314+
utils.Config.Hostname = "127.0.0.1"
315+
utils.Config.Db.Port = 5432
316+
317+
conn := pgtest.NewConn()
318+
defer conn.Close(t)
319+
conn.Query("begin").Reply("BEGIN").
320+
Query(lintsSQL).
321+
Reply("SELECT 1",
322+
[]any{
323+
"rls_disabled_in_public",
324+
"RLS disabled in public",
325+
"ERROR",
326+
"EXTERNAL",
327+
[]string{"SECURITY"},
328+
"Detects tables in the public schema without RLS.",
329+
"Table public.users has RLS disabled",
330+
"https://supabase.com/docs/guides/database/database-linter?lint=0013_rls_disabled_in_public",
331+
[]byte(`{"schema":"public","name":"users","type":"table"}`),
332+
"rls_disabled_in_public_public_users",
333+
},
334+
).
335+
Query("rollback").Reply("ROLLBACK")
336+
337+
err := RunLocal(context.Background(), "all", "info", "none", dbConfig, conn.Intercept)
338+
assert.NoError(t, err)
339+
})
340+
341+
t.Run("returns no issues for empty results", func(t *testing.T) {
342+
conn := pgtest.NewConn()
343+
defer conn.Close(t)
344+
conn.Query("begin").Reply("BEGIN").
345+
Query(lintsSQL).
346+
Reply("SELECT 0").
347+
Query("rollback").Reply("ROLLBACK")
348+
349+
err := RunLocal(context.Background(), "all", "info", "none", dbConfig, conn.Intercept)
350+
assert.NoError(t, err)
351+
})
352+
353+
t.Run("fails on error level when fail-on is set", func(t *testing.T) {
354+
conn := pgtest.NewConn()
355+
defer conn.Close(t)
356+
conn.Query("begin").Reply("BEGIN").
357+
Query(lintsSQL).
358+
Reply("SELECT 1",
359+
[]any{
360+
"rls_disabled_in_public",
361+
"RLS disabled in public",
362+
"ERROR",
363+
"EXTERNAL",
364+
[]string{"SECURITY"},
365+
"Detects tables in the public schema without RLS.",
366+
"Table public.users has RLS disabled",
367+
"https://supabase.com/docs",
368+
[]byte(`{}`),
369+
"test_key",
370+
},
371+
).
372+
Query("rollback").Reply("ROLLBACK")
373+
374+
err := RunLocal(context.Background(), "all", "info", "error", dbConfig, conn.Intercept)
375+
assert.ErrorContains(t, err, "fail-on is set to error")
376+
})
377+
}

0 commit comments

Comments
 (0)