diff options
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/config/config.go | 45 | ||||
| -rw-r--r-- | internal/config/config_test.go | 105 | ||||
| -rw-r--r-- | internal/config/testdata/full.conf | 5 | ||||
| -rw-r--r-- | internal/config/testdata/no-db.conf | 7 |
4 files changed, 95 insertions, 67 deletions
diff --git a/internal/config/config.go b/internal/config/config.go index 2be9dd6..d212e1f 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,10 +1,12 @@ package config import ( + "errors" "fmt" "net/url" "os" "path/filepath" + "reflect" "gopkg.in/ini.v1" ) @@ -13,6 +15,13 @@ type Serve struct { Addr string `ini:"addr"` } +type Database struct { + Name string `ini:"name"` + User string `ini:"user"` + Password string `ini:"password"` + Port string `ini:"port"` +} + type Instance struct { Name string `ini:"name"` ContactEmail string `ini:"contact-email"` @@ -21,9 +30,15 @@ type Instance struct { type Config struct { Serve Serve `ini:"serve"` + Database Database `ini:"database"` Instance Instance `ini:"instance"` } +var ( + ErrBadConfig = errors.New("no database configuration found") + ErrBadConfigUrl = errors.New("bad url in config") +) + var systemConfigPath = "/etc/openstore/openstore.conf" func DefaultPath() (string, error) { @@ -40,22 +55,11 @@ func Load(filename string) (*Config, error) { Loose: true, KeyValueDelimiters: "=", }, systemConfigPath, filename) - if err != nil { return nil, err } - config := &Config{ - Serve: Serve{ - Addr: ":3000", - }, - Instance: Instance{ - Name: "Openstore", - ContactEmail: "jane@doe.com", - BaseURL: "https://www.example.com", - }, - } - + config := new(Config) err = cfg.MapTo(config) if err != nil { return nil, err @@ -64,10 +68,25 @@ func Load(filename string) (*Config, error) { u, err := url.Parse(config.Instance.BaseURL) if err != nil || u.Host == "" || u.Scheme == "" { return nil, fmt.Errorf( - "config: base-url must be an absolute url, got %s", + "%w: base-url must be an absolute url, got %s", + ErrBadConfigUrl, config.Instance.BaseURL, ) } + v := reflect.ValueOf(config).Elem() + t := v.Type() + + for i := 0; i < v.NumField(); i++ { + key := t.Field(i).Name + value := v.Field(i).Interface() + if value == nil || value == "" { + return nil, fmt.Errorf( + "%w: config field %s empty", + ErrBadConfig, key, + ) + } + } + return config, nil } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 36b2e97..29a1824 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -1,8 +1,11 @@ package config import ( + "errors" + "fmt" "os" "path/filepath" + "regexp" "strings" "testing" ) @@ -14,6 +17,28 @@ func noSystemConfig(t *testing.T) { t.Cleanup(func() { systemConfigPath = old }) } +func setConfKey(t *testing.T, key, value string) string { + t.Helper() + path := filepath.Join(t.TempDir(), "openstore.conf") + buff, err := os.ReadFile("testdata/full.conf") + if err != nil { + t.Fatalf("read full.conf: %v", err) + } + + expr := fmt.Sprintf("(%s)\\s*=.*", key) + repl := fmt.Sprintf("${1} = %s", value) + + re := regexp.MustCompile(expr) + result := re.ReplaceAll(buff, []byte(repl)) + // t.Errorf("%s", string(result)) + + if err = os.WriteFile(path, result, 0o644); err != nil { + t.Fatalf("write conf: %v", err) + } + + return path +} + func writeConf(t *testing.T, body string) string { t.Helper() path := filepath.Join(t.TempDir(), "openstore.conf") @@ -23,23 +48,16 @@ func writeConf(t *testing.T, body string) string { return path } -func TestLoadMissingFileUsesDefaults(t *testing.T) { +func TestRejectLoadingMissingFile(t *testing.T) { noSystemConfig(t) - cfg, err := Load(filepath.Join(t.TempDir(), "nonexistent.conf")) - if err != nil { - t.Fatalf("load with no config file failed: %v", err) - } - - if cfg.Serve.Addr != ":3000" { - t.Errorf("addr: got %q, want \":3000\"", cfg.Serve.Addr) - } - if cfg.Instance.Name != "Openstore" { - t.Errorf("name: got %q, want \"Openstore\"", cfg.Instance.Name) + _, err := Load(filepath.Join(t.TempDir(), "nonexistent.conf")) + if err == nil { + t.Fatalf("no error when loading missing config: %v", err) } } -func TestLoadFullFileOverridesDefaults(t *testing.T) { +func TestLoadFullConfig(t *testing.T) { noSystemConfig(t) cfg, err := Load("testdata/full.conf") @@ -56,6 +74,9 @@ func TestLoadFullFileOverridesDefaults(t *testing.T) { {"name", cfg.Instance.Name, "Full Store"}, {"contact-email", cfg.Instance.ContactEmail, "full@store.com"}, {"base-url", cfg.Instance.BaseURL, "https://full.store.com"}, + {"database-name", cfg.Database.Name, "openstore"}, + {"database-user", cfg.Database.User, "openstore"}, + {"database-password", cfg.Database.Password, "12345678"}, } for _, tt := range tests { @@ -65,25 +86,6 @@ func TestLoadFullFileOverridesDefaults(t *testing.T) { } } -func TestLoadPartialFileKeepsDefaults(t *testing.T) { - noSystemConfig(t) - - cfg, err := Load("testdata/partial.conf") - if err != nil { - t.Fatalf("load: %v", err) - } - - if cfg.Instance.Name != "Only Name Set" { - t.Errorf("name: got %q, want \"Only Name Set\"", cfg.Instance.Name) - } - if cfg.Serve.Addr != ":3000" { - t.Errorf("unset addr should keep default, got %q", cfg.Serve.Addr) - } - if cfg.Instance.BaseURL != "https://www.example.com" { - t.Errorf("unset base-url should keep default, got %q", cfg.Instance.BaseURL) - } -} - func TestLoadUserConfigWinsOverSystem(t *testing.T) { old := systemConfigPath systemConfigPath = "testdata/system.conf" @@ -128,12 +130,17 @@ func TestLoadRejectsBadBaseURL(t *testing.T) { for name, badURL := range tests { t.Run(name, func(t *testing.T) { - path := writeConf(t, "[instance]\nbase-url = "+badURL+"\n") + path := setConfKey(t, "base-url", badURL) + + cfg, err := Load(path) + if cfg != nil { + t.Error("config with bad base-url created") + } - _, err := Load(path) - if err == nil { + if !errors.Is(err, ErrBadConfigUrl) { t.Fatalf("bad base-url %q accepted", badURL) } + if !strings.Contains(err.Error(), "base-url") { t.Errorf("error should mention base-url, got: %v", err) } @@ -141,24 +148,6 @@ func TestLoadRejectsBadBaseURL(t *testing.T) { } } -func TestLoadEmptyValueKeepsDefault(t *testing.T) { - noSystemConfig(t) - - path := writeConf(t, "[instance]\nname = \nbase-url = \n") - - cfg, err := Load(path) - if err != nil { - t.Fatalf("load: %v", err) - } - - if cfg.Instance.Name != "Openstore" { - t.Errorf("empty value should leave default, got name %q", cfg.Instance.Name) - } - if cfg.Instance.BaseURL != "https://www.example.com" { - t.Errorf("empty value should leave default, got base-url %q", cfg.Instance.BaseURL) - } -} - func TestLoadAcceptsGoodBaseURL(t *testing.T) { noSystemConfig(t) @@ -171,11 +160,11 @@ func TestLoadAcceptsGoodBaseURL(t *testing.T) { for _, u := range urls { t.Run(u, func(t *testing.T) { - path := writeConf(t, "[instance]\nbase-url = "+u+"\n") + path := setConfKey(t, "base-url", u) cfg, err := Load(path) if err != nil { - t.Fatalf("good base-url %q rejected: %v", u, err) + t.Fatalf("good url %q rejected: %v", u, err) } if cfg.Instance.BaseURL != u { t.Errorf("base-url: got %q, want %q", cfg.Instance.BaseURL, u) @@ -184,6 +173,14 @@ func TestLoadAcceptsGoodBaseURL(t *testing.T) { } } +func TetsRejectsConfigWithoutDatabase(t *testing.T) { + noSystemConfig(t) + _, err := Load("testdata/no-db.conf") + if err == nil { + t.Errorf("Load returned no error when database config is missing") + } +} + func TestDefaultPath(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", "/tmp/xdg-test") diff --git a/internal/config/testdata/full.conf b/internal/config/testdata/full.conf index 1f703f0..c7d85a2 100644 --- a/internal/config/testdata/full.conf +++ b/internal/config/testdata/full.conf @@ -1,6 +1,11 @@ [serve] addr = 127.0.0.1:8080 +[database] +name = openstore +user = openstore +password = 12345678 + [instance] name = Full Store contact-email = full@store.com diff --git a/internal/config/testdata/no-db.conf b/internal/config/testdata/no-db.conf new file mode 100644 index 0000000..1f703f0 --- /dev/null +++ b/internal/config/testdata/no-db.conf @@ -0,0 +1,7 @@ +[serve] +addr = 127.0.0.1:8080 + +[instance] +name = Full Store +contact-email = full@store.com +base-url = https://full.store.com |
