diff --git a/Makefile b/Makefile index a193783..09b3b5d 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,10 @@ -.PHONY: build acceptance +.PHONY: build test acceptance build: go build -ldflags "-s -w -X 'github.com/friendsofgo/killgrave/internal/app/cmd._version=`git rev-parse --abbrev-ref HEAD`-`git rev-parse --short HEAD`'" -o bin/killgrave cmd/killgrave/main.go +test: + go test -v -vet=off -race ./... + acceptance: build - @(cd acceptance && go test -count=1 -tags=acceptance -v ./...) \ No newline at end of file + @(cd acceptance && go test -count=1 -tags=acceptance -v ./...) diff --git a/internal/server/http/handler.go b/internal/server/http/handler.go index cab0ae0..ab51b78 100644 --- a/internal/server/http/handler.go +++ b/internal/server/http/handler.go @@ -1,10 +1,12 @@ package http import ( + "github.com/gorilla/mux" "io" "log" "net/http" "os" + "strings" "time" ) @@ -15,9 +17,10 @@ func ImposterHandler(i Imposter) http.HandlerFunc { if res.Delay.Delay() > 0 { time.Sleep(res.Delay.Delay()) } + vars := mux.Vars(r) writeHeaders(res, w) w.WriteHeader(res.Status) - writeBody(i, res, w) + writeBody(i, res, w, vars) } } @@ -31,11 +34,14 @@ func writeHeaders(r Response, w http.ResponseWriter) { } } -func writeBody(i Imposter, r Response, w http.ResponseWriter) { +func writeBody(i Imposter, r Response, w http.ResponseWriter, vars map[string]string) { wb := []byte(r.Body) if r.BodyFile != nil { bodyFile := i.CalculateFilePath(*r.BodyFile) + for key, value := range vars { + bodyFile = strings.ReplaceAll(bodyFile, "{"+key+"}", value) + } wb = fetchBodyFromFile(bodyFile) } w.Write(wb) diff --git a/internal/server/http/handler_test.go b/internal/server/http/handler_test.go index ba87c9e..79df2fc 100644 --- a/internal/server/http/handler_test.go +++ b/internal/server/http/handler_test.go @@ -2,6 +2,8 @@ package http import ( "bytes" + "encoding/json" + "github.com/gorilla/mux" "io" "net/http" "net/http/httptest" @@ -69,6 +71,66 @@ func TestImposterHandler(t *testing.T) { } } +func TestImposterHandler_Variables(t *testing.T) { + var headers = make(map[string]string) + headers["Content-Type"] = "application/json" + + responseId1 := "test/testdata/imposters_variables/responses/gopher_1_response.json" + responseId2 := "test/testdata/imposters_variables/responses/gopher_2_response.json" + responseId1Variable1 := "test/testdata/imposters_variables/responses/gopher_1_1_response.json" + responseId1Variable2 := "test/testdata/imposters_variables/responses/gopher_1_2_response.json" + responseId1WithoutVariable := "test/testdata/imposters_variables/responses/gopher_1_without_variable_response.json" + + imposterFilePath := "test/testdata/imposters_variables/gopher_variables.imp.json" + imposterFile, err := os.Open(imposterFilePath) + require.NoError(t, err) + defer imposterFile.Close() + + imposterBytes, err := io.ReadAll(imposterFile) + require.NoError(t, err) + + var imposters []Imposter + err = json.Unmarshal(imposterBytes, &imposters) + require.NoError(t, err) + + var dataTest = []struct { + name string + imposter Imposter + url string + expectedBodyPath string + statusCode int + }{ + {"valid imposter with id 1 in path", imposters[0], "/gophers/1", responseId1, http.StatusOK}, + {"valid imposter with id 2 in path", imposters[0], "/gophers/2", responseId2, http.StatusOK}, + {"valid imposter with id 1 and second variable 1 in path", imposters[1], "/gophers/1/1", responseId1Variable1, http.StatusOK}, + {"valid imposter with id 1 and second variable 2 in path", imposters[1], "/gophers/1/2", responseId1Variable2, http.StatusOK}, + {"valid imposter without variable but body file has variable", imposters[2], "/gophers/1", responseId1WithoutVariable, http.StatusOK}, + } + + for _, tt := range dataTest { + tt := tt + t.Run(tt.name, func(t *testing.T) { + req, err := http.NewRequest("GET", tt.url, nil) + require.NoError(t, err) + rec := httptest.NewRecorder() + handler := ImposterHandler(tt.imposter) + + m := mux.NewRouter() + m.Handle(tt.imposter.Request.Endpoint, handler) + m.ServeHTTP(rec, req) + + expectedBodyPathFile, _ := os.Open(tt.expectedBodyPath) + defer expectedBodyPathFile.Close() + expectedBody, err := io.ReadAll(expectedBodyPathFile) + require.NoError(t, err) + + assert.Equal(t, rec.Code, tt.statusCode) + assert.Equal(t, string(expectedBody), rec.Body.String()) + + }) + } +} + func TestInvalidRequestWithSchema(t *testing.T) { validRequest := []byte(`{ "data": { diff --git a/internal/server/http/test/testdata/imposters_variables/gopher_variables.imp.json b/internal/server/http/test/testdata/imposters_variables/gopher_variables.imp.json new file mode 100644 index 0000000..47d783a --- /dev/null +++ b/internal/server/http/test/testdata/imposters_variables/gopher_variables.imp.json @@ -0,0 +1,50 @@ +[ + { + "request": { + "method": "GET", + "endpoint": "/gophers/{id:.*}", + "headers": { + "Content-Type": "application/json" + } + }, + "response": { + "status": 200, + "headers": { + "Content-Type": "application/json" + }, + "bodyFile": "test/testdata/imposters_variables/responses/gopher_{id}_response.json" + } + }, + { + "request": { + "method": "GET", + "endpoint": "/gophers/{id:.*}/{second_id:.*}", + "headers": { + "Content-Type": "application/json" + } + }, + "response": { + "status": 200, + "headers": { + "Content-Type": "application/json" + }, + "bodyFile": "test/testdata/imposters_variables/responses/gopher_{id}_{second_id}_response.json" + } + }, + { + "request": { + "method": "GET", + "endpoint": "/gophers/1", + "headers": { + "Content-Type": "application/json" + } + }, + "response": { + "status": 200, + "headers": { + "Content-Type": "application/json" + }, + "bodyFile": "test/testdata/imposters_variables/responses/gopher_{id}_response.json" + } + } +] diff --git a/internal/server/http/test/testdata/imposters_variables/responses/gopher_1_1_response.json b/internal/server/http/test/testdata/imposters_variables/responses/gopher_1_1_response.json new file mode 100644 index 0000000..42f60d7 --- /dev/null +++ b/internal/server/http/test/testdata/imposters_variables/responses/gopher_1_1_response.json @@ -0,0 +1,11 @@ +{ + "data": { + "type": "gophers", + "id": "1_1", + "attributes": { + "name": "Hannes", + "color": "Yellow", + "age": 32 + } + } +} diff --git a/internal/server/http/test/testdata/imposters_variables/responses/gopher_1_2_response.json b/internal/server/http/test/testdata/imposters_variables/responses/gopher_1_2_response.json new file mode 100644 index 0000000..03d1499 --- /dev/null +++ b/internal/server/http/test/testdata/imposters_variables/responses/gopher_1_2_response.json @@ -0,0 +1,11 @@ +{ + "data": { + "type": "gophers", + "id": "1_2", + "attributes": { + "name": "Manfred", + "color": "Blue", + "age": 31 + } + } +} diff --git a/internal/server/http/test/testdata/imposters_variables/responses/gopher_1_response.json b/internal/server/http/test/testdata/imposters_variables/responses/gopher_1_response.json new file mode 100644 index 0000000..410d54d --- /dev/null +++ b/internal/server/http/test/testdata/imposters_variables/responses/gopher_1_response.json @@ -0,0 +1,11 @@ +{ + "data": { + "type": "gophers", + "id": "1", + "attributes": { + "name": "Zebediah", + "color": "Purple", + "age": 54 + } + } +} diff --git a/internal/server/http/test/testdata/imposters_variables/responses/gopher_1_without_variable_response.json b/internal/server/http/test/testdata/imposters_variables/responses/gopher_1_without_variable_response.json new file mode 100644 index 0000000..e69de29 diff --git a/internal/server/http/test/testdata/imposters_variables/responses/gopher_2_response.json b/internal/server/http/test/testdata/imposters_variables/responses/gopher_2_response.json new file mode 100644 index 0000000..7e95255 --- /dev/null +++ b/internal/server/http/test/testdata/imposters_variables/responses/gopher_2_response.json @@ -0,0 +1,11 @@ +{ + "data": { + "type": "gophers", + "id": "2", + "attributes": { + "name": "Brian", + "color": "Red", + "age": 35 + } + } +}