github.com/influxdata/influxdb/v2@v2.7.6/dashboards/transport/http_test.go (about)

     1  package transport
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"net/http/httptest"
    11  	"strconv"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/go-chi/chi"
    16  	"github.com/google/go-cmp/cmp"
    17  	"github.com/influxdata/influxdb/v2"
    18  	"github.com/influxdata/influxdb/v2/dashboards"
    19  	dashboardstesting "github.com/influxdata/influxdb/v2/dashboards/testing"
    20  	ihttp "github.com/influxdata/influxdb/v2/http"
    21  	"github.com/influxdata/influxdb/v2/kit/platform"
    22  	"github.com/influxdata/influxdb/v2/kit/platform/errors"
    23  	"github.com/influxdata/influxdb/v2/kv"
    24  	"github.com/influxdata/influxdb/v2/label"
    25  	"github.com/influxdata/influxdb/v2/mock"
    26  	"github.com/influxdata/influxdb/v2/tenant"
    27  	itesting "github.com/influxdata/influxdb/v2/testing"
    28  	"github.com/yudai/gojsondiff"
    29  	"github.com/yudai/gojsondiff/formatter"
    30  	"go.uber.org/zap"
    31  	"go.uber.org/zap/zaptest"
    32  )
    33  
    34  func newDashboardHandler(log *zap.Logger, opts ...option) *DashboardHandler {
    35  	deps := dashboardDependencies{
    36  		dashboardService: mock.NewDashboardService(),
    37  		userService:      mock.NewUserService(),
    38  		orgService:       mock.NewOrganizationService(),
    39  		labelService:     mock.NewLabelService(),
    40  		urmService:       mock.NewUserResourceMappingService(),
    41  	}
    42  
    43  	for _, opt := range opts {
    44  		opt(&deps)
    45  	}
    46  
    47  	return NewDashboardHandler(
    48  		log,
    49  		deps.dashboardService,
    50  		deps.labelService,
    51  		deps.userService,
    52  		deps.orgService,
    53  		tenant.NewURMHandler(
    54  			log.With(zap.String("handler", "urm")),
    55  			influxdb.DashboardsResourceType,
    56  			"id",
    57  			deps.userService,
    58  			deps.urmService,
    59  		),
    60  		label.NewHTTPEmbeddedHandler(
    61  			log.With(zap.String("handler", "label")),
    62  			influxdb.DashboardsResourceType,
    63  			deps.labelService,
    64  		),
    65  	)
    66  }
    67  
    68  func TestService_handleGetDashboards(t *testing.T) {
    69  	type fields struct {
    70  		DashboardService influxdb.DashboardService
    71  		LabelService     influxdb.LabelService
    72  	}
    73  	type args struct {
    74  		queryParams map[string][]string
    75  	}
    76  	type wants struct {
    77  		statusCode  int
    78  		contentType string
    79  		body        string
    80  	}
    81  
    82  	tests := []struct {
    83  		name   string
    84  		fields fields
    85  		args   args
    86  		wants  wants
    87  	}{
    88  		{
    89  			name: "get all dashboards",
    90  			fields: fields{
    91  				&mock.DashboardService{
    92  					FindDashboardsF: func(ctx context.Context, filter influxdb.DashboardFilter, opts influxdb.FindOptions) ([]*influxdb.Dashboard, int, error) {
    93  						return []*influxdb.Dashboard{
    94  							{
    95  								ID:             dashboardstesting.MustIDBase16("da7aba5e5d81e550"),
    96  								OrganizationID: 1,
    97  								Name:           "hello",
    98  								Description:    "oh hello there!",
    99  								Meta: influxdb.DashboardMeta{
   100  									CreatedAt: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC),
   101  									UpdatedAt: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC),
   102  								},
   103  								Cells: []*influxdb.Cell{
   104  									{
   105  										ID: dashboardstesting.MustIDBase16("da7aba5e5d81e550"),
   106  										CellProperty: influxdb.CellProperty{
   107  											X: 1,
   108  											Y: 2,
   109  											W: 3,
   110  											H: 4,
   111  										},
   112  									},
   113  								},
   114  							},
   115  							{
   116  								ID:             dashboardstesting.MustIDBase16("0ca2204eca2204e0"),
   117  								OrganizationID: 1,
   118  								Meta: influxdb.DashboardMeta{
   119  									CreatedAt: time.Date(2012, time.November, 10, 23, 0, 0, 0, time.UTC),
   120  									UpdatedAt: time.Date(2012, time.November, 10, 24, 0, 0, 0, time.UTC),
   121  								},
   122  								Name: "example",
   123  							},
   124  						}, 2, nil
   125  					},
   126  				},
   127  				&mock.LabelService{
   128  					FindResourceLabelsFn: func(ctx context.Context, f influxdb.LabelMappingFilter) ([]*influxdb.Label, error) {
   129  						labels := []*influxdb.Label{
   130  							{
   131  								ID:   dashboardstesting.MustIDBase16("fc3dc670a4be9b9a"),
   132  								Name: "label",
   133  								Properties: map[string]string{
   134  									"color": "fff000",
   135  								},
   136  							},
   137  						}
   138  						return labels, nil
   139  					},
   140  				},
   141  			},
   142  			args: args{},
   143  			wants: wants{
   144  				statusCode:  http.StatusOK,
   145  				contentType: "application/json; charset=utf-8",
   146  				body: `
   147  {
   148    "links": {
   149      "self": "/api/v2/dashboards?descending=false&limit=` + strconv.Itoa(influxdb.DefaultPageSize) + `&offset=0"
   150    },
   151    "dashboards": [
   152      {
   153        "id": "da7aba5e5d81e550",
   154        "orgID": "0000000000000001",
   155        "name": "hello",
   156        "description": "oh hello there!",
   157        "labels": [
   158          {
   159            "id": "fc3dc670a4be9b9a",
   160            "name": "label",
   161            "properties": {
   162              "color": "fff000"
   163            }
   164          }
   165        ],
   166        "meta": {
   167          "createdAt": "2009-11-10T23:00:00Z",
   168          "updatedAt": "2009-11-11T00:00:00Z"
   169        },
   170        "cells": [
   171          {
   172            "id": "da7aba5e5d81e550",
   173            "x": 1,
   174            "y": 2,
   175            "w": 3,
   176            "h": 4,
   177            "links": {
   178              "self": "/api/v2/dashboards/da7aba5e5d81e550/cells/da7aba5e5d81e550",
   179              "view": "/api/v2/dashboards/da7aba5e5d81e550/cells/da7aba5e5d81e550/view"
   180            }
   181          }
   182        ],
   183        "links": {
   184          "self": "/api/v2/dashboards/da7aba5e5d81e550",
   185          "org": "/api/v2/orgs/0000000000000001",
   186          "members": "/api/v2/dashboards/da7aba5e5d81e550/members",
   187          "owners": "/api/v2/dashboards/da7aba5e5d81e550/owners",
   188          "cells": "/api/v2/dashboards/da7aba5e5d81e550/cells",
   189          "labels": "/api/v2/dashboards/da7aba5e5d81e550/labels"
   190        }
   191      },
   192      {
   193        "id": "0ca2204eca2204e0",
   194        "orgID": "0000000000000001",
   195        "name": "example",
   196        "description": "",
   197  			"labels": [
   198          {
   199            "id": "fc3dc670a4be9b9a",
   200            "name": "label",
   201            "properties": {
   202              "color": "fff000"
   203            }
   204          }
   205        ],
   206        "meta": {
   207          "createdAt": "2012-11-10T23:00:00Z",
   208          "updatedAt": "2012-11-11T00:00:00Z"
   209        },
   210        "cells": [],
   211        "links": {
   212          "self": "/api/v2/dashboards/0ca2204eca2204e0",
   213          "org": "/api/v2/orgs/0000000000000001",
   214          "members": "/api/v2/dashboards/0ca2204eca2204e0/members",
   215          "owners": "/api/v2/dashboards/0ca2204eca2204e0/owners",
   216          "cells": "/api/v2/dashboards/0ca2204eca2204e0/cells",
   217          "labels": "/api/v2/dashboards/0ca2204eca2204e0/labels"
   218        }
   219      }
   220    ]
   221  }
   222  `,
   223  			},
   224  		},
   225  		{
   226  			name: "get all dashboards when there are none",
   227  			fields: fields{
   228  				&mock.DashboardService{
   229  					FindDashboardsF: func(ctx context.Context, filter influxdb.DashboardFilter, opts influxdb.FindOptions) ([]*influxdb.Dashboard, int, error) {
   230  						return []*influxdb.Dashboard{}, 0, nil
   231  					},
   232  				},
   233  				&mock.LabelService{
   234  					FindResourceLabelsFn: func(ctx context.Context, f influxdb.LabelMappingFilter) ([]*influxdb.Label, error) {
   235  						return []*influxdb.Label{}, nil
   236  					},
   237  				},
   238  			},
   239  			args: args{},
   240  			wants: wants{
   241  				statusCode:  http.StatusOK,
   242  				contentType: "application/json; charset=utf-8",
   243  				body: `
   244  {
   245    "links": {
   246      "self": "/api/v2/dashboards?descending=false&limit=` + strconv.Itoa(influxdb.DefaultPageSize) + `&offset=0"
   247    },
   248    "dashboards": []
   249  }`,
   250  			},
   251  		},
   252  		{
   253  			name: "get all dashboards belonging to org 1",
   254  			fields: fields{
   255  				&mock.DashboardService{
   256  					FindDashboardsF: func(ctx context.Context, filter influxdb.DashboardFilter, opts influxdb.FindOptions) ([]*influxdb.Dashboard, int, error) {
   257  						return []*influxdb.Dashboard{
   258  							{
   259  								ID:             dashboardstesting.MustIDBase16("da7aba5e5d81e550"),
   260  								OrganizationID: 1,
   261  								Name:           "hello",
   262  								Description:    "oh hello there!",
   263  								Meta: influxdb.DashboardMeta{
   264  									CreatedAt: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC),
   265  									UpdatedAt: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC),
   266  								},
   267  								Cells: []*influxdb.Cell{
   268  									{
   269  										ID: dashboardstesting.MustIDBase16("da7aba5e5d81e550"),
   270  										CellProperty: influxdb.CellProperty{
   271  											X: 1,
   272  											Y: 2,
   273  											W: 3,
   274  											H: 4,
   275  										},
   276  									},
   277  								},
   278  							},
   279  						}, 1, nil
   280  					},
   281  				},
   282  				&mock.LabelService{
   283  					FindResourceLabelsFn: func(ctx context.Context, f influxdb.LabelMappingFilter) ([]*influxdb.Label, error) {
   284  						labels := []*influxdb.Label{
   285  							{
   286  								ID:   dashboardstesting.MustIDBase16("fc3dc670a4be9b9a"),
   287  								Name: "label",
   288  								Properties: map[string]string{
   289  									"color": "fff000",
   290  								},
   291  							},
   292  						}
   293  						return labels, nil
   294  					},
   295  				},
   296  			},
   297  			args: args{
   298  				map[string][]string{
   299  					"orgID": {"0000000000000001"},
   300  				},
   301  			},
   302  			wants: wants{
   303  				statusCode:  http.StatusOK,
   304  				contentType: "application/json; charset=utf-8",
   305  				body: `
   306  {
   307    "links": {
   308      "self": "/api/v2/dashboards?descending=false&limit=` + strconv.Itoa(influxdb.DefaultPageSize) + `&offset=0&orgID=0000000000000001"
   309    },
   310    "dashboards": [
   311      {
   312        "id": "da7aba5e5d81e550",
   313        "orgID": "0000000000000001",
   314        "name": "hello",
   315        "description": "oh hello there!",
   316        "meta": {
   317          "createdAt": "2009-11-10T23:00:00Z",
   318          "updatedAt": "2009-11-11T00:00:00Z"
   319      },
   320      "labels": [
   321        {
   322          "id": "fc3dc670a4be9b9a",
   323          "name": "label",
   324          "properties": {
   325            "color": "fff000"
   326          }
   327        }
   328      ],
   329        "cells": [
   330          {
   331            "id": "da7aba5e5d81e550",
   332            "x": 1,
   333            "y": 2,
   334            "w": 3,
   335            "h": 4,
   336            "links": {
   337              "self": "/api/v2/dashboards/da7aba5e5d81e550/cells/da7aba5e5d81e550",
   338              "view": "/api/v2/dashboards/da7aba5e5d81e550/cells/da7aba5e5d81e550/view"
   339            }
   340          }
   341        ],
   342        "links": {
   343          "self": "/api/v2/dashboards/da7aba5e5d81e550",
   344          "org": "/api/v2/orgs/0000000000000001",
   345          "members": "/api/v2/dashboards/da7aba5e5d81e550/members",
   346          "owners": "/api/v2/dashboards/da7aba5e5d81e550/owners",
   347          "cells": "/api/v2/dashboards/da7aba5e5d81e550/cells",
   348          "labels": "/api/v2/dashboards/da7aba5e5d81e550/labels"
   349        }
   350      }
   351    ]
   352  }
   353  `,
   354  			},
   355  		},
   356  	}
   357  
   358  	for _, tt := range tests {
   359  		t.Run(tt.name, func(t *testing.T) {
   360  			log := zaptest.NewLogger(t)
   361  			h := newDashboardHandler(
   362  				log,
   363  				withDashboardService(tt.fields.DashboardService),
   364  				withLabelService(tt.fields.LabelService),
   365  			)
   366  
   367  			r := httptest.NewRequest("GET", "http://any.url", nil)
   368  
   369  			qp := r.URL.Query()
   370  			for k, vs := range tt.args.queryParams {
   371  				for _, v := range vs {
   372  					qp.Add(k, v)
   373  				}
   374  			}
   375  			r.URL.RawQuery = qp.Encode()
   376  
   377  			w := httptest.NewRecorder()
   378  
   379  			h.handleGetDashboards(w, r)
   380  
   381  			res := w.Result()
   382  			content := res.Header.Get("Content-Type")
   383  			body, _ := io.ReadAll(res.Body)
   384  
   385  			if res.StatusCode != tt.wants.statusCode {
   386  				t.Errorf("%q. handleGetDashboards() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode)
   387  			}
   388  			if tt.wants.contentType != "" && content != tt.wants.contentType {
   389  				t.Errorf("%q. handleGetDashboards() = %v, want %v", tt.name, content, tt.wants.contentType)
   390  			}
   391  			if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil {
   392  				t.Errorf("%q, handleGetDashboards(). error unmarshalling json %v", tt.name, err)
   393  			} else if tt.wants.body != "" && !eq {
   394  				t.Errorf("%q. handleGetDashboards() = ***%s***", tt.name, diff)
   395  			}
   396  		})
   397  	}
   398  }
   399  
   400  func TestService_handleGetDashboard(t *testing.T) {
   401  	type fields struct {
   402  		DashboardService influxdb.DashboardService
   403  	}
   404  	type args struct {
   405  		id          string
   406  		queryString map[string]string
   407  	}
   408  	type wants struct {
   409  		statusCode  int
   410  		contentType string
   411  		body        string
   412  	}
   413  	tests := []struct {
   414  		name   string
   415  		fields fields
   416  		args   args
   417  		wants  wants
   418  	}{
   419  		{
   420  			name: "get a dashboard by id with view properties",
   421  			fields: fields{
   422  				&mock.DashboardService{
   423  					GetDashboardCellViewF: func(ctx context.Context, dashboardID platform.ID, cellID platform.ID) (*influxdb.View, error) {
   424  						return &influxdb.View{ViewContents: influxdb.ViewContents{Name: "the cell name"}, Properties: influxdb.XYViewProperties{Type: influxdb.ViewPropertyTypeXY}}, nil
   425  					},
   426  					FindDashboardByIDF: func(ctx context.Context, id platform.ID) (*influxdb.Dashboard, error) {
   427  						if id == dashboardstesting.MustIDBase16("020f755c3c082000") {
   428  							return &influxdb.Dashboard{
   429  								ID:             dashboardstesting.MustIDBase16("020f755c3c082000"),
   430  								OrganizationID: 1,
   431  								Meta: influxdb.DashboardMeta{
   432  									CreatedAt: time.Date(2012, time.November, 10, 23, 0, 0, 0, time.UTC),
   433  									UpdatedAt: time.Date(2012, time.November, 10, 24, 0, 0, 0, time.UTC),
   434  								},
   435  								Name: "hello",
   436  								Cells: []*influxdb.Cell{
   437  									{
   438  										ID: dashboardstesting.MustIDBase16("da7aba5e5d81e550"),
   439  										CellProperty: influxdb.CellProperty{
   440  											X: 1,
   441  											Y: 2,
   442  											W: 3,
   443  											H: 4,
   444  										},
   445  										View: &influxdb.View{ViewContents: influxdb.ViewContents{Name: "the cell name"}, Properties: influxdb.XYViewProperties{Type: influxdb.ViewPropertyTypeXY}},
   446  									},
   447  								},
   448  							}, nil
   449  						}
   450  
   451  						return nil, fmt.Errorf("not found")
   452  					},
   453  				},
   454  			},
   455  			args: args{
   456  				id: "020f755c3c082000",
   457  				queryString: map[string]string{
   458  					"include": "properties",
   459  				},
   460  			},
   461  			wants: wants{
   462  				statusCode:  http.StatusOK,
   463  				contentType: "application/json; charset=utf-8",
   464  				body: `
   465  {
   466    "id": "020f755c3c082000",
   467    "orgID": "0000000000000001",
   468    "name": "hello",
   469    "description": "",
   470    "labels": [],
   471    "meta": {
   472      "createdAt": "2012-11-10T23:00:00Z",
   473      "updatedAt": "2012-11-11T00:00:00Z"
   474    },
   475    "cells": [
   476      {
   477        "id": "da7aba5e5d81e550",
   478        "x": 1,
   479        "y": 2,
   480        "w": 3,
   481  			"h": 4,
   482  			"name": "the cell name",
   483  			"properties": {
   484  			"shape": "chronograf-v2",
   485  			"axes": null,
   486  			"colors": null,
   487  			"geom": "",
   488  			"staticLegend": {},
   489  			"position": "",
   490  			"note": "",
   491  			"queries": null,
   492  			"shadeBelow": false,
   493  			"hoverDimension": "",
   494  			"showNoteWhenEmpty": false,
   495  			"timeFormat": "",
   496  			"type": "xy",
   497  			"xColumn": "",
   498  			"generateXAxisTicks": null,
   499  			"xTotalTicks": 0,
   500  			"xTickStart": 0,
   501  			"xTickStep": 0,
   502  			"yColumn": "",
   503  			"generateYAxisTicks": null,
   504  			"yTotalTicks": 0,
   505  			"yTickStart": 0,
   506  			"yTickStep": 0,
   507  			"legendColorizeRows": false,
   508  			"legendHide": false,
   509  			"legendOpacity": 0,
   510  			"legendOrientationThreshold": 0
   511  	  },
   512  	  "links": {
   513  		"self": "/api/v2/dashboards/020f755c3c082000/cells/da7aba5e5d81e550",
   514  		"view": "/api/v2/dashboards/020f755c3c082000/cells/da7aba5e5d81e550/view"
   515  	  }
   516      }
   517    ],
   518    "links": {
   519  	     "self": "/api/v2/dashboards/020f755c3c082000",
   520  	     "org": "/api/v2/orgs/0000000000000001",
   521  	     "members": "/api/v2/dashboards/020f755c3c082000/members",
   522  	     "owners": "/api/v2/dashboards/020f755c3c082000/owners",
   523  	     "cells": "/api/v2/dashboards/020f755c3c082000/cells",
   524  	     "labels": "/api/v2/dashboards/020f755c3c082000/labels"
   525  	}
   526  }
   527  `,
   528  			},
   529  		},
   530  		{
   531  			name: "get a dashboard by id with view properties, but a cell doesnt exist",
   532  			fields: fields{
   533  				&mock.DashboardService{
   534  					GetDashboardCellViewF: func(ctx context.Context, dashboardID platform.ID, cellID platform.ID) (*influxdb.View, error) {
   535  						return nil, nil
   536  					},
   537  					FindDashboardByIDF: func(ctx context.Context, id platform.ID) (*influxdb.Dashboard, error) {
   538  						if id == dashboardstesting.MustIDBase16("020f755c3c082000") {
   539  							return &influxdb.Dashboard{
   540  								ID:             dashboardstesting.MustIDBase16("020f755c3c082000"),
   541  								OrganizationID: 1,
   542  								Meta: influxdb.DashboardMeta{
   543  									CreatedAt: time.Date(2012, time.November, 10, 23, 0, 0, 0, time.UTC),
   544  									UpdatedAt: time.Date(2012, time.November, 10, 24, 0, 0, 0, time.UTC),
   545  								},
   546  								Name: "hello",
   547  								Cells: []*influxdb.Cell{
   548  									{
   549  										ID: dashboardstesting.MustIDBase16("da7aba5e5d81e550"),
   550  										CellProperty: influxdb.CellProperty{
   551  											X: 1,
   552  											Y: 2,
   553  											W: 3,
   554  											H: 4,
   555  										},
   556  									},
   557  								},
   558  							}, nil
   559  						}
   560  
   561  						return nil, fmt.Errorf("not found")
   562  					},
   563  				},
   564  			},
   565  			args: args{
   566  				id: "020f755c3c082000",
   567  				queryString: map[string]string{
   568  					"include": "properties",
   569  				},
   570  			},
   571  			wants: wants{
   572  				statusCode:  http.StatusOK,
   573  				contentType: "application/json; charset=utf-8",
   574  				body: `
   575  {
   576    "id": "020f755c3c082000",
   577    "orgID": "0000000000000001",
   578    "name": "hello",
   579    "description": "",
   580    "labels": [],
   581    "meta": {
   582      "createdAt": "2012-11-10T23:00:00Z",
   583      "updatedAt": "2012-11-11T00:00:00Z"
   584    },
   585    "cells": [
   586      {
   587        "id": "da7aba5e5d81e550",
   588        "x": 1,
   589        "y": 2,
   590        "w": 3,
   591  	  "h": 4,
   592  	  "links": {
   593  		"self": "/api/v2/dashboards/020f755c3c082000/cells/da7aba5e5d81e550",
   594  		"view": "/api/v2/dashboards/020f755c3c082000/cells/da7aba5e5d81e550/view"
   595  	  }
   596      }
   597    ],
   598    "links": {
   599  	     "self": "/api/v2/dashboards/020f755c3c082000",
   600  	     "org": "/api/v2/orgs/0000000000000001",
   601  	     "members": "/api/v2/dashboards/020f755c3c082000/members",
   602  	     "owners": "/api/v2/dashboards/020f755c3c082000/owners",
   603  	     "cells": "/api/v2/dashboards/020f755c3c082000/cells",
   604  	     "labels": "/api/v2/dashboards/020f755c3c082000/labels"
   605  	}
   606  }
   607  `,
   608  			},
   609  		},
   610  		{
   611  			name: "get a dashboard by id doesnt return cell properties if they exist by default",
   612  			fields: fields{
   613  				&mock.DashboardService{
   614  					GetDashboardCellViewF: func(ctx context.Context, dashboardID platform.ID, cellID platform.ID) (*influxdb.View, error) {
   615  						return &influxdb.View{ViewContents: influxdb.ViewContents{Name: "the cell name"}, Properties: influxdb.XYViewProperties{Type: influxdb.ViewPropertyTypeXY}}, nil
   616  					},
   617  					FindDashboardByIDF: func(ctx context.Context, id platform.ID) (*influxdb.Dashboard, error) {
   618  						if id == dashboardstesting.MustIDBase16("020f755c3c082000") {
   619  							return &influxdb.Dashboard{
   620  								ID:             dashboardstesting.MustIDBase16("020f755c3c082000"),
   621  								OrganizationID: 1,
   622  								Meta: influxdb.DashboardMeta{
   623  									CreatedAt: time.Date(2012, time.November, 10, 23, 0, 0, 0, time.UTC),
   624  									UpdatedAt: time.Date(2012, time.November, 10, 24, 0, 0, 0, time.UTC),
   625  								},
   626  								Name: "hello",
   627  								Cells: []*influxdb.Cell{
   628  									{
   629  										ID: dashboardstesting.MustIDBase16("da7aba5e5d81e550"),
   630  										CellProperty: influxdb.CellProperty{
   631  											X: 1,
   632  											Y: 2,
   633  											W: 3,
   634  											H: 4,
   635  										},
   636  									},
   637  								},
   638  							}, nil
   639  						}
   640  
   641  						return nil, fmt.Errorf("not found")
   642  					},
   643  				},
   644  			},
   645  			args: args{
   646  				id:          "020f755c3c082000",
   647  				queryString: map[string]string{},
   648  			},
   649  			wants: wants{
   650  				statusCode:  http.StatusOK,
   651  				contentType: "application/json; charset=utf-8",
   652  				body: `
   653  {
   654    "id": "020f755c3c082000",
   655    "orgID": "0000000000000001",
   656    "name": "hello",
   657    "description": "",
   658    "labels": [],
   659    "meta": {
   660      "createdAt": "2012-11-10T23:00:00Z",
   661      "updatedAt": "2012-11-11T00:00:00Z"
   662    },
   663    "cells": [
   664      {
   665        "id": "da7aba5e5d81e550",
   666        "x": 1,
   667        "y": 2,
   668        "w": 3,
   669  	  "h": 4,
   670  	  "links": {
   671  		"self": "/api/v2/dashboards/020f755c3c082000/cells/da7aba5e5d81e550",
   672  		"view": "/api/v2/dashboards/020f755c3c082000/cells/da7aba5e5d81e550/view"
   673  	  }
   674      }
   675    ],
   676    "links": {
   677  	     "self": "/api/v2/dashboards/020f755c3c082000",
   678  	     "org": "/api/v2/orgs/0000000000000001",
   679  	     "members": "/api/v2/dashboards/020f755c3c082000/members",
   680  	     "owners": "/api/v2/dashboards/020f755c3c082000/owners",
   681  	     "cells": "/api/v2/dashboards/020f755c3c082000/cells",
   682  	     "labels": "/api/v2/dashboards/020f755c3c082000/labels"
   683  	}
   684  }
   685  `,
   686  			},
   687  		},
   688  		{
   689  			name: "get a dashboard by id",
   690  			fields: fields{
   691  				&mock.DashboardService{
   692  					FindDashboardByIDF: func(ctx context.Context, id platform.ID) (*influxdb.Dashboard, error) {
   693  						if id == dashboardstesting.MustIDBase16("020f755c3c082000") {
   694  							return &influxdb.Dashboard{
   695  								ID:             dashboardstesting.MustIDBase16("020f755c3c082000"),
   696  								OrganizationID: 1,
   697  								Meta: influxdb.DashboardMeta{
   698  									CreatedAt: time.Date(2012, time.November, 10, 23, 0, 0, 0, time.UTC),
   699  									UpdatedAt: time.Date(2012, time.November, 10, 24, 0, 0, 0, time.UTC),
   700  								},
   701  								Name: "hello",
   702  								Cells: []*influxdb.Cell{
   703  									{
   704  										ID: dashboardstesting.MustIDBase16("da7aba5e5d81e550"),
   705  										CellProperty: influxdb.CellProperty{
   706  											X: 1,
   707  											Y: 2,
   708  											W: 3,
   709  											H: 4,
   710  										},
   711  									},
   712  								},
   713  							}, nil
   714  						}
   715  
   716  						return nil, fmt.Errorf("not found")
   717  					},
   718  				},
   719  			},
   720  			args: args{
   721  				id: "020f755c3c082000",
   722  			},
   723  			wants: wants{
   724  				statusCode:  http.StatusOK,
   725  				contentType: "application/json; charset=utf-8",
   726  				body: `
   727  		{
   728  		  "id": "020f755c3c082000",
   729  		  "orgID": "0000000000000001",
   730  		  "name": "hello",
   731  		  "description": "",
   732  		  "labels": [],
   733  		  "meta": {
   734  		    "createdAt": "2012-11-10T23:00:00Z",
   735  		    "updatedAt": "2012-11-11T00:00:00Z"
   736  		  },
   737  		  "cells": [
   738  		    {
   739  			  "id": "da7aba5e5d81e550",
   740  		      "x": 1,
   741  		      "y": 2,
   742  		      "w": 3,
   743  		      "h": 4,
   744  		      "links": {
   745  		        "self": "/api/v2/dashboards/020f755c3c082000/cells/da7aba5e5d81e550",
   746  		        "view": "/api/v2/dashboards/020f755c3c082000/cells/da7aba5e5d81e550/view"
   747  		      }
   748  		    }
   749  		  ],
   750  		  "links": {
   751  		    "self": "/api/v2/dashboards/020f755c3c082000",
   752  		    "org": "/api/v2/orgs/0000000000000001",
   753  		    "members": "/api/v2/dashboards/020f755c3c082000/members",
   754  		    "owners": "/api/v2/dashboards/020f755c3c082000/owners",
   755  		    "cells": "/api/v2/dashboards/020f755c3c082000/cells",
   756  		    "labels": "/api/v2/dashboards/020f755c3c082000/labels"
   757  		  }
   758  		}
   759  		`,
   760  			},
   761  		},
   762  		{
   763  			name: "not found",
   764  			fields: fields{
   765  				&mock.DashboardService{
   766  					FindDashboardByIDF: func(ctx context.Context, id platform.ID) (*influxdb.Dashboard, error) {
   767  						return nil, &errors.Error{
   768  							Code: errors.ENotFound,
   769  							Msg:  influxdb.ErrDashboardNotFound,
   770  						}
   771  					},
   772  				},
   773  			},
   774  			args: args{
   775  				id: "020f755c3c082000",
   776  			},
   777  			wants: wants{
   778  				statusCode: http.StatusNotFound,
   779  			},
   780  		},
   781  	}
   782  
   783  	for _, tt := range tests {
   784  		t.Run(tt.name, func(t *testing.T) {
   785  			h := newDashboardHandler(
   786  				zaptest.NewLogger(t),
   787  				withDashboardService(tt.fields.DashboardService),
   788  			)
   789  
   790  			r := httptest.NewRequest("GET", "http://any.url", nil)
   791  
   792  			urlQuery := r.URL.Query()
   793  
   794  			for k, v := range tt.args.queryString {
   795  				urlQuery.Add(k, v)
   796  			}
   797  
   798  			r.URL.RawQuery = urlQuery.Encode()
   799  
   800  			rctx := chi.NewRouteContext()
   801  			rctx.URLParams.Add("id", tt.args.id)
   802  			r = r.WithContext(context.WithValue(
   803  				context.Background(),
   804  				chi.RouteCtxKey,
   805  				rctx),
   806  			)
   807  
   808  			w := httptest.NewRecorder()
   809  
   810  			h.handleGetDashboard(w, r)
   811  
   812  			res := w.Result()
   813  			content := res.Header.Get("Content-Type")
   814  			body, _ := io.ReadAll(res.Body)
   815  			if res.StatusCode != tt.wants.statusCode {
   816  				t.Errorf("%q. handleGetDashboard() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode)
   817  			}
   818  			if tt.wants.contentType != "" && content != tt.wants.contentType {
   819  				t.Errorf("%q. handleGetDashboard() = %v, want %v", tt.name, content, tt.wants.contentType)
   820  			}
   821  			if eq, diff, err := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && err != nil {
   822  				t.Errorf("%q, handleGetDashboard(). error unmarshalling json %v", tt.name, err)
   823  			} else if tt.wants.body != "" && !eq {
   824  				t.Errorf("%q. handleGetDashboard() = ***%s***", tt.name, diff)
   825  			}
   826  		})
   827  	}
   828  }
   829  
   830  func TestService_handlePostDashboard(t *testing.T) {
   831  	type fields struct {
   832  		DashboardService influxdb.DashboardService
   833  	}
   834  	type args struct {
   835  		dashboard *influxdb.Dashboard
   836  	}
   837  	type wants struct {
   838  		statusCode  int
   839  		contentType string
   840  		body        string
   841  	}
   842  
   843  	tests := []struct {
   844  		name   string
   845  		fields fields
   846  		args   args
   847  		wants  wants
   848  	}{
   849  		{
   850  			name: "create a new dashboard",
   851  			fields: fields{
   852  				&mock.DashboardService{
   853  					CreateDashboardF: func(ctx context.Context, c *influxdb.Dashboard) error {
   854  						c.ID = dashboardstesting.MustIDBase16("020f755c3c082000")
   855  						c.Meta = influxdb.DashboardMeta{
   856  							CreatedAt: time.Date(2012, time.November, 10, 23, 0, 0, 0, time.UTC),
   857  							UpdatedAt: time.Date(2012, time.November, 10, 24, 0, 0, 0, time.UTC),
   858  						}
   859  						return nil
   860  					},
   861  				},
   862  			},
   863  			args: args{
   864  				dashboard: &influxdb.Dashboard{
   865  					ID:             dashboardstesting.MustIDBase16("020f755c3c082000"),
   866  					OrganizationID: 1,
   867  					Name:           "hello",
   868  					Description:    "howdy there",
   869  					Cells: []*influxdb.Cell{
   870  						{
   871  							ID: dashboardstesting.MustIDBase16("da7aba5e5d81e550"),
   872  							CellProperty: influxdb.CellProperty{
   873  								X: 1,
   874  								Y: 2,
   875  								W: 3,
   876  								H: 4,
   877  							},
   878  						},
   879  					},
   880  				},
   881  			},
   882  			wants: wants{
   883  				statusCode:  http.StatusCreated,
   884  				contentType: "application/json; charset=utf-8",
   885  				body: `
   886  						{
   887  						"id": "020f755c3c082000",
   888  						"orgID": "0000000000000001",
   889  						"name": "hello",
   890  						"description": "howdy there",
   891  						"labels": [],
   892  						"meta": {
   893  							"createdAt": "2012-11-10T23:00:00Z",
   894  							"updatedAt": "2012-11-11T00:00:00Z"
   895  						},
   896  						"cells": [
   897  							{
   898  								"id": "da7aba5e5d81e550",
   899  								"x": 1,
   900  								"y": 2,
   901  								"w": 3,
   902  								"h": 4,
   903  								"links": {
   904  									"self": "/api/v2/dashboards/020f755c3c082000/cells/da7aba5e5d81e550",
   905  									"view": "/api/v2/dashboards/020f755c3c082000/cells/da7aba5e5d81e550/view"
   906  								}
   907  							}
   908  						],
   909  						"links": {
   910  							"self": "/api/v2/dashboards/020f755c3c082000",
   911  							"org": "/api/v2/orgs/0000000000000001",
   912  							"members": "/api/v2/dashboards/020f755c3c082000/members",
   913  							"owners": "/api/v2/dashboards/020f755c3c082000/owners",
   914  							"cells": "/api/v2/dashboards/020f755c3c082000/cells",
   915  							"labels": "/api/v2/dashboards/020f755c3c082000/labels"
   916  						}
   917  						}`,
   918  			},
   919  		},
   920  		{
   921  			name: "create a new dashboard with cell view properties",
   922  			fields: fields{
   923  				&mock.DashboardService{
   924  					CreateDashboardF: func(ctx context.Context, c *influxdb.Dashboard) error {
   925  						c.ID = dashboardstesting.MustIDBase16("020f755c3c082000")
   926  						c.Meta = influxdb.DashboardMeta{
   927  							CreatedAt: time.Date(2012, time.November, 10, 23, 0, 0, 0, time.UTC),
   928  							UpdatedAt: time.Date(2012, time.November, 10, 24, 0, 0, 0, time.UTC),
   929  						}
   930  						c.Cells = []*influxdb.Cell{
   931  							{
   932  								ID: dashboardstesting.MustIDBase16("da7aba5e5d81e550"),
   933  								CellProperty: influxdb.CellProperty{
   934  									X: 1,
   935  									Y: 2,
   936  									W: 3,
   937  									H: 4,
   938  								},
   939  								View: &influxdb.View{
   940  									ViewContents: influxdb.ViewContents{
   941  										Name: "hello a view",
   942  									},
   943  									Properties: influxdb.XYViewProperties{
   944  										Type: influxdb.ViewPropertyTypeXY,
   945  										Note: "note",
   946  									},
   947  								},
   948  							},
   949  						}
   950  						return nil
   951  					},
   952  				},
   953  			},
   954  			args: args{
   955  				dashboard: &influxdb.Dashboard{
   956  					ID:             dashboardstesting.MustIDBase16("020f755c3c082000"),
   957  					OrganizationID: 1,
   958  					Name:           "hello",
   959  					Description:    "howdy there",
   960  					Cells: []*influxdb.Cell{
   961  						{
   962  							ID: dashboardstesting.MustIDBase16("da7aba5e5d81e550"),
   963  							CellProperty: influxdb.CellProperty{
   964  								X: 1,
   965  								Y: 2,
   966  								W: 3,
   967  								H: 4,
   968  							},
   969  							View: &influxdb.View{
   970  								ViewContents: influxdb.ViewContents{
   971  									Name: "hello a view",
   972  								},
   973  								Properties: struct {
   974  									influxdb.XYViewProperties
   975  									Shape string
   976  								}{
   977  									XYViewProperties: influxdb.XYViewProperties{
   978  										Note: "note",
   979  										Type: influxdb.ViewPropertyTypeXY,
   980  									},
   981  									Shape: "chronograf-v2",
   982  								},
   983  							},
   984  						},
   985  					},
   986  				},
   987  			},
   988  			wants: wants{
   989  				statusCode:  http.StatusCreated,
   990  				contentType: "application/json; charset=utf-8",
   991  				body: `
   992  				{
   993  					"id": "020f755c3c082000",
   994  					"orgID": "0000000000000001",
   995  					"name": "hello",
   996  					"description": "howdy there",
   997  					"labels": [],
   998  					"meta": {
   999  						"createdAt": "2012-11-10T23:00:00Z",
  1000  						"updatedAt": "2012-11-11T00:00:00Z"
  1001  					},
  1002  					"cells": [
  1003  						{
  1004  							"id": "da7aba5e5d81e550",
  1005  							"x": 1,
  1006  							"y": 2,
  1007  							"w": 3,
  1008  							"h": 4,
  1009  							"name": "hello a view",
  1010  							"properties": {
  1011  								"shape": "chronograf-v2",
  1012  								"axes": null,
  1013  								"colors": null,
  1014  								"geom": "",
  1015  								"staticLegend": {},
  1016  								"note": "note",
  1017  								"position": "",
  1018  								"queries": null,
  1019  								"shadeBelow": false,
  1020  								"hoverDimension": "",
  1021  								"showNoteWhenEmpty": false,
  1022  								"timeFormat": "",
  1023  								"type": "",
  1024  								"xColumn": "",
  1025  								"generateXAxisTicks": null,
  1026  								"xTotalTicks": 0,
  1027  								"xTickStart": 0,
  1028  								"xTickStep": 0,
  1029  								"yColumn": "",
  1030  								"generateYAxisTicks": null,
  1031  								"yTotalTicks": 0,
  1032  								"yTickStart": 0,
  1033  								"yTickStep": 0,
  1034  								"type": "xy",
  1035  								"legendColorizeRows": false,
  1036  								"legendHide": false,
  1037  								"legendOpacity": 0,
  1038  								"legendOrientationThreshold": 0
  1039  							},
  1040  							"links": {
  1041  								"self": "/api/v2/dashboards/020f755c3c082000/cells/da7aba5e5d81e550",
  1042  								"view": "/api/v2/dashboards/020f755c3c082000/cells/da7aba5e5d81e550/view"
  1043  							}
  1044  						}
  1045  					],
  1046  					"links": {
  1047  						"self": "/api/v2/dashboards/020f755c3c082000",
  1048  						"org": "/api/v2/orgs/0000000000000001",
  1049  						"members": "/api/v2/dashboards/020f755c3c082000/members",
  1050  						"owners": "/api/v2/dashboards/020f755c3c082000/owners",
  1051  						"cells": "/api/v2/dashboards/020f755c3c082000/cells",
  1052  						"labels": "/api/v2/dashboards/020f755c3c082000/labels"
  1053  					}
  1054  				}`,
  1055  			},
  1056  		},
  1057  	}
  1058  
  1059  	for _, tt := range tests {
  1060  		t.Run(tt.name, func(t *testing.T) {
  1061  			h := newDashboardHandler(
  1062  				zaptest.NewLogger(t),
  1063  				withDashboardService(tt.fields.DashboardService),
  1064  			)
  1065  
  1066  			b, err := json.Marshal(tt.args.dashboard)
  1067  			if err != nil {
  1068  				t.Fatalf("failed to unmarshal dashboard: %v", err)
  1069  			}
  1070  
  1071  			r := httptest.NewRequest("GET", "http://any.url", bytes.NewReader(b))
  1072  			w := httptest.NewRecorder()
  1073  
  1074  			h.handlePostDashboard(w, r)
  1075  
  1076  			res := w.Result()
  1077  			content := res.Header.Get("Content-Type")
  1078  			body, _ := io.ReadAll(res.Body)
  1079  
  1080  			if res.StatusCode != tt.wants.statusCode {
  1081  				t.Errorf("%q. handlePostDashboard() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode)
  1082  			}
  1083  			if tt.wants.contentType != "" && content != tt.wants.contentType {
  1084  				t.Errorf("%q. handlePostDashboard() = %v, want %v", tt.name, content, tt.wants.contentType)
  1085  			}
  1086  			if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil {
  1087  				t.Errorf("%q, handlePostDashboard(). error unmarshalling json %v", tt.name, err)
  1088  			} else if tt.wants.body != "" && !eq {
  1089  				t.Errorf("%q. handlePostDashboard() = ***%s***", tt.name, diff)
  1090  			}
  1091  		})
  1092  	}
  1093  }
  1094  
  1095  func TestService_handleDeleteDashboard(t *testing.T) {
  1096  	type fields struct {
  1097  		DashboardService influxdb.DashboardService
  1098  	}
  1099  	type args struct {
  1100  		id string
  1101  	}
  1102  	type wants struct {
  1103  		statusCode  int
  1104  		contentType string
  1105  		body        string
  1106  	}
  1107  
  1108  	tests := []struct {
  1109  		name   string
  1110  		fields fields
  1111  		args   args
  1112  		wants  wants
  1113  	}{
  1114  		{
  1115  			name: "remove a dashboard by id",
  1116  			fields: fields{
  1117  				&mock.DashboardService{
  1118  					DeleteDashboardF: func(ctx context.Context, id platform.ID) error {
  1119  						if id == dashboardstesting.MustIDBase16("020f755c3c082000") {
  1120  							return nil
  1121  						}
  1122  
  1123  						return fmt.Errorf("wrong id")
  1124  					},
  1125  				},
  1126  			},
  1127  			args: args{
  1128  				id: "020f755c3c082000",
  1129  			},
  1130  			wants: wants{
  1131  				statusCode: http.StatusNoContent,
  1132  			},
  1133  		},
  1134  		{
  1135  			name: "dashboard not found",
  1136  			fields: fields{
  1137  				&mock.DashboardService{
  1138  					DeleteDashboardF: func(ctx context.Context, id platform.ID) error {
  1139  						return &errors.Error{
  1140  							Code: errors.ENotFound,
  1141  							Msg:  influxdb.ErrDashboardNotFound,
  1142  						}
  1143  					},
  1144  				},
  1145  			},
  1146  			args: args{
  1147  				id: "020f755c3c082000",
  1148  			},
  1149  			wants: wants{
  1150  				statusCode: http.StatusNotFound,
  1151  			},
  1152  		},
  1153  	}
  1154  
  1155  	for _, tt := range tests {
  1156  		t.Run(tt.name, func(t *testing.T) {
  1157  			h := newDashboardHandler(
  1158  				zaptest.NewLogger(t),
  1159  				withDashboardService(tt.fields.DashboardService),
  1160  			)
  1161  
  1162  			r := httptest.NewRequest("GET", "http://any.url", nil)
  1163  
  1164  			rctx := chi.NewRouteContext()
  1165  			rctx.URLParams.Add("id", tt.args.id)
  1166  			r = r.WithContext(context.WithValue(
  1167  				context.Background(),
  1168  				chi.RouteCtxKey,
  1169  				rctx),
  1170  			)
  1171  			w := httptest.NewRecorder()
  1172  
  1173  			h.handleDeleteDashboard(w, r)
  1174  
  1175  			res := w.Result()
  1176  			content := res.Header.Get("Content-Type")
  1177  			body, _ := io.ReadAll(res.Body)
  1178  
  1179  			if res.StatusCode != tt.wants.statusCode {
  1180  				t.Errorf("%q. handleDeleteDashboard() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode)
  1181  			}
  1182  			if tt.wants.contentType != "" && content != tt.wants.contentType {
  1183  				t.Errorf("%q. handleDeleteDashboard() = %v, want %v", tt.name, content, tt.wants.contentType)
  1184  			}
  1185  			if tt.wants.body != "" {
  1186  				if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil {
  1187  					t.Errorf("%q, handleDeleteDashboard(). error unmarshalling json %v", tt.name, err)
  1188  				} else if !eq {
  1189  					t.Errorf("%q. handleDeleteDashboard() = ***%s***", tt.name, diff)
  1190  				}
  1191  			}
  1192  		})
  1193  	}
  1194  }
  1195  
  1196  func TestService_handlePatchDashboard(t *testing.T) {
  1197  	type fields struct {
  1198  		DashboardService influxdb.DashboardService
  1199  	}
  1200  	type args struct {
  1201  		id   string
  1202  		name string
  1203  	}
  1204  	type wants struct {
  1205  		statusCode  int
  1206  		contentType string
  1207  		body        string
  1208  	}
  1209  
  1210  	tests := []struct {
  1211  		name   string
  1212  		fields fields
  1213  		args   args
  1214  		wants  wants
  1215  	}{
  1216  		{
  1217  			name: "update a dashboard name",
  1218  			fields: fields{
  1219  				&mock.DashboardService{
  1220  					UpdateDashboardF: func(ctx context.Context, id platform.ID, upd influxdb.DashboardUpdate) (*influxdb.Dashboard, error) {
  1221  						if id == dashboardstesting.MustIDBase16("020f755c3c082000") {
  1222  							d := &influxdb.Dashboard{
  1223  								ID:             dashboardstesting.MustIDBase16("020f755c3c082000"),
  1224  								OrganizationID: 1,
  1225  								Name:           "hello",
  1226  								Meta: influxdb.DashboardMeta{
  1227  									CreatedAt: time.Date(2012, time.November, 10, 23, 0, 0, 0, time.UTC),
  1228  									UpdatedAt: time.Date(2012, time.November, 10, 25, 0, 0, 0, time.UTC),
  1229  								},
  1230  								Cells: []*influxdb.Cell{
  1231  									{
  1232  										ID: dashboardstesting.MustIDBase16("da7aba5e5d81e550"),
  1233  										CellProperty: influxdb.CellProperty{
  1234  											X: 1,
  1235  											Y: 2,
  1236  											W: 3,
  1237  											H: 4,
  1238  										},
  1239  									},
  1240  								},
  1241  							}
  1242  
  1243  							if upd.Name != nil {
  1244  								d.Name = *upd.Name
  1245  							}
  1246  
  1247  							return d, nil
  1248  						}
  1249  
  1250  						return nil, fmt.Errorf("not found")
  1251  					},
  1252  				},
  1253  			},
  1254  			args: args{
  1255  				id:   "020f755c3c082000",
  1256  				name: "example",
  1257  			},
  1258  			wants: wants{
  1259  				statusCode:  http.StatusOK,
  1260  				contentType: "application/json; charset=utf-8",
  1261  				body: `
  1262  		{
  1263  		  "id": "020f755c3c082000",
  1264  		  "orgID": "0000000000000001",
  1265  		  "name": "example",
  1266  		  "description": "",
  1267  		  "labels": [],
  1268  		  "meta": {
  1269  		    "createdAt": "2012-11-10T23:00:00Z",
  1270  		    "updatedAt": "2012-11-11T01:00:00Z"
  1271  		  },
  1272  		  "cells": [
  1273  		    {
  1274  		      "id": "da7aba5e5d81e550",
  1275  		      "x": 1,
  1276  		      "y": 2,
  1277  		      "w": 3,
  1278  		      "h": 4,
  1279  		      "links": {
  1280  		        "self": "/api/v2/dashboards/020f755c3c082000/cells/da7aba5e5d81e550",
  1281  		        "view": "/api/v2/dashboards/020f755c3c082000/cells/da7aba5e5d81e550/view"
  1282  		      }
  1283  		    }
  1284  		  ],
  1285  		  "links": {
  1286  		    "self": "/api/v2/dashboards/020f755c3c082000",
  1287  		    "org": "/api/v2/orgs/0000000000000001",
  1288  		    "members": "/api/v2/dashboards/020f755c3c082000/members",
  1289  		    "owners": "/api/v2/dashboards/020f755c3c082000/owners",
  1290  		    "cells": "/api/v2/dashboards/020f755c3c082000/cells",
  1291  		    "labels": "/api/v2/dashboards/020f755c3c082000/labels"
  1292  		  }
  1293  		}
  1294  		`,
  1295  			},
  1296  		},
  1297  		{
  1298  			name: "update a dashboard with empty request body",
  1299  			fields: fields{
  1300  				&mock.DashboardService{
  1301  					UpdateDashboardF: func(ctx context.Context, id platform.ID, upd influxdb.DashboardUpdate) (*influxdb.Dashboard, error) {
  1302  						return nil, fmt.Errorf("not found")
  1303  					},
  1304  				},
  1305  			},
  1306  			args: args{
  1307  				id: "020f755c3c082000",
  1308  			},
  1309  			wants: wants{
  1310  				statusCode: http.StatusBadRequest,
  1311  			},
  1312  		},
  1313  		{
  1314  			name: "dashboard not found",
  1315  			fields: fields{
  1316  				&mock.DashboardService{
  1317  					UpdateDashboardF: func(ctx context.Context, id platform.ID, upd influxdb.DashboardUpdate) (*influxdb.Dashboard, error) {
  1318  						return nil, &errors.Error{
  1319  							Code: errors.ENotFound,
  1320  							Msg:  influxdb.ErrDashboardNotFound,
  1321  						}
  1322  					},
  1323  				},
  1324  			},
  1325  			args: args{
  1326  				id:   "020f755c3c082000",
  1327  				name: "hello",
  1328  			},
  1329  			wants: wants{
  1330  				statusCode: http.StatusNotFound,
  1331  			},
  1332  		},
  1333  	}
  1334  
  1335  	for _, tt := range tests {
  1336  		t.Run(tt.name, func(t *testing.T) {
  1337  			h := newDashboardHandler(
  1338  				zaptest.NewLogger(t),
  1339  				withDashboardService(tt.fields.DashboardService),
  1340  			)
  1341  
  1342  			upd := influxdb.DashboardUpdate{}
  1343  			if tt.args.name != "" {
  1344  				upd.Name = &tt.args.name
  1345  			}
  1346  
  1347  			b, err := json.Marshal(upd)
  1348  			if err != nil {
  1349  				t.Fatalf("failed to unmarshal dashboard update: %v", err)
  1350  			}
  1351  
  1352  			r := httptest.NewRequest("GET", "http://any.url", bytes.NewReader(b))
  1353  
  1354  			rctx := chi.NewRouteContext()
  1355  			rctx.URLParams.Add("id", tt.args.id)
  1356  			r = r.WithContext(context.WithValue(
  1357  				context.Background(),
  1358  				chi.RouteCtxKey,
  1359  				rctx),
  1360  			)
  1361  
  1362  			w := httptest.NewRecorder()
  1363  
  1364  			h.handlePatchDashboard(w, r)
  1365  
  1366  			res := w.Result()
  1367  			content := res.Header.Get("Content-Type")
  1368  			body, _ := io.ReadAll(res.Body)
  1369  
  1370  			if res.StatusCode != tt.wants.statusCode {
  1371  				t.Errorf("%q. handlePatchDashboard() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode)
  1372  			}
  1373  			if tt.wants.contentType != "" && content != tt.wants.contentType {
  1374  				t.Errorf("%q. handlePatchDashboard() = %v, want %v", tt.name, content, tt.wants.contentType)
  1375  			}
  1376  			if tt.wants.body != "" {
  1377  				if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil {
  1378  					t.Errorf("%q, handlePatchDashboard(). error unmarshalling json %v", tt.name, err)
  1379  				} else if !eq {
  1380  					t.Errorf("%q. handlePatchDashboard() = ***%s***", tt.name, diff)
  1381  				}
  1382  			}
  1383  		})
  1384  	}
  1385  }
  1386  
  1387  func TestService_handlePostDashboardCell(t *testing.T) {
  1388  	type fields struct {
  1389  		DashboardService influxdb.DashboardService
  1390  	}
  1391  	type args struct {
  1392  		id   string
  1393  		body string
  1394  	}
  1395  	type wants struct {
  1396  		statusCode  int
  1397  		contentType string
  1398  		body        string
  1399  	}
  1400  
  1401  	tests := []struct {
  1402  		name   string
  1403  		fields fields
  1404  		args   args
  1405  		wants  wants
  1406  	}{
  1407  		{
  1408  			name: "empty body",
  1409  			fields: fields{
  1410  				&mock.DashboardService{
  1411  					AddDashboardCellF: func(ctx context.Context, id platform.ID, c *influxdb.Cell, opt influxdb.AddDashboardCellOptions) error {
  1412  						c.ID = dashboardstesting.MustIDBase16("020f755c3c082000")
  1413  						return nil
  1414  					},
  1415  				},
  1416  			},
  1417  			args: args{
  1418  				id: "020f755c3c082000",
  1419  			},
  1420  			wants: wants{
  1421  				statusCode:  http.StatusBadRequest,
  1422  				contentType: "application/json; charset=utf-8",
  1423  				body:        `{"code":"invalid","message":"bad request json body: EOF"}`,
  1424  			},
  1425  		},
  1426  		{
  1427  			name: "no properties",
  1428  			fields: fields{
  1429  				&mock.DashboardService{
  1430  					AddDashboardCellF: func(ctx context.Context, id platform.ID, c *influxdb.Cell, opt influxdb.AddDashboardCellOptions) error {
  1431  						c.ID = dashboardstesting.MustIDBase16("020f755c3c082000")
  1432  						return nil
  1433  					},
  1434  				},
  1435  			},
  1436  			args: args{
  1437  				id:   "020f755c3c082000",
  1438  				body: `{"bad":1}`,
  1439  			},
  1440  			wants: wants{
  1441  				statusCode:  http.StatusBadRequest,
  1442  				contentType: "application/json; charset=utf-8",
  1443  				body: `
  1444  				{
  1445  					"code": "invalid",
  1446  					"message": "req body is empty"
  1447  				}`,
  1448  			},
  1449  		},
  1450  		{
  1451  			name: "bad dash id",
  1452  			fields: fields{
  1453  				&mock.DashboardService{
  1454  					AddDashboardCellF: func(ctx context.Context, id platform.ID, c *influxdb.Cell, opt influxdb.AddDashboardCellOptions) error {
  1455  						c.ID = dashboardstesting.MustIDBase16("020f755c3c082000")
  1456  						return nil
  1457  					},
  1458  				},
  1459  			},
  1460  			args: args{
  1461  				id:   "fff",
  1462  				body: `{}`,
  1463  			},
  1464  			wants: wants{
  1465  				statusCode:  http.StatusBadRequest,
  1466  				contentType: "application/json; charset=utf-8",
  1467  				body: `
  1468  				{
  1469  					"code": "invalid",
  1470  					"message": "id must have a length of 16 bytes"
  1471  				}`,
  1472  			},
  1473  		},
  1474  		{
  1475  			name: "general create a dashboard cell",
  1476  			fields: fields{
  1477  				&mock.DashboardService{
  1478  					AddDashboardCellF: func(ctx context.Context, id platform.ID, c *influxdb.Cell, opt influxdb.AddDashboardCellOptions) error {
  1479  						c.ID = dashboardstesting.MustIDBase16("020f755c3c082000")
  1480  						return nil
  1481  					},
  1482  					GetDashboardCellViewF: func(ctx context.Context, id1, id2 platform.ID) (*influxdb.View, error) {
  1483  						return &influxdb.View{
  1484  							ViewContents: influxdb.ViewContents{
  1485  								ID: dashboardstesting.MustIDBase16("020f755c3c082001"),
  1486  							}}, nil
  1487  					},
  1488  				},
  1489  			},
  1490  			args: args{
  1491  				id:   "020f755c3c082000",
  1492  				body: `{"x":10,"y":11,"name":"name1","usingView":"020f755c3c082001"}`,
  1493  			},
  1494  			wants: wants{
  1495  				statusCode:  http.StatusCreated,
  1496  				contentType: "application/json; charset=utf-8",
  1497  				body: `
  1498  {
  1499    "id": "020f755c3c082000",
  1500    "x": 10,
  1501    "y": 11,
  1502    "w": 0,
  1503    "h": 0,
  1504    "links": {
  1505      "self": "/api/v2/dashboards/020f755c3c082000/cells/020f755c3c082000",
  1506      "view": "/api/v2/dashboards/020f755c3c082000/cells/020f755c3c082000/view"
  1507    }
  1508  }
  1509  `,
  1510  			},
  1511  		},
  1512  	}
  1513  
  1514  	for _, tt := range tests {
  1515  		t.Run(tt.name, func(t *testing.T) {
  1516  			h := newDashboardHandler(
  1517  				zaptest.NewLogger(t),
  1518  				withDashboardService(tt.fields.DashboardService),
  1519  			)
  1520  
  1521  			buf := new(bytes.Buffer)
  1522  			_, _ = buf.WriteString(tt.args.body)
  1523  			r := httptest.NewRequest("POST", "http://any.url", buf)
  1524  
  1525  			rctx := chi.NewRouteContext()
  1526  			rctx.URLParams.Add("id", tt.args.id)
  1527  			r = r.WithContext(context.WithValue(
  1528  				context.Background(),
  1529  				chi.RouteCtxKey,
  1530  				rctx),
  1531  			)
  1532  
  1533  			w := httptest.NewRecorder()
  1534  
  1535  			h.handlePostDashboardCell(w, r)
  1536  
  1537  			res := w.Result()
  1538  			content := res.Header.Get("Content-Type")
  1539  			body, _ := io.ReadAll(res.Body)
  1540  
  1541  			if res.StatusCode != tt.wants.statusCode {
  1542  				t.Errorf("%q. handlePostDashboardCell() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode)
  1543  			}
  1544  			if tt.wants.contentType != "" && content != tt.wants.contentType {
  1545  				t.Errorf("%q. handlePostDashboardCell() = %v, want %v", tt.name, content, tt.wants.contentType)
  1546  			}
  1547  			if tt.wants.body != "" {
  1548  				if eq, diff, err := jsonEqual(tt.wants.body, string(body)); err != nil {
  1549  					t.Errorf("%q, handlePostDashboardCell(). error unmarshalling json %v", tt.name, err)
  1550  				} else if !eq {
  1551  					t.Errorf("%q. handlePostDashboardCell() = ***%s***", tt.name, diff)
  1552  				}
  1553  			}
  1554  		})
  1555  	}
  1556  }
  1557  
  1558  func TestService_handleDeleteDashboardCell(t *testing.T) {
  1559  	type fields struct {
  1560  		DashboardService influxdb.DashboardService
  1561  	}
  1562  	type args struct {
  1563  		id     string
  1564  		cellID string
  1565  	}
  1566  	type wants struct {
  1567  		statusCode  int
  1568  		contentType string
  1569  		body        string
  1570  	}
  1571  
  1572  	tests := []struct {
  1573  		name   string
  1574  		fields fields
  1575  		args   args
  1576  		wants  wants
  1577  	}{
  1578  		{
  1579  			name: "remove a dashboard cell",
  1580  			fields: fields{
  1581  				&mock.DashboardService{
  1582  					RemoveDashboardCellF: func(ctx context.Context, id platform.ID, cellID platform.ID) error {
  1583  						return nil
  1584  					},
  1585  				},
  1586  			},
  1587  			args: args{
  1588  				id:     "020f755c3c082000",
  1589  				cellID: "020f755c3c082000",
  1590  			},
  1591  			wants: wants{
  1592  				statusCode: http.StatusNoContent,
  1593  			},
  1594  		},
  1595  	}
  1596  
  1597  	for _, tt := range tests {
  1598  		t.Run(tt.name, func(t *testing.T) {
  1599  			h := newDashboardHandler(
  1600  				zaptest.NewLogger(t),
  1601  				withDashboardService(tt.fields.DashboardService),
  1602  			)
  1603  
  1604  			r := httptest.NewRequest("GET", "http://any.url", nil)
  1605  
  1606  			rctx := chi.NewRouteContext()
  1607  			rctx.URLParams.Add("id", tt.args.id)
  1608  			rctx.URLParams.Add("cellID", tt.args.cellID)
  1609  			r = r.WithContext(context.WithValue(
  1610  				context.Background(),
  1611  				chi.RouteCtxKey,
  1612  				rctx),
  1613  			)
  1614  
  1615  			w := httptest.NewRecorder()
  1616  
  1617  			h.handleDeleteDashboardCell(w, r)
  1618  
  1619  			res := w.Result()
  1620  			content := res.Header.Get("Content-Type")
  1621  			body, _ := io.ReadAll(res.Body)
  1622  
  1623  			if res.StatusCode != tt.wants.statusCode {
  1624  				t.Errorf("%q. handleDeleteDashboardCell() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode)
  1625  			}
  1626  			if tt.wants.contentType != "" && content != tt.wants.contentType {
  1627  				t.Errorf("%q. handleDeleteDashboardCell() = %v, want %v", tt.name, content, tt.wants.contentType)
  1628  			}
  1629  			if tt.wants.body != "" {
  1630  				if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil {
  1631  					t.Errorf("%q, handleDeleteDashboardCell(). error unmarshalling json %v", tt.name, err)
  1632  				} else if !eq {
  1633  					t.Errorf("%q. handleDeleteDashboardCell() = ***%s***", tt.name, diff)
  1634  				}
  1635  			}
  1636  		})
  1637  	}
  1638  }
  1639  
  1640  func TestService_handlePatchDashboardCell(t *testing.T) {
  1641  	type fields struct {
  1642  		DashboardService influxdb.DashboardService
  1643  	}
  1644  	type args struct {
  1645  		id     string
  1646  		cellID string
  1647  		x      int32
  1648  		y      int32
  1649  		w      int32
  1650  		h      int32
  1651  	}
  1652  	type wants struct {
  1653  		statusCode  int
  1654  		contentType string
  1655  		body        string
  1656  	}
  1657  
  1658  	tests := []struct {
  1659  		name   string
  1660  		fields fields
  1661  		args   args
  1662  		wants  wants
  1663  	}{
  1664  		{
  1665  			name: "update a dashboard cell",
  1666  			fields: fields{
  1667  				&mock.DashboardService{
  1668  					UpdateDashboardCellF: func(ctx context.Context, id, cellID platform.ID, upd influxdb.CellUpdate) (*influxdb.Cell, error) {
  1669  						cell := &influxdb.Cell{
  1670  							ID: dashboardstesting.MustIDBase16("020f755c3c082000"),
  1671  						}
  1672  
  1673  						if err := upd.Apply(cell); err != nil {
  1674  							return nil, err
  1675  						}
  1676  
  1677  						return cell, nil
  1678  					},
  1679  				},
  1680  			},
  1681  			args: args{
  1682  				id:     "020f755c3c082000",
  1683  				cellID: "020f755c3c082000",
  1684  				x:      10,
  1685  				y:      11,
  1686  			},
  1687  			wants: wants{
  1688  				statusCode:  http.StatusOK,
  1689  				contentType: "application/json; charset=utf-8",
  1690  				body: `
  1691  {
  1692    "id": "020f755c3c082000",
  1693    "x": 10,
  1694    "y": 11,
  1695    "w": 0,
  1696    "h": 0,
  1697    "links": {
  1698      "self": "/api/v2/dashboards/020f755c3c082000/cells/020f755c3c082000",
  1699      "view": "/api/v2/dashboards/020f755c3c082000/cells/020f755c3c082000/view"
  1700    }
  1701  }
  1702  `,
  1703  			},
  1704  		},
  1705  	}
  1706  
  1707  	for _, tt := range tests {
  1708  		t.Run(tt.name, func(t *testing.T) {
  1709  			h := newDashboardHandler(
  1710  				zaptest.NewLogger(t),
  1711  				withDashboardService(tt.fields.DashboardService),
  1712  			)
  1713  
  1714  			upd := influxdb.CellUpdate{}
  1715  			if tt.args.x != 0 {
  1716  				upd.X = &tt.args.x
  1717  			}
  1718  			if tt.args.y != 0 {
  1719  				upd.Y = &tt.args.y
  1720  			}
  1721  			if tt.args.w != 0 {
  1722  				upd.W = &tt.args.w
  1723  			}
  1724  			if tt.args.h != 0 {
  1725  				upd.H = &tt.args.h
  1726  			}
  1727  
  1728  			b, err := json.Marshal(upd)
  1729  			if err != nil {
  1730  				t.Fatalf("failed to unmarshal cell: %v", err)
  1731  			}
  1732  
  1733  			r := httptest.NewRequest("GET", "http://any.url", bytes.NewReader(b))
  1734  
  1735  			rctx := chi.NewRouteContext()
  1736  			rctx.URLParams.Add("id", tt.args.id)
  1737  			rctx.URLParams.Add("cellID", tt.args.cellID)
  1738  			r = r.WithContext(context.WithValue(
  1739  				context.Background(),
  1740  				chi.RouteCtxKey,
  1741  				rctx),
  1742  			)
  1743  			w := httptest.NewRecorder()
  1744  
  1745  			h.handlePatchDashboardCell(w, r)
  1746  
  1747  			res := w.Result()
  1748  			content := res.Header.Get("Content-Type")
  1749  			body, _ := io.ReadAll(res.Body)
  1750  
  1751  			if res.StatusCode != tt.wants.statusCode {
  1752  				t.Errorf("%q. handlePatchDashboardCell() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode)
  1753  			}
  1754  			if tt.wants.contentType != "" && content != tt.wants.contentType {
  1755  				t.Errorf("%q. handlePatchDashboardCell() = %v, want %v", tt.name, content, tt.wants.contentType)
  1756  			}
  1757  			if tt.wants.body != "" {
  1758  				if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil {
  1759  					t.Errorf("%q, handlePatchDashboardCell(). error unmarshalling json %v", tt.name, err)
  1760  				} else if !eq {
  1761  					t.Errorf("%q. handlePatchDashboardCell() = ***%s***", tt.name, diff)
  1762  				}
  1763  			}
  1764  		})
  1765  	}
  1766  }
  1767  
  1768  func Test_dashboardCellIDPath(t *testing.T) {
  1769  	t.Parallel()
  1770  	dashboard, err := platform.IDFromString("deadbeefdeadbeef")
  1771  	if err != nil {
  1772  		t.Fatal(err)
  1773  	}
  1774  
  1775  	cell, err := platform.IDFromString("cade9a7ecade9a7e")
  1776  	if err != nil {
  1777  		t.Fatal(err)
  1778  	}
  1779  
  1780  	want := "/api/v2/dashboards/deadbeefdeadbeef/cells/cade9a7ecade9a7e"
  1781  	if got := dashboardCellIDPath(*dashboard, *cell); got != want {
  1782  		t.Errorf("dashboardCellIDPath() = got: %s want: %s", got, want)
  1783  	}
  1784  }
  1785  
  1786  func initDashboardService(f dashboardstesting.DashboardFields, t *testing.T) (influxdb.DashboardService, string, func()) {
  1787  	t.Helper()
  1788  	log := zaptest.NewLogger(t)
  1789  	store := itesting.NewTestInmemStore(t)
  1790  
  1791  	kvsvc := kv.NewService(log, store, &mock.OrganizationService{})
  1792  	kvsvc.IDGenerator = f.IDGenerator
  1793  
  1794  	svc := dashboards.NewService(
  1795  		store,
  1796  		kvsvc, // operation log storage
  1797  	)
  1798  
  1799  	svc.IDGenerator = f.IDGenerator
  1800  
  1801  	ctx := context.Background()
  1802  
  1803  	for _, d := range f.Dashboards {
  1804  		if err := svc.PutDashboard(ctx, d); err != nil {
  1805  			t.Fatalf("failed to populate dashboard")
  1806  		}
  1807  	}
  1808  
  1809  	h := newDashboardHandler(
  1810  		log,
  1811  		withDashboardService(svc),
  1812  	)
  1813  
  1814  	r := chi.NewRouter()
  1815  	r.Mount(h.Prefix(), h)
  1816  	server := httptest.NewServer(r)
  1817  
  1818  	httpClient, err := ihttp.NewHTTPClient(server.URL, "", false)
  1819  	if err != nil {
  1820  		t.Fatal(err)
  1821  	}
  1822  
  1823  	client := DashboardService{Client: httpClient}
  1824  
  1825  	return &client, "", server.Close
  1826  }
  1827  
  1828  func TestDashboardService(t *testing.T) {
  1829  	t.Parallel()
  1830  	dashboardstesting.DeleteDashboard(initDashboardService, t)
  1831  }
  1832  
  1833  func TestService_handlePostDashboardLabel(t *testing.T) {
  1834  	type fields struct {
  1835  		LabelService influxdb.LabelService
  1836  	}
  1837  	type args struct {
  1838  		labelMapping *influxdb.LabelMapping
  1839  		dashboardID  platform.ID
  1840  	}
  1841  	type wants struct {
  1842  		statusCode  int
  1843  		contentType string
  1844  		body        string
  1845  	}
  1846  
  1847  	tests := []struct {
  1848  		name   string
  1849  		fields fields
  1850  		args   args
  1851  		wants  wants
  1852  	}{
  1853  		{
  1854  			name: "add label to dashboard",
  1855  			fields: fields{
  1856  				LabelService: &mock.LabelService{
  1857  					FindLabelByIDFn: func(ctx context.Context, id platform.ID) (*influxdb.Label, error) {
  1858  						return &influxdb.Label{
  1859  							ID:   1,
  1860  							Name: "label",
  1861  							Properties: map[string]string{
  1862  								"color": "fff000",
  1863  							},
  1864  						}, nil
  1865  					},
  1866  					CreateLabelMappingFn: func(ctx context.Context, m *influxdb.LabelMapping) error { return nil },
  1867  				},
  1868  			},
  1869  			args: args{
  1870  				labelMapping: &influxdb.LabelMapping{
  1871  					ResourceID: 100,
  1872  					LabelID:    1,
  1873  				},
  1874  				dashboardID: 100,
  1875  			},
  1876  			wants: wants{
  1877  				statusCode:  http.StatusCreated,
  1878  				contentType: "application/json; charset=utf-8",
  1879  				body: `
  1880  {
  1881    "label": {
  1882      "id": "0000000000000001",
  1883      "name": "label",
  1884      "properties": {
  1885        "color": "fff000"
  1886      }
  1887    },
  1888    "links": {
  1889      "self": "/api/v2/labels/0000000000000001"
  1890    }
  1891  }
  1892  `,
  1893  			},
  1894  		},
  1895  	}
  1896  
  1897  	for _, tt := range tests {
  1898  		t.Run(tt.name, func(t *testing.T) {
  1899  			h := newDashboardHandler(
  1900  				zaptest.NewLogger(t),
  1901  				withLabelService(tt.fields.LabelService),
  1902  				withDashboardService(&mock.DashboardService{
  1903  					FindDashboardByIDF: func(_ context.Context, id platform.ID) (*influxdb.Dashboard, error) {
  1904  						return &influxdb.Dashboard{
  1905  							ID:             id,
  1906  							OrganizationID: platform.ID(25),
  1907  						}, nil
  1908  					},
  1909  				}),
  1910  			)
  1911  
  1912  			router := chi.NewRouter()
  1913  			router.Mount(h.Prefix(), h)
  1914  
  1915  			b, err := json.Marshal(tt.args.labelMapping)
  1916  			if err != nil {
  1917  				t.Fatalf("failed to unmarshal label mapping: %v", err)
  1918  			}
  1919  
  1920  			url := fmt.Sprintf("http://localhost:8086/api/v2/dashboards/%s/labels", tt.args.dashboardID)
  1921  			r := httptest.NewRequest("POST", url, bytes.NewReader(b))
  1922  			w := httptest.NewRecorder()
  1923  
  1924  			router.ServeHTTP(w, r)
  1925  
  1926  			res := w.Result()
  1927  			content := res.Header.Get("Content-Type")
  1928  			body, _ := io.ReadAll(res.Body)
  1929  
  1930  			if res.StatusCode != tt.wants.statusCode {
  1931  				t.Errorf("got %v, want %v", res.StatusCode, tt.wants.statusCode)
  1932  			}
  1933  			if tt.wants.contentType != "" && content != tt.wants.contentType {
  1934  				t.Errorf("got %v, want %v", content, tt.wants.contentType)
  1935  			}
  1936  			if eq, diff, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq {
  1937  				t.Errorf("Diff\n%s", diff)
  1938  			}
  1939  		})
  1940  	}
  1941  }
  1942  
  1943  func jsonEqual(s1, s2 string) (eq bool, diff string, err error) {
  1944  	if s1 == s2 {
  1945  		return true, "", nil
  1946  	}
  1947  
  1948  	if s1 == "" {
  1949  		return false, s2, fmt.Errorf("s1 is empty")
  1950  	}
  1951  
  1952  	if s2 == "" {
  1953  		return false, s1, fmt.Errorf("s2 is empty")
  1954  	}
  1955  
  1956  	var o1 interface{}
  1957  	if err = json.Unmarshal([]byte(s1), &o1); err != nil {
  1958  		return
  1959  	}
  1960  
  1961  	var o2 interface{}
  1962  	if err = json.Unmarshal([]byte(s2), &o2); err != nil {
  1963  		return
  1964  	}
  1965  
  1966  	differ := gojsondiff.New()
  1967  	d, err := differ.Compare([]byte(s1), []byte(s2))
  1968  	if err != nil {
  1969  		return
  1970  	}
  1971  
  1972  	config := formatter.AsciiFormatterConfig{}
  1973  
  1974  	formatter := formatter.NewAsciiFormatter(o1, config)
  1975  	diff, err = formatter.Format(d)
  1976  
  1977  	return cmp.Equal(o1, o2), diff, err
  1978  }
  1979  
  1980  type dashboardDependencies struct {
  1981  	dashboardService influxdb.DashboardService
  1982  	userService      influxdb.UserService
  1983  	orgService       influxdb.OrganizationService
  1984  	labelService     influxdb.LabelService
  1985  	urmService       influxdb.UserResourceMappingService
  1986  }
  1987  
  1988  type option func(*dashboardDependencies)
  1989  
  1990  func withDashboardService(svc influxdb.DashboardService) option {
  1991  	return func(d *dashboardDependencies) {
  1992  		d.dashboardService = svc
  1993  	}
  1994  }
  1995  
  1996  func withLabelService(svc influxdb.LabelService) option {
  1997  	return func(d *dashboardDependencies) {
  1998  		d.labelService = svc
  1999  	}
  2000  }