From 0bfd88295c5e2824edcf47cf379001e6129eb648 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20L=C3=B3pez=20de=20la=20Franca=20Beltran?= Date: Thu, 25 Apr 2019 18:00:54 +0200 Subject: [PATCH 1/7] Moving request schema validation from route handler to route matching --- handler.go | 45 ------------------------------------------ handler_test.go | 16 +-------------- server.go | 52 ++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 52 insertions(+), 61 deletions(-) diff --git a/handler.go b/handler.go index cc48667..143e60e 100644 --- a/handler.go +++ b/handler.go @@ -2,74 +2,29 @@ package killgrave import ( "fmt" - "io" "io/ioutil" "log" "net/http" "net/textproto" "os" "strings" - - "github.com/pkg/errors" - "github.com/xeipuuv/gojsonschema" ) // ImposterHandler create specific handler for the received imposter func ImposterHandler(imposter Imposter) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - if err := validateSchema(imposter, r.Body); err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - if err := validateHeaders(imposter, r.Header); err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } - writeHeaders(imposter, w) w.WriteHeader(imposter.Response.Status) writeBody(imposter, w) } } -func validateSchema(imposter Imposter, bodyRequest io.ReadCloser) error { - if imposter.Request.SchemaFile == nil { - return nil - } - - schemaFile := *imposter.Request.SchemaFile - if _, err := os.Stat(schemaFile); os.IsNotExist(err) { - return errors.Wrapf(err, "the schema file %s not found", schemaFile) - } - - b, err := ioutil.ReadAll(bodyRequest) - if err != nil { - return errors.Wrapf(err, "impossible read the request body") - } - - dir, _ := os.Getwd() - schemaFilePath := "file://" + dir + "/" + schemaFile - schema := gojsonschema.NewReferenceLoader(schemaFilePath) - document := gojsonschema.NewStringLoader(string(b)) - - res, err := gojsonschema.Validate(schema, document) - if err != nil { - return errors.Wrap(err, "error validating the json schema") - } - - if !res.Valid() { - for _, desc := range res.Errors() { - return errors.New(desc.String()) - } - } - - return nil -} - func validateHeaders(imposter Imposter, header http.Header) error { if imposter.Request.Headers == nil { return nil diff --git a/handler_test.go b/handler_test.go index fd92ab5..261d230 100644 --- a/handler_test.go +++ b/handler_test.go @@ -75,15 +75,6 @@ func TestImposterHandler(t *testing.T) { } func TestInvalidRequestWithSchema(t *testing.T) { - wrongRequest := []byte(`{ - "data": { - "type": "gophers", - "attributes": { - "name": "Zebediah", - "color": "Purple" - } - } - }`) validRequest := []byte(`{ "data": { "type": "gophers", @@ -93,9 +84,6 @@ func TestInvalidRequestWithSchema(t *testing.T) { } } }`) - notExistFile := "failSchema" - wrongSchema := "test/testdata/schemas/create_gopher_request_fail.json" - validSchema := "test/testdata/schemas/create_gopher_request.json" var dataTest = []struct { name string @@ -103,13 +91,11 @@ func TestInvalidRequestWithSchema(t *testing.T) { statusCode int request []byte }{ - {"schema file not found", Imposter{Request: Request{Method: "POST", Endpoint: "/gophers", SchemaFile: ¬ExistFile}}, http.StatusBadRequest, validRequest}, - {"wrong schema", Imposter{Request: Request{Method: "POST", Endpoint: "/gophers", SchemaFile: &wrongSchema}}, http.StatusBadRequest, validRequest}, - {"request invalid", Imposter{Request: Request{Method: "POST", Endpoint: "/gophers", SchemaFile: &validSchema}}, http.StatusBadRequest, wrongRequest}, {"valid request no schema", Imposter{Request: Request{Method: "POST", Endpoint: "/gophers"}, Response: Response{Status: http.StatusOK, Body: "test ok"}}, http.StatusOK, validRequest}, } for _, tt := range dataTest { + t.Run(tt.name, func(t *testing.T) { req, err := http.NewRequest("POST", "/gophers", bytes.NewBuffer(tt.request)) if err != nil { diff --git a/server.go b/server.go index 7eafb36..4babe5a 100644 --- a/server.go +++ b/server.go @@ -1,9 +1,13 @@ package killgrave import ( + "bytes" "encoding/json" "fmt" + "github.com/pkg/errors" + "github.com/xeipuuv/gojsonschema" "io/ioutil" + "net/http" "os" "github.com/gorilla/mux" @@ -48,7 +52,12 @@ func (s *Server) buildImposters() error { if imposter.Request.Endpoint == "" { continue } - s.router.HandleFunc(imposter.Request.Endpoint, ImposterHandler(imposter)).Methods(imposter.Request.Method) + s.router.HandleFunc(imposter.Request.Endpoint, ImposterHandler(imposter)). + Methods(imposter.Request.Method). + MatcherFunc(func(req *http.Request, rm *mux.RouteMatch) bool { + err := validateSchema(imposter, req) + return err == nil + }) } return nil @@ -65,3 +74,44 @@ func (s *Server) buildImposter(imposterFileName string, imposter *Imposter) erro } return nil } + +func validateSchema(imposter Imposter, req *http.Request) error { + var b []byte + + defer func() { + req.Body.Close() + req.Body = ioutil.NopCloser(bytes.NewBuffer(b)) + }() + + if imposter.Request.SchemaFile == nil { + return nil + } + + schemaFile := *imposter.Request.SchemaFile + if _, err := os.Stat(schemaFile); os.IsNotExist(err) { + return errors.Wrapf(err, "the schema file %s not found", schemaFile) + } + + b, err := ioutil.ReadAll(req.Body) + if err != nil { + return errors.Wrapf(err, "impossible read the request body") + } + + dir, _ := os.Getwd() + schemaFilePath := "file://" + dir + "/" + schemaFile + schema := gojsonschema.NewReferenceLoader(schemaFilePath) + document := gojsonschema.NewStringLoader(string(b)) + + res, err := gojsonschema.Validate(schema, document) + if err != nil { + return errors.Wrap(err, "error validating the json schema") + } + + if !res.Valid() { + for _, desc := range res.Errors() { + return errors.New(desc.String()) + } + } + + return nil +} From 0a7fd7dd004d6887fbcb40a60ecb2f5b375784c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20L=C3=B3pez=20de=20la=20Franca=20Beltran?= Date: Thu, 25 Apr 2019 18:01:27 +0200 Subject: [PATCH 2/7] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c068cf6..9e0d65a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,3 +14,7 @@ * Create an official docker image for the application * Update README.md with how to use the application with docker * Allow write headers for the response + +## v0.2.1 (2019/04/25) + +* Allow imposter's matching by request schema \ No newline at end of file From f38ad8f1c62412904ea75ff6dc489f28ed28c6b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20L=C3=B3pez=20de=20la=20Franca=20Beltran?= Date: Thu, 25 Apr 2019 18:39:11 +0200 Subject: [PATCH 3/7] Refactor of route matchers + test coverage --- route_matchers.go | 59 ++++++++++++++++++++++++++++++ route_matchers_test.go | 60 +++++++++++++++++++++++++++++++ server.go | 50 +------------------------- test/testdata/schemas/type_a.json | 12 +++++++ 4 files changed, 132 insertions(+), 49 deletions(-) create mode 100644 route_matchers.go create mode 100644 route_matchers_test.go create mode 100644 test/testdata/schemas/type_a.json diff --git a/route_matchers.go b/route_matchers.go new file mode 100644 index 0000000..8621b26 --- /dev/null +++ b/route_matchers.go @@ -0,0 +1,59 @@ +package killgrave + +import ( + "bytes" + "github.com/gorilla/mux" + "github.com/pkg/errors" + "github.com/xeipuuv/gojsonschema" + "io/ioutil" + "net/http" + "os" +) + +func MatcherBySchema(imposter Imposter) mux.MatcherFunc { + return func(req *http.Request, rm *mux.RouteMatch) bool { + err := validateSchema(imposter, req) + return err == nil + } +} + +func validateSchema(imposter Imposter, req *http.Request) error { + if imposter.Request.SchemaFile == nil { + return nil + } + + var b []byte + + defer func() { + req.Body.Close() + req.Body = ioutil.NopCloser(bytes.NewBuffer(b)) + }() + + schemaFile := *imposter.Request.SchemaFile + if _, err := os.Stat(schemaFile); os.IsNotExist(err) { + return errors.Wrapf(err, "the schema file %s not found", schemaFile) + } + + b, err := ioutil.ReadAll(req.Body) + if err != nil { + return errors.Wrapf(err, "impossible read the request body") + } + + dir, _ := os.Getwd() + schemaFilePath := "file://" + dir + "/" + schemaFile + schema := gojsonschema.NewReferenceLoader(schemaFilePath) + document := gojsonschema.NewStringLoader(string(b)) + + res, err := gojsonschema.Validate(schema, document) + if err != nil { + return errors.Wrap(err, "error validating the json schema") + } + + if !res.Valid() { + for _, desc := range res.Errors() { + return errors.New(desc.String()) + } + } + + return nil +} \ No newline at end of file diff --git a/route_matchers_test.go b/route_matchers_test.go new file mode 100644 index 0000000..ad2a5a5 --- /dev/null +++ b/route_matchers_test.go @@ -0,0 +1,60 @@ +package killgrave + +import ( + "bytes" + "github.com/gorilla/mux" + "io/ioutil" + "net/http" + "testing" +) + +func TestMatcherBySchema(t *testing.T) { + bodyA := ioutil.NopCloser(bytes.NewReader([]byte("{\"type\": \"A\"}"))) + bodyB := ioutil.NopCloser(bytes.NewReader([]byte("{\"type\": \"B\"}"))) + + schemaAFile := "test/testdata/schemas/type_a.json" + schemaBFile := "test/testdata/schemas/type_b.json" + + + requestWithoutSchema := Request{ + Method: "POST", + Endpoint: "/login", + SchemaFile: nil, + } + + requestWithSchema := Request{ + Method: "POST", + Endpoint: "/login", + SchemaFile: &schemaAFile, + } + + requestWithNonExistingSchema := Request{ + Method: "POST", + Endpoint: "/login", + SchemaFile: &schemaBFile, + } + + okResponse := Response{Status: http.StatusOK} + + var matcherData = []struct { + name string + fn mux.MatcherFunc + req *http.Request + res bool + }{ + {"imposter without request schema", MatcherBySchema(Imposter{Request: requestWithoutSchema, Response: okResponse}), &http.Request{Body: bodyA}, true}, + {"correct request schema", MatcherBySchema(Imposter{Request: requestWithSchema, Response: okResponse}), &http.Request{Body: bodyA}, true}, + {"incorrect request schema", MatcherBySchema(Imposter{Request: requestWithSchema, Response: okResponse}), &http.Request{Body: bodyB}, false}, + {"non-existing schema file", MatcherBySchema(Imposter{Request: requestWithNonExistingSchema, Response: okResponse}), &http.Request{Body: bodyB}, false}, + } + + for _, tt := range matcherData { + t.Run(tt.name, func(t *testing.T) { + res := tt.fn(tt.req, nil) + if res != tt.res { + t.Fatalf("error while matching by request schema - expected: %t, given: %t", tt.res, res) + } + }) + + } +} diff --git a/server.go b/server.go index 4babe5a..7fffda9 100644 --- a/server.go +++ b/server.go @@ -1,13 +1,9 @@ package killgrave import ( - "bytes" "encoding/json" "fmt" - "github.com/pkg/errors" - "github.com/xeipuuv/gojsonschema" "io/ioutil" - "net/http" "os" "github.com/gorilla/mux" @@ -54,10 +50,7 @@ func (s *Server) buildImposters() error { } s.router.HandleFunc(imposter.Request.Endpoint, ImposterHandler(imposter)). Methods(imposter.Request.Method). - MatcherFunc(func(req *http.Request, rm *mux.RouteMatch) bool { - err := validateSchema(imposter, req) - return err == nil - }) + MatcherFunc(MatcherBySchema(imposter)) } return nil @@ -74,44 +67,3 @@ func (s *Server) buildImposter(imposterFileName string, imposter *Imposter) erro } return nil } - -func validateSchema(imposter Imposter, req *http.Request) error { - var b []byte - - defer func() { - req.Body.Close() - req.Body = ioutil.NopCloser(bytes.NewBuffer(b)) - }() - - if imposter.Request.SchemaFile == nil { - return nil - } - - schemaFile := *imposter.Request.SchemaFile - if _, err := os.Stat(schemaFile); os.IsNotExist(err) { - return errors.Wrapf(err, "the schema file %s not found", schemaFile) - } - - b, err := ioutil.ReadAll(req.Body) - if err != nil { - return errors.Wrapf(err, "impossible read the request body") - } - - dir, _ := os.Getwd() - schemaFilePath := "file://" + dir + "/" + schemaFile - schema := gojsonschema.NewReferenceLoader(schemaFilePath) - document := gojsonschema.NewStringLoader(string(b)) - - res, err := gojsonschema.Validate(schema, document) - if err != nil { - return errors.Wrap(err, "error validating the json schema") - } - - if !res.Valid() { - for _, desc := range res.Errors() { - return errors.New(desc.String()) - } - } - - return nil -} diff --git a/test/testdata/schemas/type_a.json b/test/testdata/schemas/type_a.json new file mode 100644 index 0000000..7a43966 --- /dev/null +++ b/test/testdata/schemas/type_a.json @@ -0,0 +1,12 @@ +{ + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "pattern": "^(A)$" + } + } +} \ No newline at end of file From 8a969a85aa2ff58cb75d2fda7a4477190ce0b092 Mon Sep 17 00:00:00 2001 From: "fixmie[bot]" Date: Thu, 25 Apr 2019 18:40:54 +0200 Subject: [PATCH 4/7] [fixmie] Add func comment Co-Authored-By: joanlopez --- route_matchers.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/route_matchers.go b/route_matchers.go index 8621b26..80d431f 100644 --- a/route_matchers.go +++ b/route_matchers.go @@ -10,6 +10,7 @@ import ( "os" ) +// MatcherBySchema ... func MatcherBySchema(imposter Imposter) mux.MatcherFunc { return func(req *http.Request, rm *mux.RouteMatch) bool { err := validateSchema(imposter, req) @@ -56,4 +57,4 @@ func validateSchema(imposter Imposter, req *http.Request) error { } return nil -} \ No newline at end of file +} From 292a04e02b88c255f6cbc3f8307a37a937f9ff67 Mon Sep 17 00:00:00 2001 From: aperezg Date: Thu, 25 Apr 2019 21:01:23 +0200 Subject: [PATCH 5/7] Modify test to cover all possible cases and adapt the examples to library style --- README.md | 2 + route_matchers.go | 7 +++- route_matchers_test.go | 21 ++++++---- .../schemas/create_gopher_request_fail.json | 41 ------------------- .../schemas/{type_a.json => type_gopher.json} | 4 +- test/testdata/schemas/type_gopher_fail.json | 12 ++++++ 6 files changed, 36 insertions(+), 51 deletions(-) delete mode 100644 test/testdata/schemas/create_gopher_request_fail.json rename test/testdata/schemas/{type_a.json => type_gopher.json} (78%) create mode 100644 test/testdata/schemas/type_gopher_fail.json diff --git a/README.md b/README.md index a8e7c71..75e004e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ [![CircleCI](https://circleci.com/gh/friendsofgo/killgrave/tree/master.svg?style=svg)](https://circleci.com/gh/friendsofgo/killgrave/tree/master) +[![Version](https://img.shields.io/badge/version-v0.2.1-blue.svg)](https://img.shields.io/badge/version-v0.2.1-blue.svg) [![codecov](https://codecov.io/gh/friendsofgo/killgrave/branch/master/graph/badge.svg)](https://codecov.io/gh/friendsofgo/killgrave) [![Go Report Card](https://goreportcard.com/badge/github.com/friendsofgo/killgrave)](https://goreportcard.com/report/github.com/friendsofgo/killgrave) [![GoDoc](https://godoc.org/graphql.co/graphql?status.svg)](https://godoc.org/github.com/friendsofgo/killgrave) @@ -181,6 +182,7 @@ NOTE: If you want to use `killgrave` through Docker at the same time you use you * Write bodies in line * Regex for using on endpoint urls * Allow write headers on response +* Allow imposter's matching by request schema ## Next Features - [ ] Proxy server diff --git a/route_matchers.go b/route_matchers.go index 80d431f..e3c30b6 100644 --- a/route_matchers.go +++ b/route_matchers.go @@ -6,14 +6,19 @@ import ( "github.com/pkg/errors" "github.com/xeipuuv/gojsonschema" "io/ioutil" + "log" "net/http" "os" ) -// MatcherBySchema ... +// MatcherBySchema check if the request matching with the schema file func MatcherBySchema(imposter Imposter) mux.MatcherFunc { return func(req *http.Request, rm *mux.RouteMatch) bool { err := validateSchema(imposter, req) + + // TODO: inject the logger + log.Println(err) + return err == nil } } diff --git a/route_matchers_test.go b/route_matchers_test.go index ad2a5a5..5ff1719 100644 --- a/route_matchers_test.go +++ b/route_matchers_test.go @@ -9,12 +9,12 @@ import ( ) func TestMatcherBySchema(t *testing.T) { - bodyA := ioutil.NopCloser(bytes.NewReader([]byte("{\"type\": \"A\"}"))) - bodyB := ioutil.NopCloser(bytes.NewReader([]byte("{\"type\": \"B\"}"))) - - schemaAFile := "test/testdata/schemas/type_a.json" - schemaBFile := "test/testdata/schemas/type_b.json" + bodyA := ioutil.NopCloser(bytes.NewReader([]byte("{\"type\": \"gopher\"}"))) + bodyB := ioutil.NopCloser(bytes.NewReader([]byte("{\"type\": \"cat\"}"))) + schemaGopherFile := "test/testdata/schemas/type_gopher.json" + schemaCatFile := "test/testdata/schemas/type_cat.json" + schemeFailFile := "test/testdata/schemas/type_gopher_fail.json" requestWithoutSchema := Request{ Method: "POST", @@ -25,13 +25,19 @@ func TestMatcherBySchema(t *testing.T) { requestWithSchema := Request{ Method: "POST", Endpoint: "/login", - SchemaFile: &schemaAFile, + SchemaFile: &schemaGopherFile, } requestWithNonExistingSchema := Request{ Method: "POST", Endpoint: "/login", - SchemaFile: &schemaBFile, + SchemaFile: &schemaCatFile, + } + + requestWithWrongSchema := Request{ + Method: "POST", + Endpoint: "/login", + SchemaFile: &schemeFailFile, } okResponse := Response{Status: http.StatusOK} @@ -46,6 +52,7 @@ func TestMatcherBySchema(t *testing.T) { {"correct request schema", MatcherBySchema(Imposter{Request: requestWithSchema, Response: okResponse}), &http.Request{Body: bodyA}, true}, {"incorrect request schema", MatcherBySchema(Imposter{Request: requestWithSchema, Response: okResponse}), &http.Request{Body: bodyB}, false}, {"non-existing schema file", MatcherBySchema(Imposter{Request: requestWithNonExistingSchema, Response: okResponse}), &http.Request{Body: bodyB}, false}, + {"malformatted schema file", MatcherBySchema(Imposter{Request: requestWithWrongSchema, Response: okResponse}), &http.Request{Body: bodyB}, false}, } for _, tt := range matcherData { diff --git a/test/testdata/schemas/create_gopher_request_fail.json b/test/testdata/schemas/create_gopher_request_fail.json deleted file mode 100644 index 49ed8f3..0000000 --- a/test/testdata/schemas/create_gopher_request_fail.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "type": "error", - "properties": { - "nul": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "gophers" - ] - }, - "attributes": { - "properties": { - "name": { - "type": "string" - }, - "color": { - "type": "string" - }, - "age": { - "type": "integer" - } - }, - "required": [ - "name", - "color", - "age" - ] - } - }, - "required": [ - "type", - "attributes" - ] - } - }, - "required": [ - "data" - ] -} \ No newline at end of file diff --git a/test/testdata/schemas/type_a.json b/test/testdata/schemas/type_gopher.json similarity index 78% rename from test/testdata/schemas/type_a.json rename to test/testdata/schemas/type_gopher.json index 7a43966..8467b21 100644 --- a/test/testdata/schemas/type_a.json +++ b/test/testdata/schemas/type_gopher.json @@ -6,7 +6,7 @@ "properties": { "type": { "type": "string", - "pattern": "^(A)$" + "pattern": "^(gopher)$" } } -} \ No newline at end of file +} diff --git a/test/testdata/schemas/type_gopher_fail.json b/test/testdata/schemas/type_gopher_fail.json new file mode 100644 index 0000000..9cfb314 --- /dev/null +++ b/test/testdata/schemas/type_gopher_fail.json @@ -0,0 +1,12 @@ +{ + "type": "error", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "pattern": "^(gopher)$" + } + } +} From d90d093b5fdb1a7b1cd0a06311704dbf9c0e0c58 Mon Sep 17 00:00:00 2001 From: aperezg Date: Thu, 25 Apr 2019 23:18:20 +0200 Subject: [PATCH 6/7] Update README.md with resolved features and new future features, and fix some badges --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 75e004e..8760fa3 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ [![CircleCI](https://circleci.com/gh/friendsofgo/killgrave/tree/master.svg?style=svg)](https://circleci.com/gh/friendsofgo/killgrave/tree/master) -[![Version](https://img.shields.io/badge/version-v0.2.1-blue.svg)](https://img.shields.io/badge/version-v0.2.1-blue.svg) +[![Version](https://img.shields.io/github/release/friendsofgo/killgrave.svg?style=flat-square)](https://github.com/friendsofgo/killgrave/releases/latest) [![codecov](https://codecov.io/gh/friendsofgo/killgrave/branch/master/graph/badge.svg)](https://codecov.io/gh/friendsofgo/killgrave) [![Go Report Card](https://goreportcard.com/badge/github.com/friendsofgo/killgrave)](https://goreportcard.com/report/github.com/friendsofgo/killgrave) [![GoDoc](https://godoc.org/graphql.co/graphql?status.svg)](https://godoc.org/github.com/friendsofgo/killgrave) -[![FriendsOfGo](https://img.shields.io/badge/powered%20by-Friends%20of%20Go-73D7E2.svg)](https://img.shields.io/badge/powered%20by-Friends%20of%20Go-73D7E2.svg) +[![FriendsOfGo](https://img.shields.io/badge/powered%20by-Friends%20of%20Go-73D7E2.svg)](https://friendsofgo.tech)

Golang Killgrave @@ -183,10 +183,13 @@ NOTE: If you want to use `killgrave` through Docker at the same time you use you * Regex for using on endpoint urls * Allow write headers on response * Allow imposter's matching by request schema +* Dynamic responses based on regex endpoint or request schema ## Next Features +- [ ] Dynamic responses based on headers +- [ ] Dynamic responses based on query params +- [ ] Allow write multiples imposters by file - [ ] Proxy server -- [ ] Dynamic responses and error responses - [ ] Record proxy server - [ ] Better documentation with examples of each feature From bc924cd3bba14df25c631bd0d3a9c2dacf9ff44a Mon Sep 17 00:00:00 2001 From: aperezg Date: Thu, 25 Apr 2019 23:18:59 +0200 Subject: [PATCH 7/7] Calculate file paths(schema and body) based on imposter path --- .gitignore | 2 +- CHANGELOG.md | 5 ++++- handler.go | 3 ++- handler_test.go | 8 ++++---- imposter.go | 19 ++++++++++++++----- route_matchers.go | 10 ++++++---- route_matchers_test.go | 6 +++--- server.go | 6 ++++++ server_test.go | 2 +- .../responses/create_gopher_response.json | 0 .../schemas/create_gopher_request.json | 0 .../{ => imposters}/schemas/type_gopher.json | 0 .../schemas/type_gopher_fail.json | 0 .../create_gopher.json | 0 14 files changed, 41 insertions(+), 20 deletions(-) rename test/testdata/{ => imposters}/responses/create_gopher_response.json (100%) rename test/testdata/{ => imposters}/schemas/create_gopher_request.json (100%) rename test/testdata/{ => imposters}/schemas/type_gopher.json (100%) rename test/testdata/{ => imposters}/schemas/type_gopher_fail.json (100%) rename test/testdata/{malformatted => malformatted_imposters}/create_gopher.json (100%) diff --git a/.gitignore b/.gitignore index 81fbc3a..6b4d7bb 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,4 @@ imposters schemas !test/testdata/imposters/ -!test/testdata/schemas/ \ No newline at end of file +!test/testdata/imposters/schemas/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e0d65a..3913b6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,4 +17,7 @@ ## v0.2.1 (2019/04/25) -* Allow imposter's matching by request schema \ No newline at end of file +* Allow imposter's matching by request schema +* Dynamic responses based on regex endpoint or request schema +* Calculate files directory(body and schema) based on imposters path +* Update REAMDE.md with resolved features and new future features diff --git a/handler.go b/handler.go index 143e60e..94bbbca 100644 --- a/handler.go +++ b/handler.go @@ -72,7 +72,8 @@ func writeBody(imposter Imposter, w http.ResponseWriter) { wb := []byte(imposter.Response.Body) if imposter.Response.BodyFile != nil { - wb = fetchBodyFromFile(*imposter.Response.BodyFile) + bodyFile := imposter.CalculateFilePath(*imposter.Response.BodyFile) + wb = fetchBodyFromFile(bodyFile) } w.Write(wb) } diff --git a/handler_test.go b/handler_test.go index 261d230..1e7372b 100644 --- a/handler_test.go +++ b/handler_test.go @@ -23,9 +23,9 @@ func TestImposterHandler(t *testing.T) { var headers = make(http.Header) headers.Add("Content-Type", "application/json") - schemaFile := "test/testdata/schemas/create_gopher_request.json" - bodyFile := "test/testdata/responses/create_gopher_response.json" - bodyFileFake := "test/testdata/responses/create_gopher_response_fail.json" + schemaFile := "test/testdata/imposters/schemas/create_gopher_request.json" + bodyFile := "test/testdata/imposters/responses/create_gopher_response.json" + bodyFileFake := "test/testdata/imposters/responses/create_gopher_response_fail.json" body := `{"test":true}` validRequest := Request{ @@ -123,7 +123,7 @@ func TestInvalidHeaders(t *testing.T) { } } }`) - schemaFile := "test/testdata/schemas/create_gopher_request.json" + schemaFile := "test/testdata/imposters/schemas/create_gopher_request.json" var expectedHeaders = make(http.Header) expectedHeaders.Add("Content-Type", "application/json") expectedHeaders.Add("Authorization", "Bearer gopher") diff --git a/imposter.go b/imposter.go index 5ade3c1..dbdd2cb 100644 --- a/imposter.go +++ b/imposter.go @@ -1,13 +1,22 @@ package killgrave -import "net/http" +import ( + "net/http" + "path" +) // Imposter define an imposter structure type Imposter struct { + BasePath string Request Request `json:"request"` Response Response `json:"response"` } +// CalculateFilePath calculate file path based on basePath of imposter directory +func (i *Imposter) CalculateFilePath(filePath string) string { + return path.Join(i.BasePath, filePath) +} + // Request represent the structure of real request type Request struct { Method string `json:"method"` @@ -18,8 +27,8 @@ type Request struct { // Response represent the structure of real response type Response struct { - Status int `json:"status"` - Body string `json:"body"` - BodyFile *string `json:"bodyFile"` - Headers *http.Header `json:"headers"` + Status int `json:"status"` + Body string `json:"body"` + BodyFile *string `json:"bodyFile"` + Headers *http.Header `json:"headers"` } diff --git a/route_matchers.go b/route_matchers.go index e3c30b6..058f917 100644 --- a/route_matchers.go +++ b/route_matchers.go @@ -17,9 +17,11 @@ func MatcherBySchema(imposter Imposter) mux.MatcherFunc { err := validateSchema(imposter, req) // TODO: inject the logger - log.Println(err) - - return err == nil + if err != nil { + log.Println(err) + return false + } + return true } } @@ -35,7 +37,7 @@ func validateSchema(imposter Imposter, req *http.Request) error { req.Body = ioutil.NopCloser(bytes.NewBuffer(b)) }() - schemaFile := *imposter.Request.SchemaFile + schemaFile := imposter.CalculateFilePath(*imposter.Request.SchemaFile) if _, err := os.Stat(schemaFile); os.IsNotExist(err) { return errors.Wrapf(err, "the schema file %s not found", schemaFile) } diff --git a/route_matchers_test.go b/route_matchers_test.go index 5ff1719..67ae4d1 100644 --- a/route_matchers_test.go +++ b/route_matchers_test.go @@ -12,9 +12,9 @@ func TestMatcherBySchema(t *testing.T) { bodyA := ioutil.NopCloser(bytes.NewReader([]byte("{\"type\": \"gopher\"}"))) bodyB := ioutil.NopCloser(bytes.NewReader([]byte("{\"type\": \"cat\"}"))) - schemaGopherFile := "test/testdata/schemas/type_gopher.json" - schemaCatFile := "test/testdata/schemas/type_cat.json" - schemeFailFile := "test/testdata/schemas/type_gopher_fail.json" + schemaGopherFile := "test/testdata/imposters/schemas/type_gopher.json" + schemaCatFile := "test/testdata/imposters/schemas/type_cat.json" + schemeFailFile := "test/testdata/imposters/schemas/type_gopher_fail.json" requestWithoutSchema := Request{ Method: "POST", diff --git a/server.go b/server.go index 7fffda9..45e11d6 100644 --- a/server.go +++ b/server.go @@ -40,6 +40,10 @@ func (s *Server) buildImposters() error { files, _ := ioutil.ReadDir(s.impostersPath) for _, f := range files { + if f.IsDir() { + continue + } + var imposter Imposter if err := s.buildImposter(f.Name(), &imposter); err != nil { return err @@ -65,5 +69,7 @@ func (s *Server) buildImposter(imposterFileName string, imposter *Imposter) erro if err := json.Unmarshal(bytes, imposter); err != nil { return malformattedImposterError(fmt.Sprintf("error while unmarshall imposter file %s", f)) } + imposter.BasePath = s.impostersPath + return nil } diff --git a/server_test.go b/server_test.go index 0a35ae7..35e063f 100644 --- a/server_test.go +++ b/server_test.go @@ -13,7 +13,7 @@ func TestRunServer(t *testing.T) { err error }{ {"imposter directory not found", NewServer("failImposterPath", nil), invalidDirectoryError("error")}, - {"malformatted json", NewServer("test/testdata/malformatted", nil), malformattedImposterError("error")}, + {"malformatted json", NewServer("test/testdata/malformatted_imposters", nil), malformattedImposterError("error")}, {"valid imposter", NewServer("test/testdata/imposters", mux.NewRouter()), nil}, } diff --git a/test/testdata/responses/create_gopher_response.json b/test/testdata/imposters/responses/create_gopher_response.json similarity index 100% rename from test/testdata/responses/create_gopher_response.json rename to test/testdata/imposters/responses/create_gopher_response.json diff --git a/test/testdata/schemas/create_gopher_request.json b/test/testdata/imposters/schemas/create_gopher_request.json similarity index 100% rename from test/testdata/schemas/create_gopher_request.json rename to test/testdata/imposters/schemas/create_gopher_request.json diff --git a/test/testdata/schemas/type_gopher.json b/test/testdata/imposters/schemas/type_gopher.json similarity index 100% rename from test/testdata/schemas/type_gopher.json rename to test/testdata/imposters/schemas/type_gopher.json diff --git a/test/testdata/schemas/type_gopher_fail.json b/test/testdata/imposters/schemas/type_gopher_fail.json similarity index 100% rename from test/testdata/schemas/type_gopher_fail.json rename to test/testdata/imposters/schemas/type_gopher_fail.json diff --git a/test/testdata/malformatted/create_gopher.json b/test/testdata/malformatted_imposters/create_gopher.json similarity index 100% rename from test/testdata/malformatted/create_gopher.json rename to test/testdata/malformatted_imposters/create_gopher.json