2021-11-16 17:26:46 +08:00
package dashboards
import (
"bytes"
"encoding/json"
"fmt"
2022-08-10 21:37:51 +08:00
"io"
2021-11-16 17:26:46 +08:00
"net/http"
2021-12-17 23:31:52 +08:00
"net/url"
"os"
"path/filepath"
2021-11-16 17:26:46 +08:00
"testing"
2024-04-08 15:47:34 +08:00
"time"
2021-11-16 17:26:46 +08:00
2022-06-30 21:31:54 +08:00
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
2023-11-01 23:01:54 +08:00
"github.com/grafana/grafana/pkg/api/dtos"
2021-11-16 17:26:46 +08:00
"github.com/grafana/grafana/pkg/components/simplejson"
2022-01-28 17:28:33 +08:00
"github.com/grafana/grafana/pkg/services/dashboardimport"
2022-06-30 21:31:54 +08:00
"github.com/grafana/grafana/pkg/services/dashboards"
2023-11-01 23:01:54 +08:00
"github.com/grafana/grafana/pkg/services/folder"
2025-07-29 15:52:27 +08:00
"github.com/grafana/grafana/pkg/services/org"
2022-03-11 01:38:04 +08:00
"github.com/grafana/grafana/pkg/services/plugindashboards"
2023-01-31 18:04:55 +08:00
"github.com/grafana/grafana/pkg/services/search/model"
2025-07-29 15:52:27 +08:00
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/tests"
2021-11-16 17:26:46 +08:00
"github.com/grafana/grafana/pkg/tests/testinfra"
2024-02-09 22:35:39 +08:00
"github.com/grafana/grafana/pkg/tests/testsuite"
2023-11-01 23:01:54 +08:00
"github.com/grafana/grafana/pkg/util"
2021-11-16 17:26:46 +08:00
)
2024-02-09 22:35:39 +08:00
func TestMain ( m * testing . M ) {
testsuite . Run ( m )
}
2025-07-29 15:52:27 +08:00
func TestIntegrationDashboardServiceValidation ( t * testing . T ) {
2022-12-09 15:11:56 +08:00
if testing . Short ( ) {
t . Skip ( "skipping integration test" )
}
2025-03-31 21:34:39 +08:00
2025-07-29 15:52:27 +08:00
dir , path := testinfra . CreateGrafDir ( t , testinfra . GrafanaOpts {
2025-07-30 05:52:57 +08:00
DisableAnonymous : true ,
2025-07-29 15:52:27 +08:00
} )
grafanaListedAddr , env := testinfra . StartGrafanaEnv ( t , dir , path )
orgPayload := map [ string ] interface { } {
"name" : "Org B" ,
2025-03-31 21:34:39 +08:00
}
2025-07-29 15:52:27 +08:00
orgPayloadBytes , err := json . Marshal ( orgPayload )
require . NoError ( t , err )
orgURL := fmt . Sprintf ( "http://admin:admin@%s/api/orgs" , grafanaListedAddr )
orgResp , err := http . Post ( orgURL , "application/json" , bytes . NewBuffer ( orgPayloadBytes ) ) // nolint:gosec
require . NoError ( t , err )
assert . Equal ( t , http . StatusOK , orgResp . StatusCode )
err = orgResp . Body . Close ( )
require . NoError ( t , err )
tests . CreateUser ( t , env . SQLStore , env . Cfg , user . CreateUserCommand {
DefaultOrgRole : string ( org . RoleAdmin ) ,
Login : "admin-org2" ,
Password : "admin" ,
IsAdmin : true ,
OrgID : 2 ,
} )
savedFolder := createFolder ( t , grafanaListedAddr , "Saved folder" )
savedDashInFolder := createDashboard ( t , grafanaListedAddr , "Saved dash in folder" , savedFolder . ID , savedFolder . UID ) // nolint:staticcheck
savedDashInGeneralFolder := createDashboard ( t , grafanaListedAddr , "Saved dashboard in general folder" , 0 , "" )
t . Run ( "When saving a dashboard with non-existing id in org A" , func ( t * testing . T ) {
resp , err := postDashboard ( t , grafanaListedAddr , "admin" , "admin" , map [ string ] interface { } {
"dashboard" : map [ string ] interface { } {
"id" : 123412321 ,
"title" : "Expect error" ,
} ,
} )
require . NoError ( t , err )
assert . Equal ( t , http . StatusNotFound , resp . StatusCode )
err = resp . Body . Close ( )
require . NoError ( t , err )
} )
t . Run ( "When saving a dashboard with existing ID from org A in org B" , func ( t * testing . T ) {
resp , err := postDashboard ( t , grafanaListedAddr , "admin-org2" , "admin" , map [ string ] interface { } {
"dashboard" : map [ string ] interface { } {
"id" : savedDashInFolder . ID , // nolint:staticcheck
"title" : "Expect error" ,
} ,
} )
require . NoError ( t , err )
assert . Equal ( t , http . StatusNotFound , resp . StatusCode )
err = resp . Body . Close ( )
require . NoError ( t , err )
} )
t . Run ( "When saving a dashboard with same UID in org A and org B, should be okay" , func ( t * testing . T ) {
resp , err := postDashboard ( t , grafanaListedAddr , "admin-org2" , "admin" , map [ string ] interface { } {
"dashboard" : map [ string ] interface { } {
"uid" : savedDashInFolder . UID ,
"title" : "Saved dash in folder" ,
} ,
} )
require . NoError ( t , err )
assert . Equal ( t , http . StatusOK , resp . StatusCode )
err = resp . Body . Close ( )
require . NoError ( t , err )
} )
t . Run ( "When creating a dashboard in General folder with same name as dashboard in other folder" , func ( t * testing . T ) {
resp , err := postDashboard ( t , grafanaListedAddr , "admin" , "admin" , map [ string ] interface { } {
"dashboard" : map [ string ] interface { } {
"title" : "Saved dash in folder" ,
} ,
} )
require . NoError ( t , err )
assert . Equal ( t , http . StatusOK , resp . StatusCode )
err = resp . Body . Close ( )
require . NoError ( t , err )
} )
t . Run ( "When creating a dashboard in other folder with same name as dashboard in General folder" , func ( t * testing . T ) {
resp , err := postDashboard ( t , grafanaListedAddr , "admin" , "admin" , map [ string ] interface { } {
"dashboard" : map [ string ] interface { } {
"uid" : savedDashInFolder ,
"title" : "Dash with existing uid in other org" ,
} ,
} )
require . NoError ( t , err )
assert . Equal ( t , http . StatusOK , resp . StatusCode )
err = resp . Body . Close ( )
require . NoError ( t , err )
} )
t . Run ( "When creating a folder with same name as dashboard in other folder" , func ( t * testing . T ) {
f := createFolder ( t , grafanaListedAddr , "Saved dashboard in general folder" )
require . Equal ( t , f . Title , "Saved dashboard in general folder" )
} )
t . Run ( "When saving a dashboard without id and uid and unique title in folder" , func ( t * testing . T ) {
resp , err := postDashboard ( t , grafanaListedAddr , "admin" , "admin" , map [ string ] interface { } {
"dashboard" : map [ string ] interface { } {
"title" : "Unique" ,
} ,
} )
require . NoError ( t , err )
assert . Equal ( t , http . StatusOK , resp . StatusCode )
err = resp . Body . Close ( )
require . NoError ( t , err )
} )
t . Run ( "When saving a dashboard with id 0" , func ( t * testing . T ) {
resp , err := postDashboard ( t , grafanaListedAddr , "admin" , "admin" , map [ string ] interface { } {
"dashboard" : map [ string ] interface { } {
"id" : 0 ,
"title" : "Dash with zero id" ,
} ,
} )
require . NoError ( t , err )
assert . Equal ( t , http . StatusOK , resp . StatusCode )
err = resp . Body . Close ( )
require . NoError ( t , err )
} )
t . Run ( "When saving a dashboard in non-existing folder" , func ( t * testing . T ) {
resp , err := postDashboard ( t , grafanaListedAddr , "admin" , "admin" , map [ string ] interface { } {
"dashboard" : map [ string ] interface { } {
"title" : "no folder" ,
} ,
"folderUid" : "non-existing-folder" ,
} )
require . NoError ( t , err )
assert . Equal ( t , http . StatusBadRequest , resp . StatusCode )
err = resp . Body . Close ( )
require . NoError ( t , err )
} )
t . Run ( "When saving a dashboard with incorrect version but no overwrite" , func ( t * testing . T ) {
resp , err := postDashboard ( t , grafanaListedAddr , "admin" , "admin" , map [ string ] interface { } {
"dashboard" : map [ string ] interface { } {
"uid" : savedDashInFolder . UID ,
"version" : 1 ,
} ,
"folderUid" : savedDashInFolder . FolderUID ,
} )
require . NoError ( t , err )
assert . Equal ( t , http . StatusBadRequest , resp . StatusCode )
err = resp . Body . Close ( )
require . NoError ( t , err )
} )
t . Run ( "When saving a dashboard with current version and overwrite is true" , func ( t * testing . T ) {
resp , err := postDashboard ( t , grafanaListedAddr , "admin" , "admin" , map [ string ] interface { } {
"dashboard" : map [ string ] interface { } {
"uid" : savedDashInFolder . UID ,
"version" : savedDashInFolder . Version ,
"title" : "Saved dash in folder" ,
} ,
"folderUid" : savedDashInFolder . FolderUID ,
} )
require . NoError ( t , err )
assert . Equal ( t , http . StatusOK , resp . StatusCode )
err = resp . Body . Close ( )
require . NoError ( t , err )
} )
t . Run ( "When saving a dashboard with no version set and title set to a folder title" , func ( t * testing . T ) {
resp , err := postDashboard ( t , grafanaListedAddr , "admin" , "admin" , map [ string ] interface { } {
"dashboard" : map [ string ] interface { } {
"uid" : savedDashInFolder . UID ,
"title" : "Saved folder" ,
} ,
"folderUid" : savedDashInFolder . FolderUID ,
"overwrite" : true ,
} )
require . NoError ( t , err )
assert . Equal ( t , http . StatusOK , resp . StatusCode )
err = resp . Body . Close ( )
require . NoError ( t , err )
} )
t . Run ( "When updating uid with id" , func ( t * testing . T ) {
resp , err := postDashboard ( t , grafanaListedAddr , "admin" , "admin" , map [ string ] interface { } {
"dashboard" : map [ string ] interface { } {
"id" : savedDashInFolder . ID , // nolint:staticcheck
"uid" : "new-uid" ,
"title" : "Updated title" ,
} ,
"folderUid" : savedDashInFolder . FolderUID ,
"overwrite" : true ,
} )
require . NoError ( t , err )
assert . Equal ( t , http . StatusOK , resp . StatusCode )
err = resp . Body . Close ( )
require . NoError ( t , err )
} )
t . Run ( "When updating uid with a dashboard already using that uid" , func ( t * testing . T ) {
resp , err := postDashboard ( t , grafanaListedAddr , "admin" , "admin" , map [ string ] interface { } {
"dashboard" : map [ string ] interface { } {
"id" : savedDashInFolder . ID , // nolint:staticcheck
"uid" : savedDashInGeneralFolder . UID ,
"title" : "Updated title" ,
} ,
"folderUid" : savedDashInFolder . FolderUID ,
"overwrite" : true ,
} )
require . NoError ( t , err )
assert . Equal ( t , http . StatusBadRequest , resp . StatusCode )
err = resp . Body . Close ( )
require . NoError ( t , err )
} )
t . Run ( "When trying to update to a folder" , func ( t * testing . T ) {
resp , err := postDashboard ( t , grafanaListedAddr , "admin" , "admin" , map [ string ] interface { } {
"dashboard" : map [ string ] interface { } {
"id" : savedDashInFolder . ID , // nolint:staticcheck
"uid" : savedDashInFolder . UID ,
"title" : "Updated title" ,
} ,
"isFolder" : true ,
"folderUid" : savedDashInFolder . FolderUID ,
"overwrite" : true ,
} )
require . NoError ( t , err )
require . NoError ( t , err )
assert . Equal ( t , http . StatusBadRequest , resp . StatusCode )
err = resp . Body . Close ( )
require . NoError ( t , err )
} )
2025-03-31 21:34:39 +08:00
}
2022-12-09 15:11:56 +08:00
2025-07-29 15:52:27 +08:00
func TestIntegrationDashboardQuota ( t * testing . T ) {
2021-11-16 17:26:46 +08:00
// enable quota and set low dashboard quota
// Setup Grafana and its Database
dashboardQuota := int64 ( 1 )
dir , path := testinfra . CreateGrafDir ( t , testinfra . GrafanaOpts {
2025-07-30 05:52:57 +08:00
DisableAnonymous : true ,
EnableQuota : true ,
DashboardOrgQuota : & dashboardQuota ,
2021-11-16 17:26:46 +08:00
} )
2022-01-20 18:10:12 +08:00
2025-05-28 18:25:01 +08:00
grafanaListedAddr , _ := testinfra . StartGrafanaEnv ( t , dir , path )
2021-11-16 17:26:46 +08:00
t . Run ( "when quota limit doesn't exceed, importing a dashboard should succeed" , func ( t * testing . T ) {
// Import dashboard
dashboardDataOne , err := simplejson . NewJson ( [ ] byte ( ` { "title":"just testing"} ` ) )
require . NoError ( t , err )
buf1 := & bytes . Buffer { }
2022-01-28 17:28:33 +08:00
err = json . NewEncoder ( buf1 ) . Encode ( dashboardimport . ImportDashboardRequest {
2021-11-16 17:26:46 +08:00
Dashboard : dashboardDataOne ,
} )
require . NoError ( t , err )
u := fmt . Sprintf ( "http://admin:admin@%s/api/dashboards/import" , grafanaListedAddr )
// nolint:gosec
resp , err := http . Post ( u , "application/json" , buf1 )
require . NoError ( t , err )
assert . Equal ( t , http . StatusOK , resp . StatusCode )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-11-16 17:26:46 +08:00
require . NoError ( t , err )
2022-03-11 01:38:04 +08:00
dashboardDTO := & plugindashboards . PluginDashboard { }
2021-11-16 17:26:46 +08:00
err = json . Unmarshal ( b , dashboardDTO )
require . NoError ( t , err )
require . EqualValues ( t , 1 , dashboardDTO . DashboardId )
} )
t . Run ( "when quota limit exceeds importing a dashboard should fail" , func ( t * testing . T ) {
dashboardDataOne , err := simplejson . NewJson ( [ ] byte ( ` { "title":"just testing"} ` ) )
require . NoError ( t , err )
buf1 := & bytes . Buffer { }
2022-01-28 17:28:33 +08:00
err = json . NewEncoder ( buf1 ) . Encode ( dashboardimport . ImportDashboardRequest {
2021-11-16 17:26:46 +08:00
Dashboard : dashboardDataOne ,
} )
require . NoError ( t , err )
u := fmt . Sprintf ( "http://admin:admin@%s/api/dashboards/import" , grafanaListedAddr )
// nolint:gosec
resp , err := http . Post ( u , "application/json" , buf1 )
require . NoError ( t , err )
assert . Equal ( t , http . StatusForbidden , resp . StatusCode )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-11-16 17:26:46 +08:00
require . NoError ( t , err )
assert . Equal ( t , http . StatusForbidden , resp . StatusCode )
require . JSONEq ( t , ` { "message":"Quota reached"} ` , string ( b ) )
} )
}
2022-12-09 15:11:56 +08:00
func TestIntegrationUpdatingProvisionionedDashboards ( t * testing . T ) {
2021-12-17 23:31:52 +08:00
// Setup Grafana and its Database
dir , path := testinfra . CreateGrafDir ( t , testinfra . GrafanaOpts {
2025-07-30 05:52:57 +08:00
DisableAnonymous : true ,
2021-12-17 23:31:52 +08:00
} )
provDashboardsDir := filepath . Join ( dir , "conf" , "provisioning" , "dashboards" )
provDashboardsCfg := filepath . Join ( provDashboardsDir , "dev.yaml" )
blob := [ ] byte ( fmt . Sprintf ( `
apiVersion : 1
providers :
- name : ' provisioned dashboards '
type : file
allowUiUpdates : false
options :
path : % s ` , provDashboardsDir ) )
err := os . WriteFile ( provDashboardsCfg , blob , 0644 )
require . NoError ( t , err )
2022-08-10 21:37:51 +08:00
input , err := os . ReadFile ( filepath . Join ( "./home.json" ) )
2021-12-17 23:31:52 +08:00
require . NoError ( t , err )
provDashboardFile := filepath . Join ( provDashboardsDir , "home.json" )
2022-08-10 21:37:51 +08:00
err = os . WriteFile ( provDashboardFile , input , 0644 )
2021-12-17 23:31:52 +08:00
require . NoError ( t , err )
2025-05-28 18:25:01 +08:00
grafanaListedAddr , _ := testinfra . StartGrafanaEnv ( t , dir , path )
2021-12-17 23:31:52 +08:00
2024-04-08 23:42:12 +08:00
// give provisioner some time since we don't have a way to know when provisioning is complete
// TODO https://github.com/grafana/grafana/issues/85617
time . Sleep ( 1 * time . Second )
2021-12-17 23:31:52 +08:00
type errorResponseBody struct {
Message string ` json:"message" `
}
t . Run ( "when provisioned directory is not empty, dashboard should be created" , func ( t * testing . T ) {
title := "Grafana Dev Overview & Home"
2023-01-31 18:04:55 +08:00
dashboardList := & model . HitList { }
2024-04-08 15:47:34 +08:00
2025-04-07 10:44:28 +08:00
require . EventuallyWithT ( t , func ( collect * assert . CollectT ) {
2024-04-08 15:47:34 +08:00
u := fmt . Sprintf ( "http://admin:admin@%s/api/search?query=%s" , grafanaListedAddr , url . QueryEscape ( title ) )
// nolint:gosec
resp , err := http . Get ( u )
require . NoError ( t , err )
assert . Equal ( t , http . StatusOK , resp . StatusCode )
2025-04-07 10:44:28 +08:00
2024-04-08 15:47:34 +08:00
b , err := io . ReadAll ( resp . Body )
require . NoError ( t , err )
2025-04-07 10:44:28 +08:00
err = resp . Body . Close ( )
require . NoError ( t , err )
2024-04-08 15:47:34 +08:00
err = json . Unmarshal ( b , dashboardList )
require . NoError ( t , err )
2025-04-07 10:44:28 +08:00
assert . Greater ( collect , dashboardList . Len ( ) , 0 , "Dashboard should be ready" )
} , 10 * time . Second , 25 * time . Millisecond )
2024-04-08 15:47:34 +08:00
2021-12-17 23:31:52 +08:00
var dashboardUID string
var dashboardID int64
for _ , d := range * dashboardList {
dashboardUID = d . UID
2025-07-29 15:52:27 +08:00
dashboardID = d . ID // nolint:staticcheck
2021-12-17 23:31:52 +08:00
}
assert . Equal ( t , int64 ( 1 ) , dashboardID )
testCases := [ ] struct {
desc string
dashboardData string
2022-01-12 00:39:53 +08:00
expStatus int
expErrReason string
2021-12-17 23:31:52 +08:00
} {
{
desc : "when updating provisioned dashboard using ID it should fail" ,
dashboardData : fmt . Sprintf ( ` { "title":"just testing", "id": %d, "version": 1} ` , dashboardID ) ,
2022-01-12 00:39:53 +08:00
expStatus : http . StatusBadRequest ,
2022-06-30 21:31:54 +08:00
expErrReason : dashboards . ErrDashboardCannotSaveProvisionedDashboard . Reason ,
2021-12-17 23:31:52 +08:00
} ,
{
2022-01-12 00:39:53 +08:00
desc : "when updating provisioned dashboard using UID it should fail" ,
2021-12-17 23:31:52 +08:00
dashboardData : fmt . Sprintf ( ` { "title":"just testing", "uid": %q, "version": 1} ` , dashboardUID ) ,
2022-01-12 00:39:53 +08:00
expStatus : http . StatusBadRequest ,
2022-06-30 21:31:54 +08:00
expErrReason : dashboards . ErrDashboardCannotSaveProvisionedDashboard . Reason ,
2022-01-12 00:39:53 +08:00
} ,
{
desc : "when updating dashboard using unknown ID, it should fail" ,
dashboardData : ` { "title":"just testing", "id": 42, "version": 1} ` ,
expStatus : http . StatusNotFound ,
2022-06-30 21:31:54 +08:00
expErrReason : dashboards . ErrDashboardNotFound . Reason ,
2022-01-12 00:39:53 +08:00
} ,
{
desc : "when updating dashboard using unknown UID, it should succeed" ,
dashboardData : ` { "title":"just testing", "uid": "unknown", "version": 1} ` ,
expStatus : http . StatusOK ,
2021-12-17 23:31:52 +08:00
} ,
}
for _ , tc := range testCases {
t . Run ( tc . desc , func ( t * testing . T ) {
u := fmt . Sprintf ( "http://admin:admin@%s/api/dashboards/db" , grafanaListedAddr )
// nolint:gosec
dashboardData , err := simplejson . NewJson ( [ ] byte ( tc . dashboardData ) )
require . NoError ( t , err )
buf := & bytes . Buffer { }
2023-01-18 20:52:41 +08:00
err = json . NewEncoder ( buf ) . Encode ( dashboards . SaveDashboardCommand {
2021-12-17 23:31:52 +08:00
Dashboard : dashboardData ,
} )
require . NoError ( t , err )
// nolint:gosec
resp , err := http . Post ( u , "application/json" , buf )
require . NoError ( t , err )
2022-01-12 00:39:53 +08:00
assert . Equal ( t , tc . expStatus , resp . StatusCode )
2021-12-17 23:31:52 +08:00
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-01-12 00:39:53 +08:00
if tc . expErrReason == "" {
return
}
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-12-17 23:31:52 +08:00
require . NoError ( t , err )
dashboardErr := & errorResponseBody { }
err = json . Unmarshal ( b , dashboardErr )
require . NoError ( t , err )
2022-01-12 00:39:53 +08:00
assert . Equal ( t , tc . expErrReason , dashboardErr . Message )
2021-12-17 23:31:52 +08:00
} )
}
t . Run ( "deleting provisioned dashboard should fail" , func ( t * testing . T ) {
u := fmt . Sprintf ( "http://admin:admin@%s/api/dashboards/uid/%s" , grafanaListedAddr , dashboardUID )
req , err := http . NewRequest ( "DELETE" , u , nil )
if err != nil {
fmt . Println ( err )
return
}
client := & http . Client { }
resp , err := client . Do ( req )
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
assert . Equal ( t , http . StatusBadRequest , resp . StatusCode )
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-12-17 23:31:52 +08:00
require . NoError ( t , err )
dashboardErr := & errorResponseBody { }
err = json . Unmarshal ( b , dashboardErr )
require . NoError ( t , err )
2022-06-30 21:31:54 +08:00
assert . Equal ( t , dashboards . ErrDashboardCannotDeleteProvisionedDashboard . Reason , dashboardErr . Message )
2021-12-17 23:31:52 +08:00
} )
} )
}
2023-11-01 23:01:54 +08:00
func TestIntegrationCreate ( t * testing . T ) {
// Setup Grafana and its Database
dir , path := testinfra . CreateGrafDir ( t , testinfra . GrafanaOpts {
2025-07-30 05:52:57 +08:00
DisableAnonymous : true ,
2023-11-01 23:01:54 +08:00
} )
2025-05-28 18:25:01 +08:00
grafanaListedAddr , _ := testinfra . StartGrafanaEnv ( t , dir , path )
2023-11-01 23:01:54 +08:00
t . Run ( "create dashboard should succeed" , func ( t * testing . T ) {
dashboardDataOne , err := simplejson . NewJson ( [ ] byte ( ` { "title":"just testing"} ` ) )
require . NoError ( t , err )
buf1 := & bytes . Buffer { }
err = json . NewEncoder ( buf1 ) . Encode ( dashboards . SaveDashboardCommand {
Dashboard : dashboardDataOne ,
} )
require . NoError ( t , err )
u := fmt . Sprintf ( "http://admin:admin@%s/api/dashboards/db" , grafanaListedAddr )
// nolint:gosec
resp , err := http . Post ( u , "application/json" , buf1 )
require . NoError ( t , err )
assert . Equal ( t , http . StatusOK , resp . StatusCode )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
b , err := io . ReadAll ( resp . Body )
require . NoError ( t , err )
var m util . DynMap
err = json . Unmarshal ( b , & m )
require . NoError ( t , err )
assert . NotEmpty ( t , m [ "id" ] )
assert . NotEmpty ( t , m [ "uid" ] )
} )
t . Run ( "create dashboard under folder should succeed" , func ( t * testing . T ) {
folder := createFolder ( t , grafanaListedAddr , "test folder" )
dashboardDataOne , err := simplejson . NewJson ( [ ] byte ( ` { "title":"just testing"} ` ) )
require . NoError ( t , err )
buf1 := & bytes . Buffer { }
err = json . NewEncoder ( buf1 ) . Encode ( dashboards . SaveDashboardCommand {
Dashboard : dashboardDataOne ,
2023-12-07 20:56:04 +08:00
OrgID : 0 ,
2023-12-07 20:15:58 +08:00
FolderUID : folder . UID ,
2023-11-01 23:01:54 +08:00
} )
require . NoError ( t , err )
u := fmt . Sprintf ( "http://admin:admin@%s/api/dashboards/db" , grafanaListedAddr )
// nolint:gosec
resp , err := http . Post ( u , "application/json" , buf1 )
require . NoError ( t , err )
require . Equal ( t , http . StatusOK , resp . StatusCode )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
b , err := io . ReadAll ( resp . Body )
require . NoError ( t , err )
var m util . DynMap
err = json . Unmarshal ( b , & m )
require . NoError ( t , err )
assert . NotEmpty ( t , m [ "id" ] )
assert . NotEmpty ( t , m [ "uid" ] )
2023-12-07 20:15:58 +08:00
assert . Equal ( t , folder . UID , m [ "folderUid" ] )
2023-11-01 23:01:54 +08:00
} )
t . Run ( "create dashboard under folder (using deprecated folder sequential ID) should succeed" , func ( t * testing . T ) {
folder := createFolder ( t , grafanaListedAddr , "test folder 2" )
dashboardDataOne , err := simplejson . NewJson ( [ ] byte ( ` { "title":"just testing"} ` ) )
require . NoError ( t , err )
buf1 := & bytes . Buffer { }
err = json . NewEncoder ( buf1 ) . Encode ( dashboards . SaveDashboardCommand {
Dashboard : dashboardDataOne ,
2023-12-07 20:56:04 +08:00
OrgID : 0 ,
FolderUID : folder . UID ,
2023-11-01 23:01:54 +08:00
} )
require . NoError ( t , err )
u := fmt . Sprintf ( "http://admin:admin@%s/api/dashboards/db" , grafanaListedAddr )
// nolint:gosec
resp , err := http . Post ( u , "application/json" , buf1 )
require . NoError ( t , err )
require . Equal ( t , http . StatusOK , resp . StatusCode )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
b , err := io . ReadAll ( resp . Body )
require . NoError ( t , err )
var m util . DynMap
err = json . Unmarshal ( b , & m )
require . NoError ( t , err )
assert . NotEmpty ( t , m [ "id" ] )
assert . NotEmpty ( t , m [ "uid" ] )
2023-12-07 20:15:58 +08:00
assert . Equal ( t , folder . UID , m [ "folderUid" ] )
2023-11-01 23:01:54 +08:00
} )
t . Run ( "create dashboard under unknow folder should fail" , func ( t * testing . T ) {
folderUID := "unknown"
// Import dashboard
dashboardDataOne , err := simplejson . NewJson ( [ ] byte ( ` { "title":"just testing"} ` ) )
require . NoError ( t , err )
buf1 := & bytes . Buffer { }
err = json . NewEncoder ( buf1 ) . Encode ( dashboards . SaveDashboardCommand {
Dashboard : dashboardDataOne ,
FolderUID : folderUID ,
} )
require . NoError ( t , err )
u := fmt . Sprintf ( "http://admin:admin@%s/api/dashboards/db" , grafanaListedAddr )
// nolint:gosec
resp , err := http . Post ( u , "application/json" , buf1 )
require . NoError ( t , err )
assert . Equal ( t , http . StatusBadRequest , resp . StatusCode )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
b , err := io . ReadAll ( resp . Body )
require . NoError ( t , err )
var m util . DynMap
err = json . Unmarshal ( b , & m )
require . NoError ( t , err )
2023-11-16 19:11:35 +08:00
assert . Equal ( t , dashboards . ErrFolderNotFound . Error ( ) , m [ "message" ] )
2023-11-01 23:01:54 +08:00
} )
}
func createFolder ( t * testing . T , grafanaListedAddr string , title string ) * dtos . Folder {
t . Helper ( )
buf1 := & bytes . Buffer { }
err := json . NewEncoder ( buf1 ) . Encode ( folder . CreateFolderCommand {
Title : title ,
} )
require . NoError ( t , err )
u := fmt . Sprintf ( "http://admin:admin@%s/api/folders" , grafanaListedAddr )
// nolint:gosec
resp , err := http . Post ( u , "application/json" , buf1 )
require . NoError ( t , err )
assert . Equal ( t , http . StatusOK , resp . StatusCode )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
b , err := io . ReadAll ( resp . Body )
require . NoError ( t , err )
require . Equal ( t , http . StatusOK , resp . StatusCode )
var f * dtos . Folder
err = json . Unmarshal ( b , & f )
require . NoError ( t , err )
return f
}
2025-04-21 23:51:28 +08:00
func intPtr ( n int ) * int {
return & n
}
2025-07-29 15:52:27 +08:00
func TestIntegrationPreserveSchemaVersion ( t * testing . T ) {
2025-04-21 23:51:28 +08:00
dir , path := testinfra . CreateGrafDir ( t , testinfra . GrafanaOpts {
2025-07-30 05:52:57 +08:00
DisableAnonymous : true ,
2025-04-21 23:51:28 +08:00
} )
2025-05-28 18:25:01 +08:00
grafanaListedAddr , _ := testinfra . StartGrafanaEnv ( t , dir , path )
2025-04-21 23:51:28 +08:00
schemaVersions := [ ] * int { intPtr ( 1 ) , intPtr ( 36 ) , intPtr ( 40 ) , nil }
for _ , schemaVersion := range schemaVersions {
var title string
if schemaVersion == nil {
title = "save dashboard with no schemaVersion"
} else {
title = fmt . Sprintf ( "save dashboard with schemaVersion %d" , * schemaVersion )
}
t . Run ( title , func ( t * testing . T ) {
// Create dashboard JSON with specified schema version
var dashboardJSON string
if schemaVersion != nil {
dashboardJSON = fmt . Sprintf ( ` { "title":"Schema Version Test", "schemaVersion": %d} ` , * schemaVersion )
} else {
dashboardJSON = ` { "title":"Schema Version Test"} `
}
dashboardData , err := simplejson . NewJson ( [ ] byte ( dashboardJSON ) )
require . NoError ( t , err )
// Save the dashboard via API
buf := & bytes . Buffer { }
err = json . NewEncoder ( buf ) . Encode ( dashboards . SaveDashboardCommand {
Dashboard : dashboardData ,
} )
require . NoError ( t , err )
url := fmt . Sprintf ( "http://admin:admin@%s/api/dashboards/db" , grafanaListedAddr )
// nolint:gosec
resp , err := http . Post ( url , "application/json" , buf )
require . NoError ( t , err )
require . Equal ( t , http . StatusOK , resp . StatusCode )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
// Get dashboard UID from response
b , err := io . ReadAll ( resp . Body )
require . NoError ( t , err )
var saveResp struct {
UID string ` json:"uid" `
}
err = json . Unmarshal ( b , & saveResp )
require . NoError ( t , err )
require . NotEmpty ( t , saveResp . UID )
getDashURL := fmt . Sprintf ( "http://admin:admin@%s/api/dashboards/uid/%s" , grafanaListedAddr , saveResp . UID )
// nolint:gosec
getResp , err := http . Get ( getDashURL )
require . NoError ( t , err )
require . Equal ( t , http . StatusOK , getResp . StatusCode )
t . Cleanup ( func ( ) {
err := getResp . Body . Close ( )
require . NoError ( t , err )
} )
// Parse response and check if schema version is preserved
dashBody , err := io . ReadAll ( getResp . Body )
require . NoError ( t , err )
var dashResp struct {
Dashboard * simplejson . Json ` json:"dashboard" `
}
err = json . Unmarshal ( dashBody , & dashResp )
require . NoError ( t , err )
actualSchemaVersion := dashResp . Dashboard . Get ( "schemaVersion" )
if schemaVersion != nil {
// Check if schemaVersion is preserved (not migrated to latest)
actualVersion := actualSchemaVersion . MustInt ( )
require . Equal ( t , * schemaVersion , actualVersion ,
"Dashboard schemaVersion should not be automatically changed when saved through /api/dashboards/db" )
} else {
actualVersion , err := actualSchemaVersion . Int ( )
s , _ := dashResp . Dashboard . EncodePretty ( )
require . Error ( t , err , fmt . Sprintf ( "Dashboard schemaVersion should not be automatically populated when saved through /api/dashboards/db, was %d. %s" , actualVersion , string ( s ) ) )
}
} )
}
}
2025-06-26 19:23:05 +08:00
func TestIntegrationImportDashboardWithLibraryPanels ( t * testing . T ) {
dir , path := testinfra . CreateGrafDir ( t , testinfra . GrafanaOpts {
2025-07-30 05:52:57 +08:00
DisableAnonymous : true ,
2025-06-26 19:23:05 +08:00
} )
grafanaListedAddr , _ := testinfra . StartGrafanaEnv ( t , dir , path )
t . Run ( "import dashboard with library panels should create library panels and connections" , func ( t * testing . T ) {
dashboardJSON := ` {
"title" : "Test Dashboard with Library Panels" ,
"panels" : [
{
"id" : 1 ,
"title" : "Library Panel 1" ,
"type" : "text" ,
"gridPos" : { "h" : 8 , "w" : 12 , "x" : 0 , "y" : 0 } ,
"libraryPanel" : {
"uid" : "test-lib-panel-1" ,
"name" : "Test Library Panel 1"
}
} ,
{
"id" : 2 ,
"title" : "Library Panel 2" ,
"type" : "stat" ,
"gridPos" : { "h" : 8 , "w" : 12 , "x" : 12 , "y" : 0 } ,
"libraryPanel" : {
"uid" : "test-lib-panel-2" ,
"name" : "Test Library Panel 2"
}
}
] ,
"__elements" : {
"test-lib-panel-1" : {
"uid" : "test-lib-panel-1" ,
"name" : "Test Library Panel 1" ,
"kind" : 1 ,
"type" : "text" ,
"model" : {
"title" : "Test Library Panel 1" ,
"type" : "text" ,
"options" : {
"content" : "This is a test library panel"
}
}
} ,
"test-lib-panel-2" : {
"uid" : "test-lib-panel-2" ,
"name" : "Test Library Panel 2" ,
"kind" : 1 ,
"type" : "stat" ,
"model" : {
"title" : "Test Library Panel 2" ,
"type" : "stat" ,
"options" : {
"colorMode" : "value" ,
"graphMode" : "area" ,
"justifyMode" : "auto" ,
"orientation" : "auto" ,
"reduceOptions" : {
"calcs" : [ "lastNotNull" ] ,
"fields" : "" ,
"values" : false
} ,
"textMode" : "auto"
} ,
"targets" : [
{
"refId" : "A" ,
"scenarioId" : "csv_metric_values" ,
"stringInput" : "1,20,90,30,5,0"
}
]
}
}
}
} `
data , err := simplejson . NewJson ( [ ] byte ( dashboardJSON ) )
require . NoError ( t , err )
buf := & bytes . Buffer { }
err = json . NewEncoder ( buf ) . Encode ( dashboardimport . ImportDashboardRequest {
Dashboard : data ,
} )
require . NoError ( t , err )
u := fmt . Sprintf ( "http://admin:admin@%s/api/dashboards/import" , grafanaListedAddr )
// nolint:gosec
resp , err := http . Post ( u , "application/json" , buf )
require . NoError ( t , err )
assert . Equal ( t , http . StatusOK , resp . StatusCode )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
b , err := io . ReadAll ( resp . Body )
require . NoError ( t , err )
var importResp struct {
UID string ` json:"uid" `
}
err = json . Unmarshal ( b , & importResp )
require . NoError ( t , err )
require . NotEmpty ( t , importResp . UID )
t . Run ( "library panels should be created" , func ( t * testing . T ) {
url := fmt . Sprintf ( "http://admin:admin@%s/api/library-elements/test-lib-panel-1" , grafanaListedAddr )
// nolint:gosec
resp , err := http . Get ( url )
require . NoError ( t , err )
assert . Equal ( t , http . StatusOK , resp . StatusCode )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
panel , err := io . ReadAll ( resp . Body )
require . NoError ( t , err )
var panelRes struct {
Result struct {
UID string ` json:"uid" `
Name string ` json:"name" `
Type string ` json:"type" `
} ` json:"result" `
}
err = json . Unmarshal ( panel , & panelRes )
require . NoError ( t , err )
assert . Equal ( t , "test-lib-panel-1" , panelRes . Result . UID )
assert . Equal ( t , "Test Library Panel 1" , panelRes . Result . Name )
assert . Equal ( t , "text" , panelRes . Result . Type )
url = fmt . Sprintf ( "http://admin:admin@%s/api/library-elements/test-lib-panel-2" , grafanaListedAddr )
// nolint:gosec
resp , err = http . Get ( url )
require . NoError ( t , err )
assert . Equal ( t , http . StatusOK , resp . StatusCode )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
panel , err = io . ReadAll ( resp . Body )
require . NoError ( t , err )
err = json . Unmarshal ( panel , & panelRes )
require . NoError ( t , err )
assert . Equal ( t , "test-lib-panel-2" , panelRes . Result . UID )
assert . Equal ( t , "Test Library Panel 2" , panelRes . Result . Name )
assert . Equal ( t , "stat" , panelRes . Result . Type )
} )
t . Run ( "library panels should be connected to dashboard" , func ( t * testing . T ) {
url := fmt . Sprintf ( "http://admin:admin@%s/api/library-elements/test-lib-panel-1/connections" , grafanaListedAddr )
// nolint:gosec
connectionsResp , err := http . Get ( url )
require . NoError ( t , err )
assert . Equal ( t , http . StatusOK , connectionsResp . StatusCode )
t . Cleanup ( func ( ) {
err := connectionsResp . Body . Close ( )
require . NoError ( t , err )
} )
connections , err := io . ReadAll ( connectionsResp . Body )
require . NoError ( t , err )
var connectionsRes struct {
Result [ ] struct {
ConnectionUID string ` json:"connectionUid" `
} ` json:"result" `
}
err = json . Unmarshal ( connections , & connectionsRes )
require . NoError ( t , err )
assert . Len ( t , connectionsRes . Result , 1 )
assert . Equal ( t , importResp . UID , connectionsRes . Result [ 0 ] . ConnectionUID )
url = fmt . Sprintf ( "http://admin:admin@%s/api/library-elements/test-lib-panel-2/connections" , grafanaListedAddr )
// nolint:gosec
connectionsResp , err = http . Get ( url )
require . NoError ( t , err )
assert . Equal ( t , http . StatusOK , connectionsResp . StatusCode )
t . Cleanup ( func ( ) {
err := connectionsResp . Body . Close ( )
require . NoError ( t , err )
} )
connections , err = io . ReadAll ( connectionsResp . Body )
require . NoError ( t , err )
err = json . Unmarshal ( connections , & connectionsRes )
require . NoError ( t , err )
assert . Len ( t , connectionsRes . Result , 1 )
assert . Equal ( t , importResp . UID , connectionsRes . Result [ 0 ] . ConnectionUID )
} )
} )
}
2025-07-29 15:52:27 +08:00
func createDashboard ( t * testing . T , grafanaListedAddr string , title string , folderID int64 , folderUID string ) * dashboards . Dashboard {
t . Helper ( )
buf := & bytes . Buffer { }
err := json . NewEncoder ( buf ) . Encode ( map [ string ] interface { } {
"dashboard" : map [ string ] interface { } {
"title" : title ,
} ,
"folderId" : folderID ,
"folderUid" : folderUID ,
"overwrite" : true ,
} )
require . NoError ( t , err )
u := fmt . Sprintf ( "http://admin:admin@%s/api/dashboards/db" , grafanaListedAddr )
// nolint:gosec
resp , err := http . Post ( u , "application/json" , buf )
require . NoError ( t , err )
require . Equal ( t , http . StatusOK , resp . StatusCode )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
b , err := io . ReadAll ( resp . Body )
require . NoError ( t , err )
var saveResp struct {
Status string ` json:"status" `
Slug string ` json:"slug" `
Version int64 ` json:"version" `
ID int64 ` json:"id" `
UID string ` json:"uid" `
URL string ` json:"url" `
FolderUID string ` json:"folderUid" `
}
err = json . Unmarshal ( b , & saveResp )
require . NoError ( t , err )
require . NotEmpty ( t , saveResp . UID )
return & dashboards . Dashboard {
ID : saveResp . ID , // nolint:staticcheck
UID : saveResp . UID ,
Slug : saveResp . Slug ,
Version : int ( saveResp . Version ) ,
FolderUID : saveResp . FolderUID ,
}
}
func postDashboard ( t * testing . T , grafanaListedAddr , user , password string , payload map [ string ] interface { } ) ( * http . Response , error ) {
t . Helper ( )
payloadBytes , err := json . Marshal ( payload )
require . NoError ( t , err )
u := fmt . Sprintf ( "http://%s:%s@%s/api/dashboards/db" , user , password , grafanaListedAddr )
return http . Post ( u , "application/json" , bytes . NewBuffer ( payloadBytes ) ) // nolint:gosec
}
func TestIntegrationDashboardServicePermissions ( t * testing . T ) {
if testing . Short ( ) {
t . Skip ( "skipping integration test" )
}
dir , path := testinfra . CreateGrafDir ( t , testinfra . GrafanaOpts {
2025-07-30 05:52:57 +08:00
DisableAnonymous : true ,
2025-07-29 15:52:27 +08:00
} )
grafanaListedAddr , env := testinfra . StartGrafanaEnv ( t , dir , path )
tests . CreateUser ( t , env . SQLStore , env . Cfg , user . CreateUserCommand {
DefaultOrgRole : string ( org . RoleEditor ) ,
Login : "editor" ,
Password : "editor" ,
IsAdmin : false ,
} )
tests . CreateUser ( t , env . SQLStore , env . Cfg , user . CreateUserCommand {
DefaultOrgRole : string ( org . RoleViewer ) ,
Login : "viewer" ,
Password : "viewer" ,
IsAdmin : false ,
} )
savedFolder := createFolder ( t , grafanaListedAddr , "Saved folder" )
otherSavedFolder := createFolder ( t , grafanaListedAddr , "Other saved folder" )
savedDashInFolder := createDashboard ( t , grafanaListedAddr , "Saved dash in folder" , savedFolder . ID , savedFolder . UID ) // nolint:staticcheck
savedDashInGeneralFolder := createDashboard ( t , grafanaListedAddr , "Saved dashboard in general folder" , 0 , "" )
t . Run ( "When creating a new dashboard in the General folder, requires create permissions scoped to the general folder" , func ( t * testing . T ) {
dashboardPayload := map [ string ] interface { } {
"dashboard" : map [ string ] interface { } {
"title" : "Dash" ,
} ,
"overwrite" : true ,
}
payloadBytes , err := json . Marshal ( dashboardPayload )
require . NoError ( t , err )
u := fmt . Sprintf ( "http://viewer:viewer@%s/api/dashboards/db" , grafanaListedAddr )
resp , err := http . Post ( u , "application/json" , bytes . NewBuffer ( payloadBytes ) ) // nolint:gosec
require . NoError ( t , err )
assert . Equal ( t , http . StatusForbidden , resp . StatusCode )
err = resp . Body . Close ( )
require . NoError ( t , err )
u = fmt . Sprintf ( "http://editor:editor@%s/api/dashboards/db" , grafanaListedAddr )
resp , err = http . Post ( u , "application/json" , bytes . NewBuffer ( payloadBytes ) ) // nolint:gosec
require . NoError ( t , err )
assert . Equal ( t , http . StatusOK , resp . StatusCode )
err = resp . Body . Close ( )
require . NoError ( t , err )
} )
t . Run ( "When creating a new dashboard in other folder, requires create permissions scoped to the other folder" , func ( t * testing . T ) {
dashboardPayload := map [ string ] interface { } {
"dashboard" : map [ string ] interface { } {
"title" : "Dash" ,
} ,
"folderUid" : otherSavedFolder . UID ,
"overwrite" : true ,
}
resp , err := postDashboard ( t , grafanaListedAddr , "viewer" , "viewer" , dashboardPayload )
require . NoError ( t , err )
assert . Equal ( t , http . StatusForbidden , resp . StatusCode )
err = resp . Body . Close ( )
require . NoError ( t , err )
resp , err = postDashboard ( t , grafanaListedAddr , "editor" , "editor" , dashboardPayload )
require . NoError ( t , err )
assert . Equal ( t , http . StatusOK , resp . StatusCode )
err = resp . Body . Close ( )
require . NoError ( t , err )
} )
t . Run ( "When creating a new dashboard by existing UID in folder, requires write permissions on the existing dashboard" , func ( t * testing . T ) {
dashboardPayload := map [ string ] interface { } {
"dashboard" : map [ string ] interface { } {
"uid" : savedDashInFolder . UID ,
"title" : "New dash" ,
} ,
"folderUid" : savedFolder . UID ,
"overwrite" : true ,
}
resp , err := postDashboard ( t , grafanaListedAddr , "viewer" , "viewer" , dashboardPayload )
require . NoError ( t , err )
assert . Equal ( t , http . StatusForbidden , resp . StatusCode )
err = resp . Body . Close ( )
require . NoError ( t , err )
resp , err = postDashboard ( t , grafanaListedAddr , "editor" , "editor" , dashboardPayload )
require . NoError ( t , err )
assert . Equal ( t , http . StatusOK , resp . StatusCode )
err = resp . Body . Close ( )
require . NoError ( t , err )
} )
t . Run ( "When moving a dashboard by existing uid to other folder from General folder, requires dashboard creation permissions on the destination folder and write access to the dashboard" , func ( t * testing . T ) {
dashboardPayload := map [ string ] interface { } {
"dashboard" : map [ string ] interface { } {
"uid" : savedDashInGeneralFolder . UID ,
"title" : "Dash" ,
} ,
"folderUid" : otherSavedFolder . UID ,
"overwrite" : true ,
}
resp , err := postDashboard ( t , grafanaListedAddr , "viewer" , "viewer" , dashboardPayload )
require . NoError ( t , err )
assert . Equal ( t , http . StatusForbidden , resp . StatusCode )
err = resp . Body . Close ( )
require . NoError ( t , err )
resp , err = postDashboard ( t , grafanaListedAddr , "editor" , "editor" , dashboardPayload )
require . NoError ( t , err )
assert . Equal ( t , http . StatusOK , resp . StatusCode )
err = resp . Body . Close ( )
require . NoError ( t , err )
} )
t . Run ( "When moving a dashboard by existing uid to the General folder from other folder, requires dashboard creation permissions on the general folder and write access to the dashboard" , func ( t * testing . T ) {
dashboardPayload := map [ string ] interface { } {
"dashboard" : map [ string ] interface { } {
"uid" : savedDashInFolder . UID ,
"title" : "Dash" ,
} ,
"folderUid" : "" ,
"overwrite" : true ,
}
resp , err := postDashboard ( t , grafanaListedAddr , "viewer" , "viewer" , dashboardPayload )
require . NoError ( t , err )
assert . Equal ( t , http . StatusForbidden , resp . StatusCode )
err = resp . Body . Close ( )
require . NoError ( t , err )
resp , err = postDashboard ( t , grafanaListedAddr , "editor" , "editor" , dashboardPayload )
require . NoError ( t , err )
assert . Equal ( t , http . StatusOK , resp . StatusCode )
err = resp . Body . Close ( )
require . NoError ( t , err )
} )
t . Run ( "RBAC tests" , func ( t * testing . T ) {
setFolderPermissions := func ( t * testing . T , grafanaListedAddr string , folderUID string , permissions [ ] map [ string ] interface { } ) {
t . Helper ( )
permissionPayload := map [ string ] interface { } {
"items" : permissions ,
}
payloadBytes , err := json . Marshal ( permissionPayload )
require . NoError ( t , err )
u := fmt . Sprintf ( "http://admin:admin@%s/api/folders/%s/permissions" , grafanaListedAddr , folderUID )
resp , err := http . Post ( u , "application/json" , bytes . NewBuffer ( payloadBytes ) ) // nolint:gosec
require . NoError ( t , err )
assert . Equal ( t , http . StatusOK , resp . StatusCode )
err = resp . Body . Close ( )
require . NoError ( t , err )
}
searchDashboards := func ( t * testing . T , grafanaListedAddr string , userLogin , userPassword string ) [ ] map [ string ] interface { } {
t . Helper ( )
u := fmt . Sprintf ( "http://%s:%s@%s/api/search?type=dash-db" , userLogin , userPassword , grafanaListedAddr )
resp , err := http . Get ( u ) // nolint:gosec
require . NoError ( t , err )
assert . Equal ( t , http . StatusOK , resp . StatusCode )
defer resp . Body . Close ( ) // nolint:errcheck
var results [ ] map [ string ] interface { }
err = json . NewDecoder ( resp . Body ) . Decode ( & results )
require . NoError ( t , err )
return results
}
noneUserID := tests . CreateUser ( t , env . SQLStore , env . Cfg , user . CreateUserCommand {
DefaultOrgRole : string ( org . RoleNone ) ,
Login : "noneuser" ,
Password : "noneuser" ,
IsAdmin : false ,
} )
parentFolder := createFolder ( t , grafanaListedAddr , "parent" )
childFolder := createFolder ( t , grafanaListedAddr , "child" )
createDashboard ( t , grafanaListedAddr , "dashboard in root" , 0 , "" )
createDashboard ( t , grafanaListedAddr , "dashboard in parent" , parentFolder . ID , parentFolder . UID ) // nolint:staticcheck
createDashboard ( t , grafanaListedAddr , "dashboard in child" , childFolder . ID , childFolder . UID ) // nolint:staticcheck
viewPermissions := [ ] map [ string ] interface { } {
{
"permission" : 1 ,
"userId" : noneUserID ,
} ,
}
t . Run ( "it should not return folder if ACL is not set for parent folder" , func ( t * testing . T ) {
results := searchDashboards ( t , grafanaListedAddr , "noneuser" , "noneuser" )
assert . Empty ( t , results , "Should not return any dashboards when no permissions are set" )
} )
t . Run ( "it should return child folder when user has permission to read child folder" , func ( t * testing . T ) {
setFolderPermissions ( t , grafanaListedAddr , childFolder . UID , viewPermissions )
results := searchDashboards ( t , grafanaListedAddr , "noneuser" , "noneuser" )
foundTitles := make ( [ ] string , 0 )
for _ , result := range results {
if title , ok := result [ "title" ] . ( string ) ; ok {
foundTitles = append ( foundTitles , title )
}
}
assert . Contains ( t , foundTitles , "dashboard in child" , "Should return dashboard in child folder" )
} )
t . Run ( "it should return parent folder when user has permission to read parent folder but no permission to read child folder" , func ( t * testing . T ) {
setFolderPermissions ( t , grafanaListedAddr , parentFolder . UID , viewPermissions )
setFolderPermissions ( t , grafanaListedAddr , childFolder . UID , [ ] map [ string ] interface { } { } )
results := searchDashboards ( t , grafanaListedAddr , "noneuser" , "noneuser" )
foundTitles := make ( [ ] string , 0 )
for _ , result := range results {
if title , ok := result [ "title" ] . ( string ) ; ok {
foundTitles = append ( foundTitles , title )
}
}
assert . Contains ( t , foundTitles , "dashboard in parent" , "Should return dashboard in parent folder" )
assert . NotContains ( t , foundTitles , "dashboard in child" , "Should not return dashboard in child folder" )
} )
} )
}