package api import ( "context" "net/http" "net/http/httptest" "testing" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/provisioning" "github.com/grafana/grafana/pkg/setting" "github.com/stretchr/testify/assert" ) type reloadProvisioningTestCase struct { desc string url string expectedCode int expectedBody string permissions []*accesscontrol.Permission exit bool checkCall func(mock provisioning.ProvisioningServiceMock) } func TestAPI_AdminProvisioningReload_AccessControl(t *testing.T) { tests := []reloadProvisioningTestCase{ { desc: "should work for dashboards with specific scope", expectedCode: http.StatusOK, expectedBody: `{"message":"Dashboards config reloaded"}`, permissions: []*accesscontrol.Permission{ { Action: ActionProvisioningReload, Scope: ScopeProvisionersDashboards, }, }, url: "/api/admin/provisioning/dashboards/reload", checkCall: func(mock provisioning.ProvisioningServiceMock) { assert.Len(t, mock.Calls.ProvisionDashboards, 1) }, }, { desc: "should work for dashboards with broader scope", expectedCode: http.StatusOK, expectedBody: `{"message":"Dashboards config reloaded"}`, permissions: []*accesscontrol.Permission{ { Action: ActionProvisioningReload, Scope: ScopeProvisionersAll, }, }, url: "/api/admin/provisioning/dashboards/reload", checkCall: func(mock provisioning.ProvisioningServiceMock) { assert.Len(t, mock.Calls.ProvisionDashboards, 1) }, }, { desc: "should fail for dashboard with wrong scope", expectedCode: http.StatusForbidden, permissions: []*accesscontrol.Permission{ { Action: ActionProvisioningReload, Scope: "services:noservice", }, }, url: "/api/admin/provisioning/dashboards/reload", exit: true, }, { desc: "should fail for dashboard with no permission", expectedCode: http.StatusForbidden, url: "/api/admin/provisioning/dashboards/reload", exit: true, }, { desc: "should work for notifications with specific scope", expectedCode: http.StatusOK, expectedBody: `{"message":"Notifications config reloaded"}`, permissions: []*accesscontrol.Permission{ { Action: ActionProvisioningReload, Scope: ScopeProvisionersNotifications, }, }, url: "/api/admin/provisioning/notifications/reload", checkCall: func(mock provisioning.ProvisioningServiceMock) { assert.Len(t, mock.Calls.ProvisionNotifications, 1) }, }, { desc: "should fail for notifications with no permission", expectedCode: http.StatusForbidden, url: "/api/admin/provisioning/notifications/reload", exit: true, }, { desc: "should work for datasources with specific scope", expectedCode: http.StatusOK, expectedBody: `{"message":"Datasources config reloaded"}`, permissions: []*accesscontrol.Permission{ { Action: ActionProvisioningReload, Scope: ScopeProvisionersDatasources, }, }, url: "/api/admin/provisioning/datasources/reload", checkCall: func(mock provisioning.ProvisioningServiceMock) { assert.Len(t, mock.Calls.ProvisionDatasources, 1) }, }, { desc: "should fail for datasources with no permission", expectedCode: http.StatusForbidden, url: "/api/admin/provisioning/datasources/reload", exit: true, }, { desc: "should work for plugins with specific scope", expectedCode: http.StatusOK, expectedBody: `{"message":"Plugins config reloaded"}`, permissions: []*accesscontrol.Permission{ { Action: ActionProvisioningReload, Scope: ScopeProvisionersPlugins, }, }, url: "/api/admin/provisioning/plugins/reload", checkCall: func(mock provisioning.ProvisioningServiceMock) { assert.Len(t, mock.Calls.ProvisionPlugins, 1) }, }, { desc: "should fail for plugins with no permission", expectedCode: http.StatusForbidden, url: "/api/admin/provisioning/plugins/reload", exit: true, }, } cfg := setting.NewCfg() for _, test := range tests { t.Run(test.desc, func(t *testing.T) { sc, hs := setupAccessControlScenarioContext(t, cfg, test.url, test.permissions) // Setup the mock provisioningMock := provisioning.NewProvisioningServiceMock(context.Background()) hs.ProvisioningService = provisioningMock sc.resp = httptest.NewRecorder() var err error sc.req, err = http.NewRequest(http.MethodPost, test.url, nil) assert.NoError(t, err) sc.exec() // Check return code assert.Equal(t, test.expectedCode, sc.resp.Code) if test.exit { return } // Check body assert.Equal(t, test.expectedBody, sc.resp.Body.String()) // Check we actually called the provisioning service test.checkCall(*provisioningMock) }) } }