Skip to content

Commit

Permalink
the ability to create custom mock services
Browse files Browse the repository at this point in the history
  • Loading branch information
andrey.suchilov committed Dec 1, 2022
1 parent f19def8 commit 1c1bb7a
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 49 deletions.
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -9,6 +9,7 @@ require (
github.com/kylelemons/godebug v1.1.0
github.com/lib/pq v1.3.0
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/stretchr/testify v1.5.1
github.com/tidwall/gjson v1.6.0
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Expand Up @@ -14,6 +14,8 @@ github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand All @@ -27,6 +29,8 @@ github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 h1:FVCohIoYO7IJoDDVpV2pdq7SgrMH6wHnuTyrdrxJNoY=
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
18 changes: 9 additions & 9 deletions mocks/definition.go
Expand Up @@ -7,27 +7,27 @@ import (
"sync"
)

const callsNoConstraint = -1
const CallsNoConstraint = -1

type definition struct {
type Definition struct {
path string
requestConstraints []verifier
replyStrategy replyStrategy
replyStrategy ReplyStrategy
sync.Mutex
calls int
callsConstraint int
}

func newDefinition(path string, constraints []verifier, strategy replyStrategy, callsConstraint int) *definition {
return &definition{
func NewDefinition(path string, constraints []verifier, strategy ReplyStrategy, callsConstraint int) *Definition {
return &Definition{
path: path,
requestConstraints: constraints,
replyStrategy: strategy,
callsConstraint: callsConstraint,
}
}

func (d *definition) Execute(w http.ResponseWriter, r *http.Request) []error {
func (d *Definition) Execute(w http.ResponseWriter, r *http.Request) []error {
d.Lock()
d.calls++
d.Unlock()
Expand Down Expand Up @@ -55,7 +55,7 @@ func (d *definition) Execute(w http.ResponseWriter, r *http.Request) []error {
return errors
}

func (d *definition) ResetRunningContext() {
func (d *Definition) ResetRunningContext() {
if s, ok := d.replyStrategy.(contextAwareStrategy); ok {
s.ResetRunningContext()
}
Expand All @@ -64,14 +64,14 @@ func (d *definition) ResetRunningContext() {
d.Unlock()
}

func (d *definition) EndRunningContext() []error {
func (d *Definition) EndRunningContext() []error {
d.Lock()
defer d.Unlock()
var errs []error
if s, ok := d.replyStrategy.(contextAwareStrategy); ok {
errs = s.EndRunningContext()
}
if d.callsConstraint != callsNoConstraint && d.calls != d.callsConstraint {
if d.callsConstraint != CallsNoConstraint && d.calls != d.callsConstraint {
err := fmt.Errorf("at path %s: number of calls does not match: expected %d, actual %d",
d.path, d.callsConstraint, d.calls)
errs = append(errs, err)
Expand Down
44 changes: 22 additions & 22 deletions mocks/loader.go
Expand Up @@ -25,18 +25,18 @@ func (l *Loader) Load(mocksDefinition map[string]interface{}) error {
}
def, err := l.loadDefinition("$", definition)
if err != nil {
return fmt.Errorf("unable to load definition for %s: %v", serviceName, err)
return fmt.Errorf("unable to load Definition for %s: %v", serviceName, err)
}
// load the definition into the mock
// load the Definition into the mock
service.SetDefinition(def)
}
return nil
}

func (l *Loader) loadDefinition(path string, rawDef interface{}) (*definition, error) {
func (l *Loader) loadDefinition(path string, rawDef interface{}) (*Definition, error) {
def, ok := rawDef.(map[interface{}]interface{})
if !ok {
return nil, fmt.Errorf("at path %s: definition must be key-values", path)
return nil, fmt.Errorf("at path %s: Definition must be key-values", path)
}

// load request constraints
Expand Down Expand Up @@ -76,7 +76,7 @@ func (l *Loader) loadDefinition(path string, rawDef interface{}) (*definition, e
return nil, err
}

callsConstraint := callsNoConstraint
callsConstraint := CallsNoConstraint
if _, ok = def["calls"]; ok {
if value, ok := def["calls"].(int); ok {
callsConstraint = value
Expand All @@ -87,10 +87,10 @@ func (l *Loader) loadDefinition(path string, rawDef interface{}) (*definition, e
return nil, err
}

return newDefinition(path, requestConstraints, replyStrategy, callsConstraint), nil
return NewDefinition(path, requestConstraints, replyStrategy, callsConstraint), nil
}

func (l *Loader) loadStrategy(path, strategyName string, definition map[interface{}]interface{}, ak *[]string) (replyStrategy, error) {
func (l *Loader) loadStrategy(path, strategyName string, definition map[interface{}]interface{}, ak *[]string) (ReplyStrategy, error) {
switch strategyName {
case "nop":
return &nopReply{}, nil
Expand All @@ -114,18 +114,18 @@ func (l *Loader) loadStrategy(path, strategyName string, definition map[interfac
}
}

func (l *Loader) loadUriVaryStrategy(path string, def map[interface{}]interface{}) (replyStrategy, error) {
func (l *Loader) loadUriVaryStrategy(path string, def map[interface{}]interface{}) (ReplyStrategy, error) {
var basePath string
if b, ok := def["basePath"]; ok {
basePath = b.(string)
}
var uris map[string]*definition
var uris map[string]*Definition
if u, ok := def["uris"]; ok {
urisMap, ok := u.(map[interface{}]interface{})
if !ok {
return nil, errors.New("`uriVary` requires map under `uris` key")
}
uris = make(map[string]*definition, len(urisMap))
uris = make(map[string]*Definition, len(urisMap))
for uri, v := range urisMap {
def, err := l.loadDefinition(path+"."+uri.(string), v)
if err != nil {
Expand All @@ -134,17 +134,17 @@ func (l *Loader) loadUriVaryStrategy(path string, def map[interface{}]interface{
uris[uri.(string)] = def
}
}
return newUriVaryReply(basePath, uris), nil
return NewUriVaryReply(basePath, uris), nil
}

func (l *Loader) loadMethodVaryStrategy(path string, def map[interface{}]interface{}) (replyStrategy, error) {
var methods map[string]*definition
func (l *Loader) loadMethodVaryStrategy(path string, def map[interface{}]interface{}) (ReplyStrategy, error) {
var methods map[string]*Definition
if u, ok := def["methods"]; ok {
methodsMap, ok := u.(map[interface{}]interface{})
if !ok {
return nil, errors.New("`methodVary` requires map under `methods` key")
}
methods = make(map[string]*definition, len(methodsMap))
methods = make(map[string]*Definition, len(methodsMap))
for method, v := range methodsMap {
def, err := l.loadDefinition(path+"."+method.(string), v)
if err != nil {
Expand All @@ -153,10 +153,10 @@ func (l *Loader) loadMethodVaryStrategy(path string, def map[interface{}]interfa
methods[method.(string)] = def
}
}
return newMethodVaryReply(methods), nil
return NewMethodVaryReply(methods), nil
}

func (l *Loader) loadFileStrategy(path string, def map[interface{}]interface{}) (replyStrategy, error) {
func (l *Loader) loadFileStrategy(path string, def map[interface{}]interface{}) (ReplyStrategy, error) {
f, ok := def["filename"]
if !ok {
return nil, errors.New("`file` requires `filename` key")
Expand All @@ -173,10 +173,10 @@ func (l *Loader) loadFileStrategy(path string, def map[interface{}]interface{})
if err != nil {
return nil, err
}
return newFileReplyWithCode(filename, statusCode, headers)
return NewFileReplyWithCode(filename, statusCode, headers)
}

func (l *Loader) loadConstantStrategy(path string, def map[interface{}]interface{}) (replyStrategy, error) {
func (l *Loader) loadConstantStrategy(path string, def map[interface{}]interface{}) (ReplyStrategy, error) {
c, ok := def["body"]
if !ok {
return nil, errors.New("`constant` requires `body` key")
Expand All @@ -193,26 +193,26 @@ func (l *Loader) loadConstantStrategy(path string, def map[interface{}]interface
if err != nil {
return nil, err
}
return newConstantReplyWithCode([]byte(body), statusCode, headers), nil
return NewConstantReplyWithCode([]byte(body), statusCode, headers), nil
}

func (l *Loader) loadSequenceStrategy(path string, def map[interface{}]interface{}) (replyStrategy, error) {
func (l *Loader) loadSequenceStrategy(path string, def map[interface{}]interface{}) (ReplyStrategy, error) {
if _, ok := def["sequence"]; !ok {
return nil, errors.New("`sequence` requires `sequence` key")
}
seqSlice, ok := def["sequence"].([]interface{})
if !ok {
return nil, errors.New("`sequence` must be a list")
}
strategies := make([]*definition, len(seqSlice))
strategies := make([]*Definition, len(seqSlice))
for i, v := range seqSlice {
def, err := l.loadDefinition(path+"."+strconv.Itoa(i), v)
if err != nil {
return nil, err
}
strategies[i] = def
}
return newSequentialReply(strategies), nil
return NewSequentialReply(strategies), nil
}

func (l *Loader) loadHeaders(def map[interface{}]interface{}) (map[string]string, error) {
Expand Down
6 changes: 5 additions & 1 deletion mocks/mocks.go
Expand Up @@ -24,7 +24,7 @@ func New(mocks ...*ServiceMock) *Mocks {
func NewNop(serviceNames ...string) *Mocks {
mocksMap := make(map[string]*ServiceMock, len(serviceNames))
for _, name := range serviceNames {
mocksMap[name] = NewServiceMock(name, newDefinition("$", nil, &failReply{}, callsNoConstraint))
mocksMap[name] = NewServiceMock(name, NewDefinition("$", nil, &failReply{}, CallsNoConstraint))
}
return &Mocks{
mocks: mocksMap,
Expand Down Expand Up @@ -68,6 +68,10 @@ func (m *Mocks) ShutdownContext(ctx context.Context) error {
return nil
}

func (m *Mocks) SetMock(mock *ServiceMock) {
m.mocks[mock.ServiceName] = mock
}

func (m *Mocks) Service(serviceName string) *ServiceMock {
mock, _ := m.mocks[serviceName]
return mock
Expand Down
26 changes: 13 additions & 13 deletions mocks/reply_strategy.go
Expand Up @@ -9,7 +9,7 @@ import (
"sync"
)

type replyStrategy interface {
type ReplyStrategy interface {
HandleRequest(w http.ResponseWriter, r *http.Request) []error
}

Expand All @@ -19,7 +19,7 @@ type contextAwareStrategy interface {
}

type constantReply struct {
replyStrategy
ReplyStrategy

replyBody []byte
statusCode int
Expand All @@ -34,7 +34,7 @@ func unhandledRequestError(r *http.Request) []error {
return []error{fmt.Errorf("unhandled request to mock:\n%s", requestContent)}
}

func newFileReplyWithCode(filename string, statusCode int, headers map[string]string) (replyStrategy, error) {
func NewFileReplyWithCode(filename string, statusCode int, headers map[string]string) (ReplyStrategy, error) {
content, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
Expand All @@ -47,7 +47,7 @@ func newFileReplyWithCode(filename string, statusCode int, headers map[string]st
return r, nil
}

func newConstantReplyWithCode(content []byte, statusCode int, headers map[string]string) replyStrategy {
func NewConstantReplyWithCode(content []byte, statusCode int, headers map[string]string) ReplyStrategy {
return &constantReply{
replyBody: content,
statusCode: statusCode,
Expand All @@ -72,7 +72,7 @@ func (s *failReply) HandleRequest(w http.ResponseWriter, r *http.Request) []erro
}

type nopReply struct {
replyStrategy
ReplyStrategy
}

func (s *nopReply) HandleRequest(w http.ResponseWriter, r *http.Request) []error {
Expand All @@ -81,14 +81,14 @@ func (s *nopReply) HandleRequest(w http.ResponseWriter, r *http.Request) []error
}

type uriVaryReply struct {
replyStrategy
ReplyStrategy
contextAwareStrategy

basePath string
variants map[string]*definition
variants map[string]*Definition
}

func newUriVaryReply(basePath string, variants map[string]*definition) replyStrategy {
func NewUriVaryReply(basePath string, variants map[string]*Definition) ReplyStrategy {
return &uriVaryReply{
basePath: strings.TrimRight(basePath, "/") + "/",
variants: variants,
Expand Down Expand Up @@ -120,13 +120,13 @@ func (s *uriVaryReply) EndRunningContext() []error {
}

type methodVaryReply struct {
replyStrategy
ReplyStrategy
contextAwareStrategy

variants map[string]*definition
variants map[string]*Definition
}

func newMethodVaryReply(variants map[string]*definition) replyStrategy {
func NewMethodVaryReply(variants map[string]*Definition) ReplyStrategy {
return &methodVaryReply{
variants: variants,
}
Expand Down Expand Up @@ -155,7 +155,7 @@ func (s *methodVaryReply) EndRunningContext() []error {
return errs
}

func newSequentialReply(strategies []*definition) replyStrategy {
func NewSequentialReply(strategies []*Definition) ReplyStrategy {
return &sequentialReply{
sequence: strategies,
}
Expand All @@ -164,7 +164,7 @@ func newSequentialReply(strategies []*definition) replyStrategy {
type sequentialReply struct {
sync.Mutex
count int
sequence []*definition
sequence []*Definition
}

func (s *sequentialReply) ResetRunningContext() {
Expand Down
8 changes: 4 additions & 4 deletions mocks/service_mock.go
Expand Up @@ -10,15 +10,15 @@ import (
type ServiceMock struct {
server *http.Server
listener net.Listener
mock *definition
defaultDefinition *definition
mock *Definition
defaultDefinition *Definition
sync.Mutex
errors []error

ServiceName string
}

func NewServiceMock(serviceName string, mock *definition) *ServiceMock {
func NewServiceMock(serviceName string, mock *Definition) *ServiceMock {
return &ServiceMock{
mock: mock,
defaultDefinition: mock,
Expand Down Expand Up @@ -62,7 +62,7 @@ func (m *ServiceMock) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}

func (m *ServiceMock) SetDefinition(newDefinition *definition) {
func (m *ServiceMock) SetDefinition(newDefinition *Definition) {
m.Lock()
defer m.Unlock()
m.mock = newDefinition
Expand Down

0 comments on commit 1c1bb7a

Please sign in to comment.