@ -1,12 +1,23 @@
package playlist
import (
"context"
"encoding/json"
"net/http"
"slices"
"testing"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
folderv0alpha1 "github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tests/apis"
"github.com/grafana/grafana/pkg/tests/testinfra"
"github.com/grafana/grafana/pkg/tests/testsuite"
@ -16,14 +27,20 @@ func TestMain(m *testing.M) {
testsuite . Run ( m )
}
var gvr = schema . GroupVersionResource {
Group : "folder.grafana.app" ,
Version : "v0alpha1" ,
Resource : "folders" ,
}
func TestIntegrationFoldersApp ( t * testing . T ) {
if testing . Short ( ) {
t . Skip ( "skipping integration test" )
}
helper := apis . NewK8sTestHelper ( t , testinfra . GrafanaOpts {
AppModeProduction : false , // required for experimental APIs
AppModeProduction : true ,
EnableFeatureToggles : [ ] string {
featuremgmt . FlagGrafanaAPIServerWithExperimentalAPIs , // Required to start the example service
featuremgmt . FlagKubernetesFolders ,
} ,
} )
@ -34,7 +51,6 @@ func TestIntegrationFoldersApp(t *testing.T) {
v1Disco , err := json . MarshalIndent ( resources , "" , " " )
require . NoError ( t , err )
// fmt.Printf("%s", string(v1Disco))
require . JSONEq ( t , ` {
"kind" : "APIResourceList" ,
@ -86,4 +102,293 @@ func TestIntegrationFoldersApp(t *testing.T) {
]
} ` , string ( v1Disco ) )
} )
t . Run ( "with k8s api flag" , func ( t * testing . T ) {
doFolderTests ( t , apis . NewK8sTestHelper ( t , testinfra . GrafanaOpts {
AppModeProduction : true ,
DisableAnonymous : true ,
EnableFeatureToggles : [ ] string {
featuremgmt . FlagKubernetesFolders ,
} ,
} ) )
} )
t . Run ( "with dual write (file, mode 0)" , func ( t * testing . T ) {
doFolderTests ( t , apis . NewK8sTestHelper ( t , testinfra . GrafanaOpts {
AppModeProduction : true ,
DisableAnonymous : true ,
APIServerStorageType : "file" , // write the files to disk
UnifiedStorageConfig : map [ string ] setting . UnifiedStorageConfig {
folderv0alpha1 . RESOURCEGROUP : {
DualWriterMode : grafanarest . Mode0 ,
} ,
} ,
EnableFeatureToggles : [ ] string {
featuremgmt . FlagKubernetesFolders ,
} ,
} ) )
} )
t . Run ( "with dual write (file, mode 1)" , func ( t * testing . T ) {
doFolderTests ( t , apis . NewK8sTestHelper ( t , testinfra . GrafanaOpts {
AppModeProduction : true ,
DisableAnonymous : true ,
APIServerStorageType : "file" , // write the files to disk
UnifiedStorageConfig : map [ string ] setting . UnifiedStorageConfig {
folderv0alpha1 . RESOURCEGROUP : {
DualWriterMode : grafanarest . Mode1 ,
} ,
} ,
EnableFeatureToggles : [ ] string {
featuremgmt . FlagKubernetesFolders ,
} ,
} ) )
} )
t . Run ( "with dual write (unified storage, mode 0)" , func ( t * testing . T ) {
doFolderTests ( t , apis . NewK8sTestHelper ( t , testinfra . GrafanaOpts {
AppModeProduction : true ,
DisableAnonymous : true ,
APIServerStorageType : "unified" ,
UnifiedStorageConfig : map [ string ] setting . UnifiedStorageConfig {
folderv0alpha1 . RESOURCEGROUP : {
DualWriterMode : grafanarest . Mode0 ,
} ,
} ,
EnableFeatureToggles : [ ] string {
featuremgmt . FlagKubernetesFolders ,
} ,
} ) )
} )
t . Run ( "with dual write (unified storage, mode 1)" , func ( t * testing . T ) {
doFolderTests ( t , apis . NewK8sTestHelper ( t , testinfra . GrafanaOpts {
AppModeProduction : true ,
DisableAnonymous : true ,
APIServerStorageType : "unified" ,
UnifiedStorageConfig : map [ string ] setting . UnifiedStorageConfig {
folderv0alpha1 . RESOURCEGROUP : {
DualWriterMode : grafanarest . Mode1 ,
} ,
} ,
EnableFeatureToggles : [ ] string {
featuremgmt . FlagKubernetesFolders ,
} ,
} ) )
} )
t . Run ( "with dual write (unified-grpc, mode 0)" , func ( t * testing . T ) {
doFolderTests ( t , apis . NewK8sTestHelper ( t , testinfra . GrafanaOpts {
AppModeProduction : true ,
DisableAnonymous : true ,
APIServerStorageType : "unified-grpc" ,
UnifiedStorageConfig : map [ string ] setting . UnifiedStorageConfig {
folderv0alpha1 . RESOURCEGROUP : {
DualWriterMode : grafanarest . Mode0 ,
} ,
} ,
EnableFeatureToggles : [ ] string {
featuremgmt . FlagKubernetesFolders ,
} ,
} ) )
} )
t . Run ( "with dual write (unified-grpc, mode 1)" , func ( t * testing . T ) {
doFolderTests ( t , apis . NewK8sTestHelper ( t , testinfra . GrafanaOpts {
AppModeProduction : true ,
DisableAnonymous : true ,
APIServerStorageType : "unified-grpc" ,
UnifiedStorageConfig : map [ string ] setting . UnifiedStorageConfig {
folderv0alpha1 . RESOURCEGROUP : {
DualWriterMode : grafanarest . Mode1 ,
} ,
} ,
EnableFeatureToggles : [ ] string {
featuremgmt . FlagKubernetesFolders ,
} ,
} ) )
} )
t . Run ( "with dual write (etcd, mode 0)" , func ( t * testing . T ) {
// NOTE: running local etcd, that will be wiped clean!
t . Skip ( "local etcd testing" )
helper := apis . NewK8sTestHelper ( t , testinfra . GrafanaOpts {
AppModeProduction : true ,
DisableAnonymous : true ,
APIServerStorageType : "etcd" ,
UnifiedStorageConfig : map [ string ] setting . UnifiedStorageConfig {
folderv0alpha1 . RESOURCEGROUP : {
DualWriterMode : grafanarest . Mode0 ,
} ,
} ,
EnableFeatureToggles : [ ] string {
featuremgmt . FlagKubernetesFolders ,
} ,
} )
// Clear the collection before starting (etcd)
client := helper . GetResourceClient ( apis . ResourceClientArgs {
User : helper . Org1 . Admin ,
GVR : gvr ,
} )
err := client . Resource . DeleteCollection ( context . Background ( ) , metav1 . DeleteOptions { } , metav1 . ListOptions { } )
require . NoError ( t , err )
doFolderTests ( t , helper )
} )
t . Run ( "with dual write (etcd, mode 1)" , func ( t * testing . T ) {
// NOTE: running local etcd, that will be wiped clean!
t . Skip ( "local etcd testing" )
helper := apis . NewK8sTestHelper ( t , testinfra . GrafanaOpts {
AppModeProduction : true ,
DisableAnonymous : true ,
APIServerStorageType : "etcd" ,
UnifiedStorageConfig : map [ string ] setting . UnifiedStorageConfig {
folderv0alpha1 . RESOURCEGROUP : {
DualWriterMode : grafanarest . Mode1 ,
} ,
} ,
EnableFeatureToggles : [ ] string {
featuremgmt . FlagKubernetesFolders ,
} ,
} )
// Clear the collection before starting (etcd)
client := helper . GetResourceClient ( apis . ResourceClientArgs {
User : helper . Org1 . Admin ,
GVR : gvr ,
} )
err := client . Resource . DeleteCollection ( context . Background ( ) , metav1 . DeleteOptions { } , metav1 . ListOptions { } )
require . NoError ( t , err )
doFolderTests ( t , helper )
} )
}
func doFolderTests ( t * testing . T , helper * apis . K8sTestHelper ) * apis . K8sTestHelper {
t . Run ( "Check folder CRUD (just create for now) in legacy API appears in k8s apis" , func ( t * testing . T ) {
client := helper . GetResourceClient ( apis . ResourceClientArgs {
// #TODO: figure out permissions topic
User : helper . Org1 . Admin ,
GVR : gvr ,
} )
// #TODO fill out the payload: parentUID, description
// and check about uid orgid and siU
legacyPayload := ` {
"title" : "Test" ,
"uid" : ""
} `
legacyCreate := apis . DoRequest ( helper , apis . RequestParams {
User : client . Args . User ,
Method : http . MethodPost ,
Path : "/api/folders" ,
Body : [ ] byte ( legacyPayload ) ,
} , & folder . Folder { } )
require . NotNil ( t , legacyCreate . Result )
uid := legacyCreate . Result . UID
require . NotEmpty ( t , uid )
expectedResult := ` {
"apiVersion" : "folder.grafana.app/v0alpha1" ,
"kind" : "Folder" ,
"metadata" : {
"annotations" : {
"grafana.app/originPath" : "${originPath}" ,
"grafana.app/originName" : "SQL"
} ,
"creationTimestamp" : "${creationTimestamp}" ,
"name" : "` + uid + `" ,
"namespace" : "default" ,
"resourceVersion" : "${resourceVersion}" ,
"uid" : "${uid}"
} ,
"spec" : {
"title" : "Test"
}
} `
// Get should return the same result
found , err := client . Resource . Get ( context . Background ( ) , uid , metav1 . GetOptions { } )
require . NoError ( t , err )
require . JSONEq ( t , expectedResult , client . SanitizeJSON ( found ) )
} )
t . Run ( "Do CRUD (just CR for now) via k8s (and check that legacy api still works)" , func ( t * testing . T ) {
client := helper . GetResourceClient ( apis . ResourceClientArgs {
// #TODO: figure out permissions topic
User : helper . Org1 . Admin ,
GVR : gvr ,
} )
// Create the folder "test"
first , err := client . Resource . Create ( context . Background ( ) ,
helper . LoadYAMLOrJSONFile ( "testdata/folder-test-create.yaml" ) ,
metav1 . CreateOptions { } ,
)
require . NoError ( t , err )
require . Equal ( t , "test" , first . GetName ( ) )
uids := [ ] string { first . GetName ( ) }
// Create (with name generation) two folders
for i := 0 ; i < 2 ; i ++ {
out , err := client . Resource . Create ( context . Background ( ) ,
helper . LoadYAMLOrJSONFile ( "testdata/folder-generate.yaml" ) ,
metav1 . CreateOptions { } ,
)
require . NoError ( t , err )
uids = append ( uids , out . GetName ( ) )
}
slices . Sort ( uids ) // make list compare stable
// Check all playlists
for _ , uid := range uids {
getFromBothAPIs ( t , helper , client , uid , nil )
}
} )
return helper
}
// This does a get with both k8s and legacy API, and verifies the results are the same
func getFromBothAPIs ( t * testing . T ,
helper * apis . K8sTestHelper ,
client * apis . K8sResourceClient ,
uid string ,
// Optionally match some expect some values
expect * folder . Folder ,
) * unstructured . Unstructured {
t . Helper ( )
found , err := client . Resource . Get ( context . Background ( ) , uid , metav1 . GetOptions { } )
require . NoError ( t , err )
require . Equal ( t , uid , found . GetName ( ) )
dto := apis . DoRequest ( helper , apis . RequestParams {
User : client . Args . User ,
Method : http . MethodGet ,
Path : "/api/folders/" + uid ,
} , & folder . Folder { } ) . Result
require . NotNil ( t , dto )
require . Equal ( t , uid , dto . UID )
spec , ok := found . Object [ "spec" ] . ( map [ string ] any )
require . True ( t , ok )
require . Equal ( t , dto . UID , found . GetName ( ) )
require . Equal ( t , dto . Title , spec [ "title" ] )
// #TODO add checks for other fields
if expect != nil {
if expect . Title != "" {
require . Equal ( t , expect . Title , dto . Title )
require . Equal ( t , expect . Title , spec [ "title" ] )
}
if expect . UID != "" {
require . Equal ( t , expect . UID , dto . UID )
require . Equal ( t , expect . UID , found . GetName ( ) )
}
}
return found
}