Skip to content

Commit

Permalink
Merge pull request #13 from friendsofgo/match_request
Browse files Browse the repository at this point in the history
Way to v0.3.0
  • Loading branch information
aperezg authored Apr 28, 2019
2 parents 36f066a + a967dfd commit 1ff0ae2
Show file tree
Hide file tree
Showing 24 changed files with 275 additions and 295 deletions.
10 changes: 10 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -27,6 +33,8 @@ jobs:
coverage:
docker:
- image: circleci/golang:latest
environment:
GO111MODULE: "on"
working_directory: /go/src/github.com/friendsofgo/killgrave
steps:
- checkout
Expand All @@ -35,6 +43,8 @@ jobs:
release:
docker:
- image: circleci/golang:latest
environment:
GO111MODULE: "on"
steps:
- checkout
- run: curl -sL https://git.io/goreleaser | bash
Expand Down
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@
imposters
schemas

!test/testdata/imposters/
!test/testdata/imposters/schemas/
!internal/test/testdata/imposters/
!internal/test/testdata/imposters/schemas/
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,12 @@
* 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/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 (using new extension `.imp.json`)
* Allow write multiple imposters by file
67 changes: 44 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,32 +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.
```json
imposters/create_gopher.json
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.
{
"request": {
"method": "POST",
"endpoint": "/gophers",
"schema_file": "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
Expand Down Expand Up @@ -184,14 +203,16 @@ 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
* Dynamic responses based on query params
* Allow organize your imposters with structured folders
* Allow write multiple imposters by file
## Next Features
- [ ] Dynamic responses based on headers
- [ ] Dynamic responses based on query params
- [ ] Allow write multiples imposters by file
- [ ] 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.
Expand Down
5 changes: 2 additions & 3 deletions cmd/killgrave/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import (
"log"
"net/http"

"github.com/friendsofgo/killgrave"

killgrave "github.com/friendsofgo/killgrave/internal"
"github.com/gorilla/mux"
)

Expand All @@ -31,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)
}

Expand Down
9 changes: 0 additions & 9 deletions error.go

This file was deleted.

34 changes: 0 additions & 34 deletions imposter.go

This file was deleted.

46 changes: 2 additions & 44 deletions handler.go → internal/handler.go
Original file line number Diff line number Diff line change
@@ -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)
}
}

Expand Down
60 changes: 5 additions & 55 deletions handler_test.go → internal/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 {
Expand All @@ -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))
Expand Down Expand Up @@ -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)
}
})
}
}
Loading

0 comments on commit 1ff0ae2

Please sign in to comment.