diff --git a/go.mod b/go.mod index 54d34e5..4291148 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/golangci/golangci-lint v1.20.0 // indirect github.com/golangci/revgrep v0.0.0-20180812185044-276a5c0a1039 // indirect github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf - github.com/google/uuid v1.1.1 // indirect + github.com/google/uuid v1.1.1 github.com/gorilla/mux v1.7.3 github.com/gostaticanalysis/analysisutil v0.0.3 // indirect github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect diff --git a/internal/server/user/mux/handlerbuilder.go b/internal/server/user/mux/handlerbuilder.go new file mode 100644 index 0000000..f2792a1 --- /dev/null +++ b/internal/server/user/mux/handlerbuilder.go @@ -0,0 +1,40 @@ +package mux + +import ( + "log" + "net/http" + + "github.com/google/uuid" + + "github.com/BBVA/kapow/internal/server/data" + "github.com/BBVA/kapow/internal/server/model" + "github.com/BBVA/kapow/internal/server/user/spawn" +) + +var spawner = spawn.Spawn +var idGenerator = uuid.NewUUID + +func handlerBuilder(route model.Route) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + id, err := idGenerator() + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + h := &model.Handler{ + ID: id.String(), + Route: route, + Request: r, + Writer: w, + } + + data.Handlers.Add(h) + defer data.Handlers.Remove(h.ID) + + err = spawner(h, nil) + if err != nil { + log.Println(err) + } + }) +} diff --git a/internal/server/user/mux/handlerbuilder_test.go b/internal/server/user/mux/handlerbuilder_test.go new file mode 100644 index 0000000..e44e849 --- /dev/null +++ b/internal/server/user/mux/handlerbuilder_test.go @@ -0,0 +1,182 @@ +package mux + +import ( + "errors" + "io" + "net/http" + "net/http/httptest" + "reflect" + "testing" + + "github.com/google/uuid" + + "github.com/BBVA/kapow/internal/server/data" + "github.com/BBVA/kapow/internal/server/model" + "github.com/BBVA/kapow/internal/server/user/spawn" +) + +func TestHandlerBuilderCallsSpawner(t *testing.T) { + route := model.Route{} + idGenerator = uuid.NewUUID + called := false + spawner = func(h *model.Handler, out io.Writer) error { + called = true + return nil + } + + handlerBuilder(route).ServeHTTP(nil, nil) + + if !called { + t.Error("Didn't call spawner") + } +} + +func TestHandlerBuilderStoresHandlerInDataHandlers(t *testing.T) { + route := model.Route{} + added := false + spawner = func(h *model.Handler, out io.Writer) error { + added = len(data.Handlers.ListIDs()) != 0 + + return nil + } + h := handlerBuilder(route) + data.Handlers = data.New() + + h.ServeHTTP(nil, nil) + + if !added { + t.Error("handler not added to data.Handlers") + } +} + +func TestHandlerBuilderStoresTheProperRoute(t *testing.T) { + data.Handlers = data.New() + route := model.Route{ + ID: "foo", + } + var got model.Route + spawner = func(h *model.Handler, out io.Writer) error { + hid := data.Handlers.ListIDs()[0] + handler, _ := data.Handlers.Get(hid) + got = handler.Route + + return nil + } + + handlerBuilder(route).ServeHTTP(nil, nil) + + if !reflect.DeepEqual(got, route) { + t.Error("Route not stored properly in the handler") + } +} + +func TestHandlerBuilderStoresTheProperRequest(t *testing.T) { + data.Handlers = data.New() + route := model.Route{} + var got *http.Request + spawner = func(h *model.Handler, out io.Writer) error { + hid := data.Handlers.ListIDs()[0] + handler, _ := data.Handlers.Get(hid) + got = handler.Request + + return nil + } + r := &http.Request{} + + handlerBuilder(route).ServeHTTP(nil, r) + + if got != r { + t.Error("Request not stored properly in the handler") + } +} + +func TestHandlerBuilderStoresTheProperResponseWriter(t *testing.T) { + data.Handlers = data.New() + route := model.Route{} + var got http.ResponseWriter + spawner = func(h *model.Handler, out io.Writer) error { + hid := data.Handlers.ListIDs()[0] + handler, _ := data.Handlers.Get(hid) + got = handler.Writer + + return nil + } + w := httptest.NewRecorder() + w.Flushed = !w.Flushed + + handlerBuilder(route).ServeHTTP(w, nil) + + if !reflect.DeepEqual(got, w) { + t.Error("ResponseWriter not stored properly in the handler") + } +} + +func TestHandlerBuilderGeneratesAProperID(t *testing.T) { + data.Handlers = data.New() + route := model.Route{} + var got string + spawner = func(h *model.Handler, out io.Writer) error { + hid := data.Handlers.ListIDs()[0] + handler, _ := data.Handlers.Get(hid) + got = handler.ID + + return nil + } + + handlerBuilder(route).ServeHTTP(nil, nil) + + if _, err := uuid.Parse(got); err != nil { + t.Error("ID not generated properly") + } +} + +func TestHandlerBuilderCallsSpawnerWithTheStoredHandler(t *testing.T) { + data.Handlers = data.New() + route := model.Route{} + var gotStored *model.Handler + var gotPassed *model.Handler + spawner = func(h *model.Handler, out io.Writer) error { + gotPassed = h + hid := data.Handlers.ListIDs()[0] + gotStored, _ = data.Handlers.Get(hid) + + return nil + } + + handlerBuilder(route).ServeHTTP(nil, nil) + + if gotStored != gotPassed { + t.Error("Proper handler not passed to spawner()") + } +} + +func TestHandlerBuilder500sWhenIDGeneratorFails(t *testing.T) { + data.Handlers = data.New() + spawner = spawn.Spawn + route := model.Route{} + w := httptest.NewRecorder() + idGenerator = func() (uuid.UUID, error) { + var uuid uuid.UUID + return uuid, errors.New( + "End of Time reached; Try again before, or in the next Big Bang cycle") + } + + handlerBuilder(route).ServeHTTP(w, nil) + + if w.Result().StatusCode != http.StatusInternalServerError { + t.Error("ID generation failure not handled gracefully") + } +} + +func TestHandlerBuilderRemovesHandlerWhenDone(t *testing.T) { + data.Handlers = data.New() + spawner = spawn.Spawn + idGenerator = uuid.NewUUID + route := model.Route{} + + handlerBuilder(route).ServeHTTP(nil, nil) + + if len(data.Handlers.ListIDs()) != 0 { + t.Error("Handler not removed upon completion") + } +}