From 6f06adc37d30b2e955b086f1c9c163402cc9e950 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Sun, 26 Jan 2025 20:07:45 -0600 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make it easier to setup stores --- app.go | 10 +- app_test.go | 58 ++-- cmd/combluotion/main.go | 8 +- config/config.go | 80 ++++- config/config_test.go | 12 +- config/interface.go | 17 ++ config/load.go | 38 ++- config/load_test.go | 25 +- internal/testmocks/config/config_mock.go | 371 +++++++++++++++++++++++ store/factory.go | 11 +- store/factory_test.go | 61 ++-- store/sqlite/repo.go | 13 +- 12 files changed, 623 insertions(+), 81 deletions(-) create mode 100644 config/interface.go create mode 100644 internal/testmocks/config/config_mock.go diff --git a/app.go b/app.go index 514df57..fbe7524 100644 --- a/app.go +++ b/app.go @@ -27,7 +27,7 @@ type App struct { } func NewApp(ver string, conf config.Config, db store.Store) (*App, error) { - if conf.BaseURL == "" { + if conf.BaseURL() == "" { return nil, errors.New("missing BaseURL") } app := App{ @@ -35,7 +35,7 @@ func NewApp(ver string, conf config.Config, db store.Store) (*App, error) { conf: conf, } - selfIRI := boxap.DefaultServiceIRI(conf.BaseURL) + selfIRI := boxap.DefaultServiceIRI(conf.BaseURL()) self := boxap.Self(selfIRI) self.Name = vocab.DefaultNaturalLanguageValue(app.Name()) self.AttributedTo = vocab.IRI(config.DevUrl) @@ -54,7 +54,7 @@ func (l *App) Config() config.Config { } func (l *App) Environment() config.Env { - return l.conf.Environment() + return l.conf.Env() } func (l *App) Storage() store.Store { @@ -78,10 +78,10 @@ func (l *App) Version() string { } func (l *App) Name() string { - if l.conf.Name == "" { + if l.conf.Name() == "" { return "Lenore" } - return l.conf.Name + return l.conf.Name() } func (l *App) String() string { diff --git a/app_test.go b/app_test.go index 1182eb8..7819765 100644 --- a/app_test.go +++ b/app_test.go @@ -9,32 +9,33 @@ import ( "go.uber.org/mock/gomock" "codeberg.org/danjones000/combluotion/config" + confMock "codeberg.org/danjones000/combluotion/internal/testmocks/config" storeMock "codeberg.org/danjones000/combluotion/internal/testmocks/store" ) -func getStore(t *testing.T) *storeMock.MockStore { +type appTest struct { + ctrl *gomock.Controller + store *storeMock.MockStore + conf *confMock.MockConfig +} + +func setupAppTest(t *testing.T) appTest { t.Helper() ctrl := gomock.NewController(t) - return storeMock.NewMockStore(ctrl) + return appTest{ctrl, storeMock.NewMockStore(ctrl), confMock.NewMockConfig(ctrl)} } func TestEmptyBaseURL(t *testing.T) { - c := config.Config{} - a, er := NewApp("0.0.0", c, getStore(t)) + th := setupAppTest(t) + th.conf. + EXPECT(). + BaseURL(). + Return("") + a, er := NewApp("0.0.0", th.conf, th.store) assert.Nil(t, a) assert.EqualError(t, er, "missing BaseURL") } -func TestDefaultEnvironment(t *testing.T) { - store := getStore(t) - c := config.Config{BaseURL: "http://localhost:1234/"} - a, er := NewApp("0.0.0", c, store) - assert.NoError(t, er) - if assert.NotNil(t, a) { - assert.Equal(t, config.Dev, a.Environment()) - } -} - func TestGivenEnvironment(t *testing.T) { cases := [...]struct { given config.Env @@ -43,18 +44,21 @@ func TestGivenEnvironment(t *testing.T) { {config.Dev, config.Dev}, {config.Prod, config.Prod}, {config.Qa, config.Qa}, - {config.Env("foo"), config.Dev}, - {config.Env("✨"), config.Dev}, } for _, c := range cases { t.Run(string(c.given), func(t *testing.T) { - store := getStore(t) - conf := config.Config{BaseURL: "http://localhost:1234/", Env: c.given} - a, er := NewApp("0.0.0", conf, store) + th := setupAppTest(t) + th.conf.EXPECT().BaseURL().Return("http://localhost:1234/").Times(2) + th.conf.EXPECT().Name().Return("") + th.conf. + EXPECT(). + Env(). + Return(c.given) + a, er := NewApp("0.0.0", th.conf, th.store) assert.NoError(t, er) if assert.NotNil(t, a) { - assert.Equal(t, conf, a.Config()) + assert.Equal(t, th.conf, a.Config()) assert.Equal(t, c.exp, a.Environment()) } }) @@ -62,10 +66,11 @@ func TestGivenEnvironment(t *testing.T) { } func TestService(t *testing.T) { - store := getStore(t) + th := setupAppTest(t) base := "http://localhost:1234/" - conf := config.Config{BaseURL: base} - a, er := NewApp("0.0.0.0", conf, store) + th.conf.EXPECT().BaseURL().Return(base).Times(2) + th.conf.EXPECT().Name().Return("") + a, er := NewApp("0.0.0.0", th.conf, th.store) assert.NoError(t, er) if assert.NotNil(t, a) { assert.Equal(t, vocab.IRI(base), a.ServiceIRI()) @@ -86,10 +91,11 @@ func TestStrings(t *testing.T) { for _, c := range cases { t.Run(c.given, func(t *testing.T) { - store := getStore(t) - conf := config.Config{BaseURL: "http://localhost:1234/", Name: c.given} + th := setupAppTest(t) + th.conf.EXPECT().BaseURL().Return("http://localhost:1234/").AnyTimes() + th.conf.EXPECT().Name().Return(c.given).MinTimes(1) expStr := fmt.Sprintf("%s (%s)", c.exp, "0.0.0.0") - a, er := NewApp("0.0.0.0", conf, store) + a, er := NewApp("0.0.0.0", th.conf, th.store) assert.NoError(t, er) if assert.NotNil(t, a) { assert.Equal(t, c.exp, a.Name()) diff --git a/cmd/combluotion/main.go b/cmd/combluotion/main.go index 0b6b769..5557203 100644 --- a/cmd/combluotion/main.go +++ b/cmd/combluotion/main.go @@ -18,7 +18,7 @@ func main() { quitErr(err) fmt.Printf("%+v\n", conf) - db, err := store.MakeStore(conf.Conn.Store, conf) + db, err := store.MakeStore(conf.StoreName(), conf) quitErr(err) app, err := combluotion.NewApp(config.Version, conf, db) @@ -50,7 +50,9 @@ func getTomlFile() string { var confStr = ` base_url = "http://localhost:4523/" -[conn] +[stores] store = "sqlite" -dsn = "store" + +[stores.settings.sqlite] +path = "storage" ` diff --git a/config/config.go b/config/config.go index 27102a8..175a4bf 100644 --- a/config/config.go +++ b/config/config.go @@ -1,18 +1,76 @@ package config -type Config struct { - Name string `toml:"name"` - Env Env `toml:"env"` - BaseURL string `toml:"base_url"` - Conn ConnSettings `toml:"conn"` +import ( + "errors" + + "github.com/BurntSushi/toml" +) + +type config struct { + name string + env Env + baseURL string + stores stores + md toml.MetaData } -type ConnSettings struct { - Store string `toml:"store"` - DSN string `toml:"dsn"` - Settings map[string]any `toml:"settings"` +var _ Config = config{} + +func (c config) Name() string { + return c.name } -func (c Config) Environment() Env { - return ValidEnvOrDev(c.Env) +func (c config) Env() Env { + return ValidEnvOrDev(c.env) +} + +func (c config) BaseURL() string { + return c.baseURL +} + +func (c config) Store(name string) (Store, error) { + if name == "" { + name = c.stores.store + } + return c.stores.GetStore(name) +} + +func (c config) StoreName() string { + return c.stores.store +} + +type stores struct { + store string + settings map[string]toml.Primitive + conf *config +} + +var ErrMissingStore = errors.New("unknown store") + +func (ss stores) GetStore(name string) (Store, error) { + if tp, ok := ss.settings[name]; ok { + return store{name, tp, ss.conf.md}, nil + } + return nil, ErrMissingStore +} + +type store struct { + name string + settings toml.Primitive + md toml.MetaData +} + +var _ Store = store{} + +func (s store) Name() string { + return s.name +} + +func (s store) Map() (m map[string]any, err error) { + err = s.Decode(&m) + return +} + +func (s store) Decode(v any) error { + return s.md.PrimitiveDecode(s.settings, v) } diff --git a/config/config_test.go b/config/config_test.go index 12c0e4c..28dfc9f 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -7,20 +7,20 @@ import ( ) func TestEnvDefaultsToDev(t *testing.T) { - c := Config{} - assert.Equal(t, Dev, c.Environment()) + c := config{} + assert.Equal(t, Dev, c.Env()) } func TestInvalidEnvReturnsDev(t *testing.T) { - c := Config{Env: Env("foobar")} - assert.Equal(t, Dev, c.Environment()) + c := config{env: Env("foobar")} + assert.Equal(t, Dev, c.Env()) } func TestValidEnvReturnsCorrect(t *testing.T) { for _, e := range Envs { t.Run(string(e), func(t *testing.T) { - c := Config{Env: e} - assert.Equal(t, e, c.Environment()) + c := config{env: e} + assert.Equal(t, e, c.Env()) }) } } diff --git a/config/interface.go b/config/interface.go new file mode 100644 index 0000000..b8c59b0 --- /dev/null +++ b/config/interface.go @@ -0,0 +1,17 @@ +package config + +//go:generate mockgen -source interface.go -destination ../internal/testmocks/config/config_mock.go -package config -typed + +type Config interface { + Name() string + Env() Env + BaseURL() string + StoreName() string + Store(name string) (Store, error) +} + +type Store interface { + Name() string + Decode(v any) error + Map() (map[string]any, error) +} diff --git a/config/load.go b/config/load.go index 3293b65..1295a6d 100644 --- a/config/load.go +++ b/config/load.go @@ -2,7 +2,39 @@ package config import "github.com/BurntSushi/toml" -func LoadFromToml(path string) (c Config, err error) { - _, err = toml.DecodeFile(path, &c) - return +type confToml struct { + Name string + Env Env + BaseURL string `toml:"base_url"` + Stores storeSettings `toml:"stores"` +} + +type storeSettings struct { + Store string + Settings map[string]toml.Primitive +} + +func LoadFromToml(path string) (Config, error) { + var c confToml + md, err := toml.DecodeFile(path, &c) + if err != nil { + return nil, err + } + + conf := config{ + name: c.Name, + env: c.Env, + baseURL: c.BaseURL, + md: md, + } + + st := stores{ + store: c.Stores.Store, + settings: c.Stores.Settings, + conf: &conf, + } + + conf.stores = st + + return conf, nil } diff --git a/config/load_test.go b/config/load_test.go index c532392..a7f4d1a 100644 --- a/config/load_test.go +++ b/config/load_test.go @@ -13,18 +13,33 @@ func TestLoadTomlMissing(t *testing.T) { assert.Error(t, e) } +type sqlSett struct { + Path string + Num int +} + func TestLoadTomlGood(t *testing.T) { tmp, _ := os.CreateTemp("", "*.toml") defer os.Remove(tmp.Name()) defer tmp.Close() fmt.Fprintln(tmp, `name = "Cool"`) - fmt.Fprintln(tmp, "[conn]") + fmt.Fprintln(tmp, "[stores]") fmt.Fprintln(tmp, `store = "sqlite"`) - fmt.Fprintln(tmp, "[conn.settings]") + fmt.Fprintln(tmp, "[stores.settings.sqlite]") + fmt.Fprintln(tmp, `path = "tmp"`) fmt.Fprintln(tmp, `num = 42`) c, e := LoadFromToml(tmp.Name()) assert.NoError(t, e) - assert.Equal(t, "Cool", c.Name) - assert.Equal(t, "sqlite", c.Conn.Store) - assert.Equal(t, int64(42), c.Conn.Settings["num"]) + assert.Equal(t, "Cool", c.Name()) + + st, err := c.Store("") + assert.NoError(t, err) + + assert.Equal(t, "sqlite", st.Name()) + + var sett sqlSett + err = st.Decode(&sett) + assert.NoError(t, err) + assert.Equal(t, 42, sett.Num) + assert.Equal(t, "tmp", sett.Path) } diff --git a/internal/testmocks/config/config_mock.go b/internal/testmocks/config/config_mock.go new file mode 100644 index 0000000..f3b14ec --- /dev/null +++ b/internal/testmocks/config/config_mock.go @@ -0,0 +1,371 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: interface.go +// +// Generated by this command: +// +// mockgen -source interface.go -destination ../internal/testmocks/config/config_mock.go -package config -typed +// + +// Package config is a generated GoMock package. +package config + +import ( + reflect "reflect" + + config "codeberg.org/danjones000/combluotion/config" + gomock "go.uber.org/mock/gomock" +) + +// MockConfig is a mock of Config interface. +type MockConfig struct { + ctrl *gomock.Controller + recorder *MockConfigMockRecorder + isgomock struct{} +} + +// MockConfigMockRecorder is the mock recorder for MockConfig. +type MockConfigMockRecorder struct { + mock *MockConfig +} + +// NewMockConfig creates a new mock instance. +func NewMockConfig(ctrl *gomock.Controller) *MockConfig { + mock := &MockConfig{ctrl: ctrl} + mock.recorder = &MockConfigMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockConfig) EXPECT() *MockConfigMockRecorder { + return m.recorder +} + +// BaseURL mocks base method. +func (m *MockConfig) BaseURL() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BaseURL") + ret0, _ := ret[0].(string) + return ret0 +} + +// BaseURL indicates an expected call of BaseURL. +func (mr *MockConfigMockRecorder) BaseURL() *MockConfigBaseURLCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BaseURL", reflect.TypeOf((*MockConfig)(nil).BaseURL)) + return &MockConfigBaseURLCall{Call: call} +} + +// MockConfigBaseURLCall wrap *gomock.Call +type MockConfigBaseURLCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockConfigBaseURLCall) Return(arg0 string) *MockConfigBaseURLCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockConfigBaseURLCall) Do(f func() string) *MockConfigBaseURLCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockConfigBaseURLCall) DoAndReturn(f func() string) *MockConfigBaseURLCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// Env mocks base method. +func (m *MockConfig) Env() config.Env { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Env") + ret0, _ := ret[0].(config.Env) + return ret0 +} + +// Env indicates an expected call of Env. +func (mr *MockConfigMockRecorder) Env() *MockConfigEnvCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Env", reflect.TypeOf((*MockConfig)(nil).Env)) + return &MockConfigEnvCall{Call: call} +} + +// MockConfigEnvCall wrap *gomock.Call +type MockConfigEnvCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockConfigEnvCall) Return(arg0 config.Env) *MockConfigEnvCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockConfigEnvCall) Do(f func() config.Env) *MockConfigEnvCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockConfigEnvCall) DoAndReturn(f func() config.Env) *MockConfigEnvCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// Name mocks base method. +func (m *MockConfig) Name() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Name") + ret0, _ := ret[0].(string) + return ret0 +} + +// Name indicates an expected call of Name. +func (mr *MockConfigMockRecorder) Name() *MockConfigNameCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockConfig)(nil).Name)) + return &MockConfigNameCall{Call: call} +} + +// MockConfigNameCall wrap *gomock.Call +type MockConfigNameCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockConfigNameCall) Return(arg0 string) *MockConfigNameCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockConfigNameCall) Do(f func() string) *MockConfigNameCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockConfigNameCall) DoAndReturn(f func() string) *MockConfigNameCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// Store mocks base method. +func (m *MockConfig) Store(name string) (config.Store, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Store", name) + ret0, _ := ret[0].(config.Store) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Store indicates an expected call of Store. +func (mr *MockConfigMockRecorder) Store(name any) *MockConfigStoreCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Store", reflect.TypeOf((*MockConfig)(nil).Store), name) + return &MockConfigStoreCall{Call: call} +} + +// MockConfigStoreCall wrap *gomock.Call +type MockConfigStoreCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockConfigStoreCall) Return(arg0 config.Store, arg1 error) *MockConfigStoreCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockConfigStoreCall) Do(f func(string) (config.Store, error)) *MockConfigStoreCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockConfigStoreCall) DoAndReturn(f func(string) (config.Store, error)) *MockConfigStoreCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// StoreName mocks base method. +func (m *MockConfig) StoreName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StoreName") + ret0, _ := ret[0].(string) + return ret0 +} + +// StoreName indicates an expected call of StoreName. +func (mr *MockConfigMockRecorder) StoreName() *MockConfigStoreNameCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StoreName", reflect.TypeOf((*MockConfig)(nil).StoreName)) + return &MockConfigStoreNameCall{Call: call} +} + +// MockConfigStoreNameCall wrap *gomock.Call +type MockConfigStoreNameCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockConfigStoreNameCall) Return(arg0 string) *MockConfigStoreNameCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockConfigStoreNameCall) Do(f func() string) *MockConfigStoreNameCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockConfigStoreNameCall) DoAndReturn(f func() string) *MockConfigStoreNameCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// MockStore is a mock of Store interface. +type MockStore struct { + ctrl *gomock.Controller + recorder *MockStoreMockRecorder + isgomock struct{} +} + +// MockStoreMockRecorder is the mock recorder for MockStore. +type MockStoreMockRecorder struct { + mock *MockStore +} + +// NewMockStore creates a new mock instance. +func NewMockStore(ctrl *gomock.Controller) *MockStore { + mock := &MockStore{ctrl: ctrl} + mock.recorder = &MockStoreMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockStore) EXPECT() *MockStoreMockRecorder { + return m.recorder +} + +// Decode mocks base method. +func (m *MockStore) Decode(v any) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Decode", v) + ret0, _ := ret[0].(error) + return ret0 +} + +// Decode indicates an expected call of Decode. +func (mr *MockStoreMockRecorder) Decode(v any) *MockStoreDecodeCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Decode", reflect.TypeOf((*MockStore)(nil).Decode), v) + return &MockStoreDecodeCall{Call: call} +} + +// MockStoreDecodeCall wrap *gomock.Call +type MockStoreDecodeCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockStoreDecodeCall) Return(arg0 error) *MockStoreDecodeCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockStoreDecodeCall) Do(f func(any) error) *MockStoreDecodeCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockStoreDecodeCall) DoAndReturn(f func(any) error) *MockStoreDecodeCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// Map mocks base method. +func (m *MockStore) Map() (map[string]any, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Map") + ret0, _ := ret[0].(map[string]any) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Map indicates an expected call of Map. +func (mr *MockStoreMockRecorder) Map() *MockStoreMapCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Map", reflect.TypeOf((*MockStore)(nil).Map)) + return &MockStoreMapCall{Call: call} +} + +// MockStoreMapCall wrap *gomock.Call +type MockStoreMapCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockStoreMapCall) Return(arg0 map[string]any, arg1 error) *MockStoreMapCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockStoreMapCall) Do(f func() (map[string]any, error)) *MockStoreMapCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockStoreMapCall) DoAndReturn(f func() (map[string]any, error)) *MockStoreMapCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// Name mocks base method. +func (m *MockStore) Name() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Name") + ret0, _ := ret[0].(string) + return ret0 +} + +// Name indicates an expected call of Name. +func (mr *MockStoreMockRecorder) Name() *MockStoreNameCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockStore)(nil).Name)) + return &MockStoreNameCall{Call: call} +} + +// MockStoreNameCall wrap *gomock.Call +type MockStoreNameCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockStoreNameCall) Return(arg0 string) *MockStoreNameCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockStoreNameCall) Do(f func() string) *MockStoreNameCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockStoreNameCall) DoAndReturn(f func() string) *MockStoreNameCall { + c.Call = c.Call.DoAndReturn(f) + return c +} diff --git a/store/factory.go b/store/factory.go index 4d19fc4..5bf2307 100644 --- a/store/factory.go +++ b/store/factory.go @@ -9,7 +9,7 @@ import ( var ErrNoFactory = errors.New("unknown factory") -type StoreFactory func(config.Config) (Store, error) +type StoreFactory func(config.Store) (Store, error) var factories map[string]StoreFactory @@ -26,13 +26,18 @@ func GetFactory(name string) StoreFactory { } func MakeStore(name string, conf config.Config) (Store, error) { + st, err := conf.Store(name) + if err != nil { + return nil, err + } + if name == "" { - name = conf.Conn.Store + name = st.Name() } f, ok := factories[name] if !ok { return nil, fmt.Errorf("%w: %s", ErrNoFactory, name) } - return f(conf) + return f(st) } diff --git a/store/factory_test.go b/store/factory_test.go index f4aa205..6d3ed8f 100644 --- a/store/factory_test.go +++ b/store/factory_test.go @@ -7,20 +7,31 @@ import ( "go.uber.org/mock/gomock" "codeberg.org/danjones000/combluotion/config" + confMock "codeberg.org/danjones000/combluotion/internal/testmocks/config" storeMock "codeberg.org/danjones000/combluotion/internal/testmocks/store" ) -func getStoreFactory(t *testing.T) (*gomock.Controller, StoreFactory) { +type testHelp struct { + ctrl *gomock.Controller + store *storeMock.MockStore + fact StoreFactory + conf *confMock.MockConfig +} + +func setupFactoryTest(t *testing.T) testHelp { t.Helper() ctrl := gomock.NewController(t) - return ctrl, func(config.Config) (Store, error) { - return storeMock.NewMockStore(ctrl), nil + store := storeMock.NewMockStore(ctrl) + fact := func(config.Store) (Store, error) { + return store, nil } + + return testHelp{ctrl, store, fact, confMock.NewMockConfig(ctrl)} } func TestAddFactory(t *testing.T) { - _, f := getStoreFactory(t) - AddFactory("mock", f) + th := setupFactoryTest(t) + AddFactory("mock", th.fact) defer delete(factories, "mock") _, ok := factories["mock"] assert.True(t, ok) @@ -32,36 +43,52 @@ func TestGetFactoryNil(t *testing.T) { } func TestGetFactoryNotNil(t *testing.T) { - _, f := getStoreFactory(t) - AddFactory("mock", f) + th := setupFactoryTest(t) + AddFactory("mock", th.fact) defer delete(factories, "mock") - f = GetFactory("mock") + f := GetFactory("mock") assert.NotNil(t, f) } func TestMakeStoreError(t *testing.T) { - s, e := MakeStore("mock", config.Config{}) + th := setupFactoryTest(t) + th.conf. + EXPECT(). + Store("mock"). + Return(nil, nil) + s, e := MakeStore("mock", th.conf) assert.Nil(t, s) assert.ErrorIs(t, e, ErrNoFactory) assert.ErrorContains(t, e, ErrNoFactory.Error()+": mock") } func TestMakeStoreNoError(t *testing.T) { - _, f := getStoreFactory(t) - AddFactory("mock", f) + th := setupFactoryTest(t) + th.conf. + EXPECT(). + Store("mock"). + Return(nil, nil) + AddFactory("mock", th.fact) defer delete(factories, "mock") - s, e := MakeStore("mock", config.Config{}) + s, e := MakeStore("mock", th.conf) assert.NotNil(t, s) assert.NoError(t, e) } func TestMakeStoreNoName(t *testing.T) { - _, f := getStoreFactory(t) - AddFactory("mock", f) + th := setupFactoryTest(t) + confStore := confMock.NewMockStore(th.ctrl) + th.conf. + EXPECT(). + Store(""). + Return(confStore, nil) + confStore. + EXPECT(). + Name(). + Return("mock") + AddFactory("mock", th.fact) defer delete(factories, "mock") - s, e := MakeStore("", config.Config{ - Conn: config.ConnSettings{Store: "mock"}, - }) + s, e := MakeStore("", th.conf) assert.NotNil(t, s) assert.NoError(t, e) } diff --git a/store/sqlite/repo.go b/store/sqlite/repo.go index c0e780e..f2fca17 100644 --- a/store/sqlite/repo.go +++ b/store/sqlite/repo.go @@ -10,8 +10,17 @@ func init() { store.AddFactory("sqlite", MakeStore) } -func MakeStore(conf config.Config) (store.Store, error) { - sqlConf := sqlite.Config{Path: conf.Conn.DSN} +type settings struct { + Path string +} + +func MakeStore(conf config.Store) (store.Store, error) { + var s settings + err := conf.Decode(&s) + if err != nil { + return nil, err + } + sqlConf := sqlite.Config{Path: s.Path} db, err := sqlite.New(sqlConf) if err != nil { return nil, err