mirror of
https://github.com/dstotijn/hetty.git
synced 2025-07-01 18:47:29 -04:00
Add Sender module
This commit is contained in:
18
pkg/sender/repo.go
Normal file
18
pkg/sender/repo.go
Normal file
@ -0,0 +1,18 @@
|
||||
package sender
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
)
|
||||
|
||||
type Repository interface {
|
||||
FindSenderRequestByID(ctx context.Context, id ulid.ULID) (Request, error)
|
||||
FindSenderRequests(ctx context.Context, filter FindRequestsFilter, scope *scope.Scope) ([]Request, error)
|
||||
StoreSenderRequest(ctx context.Context, req Request) error
|
||||
StoreResponseLog(ctx context.Context, reqLogID ulid.ULID, resLog reqlog.ResponseLog) error
|
||||
DeleteSenderRequests(ctx context.Context, projectID ulid.ULID) error
|
||||
}
|
292
pkg/sender/repo_mock_test.go
Normal file
292
pkg/sender/repo_mock_test.go
Normal file
@ -0,0 +1,292 @@
|
||||
// Code generated by moq; DO NOT EDIT.
|
||||
// github.com/matryer/moq
|
||||
|
||||
package sender_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
"github.com/dstotijn/hetty/pkg/sender"
|
||||
"github.com/oklog/ulid"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Ensure, that RepoMock does implement sender.Repository.
|
||||
// If this is not the case, regenerate this file with moq.
|
||||
var _ sender.Repository = &RepoMock{}
|
||||
|
||||
// RepoMock is a mock implementation of sender.Repository.
|
||||
//
|
||||
// func TestSomethingThatUsesRepository(t *testing.T) {
|
||||
//
|
||||
// // make and configure a mocked sender.Repository
|
||||
// mockedRepository := &RepoMock{
|
||||
// DeleteSenderRequestsFunc: func(ctx context.Context, projectID ulid.ULID) error {
|
||||
// panic("mock out the DeleteSenderRequests method")
|
||||
// },
|
||||
// FindSenderRequestByIDFunc: func(ctx context.Context, id ulid.ULID) (sender.Request, error) {
|
||||
// panic("mock out the FindSenderRequestByID method")
|
||||
// },
|
||||
// FindSenderRequestsFunc: func(ctx context.Context, filter sender.FindRequestsFilter, scopeMoqParam *scope.Scope) ([]sender.Request, error) {
|
||||
// panic("mock out the FindSenderRequests method")
|
||||
// },
|
||||
// StoreResponseLogFunc: func(ctx context.Context, reqLogID ulid.ULID, resLog reqlog.ResponseLog) error {
|
||||
// panic("mock out the StoreResponseLog method")
|
||||
// },
|
||||
// StoreSenderRequestFunc: func(ctx context.Context, req sender.Request) error {
|
||||
// panic("mock out the StoreSenderRequest method")
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// // use mockedRepository in code that requires sender.Repository
|
||||
// // and then make assertions.
|
||||
//
|
||||
// }
|
||||
type RepoMock struct {
|
||||
// DeleteSenderRequestsFunc mocks the DeleteSenderRequests method.
|
||||
DeleteSenderRequestsFunc func(ctx context.Context, projectID ulid.ULID) error
|
||||
|
||||
// FindSenderRequestByIDFunc mocks the FindSenderRequestByID method.
|
||||
FindSenderRequestByIDFunc func(ctx context.Context, id ulid.ULID) (sender.Request, error)
|
||||
|
||||
// FindSenderRequestsFunc mocks the FindSenderRequests method.
|
||||
FindSenderRequestsFunc func(ctx context.Context, filter sender.FindRequestsFilter, scopeMoqParam *scope.Scope) ([]sender.Request, error)
|
||||
|
||||
// StoreResponseLogFunc mocks the StoreResponseLog method.
|
||||
StoreResponseLogFunc func(ctx context.Context, reqLogID ulid.ULID, resLog reqlog.ResponseLog) error
|
||||
|
||||
// StoreSenderRequestFunc mocks the StoreSenderRequest method.
|
||||
StoreSenderRequestFunc func(ctx context.Context, req sender.Request) error
|
||||
|
||||
// calls tracks calls to the methods.
|
||||
calls struct {
|
||||
// DeleteSenderRequests holds details about calls to the DeleteSenderRequests method.
|
||||
DeleteSenderRequests []struct {
|
||||
// Ctx is the ctx argument value.
|
||||
Ctx context.Context
|
||||
// ProjectID is the projectID argument value.
|
||||
ProjectID ulid.ULID
|
||||
}
|
||||
// FindSenderRequestByID holds details about calls to the FindSenderRequestByID method.
|
||||
FindSenderRequestByID []struct {
|
||||
// Ctx is the ctx argument value.
|
||||
Ctx context.Context
|
||||
// ID is the id argument value.
|
||||
ID ulid.ULID
|
||||
}
|
||||
// FindSenderRequests holds details about calls to the FindSenderRequests method.
|
||||
FindSenderRequests []struct {
|
||||
// Ctx is the ctx argument value.
|
||||
Ctx context.Context
|
||||
// Filter is the filter argument value.
|
||||
Filter sender.FindRequestsFilter
|
||||
// ScopeMoqParam is the scopeMoqParam argument value.
|
||||
ScopeMoqParam *scope.Scope
|
||||
}
|
||||
// StoreResponseLog holds details about calls to the StoreResponseLog method.
|
||||
StoreResponseLog []struct {
|
||||
// Ctx is the ctx argument value.
|
||||
Ctx context.Context
|
||||
// ReqLogID is the reqLogID argument value.
|
||||
ReqLogID ulid.ULID
|
||||
// ResLog is the resLog argument value.
|
||||
ResLog reqlog.ResponseLog
|
||||
}
|
||||
// StoreSenderRequest holds details about calls to the StoreSenderRequest method.
|
||||
StoreSenderRequest []struct {
|
||||
// Ctx is the ctx argument value.
|
||||
Ctx context.Context
|
||||
// Req is the req argument value.
|
||||
Req sender.Request
|
||||
}
|
||||
}
|
||||
lockDeleteSenderRequests sync.RWMutex
|
||||
lockFindSenderRequestByID sync.RWMutex
|
||||
lockFindSenderRequests sync.RWMutex
|
||||
lockStoreResponseLog sync.RWMutex
|
||||
lockStoreSenderRequest sync.RWMutex
|
||||
}
|
||||
|
||||
// DeleteSenderRequests calls DeleteSenderRequestsFunc.
|
||||
func (mock *RepoMock) DeleteSenderRequests(ctx context.Context, projectID ulid.ULID) error {
|
||||
if mock.DeleteSenderRequestsFunc == nil {
|
||||
panic("RepoMock.DeleteSenderRequestsFunc: method is nil but Repository.DeleteSenderRequests was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
Ctx context.Context
|
||||
ProjectID ulid.ULID
|
||||
}{
|
||||
Ctx: ctx,
|
||||
ProjectID: projectID,
|
||||
}
|
||||
mock.lockDeleteSenderRequests.Lock()
|
||||
mock.calls.DeleteSenderRequests = append(mock.calls.DeleteSenderRequests, callInfo)
|
||||
mock.lockDeleteSenderRequests.Unlock()
|
||||
return mock.DeleteSenderRequestsFunc(ctx, projectID)
|
||||
}
|
||||
|
||||
// DeleteSenderRequestsCalls gets all the calls that were made to DeleteSenderRequests.
|
||||
// Check the length with:
|
||||
// len(mockedRepository.DeleteSenderRequestsCalls())
|
||||
func (mock *RepoMock) DeleteSenderRequestsCalls() []struct {
|
||||
Ctx context.Context
|
||||
ProjectID ulid.ULID
|
||||
} {
|
||||
var calls []struct {
|
||||
Ctx context.Context
|
||||
ProjectID ulid.ULID
|
||||
}
|
||||
mock.lockDeleteSenderRequests.RLock()
|
||||
calls = mock.calls.DeleteSenderRequests
|
||||
mock.lockDeleteSenderRequests.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// FindSenderRequestByID calls FindSenderRequestByIDFunc.
|
||||
func (mock *RepoMock) FindSenderRequestByID(ctx context.Context, id ulid.ULID) (sender.Request, error) {
|
||||
if mock.FindSenderRequestByIDFunc == nil {
|
||||
panic("RepoMock.FindSenderRequestByIDFunc: method is nil but Repository.FindSenderRequestByID was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
Ctx context.Context
|
||||
ID ulid.ULID
|
||||
}{
|
||||
Ctx: ctx,
|
||||
ID: id,
|
||||
}
|
||||
mock.lockFindSenderRequestByID.Lock()
|
||||
mock.calls.FindSenderRequestByID = append(mock.calls.FindSenderRequestByID, callInfo)
|
||||
mock.lockFindSenderRequestByID.Unlock()
|
||||
return mock.FindSenderRequestByIDFunc(ctx, id)
|
||||
}
|
||||
|
||||
// FindSenderRequestByIDCalls gets all the calls that were made to FindSenderRequestByID.
|
||||
// Check the length with:
|
||||
// len(mockedRepository.FindSenderRequestByIDCalls())
|
||||
func (mock *RepoMock) FindSenderRequestByIDCalls() []struct {
|
||||
Ctx context.Context
|
||||
ID ulid.ULID
|
||||
} {
|
||||
var calls []struct {
|
||||
Ctx context.Context
|
||||
ID ulid.ULID
|
||||
}
|
||||
mock.lockFindSenderRequestByID.RLock()
|
||||
calls = mock.calls.FindSenderRequestByID
|
||||
mock.lockFindSenderRequestByID.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// FindSenderRequests calls FindSenderRequestsFunc.
|
||||
func (mock *RepoMock) FindSenderRequests(ctx context.Context, filter sender.FindRequestsFilter, scopeMoqParam *scope.Scope) ([]sender.Request, error) {
|
||||
if mock.FindSenderRequestsFunc == nil {
|
||||
panic("RepoMock.FindSenderRequestsFunc: method is nil but Repository.FindSenderRequests was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
Ctx context.Context
|
||||
Filter sender.FindRequestsFilter
|
||||
ScopeMoqParam *scope.Scope
|
||||
}{
|
||||
Ctx: ctx,
|
||||
Filter: filter,
|
||||
ScopeMoqParam: scopeMoqParam,
|
||||
}
|
||||
mock.lockFindSenderRequests.Lock()
|
||||
mock.calls.FindSenderRequests = append(mock.calls.FindSenderRequests, callInfo)
|
||||
mock.lockFindSenderRequests.Unlock()
|
||||
return mock.FindSenderRequestsFunc(ctx, filter, scopeMoqParam)
|
||||
}
|
||||
|
||||
// FindSenderRequestsCalls gets all the calls that were made to FindSenderRequests.
|
||||
// Check the length with:
|
||||
// len(mockedRepository.FindSenderRequestsCalls())
|
||||
func (mock *RepoMock) FindSenderRequestsCalls() []struct {
|
||||
Ctx context.Context
|
||||
Filter sender.FindRequestsFilter
|
||||
ScopeMoqParam *scope.Scope
|
||||
} {
|
||||
var calls []struct {
|
||||
Ctx context.Context
|
||||
Filter sender.FindRequestsFilter
|
||||
ScopeMoqParam *scope.Scope
|
||||
}
|
||||
mock.lockFindSenderRequests.RLock()
|
||||
calls = mock.calls.FindSenderRequests
|
||||
mock.lockFindSenderRequests.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// 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
|
||||
ReqLogID ulid.ULID
|
||||
ResLog reqlog.ResponseLog
|
||||
}{
|
||||
Ctx: ctx,
|
||||
ReqLogID: reqLogID,
|
||||
ResLog: resLog,
|
||||
}
|
||||
mock.lockStoreResponseLog.Lock()
|
||||
mock.calls.StoreResponseLog = append(mock.calls.StoreResponseLog, callInfo)
|
||||
mock.lockStoreResponseLog.Unlock()
|
||||
return mock.StoreResponseLogFunc(ctx, reqLogID, resLog)
|
||||
}
|
||||
|
||||
// StoreResponseLogCalls gets all the calls that were made to StoreResponseLog.
|
||||
// Check the length with:
|
||||
// len(mockedRepository.StoreResponseLogCalls())
|
||||
func (mock *RepoMock) StoreResponseLogCalls() []struct {
|
||||
Ctx context.Context
|
||||
ReqLogID ulid.ULID
|
||||
ResLog reqlog.ResponseLog
|
||||
} {
|
||||
var calls []struct {
|
||||
Ctx context.Context
|
||||
ReqLogID ulid.ULID
|
||||
ResLog reqlog.ResponseLog
|
||||
}
|
||||
mock.lockStoreResponseLog.RLock()
|
||||
calls = mock.calls.StoreResponseLog
|
||||
mock.lockStoreResponseLog.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// StoreSenderRequest calls StoreSenderRequestFunc.
|
||||
func (mock *RepoMock) StoreSenderRequest(ctx context.Context, req sender.Request) error {
|
||||
if mock.StoreSenderRequestFunc == nil {
|
||||
panic("RepoMock.StoreSenderRequestFunc: method is nil but Repository.StoreSenderRequest was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
Ctx context.Context
|
||||
Req sender.Request
|
||||
}{
|
||||
Ctx: ctx,
|
||||
Req: req,
|
||||
}
|
||||
mock.lockStoreSenderRequest.Lock()
|
||||
mock.calls.StoreSenderRequest = append(mock.calls.StoreSenderRequest, callInfo)
|
||||
mock.lockStoreSenderRequest.Unlock()
|
||||
return mock.StoreSenderRequestFunc(ctx, req)
|
||||
}
|
||||
|
||||
// StoreSenderRequestCalls gets all the calls that were made to StoreSenderRequest.
|
||||
// Check the length with:
|
||||
// len(mockedRepository.StoreSenderRequestCalls())
|
||||
func (mock *RepoMock) StoreSenderRequestCalls() []struct {
|
||||
Ctx context.Context
|
||||
Req sender.Request
|
||||
} {
|
||||
var calls []struct {
|
||||
Ctx context.Context
|
||||
Req sender.Request
|
||||
}
|
||||
mock.lockStoreSenderRequest.RLock()
|
||||
calls = mock.calls.StoreSenderRequest
|
||||
mock.lockStoreSenderRequest.RUnlock()
|
||||
return calls
|
||||
}
|
498
pkg/sender/reqlog_mock_test.go
Normal file
498
pkg/sender/reqlog_mock_test.go
Normal file
@ -0,0 +1,498 @@
|
||||
// Code generated by moq; DO NOT EDIT.
|
||||
// github.com/matryer/moq
|
||||
|
||||
package sender_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/dstotijn/hetty/pkg/proxy"
|
||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||
"github.com/oklog/ulid"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Ensure, that ReqLogServiceMock does implement reqlog.Service.
|
||||
// If this is not the case, regenerate this file with moq.
|
||||
var _ reqlog.Service = &ReqLogServiceMock{}
|
||||
|
||||
// ReqLogServiceMock is a mock implementation of reqlog.Service.
|
||||
//
|
||||
// func TestSomethingThatUsesService(t *testing.T) {
|
||||
//
|
||||
// // make and configure a mocked reqlog.Service
|
||||
// mockedService := &ReqLogServiceMock{
|
||||
// ActiveProjectIDFunc: func() ulid.ULID {
|
||||
// panic("mock out the ActiveProjectID method")
|
||||
// },
|
||||
// BypassOutOfScopeRequestsFunc: func() bool {
|
||||
// panic("mock out the BypassOutOfScopeRequests method")
|
||||
// },
|
||||
// ClearRequestsFunc: func(ctx context.Context, projectID ulid.ULID) error {
|
||||
// panic("mock out the ClearRequests method")
|
||||
// },
|
||||
// FindReqsFilterFunc: func() reqlog.FindRequestsFilter {
|
||||
// panic("mock out the FindReqsFilter method")
|
||||
// },
|
||||
// FindRequestLogByIDFunc: func(ctx context.Context, id ulid.ULID) (reqlog.RequestLog, error) {
|
||||
// panic("mock out the FindRequestLogByID method")
|
||||
// },
|
||||
// FindRequestsFunc: func(ctx context.Context) ([]reqlog.RequestLog, error) {
|
||||
// panic("mock out the FindRequests method")
|
||||
// },
|
||||
// RequestModifierFunc: func(next proxy.RequestModifyFunc) proxy.RequestModifyFunc {
|
||||
// panic("mock out the RequestModifier method")
|
||||
// },
|
||||
// ResponseModifierFunc: func(next proxy.ResponseModifyFunc) proxy.ResponseModifyFunc {
|
||||
// panic("mock out the ResponseModifier method")
|
||||
// },
|
||||
// SetActiveProjectIDFunc: func(id ulid.ULID) {
|
||||
// panic("mock out the SetActiveProjectID method")
|
||||
// },
|
||||
// SetBypassOutOfScopeRequestsFunc: func(b bool) {
|
||||
// panic("mock out the SetBypassOutOfScopeRequests method")
|
||||
// },
|
||||
// SetFindReqsFilterFunc: func(filter reqlog.FindRequestsFilter) {
|
||||
// panic("mock out the SetFindReqsFilter method")
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// // use mockedService in code that requires reqlog.Service
|
||||
// // and then make assertions.
|
||||
//
|
||||
// }
|
||||
type ReqLogServiceMock struct {
|
||||
// ActiveProjectIDFunc mocks the ActiveProjectID method.
|
||||
ActiveProjectIDFunc func() ulid.ULID
|
||||
|
||||
// BypassOutOfScopeRequestsFunc mocks the BypassOutOfScopeRequests method.
|
||||
BypassOutOfScopeRequestsFunc func() bool
|
||||
|
||||
// ClearRequestsFunc mocks the ClearRequests method.
|
||||
ClearRequestsFunc func(ctx context.Context, projectID ulid.ULID) error
|
||||
|
||||
// FindReqsFilterFunc mocks the FindReqsFilter method.
|
||||
FindReqsFilterFunc func() reqlog.FindRequestsFilter
|
||||
|
||||
// FindRequestLogByIDFunc mocks the FindRequestLogByID method.
|
||||
FindRequestLogByIDFunc func(ctx context.Context, id ulid.ULID) (reqlog.RequestLog, error)
|
||||
|
||||
// FindRequestsFunc mocks the FindRequests method.
|
||||
FindRequestsFunc func(ctx context.Context) ([]reqlog.RequestLog, error)
|
||||
|
||||
// RequestModifierFunc mocks the RequestModifier method.
|
||||
RequestModifierFunc func(next proxy.RequestModifyFunc) proxy.RequestModifyFunc
|
||||
|
||||
// ResponseModifierFunc mocks the ResponseModifier method.
|
||||
ResponseModifierFunc func(next proxy.ResponseModifyFunc) proxy.ResponseModifyFunc
|
||||
|
||||
// SetActiveProjectIDFunc mocks the SetActiveProjectID method.
|
||||
SetActiveProjectIDFunc func(id ulid.ULID)
|
||||
|
||||
// SetBypassOutOfScopeRequestsFunc mocks the SetBypassOutOfScopeRequests method.
|
||||
SetBypassOutOfScopeRequestsFunc func(b bool)
|
||||
|
||||
// SetFindReqsFilterFunc mocks the SetFindReqsFilter method.
|
||||
SetFindReqsFilterFunc func(filter reqlog.FindRequestsFilter)
|
||||
|
||||
// calls tracks calls to the methods.
|
||||
calls struct {
|
||||
// ActiveProjectID holds details about calls to the ActiveProjectID method.
|
||||
ActiveProjectID []struct {
|
||||
}
|
||||
// BypassOutOfScopeRequests holds details about calls to the BypassOutOfScopeRequests method.
|
||||
BypassOutOfScopeRequests []struct {
|
||||
}
|
||||
// ClearRequests holds details about calls to the ClearRequests method.
|
||||
ClearRequests []struct {
|
||||
// Ctx is the ctx argument value.
|
||||
Ctx context.Context
|
||||
// ProjectID is the projectID argument value.
|
||||
ProjectID ulid.ULID
|
||||
}
|
||||
// FindReqsFilter holds details about calls to the FindReqsFilter method.
|
||||
FindReqsFilter []struct {
|
||||
}
|
||||
// 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 ulid.ULID
|
||||
}
|
||||
// FindRequests holds details about calls to the FindRequests method.
|
||||
FindRequests []struct {
|
||||
// Ctx is the ctx argument value.
|
||||
Ctx context.Context
|
||||
}
|
||||
// RequestModifier holds details about calls to the RequestModifier method.
|
||||
RequestModifier []struct {
|
||||
// Next is the next argument value.
|
||||
Next proxy.RequestModifyFunc
|
||||
}
|
||||
// ResponseModifier holds details about calls to the ResponseModifier method.
|
||||
ResponseModifier []struct {
|
||||
// Next is the next argument value.
|
||||
Next proxy.ResponseModifyFunc
|
||||
}
|
||||
// SetActiveProjectID holds details about calls to the SetActiveProjectID method.
|
||||
SetActiveProjectID []struct {
|
||||
// ID is the id argument value.
|
||||
ID ulid.ULID
|
||||
}
|
||||
// SetBypassOutOfScopeRequests holds details about calls to the SetBypassOutOfScopeRequests method.
|
||||
SetBypassOutOfScopeRequests []struct {
|
||||
// B is the b argument value.
|
||||
B bool
|
||||
}
|
||||
// SetFindReqsFilter holds details about calls to the SetFindReqsFilter method.
|
||||
SetFindReqsFilter []struct {
|
||||
// Filter is the filter argument value.
|
||||
Filter reqlog.FindRequestsFilter
|
||||
}
|
||||
}
|
||||
lockActiveProjectID sync.RWMutex
|
||||
lockBypassOutOfScopeRequests sync.RWMutex
|
||||
lockClearRequests sync.RWMutex
|
||||
lockFindReqsFilter sync.RWMutex
|
||||
lockFindRequestLogByID sync.RWMutex
|
||||
lockFindRequests sync.RWMutex
|
||||
lockRequestModifier sync.RWMutex
|
||||
lockResponseModifier sync.RWMutex
|
||||
lockSetActiveProjectID sync.RWMutex
|
||||
lockSetBypassOutOfScopeRequests sync.RWMutex
|
||||
lockSetFindReqsFilter sync.RWMutex
|
||||
}
|
||||
|
||||
// ActiveProjectID calls ActiveProjectIDFunc.
|
||||
func (mock *ReqLogServiceMock) ActiveProjectID() ulid.ULID {
|
||||
if mock.ActiveProjectIDFunc == nil {
|
||||
panic("ReqLogServiceMock.ActiveProjectIDFunc: method is nil but Service.ActiveProjectID was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
}{}
|
||||
mock.lockActiveProjectID.Lock()
|
||||
mock.calls.ActiveProjectID = append(mock.calls.ActiveProjectID, callInfo)
|
||||
mock.lockActiveProjectID.Unlock()
|
||||
return mock.ActiveProjectIDFunc()
|
||||
}
|
||||
|
||||
// ActiveProjectIDCalls gets all the calls that were made to ActiveProjectID.
|
||||
// Check the length with:
|
||||
// len(mockedService.ActiveProjectIDCalls())
|
||||
func (mock *ReqLogServiceMock) ActiveProjectIDCalls() []struct {
|
||||
} {
|
||||
var calls []struct {
|
||||
}
|
||||
mock.lockActiveProjectID.RLock()
|
||||
calls = mock.calls.ActiveProjectID
|
||||
mock.lockActiveProjectID.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// BypassOutOfScopeRequests calls BypassOutOfScopeRequestsFunc.
|
||||
func (mock *ReqLogServiceMock) BypassOutOfScopeRequests() bool {
|
||||
if mock.BypassOutOfScopeRequestsFunc == nil {
|
||||
panic("ReqLogServiceMock.BypassOutOfScopeRequestsFunc: method is nil but Service.BypassOutOfScopeRequests was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
}{}
|
||||
mock.lockBypassOutOfScopeRequests.Lock()
|
||||
mock.calls.BypassOutOfScopeRequests = append(mock.calls.BypassOutOfScopeRequests, callInfo)
|
||||
mock.lockBypassOutOfScopeRequests.Unlock()
|
||||
return mock.BypassOutOfScopeRequestsFunc()
|
||||
}
|
||||
|
||||
// BypassOutOfScopeRequestsCalls gets all the calls that were made to BypassOutOfScopeRequests.
|
||||
// Check the length with:
|
||||
// len(mockedService.BypassOutOfScopeRequestsCalls())
|
||||
func (mock *ReqLogServiceMock) BypassOutOfScopeRequestsCalls() []struct {
|
||||
} {
|
||||
var calls []struct {
|
||||
}
|
||||
mock.lockBypassOutOfScopeRequests.RLock()
|
||||
calls = mock.calls.BypassOutOfScopeRequests
|
||||
mock.lockBypassOutOfScopeRequests.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// ClearRequests calls ClearRequestsFunc.
|
||||
func (mock *ReqLogServiceMock) ClearRequests(ctx context.Context, projectID ulid.ULID) error {
|
||||
if mock.ClearRequestsFunc == nil {
|
||||
panic("ReqLogServiceMock.ClearRequestsFunc: method is nil but Service.ClearRequests was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
Ctx context.Context
|
||||
ProjectID ulid.ULID
|
||||
}{
|
||||
Ctx: ctx,
|
||||
ProjectID: projectID,
|
||||
}
|
||||
mock.lockClearRequests.Lock()
|
||||
mock.calls.ClearRequests = append(mock.calls.ClearRequests, callInfo)
|
||||
mock.lockClearRequests.Unlock()
|
||||
return mock.ClearRequestsFunc(ctx, projectID)
|
||||
}
|
||||
|
||||
// ClearRequestsCalls gets all the calls that were made to ClearRequests.
|
||||
// Check the length with:
|
||||
// len(mockedService.ClearRequestsCalls())
|
||||
func (mock *ReqLogServiceMock) ClearRequestsCalls() []struct {
|
||||
Ctx context.Context
|
||||
ProjectID ulid.ULID
|
||||
} {
|
||||
var calls []struct {
|
||||
Ctx context.Context
|
||||
ProjectID ulid.ULID
|
||||
}
|
||||
mock.lockClearRequests.RLock()
|
||||
calls = mock.calls.ClearRequests
|
||||
mock.lockClearRequests.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// FindReqsFilter calls FindReqsFilterFunc.
|
||||
func (mock *ReqLogServiceMock) FindReqsFilter() reqlog.FindRequestsFilter {
|
||||
if mock.FindReqsFilterFunc == nil {
|
||||
panic("ReqLogServiceMock.FindReqsFilterFunc: method is nil but Service.FindReqsFilter was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
}{}
|
||||
mock.lockFindReqsFilter.Lock()
|
||||
mock.calls.FindReqsFilter = append(mock.calls.FindReqsFilter, callInfo)
|
||||
mock.lockFindReqsFilter.Unlock()
|
||||
return mock.FindReqsFilterFunc()
|
||||
}
|
||||
|
||||
// FindReqsFilterCalls gets all the calls that were made to FindReqsFilter.
|
||||
// Check the length with:
|
||||
// len(mockedService.FindReqsFilterCalls())
|
||||
func (mock *ReqLogServiceMock) FindReqsFilterCalls() []struct {
|
||||
} {
|
||||
var calls []struct {
|
||||
}
|
||||
mock.lockFindReqsFilter.RLock()
|
||||
calls = mock.calls.FindReqsFilter
|
||||
mock.lockFindReqsFilter.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// FindRequestLogByID calls FindRequestLogByIDFunc.
|
||||
func (mock *ReqLogServiceMock) FindRequestLogByID(ctx context.Context, id ulid.ULID) (reqlog.RequestLog, error) {
|
||||
if mock.FindRequestLogByIDFunc == nil {
|
||||
panic("ReqLogServiceMock.FindRequestLogByIDFunc: method is nil but Service.FindRequestLogByID was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
Ctx context.Context
|
||||
ID ulid.ULID
|
||||
}{
|
||||
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(mockedService.FindRequestLogByIDCalls())
|
||||
func (mock *ReqLogServiceMock) FindRequestLogByIDCalls() []struct {
|
||||
Ctx context.Context
|
||||
ID ulid.ULID
|
||||
} {
|
||||
var calls []struct {
|
||||
Ctx context.Context
|
||||
ID ulid.ULID
|
||||
}
|
||||
mock.lockFindRequestLogByID.RLock()
|
||||
calls = mock.calls.FindRequestLogByID
|
||||
mock.lockFindRequestLogByID.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// FindRequests calls FindRequestsFunc.
|
||||
func (mock *ReqLogServiceMock) FindRequests(ctx context.Context) ([]reqlog.RequestLog, error) {
|
||||
if mock.FindRequestsFunc == nil {
|
||||
panic("ReqLogServiceMock.FindRequestsFunc: method is nil but Service.FindRequests was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
Ctx context.Context
|
||||
}{
|
||||
Ctx: ctx,
|
||||
}
|
||||
mock.lockFindRequests.Lock()
|
||||
mock.calls.FindRequests = append(mock.calls.FindRequests, callInfo)
|
||||
mock.lockFindRequests.Unlock()
|
||||
return mock.FindRequestsFunc(ctx)
|
||||
}
|
||||
|
||||
// FindRequestsCalls gets all the calls that were made to FindRequests.
|
||||
// Check the length with:
|
||||
// len(mockedService.FindRequestsCalls())
|
||||
func (mock *ReqLogServiceMock) FindRequestsCalls() []struct {
|
||||
Ctx context.Context
|
||||
} {
|
||||
var calls []struct {
|
||||
Ctx context.Context
|
||||
}
|
||||
mock.lockFindRequests.RLock()
|
||||
calls = mock.calls.FindRequests
|
||||
mock.lockFindRequests.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// RequestModifier calls RequestModifierFunc.
|
||||
func (mock *ReqLogServiceMock) RequestModifier(next proxy.RequestModifyFunc) proxy.RequestModifyFunc {
|
||||
if mock.RequestModifierFunc == nil {
|
||||
panic("ReqLogServiceMock.RequestModifierFunc: method is nil but Service.RequestModifier was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
Next proxy.RequestModifyFunc
|
||||
}{
|
||||
Next: next,
|
||||
}
|
||||
mock.lockRequestModifier.Lock()
|
||||
mock.calls.RequestModifier = append(mock.calls.RequestModifier, callInfo)
|
||||
mock.lockRequestModifier.Unlock()
|
||||
return mock.RequestModifierFunc(next)
|
||||
}
|
||||
|
||||
// RequestModifierCalls gets all the calls that were made to RequestModifier.
|
||||
// Check the length with:
|
||||
// len(mockedService.RequestModifierCalls())
|
||||
func (mock *ReqLogServiceMock) RequestModifierCalls() []struct {
|
||||
Next proxy.RequestModifyFunc
|
||||
} {
|
||||
var calls []struct {
|
||||
Next proxy.RequestModifyFunc
|
||||
}
|
||||
mock.lockRequestModifier.RLock()
|
||||
calls = mock.calls.RequestModifier
|
||||
mock.lockRequestModifier.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// ResponseModifier calls ResponseModifierFunc.
|
||||
func (mock *ReqLogServiceMock) ResponseModifier(next proxy.ResponseModifyFunc) proxy.ResponseModifyFunc {
|
||||
if mock.ResponseModifierFunc == nil {
|
||||
panic("ReqLogServiceMock.ResponseModifierFunc: method is nil but Service.ResponseModifier was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
Next proxy.ResponseModifyFunc
|
||||
}{
|
||||
Next: next,
|
||||
}
|
||||
mock.lockResponseModifier.Lock()
|
||||
mock.calls.ResponseModifier = append(mock.calls.ResponseModifier, callInfo)
|
||||
mock.lockResponseModifier.Unlock()
|
||||
return mock.ResponseModifierFunc(next)
|
||||
}
|
||||
|
||||
// ResponseModifierCalls gets all the calls that were made to ResponseModifier.
|
||||
// Check the length with:
|
||||
// len(mockedService.ResponseModifierCalls())
|
||||
func (mock *ReqLogServiceMock) ResponseModifierCalls() []struct {
|
||||
Next proxy.ResponseModifyFunc
|
||||
} {
|
||||
var calls []struct {
|
||||
Next proxy.ResponseModifyFunc
|
||||
}
|
||||
mock.lockResponseModifier.RLock()
|
||||
calls = mock.calls.ResponseModifier
|
||||
mock.lockResponseModifier.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// SetActiveProjectID calls SetActiveProjectIDFunc.
|
||||
func (mock *ReqLogServiceMock) SetActiveProjectID(id ulid.ULID) {
|
||||
if mock.SetActiveProjectIDFunc == nil {
|
||||
panic("ReqLogServiceMock.SetActiveProjectIDFunc: method is nil but Service.SetActiveProjectID was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
ID ulid.ULID
|
||||
}{
|
||||
ID: id,
|
||||
}
|
||||
mock.lockSetActiveProjectID.Lock()
|
||||
mock.calls.SetActiveProjectID = append(mock.calls.SetActiveProjectID, callInfo)
|
||||
mock.lockSetActiveProjectID.Unlock()
|
||||
mock.SetActiveProjectIDFunc(id)
|
||||
}
|
||||
|
||||
// SetActiveProjectIDCalls gets all the calls that were made to SetActiveProjectID.
|
||||
// Check the length with:
|
||||
// len(mockedService.SetActiveProjectIDCalls())
|
||||
func (mock *ReqLogServiceMock) SetActiveProjectIDCalls() []struct {
|
||||
ID ulid.ULID
|
||||
} {
|
||||
var calls []struct {
|
||||
ID ulid.ULID
|
||||
}
|
||||
mock.lockSetActiveProjectID.RLock()
|
||||
calls = mock.calls.SetActiveProjectID
|
||||
mock.lockSetActiveProjectID.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// SetBypassOutOfScopeRequests calls SetBypassOutOfScopeRequestsFunc.
|
||||
func (mock *ReqLogServiceMock) SetBypassOutOfScopeRequests(b bool) {
|
||||
if mock.SetBypassOutOfScopeRequestsFunc == nil {
|
||||
panic("ReqLogServiceMock.SetBypassOutOfScopeRequestsFunc: method is nil but Service.SetBypassOutOfScopeRequests was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
B bool
|
||||
}{
|
||||
B: b,
|
||||
}
|
||||
mock.lockSetBypassOutOfScopeRequests.Lock()
|
||||
mock.calls.SetBypassOutOfScopeRequests = append(mock.calls.SetBypassOutOfScopeRequests, callInfo)
|
||||
mock.lockSetBypassOutOfScopeRequests.Unlock()
|
||||
mock.SetBypassOutOfScopeRequestsFunc(b)
|
||||
}
|
||||
|
||||
// SetBypassOutOfScopeRequestsCalls gets all the calls that were made to SetBypassOutOfScopeRequests.
|
||||
// Check the length with:
|
||||
// len(mockedService.SetBypassOutOfScopeRequestsCalls())
|
||||
func (mock *ReqLogServiceMock) SetBypassOutOfScopeRequestsCalls() []struct {
|
||||
B bool
|
||||
} {
|
||||
var calls []struct {
|
||||
B bool
|
||||
}
|
||||
mock.lockSetBypassOutOfScopeRequests.RLock()
|
||||
calls = mock.calls.SetBypassOutOfScopeRequests
|
||||
mock.lockSetBypassOutOfScopeRequests.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// SetFindReqsFilter calls SetFindReqsFilterFunc.
|
||||
func (mock *ReqLogServiceMock) SetFindReqsFilter(filter reqlog.FindRequestsFilter) {
|
||||
if mock.SetFindReqsFilterFunc == nil {
|
||||
panic("ReqLogServiceMock.SetFindReqsFilterFunc: method is nil but Service.SetFindReqsFilter was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
Filter reqlog.FindRequestsFilter
|
||||
}{
|
||||
Filter: filter,
|
||||
}
|
||||
mock.lockSetFindReqsFilter.Lock()
|
||||
mock.calls.SetFindReqsFilter = append(mock.calls.SetFindReqsFilter, callInfo)
|
||||
mock.lockSetFindReqsFilter.Unlock()
|
||||
mock.SetFindReqsFilterFunc(filter)
|
||||
}
|
||||
|
||||
// SetFindReqsFilterCalls gets all the calls that were made to SetFindReqsFilter.
|
||||
// Check the length with:
|
||||
// len(mockedService.SetFindReqsFilterCalls())
|
||||
func (mock *ReqLogServiceMock) SetFindReqsFilterCalls() []struct {
|
||||
Filter reqlog.FindRequestsFilter
|
||||
} {
|
||||
var calls []struct {
|
||||
Filter reqlog.FindRequestsFilter
|
||||
}
|
||||
mock.lockSetFindReqsFilter.RLock()
|
||||
calls = mock.calls.SetFindReqsFilter
|
||||
mock.lockSetFindReqsFilter.RUnlock()
|
||||
return calls
|
||||
}
|
228
pkg/sender/search.go
Normal file
228
pkg/sender/search.go
Normal file
@ -0,0 +1,228 @@
|
||||
package sender
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
"github.com/dstotijn/hetty/pkg/search"
|
||||
)
|
||||
|
||||
var senderReqSearchKeyFns = map[string]func(req Request) string{
|
||||
"req.id": func(req Request) string { return req.ID.String() },
|
||||
"req.proto": func(req Request) string { return req.Proto },
|
||||
"req.url": func(req Request) string {
|
||||
if req.URL == nil {
|
||||
return ""
|
||||
}
|
||||
return req.URL.String()
|
||||
},
|
||||
"req.method": func(req Request) string { return req.Method },
|
||||
"req.body": func(req Request) string { return string(req.Body) },
|
||||
"req.timestamp": func(req Request) string { return ulid.Time(req.ID.Time()).String() },
|
||||
}
|
||||
|
||||
// TODO: Request and response headers search key functions.
|
||||
|
||||
// Matches returns true if the supplied search expression evaluates to true.
|
||||
func (req Request) Matches(expr search.Expression) (bool, error) {
|
||||
switch e := expr.(type) {
|
||||
case search.PrefixExpression:
|
||||
return req.matchPrefixExpr(e)
|
||||
case search.InfixExpression:
|
||||
return req.matchInfixExpr(e)
|
||||
case search.StringLiteral:
|
||||
return req.matchStringLiteral(e)
|
||||
default:
|
||||
return false, fmt.Errorf("expression type (%T) not supported", expr)
|
||||
}
|
||||
}
|
||||
|
||||
func (req Request) matchPrefixExpr(expr search.PrefixExpression) (bool, error) {
|
||||
switch expr.Operator {
|
||||
case search.TokOpNot:
|
||||
match, err := req.Matches(expr.Right)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return !match, nil
|
||||
default:
|
||||
return false, errors.New("operator is not supported")
|
||||
}
|
||||
}
|
||||
|
||||
func (req Request) matchInfixExpr(expr search.InfixExpression) (bool, error) {
|
||||
switch expr.Operator {
|
||||
case search.TokOpAnd:
|
||||
left, err := req.Matches(expr.Left)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
right, err := req.Matches(expr.Right)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return left && right, nil
|
||||
case search.TokOpOr:
|
||||
left, err := req.Matches(expr.Left)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
right, err := req.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 := req.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 := req.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 (req Request) getMappedStringLiteral(s string) string {
|
||||
switch {
|
||||
case strings.HasPrefix(s, "req."):
|
||||
fn, ok := senderReqSearchKeyFns[s]
|
||||
if ok {
|
||||
return fn(req)
|
||||
}
|
||||
case strings.HasPrefix(s, "res."):
|
||||
if req.Response == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
fn, ok := reqlog.ResLogSearchKeyFns[s]
|
||||
if ok {
|
||||
return fn(*req.Response)
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (req Request) matchStringLiteral(strLiteral search.StringLiteral) (bool, error) {
|
||||
for _, fn := range senderReqSearchKeyFns {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fn(req)),
|
||||
strings.ToLower(strLiteral.Value),
|
||||
) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
if req.Response != nil {
|
||||
for _, fn := range reqlog.ResLogSearchKeyFns {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fn(*req.Response)),
|
||||
strings.ToLower(strLiteral.Value),
|
||||
) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (req Request) MatchScope(s *scope.Scope) bool {
|
||||
for _, rule := range s.Rules() {
|
||||
if rule.URL != nil && req.URL != nil {
|
||||
if matches := rule.URL.MatchString(req.URL.String()); matches {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for key, values := range req.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(req.Body); matches {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
204
pkg/sender/search_test.go
Normal file
204
pkg/sender/search_test.go
Normal file
@ -0,0 +1,204 @@
|
||||
package sender_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||
"github.com/dstotijn/hetty/pkg/search"
|
||||
"github.com/dstotijn/hetty/pkg/sender"
|
||||
)
|
||||
|
||||
func TestRequestLogMatch(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
senderReq sender.Request
|
||||
expectedMatch bool
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "infix expression, equal operator, match",
|
||||
query: "req.body = foo",
|
||||
senderReq: sender.Request{
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "infix expression, not equal operator, match",
|
||||
query: "req.body != bar",
|
||||
senderReq: sender.Request{
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "infix expression, greater than operator, match",
|
||||
query: "req.body > a",
|
||||
senderReq: sender.Request{
|
||||
Body: []byte("b"),
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "infix expression, less than operator, match",
|
||||
query: "req.body < b",
|
||||
senderReq: sender.Request{
|
||||
Body: []byte("a"),
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "infix expression, greater than or equal operator, match greater than",
|
||||
query: "req.body >= a",
|
||||
senderReq: sender.Request{
|
||||
Body: []byte("b"),
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "infix expression, greater than or equal operator, match equal",
|
||||
query: "req.body >= a",
|
||||
senderReq: sender.Request{
|
||||
Body: []byte("a"),
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "infix expression, less than or equal operator, match less than",
|
||||
query: "req.body <= b",
|
||||
senderReq: sender.Request{
|
||||
Body: []byte("a"),
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "infix expression, less than or equal operator, match equal",
|
||||
query: "req.body <= b",
|
||||
senderReq: sender.Request{
|
||||
Body: []byte("b"),
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "infix expression, regular expression operator, match",
|
||||
query: `req.body =~ "^foo(.*)$"`,
|
||||
senderReq: sender.Request{
|
||||
Body: []byte("foobar"),
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "infix expression, negate regular expression operator, match",
|
||||
query: `req.body !~ "^foo(.*)$"`,
|
||||
senderReq: sender.Request{
|
||||
Body: []byte("xoobar"),
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "infix expression, and operator, match",
|
||||
query: "req.body = bar AND res.body = yolo",
|
||||
senderReq: sender.Request{
|
||||
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",
|
||||
senderReq: sender.Request{
|
||||
Body: []byte("foo"),
|
||||
Response: &reqlog.ResponseLog{
|
||||
Body: []byte("yolo"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "prefix expression, not operator, match",
|
||||
query: "NOT (req.body = bar)",
|
||||
senderReq: sender.Request{
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "string literal expression, match in request log",
|
||||
query: "foo",
|
||||
senderReq: sender.Request{
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "string literal expression, no match",
|
||||
query: "foo",
|
||||
senderReq: sender.Request{
|
||||
Body: []byte("bar"),
|
||||
},
|
||||
expectedMatch: false,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "string literal expression, match in response log",
|
||||
query: "foo",
|
||||
senderReq: sender.Request{
|
||||
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.senderReq.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())
|
||||
}
|
||||
}
|
251
pkg/sender/sender.go
Normal file
251
pkg/sender/sender.go
Normal file
@ -0,0 +1,251 @@
|
||||
package sender
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
"github.com/dstotijn/hetty/pkg/search"
|
||||
)
|
||||
|
||||
//nolint:gosec
|
||||
var ulidEntropy = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
var defaultHTTPClient = &http.Client{
|
||||
Transport: &HTTPTransport{},
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
|
||||
var (
|
||||
ErrProjectIDMustBeSet = errors.New("sender: project ID must be set")
|
||||
ErrRequestNotFound = errors.New("sender: request not found")
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
FindRequestByID(ctx context.Context, id ulid.ULID) (Request, error)
|
||||
FindRequests(ctx context.Context) ([]Request, error)
|
||||
CreateOrUpdateRequest(ctx context.Context, req Request) (Request, error)
|
||||
CloneFromRequestLog(ctx context.Context, reqLogID ulid.ULID) (Request, error)
|
||||
DeleteRequests(ctx context.Context, projectID ulid.ULID) error
|
||||
SendRequest(ctx context.Context, id ulid.ULID) (Request, error)
|
||||
SetActiveProjectID(ulid.ULID)
|
||||
SetFindReqsFilter(filter FindRequestsFilter)
|
||||
FindReqsFilter() FindRequestsFilter
|
||||
}
|
||||
|
||||
type service struct {
|
||||
activeProjectID ulid.ULID
|
||||
findReqsFilter FindRequestsFilter
|
||||
scope *scope.Scope
|
||||
repo Repository
|
||||
reqLogSvc reqlog.Service
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
type FindRequestsFilter struct {
|
||||
ProjectID ulid.ULID
|
||||
OnlyInScope bool
|
||||
SearchExpr search.Expression
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Scope *scope.Scope
|
||||
Repository Repository
|
||||
ReqLogService reqlog.Service
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
type SendError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func NewService(cfg Config) Service {
|
||||
svc := &service{
|
||||
repo: cfg.Repository,
|
||||
reqLogSvc: cfg.ReqLogService,
|
||||
httpClient: defaultHTTPClient,
|
||||
scope: cfg.Scope,
|
||||
}
|
||||
|
||||
if cfg.HTTPClient != nil {
|
||||
svc.httpClient = cfg.HTTPClient
|
||||
}
|
||||
|
||||
return svc
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
ID ulid.ULID
|
||||
ProjectID ulid.ULID
|
||||
SourceRequestLogID ulid.ULID
|
||||
|
||||
URL *url.URL
|
||||
Method string
|
||||
Proto string
|
||||
Header http.Header
|
||||
Body []byte
|
||||
|
||||
Response *reqlog.ResponseLog
|
||||
}
|
||||
|
||||
func (svc *service) FindRequestByID(ctx context.Context, id ulid.ULID) (Request, error) {
|
||||
req, err := svc.repo.FindSenderRequestByID(ctx, id)
|
||||
if err != nil {
|
||||
return Request{}, fmt.Errorf("sender: failed to find request: %w", err)
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (svc *service) FindRequests(ctx context.Context) ([]Request, error) {
|
||||
return svc.repo.FindSenderRequests(ctx, svc.findReqsFilter, svc.scope)
|
||||
}
|
||||
|
||||
func (svc *service) CreateOrUpdateRequest(ctx context.Context, req Request) (Request, error) {
|
||||
if svc.activeProjectID.Compare(ulid.ULID{}) == 0 {
|
||||
return Request{}, ErrProjectIDMustBeSet
|
||||
}
|
||||
|
||||
if req.ID.Compare(ulid.ULID{}) == 0 {
|
||||
req.ID = ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
}
|
||||
|
||||
req.ProjectID = svc.activeProjectID
|
||||
|
||||
if req.Method == "" {
|
||||
req.Method = http.MethodGet
|
||||
}
|
||||
|
||||
if req.Proto == "" {
|
||||
req.Proto = HTTPProto2
|
||||
}
|
||||
|
||||
if !isValidProto(req.Proto) {
|
||||
return Request{}, fmt.Errorf("sender: unsupported HTTP protocol: %v", req.Proto)
|
||||
}
|
||||
|
||||
err := svc.repo.StoreSenderRequest(ctx, req)
|
||||
if err != nil {
|
||||
return Request{}, fmt.Errorf("sender: failed to store request: %w", err)
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (svc *service) CloneFromRequestLog(ctx context.Context, reqLogID ulid.ULID) (Request, error) {
|
||||
if svc.activeProjectID.Compare(ulid.ULID{}) == 0 {
|
||||
return Request{}, ErrProjectIDMustBeSet
|
||||
}
|
||||
|
||||
reqLog, err := svc.reqLogSvc.FindRequestLogByID(ctx, reqLogID)
|
||||
if err != nil {
|
||||
return Request{}, fmt.Errorf("sender: failed to find request log: %w", err)
|
||||
}
|
||||
|
||||
req := Request{
|
||||
ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
|
||||
ProjectID: svc.activeProjectID,
|
||||
SourceRequestLogID: reqLogID,
|
||||
Method: reqLog.Method,
|
||||
URL: reqLog.URL,
|
||||
Proto: HTTPProto2, // Attempt HTTP/2.
|
||||
Header: reqLog.Header,
|
||||
Body: reqLog.Body,
|
||||
}
|
||||
|
||||
err = svc.repo.StoreSenderRequest(ctx, req)
|
||||
if err != nil {
|
||||
return Request{}, fmt.Errorf("sender: failed to store request: %w", err)
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (svc *service) SetFindReqsFilter(filter FindRequestsFilter) {
|
||||
svc.findReqsFilter = filter
|
||||
}
|
||||
|
||||
func (svc *service) FindReqsFilter() FindRequestsFilter {
|
||||
return svc.findReqsFilter
|
||||
}
|
||||
|
||||
func (svc *service) SendRequest(ctx context.Context, id ulid.ULID) (Request, error) {
|
||||
req, err := svc.repo.FindSenderRequestByID(ctx, id)
|
||||
if err != nil {
|
||||
return Request{}, fmt.Errorf("sender: failed to find request: %w", err)
|
||||
}
|
||||
|
||||
httpReq, err := parseHTTPRequest(ctx, req)
|
||||
if err != nil {
|
||||
return Request{}, fmt.Errorf("sender: failed to parse HTTP request: %w", err)
|
||||
}
|
||||
|
||||
resLog, err := svc.sendHTTPRequest(httpReq)
|
||||
if err != nil {
|
||||
return Request{}, fmt.Errorf("sender: could not send HTTP request: %w", err)
|
||||
}
|
||||
|
||||
err = svc.repo.StoreResponseLog(ctx, id, resLog)
|
||||
if err != nil {
|
||||
return Request{}, fmt.Errorf("sender: failed to store sender response log: %w", err)
|
||||
}
|
||||
|
||||
req.Response = &resLog
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func parseHTTPRequest(ctx context.Context, req Request) (*http.Request, error) {
|
||||
ctx = context.WithValue(ctx, protoCtxKey{}, req.Proto)
|
||||
|
||||
httpReq, err := http.NewRequestWithContext(ctx, req.Method, req.URL.String(), bytes.NewReader(req.Body))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to construct HTTP request: %w", err)
|
||||
}
|
||||
|
||||
if req.Header != nil {
|
||||
httpReq.Header = req.Header
|
||||
}
|
||||
|
||||
return httpReq, nil
|
||||
}
|
||||
|
||||
func (svc *service) sendHTTPRequest(httpReq *http.Request) (reqlog.ResponseLog, error) {
|
||||
res, err := svc.httpClient.Do(httpReq)
|
||||
if err != nil {
|
||||
return reqlog.ResponseLog{}, &SendError{err}
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
resLog, err := reqlog.ParseHTTPResponse(res)
|
||||
if err != nil {
|
||||
return reqlog.ResponseLog{}, fmt.Errorf("failed to parse http response: %w", err)
|
||||
}
|
||||
|
||||
return resLog, err
|
||||
}
|
||||
|
||||
func (svc *service) SetActiveProjectID(id ulid.ULID) {
|
||||
svc.activeProjectID = id
|
||||
}
|
||||
|
||||
func (svc *service) DeleteRequests(ctx context.Context, projectID ulid.ULID) error {
|
||||
return svc.repo.DeleteSenderRequests(ctx, projectID)
|
||||
}
|
||||
|
||||
func (e SendError) Error() string {
|
||||
return fmt.Sprintf("failed to send HTTP request: %v", e.err)
|
||||
}
|
||||
|
||||
func (e SendError) Unwrap() error {
|
||||
return e.err
|
||||
}
|
289
pkg/sender/sender_test.go
Normal file
289
pkg/sender/sender_test.go
Normal file
@ -0,0 +1,289 @@
|
||||
package sender_test
|
||||
|
||||
//go:generate go run github.com/matryer/moq -out reqlog_mock_test.go -pkg sender_test ../reqlog Service:ReqLogServiceMock
|
||||
//go:generate go run github.com/matryer/moq -out repo_mock_test.go -pkg sender_test . Repository:RepoMock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/oklog/ulid"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||
"github.com/dstotijn/hetty/pkg/sender"
|
||||
)
|
||||
|
||||
//nolint:gosec
|
||||
var ulidEntropy = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
var exampleURL = func() *url.URL {
|
||||
u, err := url.Parse("https://example.com/foobar")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return u
|
||||
}()
|
||||
|
||||
func TestStoreRequest(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("without active project", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
svc := sender.NewService(sender.Config{})
|
||||
|
||||
_, err := svc.CreateOrUpdateRequest(context.Background(), sender.Request{
|
||||
URL: exampleURL,
|
||||
Method: http.MethodPost,
|
||||
Body: []byte("foobar"),
|
||||
})
|
||||
if !errors.Is(err, sender.ErrProjectIDMustBeSet) {
|
||||
t.Fatalf("expected `sender.ErrProjectIDMustBeSet`, got: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("with active project", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
repoMock := &RepoMock{
|
||||
StoreSenderRequestFunc: func(ctx context.Context, req sender.Request) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
svc := sender.NewService(sender.Config{
|
||||
Repository: repoMock,
|
||||
})
|
||||
|
||||
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
svc.SetActiveProjectID(projectID)
|
||||
|
||||
exp := sender.Request{
|
||||
ProjectID: projectID,
|
||||
URL: exampleURL,
|
||||
Method: http.MethodPost,
|
||||
Proto: "HTTP/1.1",
|
||||
Header: http.Header{
|
||||
"X-Foo": []string{"bar"},
|
||||
},
|
||||
Body: []byte("foobar"),
|
||||
}
|
||||
|
||||
got, err := svc.CreateOrUpdateRequest(context.Background(), sender.Request{
|
||||
URL: exampleURL,
|
||||
Method: http.MethodPost,
|
||||
Proto: "HTTP/1.1",
|
||||
Header: http.Header{
|
||||
"X-Foo": []string{"bar"},
|
||||
},
|
||||
Body: []byte("foobar"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error storing request: %v", err)
|
||||
}
|
||||
|
||||
if got.ID.Compare(ulid.ULID{}) == 0 {
|
||||
t.Fatal("expected request ID to be non-empty value")
|
||||
}
|
||||
|
||||
if len(repoMock.StoreSenderRequestCalls()) != 1 {
|
||||
t.Fatal("expected `svc.repo.StoreSenderRequest()` to have been called 1 time")
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(got, repoMock.StoreSenderRequestCalls()[0].Req); diff != "" {
|
||||
t.Fatalf("repo call arg not equal (-exp, +got):\n%v", diff)
|
||||
}
|
||||
|
||||
// Reset ID to make comparison with expected easier.
|
||||
got.ID = ulid.ULID{}
|
||||
|
||||
if diff := cmp.Diff(exp, got); diff != "" {
|
||||
t.Fatalf("request not equal (-exp, +got):\n%v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCloneFromRequestLog(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
reqLogID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
|
||||
t.Run("without active project", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
svc := sender.NewService(sender.Config{})
|
||||
|
||||
_, err := svc.CloneFromRequestLog(context.Background(), reqLogID)
|
||||
if !errors.Is(err, sender.ErrProjectIDMustBeSet) {
|
||||
t.Fatalf("expected `sender.ErrProjectIDMustBeSet`, got: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("with active project", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
reqLog := reqlog.RequestLog{
|
||||
ID: reqLogID,
|
||||
ProjectID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
|
||||
URL: exampleURL,
|
||||
Method: http.MethodPost,
|
||||
Proto: "HTTP/1.1",
|
||||
Header: http.Header{
|
||||
"X-Foo": []string{"bar"},
|
||||
},
|
||||
Body: []byte("foobar"),
|
||||
}
|
||||
|
||||
reqLogMock := &ReqLogServiceMock{
|
||||
FindRequestLogByIDFunc: func(ctx context.Context, id ulid.ULID) (reqlog.RequestLog, error) {
|
||||
return reqLog, nil
|
||||
},
|
||||
}
|
||||
repoMock := &RepoMock{
|
||||
StoreSenderRequestFunc: func(ctx context.Context, req sender.Request) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
svc := sender.NewService(sender.Config{
|
||||
ReqLogService: reqLogMock,
|
||||
Repository: repoMock,
|
||||
})
|
||||
|
||||
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
svc.SetActiveProjectID(projectID)
|
||||
|
||||
exp := sender.Request{
|
||||
SourceRequestLogID: reqLogID,
|
||||
ProjectID: projectID,
|
||||
URL: exampleURL,
|
||||
Method: http.MethodPost,
|
||||
Proto: sender.HTTPProto2,
|
||||
Header: http.Header{
|
||||
"X-Foo": []string{"bar"},
|
||||
},
|
||||
Body: []byte("foobar"),
|
||||
}
|
||||
|
||||
got, err := svc.CloneFromRequestLog(context.Background(), reqLogID)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error cloning from request log: %v", err)
|
||||
}
|
||||
|
||||
if len(reqLogMock.FindRequestLogByIDCalls()) != 1 {
|
||||
t.Fatal("expected `svc.reqLogSvc.FindRequestLogByID()` to have been called 1 time")
|
||||
}
|
||||
|
||||
if got := reqLogMock.FindRequestLogByIDCalls()[0].ID; reqLogID.Compare(got) != 0 {
|
||||
t.Fatalf("reqlog service call arg `id` not equal (expected: %q, got: %q)", reqLogID, got)
|
||||
}
|
||||
|
||||
if got.ID.Compare(ulid.ULID{}) == 0 {
|
||||
t.Fatal("expected request ID to be non-empty value")
|
||||
}
|
||||
|
||||
if len(repoMock.StoreSenderRequestCalls()) != 1 {
|
||||
t.Fatal("expected `svc.repo.StoreSenderRequest()` to have been called 1 time")
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(got, repoMock.StoreSenderRequestCalls()[0].Req); diff != "" {
|
||||
t.Fatalf("repo call arg not equal (-exp, +got):\n%v", diff)
|
||||
}
|
||||
|
||||
// Reset ID to make comparison with expected easier.
|
||||
got.ID = ulid.ULID{}
|
||||
|
||||
if diff := cmp.Diff(exp, got); diff != "" {
|
||||
t.Fatalf("request not equal (-exp, +got):\n%v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestSendRequest(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
date := time.Now().Format(http.TimeFormat)
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Foobar", "baz")
|
||||
w.Header().Set("Date", date)
|
||||
fmt.Fprint(w, "baz")
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
tsURL, _ := url.Parse(ts.URL)
|
||||
|
||||
reqID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
req := sender.Request{
|
||||
ID: reqID,
|
||||
ProjectID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
|
||||
URL: tsURL,
|
||||
Method: http.MethodPost,
|
||||
Proto: "HTTP/1.1",
|
||||
Header: http.Header{
|
||||
"X-Foo": []string{"bar"},
|
||||
},
|
||||
Body: []byte("foobar"),
|
||||
}
|
||||
|
||||
repoMock := &RepoMock{
|
||||
FindSenderRequestByIDFunc: func(ctx context.Context, id ulid.ULID) (sender.Request, error) {
|
||||
return req, nil
|
||||
},
|
||||
StoreResponseLogFunc: func(ctx context.Context, reqLogID ulid.ULID, resLog reqlog.ResponseLog) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
svc := sender.NewService(sender.Config{
|
||||
Repository: repoMock,
|
||||
})
|
||||
|
||||
exp := reqlog.ResponseLog{
|
||||
Proto: "HTTP/1.1",
|
||||
StatusCode: http.StatusOK,
|
||||
Status: "200 OK",
|
||||
Header: http.Header{
|
||||
"Content-Length": []string{"3"},
|
||||
"Content-Type": []string{"text/plain; charset=utf-8"},
|
||||
"Date": []string{date},
|
||||
"Foobar": []string{"baz"},
|
||||
},
|
||||
Body: []byte("baz"),
|
||||
}
|
||||
|
||||
got, err := svc.SendRequest(context.Background(), reqID)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error sending request: %v", err)
|
||||
}
|
||||
|
||||
if len(repoMock.FindSenderRequestByIDCalls()) != 1 {
|
||||
t.Fatal("expected `svc.repo.FindSenderRequestByID()` to have been called 1 time")
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(reqID, repoMock.FindSenderRequestByIDCalls()[0].ID); diff != "" {
|
||||
t.Fatalf("call arg `id` for `svc.repo.FindSenderRequestByID()` not equal (-exp, +got):\n%v", diff)
|
||||
}
|
||||
|
||||
if len(repoMock.StoreResponseLogCalls()) != 1 {
|
||||
t.Fatal("expected `svc.repo.StoreResponseLog()` to have been called 1 time")
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(reqID, repoMock.StoreResponseLogCalls()[0].ReqLogID); diff != "" {
|
||||
t.Fatalf("call arg `reqLogID` for `svc.repo.StoreResponseLog()` not equal (-exp, +got):\n%v", diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(exp, repoMock.StoreResponseLogCalls()[0].ResLog); diff != "" {
|
||||
t.Fatalf("call arg `resLog` for `svc.repo.StoreResponseLog()` not equal (-exp, +got):\n%v", diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(repoMock.StoreResponseLogCalls()[0].ResLog, *got.Response); diff != "" {
|
||||
t.Fatalf("returned response log value and persisted value not equal (-exp, +got):\n%v", diff)
|
||||
}
|
||||
}
|
50
pkg/sender/transport.go
Normal file
50
pkg/sender/transport.go
Normal file
@ -0,0 +1,50 @@
|
||||
package sender
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type HTTPTransport struct{}
|
||||
|
||||
type protoCtxKey struct{}
|
||||
|
||||
const (
|
||||
HTTPProto1 = "HTTP/1.1"
|
||||
HTTPProto2 = "HTTP/2.0"
|
||||
)
|
||||
|
||||
// h1OnlyTransport mimics `http.DefaultTransport`, but with HTTP/2 disabled.
|
||||
var h1OnlyTransport = &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).DialContext,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
|
||||
// Disable HTTP/2.
|
||||
TLSNextProto: map[string]func(string, *tls.Conn) http.RoundTripper{},
|
||||
}
|
||||
|
||||
// RountTrip implements http.RoundTripper. Based on a context value on the
|
||||
// HTTP request, it switches between using `http.DefaultTransport` (which attempts
|
||||
// HTTP/2) and a HTTP/1.1 only transport that's based off `http.DefaultTransport`.
|
||||
func (t *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
proto, ok := req.Context().Value(protoCtxKey{}).(string)
|
||||
|
||||
if ok && proto == HTTPProto1 {
|
||||
return h1OnlyTransport.RoundTrip(req)
|
||||
}
|
||||
|
||||
return http.DefaultTransport.RoundTrip(req)
|
||||
}
|
||||
|
||||
func isValidProto(proto string) bool {
|
||||
return proto == HTTPProto1 || proto == HTTPProto2
|
||||
}
|
Reference in New Issue
Block a user