From 4391004e7fcced14de426ed7c00e753ff2be9dc3 Mon Sep 17 00:00:00 2001 From: Tim Raymond Date: Thu, 27 Jul 2017 15:57:04 -0400 Subject: [PATCH] Enforce presence of "x", "y", and "y2" axes Certain aspects of the frontend requires the presence of these three axes, so part of the contract established is that the backend will always provide them. Since we centralize creation of dashboardCellResponses, this is where these axes are added to all cell responses. Additionally, because there was previously no coverage over the dashboard cells endpoints, a test has been added to cover the DashboardCells method of Service. --- mocks/dashboards.go | 37 ++++++++++ mocks/logger.go | 9 +++ server/cells.go | 14 ++++ server/cells_test.go | 137 ++++++++++++++++++++++++++++++++++++++ server/dashboards_test.go | 6 ++ 5 files changed, 203 insertions(+) create mode 100644 mocks/dashboards.go diff --git a/mocks/dashboards.go b/mocks/dashboards.go new file mode 100644 index 0000000000..a038f468ba --- /dev/null +++ b/mocks/dashboards.go @@ -0,0 +1,37 @@ +package mocks + +import ( + "context" + + "github.com/influxdata/chronograf" +) + +var _ chronograf.DashboardsStore = &DashboardsStore{} + +type DashboardsStore struct { + AddF func(ctx context.Context, newDashboard chronograf.Dashboard) (chronograf.Dashboard, error) + AllF func(ctx context.Context) ([]chronograf.Dashboard, error) + DeleteF func(ctx context.Context, target chronograf.Dashboard) error + GetF func(ctx context.Context, id chronograf.DashboardID) (chronograf.Dashboard, error) + UpdateF func(ctx context.Context, target chronograf.Dashboard) error +} + +func (d *DashboardsStore) Add(ctx context.Context, newDashboard chronograf.Dashboard) (chronograf.Dashboard, error) { + return d.AddF(ctx, newDashboard) +} + +func (d *DashboardsStore) All(ctx context.Context) ([]chronograf.Dashboard, error) { + return d.AllF(ctx) +} + +func (d *DashboardsStore) Delete(ctx context.Context, target chronograf.Dashboard) error { + return d.DeleteF(ctx, target) +} + +func (d *DashboardsStore) Get(ctx context.Context, id chronograf.DashboardID) (chronograf.Dashboard, error) { + return d.GetF(ctx, id) +} + +func (d *DashboardsStore) Update(ctx context.Context, target chronograf.Dashboard) error { + return d.UpdateF(ctx, target) +} diff --git a/mocks/logger.go b/mocks/logger.go index 46c7bae9f6..f2926e09a3 100644 --- a/mocks/logger.go +++ b/mocks/logger.go @@ -3,6 +3,7 @@ package mocks import ( "fmt" "io" + "testing" "github.com/influxdata/chronograf" ) @@ -72,3 +73,11 @@ func (tl *TestLogger) stringifyArg(arg interface{}) []byte { return []byte("UNKNOWN") } } + +// Dump dumps out logs into a given testing.T's logs +func (tl *TestLogger) Dump(t *testing.T) { + t.Log("== Dumping Test Logs ==") + for _, msg := range tl.Messages { + t.Logf("lvl: %s, msg: %s", msg.Level, msg.Body) + } +} diff --git a/server/cells.go b/server/cells.go index f60437c910..a16cb702d5 100644 --- a/server/cells.go +++ b/server/cells.go @@ -33,6 +33,20 @@ func newCellResponses(dID chronograf.DashboardID, dcells []chronograf.DashboardC if len(cell.Queries) == 0 { cell.Queries = make([]chronograf.DashboardQuery, 0) } + + // ensure x, y, and y2 axes always returned + labels := []string{"x", "y", "y2"} + if cell.Axes == nil { + cell.Axes = make(map[string]chronograf.Axis, len(labels)) + } + + for _, lbl := range labels { + _, found := cell.Axes[lbl] + if !found { + cell.Axes[lbl] = chronograf.Axis{} + } + } + cells[i] = dashboardCellResponse{ DashboardCell: cell, Links: dashboardCellLinks{ diff --git a/server/cells_test.go b/server/cells_test.go index a3ae00e9c1..1be5981f9f 100644 --- a/server/cells_test.go +++ b/server/cells_test.go @@ -1,9 +1,18 @@ package server_test import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "net/url" + "strings" "testing" + "github.com/bouk/httprouter" + "github.com/google/go-cmp/cmp" "github.com/influxdata/chronograf" + "github.com/influxdata/chronograf/mocks" "github.com/influxdata/chronograf/server" ) @@ -58,3 +67,131 @@ func Test_Cells_CorrectAxis(t *testing.T) { }) } } + +func Test_Service_DashboardCells(t *testing.T) { + cellsTests := []struct { + name string + reqURL *url.URL + ctxParams map[string]string + mockResponse []chronograf.DashboardCell + expected []chronograf.DashboardCell + expectedCode int + }{ + { + "happy path", + &url.URL{ + Path: "/chronograf/v1/dashboards/1/cells", + }, + map[string]string{ + "id": "1", + }, + []chronograf.DashboardCell{}, + []chronograf.DashboardCell{}, + http.StatusOK, + }, + { + "cell axes should always be \"x\", \"y\", and \"y2\"", + &url.URL{ + Path: "/chronograf/v1/dashboards/1/cells", + }, + map[string]string{ + "id": "1", + }, + []chronograf.DashboardCell{ + { + ID: "3899be5a-f6eb-4347-b949-de2f4fbea859", + X: 0, + Y: 0, + W: 4, + H: 4, + Name: "CPU", + Queries: []chronograf.DashboardQuery{}, + Axes: map[string]chronograf.Axis{}, + }, + }, + []chronograf.DashboardCell{ + { + ID: "3899be5a-f6eb-4347-b949-de2f4fbea859", + X: 0, + Y: 0, + W: 4, + H: 4, + Name: "CPU", + Queries: []chronograf.DashboardQuery{}, + Axes: map[string]chronograf.Axis{ + "x": chronograf.Axis{}, + "y": chronograf.Axis{}, + "y2": chronograf.Axis{}, + }, + }, + }, + http.StatusOK, + }, + } + + for _, test := range cellsTests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + // setup context with params + ctx := context.Background() + params := httprouter.Params{} + for k, v := range test.ctxParams { + params = append(params, httprouter.Param{k, v}) + } + ctx = httprouter.WithParams(ctx, params) + + // setup response recorder and request + rr := httptest.NewRecorder() + req := httptest.NewRequest("GET", test.reqURL.RequestURI(), strings.NewReader("")).WithContext(ctx) + + // setup mock DashboardCells store and logger + tlog := &mocks.TestLogger{} + svc := &server.Service{ + DashboardsStore: &mocks.DashboardsStore{ + GetF: func(ctx context.Context, id chronograf.DashboardID) (chronograf.Dashboard, error) { + return chronograf.Dashboard{ + ID: chronograf.DashboardID(1), + Cells: test.mockResponse, + Templates: []chronograf.Template{}, + Name: "empty dashboard", + }, nil + }, + }, + Logger: tlog, + } + + // invoke DashboardCell handler + svc.DashboardCells(rr, req) + + // setup frame to decode response into + respFrame := []struct { + chronograf.DashboardCell + Links json.RawMessage `json:"links"` // ignore links + }{} + + // decode response + resp := rr.Result() + + if resp.StatusCode != test.expectedCode { + tlog.Dump(t) + t.Fatalf("%q - Status codes do not match. Want %d (%s), Got %d (%s)", test.name, test.expectedCode, http.StatusText(test.expectedCode), resp.StatusCode, http.StatusText(resp.StatusCode)) + } + + if err := json.NewDecoder(resp.Body).Decode(&respFrame); err != nil { + t.Fatalf("%q - Error unmarshaling response body: err: %s", test.name, err) + } + + // extract actual + actual := []chronograf.DashboardCell{} + for _, rsp := range respFrame { + actual = append(actual, rsp.DashboardCell) + } + + // compare actual and expected + if !cmp.Equal(actual, test.expected) { + t.Fatalf("%q - Dashboard Cells do not match: diff: %s", test.name, cmp.Diff(actual, test.expected)) + } + }) + } +} diff --git a/server/dashboards_test.go b/server/dashboards_test.go index 5c92083451..d328addad5 100644 --- a/server/dashboards_test.go +++ b/server/dashboards_test.go @@ -273,6 +273,7 @@ func Test_newDashboardResponse(t *testing.T) { "y": chronograf.Axis{ Bounds: []string{"2", "95"}, }, + "y2": chronograf.Axis{}, }, }, }, @@ -284,6 +285,11 @@ func Test_newDashboardResponse(t *testing.T) { ID: "b", W: 4, H: 4, + Axes: map[string]chronograf.Axis{ + "x": chronograf.Axis{}, + "y": chronograf.Axis{}, + "y2": chronograf.Axis{}, + }, Queries: []chronograf.DashboardQuery{ { Command: "SELECT winning_horses from grays_sports_alamanc where time > now() - 15m",