chore: replace artisanal FakeDashboardService with generated mock (#49276)

* chore: replace handmade FakeDashboardService with generated mock

Maintaining a handcrafted FakeDashboardService is not sustainable now that we are in the process of moving the dashboard-related functions out of sqlstore.

* remove dialect global variable
This commit is contained in:
Kristin Laemmert 2022-05-23 11:14:27 -04:00 committed by GitHub
parent ccd160a75e
commit 8c753999df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 640 additions and 275 deletions

View File

@ -8,6 +8,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/api/dtos"
@ -51,7 +52,7 @@ func TestAnnotationsAPIEndpoint(t *testing.T) {
role := models.ROLE_VIEWER
t.Run("Should not be allowed to save an annotation", func(t *testing.T) {
postAnnotationScenario(t, "When calling POST on", "/api/annotations", "/api/annotations", role,
cmd, store, func(sc *scenarioContext) {
cmd, store, nil, func(sc *scenarioContext) {
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 403, sc.resp.Code)
})
@ -84,7 +85,7 @@ func TestAnnotationsAPIEndpoint(t *testing.T) {
role := models.ROLE_EDITOR
t.Run("Should be able to save an annotation", func(t *testing.T) {
postAnnotationScenario(t, "When calling POST on", "/api/annotations", "/api/annotations", role,
cmd, store, func(sc *scenarioContext) {
cmd, store, nil, func(sc *scenarioContext) {
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 200, sc.resp.Code)
})
@ -155,7 +156,7 @@ func TestAnnotationsAPIEndpoint(t *testing.T) {
t.Run("When user is an Org Viewer", func(t *testing.T) {
role := models.ROLE_VIEWER
t.Run("Should not be allowed to save an annotation", func(t *testing.T) {
postAnnotationScenario(t, "When calling POST on", "/api/annotations", "/api/annotations", role, cmd, store, func(sc *scenarioContext) {
postAnnotationScenario(t, "When calling POST on", "/api/annotations", "/api/annotations", role, cmd, store, nil, func(sc *scenarioContext) {
setUpACL()
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 403, sc.resp.Code)
@ -188,7 +189,7 @@ func TestAnnotationsAPIEndpoint(t *testing.T) {
t.Run("When user is an Org Editor", func(t *testing.T) {
role := models.ROLE_EDITOR
t.Run("Should be able to save an annotation", func(t *testing.T) {
postAnnotationScenario(t, "When calling POST on", "/api/annotations", "/api/annotations", role, cmd, store, func(sc *scenarioContext) {
postAnnotationScenario(t, "When calling POST on", "/api/annotations", "/api/annotations", role, cmd, store, nil, func(sc *scenarioContext) {
setUpACL()
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 200, sc.resp.Code)
@ -221,19 +222,28 @@ func TestAnnotationsAPIEndpoint(t *testing.T) {
t.Run("When user is an Admin", func(t *testing.T) {
role := models.ROLE_ADMIN
mock := mockstore.NewSQLStoreMock()
mockStore := mockstore.NewSQLStoreMock()
t.Run("Should be able to do anything", func(t *testing.T) {
postAnnotationScenario(t, "When calling POST on", "/api/annotations", "/api/annotations", role, cmd, store, func(sc *scenarioContext) {
postAnnotationScenario(t, "When calling POST on", "/api/annotations", "/api/annotations", role, cmd, store, nil, func(sc *scenarioContext) {
setUpACL()
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 200, sc.resp.Code)
})
postAnnotationScenario(t, "When calling POST on", "/api/annotations", "/api/annotations", role, dashboardUIDCmd, mock, func(sc *scenarioContext) {
dashSvc := dashboards.NewFakeDashboardService(t)
dashSvc.On("GetDashboard", mock.Anything, mock.AnythingOfType("*models.GetDashboardQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*models.GetDashboardQuery)
q.Result = &models.Dashboard{
Id: q.Id,
Uid: q.Uid,
}
}).Return(nil)
postAnnotationScenario(t, "When calling POST on", "/api/annotations", "/api/annotations", role, dashboardUIDCmd, mockStore, dashSvc, func(sc *scenarioContext) {
setUpACL()
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 200, sc.resp.Code)
dashSvc.AssertCalled(t, "GetDashboard", mock.Anything, mock.AnythingOfType("*models.GetDashboardQuery"))
})
putAnnotationScenario(t, "When calling PUT on", "/api/annotations/1", "/api/annotations/:annotationId", role, updateCmd, func(sc *scenarioContext) {
@ -249,17 +259,26 @@ func TestAnnotationsAPIEndpoint(t *testing.T) {
})
deleteAnnotationsScenario(t, "When calling POST on", "/api/annotations/mass-delete",
"/api/annotations/mass-delete", role, deleteCmd, store, func(sc *scenarioContext) {
"/api/annotations/mass-delete", role, deleteCmd, store, nil, func(sc *scenarioContext) {
setUpACL()
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 200, sc.resp.Code)
})
dashSvc = dashboards.NewFakeDashboardService(t)
dashSvc.On("GetDashboard", mock.Anything, mock.AnythingOfType("*models.GetDashboardQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*models.GetDashboardQuery)
q.Result = &models.Dashboard{
Id: 1,
Uid: deleteWithDashboardUIDCmd.DashboardUID,
}
}).Return(nil)
deleteAnnotationsScenario(t, "When calling POST with dashboardUID on", "/api/annotations/mass-delete",
"/api/annotations/mass-delete", role, deleteWithDashboardUIDCmd, mock, func(sc *scenarioContext) {
"/api/annotations/mass-delete", role, deleteWithDashboardUIDCmd, mockStore, dashSvc, func(sc *scenarioContext) {
setUpACL()
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 200, sc.resp.Code)
dashSvc.AssertCalled(t, "GetDashboard", mock.Anything, mock.AnythingOfType("*models.GetDashboardQuery"))
})
})
})
@ -320,11 +339,11 @@ func (repo *fakeAnnotationsRepo) LoadItems() {
var fakeAnnoRepo *fakeAnnotationsRepo
func postAnnotationScenario(t *testing.T, desc string, url string, routePattern string, role models.RoleType,
cmd dtos.PostAnnotationsCmd, store sqlstore.Store, fn scenarioFunc) {
cmd dtos.PostAnnotationsCmd, store sqlstore.Store, dashSvc dashboards.DashboardService, fn scenarioFunc) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
hs := setupSimpleHTTPServer(nil)
hs.SQLStore = store
hs.dashboardService = &dashboards.FakeDashboardService{}
hs.dashboardService = dashSvc
sc := setupScenarioContext(t, url)
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
@ -405,11 +424,11 @@ func patchAnnotationScenario(t *testing.T, desc string, url string, routePattern
}
func deleteAnnotationsScenario(t *testing.T, desc string, url string, routePattern string, role models.RoleType,
cmd dtos.MassDeleteAnnotationsCmd, store sqlstore.Store, fn scenarioFunc) {
cmd dtos.MassDeleteAnnotationsCmd, store sqlstore.Store, dashSvc dashboards.DashboardService, fn scenarioFunc) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
hs := setupSimpleHTTPServer(nil)
hs.SQLStore = store
hs.dashboardService = &dashboards.FakeDashboardService{}
hs.dashboardService = dashSvc
sc := setupScenarioContext(t, url)
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {

View File

@ -7,12 +7,14 @@ import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestApiRetrieveConfig(t *testing.T) {
@ -51,11 +53,10 @@ func TestApiRetrieveConfig(t *testing.T) {
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
sc := setupHTTPServerWithMockDb(t, false, false, featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards))
sc.hs.dashboardService = &dashboards.FakeDashboardService{
PublicDashboardConfigResult: test.publicDashboardConfigResult,
PublicDashboardConfigError: test.publicDashboardConfigError,
}
dashSvc := dashboards.NewFakeDashboardService(t)
dashSvc.On("GetPublicDashboardConfig", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).
Return(test.publicDashboardConfigResult, test.publicDashboardConfigError)
sc.hs.dashboardService = dashSvc
setInitCtxSignedInViewer(sc.initCtx)
response := callAPI(
@ -106,11 +107,10 @@ func TestApiPersistsValue(t *testing.T) {
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
sc := setupHTTPServerWithMockDb(t, false, false, featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards))
sc.hs.dashboardService = &dashboards.FakeDashboardService{
PublicDashboardConfigResult: &models.PublicDashboardConfig{IsPublic: true},
PublicDashboardConfigError: test.saveDashboardError,
}
dashSvc := dashboards.NewFakeDashboardService(t)
dashSvc.On("SavePublicDashboardConfig", mock.Anything, mock.AnythingOfType("*dashboards.SavePublicDashboardConfigDTO")).
Return(&models.PublicDashboardConfig{IsPublic: true}, test.saveDashboardError)
sc.hs.dashboardService = dashSvc
setInitCtxSignedInViewer(sc.initCtx)
response := callAPI(

View File

@ -9,9 +9,6 @@ import (
"net/http"
"testing"
"github.com/grafana/grafana/pkg/coremodel/dashboard"
"github.com/grafana/grafana/pkg/cuectx"
"github.com/grafana/grafana/pkg/framework/coremodel"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
@ -20,6 +17,9 @@ import (
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/coremodel/dashboard"
"github.com/grafana/grafana/pkg/cuectx"
"github.com/grafana/grafana/pkg/framework/coremodel"
"github.com/grafana/grafana/pkg/infra/usagestats"
"github.com/grafana/grafana/pkg/models"
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
@ -122,11 +122,11 @@ func TestDashboardAPIEndpoint(t *testing.T) {
fakeDash.Id = 1
fakeDash.FolderId = 1
fakeDash.HasAcl = false
dashboardService := &dashboards.FakeDashboardService{}
dashboardService.GetDashboardFn = func(ctx context.Context, cmd *models.GetDashboardQuery) error {
cmd.Result = fakeDash
return nil
}
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*models.GetDashboardQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*models.GetDashboardQuery)
q.Result = fakeDash
}).Return(nil)
mockSQLStore := mockstore.NewSQLStoreMock()
@ -225,11 +225,11 @@ func TestDashboardAPIEndpoint(t *testing.T) {
fakeDash.Id = 1
fakeDash.FolderId = 1
fakeDash.HasAcl = true
dashboardService := &dashboards.FakeDashboardService{}
dashboardService.GetDashboardFn = func(ctx context.Context, cmd *models.GetDashboardQuery) error {
cmd.Result = fakeDash
return nil
}
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*models.GetDashboardQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*models.GetDashboardQuery)
q.Result = fakeDash
}).Return(nil)
mockSQLStore := mockstore.NewSQLStoreMock()
cfg := setting.NewCfg()
@ -289,7 +289,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
"/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
setUp()
sc.sqlStore = mockSQLStore
hs.callDeleteDashboardByUID(t, sc, &dashboards.FakeDashboardService{})
hs.callDeleteDashboardByUID(t, sc, dashboardService)
assert.Equal(t, 403, sc.resp.Code)
}, mockSQLStore)
@ -327,7 +327,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi",
"/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
setUp()
hs.callDeleteDashboardByUID(t, sc, &dashboards.FakeDashboardService{})
hs.callDeleteDashboardByUID(t, sc, dashboardService)
assert.Equal(t, 403, sc.resp.Code)
}, mockSQLStore)
@ -374,17 +374,14 @@ func TestDashboardAPIEndpoint(t *testing.T) {
loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
setUpInner()
mockDashboard := &dashboards.FakeDashboardService{
SaveDashboardResult: &models.Dashboard{
Id: fakeDash.Id,
Uid: "uid",
Title: "Dash",
Slug: "dash",
Version: 2,
},
}
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*models.GetDashboardQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*models.GetDashboardQuery)
q.Result = models.NewDashboard("test")
}).Return(nil)
dashboardService.On("DeleteDashboard", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(nil)
hs.callDeleteDashboardByUID(t, sc, mockDashboard)
hs.callDeleteDashboardByUID(t, sc, dashboardService)
assert.Equal(t, 200, sc.resp.Code)
}, mockSQLStore)
@ -468,7 +465,13 @@ func TestDashboardAPIEndpoint(t *testing.T) {
loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
setUpInner()
sc.sqlStore = mockSQLStore
hs.callDeleteDashboardByUID(t, sc, &dashboards.FakeDashboardService{})
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*models.GetDashboardQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*models.GetDashboardQuery)
q.Result = models.NewDashboard("test")
}).Return(nil)
dashboardService.On("DeleteDashboard", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(nil)
hs.callDeleteDashboardByUID(t, sc, dashboardService)
assert.Equal(t, 200, sc.resp.Code)
}, mockSQLStore)
@ -564,27 +567,13 @@ func TestDashboardAPIEndpoint(t *testing.T) {
Message: "msg",
}
mock := &dashboards.FakeDashboardService{
SaveDashboardResult: &models.Dashboard{
Id: dashID,
Uid: "uid",
Title: "Dash",
Slug: "dash",
Version: 2,
},
}
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardService.On("SaveDashboard", mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"), mock.AnythingOfType("bool")).
Return(&models.Dashboard{Id: dashID, Uid: "uid", Title: "Dash", Slug: "dash", Version: 2}, nil)
postDashboardScenario(t, "When calling POST on", "/api/dashboards", "/api/dashboards", cmd, mock, nil, func(sc *scenarioContext) {
postDashboardScenario(t, "When calling POST on", "/api/dashboards", "/api/dashboards", cmd, dashboardService, nil, func(sc *scenarioContext) {
callPostDashboardShouldReturnSuccess(sc)
dto := mock.SavedDashboards[0]
assert.Equal(t, cmd.OrgId, dto.OrgId)
assert.Equal(t, cmd.UserId, dto.User.UserId)
assert.Equal(t, folderID, dto.Dashboard.FolderId)
assert.Equal(t, "Dash", dto.Dashboard.Title)
assert.True(t, dto.Overwrite)
assert.Equal(t, "msg", dto.Message)
result := sc.ToJSON()
assert.Equal(t, "success", result.Get("status").MustString())
assert.Equal(t, dashID, result.Get("id").MustInt64())
@ -610,30 +599,17 @@ func TestDashboardAPIEndpoint(t *testing.T) {
Message: "msg",
}
mock := &dashboards.FakeDashboardService{
SaveDashboardResult: &models.Dashboard{
Id: dashID,
Uid: "uid",
Title: "Dash",
Slug: "dash",
Version: 2,
},
}
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardService.On("SaveDashboard", mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"), mock.AnythingOfType("bool")).
Return(&models.Dashboard{Id: dashID, Uid: "uid", Title: "Dash", Slug: "dash", Version: 2}, nil)
mockFolder := &fakeFolderService{
GetFolderByUIDResult: &models.Folder{Id: 1, Uid: "folderUID", Title: "Folder"},
}
postDashboardScenario(t, "When calling POST on", "/api/dashboards", "/api/dashboards", cmd, mock, mockFolder, func(sc *scenarioContext) {
postDashboardScenario(t, "When calling POST on", "/api/dashboards", "/api/dashboards", cmd, dashboardService, mockFolder, func(sc *scenarioContext) {
callPostDashboardShouldReturnSuccess(sc)
dto := mock.SavedDashboards[0]
assert.Equal(t, cmd.OrgId, dto.OrgId)
assert.Equal(t, cmd.UserId, dto.User.UserId)
assert.Equal(t, "Dash", dto.Dashboard.Title)
assert.True(t, dto.Overwrite)
assert.Equal(t, "msg", dto.Message)
result := sc.ToJSON()
assert.Equal(t, "success", result.Get("status").MustString())
assert.Equal(t, dashID, result.Get("id").MustInt64())
@ -644,9 +620,6 @@ func TestDashboardAPIEndpoint(t *testing.T) {
})
t.Run("Given a request with incorrect folder uid for creating a dashboard with", func(t *testing.T) {
const folderUid string = "folderUID"
const dashID int64 = 2
cmd := models.SaveDashboardCommand{
OrgId: 1,
UserId: 5,
@ -654,26 +627,18 @@ func TestDashboardAPIEndpoint(t *testing.T) {
"title": "Dash",
}),
Overwrite: true,
FolderUid: folderUid,
FolderUid: "folderUID",
IsFolder: false,
Message: "msg",
}
mock := &dashboards.FakeDashboardService{
SaveDashboardResult: &models.Dashboard{
Id: dashID,
Uid: "uid",
Title: "Dash",
Slug: "dash",
Version: 2,
},
}
dashboardService := dashboards.NewFakeDashboardService(t)
mockFolder := &fakeFolderService{
GetFolderByUIDError: errors.New("Error while searching Folder ID"),
}
postDashboardScenario(t, "When calling POST on", "/api/dashboards", "/api/dashboards", cmd, mock, mockFolder, func(sc *scenarioContext) {
postDashboardScenario(t, "When calling POST on", "/api/dashboards", "/api/dashboards", cmd, dashboardService, mockFolder, func(sc *scenarioContext) {
callPostDashboard(sc)
assert.Equal(t, 500, sc.resp.Code)
})
@ -713,12 +678,11 @@ func TestDashboardAPIEndpoint(t *testing.T) {
}
for _, tc := range testCases {
mock := &dashboards.FakeDashboardService{
SaveDashboardError: tc.SaveError,
}
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardService.On("SaveDashboard", mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"), mock.AnythingOfType("bool")).Return(nil, tc.SaveError)
postDashboardScenario(t, fmt.Sprintf("Expect '%s' error when calling POST on", tc.SaveError.Error()),
"/api/dashboards", "/api/dashboards", cmd, mock, nil, func(sc *scenarioContext) {
"/api/dashboards", "/api/dashboards", cmd, dashboardService, nil, func(sc *scenarioContext) {
callPostDashboard(sc)
assert.Equal(t, tc.ExpectedStatusCode, sc.resp.Code)
})
@ -788,21 +752,17 @@ func TestDashboardAPIEndpoint(t *testing.T) {
fakeDash.FolderId = folderID
fakeDash.HasAcl = false
saved := &models.Dashboard{
Id: 2,
Uid: "uid",
Title: "Dash",
Slug: "dash",
Version: 1,
}
mock := &dashboards.FakeDashboardService{
SaveDashboardResult: saved,
GetDashboardFn: func(ctx context.Context, cmd *models.GetDashboardQuery) error {
cmd.Result = fakeDash
return nil
},
}
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*models.GetDashboardQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*models.GetDashboardQuery)
q.Result = fakeDash
}).Return(nil)
dashboardService.On("SaveDashboard", mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"), mock.AnythingOfType("bool")).Run(func(args mock.Arguments) {
cmd := args.Get(1).(*dashboards.SaveDashboardDTO)
cmd.Dashboard = &models.Dashboard{
Id: 2, Uid: "uid", Title: "Dash", Slug: "dash", Version: 1,
}
}).Return(nil, nil)
cmd := dtos.RestoreDashboardVersionCommand{
Version: 1,
@ -814,15 +774,11 @@ func TestDashboardAPIEndpoint(t *testing.T) {
Version: 1,
Data: fakeDash.Data,
}}
restoreDashboardVersionScenario(t, "When calling POST on", "/api/dashboards/id/1/restore",
"/api/dashboards/id/:dashboardId/restore", mock, cmd, func(sc *scenarioContext) {
"/api/dashboards/id/:dashboardId/restore", dashboardService, cmd, func(sc *scenarioContext) {
callRestoreDashboardVersion(sc)
assert.Equal(t, 200, sc.resp.Code)
dto := mock.SavedDashboards[0]
assert.Equal(t, folderID, dto.Dashboard.FolderId)
assert.Equal(t, "Child dash", dto.Dashboard.Title)
assert.Equal(t, "Restored from version 1", dto.Message)
}, mockSQLStore)
})
@ -831,21 +787,17 @@ func TestDashboardAPIEndpoint(t *testing.T) {
fakeDash.Id = 2
fakeDash.HasAcl = false
saved := &models.Dashboard{
Id: 2,
Uid: "uid",
Title: "Dash",
Slug: "dash",
Version: 1,
}
mock := &dashboards.FakeDashboardService{
SaveDashboardResult: saved,
GetDashboardFn: func(ctx context.Context, cmd *models.GetDashboardQuery) error {
cmd.Result = fakeDash
return nil
},
}
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*models.GetDashboardQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*models.GetDashboardQuery)
q.Result = fakeDash
}).Return(nil)
dashboardService.On("SaveDashboard", mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"), mock.AnythingOfType("bool")).Run(func(args mock.Arguments) {
cmd := args.Get(1).(*dashboards.SaveDashboardDTO)
cmd.Dashboard = &models.Dashboard{
Id: 2, Uid: "uid", Title: "Dash", Slug: "dash", Version: 1,
}
}).Return(nil, nil)
cmd := dtos.RestoreDashboardVersionCommand{
Version: 1,
@ -858,14 +810,9 @@ func TestDashboardAPIEndpoint(t *testing.T) {
Data: fakeDash.Data,
}}
restoreDashboardVersionScenario(t, "When calling POST on", "/api/dashboards/id/1/restore",
"/api/dashboards/id/:dashboardId/restore", mock, cmd, func(sc *scenarioContext) {
"/api/dashboards/id/:dashboardId/restore", dashboardService, cmd, func(sc *scenarioContext) {
callRestoreDashboardVersion(sc)
assert.Equal(t, 200, sc.resp.Code)
dto := mock.SavedDashboards[0]
assert.Equal(t, int64(0), dto.Dashboard.FolderId)
assert.Equal(t, "Child dash", dto.Dashboard.Title)
assert.Equal(t, "Restored from version 1", dto.Message)
}, mockSQLStore)
})
@ -879,12 +826,12 @@ func TestDashboardAPIEndpoint(t *testing.T) {
dataValue, err := simplejson.NewJson([]byte(`{"id": 1, "editable": true, "style": "dark"}`))
require.NoError(t, err)
dashboardService := &dashboards.FakeDashboardService{
GetDashboardFn: func(ctx context.Context, cmd *models.GetDashboardQuery) error {
cmd.Result = &models.Dashboard{Id: 1, Data: dataValue}
return nil
},
}
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*models.GetDashboardQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*models.GetDashboardQuery)
q.Result = &models.Dashboard{Id: 1, Data: dataValue}
}).Return(nil)
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/uid/dash", "/api/dashboards/uid/:uid", models.ROLE_EDITOR, func(sc *scenarioContext) {
setUp()
@ -950,15 +897,15 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr
dashboardStore = database.ProvideDashboardStore(sql)
}
if dashboardService == nil {
dashboardService = &dashboards.FakeDashboardService{}
}
libraryPanelsService := mockLibraryPanelService{}
libraryElementsService := mockLibraryElementService{}
cfg := setting.NewCfg()
features := featuremgmt.WithFeatures()
if dashboardService == nil {
dashboardService = service.ProvideDashboardService(cfg, dashboardStore, nil, features, nil, accesscontrolmock.NewMockedPermissionsService())
}
hs := &HTTPServer{
Cfg: cfg,
LibraryPanelService: &libraryPanelsService,

View File

@ -1,7 +1,6 @@
package api
import (
"context"
"encoding/json"
"io/ioutil"
"net/http"
@ -9,6 +8,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/models"
@ -34,12 +34,12 @@ var (
func TestAPIEndpoint_GetCurrentOrgPreferences_LegacyAccessControl(t *testing.T) {
sc := setupHTTPServer(t, true, false)
dashSvc := &dashboards.FakeDashboardService{
GetDashboardFn: func(ctx context.Context, cmd *models.GetDashboardQuery) error {
cmd.Result = &models.Dashboard{Uid: "home", Id: 1}
return nil
},
}
dashSvc := dashboards.NewFakeDashboardService(t)
dashSvc.On("GetDashboard", mock.Anything, mock.AnythingOfType("*models.GetDashboardQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*models.GetDashboardQuery)
q.Result = &models.Dashboard{Uid: "home", Id: 1}
}).Return(nil)
sc.hs.dashboardService = dashSvc
prefService := preftest.NewPreferenceServiceFake()
@ -164,7 +164,11 @@ func TestAPIEndpoint_PatchUserPreferences(t *testing.T) {
assert.Equal(t, http.StatusBadRequest, response.Code)
})
input = strings.NewReader(testUpdateOrgPreferencesWithHomeDashboardUIDCmd)
dashSvc := &dashboards.FakeDashboardService{}
dashSvc := dashboards.NewFakeDashboardService(t)
dashSvc.On("GetDashboard", mock.Anything, mock.AnythingOfType("*models.GetDashboardQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*models.GetDashboardQuery)
q.Result = &models.Dashboard{Uid: "home", Id: 1}
}).Return(nil)
sc.hs.dashboardService = dashSvc
t.Run("Returns 200 on success", func(t *testing.T) {
response := callAPI(sc.server, http.MethodPatch, patchUserPreferencesUrl, input, t)

View File

@ -6,17 +6,12 @@ import (
"github.com/grafana/grafana/pkg/models"
)
// Generating mocks is handled by vektra/mockery
// 1. install go mockery https://github.com/vektra/mockery#go-install
// 2. add your method to the relevant services
// 3. from the same directory as this file run `go generate` and it will update the mock
// If you don't see any output, this most likely means your OS can't find the mockery binary
// `which mockery` to confirm and follow one of the installation methods
//go:generate mockery --name DashboardService --structname FakeDashboardService --inpackage --filename dashboard_service_mock.go
// DashboardService is a service for operating on dashboards.
type DashboardService interface {
BuildSaveDashboardCommand(ctx context.Context, dto *SaveDashboardDTO, shouldValidateAlerts bool, validateProvisionedDashboard bool) (*models.SaveDashboardCommand, error)
DeleteDashboard(ctx context.Context, dashboardId int64, orgId int64) error
FindDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error)
GetDashboard(ctx context.Context, query *models.GetDashboardQuery) error
GetDashboards(ctx context.Context, query *models.GetDashboardsQuery) error
GetDashboardUIDById(ctx context.Context, query *models.GetDashboardRefByIdQuery) error
@ -25,6 +20,7 @@ type DashboardService interface {
MakeUserAdmin(ctx context.Context, orgID int64, userID, dashboardID int64, setViewAndEditPermissions bool) error
SaveDashboard(ctx context.Context, dto *SaveDashboardDTO, allowUiUpdate bool) (*models.Dashboard, error)
SavePublicDashboardConfig(ctx context.Context, dto *SavePublicDashboardConfigDTO) (*models.PublicDashboardConfig, error)
SearchDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) error
UpdateDashboardACL(ctx context.Context, uid int64, items []*models.DashboardAcl) error
}
@ -51,6 +47,7 @@ type DashboardProvisioningService interface {
type Store interface {
DeleteDashboard(ctx context.Context, cmd *models.DeleteDashboardCommand) error
DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *models.DeleteOrphanedProvisionedDashboardsCommand) error
FindDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error)
GetDashboard(ctx context.Context, query *models.GetDashboardQuery) error
GetDashboardUIDById(ctx context.Context, query *models.GetDashboardRefByIdQuery) error
GetDashboards(ctx context.Context, query *models.GetDashboardsQuery) error
@ -65,6 +62,7 @@ type Store interface {
SaveDashboard(cmd models.SaveDashboardCommand) (*models.Dashboard, error)
SaveProvisionedDashboard(cmd models.SaveDashboardCommand, provisioning *models.DashboardProvisioning) (*models.Dashboard, error)
SavePublicDashboardConfig(cmd models.SavePublicDashboardConfigCommand) (*models.PublicDashboardConfig, error)
SearchDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) error
UnprovisionDashboard(ctx context.Context, id int64) error
UpdateDashboardACL(ctx context.Context, uid int64, items []*models.DashboardAcl) error
// ValidateDashboardBeforeSave validates a dashboard before save.

View File

@ -1,79 +1,263 @@
// Code generated by mockery v2.12.2. DO NOT EDIT.
package dashboards
import (
"context"
context "context"
"github.com/grafana/grafana/pkg/models"
models "github.com/grafana/grafana/pkg/models"
mock "github.com/stretchr/testify/mock"
testing "testing"
)
// FakeDashboardService is an autogenerated mock type for the DashboardService type
type FakeDashboardService struct {
DashboardService
SaveDashboardResult *models.Dashboard
SavedDashboards []*SaveDashboardDTO
ProvisionedDashData *models.DashboardProvisioning
PublicDashboardConfigResult *models.PublicDashboardConfig
PublicDashboardConfigError error
SaveDashboardError error
GetDashboardFn func(ctx context.Context, cmd *models.GetDashboardQuery) error
mock.Mock
}
func (s *FakeDashboardService) SaveDashboard(ctx context.Context, dto *SaveDashboardDTO, allowUiUpdate bool) (*models.Dashboard, error) {
s.SavedDashboards = append(s.SavedDashboards, dto)
// BuildSaveDashboardCommand provides a mock function with given fields: ctx, dto, shouldValidateAlerts, validateProvisionedDashboard
func (_m *FakeDashboardService) BuildSaveDashboardCommand(ctx context.Context, dto *SaveDashboardDTO, shouldValidateAlerts bool, validateProvisionedDashboard bool) (*models.SaveDashboardCommand, error) {
ret := _m.Called(ctx, dto, shouldValidateAlerts, validateProvisionedDashboard)
if s.SaveDashboardResult == nil && s.SaveDashboardError == nil {
s.SaveDashboardResult = dto.Dashboard
}
return s.SaveDashboardResult, s.SaveDashboardError
}
func (s *FakeDashboardService) GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboardConfig, error) {
return s.PublicDashboardConfigResult, s.PublicDashboardConfigError
}
func (s *FakeDashboardService) SavePublicDashboardConfig(ctx context.Context, dto *SavePublicDashboardConfigDTO) (*models.PublicDashboardConfig, error) {
return s.PublicDashboardConfigResult, s.PublicDashboardConfigError
}
func (s *FakeDashboardService) ImportDashboard(ctx context.Context, dto *SaveDashboardDTO) (*models.Dashboard, error) {
return s.SaveDashboard(ctx, dto, true)
}
func (s *FakeDashboardService) DeleteDashboard(ctx context.Context, dashboardId int64, orgId int64) error {
for index, dash := range s.SavedDashboards {
if dash.Dashboard.Id == dashboardId && dash.OrgId == orgId {
s.SavedDashboards = append(s.SavedDashboards[:index], s.SavedDashboards[index+1:]...)
break
var r0 *models.SaveDashboardCommand
if rf, ok := ret.Get(0).(func(context.Context, *SaveDashboardDTO, bool, bool) *models.SaveDashboardCommand); ok {
r0 = rf(ctx, dto, shouldValidateAlerts, validateProvisionedDashboard)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.SaveDashboardCommand)
}
}
return nil
}
func (s *FakeDashboardService) GetProvisionedDashboardDataByDashboardID(id int64) (*models.DashboardProvisioning, error) {
return s.ProvisionedDashData, nil
}
func (s *FakeDashboardService) DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *models.DeleteOrphanedProvisionedDashboardsCommand) error {
return nil
}
func (s *FakeDashboardService) GetDashboard(ctx context.Context, cmd *models.GetDashboardQuery) error {
if s.GetDashboardFn != nil {
return s.GetDashboardFn(ctx, cmd)
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *SaveDashboardDTO, bool, bool) error); ok {
r1 = rf(ctx, dto, shouldValidateAlerts, validateProvisionedDashboard)
} else {
r1 = ret.Error(1)
}
// A minimal result for tests that need a valid result, but don't care what's in it.
d := models.NewDashboard("mocked")
d.Id = 1
d.Uid = "1"
cmd.Result = d
return nil
return r0, r1
}
func (s *FakeDashboardService) GetDashboardUIDById(ctx context.Context, query *models.GetDashboardRefByIdQuery) error {
return nil
// DeleteDashboard provides a mock function with given fields: ctx, dashboardId, orgId
func (_m *FakeDashboardService) DeleteDashboard(ctx context.Context, dashboardId int64, orgId int64) error {
ret := _m.Called(ctx, dashboardId, orgId)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64, int64) error); ok {
r0 = rf(ctx, dashboardId, orgId)
} else {
r0 = ret.Error(0)
}
return r0
}
func (s *FakeDashboardService) GetDashboards(ctx context.Context, query *models.GetDashboardsQuery) error {
return nil
// FindDashboards provides a mock function with given fields: ctx, query
func (_m *FakeDashboardService) FindDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) {
ret := _m.Called(ctx, query)
var r0 []DashboardSearchProjection
if rf, ok := ret.Get(0).(func(context.Context, *models.FindPersistedDashboardsQuery) []DashboardSearchProjection); ok {
r0 = rf(ctx, query)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]DashboardSearchProjection)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *models.FindPersistedDashboardsQuery) error); ok {
r1 = rf(ctx, query)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetDashboard provides a mock function with given fields: ctx, query
func (_m *FakeDashboardService) GetDashboard(ctx context.Context, query *models.GetDashboardQuery) error {
ret := _m.Called(ctx, query)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *models.GetDashboardQuery) error); ok {
r0 = rf(ctx, query)
} else {
r0 = ret.Error(0)
}
return r0
}
// GetDashboardUIDById provides a mock function with given fields: ctx, query
func (_m *FakeDashboardService) GetDashboardUIDById(ctx context.Context, query *models.GetDashboardRefByIdQuery) error {
ret := _m.Called(ctx, query)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *models.GetDashboardRefByIdQuery) error); ok {
r0 = rf(ctx, query)
} else {
r0 = ret.Error(0)
}
return r0
}
// GetDashboards provides a mock function with given fields: ctx, query
func (_m *FakeDashboardService) GetDashboards(ctx context.Context, query *models.GetDashboardsQuery) error {
ret := _m.Called(ctx, query)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *models.GetDashboardsQuery) error); ok {
r0 = rf(ctx, query)
} else {
r0 = ret.Error(0)
}
return r0
}
// GetPublicDashboardConfig provides a mock function with given fields: ctx, orgId, dashboardUid
func (_m *FakeDashboardService) GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboardConfig, error) {
ret := _m.Called(ctx, orgId, dashboardUid)
var r0 *models.PublicDashboardConfig
if rf, ok := ret.Get(0).(func(context.Context, int64, string) *models.PublicDashboardConfig); ok {
r0 = rf(ctx, orgId, dashboardUid)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.PublicDashboardConfig)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int64, string) error); ok {
r1 = rf(ctx, orgId, dashboardUid)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ImportDashboard provides a mock function with given fields: ctx, dto
func (_m *FakeDashboardService) ImportDashboard(ctx context.Context, dto *SaveDashboardDTO) (*models.Dashboard, error) {
ret := _m.Called(ctx, dto)
var r0 *models.Dashboard
if rf, ok := ret.Get(0).(func(context.Context, *SaveDashboardDTO) *models.Dashboard); ok {
r0 = rf(ctx, dto)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Dashboard)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *SaveDashboardDTO) error); ok {
r1 = rf(ctx, dto)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MakeUserAdmin provides a mock function with given fields: ctx, orgID, userID, dashboardID, setViewAndEditPermissions
func (_m *FakeDashboardService) MakeUserAdmin(ctx context.Context, orgID int64, userID int64, dashboardID int64, setViewAndEditPermissions bool) error {
ret := _m.Called(ctx, orgID, userID, dashboardID, setViewAndEditPermissions)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64, int64, int64, bool) error); ok {
r0 = rf(ctx, orgID, userID, dashboardID, setViewAndEditPermissions)
} else {
r0 = ret.Error(0)
}
return r0
}
// SaveDashboard provides a mock function with given fields: ctx, dto, allowUiUpdate
func (_m *FakeDashboardService) SaveDashboard(ctx context.Context, dto *SaveDashboardDTO, allowUiUpdate bool) (*models.Dashboard, error) {
ret := _m.Called(ctx, dto, allowUiUpdate)
var r0 *models.Dashboard
if rf, ok := ret.Get(0).(func(context.Context, *SaveDashboardDTO, bool) *models.Dashboard); ok {
r0 = rf(ctx, dto, allowUiUpdate)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Dashboard)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *SaveDashboardDTO, bool) error); ok {
r1 = rf(ctx, dto, allowUiUpdate)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// SavePublicDashboardConfig provides a mock function with given fields: ctx, dto
func (_m *FakeDashboardService) SavePublicDashboardConfig(ctx context.Context, dto *SavePublicDashboardConfigDTO) (*models.PublicDashboardConfig, error) {
ret := _m.Called(ctx, dto)
var r0 *models.PublicDashboardConfig
if rf, ok := ret.Get(0).(func(context.Context, *SavePublicDashboardConfigDTO) *models.PublicDashboardConfig); ok {
r0 = rf(ctx, dto)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.PublicDashboardConfig)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *SavePublicDashboardConfigDTO) error); ok {
r1 = rf(ctx, dto)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// SearchDashboards provides a mock function with given fields: ctx, query
func (_m *FakeDashboardService) SearchDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) error {
ret := _m.Called(ctx, query)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *models.FindPersistedDashboardsQuery) error); ok {
r0 = rf(ctx, query)
} else {
r0 = ret.Error(0)
}
return r0
}
// UpdateDashboardACL provides a mock function with given fields: ctx, uid, items
func (_m *FakeDashboardService) UpdateDashboardACL(ctx context.Context, uid int64, items []*models.DashboardAcl) error {
ret := _m.Called(ctx, uid, items)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64, []*models.DashboardAcl) error); ok {
r0 = rf(ctx, uid, items)
} else {
r0 = ret.Error(0)
}
return r0
}
// NewFakeDashboardService creates a new instance of FakeDashboardService. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations.
func NewFakeDashboardService(t testing.TB) *FakeDashboardService {
mock := &FakeDashboardService{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -15,19 +15,22 @@ import (
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/services/sqlstore/permissions"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
"github.com/grafana/grafana/pkg/util"
)
type DashboardStore struct {
sqlStore *sqlstore.SQLStore
log log.Logger
dialect migrator.Dialect
}
// DashboardStore implements the Store interface
var _ dashboards.Store = (*DashboardStore)(nil)
func ProvideDashboardStore(sqlStore *sqlstore.SQLStore) *DashboardStore {
return &DashboardStore{sqlStore: sqlStore, log: log.New("dashboard-store")}
return &DashboardStore{sqlStore: sqlStore, log: log.New("dashboard-store"), dialect: sqlStore.Dialect}
}
func (d *DashboardStore) ValidateDashboardBeforeSave(dashboard *models.Dashboard, overwrite bool) (bool, error) {
@ -926,3 +929,143 @@ func (d *DashboardStore) GetDashboards(ctx context.Context, query *models.GetDas
return err
})
}
func (d *DashboardStore) SearchDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) error {
res, err := d.FindDashboards(ctx, query)
if err != nil {
return err
}
makeQueryResult(query, res)
return nil
}
func getHitType(item dashboards.DashboardSearchProjection) models.HitType {
var hitType models.HitType
if item.IsFolder {
hitType = models.DashHitFolder
} else {
hitType = models.DashHitDB
}
return hitType
}
func makeQueryResult(query *models.FindPersistedDashboardsQuery, res []dashboards.DashboardSearchProjection) {
query.Result = make([]*models.Hit, 0)
hits := make(map[int64]*models.Hit)
for _, item := range res {
hit, exists := hits[item.ID]
if !exists {
hit = &models.Hit{
ID: item.ID,
UID: item.UID,
Title: item.Title,
URI: "db/" + item.Slug,
URL: models.GetDashboardFolderUrl(item.IsFolder, item.UID, item.Slug),
Type: getHitType(item),
FolderID: item.FolderID,
FolderUID: item.FolderUID,
FolderTitle: item.FolderTitle,
Tags: []string{},
}
if item.FolderID > 0 {
hit.FolderURL = models.GetFolderUrl(item.FolderUID, item.FolderSlug)
}
if query.Sort.MetaName != "" {
hit.SortMeta = item.SortMeta
hit.SortMetaName = query.Sort.MetaName
}
query.Result = append(query.Result, hit)
hits[item.ID] = hit
}
if len(item.Term) > 0 {
hit.Tags = append(hit.Tags, item.Term)
}
}
}
func (d *DashboardStore) FindDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) ([]dashboards.DashboardSearchProjection, error) {
filters := []interface{}{
permissions.DashboardPermissionFilter{
OrgRole: query.SignedInUser.OrgRole,
OrgId: query.SignedInUser.OrgId,
Dialect: d.dialect,
UserId: query.SignedInUser.UserId,
PermissionLevel: query.Permission,
},
}
if !ac.IsDisabled(d.sqlStore.Cfg) {
// if access control is enabled, overwrite the filters so far
filters = []interface{}{
permissions.NewAccessControlDashboardPermissionFilter(query.SignedInUser, query.Permission, query.Type),
}
}
for _, filter := range query.Sort.Filter {
filters = append(filters, filter)
}
filters = append(filters, query.Filters...)
if query.OrgId != 0 {
filters = append(filters, searchstore.OrgFilter{OrgId: query.OrgId})
} else if query.SignedInUser.OrgId != 0 {
filters = append(filters, searchstore.OrgFilter{OrgId: query.SignedInUser.OrgId})
}
if len(query.Tags) > 0 {
filters = append(filters, searchstore.TagsFilter{Tags: query.Tags})
}
if len(query.DashboardIds) > 0 {
filters = append(filters, searchstore.DashboardFilter{IDs: query.DashboardIds})
}
if query.IsStarred {
filters = append(filters, searchstore.StarredFilter{UserId: query.SignedInUser.UserId})
}
if len(query.Title) > 0 {
filters = append(filters, searchstore.TitleFilter{Dialect: d.dialect, Title: query.Title})
}
if len(query.Type) > 0 {
filters = append(filters, searchstore.TypeFilter{Dialect: d.dialect, Type: query.Type})
}
if len(query.FolderIds) > 0 {
filters = append(filters, searchstore.FolderFilter{IDs: query.FolderIds})
}
var res []dashboards.DashboardSearchProjection
sb := &searchstore.Builder{Dialect: d.dialect, Filters: filters}
limit := query.Limit
if limit < 1 {
limit = 1000
}
page := query.Page
if page < 1 {
page = 1
}
sql, params := sb.ToSQL(limit, page)
err := d.sqlStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
return sess.SQL(sql, params...).Find(&res)
})
if err != nil {
return nil, err
}
return res, nil
}

View File

@ -242,7 +242,7 @@ func TestDashboardDataAccess(t *testing.T) {
SignedInUser: &models.SignedInUser{},
}
err = sqlStore.SearchDashboards(context.Background(), &query)
err = dashboardStore.SearchDashboards(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, len(query.Result), 0)
@ -315,7 +315,7 @@ func TestDashboardDataAccess(t *testing.T) {
},
}
err := sqlStore.SearchDashboards(context.Background(), &query)
err := dashboardStore.SearchDashboards(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, len(query.Result), 1)
@ -339,7 +339,7 @@ func TestDashboardDataAccess(t *testing.T) {
},
}
err := sqlStore.SearchDashboards(context.Background(), &query)
err := dashboardStore.SearchDashboards(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, len(query.Result), 1)
@ -364,7 +364,7 @@ func TestDashboardDataAccess(t *testing.T) {
},
}
err := sqlStore.SearchDashboards(context.Background(), &query)
err := dashboardStore.SearchDashboards(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, len(query.Result), 1)
@ -386,7 +386,7 @@ func TestDashboardDataAccess(t *testing.T) {
},
}
err := sqlStore.SearchDashboards(context.Background(), &query)
err := dashboardStore.SearchDashboards(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, len(query.Result), 3)
@ -407,7 +407,7 @@ func TestDashboardDataAccess(t *testing.T) {
},
}
err := sqlStore.SearchDashboards(context.Background(), &query)
err := dashboardStore.SearchDashboards(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, len(query.Result), 2)
@ -433,7 +433,7 @@ func TestDashboardDataAccess(t *testing.T) {
},
}
err := sqlStore.SearchDashboards(context.Background(), &query)
err := dashboardStore.SearchDashboards(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, len(query.Result), 2)
@ -471,7 +471,7 @@ func TestDashboardDataAccess(t *testing.T) {
},
IsStarred: true,
}
err = sqlStore.SearchDashboards(context.Background(), &query)
err = dashboardStore.SearchDashboards(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, len(query.Result), 1)

View File

@ -20,3 +20,17 @@ type SavePublicDashboardConfigDTO struct {
OrgId int64
PublicDashboardConfig models.PublicDashboardConfig
}
type DashboardSearchProjection struct {
ID int64 `xorm:"id"`
UID string `xorm:"uid"`
Title string
Slug string
Term string
IsFolder bool
FolderID int64 `xorm:"folder_id"`
FolderUID string `xorm:"folder_uid"`
FolderSlug string
FolderTitle string
SortMeta int64
}

View File

@ -12,7 +12,7 @@ import (
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/alerting"
m "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/setting"
@ -22,19 +22,19 @@ import (
var (
provisionerPermissions = map[string][]string{
m.ActionFoldersCreate: {},
m.ActionFoldersWrite: {m.ScopeFoldersAll},
m.ActionDashboardsCreate: {m.ScopeFoldersAll},
m.ActionDashboardsWrite: {m.ScopeFoldersAll},
dashboards.ActionFoldersCreate: {},
dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersAll},
dashboards.ActionDashboardsCreate: {dashboards.ScopeFoldersAll},
dashboards.ActionDashboardsWrite: {dashboards.ScopeFoldersAll},
}
// DashboardServiceImpl implements the DashboardService interface
_ m.DashboardService = (*DashboardServiceImpl)(nil)
_ dashboards.DashboardService = (*DashboardServiceImpl)(nil)
)
type DashboardServiceImpl struct {
cfg *setting.Cfg
log log.Logger
dashboardStore m.Store
dashboardStore dashboards.Store
dashAlertExtractor alerting.DashAlertExtractor
features featuremgmt.FeatureToggles
folderPermissions accesscontrol.FolderPermissionsService
@ -42,7 +42,7 @@ type DashboardServiceImpl struct {
}
func ProvideDashboardService(
cfg *setting.Cfg, store m.Store, dashAlertExtractor alerting.DashAlertExtractor,
cfg *setting.Cfg, store dashboards.Store, dashAlertExtractor alerting.DashAlertExtractor,
features featuremgmt.FeatureToggles, folderPermissionsService accesscontrol.FolderPermissionsService,
dashboardPermissionsService accesscontrol.DashboardPermissionsService,
) *DashboardServiceImpl {
@ -69,7 +69,7 @@ func (dr *DashboardServiceImpl) GetProvisionedDashboardDataByDashboardUID(orgID
return dr.dashboardStore.GetProvisionedDataByDashboardUID(orgID, dashboardUID)
}
func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, dto *m.SaveDashboardDTO, shouldValidateAlerts bool,
func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, dto *dashboards.SaveDashboardDTO, shouldValidateAlerts bool,
validateProvisionedDashboard bool) (*models.SaveDashboardCommand, error) {
dash := dto.Dashboard
@ -204,7 +204,7 @@ func validateDashboardRefreshInterval(dash *models.Dashboard) error {
return nil
}
func (dr *DashboardServiceImpl) SaveProvisionedDashboard(ctx context.Context, dto *m.SaveDashboardDTO,
func (dr *DashboardServiceImpl) SaveProvisionedDashboard(ctx context.Context, dto *dashboards.SaveDashboardDTO,
provisioning *models.DashboardProvisioning) (*models.Dashboard, error) {
if err := validateDashboardRefreshInterval(dto.Dashboard); err != nil {
dr.log.Warn("Changing refresh interval for provisioned dashboard to minimum refresh interval", "dashboardUid",
@ -258,7 +258,7 @@ func (dr *DashboardServiceImpl) SaveProvisionedDashboard(ctx context.Context, dt
return dash, nil
}
func (dr *DashboardServiceImpl) SaveFolderForProvisionedDashboards(ctx context.Context, dto *m.SaveDashboardDTO) (*models.Dashboard, error) {
func (dr *DashboardServiceImpl) SaveFolderForProvisionedDashboards(ctx context.Context, dto *dashboards.SaveDashboardDTO) (*models.Dashboard, error) {
dto.User = &models.SignedInUser{
UserId: 0,
OrgRole: models.ROLE_ADMIN,
@ -299,7 +299,7 @@ func (dr *DashboardServiceImpl) SaveFolderForProvisionedDashboards(ctx context.C
return dash, nil
}
func (dr *DashboardServiceImpl) SaveDashboard(ctx context.Context, dto *m.SaveDashboardDTO,
func (dr *DashboardServiceImpl) SaveDashboard(ctx context.Context, dto *dashboards.SaveDashboardDTO,
allowUiUpdate bool) (*models.Dashboard, error) {
if err := validateDashboardRefreshInterval(dto.Dashboard); err != nil {
dr.log.Warn("Changing refresh interval for imported dashboard to minimum refresh interval",
@ -356,7 +356,7 @@ func (dr *DashboardServiceImpl) GetPublicDashboardConfig(ctx context.Context, or
// SavePublicDashboardConfig is a helper method to persist the sharing config
// to the database. It handles validations for sharing config and persistence
func (dr *DashboardServiceImpl) SavePublicDashboardConfig(ctx context.Context, dto *m.SavePublicDashboardConfigDTO) (*models.PublicDashboardConfig, error) {
func (dr *DashboardServiceImpl) SavePublicDashboardConfig(ctx context.Context, dto *dashboards.SavePublicDashboardConfigDTO) (*models.PublicDashboardConfig, error) {
cmd := models.SavePublicDashboardConfigCommand{
Uid: dto.Uid,
OrgId: dto.OrgId,
@ -440,7 +440,7 @@ func (dr *DashboardServiceImpl) deleteDashboard(ctx context.Context, dashboardId
return dr.dashboardStore.DeleteDashboard(ctx, cmd)
}
func (dr *DashboardServiceImpl) ImportDashboard(ctx context.Context, dto *m.SaveDashboardDTO) (
func (dr *DashboardServiceImpl) ImportDashboard(ctx context.Context, dto *dashboards.SaveDashboardDTO) (
*models.Dashboard, error) {
if err := validateDashboardRefreshInterval(dto.Dashboard); err != nil {
dr.log.Warn("Changing refresh interval for imported dashboard to minimum refresh interval",
@ -476,7 +476,7 @@ func (dr *DashboardServiceImpl) GetDashboardsByPluginID(ctx context.Context, que
return dr.dashboardStore.GetDashboardsByPluginID(ctx, query)
}
func (dr *DashboardServiceImpl) setDefaultPermissions(ctx context.Context, dto *m.SaveDashboardDTO, dash *models.Dashboard, provisioned bool) error {
func (dr *DashboardServiceImpl) setDefaultPermissions(ctx context.Context, dto *dashboards.SaveDashboardDTO, dash *models.Dashboard, provisioned bool) error {
inFolder := dash.FolderId > 0
if !accesscontrol.IsDisabled(dr.cfg) {
var permissions []accesscontrol.SetResourcePermissionCommand
@ -522,3 +522,11 @@ func (dr *DashboardServiceImpl) GetDashboardUIDById(ctx context.Context, query *
func (dr *DashboardServiceImpl) GetDashboards(ctx context.Context, query *models.GetDashboardsQuery) error {
return dr.dashboardStore.GetDashboards(ctx, query)
}
func (dr *DashboardServiceImpl) FindDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) ([]dashboards.DashboardSearchProjection, error) {
return dr.dashboardStore.FindDashboards(ctx, query)
}
func (dr *DashboardServiceImpl) SearchDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) error {
return dr.dashboardStore.SearchDashboards(ctx, query)
}

View File

@ -37,8 +37,7 @@ func TestProvideFolderService(t *testing.T) {
ac := acmock.New()
ProvideFolderService(
cfg, &dashboards.FakeDashboardService{DashboardService: dashboardService},
store, nil, features, folderPermissions, ac,
cfg, dashboardService, store, nil, features, folderPermissions, ac,
)
require.Len(t, ac.Calls.RegisterAttributeScopeResolver, 2)

View File

@ -44,6 +44,29 @@ func (_m *FakeDashboardStore) DeleteOrphanedProvisionedDashboards(ctx context.Co
return r0
}
// FindDashboards provides a mock function with given fields: ctx, query
func (_m *FakeDashboardStore) FindDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) {
ret := _m.Called(ctx, query)
var r0 []DashboardSearchProjection
if rf, ok := ret.Get(0).(func(context.Context, *models.FindPersistedDashboardsQuery) []DashboardSearchProjection); ok {
r0 = rf(ctx, query)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]DashboardSearchProjection)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *models.FindPersistedDashboardsQuery) error); ok {
r1 = rf(ctx, query)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetDashboard provides a mock function with given fields: ctx, query
func (_m *FakeDashboardStore) GetDashboard(ctx context.Context, query *models.GetDashboardQuery) error {
ret := _m.Called(ctx, query)
@ -344,6 +367,20 @@ func (_m *FakeDashboardStore) SavePublicDashboardConfig(cmd models.SavePublicDas
return r0, r1
}
// SearchDashboards provides a mock function with given fields: ctx, query
func (_m *FakeDashboardStore) SearchDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) error {
ret := _m.Called(ctx, query)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *models.FindPersistedDashboardsQuery) error); ok {
r0 = rf(ctx, query)
} else {
r0 = ret.Error(0)
}
return r0
}
// UnprovisionDashboard provides a mock function with given fields: ctx, id
func (_m *FakeDashboardStore) UnprovisionDashboard(ctx context.Context, id int64) error {
ret := _m.Called(ctx, id)

View File

@ -101,8 +101,7 @@ func TestAccessControlDashboardGuardian_CanSave(t *testing.T) {
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
guardian, _ := setupAccessControlGuardianTest(t, tt.dashUID, tt.permissions)
guardian, _ := setupAccessControlGuardianTest(t, tt.dashUID, tt.permissions, testDashSvc(t))
can, err := guardian.CanSave()
require.NoError(t, err)
assert.Equal(t, tt.expected, can)
@ -193,7 +192,7 @@ func TestAccessControlDashboardGuardian_CanEdit(t *testing.T) {
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
guardian, _ := setupAccessControlGuardianTest(t, tt.dashUID, tt.permissions)
guardian, _ := setupAccessControlGuardianTest(t, tt.dashUID, tt.permissions, testDashSvc(t))
if tt.viewersCanEdit {
setting.ViewersCanEdit = true
@ -277,7 +276,7 @@ func TestAccessControlDashboardGuardian_CanView(t *testing.T) {
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
guardian, _ := setupAccessControlGuardianTest(t, tt.dashUID, tt.permissions)
guardian, _ := setupAccessControlGuardianTest(t, tt.dashUID, tt.permissions, testDashSvc(t))
can, err := guardian.CanView()
require.NoError(t, err)
@ -381,7 +380,7 @@ func TestAccessControlDashboardGuardian_CanAdmin(t *testing.T) {
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
guardian, _ := setupAccessControlGuardianTest(t, tt.dashUID, tt.permissions)
guardian, _ := setupAccessControlGuardianTest(t, tt.dashUID, tt.permissions, testDashSvc(t))
can, err := guardian.CanAdmin()
require.NoError(t, err)
@ -461,7 +460,7 @@ func TestAccessControlDashboardGuardian_CanDelete(t *testing.T) {
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
guardian, _ := setupAccessControlGuardianTest(t, tt.dashUID, tt.permissions)
guardian, _ := setupAccessControlGuardianTest(t, tt.dashUID, tt.permissions, testDashSvc(t))
can, err := guardian.CanDelete()
require.NoError(t, err)
@ -525,7 +524,7 @@ func TestAccessControlDashboardGuardian_CanCreate(t *testing.T) {
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
guardian, _ := setupAccessControlGuardianTest(t, "0", tt.permissions)
guardian, _ := setupAccessControlGuardianTest(t, "0", tt.permissions, nil)
can, err := guardian.CanCreate(tt.folderID, tt.isFolder)
require.NoError(t, err)
@ -557,7 +556,7 @@ func TestAccessControlDashboardGuardian_GetHiddenACL(t *testing.T) {
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
guardian, _ := setupAccessControlGuardianTest(t, "1", nil)
guardian, _ := setupAccessControlGuardianTest(t, "1", nil, testDashSvc(t))
mocked := accesscontrolmock.NewMockedPermissionsService()
guardian.dashboardPermissionsService = mocked
@ -579,7 +578,7 @@ func TestAccessControlDashboardGuardian_GetHiddenACL(t *testing.T) {
}
}
func setupAccessControlGuardianTest(t *testing.T, uid string, permissions []*accesscontrol.Permission) (*AccessControlDashboardGuardian, *models.Dashboard) {
func setupAccessControlGuardianTest(t *testing.T, uid string, permissions []*accesscontrol.Permission, dashboardSvc dashboards.DashboardService) (*AccessControlDashboardGuardian, *models.Dashboard) {
t.Helper()
store := sqlstore.InitTestDB(t)
@ -601,6 +600,20 @@ func setupAccessControlGuardianTest(t *testing.T, uid string, permissions []*acc
dashboardPermissions, err := ossaccesscontrol.ProvideDashboardPermissions(
setting.NewCfg(), routing.NewRouteRegister(), store, ac, database.ProvideService(store), &dashboards.FakeDashboardStore{})
require.NoError(t, err)
return NewAccessControlDashboardGuardian(context.Background(), dash.Id, &models.SignedInUser{OrgId: 1}, store, ac, folderPermissions, dashboardPermissions, &dashboards.FakeDashboardService{}), dash
if dashboardSvc == nil {
dashboardSvc = &dashboards.FakeDashboardService{}
}
return NewAccessControlDashboardGuardian(context.Background(), dash.Id, &models.SignedInUser{OrgId: 1}, store, ac, folderPermissions, dashboardPermissions, dashboardSvc), dash
}
func testDashSvc(t *testing.T) dashboards.DashboardService {
dashSvc := dashboards.NewFakeDashboardService(t)
dashSvc.On("GetDashboard", mock.Anything, mock.AnythingOfType("*models.GetDashboardQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*models.GetDashboardQuery)
d := models.NewDashboard("mocked")
d.Id = 1
d.Uid = "1"
q.Result = d
}).Return(nil)
return dashSvc
}

View File

@ -12,6 +12,7 @@ import (
"github.com/golang/mock/gomock"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/components/imguploader"
@ -78,19 +79,17 @@ func TestBrowserScreenshotService(t *testing.T) {
s := NewBrowserScreenshotService(&d, r)
// a non-existent dashboard should return error
d.GetDashboardFn = func(ctx context.Context, cmd *models.GetDashboardQuery) error {
return models.ErrDashboardNotFound
}
d.On("GetDashboard", mock.Anything, mock.AnythingOfType("*models.GetDashboardQuery")).Return(models.ErrDashboardNotFound).Once()
ctx := context.Background()
opts := ScreenshotOptions{}
screenshot, err := s.Take(ctx, opts)
assert.EqualError(t, err, "Dashboard not found")
assert.Nil(t, screenshot)
d.GetDashboardFn = func(ctx context.Context, cmd *models.GetDashboardQuery) error {
cmd.Result = &models.Dashboard{Id: 1, Uid: "foo", Slug: "bar", OrgId: 2}
return nil
}
d.On("GetDashboard", mock.Anything, mock.AnythingOfType("*models.GetDashboardQuery")).Run(func(args mock.Arguments) {
q := args.Get(1).(*models.GetDashboardQuery)
q.Result = &models.Dashboard{Id: 1, Uid: "foo", Slug: "bar", OrgId: 2}
}).Return(nil)
renderOpts := rendering.Opts{
AuthOpts: rendering.AuthOpts{