From 9142412562468ac0adfdbb9e7ebf43560b1387f6 Mon Sep 17 00:00:00 2001 From: Han Qiao Date: Fri, 7 Feb 2025 15:36:43 +0800 Subject: [PATCH] feat: make config file optional (#3103) * feat: make config file optional * chore: update unit tests --- internal/db/branch/switch_/switch__test.go | 10 ---------- internal/db/diff/diff_test.go | 23 ++++++++++++++++++++-- internal/db/pull/pull_test.go | 6 +++--- internal/db/start/start_test.go | 5 +++-- internal/functions/serve/serve_test.go | 5 +++-- internal/start/start_test.go | 8 +------- internal/status/status_test.go | 8 +------- internal/stop/stop_test.go | 6 +++--- internal/utils/flags/config_path.go | 7 ------- internal/utils/misc.go | 10 ---------- pkg/config/config.go | 8 +++++--- pkg/config/config_test.go | 8 ++++++++ 12 files changed, 48 insertions(+), 56 deletions(-) diff --git a/internal/db/branch/switch_/switch__test.go b/internal/db/branch/switch_/switch__test.go index 722229b79..4e2e1055b 100644 --- a/internal/db/branch/switch_/switch__test.go +++ b/internal/db/branch/switch_/switch__test.go @@ -3,7 +3,6 @@ package switch_ import ( "context" "net/http" - "os" "path/filepath" "testing" @@ -63,15 +62,6 @@ func TestSwitchCommand(t *testing.T) { assert.Equal(t, []byte(branch), contents) }) - t.Run("throws error on missing config", func(t *testing.T) { - // Setup in-memory fs - fsys := afero.NewMemMapFs() - // Run test - err := Run(context.Background(), "target", fsys) - // Check error - assert.ErrorIs(t, err, os.ErrNotExist) - }) - t.Run("throws error on malformed config", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() diff --git a/internal/db/diff/diff_test.go b/internal/db/diff/diff_test.go index 370f1be49..d8a596c21 100644 --- a/internal/db/diff/diff_test.go +++ b/internal/db/diff/diff_test.go @@ -85,13 +85,14 @@ func TestRun(t *testing.T) { assert.Equal(t, []byte(diff), contents) }) - t.Run("throws error on missing config", func(t *testing.T) { + t.Run("throws error on malformed config", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() + require.NoError(t, afero.WriteFile(fsys, utils.ConfigPath, []byte("malformed"), 0644)) // Run test err := Run(context.Background(), []string{"public"}, "", pgconn.Config{}, DiffSchemaMigra, fsys) // Check error - assert.ErrorIs(t, err, os.ErrNotExist) + assert.ErrorContains(t, err, "toml: expected = after a key, but the document ends there") }) t.Run("throws error on failure to load user schemas", func(t *testing.T) { @@ -342,3 +343,21 @@ func TestDropStatements(t *testing.T) { drops := findDropStatements("create table t(); drop table t; alter table t drop column c") assert.Equal(t, []string{"drop table t", "alter table t drop column c"}, drops) } + +func TestLoadSchemas(t *testing.T) { + expected := []string{ + filepath.Join(utils.SchemasDir, "comment", "model.sql"), + filepath.Join(utils.SchemasDir, "model.sql"), + filepath.Join(utils.SchemasDir, "reaction", "dislike", "model.sql"), + filepath.Join(utils.SchemasDir, "reaction", "like", "model.sql"), + } + fsys := afero.NewMemMapFs() + for _, fp := range expected { + require.NoError(t, afero.WriteFile(fsys, fp, nil, 0644)) + } + // Run test + schemas, err := loadDeclaredSchemas(fsys) + // Check error + assert.NoError(t, err) + assert.ElementsMatch(t, expected, schemas) +} diff --git a/internal/db/pull/pull_test.go b/internal/db/pull/pull_test.go index 2e55fcfed..1c275df40 100644 --- a/internal/db/pull/pull_test.go +++ b/internal/db/pull/pull_test.go @@ -29,14 +29,14 @@ var dbConfig = pgconn.Config{ } func TestPullCommand(t *testing.T) { - t.Run("throws error on missing config", func(t *testing.T) { + t.Run("throws error on malformed config", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() + require.NoError(t, afero.WriteFile(fsys, utils.ConfigPath, []byte("malformed"), 0644)) // Run test err := Run(context.Background(), nil, pgconn.Config{}, "", fsys) // Check error - assert.ErrorIs(t, err, os.ErrNotExist) - assert.Empty(t, apitest.ListUnmatchedRequests()) + assert.ErrorContains(t, err, "toml: expected = after a key, but the document ends there") }) t.Run("throws error on connect failure", func(t *testing.T) { diff --git a/internal/db/start/start_test.go b/internal/db/start/start_test.go index afdb8e28b..fdc76c6ec 100644 --- a/internal/db/start/start_test.go +++ b/internal/db/start/start_test.go @@ -157,13 +157,14 @@ func TestStartDatabase(t *testing.T) { } func TestStartCommand(t *testing.T) { - t.Run("throws error on missing config", func(t *testing.T) { + t.Run("throws error on malformed config", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() + require.NoError(t, afero.WriteFile(fsys, utils.ConfigPath, []byte("malformed"), 0644)) // Run test err := Run(context.Background(), "", fsys) // Check error - assert.ErrorIs(t, err, os.ErrNotExist) + assert.ErrorContains(t, err, "toml: expected = after a key, but the document ends there") }) t.Run("throws error on missing docker", func(t *testing.T) { diff --git a/internal/functions/serve/serve_test.go b/internal/functions/serve/serve_test.go index 570c4b927..81c385bde 100644 --- a/internal/functions/serve/serve_test.go +++ b/internal/functions/serve/serve_test.go @@ -44,13 +44,14 @@ func TestServeCommand(t *testing.T) { assert.Empty(t, apitest.ListUnmatchedRequests()) }) - t.Run("throws error on missing config", func(t *testing.T) { + t.Run("throws error on malformed config", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() + require.NoError(t, afero.WriteFile(fsys, utils.ConfigPath, []byte("malformed"), 0644)) // Run test err := Run(context.Background(), "", nil, "", RuntimeOption{}, fsys) // Check error - assert.ErrorContains(t, err, "open supabase/config.toml: file does not exist") + assert.ErrorContains(t, err, "toml: expected = after a key, but the document ends there") }) t.Run("throws error on missing db", func(t *testing.T) { diff --git a/internal/start/start_test.go b/internal/start/start_test.go index bbd73ab93..bcc5be02f 100644 --- a/internal/start/start_test.go +++ b/internal/start/start_test.go @@ -5,7 +5,6 @@ import ( "context" "errors" "net/http" - "os" "regexp" "testing" @@ -25,12 +24,7 @@ import ( ) func TestStartCommand(t *testing.T) { - t.Run("throws error on missing config", func(t *testing.T) { - err := Run(context.Background(), afero.NewMemMapFs(), []string{}, false) - assert.ErrorIs(t, err, os.ErrNotExist) - }) - - t.Run("throws error on invalid config", func(t *testing.T) { + t.Run("throws error on malformed config", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() require.NoError(t, afero.WriteFile(fsys, utils.ConfigPath, []byte("malformed"), 0644)) diff --git a/internal/status/status_test.go b/internal/status/status_test.go index 175d28ca0..01dd1e73b 100644 --- a/internal/status/status_test.go +++ b/internal/status/status_test.go @@ -5,7 +5,6 @@ import ( "context" "errors" "net/http" - "os" "testing" "github.com/docker/docker/api/types" @@ -47,12 +46,7 @@ func TestStatusCommand(t *testing.T) { assert.Empty(t, apitest.ListUnmatchedRequests()) }) - t.Run("throws error on missing config", func(t *testing.T) { - err := Run(context.Background(), CustomName{}, utils.OutputPretty, afero.NewMemMapFs()) - assert.ErrorIs(t, err, os.ErrNotExist) - }) - - t.Run("throws error on invalid config", func(t *testing.T) { + t.Run("throws error on malformed config", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() require.NoError(t, afero.WriteFile(fsys, utils.ConfigPath, []byte("malformed"), 0644)) diff --git a/internal/stop/stop_test.go b/internal/stop/stop_test.go index b607d87d7..81fad5dee 100644 --- a/internal/stop/stop_test.go +++ b/internal/stop/stop_test.go @@ -6,7 +6,6 @@ import ( "fmt" "io" "net/http" - "os" "testing" "github.com/docker/docker/api/types" @@ -135,13 +134,14 @@ func TestStopCommand(t *testing.T) { assert.Empty(t, apitest.ListUnmatchedRequests()) }) - t.Run("throws error on invalid config", func(t *testing.T) { + t.Run("throws error on malformed config", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() + require.NoError(t, afero.WriteFile(fsys, utils.ConfigPath, []byte("malformed"), 0644)) // Run test err := Run(context.Background(), false, "", false, fsys) // Check error - assert.ErrorIs(t, err, os.ErrNotExist) + assert.ErrorContains(t, err, "toml: expected = after a key, but the document ends there") }) t.Run("throws error on stop failure", func(t *testing.T) { diff --git a/internal/utils/flags/config_path.go b/internal/utils/flags/config_path.go index d0ed105c7..bec77a432 100644 --- a/internal/utils/flags/config_path.go +++ b/internal/utils/flags/config_path.go @@ -1,10 +1,6 @@ package flags import ( - "fmt" - "os" - - "github.com/go-errors/errors" "github.com/spf13/afero" "github.com/supabase/cli/internal/utils" ) @@ -12,9 +8,6 @@ import ( func LoadConfig(fsys afero.Fs) error { utils.Config.ProjectId = ProjectRef if err := utils.Config.Load("", utils.NewRootFS(fsys)); err != nil { - if errors.Is(err, os.ErrNotExist) { - utils.CmdSuggestion = fmt.Sprintf("Have you set up the project with %s?", utils.Aqua("supabase init")) - } return err } utils.UpdateDockerIds() diff --git a/internal/utils/misc.go b/internal/utils/misc.go index 32a2fdf15..d7d9fd33c 100644 --- a/internal/utils/misc.go +++ b/internal/utils/misc.go @@ -261,16 +261,6 @@ func WriteFile(path string, contents []byte, fsys afero.Fs) error { return nil } -func AssertSupabaseCliIsSetUpFS(fsys afero.Fs) error { - if _, err := fsys.Stat(ConfigPath); errors.Is(err, os.ErrNotExist) { - return errors.Errorf("Cannot find %s in the current directory. Have you set up the project with %s?", Bold(ConfigPath), Aqua("supabase init")) - } else if err != nil { - return errors.Errorf("failed to read config file: %w", err) - } - - return nil -} - func AssertProjectRefIsValid(projectRef string) error { if !ProjectRefPattern.MatchString(projectRef) { return errors.New(ErrInvalidRef) diff --git a/pkg/config/config.go b/pkg/config/config.go index 59c224340..55be7e34a 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -415,8 +415,8 @@ func (c *config) loadFromFile(filename string, fsys fs.FS) error { v.SetConfigType("toml") // Load default values var buf bytes.Buffer - if err := initConfigTemplate.Option("missingkey=zero").Execute(&buf, c); err != nil { - return errors.Errorf("failed to initialise template config: %w", err) + if err := c.Eject(&buf); err != nil { + return err } else if err := c.loadFromReader(v, &buf); err != nil { return err } @@ -425,7 +425,9 @@ func (c *config) loadFromFile(filename string, fsys fs.FS) error { v.SetConfigType(ext[1:]) } f, err := fsys.Open(filename) - if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil + } else if err != nil { return errors.Errorf("failed to read file config: %w", err) } defer f.Close() diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 51ec9db9d..dcf2fe10e 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -28,6 +28,14 @@ func TestConfigParsing(t *testing.T) { assert.NoError(t, config.Load("config.toml", fsys)) }) + t.Run("optional config file", func(t *testing.T) { + config := NewConfig() + // Run test + err := config.Load("", fs.MapFS{}) + // Check error + assert.NoError(t, err) + }) + t.Run("config file with environment variables", func(t *testing.T) { config := NewConfig() // Setup in-memory fs