From 27bdda265f2b16a0df73eca77458281d7a32c3cf Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Wed, 29 Jul 2026 10:22:43 +0200 Subject: [PATCH] refactor: back the announcement store with plain NATS JetStream Replace the go-micro store with a NATS JetStream key-value bucket, created via reva's cache.NewNatsKeyValue, so the web service no longer depends on go-micro stores. The store methods now take a context, threaded through the config.json render path and the management handlers. Tests use a mockery mock of jetstream.KeyValue. --- services/web/.mockery.yaml | 14 + services/web/mocks/key_value.go | 1363 +++++++++++++++++ services/web/mocks/key_value_entry.go | 349 +++++ services/web/pkg/announcement/announcement.go | 37 +- .../web/pkg/announcement/announcement_test.go | 92 +- services/web/pkg/announcement/service.go | 6 +- services/web/pkg/config/config.go | 21 +- .../web/pkg/config/defaults/defaultconfig.go | 3 - services/web/pkg/server/http/server.go | 29 +- services/web/pkg/service/v0/service.go | 13 +- services/web/pkg/service/v0/service_test.go | 43 +- 11 files changed, 1856 insertions(+), 114 deletions(-) create mode 100644 services/web/.mockery.yaml create mode 100644 services/web/mocks/key_value.go create mode 100644 services/web/mocks/key_value_entry.go diff --git a/services/web/.mockery.yaml b/services/web/.mockery.yaml new file mode 100644 index 0000000000..04c898f18f --- /dev/null +++ b/services/web/.mockery.yaml @@ -0,0 +1,14 @@ +# maintain v2 separate mocks dir +dir: "{{.InterfaceDir}}/mocks" +structname: "{{.InterfaceName}}" +filename: "{{.InterfaceName | snakecase }}.go" +pkgname: mocks + +template: testify +packages: + github.com/nats-io/nats.go/jetstream: + config: + dir: mocks + interfaces: + KeyValue: {} + KeyValueEntry: {} diff --git a/services/web/mocks/key_value.go b/services/web/mocks/key_value.go new file mode 100644 index 0000000000..042e5dcc54 --- /dev/null +++ b/services/web/mocks/key_value.go @@ -0,0 +1,1363 @@ +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify + +package mocks + +import ( + "context" + + "github.com/nats-io/nats.go/jetstream" + mock "github.com/stretchr/testify/mock" +) + +// NewKeyValue creates a new instance of KeyValue. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewKeyValue(t interface { + mock.TestingT + Cleanup(func()) +}) *KeyValue { + mock := &KeyValue{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// KeyValue is an autogenerated mock type for the KeyValue type +type KeyValue struct { + mock.Mock +} + +type KeyValue_Expecter struct { + mock *mock.Mock +} + +func (_m *KeyValue) EXPECT() *KeyValue_Expecter { + return &KeyValue_Expecter{mock: &_m.Mock} +} + +// Bucket provides a mock function for the type KeyValue +func (_mock *KeyValue) Bucket() string { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for Bucket") + } + + var r0 string + if returnFunc, ok := ret.Get(0).(func() string); ok { + r0 = returnFunc() + } else { + r0 = ret.Get(0).(string) + } + return r0 +} + +// KeyValue_Bucket_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Bucket' +type KeyValue_Bucket_Call struct { + *mock.Call +} + +// Bucket is a helper method to define mock.On call +func (_e *KeyValue_Expecter) Bucket() *KeyValue_Bucket_Call { + return &KeyValue_Bucket_Call{Call: _e.mock.On("Bucket")} +} + +func (_c *KeyValue_Bucket_Call) Run(run func()) *KeyValue_Bucket_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *KeyValue_Bucket_Call) Return(s string) *KeyValue_Bucket_Call { + _c.Call.Return(s) + return _c +} + +func (_c *KeyValue_Bucket_Call) RunAndReturn(run func() string) *KeyValue_Bucket_Call { + _c.Call.Return(run) + return _c +} + +// Create provides a mock function for the type KeyValue +func (_mock *KeyValue) Create(ctx context.Context, key string, value []byte, opts ...jetstream.KVCreateOpt) (uint64, error) { + var tmpRet mock.Arguments + if len(opts) > 0 { + tmpRet = _mock.Called(ctx, key, value, opts) + } else { + tmpRet = _mock.Called(ctx, key, value) + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for Create") + } + + var r0 uint64 + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, string, []byte, ...jetstream.KVCreateOpt) (uint64, error)); ok { + return returnFunc(ctx, key, value, opts...) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, string, []byte, ...jetstream.KVCreateOpt) uint64); ok { + r0 = returnFunc(ctx, key, value, opts...) + } else { + r0 = ret.Get(0).(uint64) + } + if returnFunc, ok := ret.Get(1).(func(context.Context, string, []byte, ...jetstream.KVCreateOpt) error); ok { + r1 = returnFunc(ctx, key, value, opts...) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// KeyValue_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create' +type KeyValue_Create_Call struct { + *mock.Call +} + +// Create is a helper method to define mock.On call +// - ctx context.Context +// - key string +// - value []byte +// - opts ...jetstream.KVCreateOpt +func (_e *KeyValue_Expecter) Create(ctx interface{}, key interface{}, value interface{}, opts ...interface{}) *KeyValue_Create_Call { + return &KeyValue_Create_Call{Call: _e.mock.On("Create", + append([]interface{}{ctx, key, value}, opts...)...)} +} + +func (_c *KeyValue_Create_Call) Run(run func(ctx context.Context, key string, value []byte, opts ...jetstream.KVCreateOpt)) *KeyValue_Create_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 []byte + if args[2] != nil { + arg2 = args[2].([]byte) + } + var arg3 []jetstream.KVCreateOpt + var variadicArgs []jetstream.KVCreateOpt + if len(args) > 3 { + variadicArgs = args[3].([]jetstream.KVCreateOpt) + } + arg3 = variadicArgs + run( + arg0, + arg1, + arg2, + arg3..., + ) + }) + return _c +} + +func (_c *KeyValue_Create_Call) Return(v uint64, err error) *KeyValue_Create_Call { + _c.Call.Return(v, err) + return _c +} + +func (_c *KeyValue_Create_Call) RunAndReturn(run func(ctx context.Context, key string, value []byte, opts ...jetstream.KVCreateOpt) (uint64, error)) *KeyValue_Create_Call { + _c.Call.Return(run) + return _c +} + +// Delete provides a mock function for the type KeyValue +func (_mock *KeyValue) Delete(ctx context.Context, key string, opts ...jetstream.KVDeleteOpt) error { + var tmpRet mock.Arguments + if len(opts) > 0 { + tmpRet = _mock.Called(ctx, key, opts) + } else { + tmpRet = _mock.Called(ctx, key) + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for Delete") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, string, ...jetstream.KVDeleteOpt) error); ok { + r0 = returnFunc(ctx, key, opts...) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// KeyValue_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete' +type KeyValue_Delete_Call struct { + *mock.Call +} + +// Delete is a helper method to define mock.On call +// - ctx context.Context +// - key string +// - opts ...jetstream.KVDeleteOpt +func (_e *KeyValue_Expecter) Delete(ctx interface{}, key interface{}, opts ...interface{}) *KeyValue_Delete_Call { + return &KeyValue_Delete_Call{Call: _e.mock.On("Delete", + append([]interface{}{ctx, key}, opts...)...)} +} + +func (_c *KeyValue_Delete_Call) Run(run func(ctx context.Context, key string, opts ...jetstream.KVDeleteOpt)) *KeyValue_Delete_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 []jetstream.KVDeleteOpt + var variadicArgs []jetstream.KVDeleteOpt + if len(args) > 2 { + variadicArgs = args[2].([]jetstream.KVDeleteOpt) + } + arg2 = variadicArgs + run( + arg0, + arg1, + arg2..., + ) + }) + return _c +} + +func (_c *KeyValue_Delete_Call) Return(err error) *KeyValue_Delete_Call { + _c.Call.Return(err) + return _c +} + +func (_c *KeyValue_Delete_Call) RunAndReturn(run func(ctx context.Context, key string, opts ...jetstream.KVDeleteOpt) error) *KeyValue_Delete_Call { + _c.Call.Return(run) + return _c +} + +// Get provides a mock function for the type KeyValue +func (_mock *KeyValue) Get(ctx context.Context, key string) (jetstream.KeyValueEntry, error) { + ret := _mock.Called(ctx, key) + + if len(ret) == 0 { + panic("no return value specified for Get") + } + + var r0 jetstream.KeyValueEntry + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, string) (jetstream.KeyValueEntry, error)); ok { + return returnFunc(ctx, key) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, string) jetstream.KeyValueEntry); ok { + r0 = returnFunc(ctx, key) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(jetstream.KeyValueEntry) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = returnFunc(ctx, key) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// KeyValue_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' +type KeyValue_Get_Call struct { + *mock.Call +} + +// Get is a helper method to define mock.On call +// - ctx context.Context +// - key string +func (_e *KeyValue_Expecter) Get(ctx interface{}, key interface{}) *KeyValue_Get_Call { + return &KeyValue_Get_Call{Call: _e.mock.On("Get", ctx, key)} +} + +func (_c *KeyValue_Get_Call) Run(run func(ctx context.Context, key string)) *KeyValue_Get_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *KeyValue_Get_Call) Return(keyValueEntry jetstream.KeyValueEntry, err error) *KeyValue_Get_Call { + _c.Call.Return(keyValueEntry, err) + return _c +} + +func (_c *KeyValue_Get_Call) RunAndReturn(run func(ctx context.Context, key string) (jetstream.KeyValueEntry, error)) *KeyValue_Get_Call { + _c.Call.Return(run) + return _c +} + +// GetRevision provides a mock function for the type KeyValue +func (_mock *KeyValue) GetRevision(ctx context.Context, key string, revision uint64) (jetstream.KeyValueEntry, error) { + ret := _mock.Called(ctx, key, revision) + + if len(ret) == 0 { + panic("no return value specified for GetRevision") + } + + var r0 jetstream.KeyValueEntry + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, string, uint64) (jetstream.KeyValueEntry, error)); ok { + return returnFunc(ctx, key, revision) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, string, uint64) jetstream.KeyValueEntry); ok { + r0 = returnFunc(ctx, key, revision) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(jetstream.KeyValueEntry) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, string, uint64) error); ok { + r1 = returnFunc(ctx, key, revision) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// KeyValue_GetRevision_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRevision' +type KeyValue_GetRevision_Call struct { + *mock.Call +} + +// GetRevision is a helper method to define mock.On call +// - ctx context.Context +// - key string +// - revision uint64 +func (_e *KeyValue_Expecter) GetRevision(ctx interface{}, key interface{}, revision interface{}) *KeyValue_GetRevision_Call { + return &KeyValue_GetRevision_Call{Call: _e.mock.On("GetRevision", ctx, key, revision)} +} + +func (_c *KeyValue_GetRevision_Call) Run(run func(ctx context.Context, key string, revision uint64)) *KeyValue_GetRevision_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 uint64 + if args[2] != nil { + arg2 = args[2].(uint64) + } + run( + arg0, + arg1, + arg2, + ) + }) + return _c +} + +func (_c *KeyValue_GetRevision_Call) Return(keyValueEntry jetstream.KeyValueEntry, err error) *KeyValue_GetRevision_Call { + _c.Call.Return(keyValueEntry, err) + return _c +} + +func (_c *KeyValue_GetRevision_Call) RunAndReturn(run func(ctx context.Context, key string, revision uint64) (jetstream.KeyValueEntry, error)) *KeyValue_GetRevision_Call { + _c.Call.Return(run) + return _c +} + +// History provides a mock function for the type KeyValue +func (_mock *KeyValue) History(ctx context.Context, key string, opts ...jetstream.WatchOpt) ([]jetstream.KeyValueEntry, error) { + var tmpRet mock.Arguments + if len(opts) > 0 { + tmpRet = _mock.Called(ctx, key, opts) + } else { + tmpRet = _mock.Called(ctx, key) + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for History") + } + + var r0 []jetstream.KeyValueEntry + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, string, ...jetstream.WatchOpt) ([]jetstream.KeyValueEntry, error)); ok { + return returnFunc(ctx, key, opts...) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, string, ...jetstream.WatchOpt) []jetstream.KeyValueEntry); ok { + r0 = returnFunc(ctx, key, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]jetstream.KeyValueEntry) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, string, ...jetstream.WatchOpt) error); ok { + r1 = returnFunc(ctx, key, opts...) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// KeyValue_History_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'History' +type KeyValue_History_Call struct { + *mock.Call +} + +// History is a helper method to define mock.On call +// - ctx context.Context +// - key string +// - opts ...jetstream.WatchOpt +func (_e *KeyValue_Expecter) History(ctx interface{}, key interface{}, opts ...interface{}) *KeyValue_History_Call { + return &KeyValue_History_Call{Call: _e.mock.On("History", + append([]interface{}{ctx, key}, opts...)...)} +} + +func (_c *KeyValue_History_Call) Run(run func(ctx context.Context, key string, opts ...jetstream.WatchOpt)) *KeyValue_History_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 []jetstream.WatchOpt + var variadicArgs []jetstream.WatchOpt + if len(args) > 2 { + variadicArgs = args[2].([]jetstream.WatchOpt) + } + arg2 = variadicArgs + run( + arg0, + arg1, + arg2..., + ) + }) + return _c +} + +func (_c *KeyValue_History_Call) Return(keyValueEntrys []jetstream.KeyValueEntry, err error) *KeyValue_History_Call { + _c.Call.Return(keyValueEntrys, err) + return _c +} + +func (_c *KeyValue_History_Call) RunAndReturn(run func(ctx context.Context, key string, opts ...jetstream.WatchOpt) ([]jetstream.KeyValueEntry, error)) *KeyValue_History_Call { + _c.Call.Return(run) + return _c +} + +// Keys provides a mock function for the type KeyValue +func (_mock *KeyValue) Keys(ctx context.Context, opts ...jetstream.WatchOpt) ([]string, error) { + var tmpRet mock.Arguments + if len(opts) > 0 { + tmpRet = _mock.Called(ctx, opts) + } else { + tmpRet = _mock.Called(ctx) + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for Keys") + } + + var r0 []string + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, ...jetstream.WatchOpt) ([]string, error)); ok { + return returnFunc(ctx, opts...) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, ...jetstream.WatchOpt) []string); ok { + r0 = returnFunc(ctx, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, ...jetstream.WatchOpt) error); ok { + r1 = returnFunc(ctx, opts...) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// KeyValue_Keys_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Keys' +type KeyValue_Keys_Call struct { + *mock.Call +} + +// Keys is a helper method to define mock.On call +// - ctx context.Context +// - opts ...jetstream.WatchOpt +func (_e *KeyValue_Expecter) Keys(ctx interface{}, opts ...interface{}) *KeyValue_Keys_Call { + return &KeyValue_Keys_Call{Call: _e.mock.On("Keys", + append([]interface{}{ctx}, opts...)...)} +} + +func (_c *KeyValue_Keys_Call) Run(run func(ctx context.Context, opts ...jetstream.WatchOpt)) *KeyValue_Keys_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 []jetstream.WatchOpt + var variadicArgs []jetstream.WatchOpt + if len(args) > 1 { + variadicArgs = args[1].([]jetstream.WatchOpt) + } + arg1 = variadicArgs + run( + arg0, + arg1..., + ) + }) + return _c +} + +func (_c *KeyValue_Keys_Call) Return(strings []string, err error) *KeyValue_Keys_Call { + _c.Call.Return(strings, err) + return _c +} + +func (_c *KeyValue_Keys_Call) RunAndReturn(run func(ctx context.Context, opts ...jetstream.WatchOpt) ([]string, error)) *KeyValue_Keys_Call { + _c.Call.Return(run) + return _c +} + +// ListKeys provides a mock function for the type KeyValue +func (_mock *KeyValue) ListKeys(ctx context.Context, opts ...jetstream.WatchOpt) (jetstream.KeyLister, error) { + var tmpRet mock.Arguments + if len(opts) > 0 { + tmpRet = _mock.Called(ctx, opts) + } else { + tmpRet = _mock.Called(ctx) + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for ListKeys") + } + + var r0 jetstream.KeyLister + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, ...jetstream.WatchOpt) (jetstream.KeyLister, error)); ok { + return returnFunc(ctx, opts...) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, ...jetstream.WatchOpt) jetstream.KeyLister); ok { + r0 = returnFunc(ctx, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(jetstream.KeyLister) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, ...jetstream.WatchOpt) error); ok { + r1 = returnFunc(ctx, opts...) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// KeyValue_ListKeys_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListKeys' +type KeyValue_ListKeys_Call struct { + *mock.Call +} + +// ListKeys is a helper method to define mock.On call +// - ctx context.Context +// - opts ...jetstream.WatchOpt +func (_e *KeyValue_Expecter) ListKeys(ctx interface{}, opts ...interface{}) *KeyValue_ListKeys_Call { + return &KeyValue_ListKeys_Call{Call: _e.mock.On("ListKeys", + append([]interface{}{ctx}, opts...)...)} +} + +func (_c *KeyValue_ListKeys_Call) Run(run func(ctx context.Context, opts ...jetstream.WatchOpt)) *KeyValue_ListKeys_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 []jetstream.WatchOpt + var variadicArgs []jetstream.WatchOpt + if len(args) > 1 { + variadicArgs = args[1].([]jetstream.WatchOpt) + } + arg1 = variadicArgs + run( + arg0, + arg1..., + ) + }) + return _c +} + +func (_c *KeyValue_ListKeys_Call) Return(keyLister jetstream.KeyLister, err error) *KeyValue_ListKeys_Call { + _c.Call.Return(keyLister, err) + return _c +} + +func (_c *KeyValue_ListKeys_Call) RunAndReturn(run func(ctx context.Context, opts ...jetstream.WatchOpt) (jetstream.KeyLister, error)) *KeyValue_ListKeys_Call { + _c.Call.Return(run) + return _c +} + +// ListKeysFiltered provides a mock function for the type KeyValue +func (_mock *KeyValue) ListKeysFiltered(ctx context.Context, filters ...string) (jetstream.KeyLister, error) { + var tmpRet mock.Arguments + if len(filters) > 0 { + tmpRet = _mock.Called(ctx, filters) + } else { + tmpRet = _mock.Called(ctx) + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for ListKeysFiltered") + } + + var r0 jetstream.KeyLister + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, ...string) (jetstream.KeyLister, error)); ok { + return returnFunc(ctx, filters...) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, ...string) jetstream.KeyLister); ok { + r0 = returnFunc(ctx, filters...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(jetstream.KeyLister) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, ...string) error); ok { + r1 = returnFunc(ctx, filters...) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// KeyValue_ListKeysFiltered_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListKeysFiltered' +type KeyValue_ListKeysFiltered_Call struct { + *mock.Call +} + +// ListKeysFiltered is a helper method to define mock.On call +// - ctx context.Context +// - filters ...string +func (_e *KeyValue_Expecter) ListKeysFiltered(ctx interface{}, filters ...interface{}) *KeyValue_ListKeysFiltered_Call { + return &KeyValue_ListKeysFiltered_Call{Call: _e.mock.On("ListKeysFiltered", + append([]interface{}{ctx}, filters...)...)} +} + +func (_c *KeyValue_ListKeysFiltered_Call) Run(run func(ctx context.Context, filters ...string)) *KeyValue_ListKeysFiltered_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 []string + var variadicArgs []string + if len(args) > 1 { + variadicArgs = args[1].([]string) + } + arg1 = variadicArgs + run( + arg0, + arg1..., + ) + }) + return _c +} + +func (_c *KeyValue_ListKeysFiltered_Call) Return(keyLister jetstream.KeyLister, err error) *KeyValue_ListKeysFiltered_Call { + _c.Call.Return(keyLister, err) + return _c +} + +func (_c *KeyValue_ListKeysFiltered_Call) RunAndReturn(run func(ctx context.Context, filters ...string) (jetstream.KeyLister, error)) *KeyValue_ListKeysFiltered_Call { + _c.Call.Return(run) + return _c +} + +// Purge provides a mock function for the type KeyValue +func (_mock *KeyValue) Purge(ctx context.Context, key string, opts ...jetstream.KVDeleteOpt) error { + var tmpRet mock.Arguments + if len(opts) > 0 { + tmpRet = _mock.Called(ctx, key, opts) + } else { + tmpRet = _mock.Called(ctx, key) + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for Purge") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, string, ...jetstream.KVDeleteOpt) error); ok { + r0 = returnFunc(ctx, key, opts...) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// KeyValue_Purge_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Purge' +type KeyValue_Purge_Call struct { + *mock.Call +} + +// Purge is a helper method to define mock.On call +// - ctx context.Context +// - key string +// - opts ...jetstream.KVDeleteOpt +func (_e *KeyValue_Expecter) Purge(ctx interface{}, key interface{}, opts ...interface{}) *KeyValue_Purge_Call { + return &KeyValue_Purge_Call{Call: _e.mock.On("Purge", + append([]interface{}{ctx, key}, opts...)...)} +} + +func (_c *KeyValue_Purge_Call) Run(run func(ctx context.Context, key string, opts ...jetstream.KVDeleteOpt)) *KeyValue_Purge_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 []jetstream.KVDeleteOpt + var variadicArgs []jetstream.KVDeleteOpt + if len(args) > 2 { + variadicArgs = args[2].([]jetstream.KVDeleteOpt) + } + arg2 = variadicArgs + run( + arg0, + arg1, + arg2..., + ) + }) + return _c +} + +func (_c *KeyValue_Purge_Call) Return(err error) *KeyValue_Purge_Call { + _c.Call.Return(err) + return _c +} + +func (_c *KeyValue_Purge_Call) RunAndReturn(run func(ctx context.Context, key string, opts ...jetstream.KVDeleteOpt) error) *KeyValue_Purge_Call { + _c.Call.Return(run) + return _c +} + +// PurgeDeletes provides a mock function for the type KeyValue +func (_mock *KeyValue) PurgeDeletes(ctx context.Context, opts ...jetstream.KVPurgeOpt) error { + var tmpRet mock.Arguments + if len(opts) > 0 { + tmpRet = _mock.Called(ctx, opts) + } else { + tmpRet = _mock.Called(ctx) + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for PurgeDeletes") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, ...jetstream.KVPurgeOpt) error); ok { + r0 = returnFunc(ctx, opts...) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// KeyValue_PurgeDeletes_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PurgeDeletes' +type KeyValue_PurgeDeletes_Call struct { + *mock.Call +} + +// PurgeDeletes is a helper method to define mock.On call +// - ctx context.Context +// - opts ...jetstream.KVPurgeOpt +func (_e *KeyValue_Expecter) PurgeDeletes(ctx interface{}, opts ...interface{}) *KeyValue_PurgeDeletes_Call { + return &KeyValue_PurgeDeletes_Call{Call: _e.mock.On("PurgeDeletes", + append([]interface{}{ctx}, opts...)...)} +} + +func (_c *KeyValue_PurgeDeletes_Call) Run(run func(ctx context.Context, opts ...jetstream.KVPurgeOpt)) *KeyValue_PurgeDeletes_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 []jetstream.KVPurgeOpt + var variadicArgs []jetstream.KVPurgeOpt + if len(args) > 1 { + variadicArgs = args[1].([]jetstream.KVPurgeOpt) + } + arg1 = variadicArgs + run( + arg0, + arg1..., + ) + }) + return _c +} + +func (_c *KeyValue_PurgeDeletes_Call) Return(err error) *KeyValue_PurgeDeletes_Call { + _c.Call.Return(err) + return _c +} + +func (_c *KeyValue_PurgeDeletes_Call) RunAndReturn(run func(ctx context.Context, opts ...jetstream.KVPurgeOpt) error) *KeyValue_PurgeDeletes_Call { + _c.Call.Return(run) + return _c +} + +// Put provides a mock function for the type KeyValue +func (_mock *KeyValue) Put(ctx context.Context, key string, value []byte) (uint64, error) { + ret := _mock.Called(ctx, key, value) + + if len(ret) == 0 { + panic("no return value specified for Put") + } + + var r0 uint64 + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, string, []byte) (uint64, error)); ok { + return returnFunc(ctx, key, value) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, string, []byte) uint64); ok { + r0 = returnFunc(ctx, key, value) + } else { + r0 = ret.Get(0).(uint64) + } + if returnFunc, ok := ret.Get(1).(func(context.Context, string, []byte) error); ok { + r1 = returnFunc(ctx, key, value) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// KeyValue_Put_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Put' +type KeyValue_Put_Call struct { + *mock.Call +} + +// Put is a helper method to define mock.On call +// - ctx context.Context +// - key string +// - value []byte +func (_e *KeyValue_Expecter) Put(ctx interface{}, key interface{}, value interface{}) *KeyValue_Put_Call { + return &KeyValue_Put_Call{Call: _e.mock.On("Put", ctx, key, value)} +} + +func (_c *KeyValue_Put_Call) Run(run func(ctx context.Context, key string, value []byte)) *KeyValue_Put_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 []byte + if args[2] != nil { + arg2 = args[2].([]byte) + } + run( + arg0, + arg1, + arg2, + ) + }) + return _c +} + +func (_c *KeyValue_Put_Call) Return(v uint64, err error) *KeyValue_Put_Call { + _c.Call.Return(v, err) + return _c +} + +func (_c *KeyValue_Put_Call) RunAndReturn(run func(ctx context.Context, key string, value []byte) (uint64, error)) *KeyValue_Put_Call { + _c.Call.Return(run) + return _c +} + +// PutString provides a mock function for the type KeyValue +func (_mock *KeyValue) PutString(ctx context.Context, key string, value string) (uint64, error) { + ret := _mock.Called(ctx, key, value) + + if len(ret) == 0 { + panic("no return value specified for PutString") + } + + var r0 uint64 + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) (uint64, error)); ok { + return returnFunc(ctx, key, value) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) uint64); ok { + r0 = returnFunc(ctx, key, value) + } else { + r0 = ret.Get(0).(uint64) + } + if returnFunc, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = returnFunc(ctx, key, value) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// KeyValue_PutString_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PutString' +type KeyValue_PutString_Call struct { + *mock.Call +} + +// PutString is a helper method to define mock.On call +// - ctx context.Context +// - key string +// - value string +func (_e *KeyValue_Expecter) PutString(ctx interface{}, key interface{}, value interface{}) *KeyValue_PutString_Call { + return &KeyValue_PutString_Call{Call: _e.mock.On("PutString", ctx, key, value)} +} + +func (_c *KeyValue_PutString_Call) Run(run func(ctx context.Context, key string, value string)) *KeyValue_PutString_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + run( + arg0, + arg1, + arg2, + ) + }) + return _c +} + +func (_c *KeyValue_PutString_Call) Return(v uint64, err error) *KeyValue_PutString_Call { + _c.Call.Return(v, err) + return _c +} + +func (_c *KeyValue_PutString_Call) RunAndReturn(run func(ctx context.Context, key string, value string) (uint64, error)) *KeyValue_PutString_Call { + _c.Call.Return(run) + return _c +} + +// Status provides a mock function for the type KeyValue +func (_mock *KeyValue) Status(ctx context.Context) (jetstream.KeyValueStatus, error) { + ret := _mock.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Status") + } + + var r0 jetstream.KeyValueStatus + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context) (jetstream.KeyValueStatus, error)); ok { + return returnFunc(ctx) + } + if returnFunc, ok := ret.Get(0).(func(context.Context) jetstream.KeyValueStatus); ok { + r0 = returnFunc(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(jetstream.KeyValueStatus) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = returnFunc(ctx) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// KeyValue_Status_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Status' +type KeyValue_Status_Call struct { + *mock.Call +} + +// Status is a helper method to define mock.On call +// - ctx context.Context +func (_e *KeyValue_Expecter) Status(ctx interface{}) *KeyValue_Status_Call { + return &KeyValue_Status_Call{Call: _e.mock.On("Status", ctx)} +} + +func (_c *KeyValue_Status_Call) Run(run func(ctx context.Context)) *KeyValue_Status_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *KeyValue_Status_Call) Return(keyValueStatus jetstream.KeyValueStatus, err error) *KeyValue_Status_Call { + _c.Call.Return(keyValueStatus, err) + return _c +} + +func (_c *KeyValue_Status_Call) RunAndReturn(run func(ctx context.Context) (jetstream.KeyValueStatus, error)) *KeyValue_Status_Call { + _c.Call.Return(run) + return _c +} + +// Update provides a mock function for the type KeyValue +func (_mock *KeyValue) Update(ctx context.Context, key string, value []byte, revision uint64) (uint64, error) { + ret := _mock.Called(ctx, key, value, revision) + + if len(ret) == 0 { + panic("no return value specified for Update") + } + + var r0 uint64 + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, string, []byte, uint64) (uint64, error)); ok { + return returnFunc(ctx, key, value, revision) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, string, []byte, uint64) uint64); ok { + r0 = returnFunc(ctx, key, value, revision) + } else { + r0 = ret.Get(0).(uint64) + } + if returnFunc, ok := ret.Get(1).(func(context.Context, string, []byte, uint64) error); ok { + r1 = returnFunc(ctx, key, value, revision) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// KeyValue_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update' +type KeyValue_Update_Call struct { + *mock.Call +} + +// Update is a helper method to define mock.On call +// - ctx context.Context +// - key string +// - value []byte +// - revision uint64 +func (_e *KeyValue_Expecter) Update(ctx interface{}, key interface{}, value interface{}, revision interface{}) *KeyValue_Update_Call { + return &KeyValue_Update_Call{Call: _e.mock.On("Update", ctx, key, value, revision)} +} + +func (_c *KeyValue_Update_Call) Run(run func(ctx context.Context, key string, value []byte, revision uint64)) *KeyValue_Update_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 []byte + if args[2] != nil { + arg2 = args[2].([]byte) + } + var arg3 uint64 + if args[3] != nil { + arg3 = args[3].(uint64) + } + run( + arg0, + arg1, + arg2, + arg3, + ) + }) + return _c +} + +func (_c *KeyValue_Update_Call) Return(v uint64, err error) *KeyValue_Update_Call { + _c.Call.Return(v, err) + return _c +} + +func (_c *KeyValue_Update_Call) RunAndReturn(run func(ctx context.Context, key string, value []byte, revision uint64) (uint64, error)) *KeyValue_Update_Call { + _c.Call.Return(run) + return _c +} + +// Watch provides a mock function for the type KeyValue +func (_mock *KeyValue) Watch(ctx context.Context, keys string, opts ...jetstream.WatchOpt) (jetstream.KeyWatcher, error) { + var tmpRet mock.Arguments + if len(opts) > 0 { + tmpRet = _mock.Called(ctx, keys, opts) + } else { + tmpRet = _mock.Called(ctx, keys) + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for Watch") + } + + var r0 jetstream.KeyWatcher + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, string, ...jetstream.WatchOpt) (jetstream.KeyWatcher, error)); ok { + return returnFunc(ctx, keys, opts...) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, string, ...jetstream.WatchOpt) jetstream.KeyWatcher); ok { + r0 = returnFunc(ctx, keys, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(jetstream.KeyWatcher) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, string, ...jetstream.WatchOpt) error); ok { + r1 = returnFunc(ctx, keys, opts...) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// KeyValue_Watch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Watch' +type KeyValue_Watch_Call struct { + *mock.Call +} + +// Watch is a helper method to define mock.On call +// - ctx context.Context +// - keys string +// - opts ...jetstream.WatchOpt +func (_e *KeyValue_Expecter) Watch(ctx interface{}, keys interface{}, opts ...interface{}) *KeyValue_Watch_Call { + return &KeyValue_Watch_Call{Call: _e.mock.On("Watch", + append([]interface{}{ctx, keys}, opts...)...)} +} + +func (_c *KeyValue_Watch_Call) Run(run func(ctx context.Context, keys string, opts ...jetstream.WatchOpt)) *KeyValue_Watch_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 []jetstream.WatchOpt + var variadicArgs []jetstream.WatchOpt + if len(args) > 2 { + variadicArgs = args[2].([]jetstream.WatchOpt) + } + arg2 = variadicArgs + run( + arg0, + arg1, + arg2..., + ) + }) + return _c +} + +func (_c *KeyValue_Watch_Call) Return(keyWatcher jetstream.KeyWatcher, err error) *KeyValue_Watch_Call { + _c.Call.Return(keyWatcher, err) + return _c +} + +func (_c *KeyValue_Watch_Call) RunAndReturn(run func(ctx context.Context, keys string, opts ...jetstream.WatchOpt) (jetstream.KeyWatcher, error)) *KeyValue_Watch_Call { + _c.Call.Return(run) + return _c +} + +// WatchAll provides a mock function for the type KeyValue +func (_mock *KeyValue) WatchAll(ctx context.Context, opts ...jetstream.WatchOpt) (jetstream.KeyWatcher, error) { + var tmpRet mock.Arguments + if len(opts) > 0 { + tmpRet = _mock.Called(ctx, opts) + } else { + tmpRet = _mock.Called(ctx) + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for WatchAll") + } + + var r0 jetstream.KeyWatcher + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, ...jetstream.WatchOpt) (jetstream.KeyWatcher, error)); ok { + return returnFunc(ctx, opts...) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, ...jetstream.WatchOpt) jetstream.KeyWatcher); ok { + r0 = returnFunc(ctx, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(jetstream.KeyWatcher) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, ...jetstream.WatchOpt) error); ok { + r1 = returnFunc(ctx, opts...) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// KeyValue_WatchAll_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WatchAll' +type KeyValue_WatchAll_Call struct { + *mock.Call +} + +// WatchAll is a helper method to define mock.On call +// - ctx context.Context +// - opts ...jetstream.WatchOpt +func (_e *KeyValue_Expecter) WatchAll(ctx interface{}, opts ...interface{}) *KeyValue_WatchAll_Call { + return &KeyValue_WatchAll_Call{Call: _e.mock.On("WatchAll", + append([]interface{}{ctx}, opts...)...)} +} + +func (_c *KeyValue_WatchAll_Call) Run(run func(ctx context.Context, opts ...jetstream.WatchOpt)) *KeyValue_WatchAll_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 []jetstream.WatchOpt + var variadicArgs []jetstream.WatchOpt + if len(args) > 1 { + variadicArgs = args[1].([]jetstream.WatchOpt) + } + arg1 = variadicArgs + run( + arg0, + arg1..., + ) + }) + return _c +} + +func (_c *KeyValue_WatchAll_Call) Return(keyWatcher jetstream.KeyWatcher, err error) *KeyValue_WatchAll_Call { + _c.Call.Return(keyWatcher, err) + return _c +} + +func (_c *KeyValue_WatchAll_Call) RunAndReturn(run func(ctx context.Context, opts ...jetstream.WatchOpt) (jetstream.KeyWatcher, error)) *KeyValue_WatchAll_Call { + _c.Call.Return(run) + return _c +} + +// WatchFiltered provides a mock function for the type KeyValue +func (_mock *KeyValue) WatchFiltered(ctx context.Context, keys []string, opts ...jetstream.WatchOpt) (jetstream.KeyWatcher, error) { + var tmpRet mock.Arguments + if len(opts) > 0 { + tmpRet = _mock.Called(ctx, keys, opts) + } else { + tmpRet = _mock.Called(ctx, keys) + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for WatchFiltered") + } + + var r0 jetstream.KeyWatcher + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, []string, ...jetstream.WatchOpt) (jetstream.KeyWatcher, error)); ok { + return returnFunc(ctx, keys, opts...) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, []string, ...jetstream.WatchOpt) jetstream.KeyWatcher); ok { + r0 = returnFunc(ctx, keys, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(jetstream.KeyWatcher) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, []string, ...jetstream.WatchOpt) error); ok { + r1 = returnFunc(ctx, keys, opts...) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// KeyValue_WatchFiltered_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WatchFiltered' +type KeyValue_WatchFiltered_Call struct { + *mock.Call +} + +// WatchFiltered is a helper method to define mock.On call +// - ctx context.Context +// - keys []string +// - opts ...jetstream.WatchOpt +func (_e *KeyValue_Expecter) WatchFiltered(ctx interface{}, keys interface{}, opts ...interface{}) *KeyValue_WatchFiltered_Call { + return &KeyValue_WatchFiltered_Call{Call: _e.mock.On("WatchFiltered", + append([]interface{}{ctx, keys}, opts...)...)} +} + +func (_c *KeyValue_WatchFiltered_Call) Run(run func(ctx context.Context, keys []string, opts ...jetstream.WatchOpt)) *KeyValue_WatchFiltered_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 []string + if args[1] != nil { + arg1 = args[1].([]string) + } + var arg2 []jetstream.WatchOpt + var variadicArgs []jetstream.WatchOpt + if len(args) > 2 { + variadicArgs = args[2].([]jetstream.WatchOpt) + } + arg2 = variadicArgs + run( + arg0, + arg1, + arg2..., + ) + }) + return _c +} + +func (_c *KeyValue_WatchFiltered_Call) Return(keyWatcher jetstream.KeyWatcher, err error) *KeyValue_WatchFiltered_Call { + _c.Call.Return(keyWatcher, err) + return _c +} + +func (_c *KeyValue_WatchFiltered_Call) RunAndReturn(run func(ctx context.Context, keys []string, opts ...jetstream.WatchOpt) (jetstream.KeyWatcher, error)) *KeyValue_WatchFiltered_Call { + _c.Call.Return(run) + return _c +} diff --git a/services/web/mocks/key_value_entry.go b/services/web/mocks/key_value_entry.go new file mode 100644 index 0000000000..7decc5725a --- /dev/null +++ b/services/web/mocks/key_value_entry.go @@ -0,0 +1,349 @@ +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify + +package mocks + +import ( + "time" + + "github.com/nats-io/nats.go/jetstream" + mock "github.com/stretchr/testify/mock" +) + +// NewKeyValueEntry creates a new instance of KeyValueEntry. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewKeyValueEntry(t interface { + mock.TestingT + Cleanup(func()) +}) *KeyValueEntry { + mock := &KeyValueEntry{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// KeyValueEntry is an autogenerated mock type for the KeyValueEntry type +type KeyValueEntry struct { + mock.Mock +} + +type KeyValueEntry_Expecter struct { + mock *mock.Mock +} + +func (_m *KeyValueEntry) EXPECT() *KeyValueEntry_Expecter { + return &KeyValueEntry_Expecter{mock: &_m.Mock} +} + +// Bucket provides a mock function for the type KeyValueEntry +func (_mock *KeyValueEntry) Bucket() string { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for Bucket") + } + + var r0 string + if returnFunc, ok := ret.Get(0).(func() string); ok { + r0 = returnFunc() + } else { + r0 = ret.Get(0).(string) + } + return r0 +} + +// KeyValueEntry_Bucket_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Bucket' +type KeyValueEntry_Bucket_Call struct { + *mock.Call +} + +// Bucket is a helper method to define mock.On call +func (_e *KeyValueEntry_Expecter) Bucket() *KeyValueEntry_Bucket_Call { + return &KeyValueEntry_Bucket_Call{Call: _e.mock.On("Bucket")} +} + +func (_c *KeyValueEntry_Bucket_Call) Run(run func()) *KeyValueEntry_Bucket_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *KeyValueEntry_Bucket_Call) Return(s string) *KeyValueEntry_Bucket_Call { + _c.Call.Return(s) + return _c +} + +func (_c *KeyValueEntry_Bucket_Call) RunAndReturn(run func() string) *KeyValueEntry_Bucket_Call { + _c.Call.Return(run) + return _c +} + +// Created provides a mock function for the type KeyValueEntry +func (_mock *KeyValueEntry) Created() time.Time { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for Created") + } + + var r0 time.Time + if returnFunc, ok := ret.Get(0).(func() time.Time); ok { + r0 = returnFunc() + } else { + r0 = ret.Get(0).(time.Time) + } + return r0 +} + +// KeyValueEntry_Created_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Created' +type KeyValueEntry_Created_Call struct { + *mock.Call +} + +// Created is a helper method to define mock.On call +func (_e *KeyValueEntry_Expecter) Created() *KeyValueEntry_Created_Call { + return &KeyValueEntry_Created_Call{Call: _e.mock.On("Created")} +} + +func (_c *KeyValueEntry_Created_Call) Run(run func()) *KeyValueEntry_Created_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *KeyValueEntry_Created_Call) Return(time1 time.Time) *KeyValueEntry_Created_Call { + _c.Call.Return(time1) + return _c +} + +func (_c *KeyValueEntry_Created_Call) RunAndReturn(run func() time.Time) *KeyValueEntry_Created_Call { + _c.Call.Return(run) + return _c +} + +// Delta provides a mock function for the type KeyValueEntry +func (_mock *KeyValueEntry) Delta() uint64 { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for Delta") + } + + var r0 uint64 + if returnFunc, ok := ret.Get(0).(func() uint64); ok { + r0 = returnFunc() + } else { + r0 = ret.Get(0).(uint64) + } + return r0 +} + +// KeyValueEntry_Delta_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delta' +type KeyValueEntry_Delta_Call struct { + *mock.Call +} + +// Delta is a helper method to define mock.On call +func (_e *KeyValueEntry_Expecter) Delta() *KeyValueEntry_Delta_Call { + return &KeyValueEntry_Delta_Call{Call: _e.mock.On("Delta")} +} + +func (_c *KeyValueEntry_Delta_Call) Run(run func()) *KeyValueEntry_Delta_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *KeyValueEntry_Delta_Call) Return(v uint64) *KeyValueEntry_Delta_Call { + _c.Call.Return(v) + return _c +} + +func (_c *KeyValueEntry_Delta_Call) RunAndReturn(run func() uint64) *KeyValueEntry_Delta_Call { + _c.Call.Return(run) + return _c +} + +// Key provides a mock function for the type KeyValueEntry +func (_mock *KeyValueEntry) Key() string { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for Key") + } + + var r0 string + if returnFunc, ok := ret.Get(0).(func() string); ok { + r0 = returnFunc() + } else { + r0 = ret.Get(0).(string) + } + return r0 +} + +// KeyValueEntry_Key_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Key' +type KeyValueEntry_Key_Call struct { + *mock.Call +} + +// Key is a helper method to define mock.On call +func (_e *KeyValueEntry_Expecter) Key() *KeyValueEntry_Key_Call { + return &KeyValueEntry_Key_Call{Call: _e.mock.On("Key")} +} + +func (_c *KeyValueEntry_Key_Call) Run(run func()) *KeyValueEntry_Key_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *KeyValueEntry_Key_Call) Return(s string) *KeyValueEntry_Key_Call { + _c.Call.Return(s) + return _c +} + +func (_c *KeyValueEntry_Key_Call) RunAndReturn(run func() string) *KeyValueEntry_Key_Call { + _c.Call.Return(run) + return _c +} + +// Operation provides a mock function for the type KeyValueEntry +func (_mock *KeyValueEntry) Operation() jetstream.KeyValueOp { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for Operation") + } + + var r0 jetstream.KeyValueOp + if returnFunc, ok := ret.Get(0).(func() jetstream.KeyValueOp); ok { + r0 = returnFunc() + } else { + r0 = ret.Get(0).(jetstream.KeyValueOp) + } + return r0 +} + +// KeyValueEntry_Operation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Operation' +type KeyValueEntry_Operation_Call struct { + *mock.Call +} + +// Operation is a helper method to define mock.On call +func (_e *KeyValueEntry_Expecter) Operation() *KeyValueEntry_Operation_Call { + return &KeyValueEntry_Operation_Call{Call: _e.mock.On("Operation")} +} + +func (_c *KeyValueEntry_Operation_Call) Run(run func()) *KeyValueEntry_Operation_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *KeyValueEntry_Operation_Call) Return(keyValueOp jetstream.KeyValueOp) *KeyValueEntry_Operation_Call { + _c.Call.Return(keyValueOp) + return _c +} + +func (_c *KeyValueEntry_Operation_Call) RunAndReturn(run func() jetstream.KeyValueOp) *KeyValueEntry_Operation_Call { + _c.Call.Return(run) + return _c +} + +// Revision provides a mock function for the type KeyValueEntry +func (_mock *KeyValueEntry) Revision() uint64 { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for Revision") + } + + var r0 uint64 + if returnFunc, ok := ret.Get(0).(func() uint64); ok { + r0 = returnFunc() + } else { + r0 = ret.Get(0).(uint64) + } + return r0 +} + +// KeyValueEntry_Revision_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Revision' +type KeyValueEntry_Revision_Call struct { + *mock.Call +} + +// Revision is a helper method to define mock.On call +func (_e *KeyValueEntry_Expecter) Revision() *KeyValueEntry_Revision_Call { + return &KeyValueEntry_Revision_Call{Call: _e.mock.On("Revision")} +} + +func (_c *KeyValueEntry_Revision_Call) Run(run func()) *KeyValueEntry_Revision_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *KeyValueEntry_Revision_Call) Return(v uint64) *KeyValueEntry_Revision_Call { + _c.Call.Return(v) + return _c +} + +func (_c *KeyValueEntry_Revision_Call) RunAndReturn(run func() uint64) *KeyValueEntry_Revision_Call { + _c.Call.Return(run) + return _c +} + +// Value provides a mock function for the type KeyValueEntry +func (_mock *KeyValueEntry) Value() []byte { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for Value") + } + + var r0 []byte + if returnFunc, ok := ret.Get(0).(func() []byte); ok { + r0 = returnFunc() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + return r0 +} + +// KeyValueEntry_Value_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Value' +type KeyValueEntry_Value_Call struct { + *mock.Call +} + +// Value is a helper method to define mock.On call +func (_e *KeyValueEntry_Expecter) Value() *KeyValueEntry_Value_Call { + return &KeyValueEntry_Value_Call{Call: _e.mock.On("Value")} +} + +func (_c *KeyValueEntry_Value_Call) Run(run func()) *KeyValueEntry_Value_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *KeyValueEntry_Value_Call) Return(bytes []byte) *KeyValueEntry_Value_Call { + _c.Call.Return(bytes) + return _c +} + +func (_c *KeyValueEntry_Value_Call) RunAndReturn(run func() []byte) *KeyValueEntry_Value_Call { + _c.Call.Return(run) + return _c +} diff --git a/services/web/pkg/announcement/announcement.go b/services/web/pkg/announcement/announcement.go index 5fb1029b89..085fd16a71 100644 --- a/services/web/pkg/announcement/announcement.go +++ b/services/web/pkg/announcement/announcement.go @@ -2,10 +2,11 @@ package announcement import ( + "context" "encoding/json" "errors" - microstore "go-micro.dev/v4/store" + "github.com/nats-io/nats.go/jetstream" ) // _storeKey is the single key under which the announcement is persisted. @@ -19,33 +20,29 @@ type Announcement struct { InfoText string `json:"infoText"` } -// Store persists a single announcement in a key-value store. +// Store persists a single announcement in a NATS JetStream key-value bucket. type Store struct { - store microstore.Store + kv jetstream.KeyValue } -// NewStore returns a new announcement Store backed by the given key-value store. -func NewStore(s microstore.Store) *Store { - return &Store{store: s} +// NewStore returns a new announcement Store backed by the given key-value bucket. +func NewStore(kv jetstream.KeyValue) *Store { + return &Store{kv: kv} } // Get returns the currently stored announcement. An unset announcement is returned as the zero value. -func (s *Store) Get() (Announcement, error) { +func (s *Store) Get(ctx context.Context) (Announcement, error) { var a Announcement - records, err := s.store.Read(_storeKey) + entry, err := s.kv.Get(ctx, _storeKey) if err != nil { - if errors.Is(err, microstore.ErrNotFound) { + if errors.Is(err, jetstream.ErrKeyNotFound) { return a, nil } return a, err } - if len(records) == 0 { - return a, nil - } - - if err := json.Unmarshal(records[0].Value, &a); err != nil { + if err := json.Unmarshal(entry.Value(), &a); err != nil { return a, err } @@ -53,21 +50,19 @@ func (s *Store) Get() (Announcement, error) { } // Set persists the given announcement, overwriting any existing one. -func (s *Store) Set(a Announcement) error { +func (s *Store) Set(ctx context.Context, a Announcement) error { value, err := json.Marshal(a) if err != nil { return err } - return s.store.Write(µstore.Record{ - Key: _storeKey, - Value: value, - }) + _, err = s.kv.Put(ctx, _storeKey, value) + return err } // Delete removes the stored announcement. Deleting a missing announcement is a no-op. -func (s *Store) Delete() error { - if err := s.store.Delete(_storeKey); err != nil && !errors.Is(err, microstore.ErrNotFound) { +func (s *Store) Delete(ctx context.Context) error { + if err := s.kv.Delete(ctx, _storeKey); err != nil && !errors.Is(err, jetstream.ErrKeyNotFound) { return err } return nil diff --git a/services/web/pkg/announcement/announcement_test.go b/services/web/pkg/announcement/announcement_test.go index 986bc9d40b..2cfe22dd44 100644 --- a/services/web/pkg/announcement/announcement_test.go +++ b/services/web/pkg/announcement/announcement_test.go @@ -1,6 +1,7 @@ package announcement_test import ( + "context" "encoding/json" "net/http" "net/http/httptest" @@ -10,22 +11,20 @@ import ( userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" cs3permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + "github.com/nats-io/nats.go/jetstream" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" revactx "github.com/opencloud-eu/reva/v2/pkg/ctx" "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" - "github.com/opencloud-eu/reva/v2/pkg/store" cs3mocks "github.com/opencloud-eu/reva/v2/tests/cs3mocks/mocks" "github.com/stretchr/testify/mock" "google.golang.org/grpc" + "github.com/opencloud-eu/opencloud/pkg/log" + "github.com/opencloud-eu/opencloud/services/web/mocks" "github.com/opencloud-eu/opencloud/services/web/pkg/announcement" ) -func newStore() *announcement.Store { - return announcement.NewStore(store.Create(store.Store("memory"))) -} - func newGatewaySelector(allowed bool) pool.Selectable[gateway.GatewayAPIClient] { code := cs3rpc.Code_CODE_OK name := "announcement-test-allowed" @@ -54,6 +53,7 @@ func withUser(r *http.Request) *http.Request { func newService(store *announcement.Store, allowed bool) announcement.Service { svc, err := announcement.NewService(announcement.ServiceOptions{}. + WithLogger(log.NopLogger()). WithStore(store). WithGatewaySelector(newGatewaySelector(allowed))) Expect(err).ToNot(HaveOccurred()) @@ -61,28 +61,47 @@ func newService(store *announcement.Store, allowed bool) announcement.Service { } var _ = Describe("Store", func() { - It("gets, sets and deletes the announcement", func() { - s := newStore() + It("reads the stored announcement", func() { + entry := mocks.NewKeyValueEntry(GinkgoT()) + entry.EXPECT().Value().Return([]byte(`{"enabled":true,"bannerText":"hello","infoText":"world"}`)) + kv := mocks.NewKeyValue(GinkgoT()) + kv.EXPECT().Get(mock.Anything, "announcement").Return(entry, nil) - got, err := s.Get() - Expect(err).ToNot(HaveOccurred()) - Expect(got.BannerText).To(BeEmpty()) - - Expect(s.Set(announcement.Announcement{Enabled: true, BannerText: "hello", InfoText: "world"})).To(Succeed()) - got, err = s.Get() + got, err := announcement.NewStore(kv).Get(context.Background()) Expect(err).ToNot(HaveOccurred()) Expect(got.Enabled).To(BeTrue()) Expect(got.BannerText).To(Equal("hello")) Expect(got.InfoText).To(Equal("world")) + }) - Expect(s.Delete()).To(Succeed()) - got, err = s.Get() + It("returns the zero value when unset", func() { + kv := mocks.NewKeyValue(GinkgoT()) + kv.EXPECT().Get(mock.Anything, "announcement").Return(nil, jetstream.ErrKeyNotFound) + + got, err := announcement.NewStore(kv).Get(context.Background()) Expect(err).ToNot(HaveOccurred()) Expect(got.BannerText).To(BeEmpty()) }) + It("writes the announcement", func() { + kv := mocks.NewKeyValue(GinkgoT()) + kv.EXPECT().Put(mock.Anything, "announcement", mock.Anything).Return(uint64(1), nil) + + Expect(announcement.NewStore(kv).Set(context.Background(), announcement.Announcement{BannerText: "hello"})).To(Succeed()) + }) + + It("deletes the announcement", func() { + kv := mocks.NewKeyValue(GinkgoT()) + kv.EXPECT().Delete(mock.Anything, "announcement").Return(nil) + + Expect(announcement.NewStore(kv).Delete(context.Background())).To(Succeed()) + }) + It("treats deleting a missing announcement as a no-op", func() { - Expect(newStore().Delete()).To(Succeed()) + kv := mocks.NewKeyValue(GinkgoT()) + kv.EXPECT().Delete(mock.Anything, "announcement").Return(jetstream.ErrKeyNotFound) + + Expect(announcement.NewStore(kv).Delete(context.Background())).To(Succeed()) }) }) @@ -95,7 +114,7 @@ var _ = Describe("Service", func() { It("succeeds when options are valid", func() { _, err := announcement.NewService(announcement.ServiceOptions{}. - WithStore(newStore()). + WithStore(announcement.NewStore(mocks.NewKeyValue(GinkgoT()))). WithGatewaySelector(newGatewaySelector(true))) Expect(err).ToNot(HaveOccurred()) }) @@ -103,12 +122,15 @@ var _ = Describe("Service", func() { Describe("Get", func() { It("returns the full stored announcement when permitted", func() { - s := newStore() - Expect(s.Set(announcement.Announcement{Enabled: true, BannerText: "hello", InfoText: "world"})).To(Succeed()) + entry := mocks.NewKeyValueEntry(GinkgoT()) + entry.EXPECT().Value().Return([]byte(`{"enabled":true,"bannerText":"hello","infoText":"world"}`)) + kv := mocks.NewKeyValue(GinkgoT()) + kv.EXPECT().Get(mock.Anything, "announcement").Return(entry, nil) + req := withUser(httptest.NewRequest(http.MethodGet, "/announcement", nil)) resp := httptest.NewRecorder() - newService(s, true).Get(resp, req) + newService(announcement.NewStore(kv), true).Get(resp, req) Expect(resp.Code).To(Equal(http.StatusOK)) var got announcement.Announcement @@ -122,7 +144,7 @@ var _ = Describe("Service", func() { req := withUser(httptest.NewRequest(http.MethodGet, "/announcement", nil)) resp := httptest.NewRecorder() - newService(newStore(), false).Get(resp, req) + newService(announcement.NewStore(mocks.NewKeyValue(GinkgoT())), false).Get(resp, req) Expect(resp.Code).To(Equal(http.StatusForbidden)) }) @@ -130,34 +152,31 @@ var _ = Describe("Service", func() { Describe("Set", func() { It("persists the message when permitted", func() { - s := newStore() + kv := mocks.NewKeyValue(GinkgoT()) + kv.EXPECT().Put(mock.Anything, "announcement", mock.Anything).Return(uint64(1), nil) + req := withUser(httptest.NewRequest(http.MethodPut, "/announcement", strings.NewReader(`{"bannerText":"hello"}`))) resp := httptest.NewRecorder() - newService(s, true).Set(resp, req) + newService(announcement.NewStore(kv), true).Set(resp, req) Expect(resp.Code).To(Equal(http.StatusNoContent)) - got, _ := s.Get() - Expect(got.BannerText).To(Equal("hello")) }) It("is forbidden without permission", func() { - s := newStore() req := withUser(httptest.NewRequest(http.MethodPut, "/announcement", strings.NewReader(`{"bannerText":"hello"}`))) resp := httptest.NewRecorder() - newService(s, false).Set(resp, req) + newService(announcement.NewStore(mocks.NewKeyValue(GinkgoT())), false).Set(resp, req) Expect(resp.Code).To(Equal(http.StatusForbidden)) - got, _ := s.Get() - Expect(got.BannerText).To(BeEmpty()) }) It("rejects an invalid body", func() { req := withUser(httptest.NewRequest(http.MethodPut, "/announcement", strings.NewReader(`not json`))) resp := httptest.NewRecorder() - newService(newStore(), true).Set(resp, req) + newService(announcement.NewStore(mocks.NewKeyValue(GinkgoT())), true).Set(resp, req) Expect(resp.Code).To(Equal(http.StatusBadRequest)) }) @@ -167,27 +186,22 @@ var _ = Describe("Service", func() { req := withUser(httptest.NewRequest(http.MethodPut, "/announcement", strings.NewReader(body))) resp := httptest.NewRecorder() - s := newStore() - newService(s, true).Set(resp, req) + newService(announcement.NewStore(mocks.NewKeyValue(GinkgoT())), true).Set(resp, req) Expect(resp.Code).To(Equal(http.StatusRequestEntityTooLarge)) - got, _ := s.Get() - Expect(got.BannerText).To(BeEmpty()) }) }) Describe("Set with an empty banner text", func() { It("removes the stored announcement", func() { - s := newStore() - Expect(s.Set(announcement.Announcement{Enabled: true, BannerText: "hello"})).To(Succeed()) + kv := mocks.NewKeyValue(GinkgoT()) + kv.EXPECT().Delete(mock.Anything, "announcement").Return(nil) req := withUser(httptest.NewRequest(http.MethodPut, "/announcement", strings.NewReader(`{"enabled":false,"bannerText":"","infoText":""}`))) resp := httptest.NewRecorder() - newService(s, true).Set(resp, req) + newService(announcement.NewStore(kv), true).Set(resp, req) Expect(resp.Code).To(Equal(http.StatusNoContent)) - got, _ := s.Get() - Expect(got.BannerText).To(BeEmpty()) }) }) }) diff --git a/services/web/pkg/announcement/service.go b/services/web/pkg/announcement/service.go index f601073da5..d20512eb18 100644 --- a/services/web/pkg/announcement/service.go +++ b/services/web/pkg/announcement/service.go @@ -118,7 +118,7 @@ func (s Service) Get(w http.ResponseWriter, r *http.Request) { return } - a, err := s.store.Get() + a, err := s.store.Get(r.Context()) if err != nil { s.logError(r, err, "could not read announcement from store") w.WriteHeader(http.StatusInternalServerError) @@ -177,12 +177,12 @@ func (s Service) Set(w http.ResponseWriter, r *http.Request) { // an announcement without a banner text is nothing to show, so remove it entirely if body.BannerText == "" { - if err := s.store.Delete(); err != nil { + if err := s.store.Delete(r.Context()); err != nil { s.logError(r, err, "could not delete announcement from store") w.WriteHeader(http.StatusInternalServerError) return } - } else if err := s.store.Set(body); err != nil { + } else if err := s.store.Set(r.Context(), body); err != nil { s.logError(r, err, "could not write announcement to store") w.WriteHeader(http.StatusInternalServerError) return diff --git a/services/web/pkg/config/config.go b/services/web/pkg/config/config.go index d40f438b5e..b643c56f8c 100644 --- a/services/web/pkg/config/config.go +++ b/services/web/pkg/config/config.go @@ -2,7 +2,6 @@ package config import ( "context" - "time" "github.com/opencloud-eu/opencloud/pkg/shared" ) @@ -30,18 +29,16 @@ type Config struct { Context context.Context `yaml:"-"` } -// Store configures the persistent store used to keep runtime managed web settings, e.g. the announcement banner. +// Store configures the NATS JetStream key-value store used to keep runtime managed web settings, +// e.g. the announcement banner. type Store struct { - Store string `yaml:"store" env:"OC_PERSISTENT_STORE;WEB_STORE" desc:"The type of the store. Supported values are: 'memory', 'nats-js-kv', 'redis-sentinel', 'noop'. See the text description for details." introductionVersion:"%%NEXT%%"` - Nodes []string `yaml:"nodes" env:"OC_PERSISTENT_STORE_NODES;WEB_STORE_NODES" desc:"A list of nodes to access the configured store. This has no effect when 'memory' store is configured. Note that the behaviour how nodes are used is dependent on the library of the configured store. See the Environment Variable Types description for more details." introductionVersion:"%%NEXT%%"` - Database string `yaml:"database" env:"WEB_STORE_DATABASE" desc:"The database name the configured store should use." introductionVersion:"%%NEXT%%"` - Table string `yaml:"table" env:"WEB_STORE_TABLE" desc:"The database table the store should use." introductionVersion:"%%NEXT%%"` - TTL time.Duration `yaml:"ttl" env:"OC_PERSISTENT_STORE_TTL;WEB_STORE_TTL" desc:"Time to live for entries in the store. See the Environment Variable Types description for more details." introductionVersion:"%%NEXT%%"` - AuthUsername string `yaml:"username" env:"OC_PERSISTENT_STORE_AUTH_USERNAME;WEB_STORE_AUTH_USERNAME" desc:"The username to authenticate with the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"%%NEXT%%"` - AuthPassword string `yaml:"password" env:"OC_PERSISTENT_STORE_AUTH_PASSWORD;WEB_STORE_AUTH_PASSWORD" desc:"The password to authenticate with the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"%%NEXT%%"` - EnableTLS bool `yaml:"enable_tls" env:"OC_PERSISTENT_STORE_ENABLE_TLS;WEB_STORE_ENABLE_TLS" desc:"Enable TLS for the connection to the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"%%NEXT%%"` - TLSInsecure bool `yaml:"tls_insecure" env:"OC_INSECURE;OC_PERSISTENT_STORE_TLS_INSECURE;WEB_STORE_TLS_INSECURE" desc:"Whether to verify the server TLS certificates." introductionVersion:"%%NEXT%%"` - TLSRootCACertificate string `yaml:"tls_root_ca_certificate" env:"OC_PERSISTENT_STORE_TLS_ROOT_CA_CERTIFICATE;WEB_STORE_TLS_ROOT_CA_CERTIFICATE" desc:"The root CA certificate used to validate the server's TLS certificate. If provided WEB_STORE_TLS_INSECURE will be seen as false." introductionVersion:"%%NEXT%%"` + Nodes []string `yaml:"nodes" env:"OC_PERSISTENT_STORE_NODES;WEB_STORE_NODES" desc:"A list of nodes to access the NATS JetStream store. See the Environment Variable Types description for more details." introductionVersion:"%%NEXT%%"` + Database string `yaml:"database" env:"WEB_STORE_DATABASE" desc:"The bucket name the store should use." introductionVersion:"%%NEXT%%"` + AuthUsername string `yaml:"username" env:"OC_PERSISTENT_STORE_AUTH_USERNAME;WEB_STORE_AUTH_USERNAME" desc:"The username to authenticate with the store." introductionVersion:"%%NEXT%%"` + AuthPassword string `yaml:"password" env:"OC_PERSISTENT_STORE_AUTH_PASSWORD;WEB_STORE_AUTH_PASSWORD" desc:"The password to authenticate with the store." introductionVersion:"%%NEXT%%"` + EnableTLS bool `yaml:"enable_tls" env:"OC_PERSISTENT_STORE_ENABLE_TLS;WEB_STORE_ENABLE_TLS" desc:"Enable TLS for the connection to the store." introductionVersion:"%%NEXT%%"` + TLSInsecure bool `yaml:"tls_insecure" env:"OC_INSECURE;OC_PERSISTENT_STORE_TLS_INSECURE;WEB_STORE_TLS_INSECURE" desc:"Whether to verify the server TLS certificates." introductionVersion:"%%NEXT%%"` + TLSRootCACertificate string `yaml:"tls_root_ca_certificate" env:"OC_PERSISTENT_STORE_TLS_ROOT_CA_CERTIFICATE;WEB_STORE_TLS_ROOT_CA_CERTIFICATE" desc:"The root CA certificate used to validate the server's TLS certificate. If provided WEB_STORE_TLS_INSECURE will be seen as false." introductionVersion:"%%NEXT%%"` } // Asset defines the available asset configuration. diff --git a/services/web/pkg/config/defaults/defaultconfig.go b/services/web/pkg/config/defaults/defaultconfig.go index 451e43ce6e..0460bcef26 100644 --- a/services/web/pkg/config/defaults/defaultconfig.go +++ b/services/web/pkg/config/defaults/defaultconfig.go @@ -86,11 +86,8 @@ func DefaultConfig() *config.Config { }, GatewayAddress: "eu.opencloud.api.gateway", Store: config.Store{ - Store: "nats-js-kv", Nodes: []string{"127.0.0.1:9233"}, Database: "web", - Table: "", - TTL: 0, }, Web: config.Web{ ThemeServer: "https://localhost:9200", diff --git a/services/web/pkg/server/http/server.go b/services/web/pkg/server/http/server.go index eb393f31ac..13c21ec8aa 100644 --- a/services/web/pkg/server/http/server.go +++ b/services/web/pkg/server/http/server.go @@ -6,9 +6,8 @@ import ( chimiddleware "github.com/go-chi/chi/v5/middleware" "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" - "github.com/opencloud-eu/reva/v2/pkg/store" + "github.com/opencloud-eu/reva/v2/pkg/storage/cache" "go-micro.dev/v4" - microstore "go-micro.dev/v4/store" "github.com/opencloud-eu/opencloud/pkg/cors" "github.com/opencloud-eu/opencloud/pkg/middleware" @@ -79,18 +78,20 @@ func Server(opts ...Option) (http.Service, error) { fsx.NewBasePathFs(fsx.FromIOFS(web.Assets), "assets/themes"), ) - // persistent store for runtime managed web settings, e.g. the announcement banner - announcementStore := announcement.NewStore(store.Create( - store.Store(options.Config.Store.Store), - store.TTL(options.Config.Store.TTL), - microstore.Nodes(options.Config.Store.Nodes...), - microstore.Database(options.Config.Store.Database), - microstore.Table(options.Config.Store.Table), - store.Authentication(options.Config.Store.AuthUsername, options.Config.Store.AuthPassword), - store.TLSEnabled(options.Config.Store.EnableTLS), - store.TLSInsecure(options.Config.Store.TLSInsecure), - store.TLSRootCA(options.Config.Store.TLSRootCACertificate), - )) + // NATS JetStream key-value store for runtime managed web settings, e.g. the announcement banner + kv, err := cache.NewNatsKeyValue(cache.Config{ + Nodes: options.Config.Store.Nodes, + Database: options.Config.Store.Database, + AuthUsername: options.Config.Store.AuthUsername, + AuthPassword: options.Config.Store.AuthPassword, + TLSEnabled: options.Config.Store.EnableTLS, + TLSInsecure: options.Config.Store.TLSInsecure, + TLSRootCACertificate: options.Config.Store.TLSRootCACertificate, + }, &options.Logger.Logger) + if err != nil { + return http.Service{}, fmt.Errorf("could not initialize announcement store: %w", err) + } + announcementStore := announcement.NewStore(kv) handle, err := svc.NewService( svc.Logger(options.Logger), diff --git a/services/web/pkg/service/v0/service.go b/services/web/pkg/service/v0/service.go index f1ed45f372..9db9ac23c8 100644 --- a/services/web/pkg/service/v0/service.go +++ b/services/web/pkg/service/v0/service.go @@ -1,6 +1,7 @@ package svc import ( + "context" "encoding/json" "io/fs" "net/http" @@ -136,7 +137,7 @@ func (p Web) ServeHTTP(w http.ResponseWriter, r *http.Request) { p.mux.ServeHTTP(w, r) } -func (p Web) getPayload() (payload []byte, err error) { +func (p Web) getPayload(ctx context.Context) (payload []byte, err error) { // render dynamically using a copy of the config, so per-request values (e.g. the // announcement) are not written into the shared config concurrently. webConfig := p.config.Web.Config @@ -160,7 +161,7 @@ func (p Web) getPayload() (payload []byte, err error) { // the runtime store is authoritative once it manages an announcement (enabled or explicitly // disabled); only when it holds nothing do we keep a statically configured one // (web.config.options.announcement) - if a, managed := p.managedAnnouncement(); managed { + if a, managed := p.managedAnnouncement(ctx); managed { webConfig.Options.Announcement = a } @@ -171,12 +172,12 @@ func (p Web) getPayload() (payload []byte, err error) { // bool reports whether the store manages one at all: when true, the returned value (nil for an // explicitly disabled announcement) overrides any static config; when false, the store holds // nothing and a statically configured announcement is kept. -func (p Web) managedAnnouncement() (*config.Announcement, bool) { +func (p Web) managedAnnouncement(ctx context.Context) (*config.Announcement, bool) { if p.announcementStore == nil { return nil, false } - a, err := p.announcementStore.Get() + a, err := p.announcementStore.Get(ctx) if err != nil { p.logger.Error().Err(err).Msg("could not read announcement from store") return nil, false @@ -197,8 +198,8 @@ func (p Web) managedAnnouncement() (*config.Announcement, bool) { } // Config implements the Service interface. -func (p Web) Config(w http.ResponseWriter, _ *http.Request) { - payload, err := p.getPayload() +func (p Web) Config(w http.ResponseWriter, r *http.Request) { + payload, err := p.getPayload(r.Context()) if err != nil { http.Error(w, ErrConfigInvalid, http.StatusUnprocessableEntity) return diff --git a/services/web/pkg/service/v0/service_test.go b/services/web/pkg/service/v0/service_test.go index 9a983f0bca..e4cc1704c2 100644 --- a/services/web/pkg/service/v0/service_test.go +++ b/services/web/pkg/service/v0/service_test.go @@ -1,56 +1,67 @@ package svc import ( + "context" "testing" - "github.com/opencloud-eu/reva/v2/pkg/store" + "github.com/nats-io/nats.go/jetstream" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/opencloud-eu/opencloud/pkg/log" + "github.com/opencloud-eu/opencloud/services/web/mocks" "github.com/opencloud-eu/opencloud/services/web/pkg/announcement" "github.com/opencloud-eu/opencloud/services/web/pkg/config" ) func TestManagedAnnouncement(t *testing.T) { - newWeb := func(a *announcement.Store) Web { - return Web{logger: log.NopLogger(), announcementStore: a} + newWeb := func(store *announcement.Store) Web { + return Web{logger: log.NopLogger(), announcementStore: store} } - newStore := func() *announcement.Store { - return announcement.NewStore(store.Create(store.Store("memory"))) + // storeReturning builds a store whose backing bucket returns the given JSON for the announcement key. + storeReturning := func(t *testing.T, value string) *announcement.Store { + entry := mocks.NewKeyValueEntry(t) + entry.EXPECT().Value().Return([]byte(value)) + kv := mocks.NewKeyValue(t) + kv.EXPECT().Get(mock.Anything, "announcement").Return(entry, nil) + return announcement.NewStore(kv) + } + // emptyStore builds a store whose backing bucket has no announcement. + emptyStore := func(t *testing.T) *announcement.Store { + kv := mocks.NewKeyValue(t) + kv.EXPECT().Get(mock.Anything, "announcement").Return(nil, jetstream.ErrKeyNotFound) + return announcement.NewStore(kv) } t.Run("not managed when there is no store, so a static config is kept", func(t *testing.T) { - a, managed := newWeb(nil).managedAnnouncement() + a, managed := newWeb(nil).managedAnnouncement(context.Background()) require.Nil(t, a) require.False(t, managed) }) t.Run("not managed when the store is empty, so a static config is kept", func(t *testing.T) { - a, managed := newWeb(newStore()).managedAnnouncement() + a, managed := newWeb(emptyStore(t)).managedAnnouncement(context.Background()) require.Nil(t, a) require.False(t, managed) }) t.Run("managed but nil when disabled, so a static config is cleared", func(t *testing.T) { - s := newStore() - require.NoError(t, s.Set(announcement.Announcement{Enabled: false, BannerText: "hi", InfoText: "info"})) - a, managed := newWeb(s).managedAnnouncement() + s := storeReturning(t, `{"enabled":false,"bannerText":"hi","infoText":"info"}`) + a, managed := newWeb(s).managedAnnouncement(context.Background()) require.Nil(t, a) require.True(t, managed) }) t.Run("not managed when enabled but the banner text is empty", func(t *testing.T) { - s := newStore() - require.NoError(t, s.Set(announcement.Announcement{Enabled: true, InfoText: "info"})) - a, managed := newWeb(s).managedAnnouncement() + s := storeReturning(t, `{"enabled":true,"bannerText":"","infoText":"info"}`) + a, managed := newWeb(s).managedAnnouncement(context.Background()) require.Nil(t, a) require.False(t, managed) }) t.Run("managed with banner and info text when enabled with a banner text", func(t *testing.T) { - s := newStore() - require.NoError(t, s.Set(announcement.Announcement{Enabled: true, BannerText: "hi", InfoText: "info"})) - a, managed := newWeb(s).managedAnnouncement() + s := storeReturning(t, `{"enabled":true,"bannerText":"hi","infoText":"info"}`) + a, managed := newWeb(s).managedAnnouncement(context.Background()) require.Equal(t, &config.Announcement{BannerText: "hi", InfoText: "info"}, a) require.True(t, managed) })