mirror of
https://github.com/dstotijn/hetty.git
synced 2025-07-01 18:47:29 -04:00
Replace SQLite with BadgerDB
This commit is contained in:
@ -1,318 +0,0 @@
|
||||
// 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
|
||||
}
|
@ -2,18 +2,16 @@ package reqlog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
)
|
||||
|
||||
type Repository interface {
|
||||
FindRequestLogs(ctx context.Context, filter FindRequestsFilter, scope *scope.Scope) ([]Request, error)
|
||||
FindRequestLogByID(ctx context.Context, id int64) (Request, error)
|
||||
AddRequestLog(ctx context.Context, req http.Request, body []byte, timestamp time.Time) (*Request, error)
|
||||
AddResponseLog(ctx context.Context, reqID int64, res http.Response, body []byte, timestamp time.Time) (*Response, error) // nolint:lll
|
||||
ClearRequestLogs(ctx context.Context) error
|
||||
UpsertSettings(ctx context.Context, module string, settings interface{}) error
|
||||
FindSettingsByModule(ctx context.Context, module string, settings interface{}) error
|
||||
FindRequestLogs(ctx context.Context, filter FindRequestsFilter, scope *scope.Scope) ([]RequestLog, error)
|
||||
FindRequestLogByID(ctx context.Context, id ulid.ULID) (RequestLog, error)
|
||||
StoreRequestLog(ctx context.Context, reqLog RequestLog) error
|
||||
StoreResponseLog(ctx context.Context, reqLogID ulid.ULID, resLog ResponseLog) error
|
||||
ClearRequestLogs(ctx context.Context, projectID ulid.ULID) error
|
||||
}
|
||||
|
@ -7,9 +7,8 @@ import (
|
||||
"context"
|
||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
"net/http"
|
||||
"github.com/oklog/ulid"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Ensure, that RepoMock does implement reqlog.Repository.
|
||||
@ -22,26 +21,20 @@ var _ reqlog.Repository = &RepoMock{}
|
||||
//
|
||||
// // 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 {
|
||||
// ClearRequestLogsFunc: func(ctx context.Context, projectID ulid.ULID) error {
|
||||
// panic("mock out the ClearRequestLogs method")
|
||||
// },
|
||||
// FindRequestLogByIDFunc: func(ctx context.Context, id int64) (reqlog.Request, error) {
|
||||
// FindRequestLogByIDFunc: func(ctx context.Context, id ulid.ULID) (reqlog.RequestLog, error) {
|
||||
// panic("mock out the FindRequestLogByID method")
|
||||
// },
|
||||
// FindRequestLogsFunc: func(ctx context.Context, filter reqlog.FindRequestsFilter, scopeMoqParam *scope.Scope) ([]reqlog.Request, error) {
|
||||
// FindRequestLogsFunc: func(ctx context.Context, filter reqlog.FindRequestsFilter, scopeMoqParam *scope.Scope) ([]reqlog.RequestLog, error) {
|
||||
// panic("mock out the FindRequestLogs method")
|
||||
// },
|
||||
// FindSettingsByModuleFunc: func(ctx context.Context, module string, settings interface{}) error {
|
||||
// panic("mock out the FindSettingsByModule method")
|
||||
// StoreRequestLogFunc: func(ctx context.Context, reqLog reqlog.RequestLog) error {
|
||||
// panic("mock out the StoreRequestLog method")
|
||||
// },
|
||||
// UpsertSettingsFunc: func(ctx context.Context, module string, settings interface{}) error {
|
||||
// panic("mock out the UpsertSettings method")
|
||||
// StoreResponseLogFunc: func(ctx context.Context, reqLogID ulid.ULID, resLog reqlog.ResponseLog) error {
|
||||
// panic("mock out the StoreResponseLog method")
|
||||
// },
|
||||
// }
|
||||
//
|
||||
@ -50,64 +43,36 @@ var _ reqlog.Repository = &RepoMock{}
|
||||
//
|
||||
// }
|
||||
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
|
||||
ClearRequestLogsFunc func(ctx context.Context, projectID ulid.ULID) error
|
||||
|
||||
// FindRequestLogByIDFunc mocks the FindRequestLogByID method.
|
||||
FindRequestLogByIDFunc func(ctx context.Context, id int64) (reqlog.Request, error)
|
||||
FindRequestLogByIDFunc func(ctx context.Context, id ulid.ULID) (reqlog.RequestLog, error)
|
||||
|
||||
// FindRequestLogsFunc mocks the FindRequestLogs method.
|
||||
FindRequestLogsFunc func(ctx context.Context, filter reqlog.FindRequestsFilter, scopeMoqParam *scope.Scope) ([]reqlog.Request, error)
|
||||
FindRequestLogsFunc func(ctx context.Context, filter reqlog.FindRequestsFilter, scopeMoqParam *scope.Scope) ([]reqlog.RequestLog, error)
|
||||
|
||||
// FindSettingsByModuleFunc mocks the FindSettingsByModule method.
|
||||
FindSettingsByModuleFunc func(ctx context.Context, module string, settings interface{}) error
|
||||
// StoreRequestLogFunc mocks the StoreRequestLog method.
|
||||
StoreRequestLogFunc func(ctx context.Context, reqLog reqlog.RequestLog) error
|
||||
|
||||
// UpsertSettingsFunc mocks the UpsertSettings method.
|
||||
UpsertSettingsFunc func(ctx context.Context, module string, settings interface{}) error
|
||||
// StoreResponseLogFunc mocks the StoreResponseLog method.
|
||||
StoreResponseLogFunc func(ctx context.Context, reqLogID ulid.ULID, resLog reqlog.ResponseLog) 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
|
||||
// ProjectID is the projectID argument value.
|
||||
ProjectID ulid.ULID
|
||||
}
|
||||
// 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
|
||||
ID ulid.ULID
|
||||
}
|
||||
// FindRequestLogs holds details about calls to the FindRequestLogs method.
|
||||
FindRequestLogs []struct {
|
||||
@ -118,148 +83,58 @@ type RepoMock struct {
|
||||
// ScopeMoqParam is the scopeMoqParam argument value.
|
||||
ScopeMoqParam *scope.Scope
|
||||
}
|
||||
// FindSettingsByModule holds details about calls to the FindSettingsByModule method.
|
||||
FindSettingsByModule []struct {
|
||||
// StoreRequestLog holds details about calls to the StoreRequestLog method.
|
||||
StoreRequestLog []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{}
|
||||
// ReqLog is the reqLog argument value.
|
||||
ReqLog reqlog.RequestLog
|
||||
}
|
||||
// UpsertSettings holds details about calls to the UpsertSettings method.
|
||||
UpsertSettings []struct {
|
||||
// StoreResponseLog holds details about calls to the StoreResponseLog method.
|
||||
StoreResponseLog []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{}
|
||||
// ReqLogID is the reqLogID argument value.
|
||||
ReqLogID ulid.ULID
|
||||
// ResLog is the resLog argument value.
|
||||
ResLog reqlog.ResponseLog
|
||||
}
|
||||
}
|
||||
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
|
||||
lockClearRequestLogs sync.RWMutex
|
||||
lockFindRequestLogByID sync.RWMutex
|
||||
lockFindRequestLogs sync.RWMutex
|
||||
lockStoreRequestLog sync.RWMutex
|
||||
lockStoreResponseLog sync.RWMutex
|
||||
}
|
||||
|
||||
// ClearRequestLogs calls ClearRequestLogsFunc.
|
||||
func (mock *RepoMock) ClearRequestLogs(ctx context.Context) error {
|
||||
func (mock *RepoMock) ClearRequestLogs(ctx context.Context, projectID ulid.ULID) error {
|
||||
if mock.ClearRequestLogsFunc == nil {
|
||||
panic("RepoMock.ClearRequestLogsFunc: method is nil but Repository.ClearRequestLogs was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
Ctx context.Context
|
||||
Ctx context.Context
|
||||
ProjectID ulid.ULID
|
||||
}{
|
||||
Ctx: ctx,
|
||||
Ctx: ctx,
|
||||
ProjectID: projectID,
|
||||
}
|
||||
mock.lockClearRequestLogs.Lock()
|
||||
mock.calls.ClearRequestLogs = append(mock.calls.ClearRequestLogs, callInfo)
|
||||
mock.lockClearRequestLogs.Unlock()
|
||||
return mock.ClearRequestLogsFunc(ctx)
|
||||
return mock.ClearRequestLogsFunc(ctx, projectID)
|
||||
}
|
||||
|
||||
// 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
|
||||
Ctx context.Context
|
||||
ProjectID ulid.ULID
|
||||
} {
|
||||
var calls []struct {
|
||||
Ctx context.Context
|
||||
Ctx context.Context
|
||||
ProjectID ulid.ULID
|
||||
}
|
||||
mock.lockClearRequestLogs.RLock()
|
||||
calls = mock.calls.ClearRequestLogs
|
||||
@ -268,13 +143,13 @@ func (mock *RepoMock) ClearRequestLogsCalls() []struct {
|
||||
}
|
||||
|
||||
// FindRequestLogByID calls FindRequestLogByIDFunc.
|
||||
func (mock *RepoMock) FindRequestLogByID(ctx context.Context, id int64) (reqlog.Request, error) {
|
||||
func (mock *RepoMock) FindRequestLogByID(ctx context.Context, id ulid.ULID) (reqlog.RequestLog, error) {
|
||||
if mock.FindRequestLogByIDFunc == nil {
|
||||
panic("RepoMock.FindRequestLogByIDFunc: method is nil but Repository.FindRequestLogByID was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
Ctx context.Context
|
||||
ID int64
|
||||
ID ulid.ULID
|
||||
}{
|
||||
Ctx: ctx,
|
||||
ID: id,
|
||||
@ -290,11 +165,11 @@ func (mock *RepoMock) FindRequestLogByID(ctx context.Context, id int64) (reqlog.
|
||||
// len(mockedRepository.FindRequestLogByIDCalls())
|
||||
func (mock *RepoMock) FindRequestLogByIDCalls() []struct {
|
||||
Ctx context.Context
|
||||
ID int64
|
||||
ID ulid.ULID
|
||||
} {
|
||||
var calls []struct {
|
||||
Ctx context.Context
|
||||
ID int64
|
||||
ID ulid.ULID
|
||||
}
|
||||
mock.lockFindRequestLogByID.RLock()
|
||||
calls = mock.calls.FindRequestLogByID
|
||||
@ -303,7 +178,7 @@ func (mock *RepoMock) FindRequestLogByIDCalls() []struct {
|
||||
}
|
||||
|
||||
// FindRequestLogs calls FindRequestLogsFunc.
|
||||
func (mock *RepoMock) FindRequestLogs(ctx context.Context, filter reqlog.FindRequestsFilter, scopeMoqParam *scope.Scope) ([]reqlog.Request, error) {
|
||||
func (mock *RepoMock) FindRequestLogs(ctx context.Context, filter reqlog.FindRequestsFilter, scopeMoqParam *scope.Scope) ([]reqlog.RequestLog, error) {
|
||||
if mock.FindRequestLogsFunc == nil {
|
||||
panic("RepoMock.FindRequestLogsFunc: method is nil but Repository.FindRequestLogs was just called")
|
||||
}
|
||||
@ -341,80 +216,76 @@ func (mock *RepoMock) FindRequestLogsCalls() []struct {
|
||||
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")
|
||||
// StoreRequestLog calls StoreRequestLogFunc.
|
||||
func (mock *RepoMock) StoreRequestLog(ctx context.Context, reqLog reqlog.RequestLog) error {
|
||||
if mock.StoreRequestLogFunc == nil {
|
||||
panic("RepoMock.StoreRequestLogFunc: method is nil but Repository.StoreRequestLog was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
Ctx context.Context
|
||||
Module string
|
||||
Settings interface{}
|
||||
Ctx context.Context
|
||||
ReqLog reqlog.RequestLog
|
||||
}{
|
||||
Ctx: ctx,
|
||||
Module: module,
|
||||
Settings: settings,
|
||||
Ctx: ctx,
|
||||
ReqLog: reqLog,
|
||||
}
|
||||
mock.lockFindSettingsByModule.Lock()
|
||||
mock.calls.FindSettingsByModule = append(mock.calls.FindSettingsByModule, callInfo)
|
||||
mock.lockFindSettingsByModule.Unlock()
|
||||
return mock.FindSettingsByModuleFunc(ctx, module, settings)
|
||||
mock.lockStoreRequestLog.Lock()
|
||||
mock.calls.StoreRequestLog = append(mock.calls.StoreRequestLog, callInfo)
|
||||
mock.lockStoreRequestLog.Unlock()
|
||||
return mock.StoreRequestLogFunc(ctx, reqLog)
|
||||
}
|
||||
|
||||
// FindSettingsByModuleCalls gets all the calls that were made to FindSettingsByModule.
|
||||
// StoreRequestLogCalls gets all the calls that were made to StoreRequestLog.
|
||||
// Check the length with:
|
||||
// len(mockedRepository.FindSettingsByModuleCalls())
|
||||
func (mock *RepoMock) FindSettingsByModuleCalls() []struct {
|
||||
Ctx context.Context
|
||||
Module string
|
||||
Settings interface{}
|
||||
// len(mockedRepository.StoreRequestLogCalls())
|
||||
func (mock *RepoMock) StoreRequestLogCalls() []struct {
|
||||
Ctx context.Context
|
||||
ReqLog reqlog.RequestLog
|
||||
} {
|
||||
var calls []struct {
|
||||
Ctx context.Context
|
||||
Module string
|
||||
Settings interface{}
|
||||
Ctx context.Context
|
||||
ReqLog reqlog.RequestLog
|
||||
}
|
||||
mock.lockFindSettingsByModule.RLock()
|
||||
calls = mock.calls.FindSettingsByModule
|
||||
mock.lockFindSettingsByModule.RUnlock()
|
||||
mock.lockStoreRequestLog.RLock()
|
||||
calls = mock.calls.StoreRequestLog
|
||||
mock.lockStoreRequestLog.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")
|
||||
// StoreResponseLog calls StoreResponseLogFunc.
|
||||
func (mock *RepoMock) StoreResponseLog(ctx context.Context, reqLogID ulid.ULID, resLog reqlog.ResponseLog) error {
|
||||
if mock.StoreResponseLogFunc == nil {
|
||||
panic("RepoMock.StoreResponseLogFunc: method is nil but Repository.StoreResponseLog was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
Ctx context.Context
|
||||
Module string
|
||||
Settings interface{}
|
||||
ReqLogID ulid.ULID
|
||||
ResLog reqlog.ResponseLog
|
||||
}{
|
||||
Ctx: ctx,
|
||||
Module: module,
|
||||
Settings: settings,
|
||||
ReqLogID: reqLogID,
|
||||
ResLog: resLog,
|
||||
}
|
||||
mock.lockUpsertSettings.Lock()
|
||||
mock.calls.UpsertSettings = append(mock.calls.UpsertSettings, callInfo)
|
||||
mock.lockUpsertSettings.Unlock()
|
||||
return mock.UpsertSettingsFunc(ctx, module, settings)
|
||||
mock.lockStoreResponseLog.Lock()
|
||||
mock.calls.StoreResponseLog = append(mock.calls.StoreResponseLog, callInfo)
|
||||
mock.lockStoreResponseLog.Unlock()
|
||||
return mock.StoreResponseLogFunc(ctx, reqLogID, resLog)
|
||||
}
|
||||
|
||||
// UpsertSettingsCalls gets all the calls that were made to UpsertSettings.
|
||||
// StoreResponseLogCalls gets all the calls that were made to StoreResponseLog.
|
||||
// Check the length with:
|
||||
// len(mockedRepository.UpsertSettingsCalls())
|
||||
func (mock *RepoMock) UpsertSettingsCalls() []struct {
|
||||
// len(mockedRepository.StoreResponseLogCalls())
|
||||
func (mock *RepoMock) StoreResponseLogCalls() []struct {
|
||||
Ctx context.Context
|
||||
Module string
|
||||
Settings interface{}
|
||||
ReqLogID ulid.ULID
|
||||
ResLog reqlog.ResponseLog
|
||||
} {
|
||||
var calls []struct {
|
||||
Ctx context.Context
|
||||
Module string
|
||||
Settings interface{}
|
||||
ReqLogID ulid.ULID
|
||||
ResLog reqlog.ResponseLog
|
||||
}
|
||||
mock.lockUpsertSettings.RLock()
|
||||
calls = mock.calls.UpsertSettings
|
||||
mock.lockUpsertSettings.RUnlock()
|
||||
mock.lockStoreResponseLog.RLock()
|
||||
calls = mock.calls.StoreResponseLog
|
||||
mock.lockStoreResponseLog.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
@ -4,15 +4,18 @@ import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/proj"
|
||||
"github.com/oklog/ulid"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/proxy"
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
"github.com/dstotijn/hetty/pkg/search"
|
||||
@ -22,127 +25,109 @@ type contextKey int
|
||||
|
||||
const LogBypassedKey contextKey = 0
|
||||
|
||||
const moduleName = "reqlog"
|
||||
var (
|
||||
ErrRequestNotFound = errors.New("reqlog: request not found")
|
||||
ErrProjectIDMustBeSet = errors.New("reqlog: project ID must be set")
|
||||
)
|
||||
|
||||
var ErrRequestNotFound = errors.New("reqlog: request not found")
|
||||
//nolint:gosec
|
||||
var ulidEntropy = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
type Request struct {
|
||||
ID int64
|
||||
Request http.Request
|
||||
Body []byte
|
||||
Timestamp time.Time
|
||||
Response *Response
|
||||
type RequestLog struct {
|
||||
ID ulid.ULID
|
||||
ProjectID ulid.ULID
|
||||
|
||||
URL *url.URL
|
||||
Method string
|
||||
Proto string
|
||||
Header http.Header
|
||||
Body []byte
|
||||
|
||||
Response *ResponseLog
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
ID int64
|
||||
RequestID int64
|
||||
Response http.Response
|
||||
Body []byte
|
||||
Timestamp time.Time
|
||||
type ResponseLog struct {
|
||||
Proto string
|
||||
StatusCode int
|
||||
Status string
|
||||
Header http.Header
|
||||
Body []byte
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
BypassOutOfScopeRequests bool
|
||||
FindReqsFilter FindRequestsFilter
|
||||
ActiveProjectID ulid.ULID
|
||||
|
||||
scope *scope.Scope
|
||||
repo Repository
|
||||
}
|
||||
|
||||
type FindRequestsFilter struct {
|
||||
OnlyInScope bool
|
||||
SearchExpr search.Expression `json:"-"`
|
||||
RawSearchExpr string
|
||||
ProjectID ulid.ULID
|
||||
OnlyInScope bool
|
||||
SearchExpr search.Expression
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Scope *scope.Scope
|
||||
Repository Repository
|
||||
ProjectService proj.Service
|
||||
BypassOutOfScopeRequests bool
|
||||
Scope *scope.Scope
|
||||
Repository Repository
|
||||
}
|
||||
|
||||
func NewService(cfg Config) *Service {
|
||||
svc := &Service{
|
||||
scope: cfg.Scope,
|
||||
repo: cfg.Repository,
|
||||
BypassOutOfScopeRequests: cfg.BypassOutOfScopeRequests,
|
||||
return &Service{
|
||||
repo: cfg.Repository,
|
||||
scope: cfg.Scope,
|
||||
}
|
||||
|
||||
cfg.ProjectService.OnProjectOpen(func(_ string) error {
|
||||
err := svc.repo.FindSettingsByModule(context.Background(), moduleName, svc)
|
||||
if errors.Is(err, proj.ErrNoSettings) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("reqlog: could not load settings: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
cfg.ProjectService.OnProjectClose(func(_ string) error {
|
||||
svc.BypassOutOfScopeRequests = false
|
||||
svc.FindReqsFilter = FindRequestsFilter{}
|
||||
return nil
|
||||
})
|
||||
|
||||
return svc
|
||||
}
|
||||
|
||||
func (svc *Service) FindRequests(ctx context.Context) ([]Request, error) {
|
||||
func (svc *Service) FindRequests(ctx context.Context) ([]RequestLog, error) {
|
||||
return svc.repo.FindRequestLogs(ctx, svc.FindReqsFilter, svc.scope)
|
||||
}
|
||||
|
||||
func (svc *Service) FindRequestLogByID(ctx context.Context, id int64) (Request, error) {
|
||||
func (svc *Service) FindRequestLogByID(ctx context.Context, id ulid.ULID) (RequestLog, error) {
|
||||
return svc.repo.FindRequestLogByID(ctx, id)
|
||||
}
|
||||
|
||||
func (svc *Service) SetRequestLogFilter(ctx context.Context, filter FindRequestsFilter) error {
|
||||
svc.FindReqsFilter = filter
|
||||
return svc.repo.UpsertSettings(ctx, "reqlog", svc)
|
||||
func (svc *Service) ClearRequests(ctx context.Context, projectID ulid.ULID) error {
|
||||
return svc.repo.ClearRequestLogs(ctx, projectID)
|
||||
}
|
||||
|
||||
func (svc *Service) ClearRequests(ctx context.Context) error {
|
||||
return svc.repo.ClearRequestLogs(ctx)
|
||||
}
|
||||
|
||||
func (svc *Service) addRequest(
|
||||
ctx context.Context,
|
||||
req http.Request,
|
||||
body []byte,
|
||||
timestamp time.Time,
|
||||
) (*Request, error) {
|
||||
return svc.repo.AddRequestLog(ctx, req, body, timestamp)
|
||||
}
|
||||
|
||||
func (svc *Service) addResponse(
|
||||
ctx context.Context,
|
||||
reqID int64,
|
||||
res http.Response,
|
||||
body []byte,
|
||||
timestamp time.Time,
|
||||
) (*Response, error) {
|
||||
func (svc *Service) storeResponse(ctx context.Context, reqLogID ulid.ULID, res *http.Response) error {
|
||||
if res.Header.Get("Content-Encoding") == "gzip" {
|
||||
gzipReader, err := gzip.NewReader(bytes.NewBuffer(body))
|
||||
gzipReader, err := gzip.NewReader(res.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reqlog: could not create gzip reader: %w", err)
|
||||
return fmt.Errorf("could not create gzip reader: %w", err)
|
||||
}
|
||||
defer gzipReader.Close()
|
||||
|
||||
body, err = ioutil.ReadAll(gzipReader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reqlog: could not read gzipped response body: %w", err)
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
if _, err := io.Copy(buf, gzipReader); err != nil {
|
||||
return fmt.Errorf("could not read gzipped response body: %w", err)
|
||||
}
|
||||
|
||||
res.Body = io.NopCloser(buf)
|
||||
}
|
||||
|
||||
return svc.repo.AddResponseLog(ctx, reqID, res, body, timestamp)
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read body: %w", err)
|
||||
}
|
||||
|
||||
resLog := ResponseLog{
|
||||
Proto: res.Proto,
|
||||
StatusCode: res.StatusCode,
|
||||
Status: res.Status,
|
||||
Header: res.Header,
|
||||
Body: body,
|
||||
}
|
||||
|
||||
return svc.repo.StoreResponseLog(ctx, reqLogID, resLog)
|
||||
}
|
||||
|
||||
func (svc *Service) RequestModifier(next proxy.RequestModifyFunc) proxy.RequestModifyFunc {
|
||||
return func(req *http.Request) {
|
||||
now := time.Now()
|
||||
|
||||
next(req)
|
||||
|
||||
clone := req.Clone(req.Context())
|
||||
@ -160,10 +145,19 @@ func (svc *Service) RequestModifier(next proxy.RequestModifyFunc) proxy.RequestM
|
||||
}
|
||||
|
||||
req.Body = ioutil.NopCloser(bytes.NewBuffer(body))
|
||||
clone.Body = ioutil.NopCloser(bytes.NewBuffer(body))
|
||||
}
|
||||
|
||||
// Bypass logging if no project is active.
|
||||
if svc.ActiveProjectID.Compare(ulid.ULID{}) == 0 {
|
||||
ctx := context.WithValue(req.Context(), LogBypassedKey, true)
|
||||
*req = *req.WithContext(ctx)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Bypass logging if this setting is enabled and the incoming request
|
||||
// doens't match any rules of the scope.
|
||||
// doesn't match any scope rules.
|
||||
if svc.BypassOutOfScopeRequests && !svc.scope.Match(clone, body) {
|
||||
ctx := context.WithValue(req.Context(), LogBypassedKey, true)
|
||||
*req = *req.WithContext(ctx)
|
||||
@ -171,26 +165,29 @@ func (svc *Service) RequestModifier(next proxy.RequestModifyFunc) proxy.RequestM
|
||||
return
|
||||
}
|
||||
|
||||
reqLog, err := svc.addRequest(req.Context(), *clone, body, now)
|
||||
if errors.Is(err, proj.ErrNoProject) {
|
||||
ctx := context.WithValue(req.Context(), LogBypassedKey, true)
|
||||
*req = *req.WithContext(ctx)
|
||||
reqLog := RequestLog{
|
||||
ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
|
||||
ProjectID: svc.ActiveProjectID,
|
||||
Method: clone.Method,
|
||||
URL: clone.URL,
|
||||
Proto: clone.Proto,
|
||||
Header: clone.Header,
|
||||
Body: body,
|
||||
}
|
||||
|
||||
return
|
||||
} else if err != nil {
|
||||
err := svc.repo.StoreRequestLog(req.Context(), reqLog)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Could not store request log: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(req.Context(), proxy.ReqIDKey, reqLog.ID)
|
||||
ctx := context.WithValue(req.Context(), proxy.ReqLogIDKey, reqLog.ID)
|
||||
*req = *req.WithContext(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *Service) ResponseModifier(next proxy.ResponseModifyFunc) proxy.ResponseModifyFunc {
|
||||
return func(res *http.Response) error {
|
||||
now := time.Now()
|
||||
|
||||
if err := next(res); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -199,8 +196,8 @@ func (svc *Service) ResponseModifier(next proxy.ResponseModifyFunc) proxy.Respon
|
||||
return nil
|
||||
}
|
||||
|
||||
reqID, _ := res.Request.Context().Value(proxy.ReqIDKey).(int64)
|
||||
if reqID == 0 {
|
||||
reqLogID, ok := res.Request.Context().Value(proxy.ReqLogIDKey).(ulid.ULID)
|
||||
if !ok {
|
||||
return errors.New("reqlog: request is missing ID")
|
||||
}
|
||||
|
||||
@ -213,9 +210,10 @@ func (svc *Service) ResponseModifier(next proxy.ResponseModifyFunc) proxy.Respon
|
||||
}
|
||||
|
||||
res.Body = ioutil.NopCloser(bytes.NewBuffer(body))
|
||||
clone.Body = ioutil.NopCloser(bytes.NewBuffer(body))
|
||||
|
||||
go func() {
|
||||
if _, err := svc.addResponse(context.Background(), reqID, clone, body, now); err != nil {
|
||||
if err := svc.storeResponse(context.Background(), reqLogID, &clone); err != nil {
|
||||
log.Printf("[ERROR] Could not store response log: %v", err)
|
||||
}
|
||||
}()
|
||||
@ -223,33 +221,3 @@ func (svc *Service) ResponseModifier(next proxy.ResponseModifyFunc) proxy.Respon
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (f *FindRequestsFilter) UnmarshalJSON(b []byte) error {
|
||||
var dto struct {
|
||||
OnlyInScope bool
|
||||
RawSearchExpr string
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(b, &dto); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filter := FindRequestsFilter{
|
||||
OnlyInScope: dto.OnlyInScope,
|
||||
RawSearchExpr: dto.RawSearchExpr,
|
||||
}
|
||||
|
||||
if dto.RawSearchExpr != "" {
|
||||
expr, err := search.ParseQuery(dto.RawSearchExpr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filter.SearchExpr = expr
|
||||
}
|
||||
|
||||
*f = filter
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -1,124 +1,43 @@
|
||||
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
|
||||
//go:generate go run github.com/matryer/moq -out repo_mock_test.go -pkg reqlog_test . Repository:RepoMock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/proj"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/oklog/ulid"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/proxy"
|
||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
)
|
||||
|
||||
//nolint:gosec
|
||||
var ulidEntropy = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
//nolint:paralleltest
|
||||
func TestNewService(t *testing.T) {
|
||||
projSvcMock := &ProjServiceMock{
|
||||
OnProjectOpenFunc: func(fn proj.OnProjectOpenFn) {},
|
||||
OnProjectCloseFunc: func(fn proj.OnProjectCloseFn) {},
|
||||
}
|
||||
func TestRequestModifier(t *testing.T) {
|
||||
repoMock := &RepoMock{
|
||||
FindSettingsByModuleFunc: func(_ context.Context, _ string, _ interface{}) error {
|
||||
StoreRequestLogFunc: func(_ context.Context, _ reqlog.RequestLog) 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,
|
||||
Repository: repoMock,
|
||||
Scope: &scope.Scope{},
|
||||
})
|
||||
svc.ActiveProjectID = ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
|
||||
next := func(req *http.Request) {
|
||||
req.Body = ioutil.NopCloser(strings.NewReader("modified body"))
|
||||
req.Body = io.NopCloser(strings.NewReader("modified body"))
|
||||
}
|
||||
reqModFn := svc.RequestModifier(next)
|
||||
req := httptest.NewRequest("GET", "https://example.com/", strings.NewReader("bar"))
|
||||
@ -126,49 +45,54 @@ func TestRequestModifier(t *testing.T) {
|
||||
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)
|
||||
gotCount := len(repoMock.StoreRequestLogCalls())
|
||||
if expCount := 1; expCount != gotCount {
|
||||
t.Fatalf("incorrect `proj.Service.AddRequestLog` calls (expected: %v, got: %v)", expCount, gotCount)
|
||||
}
|
||||
})
|
||||
|
||||
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))
|
||||
exp := reqlog.RequestLog{
|
||||
ID: ulid.ULID{}, // Empty value
|
||||
ProjectID: svc.ActiveProjectID,
|
||||
Method: req.Method,
|
||||
URL: req.URL,
|
||||
Proto: req.Proto,
|
||||
Header: req.Header,
|
||||
Body: []byte("modified body"),
|
||||
}
|
||||
got := repoMock.StoreRequestLogCalls()[0].ReqLog
|
||||
got.ID = ulid.ULID{} // Override to empty value so we can compare against expected value.
|
||||
|
||||
if diff := cmp.Diff(exp, got); diff != "" {
|
||||
t.Fatalf("request log not equal (-exp, +got):\n%v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//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
|
||||
StoreResponseLogFunc: func(_ context.Context, _ ulid.ULID, _ reqlog.ResponseLog) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
svc := reqlog.NewService(reqlog.Config{
|
||||
ProjectService: projSvcMock,
|
||||
Repository: repoMock,
|
||||
Repository: repoMock,
|
||||
})
|
||||
svc.ActiveProjectID = ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
|
||||
next := func(res *http.Response) error {
|
||||
res.Body = ioutil.NopCloser(strings.NewReader("modified body"))
|
||||
res.Body = io.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)))
|
||||
reqLogID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
req = req.WithContext(context.WithValue(req.Context(), proxy.ReqLogIDKey, reqLogID))
|
||||
|
||||
res := &http.Response{
|
||||
Request: req,
|
||||
Body: ioutil.NopCloser(strings.NewReader("bar")),
|
||||
Body: io.NopCloser(strings.NewReader("bar")),
|
||||
}
|
||||
|
||||
if err := resModFn(res); err != nil {
|
||||
@ -178,16 +102,23 @@ func TestResponseModifier(t *testing.T) {
|
||||
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())
|
||||
got := len(repoMock.StoreResponseLogCalls())
|
||||
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))
|
||||
}
|
||||
t.Run("ran next modifier first, before calling repository", func(t *testing.T) {
|
||||
got := repoMock.StoreResponseLogCalls()[0].ResLog.Body
|
||||
if exp := "modified body"; exp != string(got) {
|
||||
t.Fatalf("incorrect `ResponseLog.Body` value (expected: %v, got: %v)", exp, string(got))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("called repository with request log id", func(t *testing.T) {
|
||||
got := repoMock.StoreResponseLogCalls()[0].ReqLogID
|
||||
if exp := reqLogID; exp.Compare(got) != 0 {
|
||||
t.Fatalf("incorrect `reqLogID` argument for `Repository.AddResponseLogCalls` (expected: %v, got: %v)", exp.String(), got.String())
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
235
pkg/reqlog/search.go
Normal file
235
pkg/reqlog/search.go
Normal file
@ -0,0 +1,235 @@
|
||||
package reqlog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
"github.com/dstotijn/hetty/pkg/search"
|
||||
)
|
||||
|
||||
var reqLogSearchKeyFns = map[string]func(rl RequestLog) string{
|
||||
"req.id": func(rl RequestLog) string { return rl.ID.String() },
|
||||
"req.proto": func(rl RequestLog) string { return rl.Proto },
|
||||
"req.url": func(rl RequestLog) string {
|
||||
if rl.URL == nil {
|
||||
return ""
|
||||
}
|
||||
return rl.URL.String()
|
||||
},
|
||||
"req.method": func(rl RequestLog) string { return rl.Method },
|
||||
"req.body": func(rl RequestLog) string { return string(rl.Body) },
|
||||
"req.timestamp": func(rl RequestLog) string { return ulid.Time(rl.ID.Time()).String() },
|
||||
}
|
||||
|
||||
var resLogSearchKeyFns = map[string]func(rl ResponseLog) string{
|
||||
"res.proto": func(rl ResponseLog) string { return rl.Proto },
|
||||
"res.statusCode": func(rl ResponseLog) string { return strconv.Itoa(rl.StatusCode) },
|
||||
"res.statusReason": func(rl ResponseLog) string { return rl.Status },
|
||||
"res.body": func(rl ResponseLog) string { return string(rl.Body) },
|
||||
}
|
||||
|
||||
// TODO: Request and response headers search key functions.
|
||||
|
||||
// Matches returns true if the supplied search expression evaluates to true.
|
||||
func (reqLog RequestLog) Matches(expr search.Expression) (bool, error) {
|
||||
switch e := expr.(type) {
|
||||
case search.PrefixExpression:
|
||||
return reqLog.matchPrefixExpr(e)
|
||||
case search.InfixExpression:
|
||||
return reqLog.matchInfixExpr(e)
|
||||
case search.StringLiteral:
|
||||
return reqLog.matchStringLiteral(e)
|
||||
default:
|
||||
return false, fmt.Errorf("expression type (%T) not supported", expr)
|
||||
}
|
||||
}
|
||||
|
||||
func (reqLog RequestLog) matchPrefixExpr(expr search.PrefixExpression) (bool, error) {
|
||||
switch expr.Operator {
|
||||
case search.TokOpNot:
|
||||
match, err := reqLog.Matches(expr.Right)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return !match, nil
|
||||
default:
|
||||
return false, errors.New("operator is not supported")
|
||||
}
|
||||
}
|
||||
|
||||
func (reqLog RequestLog) matchInfixExpr(expr search.InfixExpression) (bool, error) {
|
||||
switch expr.Operator {
|
||||
case search.TokOpAnd:
|
||||
left, err := reqLog.Matches(expr.Left)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
right, err := reqLog.Matches(expr.Right)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return left && right, nil
|
||||
case search.TokOpOr:
|
||||
left, err := reqLog.Matches(expr.Left)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
right, err := reqLog.Matches(expr.Right)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return left || right, nil
|
||||
}
|
||||
|
||||
left, ok := expr.Left.(search.StringLiteral)
|
||||
if !ok {
|
||||
return false, errors.New("left operand must be a string literal")
|
||||
}
|
||||
|
||||
leftVal := reqLog.getMappedStringLiteral(left.Value)
|
||||
|
||||
if expr.Operator == search.TokOpRe || expr.Operator == search.TokOpNotRe {
|
||||
right, ok := expr.Right.(*regexp.Regexp)
|
||||
if !ok {
|
||||
return false, errors.New("right operand must be a regular expression")
|
||||
}
|
||||
|
||||
switch expr.Operator {
|
||||
case search.TokOpRe:
|
||||
return right.MatchString(leftVal), nil
|
||||
case search.TokOpNotRe:
|
||||
return !right.MatchString(leftVal), nil
|
||||
}
|
||||
}
|
||||
|
||||
right, ok := expr.Right.(search.StringLiteral)
|
||||
if !ok {
|
||||
return false, errors.New("right operand must be a string literal")
|
||||
}
|
||||
|
||||
rightVal := reqLog.getMappedStringLiteral(right.Value)
|
||||
|
||||
switch expr.Operator {
|
||||
case search.TokOpEq:
|
||||
return leftVal == rightVal, nil
|
||||
case search.TokOpNotEq:
|
||||
return leftVal != rightVal, nil
|
||||
case search.TokOpGt:
|
||||
// TODO(?) attempt to parse as int.
|
||||
return leftVal > rightVal, nil
|
||||
case search.TokOpLt:
|
||||
// TODO(?) attempt to parse as int.
|
||||
return leftVal < rightVal, nil
|
||||
case search.TokOpGtEq:
|
||||
// TODO(?) attempt to parse as int.
|
||||
return leftVal >= rightVal, nil
|
||||
case search.TokOpLtEq:
|
||||
// TODO(?) attempt to parse as int.
|
||||
return leftVal <= rightVal, nil
|
||||
default:
|
||||
return false, errors.New("unsupported operator")
|
||||
}
|
||||
}
|
||||
|
||||
func (reqLog RequestLog) getMappedStringLiteral(s string) string {
|
||||
switch {
|
||||
case strings.HasPrefix(s, "req."):
|
||||
fn, ok := reqLogSearchKeyFns[s]
|
||||
if ok {
|
||||
return fn(reqLog)
|
||||
}
|
||||
case strings.HasPrefix(s, "res."):
|
||||
if reqLog.Response == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
fn, ok := resLogSearchKeyFns[s]
|
||||
if ok {
|
||||
return fn(*reqLog.Response)
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (reqLog RequestLog) matchStringLiteral(strLiteral search.StringLiteral) (bool, error) {
|
||||
for _, fn := range reqLogSearchKeyFns {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fn(reqLog)),
|
||||
strings.ToLower(strLiteral.Value),
|
||||
) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
if reqLog.Response != nil {
|
||||
for _, fn := range resLogSearchKeyFns {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fn(*reqLog.Response)),
|
||||
strings.ToLower(strLiteral.Value),
|
||||
) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (reqLog RequestLog) MatchScope(s *scope.Scope) bool {
|
||||
for _, rule := range s.Rules() {
|
||||
if rule.URL != nil && reqLog.URL != nil {
|
||||
if matches := rule.URL.MatchString(reqLog.URL.String()); matches {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for key, values := range reqLog.Header {
|
||||
var keyMatches, valueMatches bool
|
||||
|
||||
if rule.Header.Key != nil {
|
||||
if matches := rule.Header.Key.MatchString(key); matches {
|
||||
keyMatches = true
|
||||
}
|
||||
}
|
||||
|
||||
if rule.Header.Value != nil {
|
||||
for _, value := range values {
|
||||
if matches := rule.Header.Value.MatchString(value); matches {
|
||||
valueMatches = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// When only key or value is set, match on whatever is set.
|
||||
// When both are set, both must match.
|
||||
switch {
|
||||
case rule.Header.Key != nil && rule.Header.Value == nil && keyMatches:
|
||||
return true
|
||||
case rule.Header.Key == nil && rule.Header.Value != nil && valueMatches:
|
||||
return true
|
||||
case rule.Header.Key != nil && rule.Header.Value != nil && keyMatches && valueMatches:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if rule.Body != nil {
|
||||
if matches := rule.Body.Match(reqLog.Body); matches {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
203
pkg/reqlog/search_test.go
Normal file
203
pkg/reqlog/search_test.go
Normal file
@ -0,0 +1,203 @@
|
||||
package reqlog_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||
"github.com/dstotijn/hetty/pkg/search"
|
||||
)
|
||||
|
||||
func TestRequestLogMatch(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
requestLog reqlog.RequestLog
|
||||
expectedMatch bool
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "infix expression, equal operator, match",
|
||||
query: "req.body = foo",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "infix expression, not equal operator, match",
|
||||
query: "req.body != bar",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "infix expression, greater than operator, match",
|
||||
query: "req.body > a",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("b"),
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "infix expression, less than operator, match",
|
||||
query: "req.body < b",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("a"),
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "infix expression, greater than or equal operator, match greater than",
|
||||
query: "req.body >= a",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("b"),
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "infix expression, greater than or equal operator, match equal",
|
||||
query: "req.body >= a",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("a"),
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "infix expression, less than or equal operator, match less than",
|
||||
query: "req.body <= b",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("a"),
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "infix expression, less than or equal operator, match equal",
|
||||
query: "req.body <= b",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("b"),
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "infix expression, regular expression operator, match",
|
||||
query: `req.body =~ "^foo(.*)$"`,
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("foobar"),
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "infix expression, negate regular expression operator, match",
|
||||
query: `req.body !~ "^foo(.*)$"`,
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("xoobar"),
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "infix expression, and operator, match",
|
||||
query: "req.body = bar AND res.body = yolo",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("bar"),
|
||||
Response: &reqlog.ResponseLog{
|
||||
Body: []byte("yolo"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "infix expression, or operator, match",
|
||||
query: "req.body = bar OR res.body = yolo",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("foo"),
|
||||
Response: &reqlog.ResponseLog{
|
||||
Body: []byte("yolo"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "prefix expression, not operator, match",
|
||||
query: "NOT (req.body = bar)",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "string literal expression, match in request log",
|
||||
query: "foo",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "string literal expression, no match",
|
||||
query: "foo",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("bar"),
|
||||
},
|
||||
expectedMatch: false,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "string literal expression, match in response log",
|
||||
query: "foo",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Response: &reqlog.ResponseLog{
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
searchExpr, err := search.ParseQuery(tt.query)
|
||||
assertError(t, nil, err)
|
||||
|
||||
got, err := tt.requestLog.Matches(searchExpr)
|
||||
assertError(t, tt.expectedError, err)
|
||||
|
||||
if tt.expectedMatch != got {
|
||||
t.Errorf("expected match result: %v, got: %v", tt.expectedMatch, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func assertError(t *testing.T, exp, got error) {
|
||||
t.Helper()
|
||||
|
||||
switch {
|
||||
case exp == nil && got != nil:
|
||||
t.Fatalf("expected: nil, got: %v", got)
|
||||
case exp != nil && got == nil:
|
||||
t.Fatalf("expected: %v, got: nil", exp.Error())
|
||||
case exp != nil && got != nil && exp.Error() != got.Error():
|
||||
t.Fatalf("expected: %v, got: %v", exp.Error(), got.Error())
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user