diff --git a/pkg/api/resolvers.go b/pkg/api/resolvers.go index 27c88ce..fce3670 100644 --- a/pkg/api/resolvers.go +++ b/pkg/api/resolvers.go @@ -20,7 +20,7 @@ import ( type Resolver struct { RequestLogService *reqlog.Service - ProjectService *proj.Service + ProjectService proj.Service ScopeService *scope.Scope } diff --git a/pkg/proj/proj.go b/pkg/proj/proj.go index e26b666..f17812b 100644 --- a/pkg/proj/proj.go +++ b/pkg/proj/proj.go @@ -15,7 +15,17 @@ type ( ) // Service is used for managing projects. -type Service struct { +type Service interface { + Open(ctx context.Context, name string) (Project, error) + Close() error + Delete(name string) error + ActiveProject() (Project, error) + Projects() ([]Project, error) + OnProjectOpen(fn OnProjectOpenFn) + OnProjectClose(fn OnProjectCloseFn) +} + +type service struct { repo Repository activeProject string onProjectOpenFns []OnProjectOpenFn @@ -37,14 +47,14 @@ var ( var nameRegexp = regexp.MustCompile(`^[\w\d\s]+$`) // NewService returns a new Service. -func NewService(repo Repository) (*Service, error) { - return &Service{ +func NewService(repo Repository) (Service, error) { + return &service{ repo: repo, }, nil } // Close closes the currently open project database (if there is one). -func (svc *Service) Close() error { +func (svc *service) Close() error { svc.mu.Lock() defer svc.mu.Unlock() @@ -62,7 +72,7 @@ func (svc *Service) Close() error { } // Delete removes a project database file from disk (if there is one). -func (svc *Service) Delete(name string) error { +func (svc *service) Delete(name string) error { if name == "" { return errors.New("proj: name cannot be empty") } @@ -80,7 +90,7 @@ func (svc *Service) Delete(name string) error { // Open opens a database identified with `name`. If a database with this // identifier doesn't exist yet, it will be automatically created. -func (svc *Service) Open(ctx context.Context, name string) (Project, error) { +func (svc *service) Open(ctx context.Context, name string) (Project, error) { if !nameRegexp.MatchString(name) { return Project{}, ErrInvalidName } @@ -105,7 +115,7 @@ func (svc *Service) Open(ctx context.Context, name string) (Project, error) { }, nil } -func (svc *Service) ActiveProject() (Project, error) { +func (svc *service) ActiveProject() (Project, error) { activeProject := svc.activeProject if activeProject == "" { return Project{}, ErrNoProject @@ -116,7 +126,7 @@ func (svc *Service) ActiveProject() (Project, error) { }, nil } -func (svc *Service) Projects() ([]Project, error) { +func (svc *service) Projects() ([]Project, error) { projects, err := svc.repo.Projects() if err != nil { return nil, fmt.Errorf("proj: could not get projects: %w", err) @@ -125,21 +135,21 @@ func (svc *Service) Projects() ([]Project, error) { return projects, nil } -func (svc *Service) OnProjectOpen(fn OnProjectOpenFn) { +func (svc *service) OnProjectOpen(fn OnProjectOpenFn) { svc.mu.Lock() defer svc.mu.Unlock() svc.onProjectOpenFns = append(svc.onProjectOpenFns, fn) } -func (svc *Service) OnProjectClose(fn OnProjectCloseFn) { +func (svc *service) OnProjectClose(fn OnProjectCloseFn) { svc.mu.Lock() defer svc.mu.Unlock() svc.onProjectCloseFns = append(svc.onProjectCloseFns, fn) } -func (svc *Service) emitProjectOpened() { +func (svc *service) emitProjectOpened() { for _, fn := range svc.onProjectOpenFns { if err := fn(svc.activeProject); err != nil { log.Printf("[ERROR] Could not execute onProjectOpen function: %v", err) @@ -147,7 +157,7 @@ func (svc *Service) emitProjectOpened() { } } -func (svc *Service) emitProjectClosed(name string) { +func (svc *service) emitProjectClosed(name string) { for _, fn := range svc.onProjectCloseFns { if err := fn(name); err != nil { log.Printf("[ERROR] Could not execute onProjectClose function: %v", err) diff --git a/pkg/reqlog/proj_mock_test.go b/pkg/reqlog/proj_mock_test.go new file mode 100644 index 0000000..868162a --- /dev/null +++ b/pkg/reqlog/proj_mock_test.go @@ -0,0 +1,318 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package reqlog_test + +import ( + "context" + "github.com/dstotijn/hetty/pkg/proj" + "sync" +) + +// Ensure, that ProjServiceMock does implement proj.Service. +// If this is not the case, regenerate this file with moq. +var _ proj.Service = &ProjServiceMock{} + +// ProjServiceMock is a mock implementation of proj.Service. +// +// func TestSomethingThatUsesService(t *testing.T) { +// +// // make and configure a mocked proj.Service +// mockedService := &ProjServiceMock{ +// ActiveProjectFunc: func() (proj.Project, error) { +// panic("mock out the ActiveProject method") +// }, +// CloseFunc: func() error { +// panic("mock out the Close method") +// }, +// DeleteFunc: func(name string) error { +// panic("mock out the Delete method") +// }, +// OnProjectCloseFunc: func(fn proj.OnProjectCloseFn) { +// panic("mock out the OnProjectClose method") +// }, +// OnProjectOpenFunc: func(fn proj.OnProjectOpenFn) { +// panic("mock out the OnProjectOpen method") +// }, +// OpenFunc: func(ctx context.Context, name string) (proj.Project, error) { +// panic("mock out the Open method") +// }, +// ProjectsFunc: func() ([]proj.Project, error) { +// panic("mock out the Projects method") +// }, +// } +// +// // use mockedService in code that requires proj.Service +// // and then make assertions. +// +// } +type ProjServiceMock struct { + // ActiveProjectFunc mocks the ActiveProject method. + ActiveProjectFunc func() (proj.Project, error) + + // CloseFunc mocks the Close method. + CloseFunc func() error + + // DeleteFunc mocks the Delete method. + DeleteFunc func(name string) error + + // OnProjectCloseFunc mocks the OnProjectClose method. + OnProjectCloseFunc func(fn proj.OnProjectCloseFn) + + // OnProjectOpenFunc mocks the OnProjectOpen method. + OnProjectOpenFunc func(fn proj.OnProjectOpenFn) + + // OpenFunc mocks the Open method. + OpenFunc func(ctx context.Context, name string) (proj.Project, error) + + // ProjectsFunc mocks the Projects method. + ProjectsFunc func() ([]proj.Project, error) + + // calls tracks calls to the methods. + calls struct { + // ActiveProject holds details about calls to the ActiveProject method. + ActiveProject []struct { + } + // Close holds details about calls to the Close method. + Close []struct { + } + // Delete holds details about calls to the Delete method. + Delete []struct { + // Name is the name argument value. + Name string + } + // OnProjectClose holds details about calls to the OnProjectClose method. + OnProjectClose []struct { + // Fn is the fn argument value. + Fn proj.OnProjectCloseFn + } + // OnProjectOpen holds details about calls to the OnProjectOpen method. + OnProjectOpen []struct { + // Fn is the fn argument value. + Fn proj.OnProjectOpenFn + } + // Open holds details about calls to the Open method. + Open []struct { + // Ctx is the ctx argument value. + Ctx context.Context + // Name is the name argument value. + Name string + } + // Projects holds details about calls to the Projects method. + Projects []struct { + } + } + lockActiveProject sync.RWMutex + lockClose sync.RWMutex + lockDelete sync.RWMutex + lockOnProjectClose sync.RWMutex + lockOnProjectOpen sync.RWMutex + lockOpen sync.RWMutex + lockProjects sync.RWMutex +} + +// ActiveProject calls ActiveProjectFunc. +func (mock *ProjServiceMock) ActiveProject() (proj.Project, error) { + if mock.ActiveProjectFunc == nil { + panic("ProjServiceMock.ActiveProjectFunc: method is nil but Service.ActiveProject was just called") + } + callInfo := struct { + }{} + mock.lockActiveProject.Lock() + mock.calls.ActiveProject = append(mock.calls.ActiveProject, callInfo) + mock.lockActiveProject.Unlock() + return mock.ActiveProjectFunc() +} + +// ActiveProjectCalls gets all the calls that were made to ActiveProject. +// Check the length with: +// len(mockedService.ActiveProjectCalls()) +func (mock *ProjServiceMock) ActiveProjectCalls() []struct { +} { + var calls []struct { + } + mock.lockActiveProject.RLock() + calls = mock.calls.ActiveProject + mock.lockActiveProject.RUnlock() + return calls +} + +// Close calls CloseFunc. +func (mock *ProjServiceMock) Close() error { + if mock.CloseFunc == nil { + panic("ProjServiceMock.CloseFunc: method is nil but Service.Close was just called") + } + callInfo := struct { + }{} + mock.lockClose.Lock() + mock.calls.Close = append(mock.calls.Close, callInfo) + mock.lockClose.Unlock() + return mock.CloseFunc() +} + +// CloseCalls gets all the calls that were made to Close. +// Check the length with: +// len(mockedService.CloseCalls()) +func (mock *ProjServiceMock) CloseCalls() []struct { +} { + var calls []struct { + } + mock.lockClose.RLock() + calls = mock.calls.Close + mock.lockClose.RUnlock() + return calls +} + +// Delete calls DeleteFunc. +func (mock *ProjServiceMock) Delete(name string) error { + if mock.DeleteFunc == nil { + panic("ProjServiceMock.DeleteFunc: method is nil but Service.Delete was just called") + } + callInfo := struct { + Name string + }{ + Name: name, + } + mock.lockDelete.Lock() + mock.calls.Delete = append(mock.calls.Delete, callInfo) + mock.lockDelete.Unlock() + return mock.DeleteFunc(name) +} + +// DeleteCalls gets all the calls that were made to Delete. +// Check the length with: +// len(mockedService.DeleteCalls()) +func (mock *ProjServiceMock) DeleteCalls() []struct { + Name string +} { + var calls []struct { + Name string + } + mock.lockDelete.RLock() + calls = mock.calls.Delete + mock.lockDelete.RUnlock() + return calls +} + +// OnProjectClose calls OnProjectCloseFunc. +func (mock *ProjServiceMock) OnProjectClose(fn proj.OnProjectCloseFn) { + if mock.OnProjectCloseFunc == nil { + panic("ProjServiceMock.OnProjectCloseFunc: method is nil but Service.OnProjectClose was just called") + } + callInfo := struct { + Fn proj.OnProjectCloseFn + }{ + Fn: fn, + } + mock.lockOnProjectClose.Lock() + mock.calls.OnProjectClose = append(mock.calls.OnProjectClose, callInfo) + mock.lockOnProjectClose.Unlock() + mock.OnProjectCloseFunc(fn) +} + +// OnProjectCloseCalls gets all the calls that were made to OnProjectClose. +// Check the length with: +// len(mockedService.OnProjectCloseCalls()) +func (mock *ProjServiceMock) OnProjectCloseCalls() []struct { + Fn proj.OnProjectCloseFn +} { + var calls []struct { + Fn proj.OnProjectCloseFn + } + mock.lockOnProjectClose.RLock() + calls = mock.calls.OnProjectClose + mock.lockOnProjectClose.RUnlock() + return calls +} + +// OnProjectOpen calls OnProjectOpenFunc. +func (mock *ProjServiceMock) OnProjectOpen(fn proj.OnProjectOpenFn) { + if mock.OnProjectOpenFunc == nil { + panic("ProjServiceMock.OnProjectOpenFunc: method is nil but Service.OnProjectOpen was just called") + } + callInfo := struct { + Fn proj.OnProjectOpenFn + }{ + Fn: fn, + } + mock.lockOnProjectOpen.Lock() + mock.calls.OnProjectOpen = append(mock.calls.OnProjectOpen, callInfo) + mock.lockOnProjectOpen.Unlock() + mock.OnProjectOpenFunc(fn) +} + +// OnProjectOpenCalls gets all the calls that were made to OnProjectOpen. +// Check the length with: +// len(mockedService.OnProjectOpenCalls()) +func (mock *ProjServiceMock) OnProjectOpenCalls() []struct { + Fn proj.OnProjectOpenFn +} { + var calls []struct { + Fn proj.OnProjectOpenFn + } + mock.lockOnProjectOpen.RLock() + calls = mock.calls.OnProjectOpen + mock.lockOnProjectOpen.RUnlock() + return calls +} + +// Open calls OpenFunc. +func (mock *ProjServiceMock) Open(ctx context.Context, name string) (proj.Project, error) { + if mock.OpenFunc == nil { + panic("ProjServiceMock.OpenFunc: method is nil but Service.Open was just called") + } + callInfo := struct { + Ctx context.Context + Name string + }{ + Ctx: ctx, + Name: name, + } + mock.lockOpen.Lock() + mock.calls.Open = append(mock.calls.Open, callInfo) + mock.lockOpen.Unlock() + return mock.OpenFunc(ctx, name) +} + +// OpenCalls gets all the calls that were made to Open. +// Check the length with: +// len(mockedService.OpenCalls()) +func (mock *ProjServiceMock) OpenCalls() []struct { + Ctx context.Context + Name string +} { + var calls []struct { + Ctx context.Context + Name string + } + mock.lockOpen.RLock() + calls = mock.calls.Open + mock.lockOpen.RUnlock() + return calls +} + +// Projects calls ProjectsFunc. +func (mock *ProjServiceMock) Projects() ([]proj.Project, error) { + if mock.ProjectsFunc == nil { + panic("ProjServiceMock.ProjectsFunc: method is nil but Service.Projects was just called") + } + callInfo := struct { + }{} + mock.lockProjects.Lock() + mock.calls.Projects = append(mock.calls.Projects, callInfo) + mock.lockProjects.Unlock() + return mock.ProjectsFunc() +} + +// ProjectsCalls gets all the calls that were made to Projects. +// Check the length with: +// len(mockedService.ProjectsCalls()) +func (mock *ProjServiceMock) ProjectsCalls() []struct { +} { + var calls []struct { + } + mock.lockProjects.RLock() + calls = mock.calls.Projects + mock.lockProjects.RUnlock() + return calls +} diff --git a/pkg/reqlog/repo_mock_test.go b/pkg/reqlog/repo_mock_test.go new file mode 100644 index 0000000..6de6039 --- /dev/null +++ b/pkg/reqlog/repo_mock_test.go @@ -0,0 +1,420 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package reqlog_test + +import ( + "context" + "github.com/dstotijn/hetty/pkg/reqlog" + "github.com/dstotijn/hetty/pkg/scope" + "net/http" + "sync" + "time" +) + +// Ensure, that RepoMock does implement reqlog.Repository. +// If this is not the case, regenerate this file with moq. +var _ reqlog.Repository = &RepoMock{} + +// RepoMock is a mock implementation of reqlog.Repository. +// +// func TestSomethingThatUsesRepository(t *testing.T) { +// +// // make and configure a mocked reqlog.Repository +// mockedRepository := &RepoMock{ +// AddRequestLogFunc: func(ctx context.Context, req http.Request, body []byte, timestamp time.Time) (*reqlog.Request, error) { +// panic("mock out the AddRequestLog method") +// }, +// AddResponseLogFunc: func(ctx context.Context, reqID int64, res http.Response, body []byte, timestamp time.Time) (*reqlog.Response, error) { +// panic("mock out the AddResponseLog method") +// }, +// ClearRequestLogsFunc: func(ctx context.Context) error { +// panic("mock out the ClearRequestLogs method") +// }, +// FindRequestLogByIDFunc: func(ctx context.Context, id int64) (reqlog.Request, error) { +// panic("mock out the FindRequestLogByID method") +// }, +// FindRequestLogsFunc: func(ctx context.Context, filter reqlog.FindRequestsFilter, scopeMoqParam *scope.Scope) ([]reqlog.Request, error) { +// panic("mock out the FindRequestLogs method") +// }, +// FindSettingsByModuleFunc: func(ctx context.Context, module string, settings interface{}) error { +// panic("mock out the FindSettingsByModule method") +// }, +// UpsertSettingsFunc: func(ctx context.Context, module string, settings interface{}) error { +// panic("mock out the UpsertSettings method") +// }, +// } +// +// // use mockedRepository in code that requires reqlog.Repository +// // and then make assertions. +// +// } +type RepoMock struct { + // AddRequestLogFunc mocks the AddRequestLog method. + AddRequestLogFunc func(ctx context.Context, req http.Request, body []byte, timestamp time.Time) (*reqlog.Request, error) + + // AddResponseLogFunc mocks the AddResponseLog method. + AddResponseLogFunc func(ctx context.Context, reqID int64, res http.Response, body []byte, timestamp time.Time) (*reqlog.Response, error) + + // ClearRequestLogsFunc mocks the ClearRequestLogs method. + ClearRequestLogsFunc func(ctx context.Context) error + + // FindRequestLogByIDFunc mocks the FindRequestLogByID method. + FindRequestLogByIDFunc func(ctx context.Context, id int64) (reqlog.Request, error) + + // FindRequestLogsFunc mocks the FindRequestLogs method. + FindRequestLogsFunc func(ctx context.Context, filter reqlog.FindRequestsFilter, scopeMoqParam *scope.Scope) ([]reqlog.Request, error) + + // FindSettingsByModuleFunc mocks the FindSettingsByModule method. + FindSettingsByModuleFunc func(ctx context.Context, module string, settings interface{}) error + + // UpsertSettingsFunc mocks the UpsertSettings method. + UpsertSettingsFunc func(ctx context.Context, module string, settings interface{}) error + + // calls tracks calls to the methods. + calls struct { + // AddRequestLog holds details about calls to the AddRequestLog method. + AddRequestLog []struct { + // Ctx is the ctx argument value. + Ctx context.Context + // Req is the req argument value. + Req http.Request + // Body is the body argument value. + Body []byte + // Timestamp is the timestamp argument value. + Timestamp time.Time + } + // AddResponseLog holds details about calls to the AddResponseLog method. + AddResponseLog []struct { + // Ctx is the ctx argument value. + Ctx context.Context + // ReqID is the reqID argument value. + ReqID int64 + // Res is the res argument value. + Res http.Response + // Body is the body argument value. + Body []byte + // Timestamp is the timestamp argument value. + Timestamp time.Time + } + // ClearRequestLogs holds details about calls to the ClearRequestLogs method. + ClearRequestLogs []struct { + // Ctx is the ctx argument value. + Ctx context.Context + } + // FindRequestLogByID holds details about calls to the FindRequestLogByID method. + FindRequestLogByID []struct { + // Ctx is the ctx argument value. + Ctx context.Context + // ID is the id argument value. + ID int64 + } + // FindRequestLogs holds details about calls to the FindRequestLogs method. + FindRequestLogs []struct { + // Ctx is the ctx argument value. + Ctx context.Context + // Filter is the filter argument value. + Filter reqlog.FindRequestsFilter + // ScopeMoqParam is the scopeMoqParam argument value. + ScopeMoqParam *scope.Scope + } + // FindSettingsByModule holds details about calls to the FindSettingsByModule method. + FindSettingsByModule []struct { + // Ctx is the ctx argument value. + Ctx context.Context + // Module is the module argument value. + Module string + // Settings is the settings argument value. + Settings interface{} + } + // UpsertSettings holds details about calls to the UpsertSettings method. + UpsertSettings []struct { + // Ctx is the ctx argument value. + Ctx context.Context + // Module is the module argument value. + Module string + // Settings is the settings argument value. + Settings interface{} + } + } + lockAddRequestLog sync.RWMutex + lockAddResponseLog sync.RWMutex + lockClearRequestLogs sync.RWMutex + lockFindRequestLogByID sync.RWMutex + lockFindRequestLogs sync.RWMutex + lockFindSettingsByModule sync.RWMutex + lockUpsertSettings sync.RWMutex +} + +// AddRequestLog calls AddRequestLogFunc. +func (mock *RepoMock) AddRequestLog(ctx context.Context, req http.Request, body []byte, timestamp time.Time) (*reqlog.Request, error) { + if mock.AddRequestLogFunc == nil { + panic("RepoMock.AddRequestLogFunc: method is nil but Repository.AddRequestLog was just called") + } + callInfo := struct { + Ctx context.Context + Req http.Request + Body []byte + Timestamp time.Time + }{ + Ctx: ctx, + Req: req, + Body: body, + Timestamp: timestamp, + } + mock.lockAddRequestLog.Lock() + mock.calls.AddRequestLog = append(mock.calls.AddRequestLog, callInfo) + mock.lockAddRequestLog.Unlock() + return mock.AddRequestLogFunc(ctx, req, body, timestamp) +} + +// AddRequestLogCalls gets all the calls that were made to AddRequestLog. +// Check the length with: +// len(mockedRepository.AddRequestLogCalls()) +func (mock *RepoMock) AddRequestLogCalls() []struct { + Ctx context.Context + Req http.Request + Body []byte + Timestamp time.Time +} { + var calls []struct { + Ctx context.Context + Req http.Request + Body []byte + Timestamp time.Time + } + mock.lockAddRequestLog.RLock() + calls = mock.calls.AddRequestLog + mock.lockAddRequestLog.RUnlock() + return calls +} + +// AddResponseLog calls AddResponseLogFunc. +func (mock *RepoMock) AddResponseLog(ctx context.Context, reqID int64, res http.Response, body []byte, timestamp time.Time) (*reqlog.Response, error) { + if mock.AddResponseLogFunc == nil { + panic("RepoMock.AddResponseLogFunc: method is nil but Repository.AddResponseLog was just called") + } + callInfo := struct { + Ctx context.Context + ReqID int64 + Res http.Response + Body []byte + Timestamp time.Time + }{ + Ctx: ctx, + ReqID: reqID, + Res: res, + Body: body, + Timestamp: timestamp, + } + mock.lockAddResponseLog.Lock() + mock.calls.AddResponseLog = append(mock.calls.AddResponseLog, callInfo) + mock.lockAddResponseLog.Unlock() + return mock.AddResponseLogFunc(ctx, reqID, res, body, timestamp) +} + +// AddResponseLogCalls gets all the calls that were made to AddResponseLog. +// Check the length with: +// len(mockedRepository.AddResponseLogCalls()) +func (mock *RepoMock) AddResponseLogCalls() []struct { + Ctx context.Context + ReqID int64 + Res http.Response + Body []byte + Timestamp time.Time +} { + var calls []struct { + Ctx context.Context + ReqID int64 + Res http.Response + Body []byte + Timestamp time.Time + } + mock.lockAddResponseLog.RLock() + calls = mock.calls.AddResponseLog + mock.lockAddResponseLog.RUnlock() + return calls +} + +// ClearRequestLogs calls ClearRequestLogsFunc. +func (mock *RepoMock) ClearRequestLogs(ctx context.Context) error { + if mock.ClearRequestLogsFunc == nil { + panic("RepoMock.ClearRequestLogsFunc: method is nil but Repository.ClearRequestLogs was just called") + } + callInfo := struct { + Ctx context.Context + }{ + Ctx: ctx, + } + mock.lockClearRequestLogs.Lock() + mock.calls.ClearRequestLogs = append(mock.calls.ClearRequestLogs, callInfo) + mock.lockClearRequestLogs.Unlock() + return mock.ClearRequestLogsFunc(ctx) +} + +// ClearRequestLogsCalls gets all the calls that were made to ClearRequestLogs. +// Check the length with: +// len(mockedRepository.ClearRequestLogsCalls()) +func (mock *RepoMock) ClearRequestLogsCalls() []struct { + Ctx context.Context +} { + var calls []struct { + Ctx context.Context + } + mock.lockClearRequestLogs.RLock() + calls = mock.calls.ClearRequestLogs + mock.lockClearRequestLogs.RUnlock() + return calls +} + +// FindRequestLogByID calls FindRequestLogByIDFunc. +func (mock *RepoMock) FindRequestLogByID(ctx context.Context, id int64) (reqlog.Request, error) { + if mock.FindRequestLogByIDFunc == nil { + panic("RepoMock.FindRequestLogByIDFunc: method is nil but Repository.FindRequestLogByID was just called") + } + callInfo := struct { + Ctx context.Context + ID int64 + }{ + Ctx: ctx, + ID: id, + } + mock.lockFindRequestLogByID.Lock() + mock.calls.FindRequestLogByID = append(mock.calls.FindRequestLogByID, callInfo) + mock.lockFindRequestLogByID.Unlock() + return mock.FindRequestLogByIDFunc(ctx, id) +} + +// FindRequestLogByIDCalls gets all the calls that were made to FindRequestLogByID. +// Check the length with: +// len(mockedRepository.FindRequestLogByIDCalls()) +func (mock *RepoMock) FindRequestLogByIDCalls() []struct { + Ctx context.Context + ID int64 +} { + var calls []struct { + Ctx context.Context + ID int64 + } + mock.lockFindRequestLogByID.RLock() + calls = mock.calls.FindRequestLogByID + mock.lockFindRequestLogByID.RUnlock() + return calls +} + +// FindRequestLogs calls FindRequestLogsFunc. +func (mock *RepoMock) FindRequestLogs(ctx context.Context, filter reqlog.FindRequestsFilter, scopeMoqParam *scope.Scope) ([]reqlog.Request, error) { + if mock.FindRequestLogsFunc == nil { + panic("RepoMock.FindRequestLogsFunc: method is nil but Repository.FindRequestLogs was just called") + } + callInfo := struct { + Ctx context.Context + Filter reqlog.FindRequestsFilter + ScopeMoqParam *scope.Scope + }{ + Ctx: ctx, + Filter: filter, + ScopeMoqParam: scopeMoqParam, + } + mock.lockFindRequestLogs.Lock() + mock.calls.FindRequestLogs = append(mock.calls.FindRequestLogs, callInfo) + mock.lockFindRequestLogs.Unlock() + return mock.FindRequestLogsFunc(ctx, filter, scopeMoqParam) +} + +// FindRequestLogsCalls gets all the calls that were made to FindRequestLogs. +// Check the length with: +// len(mockedRepository.FindRequestLogsCalls()) +func (mock *RepoMock) FindRequestLogsCalls() []struct { + Ctx context.Context + Filter reqlog.FindRequestsFilter + ScopeMoqParam *scope.Scope +} { + var calls []struct { + Ctx context.Context + Filter reqlog.FindRequestsFilter + ScopeMoqParam *scope.Scope + } + mock.lockFindRequestLogs.RLock() + calls = mock.calls.FindRequestLogs + mock.lockFindRequestLogs.RUnlock() + return calls +} + +// FindSettingsByModule calls FindSettingsByModuleFunc. +func (mock *RepoMock) FindSettingsByModule(ctx context.Context, module string, settings interface{}) error { + if mock.FindSettingsByModuleFunc == nil { + panic("RepoMock.FindSettingsByModuleFunc: method is nil but Repository.FindSettingsByModule was just called") + } + callInfo := struct { + Ctx context.Context + Module string + Settings interface{} + }{ + Ctx: ctx, + Module: module, + Settings: settings, + } + mock.lockFindSettingsByModule.Lock() + mock.calls.FindSettingsByModule = append(mock.calls.FindSettingsByModule, callInfo) + mock.lockFindSettingsByModule.Unlock() + return mock.FindSettingsByModuleFunc(ctx, module, settings) +} + +// FindSettingsByModuleCalls gets all the calls that were made to FindSettingsByModule. +// Check the length with: +// len(mockedRepository.FindSettingsByModuleCalls()) +func (mock *RepoMock) FindSettingsByModuleCalls() []struct { + Ctx context.Context + Module string + Settings interface{} +} { + var calls []struct { + Ctx context.Context + Module string + Settings interface{} + } + mock.lockFindSettingsByModule.RLock() + calls = mock.calls.FindSettingsByModule + mock.lockFindSettingsByModule.RUnlock() + return calls +} + +// UpsertSettings calls UpsertSettingsFunc. +func (mock *RepoMock) UpsertSettings(ctx context.Context, module string, settings interface{}) error { + if mock.UpsertSettingsFunc == nil { + panic("RepoMock.UpsertSettingsFunc: method is nil but Repository.UpsertSettings was just called") + } + callInfo := struct { + Ctx context.Context + Module string + Settings interface{} + }{ + Ctx: ctx, + Module: module, + Settings: settings, + } + mock.lockUpsertSettings.Lock() + mock.calls.UpsertSettings = append(mock.calls.UpsertSettings, callInfo) + mock.lockUpsertSettings.Unlock() + return mock.UpsertSettingsFunc(ctx, module, settings) +} + +// UpsertSettingsCalls gets all the calls that were made to UpsertSettings. +// Check the length with: +// len(mockedRepository.UpsertSettingsCalls()) +func (mock *RepoMock) UpsertSettingsCalls() []struct { + Ctx context.Context + Module string + Settings interface{} +} { + var calls []struct { + Ctx context.Context + Module string + Settings interface{} + } + mock.lockUpsertSettings.RLock() + calls = mock.calls.UpsertSettings + mock.lockUpsertSettings.RUnlock() + return calls +} diff --git a/pkg/reqlog/reqlog.go b/pkg/reqlog/reqlog.go index 363939f..3a278b9 100644 --- a/pkg/reqlog/reqlog.go +++ b/pkg/reqlog/reqlog.go @@ -59,7 +59,7 @@ type FindRequestsFilter struct { type Config struct { Scope *scope.Scope Repository Repository - ProjectService *proj.Service + ProjectService proj.Service BypassOutOfScopeRequests bool } @@ -71,7 +71,7 @@ func NewService(cfg Config) *Service { } cfg.ProjectService.OnProjectOpen(func(_ string) error { - err := svc.loadSettings() + err := svc.repo.FindSettingsByModule(context.Background(), moduleName, svc) if errors.Is(err, proj.ErrNoSettings) { return nil } @@ -82,7 +82,8 @@ func NewService(cfg Config) *Service { return nil }) cfg.ProjectService.OnProjectClose(func(_ string) error { - svc.unloadSettings() + svc.BypassOutOfScopeRequests = false + svc.FindReqsFilter = FindRequestsFilter{} return nil }) @@ -252,12 +253,3 @@ func (f *FindRequestsFilter) UnmarshalJSON(b []byte) error { return nil } - -func (svc *Service) loadSettings() error { - return svc.repo.FindSettingsByModule(context.Background(), moduleName, svc) -} - -func (svc *Service) unloadSettings() { - svc.BypassOutOfScopeRequests = false - svc.FindReqsFilter = FindRequestsFilter{} -} diff --git a/pkg/reqlog/reqlog_test.go b/pkg/reqlog/reqlog_test.go new file mode 100644 index 0000000..ff5aa1f --- /dev/null +++ b/pkg/reqlog/reqlog_test.go @@ -0,0 +1,193 @@ +package reqlog_test + +//go:generate moq -out proj_mock_test.go -pkg reqlog_test ../proj Service:ProjServiceMock +//go:generate moq -out repo_mock_test.go -pkg reqlog_test . Repository:RepoMock + +import ( + "context" + "io/ioutil" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/dstotijn/hetty/pkg/proj" + "github.com/dstotijn/hetty/pkg/proxy" + "github.com/dstotijn/hetty/pkg/reqlog" +) + +//nolint:paralleltest +func TestNewService(t *testing.T) { + projSvcMock := &ProjServiceMock{ + OnProjectOpenFunc: func(fn proj.OnProjectOpenFn) {}, + OnProjectCloseFunc: func(fn proj.OnProjectCloseFn) {}, + } + repoMock := &RepoMock{ + FindSettingsByModuleFunc: func(_ context.Context, _ string, _ interface{}) error { + return nil + }, + } + svc := reqlog.NewService(reqlog.Config{ + ProjectService: projSvcMock, + Repository: repoMock, + }) + + t.Run("registered handlers for project open and close", func(t *testing.T) { + got := len(projSvcMock.OnProjectOpenCalls()) + if exp := 1; exp != got { + t.Fatalf("incorrect `proj.Service.OnProjectOpen` calls (expected: %v, got: %v)", exp, got) + } + + got = len(projSvcMock.OnProjectCloseCalls()) + if exp := 1; exp != got { + t.Fatalf("incorrect `proj.Service.OnProjectClose` calls (expected: %v, got: %v)", exp, got) + } + }) + + t.Run("calls handler when project is opened", func(t *testing.T) { + // Mock opening a project. + err := projSvcMock.OnProjectOpenCalls()[0].Fn("foobar") + if err != nil { + t.Errorf("unexpected error (expected: nil, got: %v)", err) + } + + // Assert that settings were fetched from repository, with `svc` as the + // destination. + got := len(repoMock.FindSettingsByModuleCalls()) + if exp := 1; exp != got { + t.Fatalf("incorrect `proj.Service.OnProjectOpen` calls (expected: %v, got: %v)", exp, got) + } + + findSettingsByModuleCall := repoMock.FindSettingsByModuleCalls()[0] + expModule := "reqlog" + expSettings := svc + + if expModule != findSettingsByModuleCall.Module { + t.Fatalf("incorrect `module` argument for `proj.Service.OnProjectOpen` (expected: %v, got: %v)", + expModule, findSettingsByModuleCall.Module) + } + + if expSettings != findSettingsByModuleCall.Settings { + t.Fatalf("incorrect `settings` argument for `proj.Service.OnProjectOpen` (expected: %v, got: %v)", + expModule, findSettingsByModuleCall.Settings) + } + }) + + t.Run("calls handler when project is closed", func(t *testing.T) { + // Mock updating service settings. + svc.BypassOutOfScopeRequests = true + svc.FindReqsFilter = reqlog.FindRequestsFilter{OnlyInScope: true} + + // Mock closing a project. + err := projSvcMock.OnProjectCloseCalls()[0].Fn("foobar") + if err != nil { + t.Errorf("unexpected error (expected: nil, got: %v)", err) + } + + // Assert that settings were set to defaults on project close. + expBypassOutOfScopeReqs := false + expFindReqsFilter := reqlog.FindRequestsFilter{} + + if expBypassOutOfScopeReqs != svc.BypassOutOfScopeRequests { + t.Fatalf("incorrect `Service.BypassOutOfScopeRequests` value (expected: %v, got: %v)", + expBypassOutOfScopeReqs, svc.BypassOutOfScopeRequests) + } + + if expFindReqsFilter != svc.FindReqsFilter { + t.Fatalf("incorrect `Service.FindReqsFilter` value (expected: %v, got: %v)", + expFindReqsFilter, svc.FindReqsFilter) + } + }) +} + +//nolint:paralleltest +func TestRequestModifier(t *testing.T) { + projSvcMock := &ProjServiceMock{ + OnProjectOpenFunc: func(fn proj.OnProjectOpenFn) {}, + OnProjectCloseFunc: func(fn proj.OnProjectCloseFn) {}, + } + repoMock := &RepoMock{ + AddRequestLogFunc: func(_ context.Context, _ http.Request, _ []byte, _ time.Time) (*reqlog.Request, error) { + return &reqlog.Request{}, nil + }, + } + svc := reqlog.NewService(reqlog.Config{ + ProjectService: projSvcMock, + Repository: repoMock, + }) + + next := func(req *http.Request) { + req.Body = ioutil.NopCloser(strings.NewReader("modified body")) + } + reqModFn := svc.RequestModifier(next) + req := httptest.NewRequest("GET", "https://example.com/", strings.NewReader("bar")) + + reqModFn(req) + + t.Run("request log was stored in repository", func(t *testing.T) { + got := len(repoMock.AddRequestLogCalls()) + if exp := 1; exp != got { + t.Fatalf("incorrect `proj.Service.AddRequestLog` calls (expected: %v, got: %v)", exp, got) + } + }) + + t.Run("ran next modifier first, before calling repository", func(t *testing.T) { + got := repoMock.AddRequestLogCalls()[0].Body + if exp := "modified body"; exp != string(got) { + t.Fatalf("incorrect `body` argument for `Repository.AddRequestLogCalls` (expected: %v, got: %v)", exp, string(got)) + } + }) +} + +//nolint:paralleltest +func TestResponseModifier(t *testing.T) { + projSvcMock := &ProjServiceMock{ + OnProjectOpenFunc: func(fn proj.OnProjectOpenFn) {}, + OnProjectCloseFunc: func(fn proj.OnProjectCloseFn) {}, + } + repoMock := &RepoMock{ + AddResponseLogFunc: func(_ context.Context, _ int64, _ http.Response, + _ []byte, _ time.Time) (*reqlog.Response, error) { + return &reqlog.Response{}, nil + }, + } + svc := reqlog.NewService(reqlog.Config{ + ProjectService: projSvcMock, + Repository: repoMock, + }) + + next := func(res *http.Response) error { + res.Body = ioutil.NopCloser(strings.NewReader("modified body")) + return nil + } + resModFn := svc.ResponseModifier(next) + + req := httptest.NewRequest("GET", "https://example.com/", strings.NewReader("bar")) + req = req.WithContext(context.WithValue(req.Context(), proxy.ReqIDKey, int64(42))) + + res := &http.Response{ + Request: req, + Body: ioutil.NopCloser(strings.NewReader("bar")), + } + + if err := resModFn(res); err != nil { + t.Fatalf("unexpected error (expected: nil, got: %v)", err) + } + + t.Run("request log was stored in repository", func(t *testing.T) { + // Dirty (but simple) wait for other goroutine to finish calling repository. + time.Sleep(10 * time.Millisecond) + got := len(repoMock.AddResponseLogCalls()) + if exp := 1; exp != got { + t.Fatalf("incorrect `proj.Service.AddResponseLog` calls (expected: %v, got: %v)", exp, got) + } + }) + + t.Run("ran next modifier first, before calling repository", func(t *testing.T) { + got := repoMock.AddResponseLogCalls()[0].Body + if exp := "modified body"; exp != string(got) { + t.Fatalf("incorrect `body` argument for `Repository.AddResponseLogCalls` (expected: %v, got: %v)", exp, string(got)) + } + }) +} diff --git a/pkg/scope/scope.go b/pkg/scope/scope.go index 2bcb5f0..2598e6c 100644 --- a/pkg/scope/scope.go +++ b/pkg/scope/scope.go @@ -32,7 +32,7 @@ type Header struct { Value *regexp.Regexp } -func New(repo Repository, projService *proj.Service) *Scope { +func New(repo Repository, projService proj.Service) *Scope { s := &Scope{ repo: repo, }