From a54c6f24a199f426c6bed2aadf6aeceaf9f78d3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Hurtado?= Date: Thu, 24 Oct 2019 17:51:55 +0200 Subject: [PATCH] Added getRoute to control api --- internal/server/control/control.go | 22 ++++++- internal/server/control/control_test.go | 50 ++++++++++++-- internal/server/user/state.go | 13 ++++ internal/server/user/state_test.go | 66 +++++++++++++++++++ .../test/features/control/get/success.feature | 2 +- 5 files changed, 145 insertions(+), 8 deletions(-) diff --git a/internal/server/control/control.go b/internal/server/control/control.go index c7a5e7f..b80b5fe 100644 --- a/internal/server/control/control.go +++ b/internal/server/control/control.go @@ -36,11 +36,13 @@ func Run(bindAddr string) { func configRouter() *mux.Router { r := mux.NewRouter() r.HandleFunc("/routes/{id}", removeRoute). - Methods("DELETE") + Methods(http.MethodDelete) + r.HandleFunc("/routes/{id}", getRoute). + Methods(http.MethodGet) r.HandleFunc("/routes", listRoutes). - Methods("GET") + Methods(http.MethodGet) r.HandleFunc("/routes", addRoute). - Methods("POST") + Methods(http.MethodPost) return r } @@ -84,6 +86,7 @@ func addRoute(res http.ResponseWriter, req *http.Request) { return } if route.Method == "" { + spec / test / features / control / get / success.feature res.WriteHeader(http.StatusUnprocessableEntity) return } @@ -112,3 +115,16 @@ func addRoute(res http.ResponseWriter, req *http.Request) { res.Header().Set("Content-Type", "application/json") _, _ = res.Write(createdBytes) } + +var funcGet func(string) (model.Route, error) = user.Routes.Get + +func getRoute(res http.ResponseWriter, req *http.Request) { + id := mux.Vars(req)["id"] + if r, err := funcGet(id); err != nil { + res.WriteHeader(http.StatusNotFound) + } else { + res.Header().Set("Content-Type", "application/json") + rBytes, _ := json.Marshal(r) + _, _ = res.Write(rBytes) + } +} diff --git a/internal/server/control/control_test.go b/internal/server/control/control_test.go index 6a918c2..2cc778f 100644 --- a/internal/server/control/control_test.go +++ b/internal/server/control/control_test.go @@ -18,6 +18,7 @@ package control import ( "encoding/json" "errors" + "io/ioutil" "net/http" "net/http/httptest" "reflect" @@ -28,6 +29,7 @@ import ( "github.com/gorilla/mux" "github.com/BBVA/kapow/internal/server/model" + "github.com/BBVA/kapow/internal/server/user" ) func TestConfigRouterHasRoutesWellConfigured(t *testing.T) { @@ -37,10 +39,10 @@ func TestConfigRouterHasRoutesWellConfigured(t *testing.T) { mustMatch bool vars []string }{ - {"/routes/ROUTE_YYYYYYYYYYYYYYY", http.MethodGet, 0, false, []string{}}, - {"/routes/ROUTE_YYYYYYYYYYYYYYY", http.MethodPut, 0, false, []string{}}, - {"/routes/ROUTE_YYYYYYYYYYYYYYY", http.MethodPost, 0, false, []string{}}, - {"/routes/ROUTE_YYYYYYYYYYYYYYY", http.MethodDelete, reflect.ValueOf(removeRoute).Pointer(), true, []string{"id"}}, + {"/routes/FOO", http.MethodGet, reflect.ValueOf(getRoute).Pointer(), true, []string{"id"}}, + {"/routes/FOO", http.MethodPut, 0, false, []string{}}, + {"/routes/FOO", http.MethodPost, 0, false, []string{}}, + {"/routes/FOO", http.MethodDelete, reflect.ValueOf(removeRoute).Pointer(), true, []string{"id"}}, {"/routes", http.MethodGet, reflect.ValueOf(listRoutes).Pointer(), true, []string{}}, {"/routes", http.MethodPut, 0, false, []string{}}, {"/routes", http.MethodPost, reflect.ValueOf(addRoute).Pointer(), true, []string{}}, @@ -402,3 +404,43 @@ func TestListRoutesReturnsTwoElementsList(t *testing.T) { t.Errorf("Response mismatch. Expected %#v, got: %#v", expectedRouteList, respJson) } } + +func TestGetRouteReturns404sWhenRouteDoesntExist(t *testing.T) { + handler := mux.NewRouter() + handler.HandleFunc("/routes/{id}", getRoute). + Methods("GET") + r := httptest.NewRequest(http.MethodGet, "/routes/FOO", nil) + w := httptest.NewRecorder() + + handler.ServeHTTP(w, r) + + resp := w.Result() + if resp.StatusCode != http.StatusNotFound { + t.Errorf("HTTP status mismatch. Expected: %d, got: %d", http.StatusNotFound, resp.StatusCode) + } +} + +func TestGetRouteReturnsTheRequestedRoute(t *testing.T) { + handler := mux.NewRouter() + handler.HandleFunc("/routes/{id}", getRoute). + Methods("GET") + r := httptest.NewRequest(http.MethodGet, "/routes/FOO", nil) + w := httptest.NewRecorder() + user.Routes.Append(model.Route{ID: "FOO"}) + handler.ServeHTTP(w, r) + + resp := w.Result() + respJson := model.Route{} + if resp.StatusCode != http.StatusOK { + t.Errorf("HTTP status mismatch. Expected: %d, got: %d", http.StatusOK, resp.StatusCode) + } + + bBytes, _ := ioutil.ReadAll(resp.Body) + if err := json.Unmarshal(bBytes, &respJson); err != nil { + t.Errorf("Invalid JSON response. %s", string(bBytes)) + } + + if respJson.ID != "FOO" { + t.Errorf(`Route mismatch. Expected: "FOO". Got: %s`, respJson.ID) + } +} diff --git a/internal/server/user/state.go b/internal/server/user/state.go index 0ff6a93..fc7e3a5 100644 --- a/internal/server/user/state.go +++ b/internal/server/user/state.go @@ -80,3 +80,16 @@ func (srl *safeRouteList) Delete(ID string) error { srl.m.Unlock() return errors.New("Route not found") } + +func (srl *safeRouteList) Get(ID string) (r model.Route, err error) { + srl.m.RLock() + defer srl.m.RUnlock() + for _, r = range srl.rs { + if r.ID == ID { + return + } + } + + err = errors.New("Route not found") + return +} diff --git a/internal/server/user/state_test.go b/internal/server/user/state_test.go index a122e33..bb0c13e 100644 --- a/internal/server/user/state_test.go +++ b/internal/server/user/state_test.go @@ -364,3 +364,69 @@ func TestDeleteUpdatesMuxWithRemainingRoutes(t *testing.T) { t.Fatalf("Unexpected error: %v", err) } } + +func TestGetReturnsAnErrorWhenEmptyList(t *testing.T) { + srl := New() + + if _, err := srl.Get("FOO"); err == nil { + t.Error("Expected error not returned") + } +} + +func TestGetReturnsAnErrorWhenRouteNotExists(t *testing.T) { + srl := New() + srl.Append(model.Route{ID: "FOO"}) + + if _, err := srl.Get("BAR"); err == nil { + t.Error("Expected error not returned") + } +} + +func TestGetReturnsTheRequestedRoute(t *testing.T) { + srl := New() + srl.Append(model.Route{ID: "FOO"}) + + if r, err := srl.Get("FOO"); err != nil { + t.Errorf("Unexpected error %+v", err) + } else if r.ID != "FOO" { + t.Errorf(`Route mismatch. Expected: "FOO". Got %q`, r.ID) + } +} + +func TestGetWaitsForTheWriterToFinish(t *testing.T) { + srl := New() + srl.Append(model.Route{ID: "FOO"}) + + srl.m.Lock() + defer srl.m.Unlock() + + c := make(chan model.Route) + go func() { r, _ := srl.Get("FOO"); c <- r }() + + time.Sleep(10 * time.Millisecond) + + select { + case <-c: + t.Error("Route list readed while mutex was acquired") + default: // This default prevents the select from being blocking + } +} + +func TestGetNonBlockingReadWithOtherReaders(t *testing.T) { + srl := New() + srl.Append(model.Route{ID: "FOO"}) + + srl.m.RLock() + defer srl.m.RUnlock() + + c := make(chan model.Route) + go func() { r, _ := srl.Get("FOO"); c <- r }() + + time.Sleep(10 * time.Millisecond) + + select { + case <-c: + default: // This default prevents the select from being blocking + t.Error("Route list couldn't be readed while mutex was acquired for read") + } +} diff --git a/spec/test/features/control/get/success.feature b/spec/test/features/control/get/success.feature index b7dddd0..04d1287 100644 --- a/spec/test/features/control/get/success.feature +++ b/spec/test/features/control/get/success.feature @@ -21,7 +21,7 @@ Feature: Retrieve route details in Kapow! server. Get route details by id. Given I have a Kapow! server with the following routes: - | method | url_pattern | entrypoint | command | + | method | url_pattern | entrypoint | command | | GET | /foo | /bin/sh -c | ls -la / \| kapow set /response/body | | GET | /qux/{dirname} | /bin/sh -c | ls -la /request/params/dirname \| kapow set /response/body | When I get the first route