From 624a0c61ccb02059f23a6710d9a2dd23948632bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20P=C3=A9rez?= Date: Fri, 26 Apr 2019 08:25:21 +0200 Subject: [PATCH 01/13] Control when request body is empty with a schema file specification --- route_matchers.go | 13 ++++++++++--- route_matchers_test.go | 5 ++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/route_matchers.go b/route_matchers.go index 058f917..a4abe14 100644 --- a/route_matchers.go +++ b/route_matchers.go @@ -2,13 +2,15 @@ package killgrave import ( "bytes" - "github.com/gorilla/mux" - "github.com/pkg/errors" - "github.com/xeipuuv/gojsonschema" + "fmt" "io/ioutil" "log" "net/http" "os" + + "github.com/gorilla/mux" + "github.com/pkg/errors" + "github.com/xeipuuv/gojsonschema" ) // MatcherBySchema check if the request matching with the schema file @@ -47,6 +49,11 @@ func validateSchema(imposter Imposter, req *http.Request) error { return errors.Wrapf(err, "impossible read the request body") } + contentBody := string(b) + if contentBody == "" { + return fmt.Errorf("expected an body request and got empy body request") + } + dir, _ := os.Getwd() schemaFilePath := "file://" + dir + "/" + schemaFile schema := gojsonschema.NewReferenceLoader(schemaFilePath) diff --git a/route_matchers_test.go b/route_matchers_test.go index 67ae4d1..e9f9940 100644 --- a/route_matchers_test.go +++ b/route_matchers_test.go @@ -2,15 +2,17 @@ package killgrave import ( "bytes" - "github.com/gorilla/mux" "io/ioutil" "net/http" "testing" + + "github.com/gorilla/mux" ) func TestMatcherBySchema(t *testing.T) { bodyA := ioutil.NopCloser(bytes.NewReader([]byte("{\"type\": \"gopher\"}"))) bodyB := ioutil.NopCloser(bytes.NewReader([]byte("{\"type\": \"cat\"}"))) + emptyBody := ioutil.NopCloser(bytes.NewReader([]byte(""))) schemaGopherFile := "test/testdata/imposters/schemas/type_gopher.json" schemaCatFile := "test/testdata/imposters/schemas/type_cat.json" @@ -53,6 +55,7 @@ func TestMatcherBySchema(t *testing.T) { {"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}, + {"empty body with required schema file", MatcherBySchema(Imposter{Request: requestWithSchema, Response: okResponse}), &http.Request{Body: emptyBody}, false}, } for _, tt := range matcherData { From 4ca8c89e39858221b3ac7aac0bb0b4e65a9263c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20P=C3=A9rez?= Date: Fri, 26 Apr 2019 15:17:36 +0200 Subject: [PATCH 02/13] Dynamic responses based on headers, using gorilla mux match headers --- CHANGELOG.md | 4 ++ README.md | 7 ++- handler.go | 46 +---------------- handler_test.go | 60 ++-------------------- imposter.go | 17 +++--- server.go | 8 ++- test/testdata/imposters/create_gopher.json | 4 +- 7 files changed, 30 insertions(+), 116 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3913b6c..5b68db9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,3 +21,7 @@ * 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 + +## v0.3.0 (2019/??/??) + +* Dynamic responses based on headers \ No newline at end of file diff --git a/README.md b/README.md index 8760fa3..4003c50 100644 --- a/README.md +++ b/README.md @@ -64,9 +64,7 @@ imposters/create_gopher.json "endpoint": "/gophers", "schema_file": "schemas/create_gopher_request.json", "headers": { - "Content-Type": [ - "application/json" - ] + "Content-Type": "application/json" } }, "response": { @@ -184,11 +182,12 @@ NOTE: If you want to use `killgrave` through Docker at the same time you use you * Allow write headers on response * Allow imposter's matching by request schema * Dynamic responses based on regex endpoint or request schema +* Dynamic responses based on headers ## Next Features -- [ ] Dynamic responses based on headers - [ ] Dynamic responses based on query params - [ ] Allow write multiples imposters by file +- [ ] Allow differents directories to organize your imposters - [ ] Proxy server - [ ] Record proxy server - [ ] Better documentation with examples of each feature diff --git a/handler.go b/handler.go index 94bbbca..485401e 100644 --- a/handler.go +++ b/handler.go @@ -1,70 +1,28 @@ package killgrave import ( - "fmt" "io/ioutil" "log" "net/http" - "net/textproto" "os" - "strings" ) // ImposterHandler create specific handler for the received imposter func ImposterHandler(imposter Imposter) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - 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 validateHeaders(imposter Imposter, header http.Header) error { - if imposter.Request.Headers == nil { - return nil - } - - for k, v := range *imposter.Request.Headers { - mimeTypeKey := textproto.CanonicalMIMEHeaderKey(k) - if _, ok := header[mimeTypeKey]; !ok { - return fmt.Errorf("the key %s is not specified on header", k) - } - - if !compareHeaderValues(v, header[mimeTypeKey]) { - return fmt.Errorf("the key %s expected: %v got:%v", k, v, header[k]) - } - } - - return nil -} - -func compareHeaderValues(a, b []string) bool { - if len(a) != len(b) { - return false - } - for i, v := range a { - if strings.ToLower(v) != strings.ToLower(b[i]) { - return false - } - } - return true -} - func writeHeaders(imposter Imposter, w http.ResponseWriter) { if imposter.Response.Headers == nil { return } - for key, values := range *imposter.Response.Headers { - for _, val := range values { - w.Header().Add(key, val) - } + for key, val := range *imposter.Response.Headers { + w.Header().Set(key, val) } } diff --git a/handler_test.go b/handler_test.go index 1e7372b..46a3247 100644 --- a/handler_test.go +++ b/handler_test.go @@ -20,8 +20,8 @@ func TestImposterHandler(t *testing.T) { } } }`) - var headers = make(http.Header) - headers.Add("Content-Type", "application/json") + var headers = make(map[string]string) + headers["Content-Type"] = "application/json" schemaFile := "test/testdata/imposters/schemas/create_gopher_request.json" bodyFile := "test/testdata/imposters/responses/create_gopher_response.json" @@ -45,9 +45,9 @@ func TestImposterHandler(t *testing.T) { expectedBody string statusCode int }{ - {"valid imposter with body", Imposter{Request: validRequest, Response: Response{Status: http.StatusOK, Headers: &http.Header{"Content-Type": []string{"application/json"}}, Body: body}}, body, http.StatusOK}, - {"valid imposter with bodyFile", Imposter{Request: validRequest, Response: Response{Status: http.StatusOK, Headers: &http.Header{"Content-Type": []string{"application/json"}}, BodyFile: &bodyFile}}, string(expectedBodyFileData), http.StatusOK}, - {"valid imposter with not exists bodyFile", Imposter{Request: validRequest, Response: Response{Status: http.StatusOK, Headers: &http.Header{"Content-Type": []string{"application/json"}}, BodyFile: &bodyFileFake}}, "", http.StatusOK}, + {"valid imposter with body", Imposter{Request: validRequest, Response: Response{Status: http.StatusOK, Headers: &headers, Body: body}}, body, http.StatusOK}, + {"valid imposter with bodyFile", Imposter{Request: validRequest, Response: Response{Status: http.StatusOK, Headers: &headers, BodyFile: &bodyFile}}, string(expectedBodyFileData), http.StatusOK}, + {"valid imposter with not exists bodyFile", Imposter{Request: validRequest, Response: Response{Status: http.StatusOK, Headers: &headers, BodyFile: &bodyFileFake}}, "", http.StatusOK}, } for _, tt := range dataTest { @@ -56,7 +56,6 @@ func TestImposterHandler(t *testing.T) { if err != nil { t.Fatalf("could not created request: %v", err) } - req.Header = headers rec := httptest.NewRecorder() handler := http.HandlerFunc(ImposterHandler(tt.imposter)) @@ -111,52 +110,3 @@ func TestInvalidRequestWithSchema(t *testing.T) { }) } } - -func TestInvalidHeaders(t *testing.T) { - validRequest := []byte(`{ - "data": { - "type": "gophers", - "attributes": { - "name": "Zebediah", - "color": "Purple", - "age": 55 - } - } - }`) - 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") - - var wrongHeaders = make(http.Header) - wrongHeaders.Add("Content-Type", "application/json") - wrongHeaders.Add("Authorization", "Bearer bad gopher") - - var dataTest = []struct { - name string - imposter Imposter - statusCode int - headers http.Header - }{ - {"missing headers", Imposter{Request: Request{Method: "POST", Endpoint: "/gophers", SchemaFile: &schemaFile, Headers: &expectedHeaders}}, http.StatusBadRequest, make(http.Header)}, - {"wrong headers", Imposter{Request: Request{Method: "POST", Endpoint: "/gophers", SchemaFile: &schemaFile, Headers: &expectedHeaders}}, http.StatusBadRequest, wrongHeaders}, - } - - for _, tt := range dataTest { - t.Run(tt.name, func(t *testing.T) { - req, err := http.NewRequest("POST", "/gophers", bytes.NewBuffer(validRequest)) - if err != nil { - t.Fatalf("could not created request: %v", err) - } - req.Header = tt.headers - - rec := httptest.NewRecorder() - handler := http.HandlerFunc(ImposterHandler(tt.imposter)) - - handler.ServeHTTP(rec, req) - if status := rec.Code; status != tt.statusCode { - t.Fatalf("handler expected %d code and got: %d code", tt.statusCode, status) - } - }) - } -} diff --git a/imposter.go b/imposter.go index dbdd2cb..ea63002 100644 --- a/imposter.go +++ b/imposter.go @@ -1,7 +1,6 @@ package killgrave import ( - "net/http" "path" ) @@ -19,16 +18,16 @@ func (i *Imposter) CalculateFilePath(filePath string) string { // Request represent the structure of real request type Request struct { - Method string `json:"method"` - Endpoint string `json:"endpoint"` - SchemaFile *string `json:"schema_file"` - Headers *http.Header `json:"headers"` + Method string `json:"method"` + Endpoint string `json:"endpoint"` + SchemaFile *string `json:"schema_file"` + Headers *map[string]string `json:"headers"` } // 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 *map[string]string `json:"headers"` } diff --git a/server.go b/server.go index 45e11d6..0f9ddbc 100644 --- a/server.go +++ b/server.go @@ -52,9 +52,15 @@ func (s *Server) buildImposters() error { if imposter.Request.Endpoint == "" { continue } - s.router.HandleFunc(imposter.Request.Endpoint, ImposterHandler(imposter)). + r := s.router.HandleFunc(imposter.Request.Endpoint, ImposterHandler(imposter)). Methods(imposter.Request.Method). MatcherFunc(MatcherBySchema(imposter)) + + if imposter.Request.Headers != nil { + for k, v := range *imposter.Request.Headers { + r.HeadersRegexp(k, v) + } + } } return nil diff --git a/test/testdata/imposters/create_gopher.json b/test/testdata/imposters/create_gopher.json index 9e56775..b1c6a43 100644 --- a/test/testdata/imposters/create_gopher.json +++ b/test/testdata/imposters/create_gopher.json @@ -4,9 +4,7 @@ "endpoint": "/gophers", "schema_file": "schemas/create_gopher_request.json", "headers": { - "Content-Type": [ - "application/json" - ] + "Content-Type": "application/json" } }, "response": { From 6b90edf9440b800dcd8e1361867a12c6e4de14f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20P=C3=A9rez?= Date: Fri, 26 Apr 2019 15:47:17 +0200 Subject: [PATCH 03/13] Standarize json files using Google JSON style guide --- README.md | 6 +++--- imposter.go | 1 + test/testdata/imposters/create_gopher.json | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4003c50..1d664b4 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ imposters/create_gopher.json "request": { "method": "POST", "endpoint": "/gophers", - "schema_file": "schemas/create_gopher_request.json", + "schemaFile": "schemas/create_gopher_request.json", "headers": { "Content-Type": "application/json" } @@ -186,8 +186,8 @@ NOTE: If you want to use `killgrave` through Docker at the same time you use you ## Next Features - [ ] Dynamic responses based on query params -- [ ] Allow write multiples imposters by file -- [ ] Allow differents directories to organize your imposters +- [ ] Allow write multiple imposters by file +- [ ] Allow different directories to organize your imposters (create extension for imposters ".imp.json") - [ ] Proxy server - [ ] Record proxy server - [ ] Better documentation with examples of each feature diff --git a/imposter.go b/imposter.go index ea63002..7d1835b 100644 --- a/imposter.go +++ b/imposter.go @@ -21,6 +21,7 @@ type Request struct { Method string `json:"method"` Endpoint string `json:"endpoint"` SchemaFile *string `json:"schema_file"` + Params *map[string]string `json:"params"` Headers *map[string]string `json:"headers"` } diff --git a/test/testdata/imposters/create_gopher.json b/test/testdata/imposters/create_gopher.json index b1c6a43..ee90115 100644 --- a/test/testdata/imposters/create_gopher.json +++ b/test/testdata/imposters/create_gopher.json @@ -2,7 +2,7 @@ "request": { "method": "POST", "endpoint": "/gophers", - "schema_file": "schemas/create_gopher_request.json", + "schemaFile": "schemas/create_gopher_request.json", "headers": { "Content-Type": "application/json" } From fc9e342e86755ffee3e2833d01f96dcb05de037a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20P=C3=A9rez?= Date: Fri, 26 Apr 2019 16:21:22 +0200 Subject: [PATCH 04/13] Move to internal not exaposable API --- CHANGELOG.md | 3 +- cmd/killgrave/main.go | 3 +- error.go => internal/error.go | 0 handler.go => internal/handler.go | 0 handler_test.go => internal/handler_test.go | 0 imposter.go => internal/imposter.go | 3 +- .../route_matchers.go | 0 .../route_matchers_test.go | 0 server.go => internal/server.go | 0 server_test.go => internal/server_test.go | 0 .../malformatted_imposters/create_gopher.json | 0 test/testdata/imposters/create_gopher.json | 15 ------- .../imposters/no_parseable_imposter.json | 3 -- .../responses/create_gopher_response.json | 11 ----- .../schemas/create_gopher_request.json | 42 ------------------- .../imposters/schemas/type_gopher.json | 12 ------ .../imposters/schemas/type_gopher_fail.json | 12 ------ 17 files changed, 4 insertions(+), 100 deletions(-) rename error.go => internal/error.go (100%) rename handler.go => internal/handler.go (100%) rename handler_test.go => internal/handler_test.go (100%) rename imposter.go => internal/imposter.go (89%) rename route_matchers.go => internal/route_matchers.go (100%) rename route_matchers_test.go => internal/route_matchers_test.go (100%) rename server.go => internal/server.go (100%) rename server_test.go => internal/server_test.go (100%) rename {test => internal/test}/testdata/malformatted_imposters/create_gopher.json (100%) delete mode 100644 test/testdata/imposters/create_gopher.json delete mode 100644 test/testdata/imposters/no_parseable_imposter.json delete mode 100644 test/testdata/imposters/responses/create_gopher_response.json delete mode 100644 test/testdata/imposters/schemas/create_gopher_request.json delete mode 100644 test/testdata/imposters/schemas/type_gopher.json delete mode 100644 test/testdata/imposters/schemas/type_gopher_fail.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b68db9..4978bdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,4 +24,5 @@ ## v0.3.0 (2019/??/??) -* Dynamic responses based on headers \ No newline at end of file +* Dynamic responses based on headers +* Standarize json files using [Google JSON style Guide](https://google.github.io/styleguide/jsoncstyleguide.xml) \ No newline at end of file diff --git a/cmd/killgrave/main.go b/cmd/killgrave/main.go index faf5d9a..4362482 100644 --- a/cmd/killgrave/main.go +++ b/cmd/killgrave/main.go @@ -6,8 +6,7 @@ import ( "log" "net/http" - "github.com/friendsofgo/killgrave" - + killgrave "github.com/friendsofgo/killgrave/internal" "github.com/gorilla/mux" ) diff --git a/error.go b/internal/error.go similarity index 100% rename from error.go rename to internal/error.go diff --git a/handler.go b/internal/handler.go similarity index 100% rename from handler.go rename to internal/handler.go diff --git a/handler_test.go b/internal/handler_test.go similarity index 100% rename from handler_test.go rename to internal/handler_test.go diff --git a/imposter.go b/internal/imposter.go similarity index 89% rename from imposter.go rename to internal/imposter.go index 7d1835b..d132531 100644 --- a/imposter.go +++ b/internal/imposter.go @@ -20,8 +20,7 @@ func (i *Imposter) CalculateFilePath(filePath string) string { type Request struct { Method string `json:"method"` Endpoint string `json:"endpoint"` - SchemaFile *string `json:"schema_file"` - Params *map[string]string `json:"params"` + SchemaFile *string `json:"schemaFile"` Headers *map[string]string `json:"headers"` } diff --git a/route_matchers.go b/internal/route_matchers.go similarity index 100% rename from route_matchers.go rename to internal/route_matchers.go diff --git a/route_matchers_test.go b/internal/route_matchers_test.go similarity index 100% rename from route_matchers_test.go rename to internal/route_matchers_test.go diff --git a/server.go b/internal/server.go similarity index 100% rename from server.go rename to internal/server.go diff --git a/server_test.go b/internal/server_test.go similarity index 100% rename from server_test.go rename to internal/server_test.go diff --git a/test/testdata/malformatted_imposters/create_gopher.json b/internal/test/testdata/malformatted_imposters/create_gopher.json similarity index 100% rename from test/testdata/malformatted_imposters/create_gopher.json rename to internal/test/testdata/malformatted_imposters/create_gopher.json diff --git a/test/testdata/imposters/create_gopher.json b/test/testdata/imposters/create_gopher.json deleted file mode 100644 index ee90115..0000000 --- a/test/testdata/imposters/create_gopher.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "request": { - "method": "POST", - "endpoint": "/gophers", - "schemaFile": "schemas/create_gopher_request.json", - "headers": { - "Content-Type": "application/json" - } - }, - "response": { - "status": 200, - "content_type": "application/json", - "bodyFile": "responses/create_gopher_response.json" - } -} \ No newline at end of file diff --git a/test/testdata/imposters/no_parseable_imposter.json b/test/testdata/imposters/no_parseable_imposter.json deleted file mode 100644 index 9f2b2b0..0000000 --- a/test/testdata/imposters/no_parseable_imposter.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "t": "random_text" -} \ No newline at end of file diff --git a/test/testdata/imposters/responses/create_gopher_response.json b/test/testdata/imposters/responses/create_gopher_response.json deleted file mode 100644 index b5dc5c3..0000000 --- a/test/testdata/imposters/responses/create_gopher_response.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "data": { - "type": "gophers", - "id": "01D8EMQ185CA8PRGE20DKZTGSR", - "attributes": { - "name": "Zebediah", - "color": "Purple", - "age": 54 - } - } -} diff --git a/test/testdata/imposters/schemas/create_gopher_request.json b/test/testdata/imposters/schemas/create_gopher_request.json deleted file mode 100644 index ce3d7ce..0000000 --- a/test/testdata/imposters/schemas/create_gopher_request.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "gophers" - ] - }, - "attributes": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "color": { - "type": "string" - }, - "age": { - "type": "integer" - } - }, - "required": [ - "name", - "color", - "age" - ] - } - }, - "required": [ - "type", - "attributes" - ] - } - }, - "required": [ - "data" - ] -} diff --git a/test/testdata/imposters/schemas/type_gopher.json b/test/testdata/imposters/schemas/type_gopher.json deleted file mode 100644 index 8467b21..0000000 --- a/test/testdata/imposters/schemas/type_gopher.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "type": "string", - "pattern": "^(gopher)$" - } - } -} diff --git a/test/testdata/imposters/schemas/type_gopher_fail.json b/test/testdata/imposters/schemas/type_gopher_fail.json deleted file mode 100644 index 9cfb314..0000000 --- a/test/testdata/imposters/schemas/type_gopher_fail.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "type": "error", - "required": [ - "type" - ], - "properties": { - "type": { - "type": "string", - "pattern": "^(gopher)$" - } - } -} From 86d9efbacdb78970cff219e1c93e315ce768485a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20P=C3=A9rez?= Date: Fri, 26 Apr 2019 16:35:13 +0200 Subject: [PATCH 05/13] Move to internal not exaposable API --- .gitignore | 4 +- .../testdata/imposters/create_gopher.json | 18 ++++++++ .../imposters/no_parseable_imposter.json | 3 ++ .../responses/create_gopher_response.json | 11 +++++ .../schemas/create_gopher_request.json | 42 +++++++++++++++++++ .../imposters/schemas/type_gopher.json | 12 ++++++ .../imposters/schemas/type_gopher_fail.json | 12 ++++++ 7 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 internal/test/testdata/imposters/create_gopher.json create mode 100644 internal/test/testdata/imposters/no_parseable_imposter.json create mode 100644 internal/test/testdata/imposters/responses/create_gopher_response.json create mode 100644 internal/test/testdata/imposters/schemas/create_gopher_request.json create mode 100644 internal/test/testdata/imposters/schemas/type_gopher.json create mode 100644 internal/test/testdata/imposters/schemas/type_gopher_fail.json diff --git a/.gitignore b/.gitignore index 6b4d7bb..e18b3ee 100644 --- a/.gitignore +++ b/.gitignore @@ -14,5 +14,5 @@ imposters schemas -!test/testdata/imposters/ -!test/testdata/imposters/schemas/ +!internal/test/testdata/imposters/ +!internal/test/testdata/imposters/schemas/ diff --git a/internal/test/testdata/imposters/create_gopher.json b/internal/test/testdata/imposters/create_gopher.json new file mode 100644 index 0000000..a5e92cc --- /dev/null +++ b/internal/test/testdata/imposters/create_gopher.json @@ -0,0 +1,18 @@ +{ + "request": { + "method": "POST", + "endpoint": "/gophers", + "schemaFile": "schemas/create_gopher_request.json", + "headers": { + "Content-Type": "application/json" + }, + "params": { + "gopherColor": "{v:[a-z]+}" + } + }, + "response": { + "status": 200, + "content_type": "application/json", + "bodyFile": "responses/create_gopher_response.json" + } +} \ No newline at end of file diff --git a/internal/test/testdata/imposters/no_parseable_imposter.json b/internal/test/testdata/imposters/no_parseable_imposter.json new file mode 100644 index 0000000..9f2b2b0 --- /dev/null +++ b/internal/test/testdata/imposters/no_parseable_imposter.json @@ -0,0 +1,3 @@ +{ + "t": "random_text" +} \ No newline at end of file diff --git a/internal/test/testdata/imposters/responses/create_gopher_response.json b/internal/test/testdata/imposters/responses/create_gopher_response.json new file mode 100644 index 0000000..b5dc5c3 --- /dev/null +++ b/internal/test/testdata/imposters/responses/create_gopher_response.json @@ -0,0 +1,11 @@ +{ + "data": { + "type": "gophers", + "id": "01D8EMQ185CA8PRGE20DKZTGSR", + "attributes": { + "name": "Zebediah", + "color": "Purple", + "age": 54 + } + } +} diff --git a/internal/test/testdata/imposters/schemas/create_gopher_request.json b/internal/test/testdata/imposters/schemas/create_gopher_request.json new file mode 100644 index 0000000..ce3d7ce --- /dev/null +++ b/internal/test/testdata/imposters/schemas/create_gopher_request.json @@ -0,0 +1,42 @@ +{ + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "gophers" + ] + }, + "attributes": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "color": { + "type": "string" + }, + "age": { + "type": "integer" + } + }, + "required": [ + "name", + "color", + "age" + ] + } + }, + "required": [ + "type", + "attributes" + ] + } + }, + "required": [ + "data" + ] +} diff --git a/internal/test/testdata/imposters/schemas/type_gopher.json b/internal/test/testdata/imposters/schemas/type_gopher.json new file mode 100644 index 0000000..8467b21 --- /dev/null +++ b/internal/test/testdata/imposters/schemas/type_gopher.json @@ -0,0 +1,12 @@ +{ + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "pattern": "^(gopher)$" + } + } +} diff --git a/internal/test/testdata/imposters/schemas/type_gopher_fail.json b/internal/test/testdata/imposters/schemas/type_gopher_fail.json new file mode 100644 index 0000000..9cfb314 --- /dev/null +++ b/internal/test/testdata/imposters/schemas/type_gopher_fail.json @@ -0,0 +1,12 @@ +{ + "type": "error", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "pattern": "^(gopher)$" + } + } +} From 6be2b3005c2f7c1811c4e44aede94d400194895b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20P=C3=A9rez?= Date: Fri, 26 Apr 2019 16:37:10 +0200 Subject: [PATCH 06/13] Dynamic responses based on query params. Fixes #2 --- CHANGELOG.md | 4 +++- README.md | 2 +- internal/imposter.go | 1 + internal/server.go | 6 ++++++ 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4978bdd..a384ab5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,4 +25,6 @@ ## v0.3.0 (2019/??/??) * Dynamic responses based on headers -* Standarize json files using [Google JSON style Guide](https://google.github.io/styleguide/jsoncstyleguide.xml) \ No newline at end of file +* Standarize json files using [Google JSON style Guide](https://google.github.io/styleguide/jsoncstyleguide.xml) +* Move to `internal` not exposable API +* Dynamic responses based on query params \ No newline at end of file diff --git a/README.md b/README.md index 1d664b4..5993f29 100644 --- a/README.md +++ b/README.md @@ -183,9 +183,9 @@ NOTE: If you want to use `killgrave` through Docker at the same time you use you * Allow imposter's matching by request schema * Dynamic responses based on regex endpoint or request schema * Dynamic responses based on headers +* Dynamic responses based on query params ## Next Features -- [ ] Dynamic responses based on query params - [ ] Allow write multiple imposters by file - [ ] Allow different directories to organize your imposters (create extension for imposters ".imp.json") - [ ] Proxy server diff --git a/internal/imposter.go b/internal/imposter.go index d132531..9d6b810 100644 --- a/internal/imposter.go +++ b/internal/imposter.go @@ -21,6 +21,7 @@ type Request struct { Method string `json:"method"` Endpoint string `json:"endpoint"` SchemaFile *string `json:"schemaFile"` + Params *map[string]string `json:"params"` Headers *map[string]string `json:"headers"` } diff --git a/internal/server.go b/internal/server.go index 0f9ddbc..c997c93 100644 --- a/internal/server.go +++ b/internal/server.go @@ -61,6 +61,12 @@ func (s *Server) buildImposters() error { r.HeadersRegexp(k, v) } } + + if imposter.Request.Params != nil { + for k, v := range *imposter.Request.Params { + r.Queries(k, v) + } + } } return nil From d1fd157359e51a13edae5e39f52b4d74ea99d3e4 Mon Sep 17 00:00:00 2001 From: aperezg Date: Sat, 27 Apr 2019 11:04:46 +0200 Subject: [PATCH 07/13] Allow organize your imposters with structured folders with new .imp.json file extension --- CHANGELOG.md | 3 +- README.md | 1 + internal/imposter.go | 20 +++++ internal/server.go | 82 +++++++++++-------- internal/server_test.go | 6 +- ...ate_gopher.json => create_gopher.imp.json} | 0 ...er.json => no_parseable_imposter.imp.json} | 0 ...ate_gopher.json => create_gopher.imp.json} | 0 8 files changed, 70 insertions(+), 42 deletions(-) rename internal/test/testdata/imposters/{create_gopher.json => create_gopher.imp.json} (100%) rename internal/test/testdata/imposters/{no_parseable_imposter.json => no_parseable_imposter.imp.json} (100%) rename internal/test/testdata/malformatted_imposters/{create_gopher.json => create_gopher.imp.json} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index a384ab5..d5dfca7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,4 +27,5 @@ * Dynamic responses based on headers * Standarize json files using [Google JSON style Guide](https://google.github.io/styleguide/jsoncstyleguide.xml) * Move to `internal` not exposable API -* Dynamic responses based on query params \ No newline at end of file +* Dynamic responses based on query params +* Allow organize your imposters with structured folders diff --git a/README.md b/README.md index 5993f29..93520a1 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,7 @@ NOTE: If you want to use `killgrave` through Docker at the same time you use you * Dynamic responses based on regex endpoint or request schema * Dynamic responses based on headers * Dynamic responses based on query params +* Allow organize your imposters with structured folders ## Next Features - [ ] Allow write multiple imposters by file diff --git a/internal/imposter.go b/internal/imposter.go index 9d6b810..85ba69b 100644 --- a/internal/imposter.go +++ b/internal/imposter.go @@ -1,9 +1,16 @@ package killgrave import ( + "os" "path" + "path/filepath" + "strings" + + "github.com/pkg/errors" ) +const imposterExtension = ".imp.json" + // Imposter define an imposter structure type Imposter struct { BasePath string @@ -32,3 +39,16 @@ type Response struct { BodyFile *string `json:"bodyFile"` Headers *map[string]string `json:"headers"` } + +func findImposters(impostersDirectory string, imposterFileCh chan string) error { + err := filepath.Walk(impostersDirectory, func(path string, info os.FileInfo, err error) error { + if err != nil { + return errors.Wrap(err, "error finding imposters") + } + if !info.IsDir() && strings.LastIndex(info.Name(), imposterExtension) != -1 { + imposterFileCh <- path + } + return nil + }) + return err +} diff --git a/internal/server.go b/internal/server.go index c997c93..6117120 100644 --- a/internal/server.go +++ b/internal/server.go @@ -4,9 +4,12 @@ import ( "encoding/json" "fmt" "io/ioutil" + "log" "os" + "path/filepath" "github.com/gorilla/mux" + "github.com/pkg/errors" ) // Server definition of mock server @@ -29,59 +32,66 @@ func (s *Server) Run() error { if _, err := os.Stat(s.impostersPath); os.IsNotExist(err) { return invalidDirectoryError(fmt.Sprintf("the directory %s doesn't exists", s.impostersPath)) } - if err := s.buildImposters(); err != nil { - return err + var imposterFileCh = make(chan string) + var done = make(chan bool) + + go func() { + findImposters(s.impostersPath, imposterFileCh) + done <- true + }() + for { + select { + case f := <-imposterFileCh: + var imposter Imposter + err := s.buildImposter(f, &imposter) + if err != nil { + log.Printf("error trying to load %s imposter: %v", f, err) + } else { + if err := s.createImposterHandler(imposter); err != nil { + log.Printf("%v on %s", err, f) + break + } + log.Printf("imposter %s loaded\n", f) + } + case <-done: + close(imposterFileCh) + close(done) + return nil + } } - - return nil } -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 - } - - if imposter.Request.Endpoint == "" { - continue - } - r := s.router.HandleFunc(imposter.Request.Endpoint, ImposterHandler(imposter)). - Methods(imposter.Request.Method). - MatcherFunc(MatcherBySchema(imposter)) +func (s *Server) createImposterHandler(imposter Imposter) error { + if imposter.Request.Endpoint == "" { + return errors.New("the request.endpoint file is required for an valid imposter") + } + r := s.router.HandleFunc(imposter.Request.Endpoint, ImposterHandler(imposter)). + Methods(imposter.Request.Method). + MatcherFunc(MatcherBySchema(imposter)) - if imposter.Request.Headers != nil { - for k, v := range *imposter.Request.Headers { - r.HeadersRegexp(k, v) - } + if imposter.Request.Headers != nil { + for k, v := range *imposter.Request.Headers { + r.HeadersRegexp(k, v) } + } - if imposter.Request.Params != nil { - for k, v := range *imposter.Request.Params { - r.Queries(k, v) - } + if imposter.Request.Params != nil { + for k, v := range *imposter.Request.Params { + r.Queries(k, v) } } - return nil } func (s *Server) buildImposter(imposterFileName string, imposter *Imposter) error { - f := s.impostersPath + "/" + imposterFileName - imposterFile, _ := os.Open(f) + imposterFile, _ := os.Open(imposterFileName) defer imposterFile.Close() bytes, _ := ioutil.ReadAll(imposterFile) if err := json.Unmarshal(bytes, imposter); err != nil { - return malformattedImposterError(fmt.Sprintf("error while unmarshall imposter file %s", f)) + return malformattedImposterError(fmt.Sprintf("error while unmarshall imposter file %s", imposterFileName)) } - imposter.BasePath = s.impostersPath + imposter.BasePath = filepath.Dir(imposterFileName) return nil } diff --git a/internal/server_test.go b/internal/server_test.go index 35e063f..c87f7ca 100644 --- a/internal/server_test.go +++ b/internal/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_imposters", nil), malformattedImposterError("error")}, + {"malformatted json", NewServer("test/testdata/malformatted_imposters", nil), nil}, {"valid imposter", NewServer("test/testdata/imposters", mux.NewRouter()), nil}, } @@ -37,10 +37,6 @@ func TestRunServer(t *testing.T) { if _, ok := (tt.err).(invalidDirectoryError); !ok { t.Fatalf("expected invalidDirectoryError got %+v", err) } - case malformattedImposterError: - if _, ok := (tt.err).(malformattedImposterError); !ok { - t.Fatalf("expected malformattedImpoasterError got %+v", err) - } default: t.Fatalf("not recognize error %+v", err) } diff --git a/internal/test/testdata/imposters/create_gopher.json b/internal/test/testdata/imposters/create_gopher.imp.json similarity index 100% rename from internal/test/testdata/imposters/create_gopher.json rename to internal/test/testdata/imposters/create_gopher.imp.json diff --git a/internal/test/testdata/imposters/no_parseable_imposter.json b/internal/test/testdata/imposters/no_parseable_imposter.imp.json similarity index 100% rename from internal/test/testdata/imposters/no_parseable_imposter.json rename to internal/test/testdata/imposters/no_parseable_imposter.imp.json diff --git a/internal/test/testdata/malformatted_imposters/create_gopher.json b/internal/test/testdata/malformatted_imposters/create_gopher.imp.json similarity index 100% rename from internal/test/testdata/malformatted_imposters/create_gopher.json rename to internal/test/testdata/malformatted_imposters/create_gopher.imp.json From f09668c31d3111515daa4a3c541463db362fecbe Mon Sep 17 00:00:00 2001 From: aperezg Date: Sat, 27 Apr 2019 11:22:16 +0200 Subject: [PATCH 08/13] Remove unnecessary sentinel errors --- internal/error.go | 9 --------- internal/server.go | 5 ++--- internal/server_test.go | 12 ++---------- 3 files changed, 4 insertions(+), 22 deletions(-) delete mode 100644 internal/error.go diff --git a/internal/error.go b/internal/error.go deleted file mode 100644 index e66c94f..0000000 --- a/internal/error.go +++ /dev/null @@ -1,9 +0,0 @@ -package killgrave - -type invalidDirectoryError string - -func (e invalidDirectoryError) Error() string { return string(e) } - -type malformattedImposterError string - -func (e malformattedImposterError) Error() string { return string(e) } diff --git a/internal/server.go b/internal/server.go index 6117120..550a215 100644 --- a/internal/server.go +++ b/internal/server.go @@ -2,7 +2,6 @@ package killgrave import ( "encoding/json" - "fmt" "io/ioutil" "log" "os" @@ -30,7 +29,7 @@ func NewServer(p string, r *mux.Router) *Server { // handlers for each imposter func (s *Server) Run() error { if _, err := os.Stat(s.impostersPath); os.IsNotExist(err) { - return invalidDirectoryError(fmt.Sprintf("the directory %s doesn't exists", s.impostersPath)) + return errors.Wrapf(err, "the directory %s doesn't exists", s.impostersPath) } var imposterFileCh = make(chan string) var done = make(chan bool) @@ -89,7 +88,7 @@ func (s *Server) buildImposter(imposterFileName string, imposter *Imposter) erro bytes, _ := ioutil.ReadAll(imposterFile) if err := json.Unmarshal(bytes, imposter); err != nil { - return malformattedImposterError(fmt.Sprintf("error while unmarshall imposter file %s", imposterFileName)) + return errors.Wrapf(err, "error while unmarshall imposter file %s", imposterFileName) } imposter.BasePath = filepath.Dir(imposterFileName) diff --git a/internal/server_test.go b/internal/server_test.go index c87f7ca..c13f3d6 100644 --- a/internal/server_test.go +++ b/internal/server_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/gorilla/mux" + "github.com/pkg/errors" ) func TestRunServer(t *testing.T) { @@ -12,7 +13,7 @@ func TestRunServer(t *testing.T) { server *Server err error }{ - {"imposter directory not found", NewServer("failImposterPath", nil), invalidDirectoryError("error")}, + {"imposter directory not found", NewServer("failImposterPath", nil), errors.New("hello")}, {"malformatted json", NewServer("test/testdata/malformatted_imposters", nil), nil}, {"valid imposter", NewServer("test/testdata/imposters", mux.NewRouter()), nil}, } @@ -31,15 +32,6 @@ func TestRunServer(t *testing.T) { if tt.err == nil { t.Fatalf("not expected any erros and got %+v", err) } - - switch err.(type) { - case invalidDirectoryError: - if _, ok := (tt.err).(invalidDirectoryError); !ok { - t.Fatalf("expected invalidDirectoryError got %+v", err) - } - default: - t.Fatalf("not recognize error %+v", err) - } } }) } From 9af4f8e156fa8e9079544c45238787f0209b6c9b Mon Sep 17 00:00:00 2001 From: aperezg Date: Sat, 27 Apr 2019 12:59:24 +0200 Subject: [PATCH 09/13] Allow write multiple imposters by file --- CHANGELOG.md | 5 +- README.md | 61 +++++++++++++------ internal/server.go | 44 ++++++------- .../testdata/imposters/create_gopher.imp.json | 35 ++++++----- .../imposters/no_parseable_imposter.imp.json | 3 - .../create_gopher.imp.json | 10 +-- 6 files changed, 90 insertions(+), 68 deletions(-) delete mode 100644 internal/test/testdata/imposters/no_parseable_imposter.imp.json diff --git a/CHANGELOG.md b/CHANGELOG.md index d5dfca7..571edab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,10 +22,11 @@ * Calculate files directory(body and schema) based on imposters path * Update REAMDE.md with resolved features and new future features -## v0.3.0 (2019/??/??) +## v0.3.0 (2019/04/27) * Dynamic responses based on headers * Standarize json files using [Google JSON style Guide](https://google.github.io/styleguide/jsoncstyleguide.xml) * Move to `internal` not exposable API * Dynamic responses based on query params -* Allow organize your imposters with structured folders +* Allow organize your imposters with structured folders (using new extension `.imp.json`) +* Allow write multiple imposters by file diff --git a/README.md b/README.md index 93520a1..707750b 100644 --- a/README.md +++ b/README.md @@ -53,30 +53,51 @@ Or custome your server with this flags: ## How to use ### Create an imposter -You must be create an imposter to start to use the application +You must be create an imposter to start to use the application, only files with the `.imp.json` extension will be interpreted as imposters files, and the base path for +the rest of the files will be the path of the `.imp.json` file are. -```json -imposters/create_gopher.json +You need to organize your imposters from more restrictive to less. -{ - "request": { - "method": "POST", - "endpoint": "/gophers", - "schemaFile": "schemas/create_gopher_request.json", - "headers": { - "Content-Type": "application/json" +```json +imposters/create_gopher.imp.json + +[ + { + "request": { + "method": "POST", + "endpoint": "/gophers", + "schemaFile": "schemas/create_gopher_request.json", + "headers": { + "Content-Type": "application/json", + "Return-Error": "error" + } + }, + "response": { + "status": 500, + "headers": { + "Content-Type": "application/json" + }, + "body": "{\"error\": \"Server error ocurred\"}" } }, - "response": { - "status": 200, - "headers": { - "Content-Type": [ - "application/json" - ] + { + "request": { + "method": "POST", + "endpoint": "/gophers", + "schemaFile": "schemas/create_gopher_request.json", + "headers": { + "Content-Type": "application/json" + } }, - "bodyFile": "responses/create_gopher_response.json" + "response": { + "status": 200, + "headers": { + "Content-Type": "application/json" + }, + "bodyFile": "responses/create_gopher_response.json" + } } -} +] ``` And its related files @@ -185,13 +206,13 @@ NOTE: If you want to use `killgrave` through Docker at the same time you use you * Dynamic responses based on headers * Dynamic responses based on query params * Allow organize your imposters with structured folders +* Allow write multiple imposters by file ## Next Features -- [ ] Allow write multiple imposters by file -- [ ] Allow different directories to organize your imposters (create extension for imposters ".imp.json") - [ ] Proxy server - [ ] Record proxy server - [ ] Better documentation with examples of each feature +- [ ] Validate request body XML ## Contributing [Contributions](https://github.com/friendsofgo/killgrave/issues?q=is%3Aissue+is%3Aopen) are more than welcome, if you are interested please fork this repo and send your Pull Request. diff --git a/internal/server.go b/internal/server.go index 550a215..fa8f8fe 100644 --- a/internal/server.go +++ b/internal/server.go @@ -41,15 +41,12 @@ func (s *Server) Run() error { for { select { case f := <-imposterFileCh: - var imposter Imposter - err := s.buildImposter(f, &imposter) + var imposters []Imposter + err := s.unmarshalImposters(f, &imposters) if err != nil { log.Printf("error trying to load %s imposter: %v", f, err) } else { - if err := s.createImposterHandler(imposter); err != nil { - log.Printf("%v on %s", err, f) - break - } + s.createImposterHandler(imposters, f) log.Printf("imposter %s loaded\n", f) } case <-done: @@ -60,37 +57,34 @@ func (s *Server) Run() error { } } -func (s *Server) createImposterHandler(imposter Imposter) error { - if imposter.Request.Endpoint == "" { - return errors.New("the request.endpoint file is required for an valid imposter") - } - r := s.router.HandleFunc(imposter.Request.Endpoint, ImposterHandler(imposter)). - Methods(imposter.Request.Method). - MatcherFunc(MatcherBySchema(imposter)) +func (s *Server) createImposterHandler(imposters []Imposter, imposterFilePath string) { + for _, imposter := range imposters { + imposter.BasePath = filepath.Dir(imposterFilePath) + r := s.router.HandleFunc(imposter.Request.Endpoint, ImposterHandler(imposter)). + Methods(imposter.Request.Method). + MatcherFunc(MatcherBySchema(imposter)) - if imposter.Request.Headers != nil { - for k, v := range *imposter.Request.Headers { - r.HeadersRegexp(k, v) + if imposter.Request.Headers != nil { + for k, v := range *imposter.Request.Headers { + r.HeadersRegexp(k, v) + } } - } - if imposter.Request.Params != nil { - for k, v := range *imposter.Request.Params { - r.Queries(k, v) + if imposter.Request.Params != nil { + for k, v := range *imposter.Request.Params { + r.Queries(k, v) + } } } - return nil } -func (s *Server) buildImposter(imposterFileName string, imposter *Imposter) error { +func (s *Server) unmarshalImposters(imposterFileName string, imposters *[]Imposter) error { imposterFile, _ := os.Open(imposterFileName) defer imposterFile.Close() bytes, _ := ioutil.ReadAll(imposterFile) - if err := json.Unmarshal(bytes, imposter); err != nil { + if err := json.Unmarshal(bytes, imposters); err != nil { return errors.Wrapf(err, "error while unmarshall imposter file %s", imposterFileName) } - imposter.BasePath = filepath.Dir(imposterFileName) - return nil } diff --git a/internal/test/testdata/imposters/create_gopher.imp.json b/internal/test/testdata/imposters/create_gopher.imp.json index a5e92cc..91bd9a5 100644 --- a/internal/test/testdata/imposters/create_gopher.imp.json +++ b/internal/test/testdata/imposters/create_gopher.imp.json @@ -1,18 +1,25 @@ -{ - "request": { - "method": "POST", - "endpoint": "/gophers", - "schemaFile": "schemas/create_gopher_request.json", - "headers": { - "Content-Type": "application/json" +[ + { + "request": { + "method": "POST", + "endpoint": "/gophers", + "schemaFile": "schemas/create_gopher_request.json", + "headers": { + "Content-Type": "application/json" + }, + "params": { + "gopherColor": "{v:[a-z]+}" + } }, - "params": { - "gopherColor": "{v:[a-z]+}" + "response": { + "status": 200, + "headers": { + "Content-Type": "application/json" + }, + "bodyFile": "responses/create_gopher_response.json" } }, - "response": { - "status": 200, - "content_type": "application/json", - "bodyFile": "responses/create_gopher_response.json" + { + "t": "random_text" } -} \ No newline at end of file +] diff --git a/internal/test/testdata/imposters/no_parseable_imposter.imp.json b/internal/test/testdata/imposters/no_parseable_imposter.imp.json deleted file mode 100644 index 9f2b2b0..0000000 --- a/internal/test/testdata/imposters/no_parseable_imposter.imp.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "t": "random_text" -} \ No newline at end of file diff --git a/internal/test/testdata/malformatted_imposters/create_gopher.imp.json b/internal/test/testdata/malformatted_imposters/create_gopher.imp.json index fca3d24..b811a0f 100644 --- a/internal/test/testdata/malformatted_imposters/create_gopher.imp.json +++ b/internal/test/testdata/malformatted_imposters/create_gopher.imp.json @@ -1,5 +1,7 @@ -{ - "request": { - "endpoint": 2222 +[ + { + "request": { + "endpoint": 2222 + } } -} \ No newline at end of file +] From aa5db38d5e2bc8d1e54ca2e03b7db5b563fae80c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20L=C3=B3pez=20de=20la=20Franca=20Beltran?= Date: Sun, 28 Apr 2019 12:36:50 +0200 Subject: [PATCH 10/13] Apply suggestions from code review Co-Authored-By: aperezg --- README.md | 2 +- internal/route_matchers.go | 2 +- internal/server.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 707750b..709eaa0 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Or custome your server with this flags: ### Create an imposter You must be create an imposter to start to use the application, only files with the `.imp.json` extension will be interpreted as imposters files, and the base path for -the rest of the files will be the path of the `.imp.json` file are. +the rest of the files will be the path of the `.imp.json` file. You need to organize your imposters from more restrictive to less. diff --git a/internal/route_matchers.go b/internal/route_matchers.go index a4abe14..bb410a9 100644 --- a/internal/route_matchers.go +++ b/internal/route_matchers.go @@ -51,7 +51,7 @@ func validateSchema(imposter Imposter, req *http.Request) error { contentBody := string(b) if contentBody == "" { - return fmt.Errorf("expected an body request and got empy body request") + return fmt.Errorf("unexpected empty body request") } dir, _ := os.Getwd() diff --git a/internal/server.go b/internal/server.go index fa8f8fe..7ca58c1 100644 --- a/internal/server.go +++ b/internal/server.go @@ -46,7 +46,7 @@ func (s *Server) Run() error { if err != nil { log.Printf("error trying to load %s imposter: %v", f, err) } else { - s.createImposterHandler(imposters, f) + s.addImposterHandler(imposters, f) log.Printf("imposter %s loaded\n", f) } case <-done: @@ -57,7 +57,7 @@ func (s *Server) Run() error { } } -func (s *Server) createImposterHandler(imposters []Imposter, imposterFilePath string) { +func (s *Server) addImposterHandler(imposters []Imposter, imposterFilePath string) { for _, imposter := range imposters { imposter.BasePath = filepath.Dir(imposterFilePath) r := s.router.HandleFunc(imposter.Request.Endpoint, ImposterHandler(imposter)). From a00c9b3cb6fee9f35b7f3c7b202d2ae620d81094 Mon Sep 17 00:00:00 2001 From: aperezg Date: Sun, 28 Apr 2019 13:21:03 +0200 Subject: [PATCH 11/13] Fix PR comments --- README.md | 2 +- cmd/killgrave/main.go | 2 +- internal/route_matchers_test.go | 12 +++++++----- internal/server.go | 4 ++-- internal/server_test.go | 2 +- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 709eaa0..a13284f 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ Or custome your server with this flags: You must be create an imposter to start to use the application, only files with the `.imp.json` extension will be interpreted as imposters files, and the base path for the rest of the files will be the path of the `.imp.json` file. -You need to organize your imposters from more restrictive to less. +You need to organize your imposters from more restrictive to less. We use a rule-based system for create each imposter, for this reason you need to organize your imposters in the way that more restrictive to less, like the example below. ```json imposters/create_gopher.imp.json diff --git a/cmd/killgrave/main.go b/cmd/killgrave/main.go index 4362482..c141cd2 100644 --- a/cmd/killgrave/main.go +++ b/cmd/killgrave/main.go @@ -30,7 +30,7 @@ func main() { r := mux.NewRouter() s := killgrave.NewServer(*imposters, r) - if err := s.Run(); err != nil { + if err := s.Build(); err != nil { log.Fatal(err) } diff --git a/internal/route_matchers_test.go b/internal/route_matchers_test.go index e9f9940..21081f4 100644 --- a/internal/route_matchers_test.go +++ b/internal/route_matchers_test.go @@ -42,6 +42,8 @@ func TestMatcherBySchema(t *testing.T) { SchemaFile: &schemeFailFile, } + httpRequestA := &http.Request{Body: bodyA} + httpRequestB := &http.Request{Body: bodyB} okResponse := Response{Status: http.StatusOK} var matcherData = []struct { @@ -50,11 +52,11 @@ func TestMatcherBySchema(t *testing.T) { 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}, - {"malformatted schema file", MatcherBySchema(Imposter{Request: requestWithWrongSchema, Response: okResponse}), &http.Request{Body: bodyB}, false}, + {"correct request schema", MatcherBySchema(Imposter{Request: requestWithSchema, Response: okResponse}), httpRequestA, true}, + {"imposter without request schema", MatcherBySchema(Imposter{Request: requestWithoutSchema, Response: okResponse}), httpRequestA, true}, + {"malformatted schema file", MatcherBySchema(Imposter{Request: requestWithWrongSchema, Response: okResponse}), httpRequestA, false}, + {"incorrect request schema", MatcherBySchema(Imposter{Request: requestWithSchema, Response: okResponse}), httpRequestB, false}, + {"non-existing schema file", MatcherBySchema(Imposter{Request: requestWithNonExistingSchema, Response: okResponse}), httpRequestB, false}, {"empty body with required schema file", MatcherBySchema(Imposter{Request: requestWithSchema, Response: okResponse}), &http.Request{Body: emptyBody}, false}, } diff --git a/internal/server.go b/internal/server.go index 7ca58c1..c69c38f 100644 --- a/internal/server.go +++ b/internal/server.go @@ -25,9 +25,9 @@ func NewServer(p string, r *mux.Router) *Server { } } -// Run read all the files on the impostersPath and creates different +// Build read all the files on the impostersPath and add different // handlers for each imposter -func (s *Server) Run() error { +func (s *Server) Build() error { if _, err := os.Stat(s.impostersPath); os.IsNotExist(err) { return errors.Wrapf(err, "the directory %s doesn't exists", s.impostersPath) } diff --git a/internal/server_test.go b/internal/server_test.go index c13f3d6..8a6f82d 100644 --- a/internal/server_test.go +++ b/internal/server_test.go @@ -20,7 +20,7 @@ func TestRunServer(t *testing.T) { for _, tt := range serverData { t.Run(tt.name, func(t *testing.T) { - err := tt.server.Run() + err := tt.server.Build() if err == nil { if tt.err != nil { From e358a8a82917b4efdcf6b5ce7cbfe3849ac030bb Mon Sep 17 00:00:00 2001 From: aperezg Date: Sun, 28 Apr 2019 13:33:11 +0200 Subject: [PATCH 12/13] Propagate public env vars --- .circleci/config.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 11ea439..61407d8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,6 +3,8 @@ jobs: build-go1_11_6: docker: - image: circleci/golang:1.11.6 + environment: + GO111MODULE: "on" working_directory: /go/src/github.com/friendsofgo/killgrave steps: - checkout @@ -11,6 +13,8 @@ jobs: build-go1_12_1: docker: - image: circleci/golang:1.12.1 + environment: + GO111MODULE: "on" working_directory: /go/src/github.com/friendsofgo/killgrave steps: - checkout @@ -19,6 +23,8 @@ jobs: build-go_latest: docker: - image: circleci/golang:latest + environment: + GO111MODULE: "on" working_directory: /go/src/github.com/friendsofgo/killgrave steps: - checkout @@ -27,6 +33,8 @@ jobs: coverage: docker: - image: circleci/golang:latest + environment: + GO111MODULE: "on" working_directory: /go/src/github.com/friendsofgo/killgrave steps: - checkout @@ -35,6 +43,8 @@ jobs: release: docker: - image: circleci/golang:latest + environment: + GO111MODULE: "on" steps: - checkout - run: curl -sL https://git.io/goreleaser | bash From a967dfd3071487924849d9f32afee5d0f0b318d2 Mon Sep 17 00:00:00 2001 From: aperezg Date: Sun, 28 Apr 2019 13:35:00 +0200 Subject: [PATCH 13/13] Propagate public env vars --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 61407d8..a783aba 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,8 +3,8 @@ jobs: build-go1_11_6: docker: - image: circleci/golang:1.11.6 - environment: - GO111MODULE: "on" + environment: + GO111MODULE: "on" working_directory: /go/src/github.com/friendsofgo/killgrave steps: - checkout