From 3fcdd77c22489ef8855ce5fb91c5a77b2bb10f56 Mon Sep 17 00:00:00 2001 From: Rustam Gilyazov <16064414+rusq@users.noreply.github.com> Date: Sat, 13 Jul 2024 21:35:38 +1000 Subject: [PATCH] add tests --- cmd/slackdump/internal/list/list.go | 53 ++--------- cmd/slackdump/internal/list/mocks_test.go | 109 ++++++++++++++++++++++ cmd/slackdump/internal/list/users.go | 50 ++++++++++ cmd/slackdump/internal/list/users_test.go | 104 +++++++++++++++++++++ 4 files changed, 269 insertions(+), 47 deletions(-) create mode 100644 cmd/slackdump/internal/list/mocks_test.go create mode 100644 cmd/slackdump/internal/list/users_test.go diff --git a/cmd/slackdump/internal/list/list.go b/cmd/slackdump/internal/list/list.go index 172aa25c..0a7b1097 100644 --- a/cmd/slackdump/internal/list/list.go +++ b/cmd/slackdump/internal/list/list.go @@ -8,8 +8,6 @@ import ( "io" "os" "path/filepath" - "runtime/trace" - "time" "github.com/charmbracelet/huh" "github.com/rusq/fsadapter" @@ -19,7 +17,6 @@ import ( "github.com/rusq/slackdump/v3/cmd/slackdump/internal/golang/base" "github.com/rusq/slackdump/v3/internal/cache" "github.com/rusq/slackdump/v3/internal/format" - "github.com/rusq/slackdump/v3/internal/osext" "github.com/rusq/slackdump/v3/logger" "github.com/rusq/slackdump/v3/types" ) @@ -110,7 +107,11 @@ func list(ctx context.Context, listFn listFunc) error { teamID := sess.Info().TeamID users, ok := data.(types.Users) // Hax if !ok && !noresolve { - users, err = getCachedUsers(ctx, sess, m, teamID, cfg.NoUserCache) + if cfg.NoUserCache { + users, err = sess.GetUsers(ctx) + } else { + users, err = getCachedUsers(ctx, sess, m, teamID) + } if err != nil { return err } @@ -150,48 +151,6 @@ func saveData(ctx context.Context, fs fsadapter.FS, data any, filename string, t return nil } -type userGetter interface { - GetUsers(ctx context.Context) (types.Users, error) -} - -type userCacher interface { - LoadUsers(teamID string, retention time.Duration) ([]slack.User, error) - CacheUsers(teamID string, users []slack.User) error -} - -func getCachedUsers(ctx context.Context, ug userGetter, m userCacher, teamID string, forceAPI bool) ([]slack.User, error) { - lg := logger.FromContext(ctx) - - var users []slack.User - if !forceAPI { - users, err := m.LoadUsers(teamID, cfg.UserCacheRetention) - if err == nil { - return users, nil - } - - // failed to load from cache - if !errors.Is(err, cache.ErrExpired) && !errors.Is(err, cache.ErrEmpty) && !os.IsNotExist(err) && !osext.IsPathError(err) { - // some funky error - return nil, err - } - lg.Println("user cache expired or empty, caching users") - } - - // getting users from API - users, err := ug.GetUsers(ctx) - if err != nil { - return nil, err - } - - // saving users to cache, will ignore any errors, but notify the user. - if err := m.CacheUsers(teamID, users); err != nil { - trace.Logf(ctx, "error", "saving user cache to %q, error: %s", userCacheBase, err) - lg.Printf("warning: failed saving user cache to %q: %s, but nevermind, let's continue", userCacheBase, err) - } - - return users, nil -} - // fmtPrint prints the given data to the given writer, using the given format. // It should be supplied with prepopulated users, as it may need to look up // users by ID. @@ -225,7 +184,7 @@ func makeFilename(prefix string, teamID string, ext string) string { return fmt.Sprintf("%s-%s%s", prefix, teamID, ext) } -func wizard(ctx context.Context, listFn listFunc) error { +func wizard(context.Context, listFunc) error { // pick format var types []string for _, t := range format.All() { diff --git a/cmd/slackdump/internal/list/mocks_test.go b/cmd/slackdump/internal/list/mocks_test.go new file mode 100644 index 00000000..700d3523 --- /dev/null +++ b/cmd/slackdump/internal/list/mocks_test.go @@ -0,0 +1,109 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: users.go +// +// Generated by this command: +// +// mockgen -source=users.go -destination=mocks_test.go -package=list userGetter,userCacher +// +// Package list is a generated GoMock package. +package list + +import ( + context "context" + reflect "reflect" + time "time" + + slack "github.com/rusq/slack" + types "github.com/rusq/slackdump/v3/types" + gomock "go.uber.org/mock/gomock" +) + +// MockuserGetter is a mock of userGetter interface. +type MockuserGetter struct { + ctrl *gomock.Controller + recorder *MockuserGetterMockRecorder +} + +// MockuserGetterMockRecorder is the mock recorder for MockuserGetter. +type MockuserGetterMockRecorder struct { + mock *MockuserGetter +} + +// NewMockuserGetter creates a new mock instance. +func NewMockuserGetter(ctrl *gomock.Controller) *MockuserGetter { + mock := &MockuserGetter{ctrl: ctrl} + mock.recorder = &MockuserGetterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockuserGetter) EXPECT() *MockuserGetterMockRecorder { + return m.recorder +} + +// GetUsers mocks base method. +func (m *MockuserGetter) GetUsers(ctx context.Context) (types.Users, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUsers", ctx) + ret0, _ := ret[0].(types.Users) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUsers indicates an expected call of GetUsers. +func (mr *MockuserGetterMockRecorder) GetUsers(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUsers", reflect.TypeOf((*MockuserGetter)(nil).GetUsers), ctx) +} + +// MockuserCacher is a mock of userCacher interface. +type MockuserCacher struct { + ctrl *gomock.Controller + recorder *MockuserCacherMockRecorder +} + +// MockuserCacherMockRecorder is the mock recorder for MockuserCacher. +type MockuserCacherMockRecorder struct { + mock *MockuserCacher +} + +// NewMockuserCacher creates a new mock instance. +func NewMockuserCacher(ctrl *gomock.Controller) *MockuserCacher { + mock := &MockuserCacher{ctrl: ctrl} + mock.recorder = &MockuserCacherMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockuserCacher) EXPECT() *MockuserCacherMockRecorder { + return m.recorder +} + +// CacheUsers mocks base method. +func (m *MockuserCacher) CacheUsers(teamID string, users []slack.User) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CacheUsers", teamID, users) + ret0, _ := ret[0].(error) + return ret0 +} + +// CacheUsers indicates an expected call of CacheUsers. +func (mr *MockuserCacherMockRecorder) CacheUsers(teamID, users any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CacheUsers", reflect.TypeOf((*MockuserCacher)(nil).CacheUsers), teamID, users) +} + +// LoadUsers mocks base method. +func (m *MockuserCacher) LoadUsers(teamID string, retention time.Duration) ([]slack.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LoadUsers", teamID, retention) + ret0, _ := ret[0].([]slack.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LoadUsers indicates an expected call of LoadUsers. +func (mr *MockuserCacherMockRecorder) LoadUsers(teamID, retention any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadUsers", reflect.TypeOf((*MockuserCacher)(nil).LoadUsers), teamID, retention) +} diff --git a/cmd/slackdump/internal/list/users.go b/cmd/slackdump/internal/list/users.go index 1d760280..c04308e8 100644 --- a/cmd/slackdump/internal/list/users.go +++ b/cmd/slackdump/internal/list/users.go @@ -2,11 +2,20 @@ package list import ( "context" + "errors" "fmt" + "os" + "runtime/trace" + "time" + "github.com/rusq/slack" "github.com/rusq/slackdump/v3" "github.com/rusq/slackdump/v3/cmd/slackdump/internal/cfg" "github.com/rusq/slackdump/v3/cmd/slackdump/internal/golang/base" + "github.com/rusq/slackdump/v3/internal/cache" + "github.com/rusq/slackdump/v3/internal/osext" + "github.com/rusq/slackdump/v3/logger" + "github.com/rusq/slackdump/v3/types" ) var CmdListUsers = &base.Command{ @@ -49,3 +58,44 @@ func wizUsers(ctx context.Context, cmd *base.Command, args []string) error { return users, filename, err }) } + +//go:generate mockgen -source=users.go -destination=mocks_test.go -package=list userGetter,userCacher + +type userGetter interface { + GetUsers(ctx context.Context) (types.Users, error) +} + +type userCacher interface { + LoadUsers(teamID string, retention time.Duration) ([]slack.User, error) + CacheUsers(teamID string, users []slack.User) error +} + +func getCachedUsers(ctx context.Context, ug userGetter, m userCacher, teamID string) ([]slack.User, error) { + lg := logger.FromContext(ctx) + + users, err := m.LoadUsers(teamID, cfg.UserCacheRetention) + if err == nil { + return users, nil + } + + // failed to load from cache + if !errors.Is(err, cache.ErrExpired) && !errors.Is(err, cache.ErrEmpty) && !os.IsNotExist(err) && !osext.IsPathError(err) { + // some funky error + return nil, err + } + lg.Println("user cache expired or empty, caching users") + + // getting users from API + users, err = ug.GetUsers(ctx) + if err != nil { + return nil, err + } + + // saving users to cache, will ignore any errors, but notify the user. + if err := m.CacheUsers(teamID, users); err != nil { + trace.Logf(ctx, "error", "saving user cache to %q, error: %s", userCacheBase, err) + lg.Printf("warning: failed saving user cache to %q: %s, but nevermind, let's continue", userCacheBase, err) + } + + return users, nil +} diff --git a/cmd/slackdump/internal/list/users_test.go b/cmd/slackdump/internal/list/users_test.go new file mode 100644 index 00000000..924276c8 --- /dev/null +++ b/cmd/slackdump/internal/list/users_test.go @@ -0,0 +1,104 @@ +package list + +import ( + "context" + "errors" + "io/fs" + "reflect" + "testing" + + "github.com/rusq/slack" + "go.uber.org/mock/gomock" +) + +func Test_getCachedUsers(t *testing.T) { + var ( + testUsers = []slack.User{ + {ID: "U1"}, + {ID: "U2"}, + {ID: "U3"}, + } + ) + type args struct { + ctx context.Context + teamID string + } + tests := []struct { + name string + args args + expect func(c *MockuserCacher, g *MockuserGetter) + want []slack.User + wantErr bool + }{ + /* oh happy days */ + { + "users loaded from cache", + args{context.Background(), "TEAM1"}, + func(c *MockuserCacher, g *MockuserGetter) { + c.EXPECT().LoadUsers("TEAM1", gomock.Any()).Return(testUsers, nil) + }, + testUsers, + false, + }, + { + "getting users from API ok (recoverable cache error)", + args{context.Background(), "TEAM1"}, + func(c *MockuserCacher, g *MockuserGetter) { + c.EXPECT().LoadUsers("TEAM1", gomock.Any()).Return(nil, &fs.PathError{}) + g.EXPECT().GetUsers(gomock.Any()).Return(testUsers, nil) + c.EXPECT().CacheUsers("TEAM1", testUsers).Return(nil) + }, + testUsers, + false, + }, + { + "saving cache fails, but we continue", + args{context.Background(), "TEAM1"}, + func(c *MockuserCacher, g *MockuserGetter) { + c.EXPECT().LoadUsers("TEAM1", gomock.Any()).Return(nil, &fs.PathError{}) + g.EXPECT().GetUsers(gomock.Any()).Return(testUsers, nil) + c.EXPECT().CacheUsers("TEAM1", testUsers).Return(errors.New("disk mulching detected")) + }, + testUsers, + false, + }, + /* unhappy days */ + { + "unrecoverable error", + args{context.Background(), "TEAM1"}, + func(c *MockuserCacher, g *MockuserGetter) { + c.EXPECT().LoadUsers("TEAM1", gomock.Any()).Return(nil, errors.New("frobnication error")) + }, + nil, + true, + }, + { + "getting users from API fails", + args{context.Background(), "TEAM1"}, + func(c *MockuserCacher, g *MockuserGetter) { + c.EXPECT().LoadUsers("TEAM1", gomock.Any()).Return(nil, &fs.PathError{}) + g.EXPECT().GetUsers(gomock.Any()).Return(nil, errors.New("blip")) + }, + nil, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + muc := NewMockuserCacher(ctrl) + mug := NewMockuserGetter(ctrl) + + tt.expect(muc, mug) + + got, err := getCachedUsers(tt.args.ctx, mug, muc, tt.args.teamID) + if (err != nil) != tt.wantErr { + t.Errorf("getCachedUsers() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("getCachedUsers() = %v, want %v", got, tt.want) + } + }) + } +}