From f9ef8e990040648c3fce551908f4b0cbb10eede7 Mon Sep 17 00:00:00 2001 From: Peter Aba Date: Sun, 8 Apr 2018 18:59:18 +0200 Subject: [PATCH 1/6] Scan endpoint directory on startup --- README.md | 4 +++ api.go | 2 +- api_test.go | 28 ++++-------------- cmd/apidemic/main.go | 70 ++++++++++++++++++++++++++++++++++++++++++++ helper.go | 25 ++++++++++++++++ 5 files changed, 105 insertions(+), 24 deletions(-) create mode 100644 helper.go diff --git a/README.md b/README.md index c1fea76..8c8c194 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,10 @@ In case you need to mimic endpoints which respond to requests other than GET the Currently supported HTTP methods are: `OPTIONS`, `GET`, `POST`, `PUT`, `DELETE`, `HEAD`, default is `GET`. Please open an issue if you think there should be others added. +### Registering endpoints automatically + +The service will scan the `./endpoints/` directory relative to the currenct working directory on startup. You can change the directory to be scanned by adding a flag `--endpoint-dir=ENDPOINT_DIR`. + # Tags Apidemic uses tags to annotate what kind of fake data to generate and also control different requrements of fake data. diff --git a/api.go b/api.go index aea6ba7..9f57b16 100644 --- a/api.go +++ b/api.go @@ -12,7 +12,7 @@ import ( ) // Version is the version of apidemic. Apidemic uses semver. -const Version = "0.3" +const Version = "0.5" var maxItemTime = cache.DefaultExpiration diff --git a/api_test.go b/api_test.go index 7a9bcba..d05acad 100644 --- a/api_test.go +++ b/api_test.go @@ -3,7 +3,6 @@ package apidemic import ( "bytes" "encoding/json" - "io" "io/ioutil" "net/http" "net/http/httptest" @@ -17,7 +16,7 @@ func TestDynamicEndpointFailsWithoutRegistration(t *testing.T) { payload := registerPayload(t, "fixtures/sample_request.json") w := httptest.NewRecorder() - req := jsonRequest("POST", "/api/test", payload) + req := JsonRequest("POST", "/api/test", payload) s.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) @@ -28,11 +27,11 @@ func TestDynamicEndpointWithGetRequest(t *testing.T) { payload := registerPayload(t, "fixtures/sample_request.json") w := httptest.NewRecorder() - req := jsonRequest("POST", "/register", payload) + req := JsonRequest("POST", "/register", payload) s.ServeHTTP(w, req) w = httptest.NewRecorder() - req = jsonRequest("GET", "/api/test", nil) + req = JsonRequest("GET", "/api/test", nil) s.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -44,11 +43,11 @@ func TestDynamicEndpointWithPostRequest(t *testing.T) { payload["http_method"] = "POST" w := httptest.NewRecorder() - req := jsonRequest("POST", "/register", payload) + req := JsonRequest("POST", "/register", payload) s.ServeHTTP(w, req) w = httptest.NewRecorder() - req = jsonRequest("POST", "/api/test", nil) + req = JsonRequest("POST", "/api/test", nil) s.ServeHTTP(w, req) assert.Equal(t, http.StatusCreated, w.Code) @@ -68,20 +67,3 @@ func registerPayload(t *testing.T, fixtureFile string) map[string]interface{} { return api } - -func jsonRequest(method string, path string, body interface{}) *http.Request { - var bEnd io.Reader - if body != nil { - b, err := json.Marshal(body) - if err != nil { - return nil - } - bEnd = bytes.NewReader(b) - } - req, err := http.NewRequest(method, path, bEnd) - if err != nil { - panic(err) - } - req.Header.Set("Content-Type", "application/json") - return req -} diff --git a/cmd/apidemic/main.go b/cmd/apidemic/main.go index bd0b3f5..0bb7514 100644 --- a/cmd/apidemic/main.go +++ b/cmd/apidemic/main.go @@ -1,18 +1,30 @@ package main import ( + "bytes" + "encoding/json" + "errors" "fmt" + "io/ioutil" "log" "net/http" + "net/http/httptest" "github.com/codegangsta/cli" "github.com/gernest/apidemic" + "github.com/gorilla/mux" ) func server(ctx *cli.Context) { port := ctx.Int("port") + endpointDir := ctx.String("endpoint-dir") s := apidemic.NewServer() + err := addEndpoints(s, endpointDir) + if err != nil { + log.Fatalln(err) + } + log.Println("starting server on port :", port) log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), s)) } @@ -38,8 +50,66 @@ func main() { Value: 3000, EnvVar: "PORT", }, + cli.StringFlag{ + Name: "endpoint-dir", + Usage: "Directory to scan for endpoint", + Value: "./endpoints/", + EnvVar: "ENDPOINT_DIR", + }, }, }, } app.RunAndExitOnError() } + +// addEndpoints iterates over a directory and tries to register each file as an endpoint +// if directory does not exist or not readable it will simply return without registration +// failing registration however causes an error response +func addEndpoints(s *mux.Router, endpointDir string) error { + var registerPayload map[string]interface{} + files, err := ioutil.ReadDir(endpointDir) + if err != nil { + return nil + } + + for _, file := range files { + if file.IsDir() { + continue + } + + filePath := endpointDir + file.Name() + + registerPayload, err = getRegisterPayload(filePath) + if err != nil { + return err + } + + w := httptest.NewRecorder() + req := apidemic.JsonRequest("POST", "/register", registerPayload) + s.ServeHTTP(w, req) + + log.Printf("%s is registered\n", filePath) + + if w.Code != http.StatusOK { + return errors.New("registering " + filePath + " failed") + } + } + + return nil +} + +func getRegisterPayload(endpoint string) (map[string]interface{}, error) { + var api map[string]interface{} + + content, err := ioutil.ReadFile(endpoint) + if err != nil { + return api, err + } + + err = json.NewDecoder(bytes.NewReader(content)).Decode(&api) + if err != nil { + return api, err + } + + return api, nil +} diff --git a/helper.go b/helper.go new file mode 100644 index 0000000..e36fef0 --- /dev/null +++ b/helper.go @@ -0,0 +1,25 @@ +package apidemic + +import ( + "bytes" + "encoding/json" + "io" + "net/http" +) + +func JsonRequest(method string, path string, body interface{}) *http.Request { + var bEnd io.Reader + if body != nil { + b, err := json.Marshal(body) + if err != nil { + return nil + } + bEnd = bytes.NewReader(b) + } + req, err := http.NewRequest(method, path, bEnd) + if err != nil { + panic(err) + } + req.Header.Set("Content-Type", "application/json") + return req +} From 3ee84beb8c59423226e3f6f3d64ff45ad2d57699 Mon Sep 17 00:00:00 2001 From: Peter Aba Date: Sun, 8 Apr 2018 18:59:18 +0200 Subject: [PATCH 2/6] Scan endpoint directory on startup --- README.md | 4 +++ api.go | 3 +- api_test.go | 28 ++++-------------- cmd/apidemic/main.go | 70 ++++++++++++++++++++++++++++++++++++++++++++ helper.go | 25 ++++++++++++++++ 5 files changed, 106 insertions(+), 24 deletions(-) create mode 100644 helper.go diff --git a/README.md b/README.md index da8cfd2..3c528ba 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,10 @@ There's also a 1% chance for the server to claim to be a [Teapot](https://tools. **Note**: JSON keys must be strings, providing your response codes as integers will not work! +### Registering endpoints automatically + +The service will scan the `./endpoints/` directory relative to the currenct working directory on startup. You can change the directory to be scanned by adding a flag `--endpoint-dir=ENDPOINT_DIR`. + # Tags Apidemic uses tags to annotate what kind of fake data to generate and also control different requrements of fake data. diff --git a/api.go b/api.go index e6c1168..5cee6c3 100644 --- a/api.go +++ b/api.go @@ -13,7 +13,7 @@ import ( ) // Version is the version of apidemic. Apidemic uses semver. -const Version = "0.4" +const Version = "0.5" var maxItemTime = cache.DefaultExpiration @@ -149,6 +149,7 @@ func DynamicEndpoint(w http.ResponseWriter, r *http.Request) { return } } + responseText := fmt.Sprintf("apidemic: %s has no %s endpoint", vars["endpoint"], r.Method) RenderJSON(w, http.StatusNotFound, NewResponse(responseText)) } diff --git a/api_test.go b/api_test.go index e6b198a..9bc629b 100644 --- a/api_test.go +++ b/api_test.go @@ -3,7 +3,6 @@ package apidemic import ( "bytes" "encoding/json" - "io" "io/ioutil" "net/http" "net/http/httptest" @@ -21,7 +20,7 @@ func TestDynamicEndpointFailsWithoutRegistration(t *testing.T) { payload := registerPayload(t, "fixtures/sample_request.json") w := httptest.NewRecorder() - req := jsonRequest("POST", "/api/test", payload) + req := JsonRequest("POST", "/api/test", payload) s.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) @@ -32,12 +31,12 @@ func TestDynamicEndpointWithGetRequest(t *testing.T) { payload := registerPayload(t, "fixtures/sample_request.json") w := httptest.NewRecorder() - req := jsonRequest("POST", "/register", payload) + req := JsonRequest("POST", "/register", payload) s.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code) w = httptest.NewRecorder() - req = jsonRequest("GET", "/api/test", nil) + req = JsonRequest("GET", "/api/test", nil) s.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) @@ -49,12 +48,12 @@ func TestDynamicEndpointWithPostRequest(t *testing.T) { payload["http_method"] = "POST" w := httptest.NewRecorder() - req := jsonRequest("POST", "/register", payload) + req := JsonRequest("POST", "/register", payload) s.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code) w = httptest.NewRecorder() - req = jsonRequest("POST", "/api/test", nil) + req = JsonRequest("POST", "/api/test", nil) s.ServeHTTP(w, req) assert.Equal(t, http.StatusCreated, w.Code) @@ -97,20 +96,3 @@ func registerPayload(t *testing.T, fixtureFile string) map[string]interface{} { return api } - -func jsonRequest(method string, path string, body interface{}) *http.Request { - var bEnd io.Reader - if body != nil { - b, err := json.Marshal(body) - if err != nil { - return nil - } - bEnd = bytes.NewReader(b) - } - req, err := http.NewRequest(method, path, bEnd) - if err != nil { - panic(err) - } - req.Header.Set("Content-Type", "application/json") - return req -} diff --git a/cmd/apidemic/main.go b/cmd/apidemic/main.go index bd0b3f5..0bb7514 100644 --- a/cmd/apidemic/main.go +++ b/cmd/apidemic/main.go @@ -1,18 +1,30 @@ package main import ( + "bytes" + "encoding/json" + "errors" "fmt" + "io/ioutil" "log" "net/http" + "net/http/httptest" "github.com/codegangsta/cli" "github.com/gernest/apidemic" + "github.com/gorilla/mux" ) func server(ctx *cli.Context) { port := ctx.Int("port") + endpointDir := ctx.String("endpoint-dir") s := apidemic.NewServer() + err := addEndpoints(s, endpointDir) + if err != nil { + log.Fatalln(err) + } + log.Println("starting server on port :", port) log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), s)) } @@ -38,8 +50,66 @@ func main() { Value: 3000, EnvVar: "PORT", }, + cli.StringFlag{ + Name: "endpoint-dir", + Usage: "Directory to scan for endpoint", + Value: "./endpoints/", + EnvVar: "ENDPOINT_DIR", + }, }, }, } app.RunAndExitOnError() } + +// addEndpoints iterates over a directory and tries to register each file as an endpoint +// if directory does not exist or not readable it will simply return without registration +// failing registration however causes an error response +func addEndpoints(s *mux.Router, endpointDir string) error { + var registerPayload map[string]interface{} + files, err := ioutil.ReadDir(endpointDir) + if err != nil { + return nil + } + + for _, file := range files { + if file.IsDir() { + continue + } + + filePath := endpointDir + file.Name() + + registerPayload, err = getRegisterPayload(filePath) + if err != nil { + return err + } + + w := httptest.NewRecorder() + req := apidemic.JsonRequest("POST", "/register", registerPayload) + s.ServeHTTP(w, req) + + log.Printf("%s is registered\n", filePath) + + if w.Code != http.StatusOK { + return errors.New("registering " + filePath + " failed") + } + } + + return nil +} + +func getRegisterPayload(endpoint string) (map[string]interface{}, error) { + var api map[string]interface{} + + content, err := ioutil.ReadFile(endpoint) + if err != nil { + return api, err + } + + err = json.NewDecoder(bytes.NewReader(content)).Decode(&api) + if err != nil { + return api, err + } + + return api, nil +} diff --git a/helper.go b/helper.go new file mode 100644 index 0000000..e36fef0 --- /dev/null +++ b/helper.go @@ -0,0 +1,25 @@ +package apidemic + +import ( + "bytes" + "encoding/json" + "io" + "net/http" +) + +func JsonRequest(method string, path string, body interface{}) *http.Request { + var bEnd io.Reader + if body != nil { + b, err := json.Marshal(body) + if err != nil { + return nil + } + bEnd = bytes.NewReader(b) + } + req, err := http.NewRequest(method, path, bEnd) + if err != nil { + panic(err) + } + req.Header.Set("Content-Type", "application/json") + return req +} From eed1a042af549e8ef8ecf863a10580b7c73e8116 Mon Sep 17 00:00:00 2001 From: Peter Aba Date: Tue, 10 Apr 2018 11:09:44 +0200 Subject: [PATCH 3/6] Fix merge issue in tests --- api_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api_test.go b/api_test.go index 9bc629b..37264a5 100644 --- a/api_test.go +++ b/api_test.go @@ -65,12 +65,12 @@ func TestDynamicEndpointWithForbiddenResponse(t *testing.T) { registerPayload["response_code_probabilities"] = map[string]int{"403": 100} w := httptest.NewRecorder() - req := jsonRequest("POST", "/register", registerPayload) + req := JsonRequest("POST", "/register", registerPayload) s.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code) w = httptest.NewRecorder() - req = jsonRequest("GET", "/api/test", nil) + req = JsonRequest("GET", "/api/test", nil) s.ServeHTTP(w, req) assert.Equal(t, http.StatusForbidden, w.Code) From 6c54b88dcaa3ca07738443de7cc2d485c0527c7e Mon Sep 17 00:00:00 2001 From: Peter Aba Date: Tue, 10 Apr 2018 11:14:41 +0200 Subject: [PATCH 4/6] Self-review fix --- README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.md b/README.md index b4c55c6..6cba7e0 100644 --- a/README.md +++ b/README.md @@ -159,11 +159,6 @@ There's also a 1% chance for the server to claim to be a [Teapot](https://tools. **Note**: JSON keys must be strings, providing your response codes as integers will not work! ### Registering endpoints automatically - -The service will scan the `./endpoints/` directory relative to the currenct working directory on startup. You can change the directory to be scanned by adding a flag `--endpoint-dir=ENDPOINT_DIR`. - -### Registering endpoints automatically - The service will scan the `./endpoints/` directory relative to the currenct working directory on startup. You can change the directory to be scanned by adding a flag `--endpoint-dir=ENDPOINT_DIR`. # Tags From 4578eb68af82cc65f5860667d4c6fd36db957306 Mon Sep 17 00:00:00 2001 From: Peter Aba Date: Tue, 10 Apr 2018 22:26:02 +0200 Subject: [PATCH 5/6] Review fixes --- README.md | 2 +- cmd/apidemic/main.go | 13 +++++++++---- helper.go | 1 + 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 6cba7e0..631a97e 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,7 @@ There's also a 1% chance for the server to claim to be a [Teapot](https://tools. **Note**: JSON keys must be strings, providing your response codes as integers will not work! ### Registering endpoints automatically -The service will scan the `./endpoints/` directory relative to the currenct working directory on startup. You can change the directory to be scanned by adding a flag `--endpoint-dir=ENDPOINT_DIR`. +The service will scan the `endpoints/` directory relative to the currenct working directory on startup. You can change the directory to be scanned by adding a flag `--endpoint-dir=ENDPOINT_DIR`. # Tags Apidemic uses tags to annotate what kind of fake data to generate and also control different requrements of fake data. diff --git a/cmd/apidemic/main.go b/cmd/apidemic/main.go index 0bb7514..9cde7c7 100644 --- a/cmd/apidemic/main.go +++ b/cmd/apidemic/main.go @@ -3,12 +3,12 @@ package main import ( "bytes" "encoding/json" - "errors" "fmt" "io/ioutil" "log" "net/http" "net/http/httptest" + "os" "github.com/codegangsta/cli" "github.com/gernest/apidemic" @@ -20,7 +20,12 @@ func server(ctx *cli.Context) { endpointDir := ctx.String("endpoint-dir") s := apidemic.NewServer() - err := addEndpoints(s, endpointDir) + _, err := os.Stat(endpointDir) + if err != nil && endpointDir != "endpoints/" { + fmt.Errorf("Endpoint not found: %s", endpointDir) + } + + err = addEndpoints(s, endpointDir) if err != nil { log.Fatalln(err) } @@ -53,7 +58,7 @@ func main() { cli.StringFlag{ Name: "endpoint-dir", Usage: "Directory to scan for endpoint", - Value: "./endpoints/", + Value: "endpoints/", EnvVar: "ENDPOINT_DIR", }, }, @@ -91,7 +96,7 @@ func addEndpoints(s *mux.Router, endpointDir string) error { log.Printf("%s is registered\n", filePath) if w.Code != http.StatusOK { - return errors.New("registering " + filePath + " failed") + return fmt.Errorf("registering %s failed", filePath) } } diff --git a/helper.go b/helper.go index e36fef0..6dd5081 100644 --- a/helper.go +++ b/helper.go @@ -7,6 +7,7 @@ import ( "net/http" ) +// JsonRequest creates an HTTP request func JsonRequest(method string, path string, body interface{}) *http.Request { var bEnd io.Reader if body != nil { From cc0970a2041f5a647c871ad6c99dc000c6052877 Mon Sep 17 00:00:00 2001 From: Peter Aba Date: Fri, 13 Apr 2018 00:20:07 +0200 Subject: [PATCH 6/6] Review fixes 2 --- cmd/apidemic/main.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cmd/apidemic/main.go b/cmd/apidemic/main.go index 9cde7c7..6d810c1 100644 --- a/cmd/apidemic/main.go +++ b/cmd/apidemic/main.go @@ -9,6 +9,7 @@ import ( "net/http" "net/http/httptest" "os" + "path/filepath" "github.com/codegangsta/cli" "github.com/gernest/apidemic" @@ -22,7 +23,7 @@ func server(ctx *cli.Context) { _, err := os.Stat(endpointDir) if err != nil && endpointDir != "endpoints/" { - fmt.Errorf("Endpoint not found: %s", endpointDir) + fmt.Errorf("endpoint not found: %s", endpointDir) } err = addEndpoints(s, endpointDir) @@ -82,9 +83,9 @@ func addEndpoints(s *mux.Router, endpointDir string) error { continue } - filePath := endpointDir + file.Name() + fullPath := filepath.Join(endpointDir ,file.Name()) - registerPayload, err = getRegisterPayload(filePath) + registerPayload, err = getRegisterPayload(fullPath) if err != nil { return err } @@ -93,10 +94,10 @@ func addEndpoints(s *mux.Router, endpointDir string) error { req := apidemic.JsonRequest("POST", "/register", registerPayload) s.ServeHTTP(w, req) - log.Printf("%s is registered\n", filePath) + log.Printf("%s is registered\n", fullPath) if w.Code != http.StatusOK { - return fmt.Errorf("registering %s failed", filePath) + return fmt.Errorf("registering %s failed", fullPath) } }