From 6af2c44f7fcc8b657fa674d3409317943e3340ee Mon Sep 17 00:00:00 2001 From: Florian Schade Date: Mon, 16 Feb 2026 23:20:39 +0100 Subject: [PATCH] test: add more backchannellogout tests --- services/proxy/.mockery.yaml | 5 + .../pkg/staticroutes/backchannellogout.go | 15 +- .../backchannellogout/backchannellogout.go | 16 +- .../backchannellogout_test.go | 222 +++++--- .../internal/backchannellogout/mocks/store.go | 509 ++++++++++++++++++ 5 files changed, 693 insertions(+), 74 deletions(-) create mode 100644 services/proxy/pkg/staticroutes/internal/backchannellogout/mocks/store.go diff --git a/services/proxy/.mockery.yaml b/services/proxy/.mockery.yaml index a490457301..d3ae0a3817 100644 --- a/services/proxy/.mockery.yaml +++ b/services/proxy/.mockery.yaml @@ -12,3 +12,8 @@ packages: github.com/opencloud-eu/opencloud/services/proxy/pkg/userroles: interfaces: UserRoleAssigner: {} + go-micro.dev/v4/store: + config: + dir: pkg/staticroutes/internal/backchannellogout/mocks + interfaces: + Store: {} diff --git a/services/proxy/pkg/staticroutes/backchannellogout.go b/services/proxy/pkg/staticroutes/backchannellogout.go index c213e090a1..0d44e7cd1c 100644 --- a/services/proxy/pkg/staticroutes/backchannellogout.go +++ b/services/proxy/pkg/staticroutes/backchannellogout.go @@ -37,6 +37,9 @@ import ( // all sessions besides the one that triggered the backchannel logout continue to exist in the identity provider, // so the user will not be fully logged out until all sessions are logged out or expired. // this leads to the situation that web renders the logout view even if the instance is not fully logged out yet. +// +// toDo: +// - check logs and errors to not contain any sensitive information like session ids or user ids (keys too) func (s *StaticRouteHandler) backchannelLogout(w http.ResponseWriter, r *http.Request) { logger := s.Logger.SubloggerWithRequestID(r.Context()) if err := r.ParseForm(); err != nil { @@ -85,14 +88,14 @@ func (s *StaticRouteHandler) backchannelLogout(w http.ResponseWriter, r *http.Re // the record value is the key of the record that contains the claim in its value key, value := record.Key, string(record.Value) - subjectSession, ok := bcl.NewSuSe(key) - if !ok { - logger.Warn().Msgf("invalid logout record key: %s", key) + subjectSession, err := bcl.NewSuSe(key) + if err != nil { + // never leak any key-related information + logger.Warn().Err(err).Msgf("invalid logout record key: %s", "XXX") continue } - err := s.publishBackchannelLogoutEvent(r.Context(), subjectSession.Session, value) - if err != nil { + if err := s.publishBackchannelLogoutEvent(r.Context(), subjectSession.Session, value); err != nil { s.Logger.Warn().Err(err).Msg("could not publish backchannel logout event") } @@ -109,7 +112,7 @@ func (s *StaticRouteHandler) backchannelLogout(w http.ResponseWriter, r *http.Re // we can ignore errors when deleting the lookup record err = s.UserInfoCache.Delete(key) if err != nil { - logger.Debug().Err(err).Msg("Failed to cleanup sessionid lookup entry") + logger.Debug().Err(err).Msg("Failed to cleanup sessionId lookup entry") } } diff --git a/services/proxy/pkg/staticroutes/internal/backchannellogout/backchannellogout.go b/services/proxy/pkg/staticroutes/internal/backchannellogout/backchannellogout.go index 92f9ab0d59..306febd0ca 100644 --- a/services/proxy/pkg/staticroutes/internal/backchannellogout/backchannellogout.go +++ b/services/proxy/pkg/staticroutes/internal/backchannellogout/backchannellogout.go @@ -19,8 +19,11 @@ type SuSe struct { Session string } +// ErrInvalidSessionOrSubject is returned when the provided key does not match the expected key format +var ErrInvalidSessionOrSubject = errors.New("invalid session or subject") + // NewSuSe parses the subject and session id from the given key and returns a SuSe struct -func NewSuSe(key string) (SuSe, bool) { +func NewSuSe(key string) (SuSe, error) { var subject, session string switch keys := strings.Split(strings.Join(strings.Fields(key), ""), "."); { case len(keys) == 2 && keys[0] == "" && keys[1] != "": @@ -33,10 +36,10 @@ func NewSuSe(key string) (SuSe, bool) { case len(keys) == 1 && keys[0] != "": session = keys[0] default: - return SuSe{}, false + return SuSe{}, ErrInvalidSessionOrSubject } - return SuSe{Session: session, Subject: subject}, true + return SuSe{Session: session, Subject: subject}, nil } // LogoutMode defines the mode of backchannel logout, either by session or by subject @@ -104,9 +107,10 @@ func GetLogoutRecords(suse SuSe, mode LogoutMode, store microstore.Store) ([]*mi // double-check if the found records match the requested subject and or session id as well, // to prevent false positives. for _, record := range records { - recordSuSe, ok := NewSuSe(record.Key) - if !ok { - return nil, microstore.ErrNotFound + recordSuSe, err := NewSuSe(record.Key) + if err != nil { + // never leak any key-related information + return nil, fmt.Errorf("%w %w: failed to parse logout record key: %s", err, ErrSuspiciousCacheResult, "XXX") } switch { diff --git a/services/proxy/pkg/staticroutes/internal/backchannellogout/backchannellogout_test.go b/services/proxy/pkg/staticroutes/internal/backchannellogout/backchannellogout_test.go index 9250a4885b..238323c728 100644 --- a/services/proxy/pkg/staticroutes/internal/backchannellogout/backchannellogout_test.go +++ b/services/proxy/pkg/staticroutes/internal/backchannellogout/backchannellogout_test.go @@ -5,84 +5,80 @@ import ( "strings" "testing" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "go-micro.dev/v4/store" + + "github.com/opencloud-eu/opencloud/services/proxy/pkg/staticroutes/internal/backchannellogout/mocks" ) func TestNewSuSe(t *testing.T) { tests := []struct { name string key string - wantsuSe SuSe - wantOk bool + wantSuSe SuSe + wantErr error }{ { - name: ".session", + name: "key variation: '.session'", key: ".session", - wantsuSe: SuSe{Session: "session", Subject: ""}, - wantOk: true, + wantSuSe: SuSe{Session: "session", Subject: ""}, }, { - name: ".session", + name: "key variation: '.session'", key: ".session", - wantsuSe: SuSe{Session: "session", Subject: ""}, - wantOk: true, + wantSuSe: SuSe{Session: "session", Subject: ""}, }, { - name: "session", + name: "key variation: 'session'", key: "session", - wantsuSe: SuSe{Session: "session", Subject: ""}, - wantOk: true, + wantSuSe: SuSe{Session: "session", Subject: ""}, }, { - name: "subject.", + name: "key variation: 'subject.'", key: "subject.", - wantsuSe: SuSe{Session: "", Subject: "subject"}, - wantOk: true, + wantSuSe: SuSe{Session: "", Subject: "subject"}, }, { - name: "subject.session", + name: "key variation: 'subject.session'", key: "subject.session", - wantsuSe: SuSe{Session: "session", Subject: "subject"}, - wantOk: true, + wantSuSe: SuSe{Session: "session", Subject: "subject"}, }, { - name: "dot", + name: "key variation: 'dot'", key: ".", - wantsuSe: SuSe{Session: "", Subject: ""}, - wantOk: false, + wantSuSe: SuSe{Session: "", Subject: ""}, + wantErr: ErrInvalidSessionOrSubject, }, { - name: "empty", + name: "key variation: 'empty'", key: "", - wantsuSe: SuSe{Session: "", Subject: ""}, - wantOk: false, + wantSuSe: SuSe{Session: "", Subject: ""}, + wantErr: ErrInvalidSessionOrSubject, }, { - name: "whitespace . whitespace", + name: "key variation: 'whitespace . whitespace'", key: " . ", - wantsuSe: SuSe{Session: "", Subject: ""}, - wantOk: false, + wantSuSe: SuSe{Session: "", Subject: ""}, + wantErr: ErrInvalidSessionOrSubject, }, { - name: "whitespace subject whitespace . whitespace", + name: "key variation: 'whitespace subject whitespace . whitespace'", key: " subject . ", - wantsuSe: SuSe{Session: "", Subject: "subject"}, - wantOk: true, + wantSuSe: SuSe{Session: "", Subject: "subject"}, }, { - name: "whitespace . whitespace session whitespace", + name: "key variation: 'whitespace . whitespace session whitespace'", key: " . session ", - wantsuSe: SuSe{Session: "session", Subject: ""}, - wantOk: true, + wantSuSe: SuSe{Session: "session", Subject: ""}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { suSe, ok := NewSuSe(tt.key) - require.Equal(t, tt.wantOk, ok) - require.Equal(t, tt.wantsuSe, suSe) + require.ErrorIs(t, tt.wantErr, ok) + require.Equal(t, tt.wantSuSe, suSe) }) } } @@ -94,22 +90,22 @@ func TestGetLogoutMode(t *testing.T) { want LogoutMode }{ { - name: ".session", + name: "key variation: '.session'", suSe: SuSe{Session: "session", Subject: ""}, want: LogoutModeSession, }, { - name: "subject.session", + name: "key variation: 'subject.session'", suSe: SuSe{Session: "session", Subject: "subject"}, want: LogoutModeSession, }, { - name: "subject.", + name: "key variation: 'subject.'", suSe: SuSe{Session: "", Subject: "subject"}, want: LogoutModeSubject, }, { - name: "", + name: "key variation: 'empty'", suSe: SuSe{Session: "", Subject: ""}, want: LogoutModeUnknown, }, @@ -130,8 +126,8 @@ func TestGetLogoutRecords(t *testing.T) { recordClaimB := &store.Record{Key: "claim-b", Value: []byte("claim-b-data")} recordClaimC := &store.Record{Key: "claim-c", Value: []byte("claim-c-data")} recordClaimD := &store.Record{Key: "claim-d", Value: []byte("claim-d-data")} - recordSessionA := &store.Record{Key: "session-a", Value: []byte(recordClaimA.Key)} - recordSessionB := &store.Record{Key: "session-b", Value: []byte(recordClaimB.Key)} + recordSessionA := &store.Record{Key: ".session-a", Value: []byte(recordClaimA.Key)} + recordSessionB := &store.Record{Key: ".session-b", Value: []byte(recordClaimB.Key)} recordSubjectASessionC := &store.Record{Key: "subject-a.session-c", Value: []byte(recordSessionA.Key)} recordSubjectASessionD := &store.Record{Key: "subject-a.session-d", Value: []byte(recordSessionB.Key)} @@ -152,46 +148,148 @@ func TestGetLogoutRecords(t *testing.T) { name string suSe SuSe mode LogoutMode - store store.Store + store func(t *testing.T) store.Store wantRecords []*store.Record - wantError error + wantErrs []error }{ { - name: "session-c", - suSe: SuSe{Session: "session-c"}, - mode: LogoutModeSession, - store: sessionStore, + name: "fails if mode is unknown", + suSe: SuSe{Session: "session-a"}, + mode: LogoutModeUnknown, + store: func(t *testing.T) store.Store { + return sessionStore + }, + wantRecords: []*store.Record{}, + wantErrs: []error{ErrSuspiciousCacheResult}, + }, + { + name: "fails if mode is any random int", + suSe: SuSe{Session: "session-a"}, + mode: 999, + store: func(t *testing.T) store.Store { + return sessionStore + }, + wantRecords: []*store.Record{}, + wantErrs: []error{ErrSuspiciousCacheResult}}, + { + name: "fails if multiple session records are found", + suSe: SuSe{Session: "session-a"}, + mode: LogoutModeSession, + store: func(t *testing.T) store.Store { + s := mocks.NewStore(t) + s.EXPECT().Read(mock.Anything, mock.Anything).Return([]*store.Record{ + recordSessionA, + recordSessionB, + }, nil) + return s + }, + wantRecords: []*store.Record{}, + wantErrs: []error{ErrSuspiciousCacheResult}}, + { + name: "fails if the record key is not ok", + suSe: SuSe{Session: "session-a"}, + mode: LogoutModeSession, + store: func(t *testing.T) store.Store { + s := mocks.NewStore(t) + s.EXPECT().Read(mock.Anything, mock.Anything).Return([]*store.Record{ + &store.Record{Key: "invalid.record.key"}, + }, nil) + return s + }, + wantRecords: []*store.Record{}, + wantErrs: []error{ErrInvalidSessionOrSubject, ErrSuspiciousCacheResult}, + }, + { + name: "fails if the session does not match the retrieved record", + suSe: SuSe{Session: "session-a"}, + mode: LogoutModeSession, + store: func(t *testing.T) store.Store { + s := mocks.NewStore(t) + s.EXPECT().Read(mock.Anything, mock.Anything).Return([]*store.Record{ + recordSessionB, + }, nil) + return s + }, + wantRecords: []*store.Record{}, + wantErrs: []error{ErrSuspiciousCacheResult}}, + { + name: "fails if the subject does not match the retrieved record", + suSe: SuSe{Subject: "subject-a"}, + mode: LogoutModeSubject, + store: func(t *testing.T) store.Store { + s := mocks.NewStore(t) + s.EXPECT().Read(mock.Anything, mock.Anything).Return([]*store.Record{ + recordSessionB, + }, nil) + return s + }, + wantRecords: []*store.Record{}, + wantErrs: []error{ErrSuspiciousCacheResult}}, + // key variation tests + { + name: "key variation: 'session-a'", + suSe: SuSe{Session: "session-a"}, + mode: LogoutModeSession, + store: func(*testing.T) store.Store { + return sessionStore + }, + wantRecords: []*store.Record{recordSessionA}, + }, + { + name: "key variation: 'session-b'", + suSe: SuSe{Session: "session-b"}, + mode: LogoutModeSession, + store: func(*testing.T) store.Store { + return sessionStore + }, + wantRecords: []*store.Record{recordSessionB}, + }, + { + name: "key variation: 'session-c'", + suSe: SuSe{Session: "session-c"}, + mode: LogoutModeSession, + store: func(*testing.T) store.Store { + return sessionStore + }, wantRecords: []*store.Record{recordSubjectASessionC}, }, { - name: "ession-c", - suSe: SuSe{Session: "ession-c"}, - mode: LogoutModeSession, - store: sessionStore, - wantError: store.ErrNotFound, + name: "key variation: 'ession-c'", + suSe: SuSe{Session: "ession-c"}, + mode: LogoutModeSession, + store: func(*testing.T) store.Store { + return sessionStore + }, wantRecords: []*store.Record{}, + wantErrs: []error{store.ErrNotFound}, }, { - name: "subject-a", - suSe: SuSe{Subject: "subject-a"}, - mode: LogoutModeSubject, - store: sessionStore, + name: "key variation: 'subject-a'", + suSe: SuSe{Subject: "subject-a"}, + mode: LogoutModeSubject, + store: func(*testing.T) store.Store { + return sessionStore + }, wantRecords: []*store.Record{recordSubjectASessionC, recordSubjectASessionD}, }, { - name: "subject-", - suSe: SuSe{Subject: "subject-"}, - mode: LogoutModeSubject, - store: sessionStore, - wantError: store.ErrNotFound, + name: "key variation: 'subject-'", + suSe: SuSe{Subject: "subject-"}, + mode: LogoutModeSubject, + store: func(*testing.T) store.Store { + return sessionStore + }, wantRecords: []*store.Record{}, + wantErrs: []error{store.ErrNotFound}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - records, err := GetLogoutRecords(tt.suSe, tt.mode, tt.store) - require.ErrorIs(t, err, tt.wantError) + records, err := GetLogoutRecords(tt.suSe, tt.mode, tt.store(t)) + for _, wantErr := range tt.wantErrs { + require.ErrorIs(t, err, wantErr) + } require.Len(t, records, len(tt.wantRecords)) sortRecords := func(r []*store.Record) []*store.Record { diff --git a/services/proxy/pkg/staticroutes/internal/backchannellogout/mocks/store.go b/services/proxy/pkg/staticroutes/internal/backchannellogout/mocks/store.go new file mode 100644 index 0000000000..359ea9cc2b --- /dev/null +++ b/services/proxy/pkg/staticroutes/internal/backchannellogout/mocks/store.go @@ -0,0 +1,509 @@ +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + "go-micro.dev/v4/store" +) + +// NewStore creates a new instance of Store. 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 NewStore(t interface { + mock.TestingT + Cleanup(func()) +}) *Store { + mock := &Store{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// Store is an autogenerated mock type for the Store type +type Store struct { + mock.Mock +} + +type Store_Expecter struct { + mock *mock.Mock +} + +func (_m *Store) EXPECT() *Store_Expecter { + return &Store_Expecter{mock: &_m.Mock} +} + +// Close provides a mock function for the type Store +func (_mock *Store) Close() error { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for Close") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func() error); ok { + r0 = returnFunc() + } else { + r0 = ret.Error(0) + } + return r0 +} + +// Store_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' +type Store_Close_Call struct { + *mock.Call +} + +// Close is a helper method to define mock.On call +func (_e *Store_Expecter) Close() *Store_Close_Call { + return &Store_Close_Call{Call: _e.mock.On("Close")} +} + +func (_c *Store_Close_Call) Run(run func()) *Store_Close_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Store_Close_Call) Return(err error) *Store_Close_Call { + _c.Call.Return(err) + return _c +} + +func (_c *Store_Close_Call) RunAndReturn(run func() error) *Store_Close_Call { + _c.Call.Return(run) + return _c +} + +// Delete provides a mock function for the type Store +func (_mock *Store) Delete(key string, opts ...store.DeleteOption) error { + var tmpRet mock.Arguments + if len(opts) > 0 { + tmpRet = _mock.Called(key, opts) + } else { + tmpRet = _mock.Called(key) + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for Delete") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(string, ...store.DeleteOption) error); ok { + r0 = returnFunc(key, opts...) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// Store_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete' +type Store_Delete_Call struct { + *mock.Call +} + +// Delete is a helper method to define mock.On call +// - key string +// - opts ...store.DeleteOption +func (_e *Store_Expecter) Delete(key interface{}, opts ...interface{}) *Store_Delete_Call { + return &Store_Delete_Call{Call: _e.mock.On("Delete", + append([]interface{}{key}, opts...)...)} +} + +func (_c *Store_Delete_Call) Run(run func(key string, opts ...store.DeleteOption)) *Store_Delete_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 []store.DeleteOption + var variadicArgs []store.DeleteOption + if len(args) > 1 { + variadicArgs = args[1].([]store.DeleteOption) + } + arg1 = variadicArgs + run( + arg0, + arg1..., + ) + }) + return _c +} + +func (_c *Store_Delete_Call) Return(err error) *Store_Delete_Call { + _c.Call.Return(err) + return _c +} + +func (_c *Store_Delete_Call) RunAndReturn(run func(key string, opts ...store.DeleteOption) error) *Store_Delete_Call { + _c.Call.Return(run) + return _c +} + +// Init provides a mock function for the type Store +func (_mock *Store) Init(options ...store.Option) error { + var tmpRet mock.Arguments + if len(options) > 0 { + tmpRet = _mock.Called(options) + } else { + tmpRet = _mock.Called() + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for Init") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(...store.Option) error); ok { + r0 = returnFunc(options...) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// Store_Init_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Init' +type Store_Init_Call struct { + *mock.Call +} + +// Init is a helper method to define mock.On call +// - options ...store.Option +func (_e *Store_Expecter) Init(options ...interface{}) *Store_Init_Call { + return &Store_Init_Call{Call: _e.mock.On("Init", + append([]interface{}{}, options...)...)} +} + +func (_c *Store_Init_Call) Run(run func(options ...store.Option)) *Store_Init_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 []store.Option + var variadicArgs []store.Option + if len(args) > 0 { + variadicArgs = args[0].([]store.Option) + } + arg0 = variadicArgs + run( + arg0..., + ) + }) + return _c +} + +func (_c *Store_Init_Call) Return(err error) *Store_Init_Call { + _c.Call.Return(err) + return _c +} + +func (_c *Store_Init_Call) RunAndReturn(run func(options ...store.Option) error) *Store_Init_Call { + _c.Call.Return(run) + return _c +} + +// List provides a mock function for the type Store +func (_mock *Store) List(opts ...store.ListOption) ([]string, error) { + var tmpRet mock.Arguments + if len(opts) > 0 { + tmpRet = _mock.Called(opts) + } else { + tmpRet = _mock.Called() + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for List") + } + + var r0 []string + var r1 error + if returnFunc, ok := ret.Get(0).(func(...store.ListOption) ([]string, error)); ok { + return returnFunc(opts...) + } + if returnFunc, ok := ret.Get(0).(func(...store.ListOption) []string); ok { + r0 = returnFunc(opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + if returnFunc, ok := ret.Get(1).(func(...store.ListOption) error); ok { + r1 = returnFunc(opts...) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// Store_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List' +type Store_List_Call struct { + *mock.Call +} + +// List is a helper method to define mock.On call +// - opts ...store.ListOption +func (_e *Store_Expecter) List(opts ...interface{}) *Store_List_Call { + return &Store_List_Call{Call: _e.mock.On("List", + append([]interface{}{}, opts...)...)} +} + +func (_c *Store_List_Call) Run(run func(opts ...store.ListOption)) *Store_List_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 []store.ListOption + var variadicArgs []store.ListOption + if len(args) > 0 { + variadicArgs = args[0].([]store.ListOption) + } + arg0 = variadicArgs + run( + arg0..., + ) + }) + return _c +} + +func (_c *Store_List_Call) Return(strings []string, err error) *Store_List_Call { + _c.Call.Return(strings, err) + return _c +} + +func (_c *Store_List_Call) RunAndReturn(run func(opts ...store.ListOption) ([]string, error)) *Store_List_Call { + _c.Call.Return(run) + return _c +} + +// Options provides a mock function for the type Store +func (_mock *Store) Options() store.Options { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for Options") + } + + var r0 store.Options + if returnFunc, ok := ret.Get(0).(func() store.Options); ok { + r0 = returnFunc() + } else { + r0 = ret.Get(0).(store.Options) + } + return r0 +} + +// Store_Options_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Options' +type Store_Options_Call struct { + *mock.Call +} + +// Options is a helper method to define mock.On call +func (_e *Store_Expecter) Options() *Store_Options_Call { + return &Store_Options_Call{Call: _e.mock.On("Options")} +} + +func (_c *Store_Options_Call) Run(run func()) *Store_Options_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Store_Options_Call) Return(options store.Options) *Store_Options_Call { + _c.Call.Return(options) + return _c +} + +func (_c *Store_Options_Call) RunAndReturn(run func() store.Options) *Store_Options_Call { + _c.Call.Return(run) + return _c +} + +// Read provides a mock function for the type Store +func (_mock *Store) Read(key string, opts ...store.ReadOption) ([]*store.Record, error) { + var tmpRet mock.Arguments + if len(opts) > 0 { + tmpRet = _mock.Called(key, opts) + } else { + tmpRet = _mock.Called(key) + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for Read") + } + + var r0 []*store.Record + var r1 error + if returnFunc, ok := ret.Get(0).(func(string, ...store.ReadOption) ([]*store.Record, error)); ok { + return returnFunc(key, opts...) + } + if returnFunc, ok := ret.Get(0).(func(string, ...store.ReadOption) []*store.Record); ok { + r0 = returnFunc(key, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*store.Record) + } + } + if returnFunc, ok := ret.Get(1).(func(string, ...store.ReadOption) error); ok { + r1 = returnFunc(key, opts...) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// Store_Read_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Read' +type Store_Read_Call struct { + *mock.Call +} + +// Read is a helper method to define mock.On call +// - key string +// - opts ...store.ReadOption +func (_e *Store_Expecter) Read(key interface{}, opts ...interface{}) *Store_Read_Call { + return &Store_Read_Call{Call: _e.mock.On("Read", + append([]interface{}{key}, opts...)...)} +} + +func (_c *Store_Read_Call) Run(run func(key string, opts ...store.ReadOption)) *Store_Read_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 []store.ReadOption + var variadicArgs []store.ReadOption + if len(args) > 1 { + variadicArgs = args[1].([]store.ReadOption) + } + arg1 = variadicArgs + run( + arg0, + arg1..., + ) + }) + return _c +} + +func (_c *Store_Read_Call) Return(records []*store.Record, err error) *Store_Read_Call { + _c.Call.Return(records, err) + return _c +} + +func (_c *Store_Read_Call) RunAndReturn(run func(key string, opts ...store.ReadOption) ([]*store.Record, error)) *Store_Read_Call { + _c.Call.Return(run) + return _c +} + +// String provides a mock function for the type Store +func (_mock *Store) String() string { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for String") + } + + var r0 string + if returnFunc, ok := ret.Get(0).(func() string); ok { + r0 = returnFunc() + } else { + r0 = ret.Get(0).(string) + } + return r0 +} + +// Store_String_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'String' +type Store_String_Call struct { + *mock.Call +} + +// String is a helper method to define mock.On call +func (_e *Store_Expecter) String() *Store_String_Call { + return &Store_String_Call{Call: _e.mock.On("String")} +} + +func (_c *Store_String_Call) Run(run func()) *Store_String_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Store_String_Call) Return(s string) *Store_String_Call { + _c.Call.Return(s) + return _c +} + +func (_c *Store_String_Call) RunAndReturn(run func() string) *Store_String_Call { + _c.Call.Return(run) + return _c +} + +// Write provides a mock function for the type Store +func (_mock *Store) Write(r *store.Record, opts ...store.WriteOption) error { + var tmpRet mock.Arguments + if len(opts) > 0 { + tmpRet = _mock.Called(r, opts) + } else { + tmpRet = _mock.Called(r) + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for Write") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(*store.Record, ...store.WriteOption) error); ok { + r0 = returnFunc(r, opts...) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// Store_Write_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Write' +type Store_Write_Call struct { + *mock.Call +} + +// Write is a helper method to define mock.On call +// - r *store.Record +// - opts ...store.WriteOption +func (_e *Store_Expecter) Write(r interface{}, opts ...interface{}) *Store_Write_Call { + return &Store_Write_Call{Call: _e.mock.On("Write", + append([]interface{}{r}, opts...)...)} +} + +func (_c *Store_Write_Call) Run(run func(r *store.Record, opts ...store.WriteOption)) *Store_Write_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 *store.Record + if args[0] != nil { + arg0 = args[0].(*store.Record) + } + var arg1 []store.WriteOption + var variadicArgs []store.WriteOption + if len(args) > 1 { + variadicArgs = args[1].([]store.WriteOption) + } + arg1 = variadicArgs + run( + arg0, + arg1..., + ) + }) + return _c +} + +func (_c *Store_Write_Call) Return(err error) *Store_Write_Call { + _c.Call.Return(err) + return _c +} + +func (_c *Store_Write_Call) RunAndReturn(run func(r *store.Record, opts ...store.WriteOption) error) *Store_Write_Call { + _c.Call.Return(run) + return _c +}