mirror of
https://github.com/grafana/grafana.git
synced 2024-11-25 10:20:29 -06:00
1d2aa7c69b
* delete / delete folder / create folder * add backend tests * implement force delete * fix merge * lint fix * fix delete root folder * fix folder name validation * fix mysql path_hash issue * Fix returning error
360 lines
8.7 KiB
Go
360 lines
8.7 KiB
Go
package filestorage
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type cmdErrorOutput struct {
|
|
message string
|
|
args []interface{}
|
|
instance error
|
|
}
|
|
|
|
type cmdDelete struct {
|
|
path string
|
|
error *cmdErrorOutput
|
|
}
|
|
|
|
type cmdUpsert struct {
|
|
cmd UpsertFileCommand
|
|
error *cmdErrorOutput
|
|
}
|
|
|
|
type cmdCreateFolder struct {
|
|
path string
|
|
error *cmdErrorOutput
|
|
}
|
|
|
|
type cmdDeleteFolder struct {
|
|
path string
|
|
error *cmdErrorOutput
|
|
options *DeleteFolderOptions
|
|
}
|
|
|
|
type queryGetInput struct {
|
|
path string
|
|
}
|
|
|
|
type fileNameCheck struct {
|
|
v string
|
|
}
|
|
|
|
type filePropertiesCheck struct {
|
|
v map[string]string
|
|
}
|
|
|
|
type fileContentsCheck struct {
|
|
v []byte
|
|
}
|
|
|
|
type fileSizeCheck struct {
|
|
v int64
|
|
}
|
|
|
|
type fileMimeTypeCheck struct {
|
|
v string
|
|
}
|
|
|
|
type filePathCheck struct {
|
|
v string
|
|
}
|
|
|
|
type listSizeCheck struct {
|
|
v int
|
|
}
|
|
|
|
type listHasMoreCheck struct {
|
|
v bool
|
|
}
|
|
|
|
type listLastPathCheck struct {
|
|
v string
|
|
}
|
|
|
|
func fContents(contents []byte) interface{} {
|
|
return fileContentsCheck{v: contents}
|
|
}
|
|
|
|
func fName(name string) interface{} {
|
|
return fileNameCheck{v: name}
|
|
}
|
|
|
|
func fPath(path string) interface{} {
|
|
return filePathCheck{v: path}
|
|
}
|
|
|
|
func fProperties(properties map[string]string) interface{} {
|
|
return filePropertiesCheck{v: properties}
|
|
}
|
|
func fSize(size int64) interface{} {
|
|
return fileSizeCheck{v: size}
|
|
}
|
|
|
|
func fMimeType(mimeType string) interface{} {
|
|
return fileMimeTypeCheck{v: mimeType}
|
|
}
|
|
|
|
func listSize(size int) interface{} {
|
|
return listSizeCheck{v: size}
|
|
}
|
|
|
|
func listHasMore(hasMore bool) interface{} {
|
|
return listHasMoreCheck{v: hasMore}
|
|
}
|
|
|
|
func listLastPath(path string) interface{} {
|
|
return listLastPathCheck{v: path}
|
|
}
|
|
|
|
func checks(c ...interface{}) []interface{} {
|
|
return c
|
|
}
|
|
|
|
type queryGet struct {
|
|
input queryGetInput
|
|
checks []interface{}
|
|
}
|
|
|
|
type queryListFilesInput struct {
|
|
path string
|
|
paging *Paging
|
|
options *ListOptions
|
|
}
|
|
|
|
type queryListFiles struct {
|
|
input queryListFilesInput
|
|
list []interface{}
|
|
files [][]interface{}
|
|
}
|
|
|
|
type queryListFoldersInput struct {
|
|
path string
|
|
options *ListOptions
|
|
}
|
|
|
|
type queryListFolders struct {
|
|
input queryListFoldersInput
|
|
checks [][]interface{}
|
|
}
|
|
|
|
func interfaceName(myvar interface{}) string {
|
|
if t := reflect.TypeOf(myvar); t.Kind() == reflect.Ptr {
|
|
return "*" + t.Elem().Name()
|
|
} else {
|
|
return t.Name()
|
|
}
|
|
}
|
|
|
|
func handleCommand(t *testing.T, ctx context.Context, cmd interface{}, cmdName string, fs FileStorage) {
|
|
t.Helper()
|
|
|
|
var err error
|
|
var expectedErr *cmdErrorOutput
|
|
switch c := cmd.(type) {
|
|
case cmdDelete:
|
|
err = fs.Delete(ctx, c.path)
|
|
if c.error == nil {
|
|
require.NoError(t, err, "%s: should be able to delete %s", cmdName, c.path)
|
|
}
|
|
expectedErr = c.error
|
|
case cmdUpsert:
|
|
err = fs.Upsert(ctx, &c.cmd)
|
|
if c.error == nil {
|
|
require.NoError(t, err, "%s: should be able to upsert file %s", cmdName, c.cmd.Path)
|
|
}
|
|
expectedErr = c.error
|
|
case cmdCreateFolder:
|
|
err = fs.CreateFolder(ctx, c.path)
|
|
if c.error == nil {
|
|
require.NoError(t, err, "%s: should be able to create folder %s", cmdName, c.path)
|
|
}
|
|
expectedErr = c.error
|
|
case cmdDeleteFolder:
|
|
err = fs.DeleteFolder(ctx, c.path, c.options)
|
|
if c.error == nil {
|
|
require.NoError(t, err, "%s: should be able to delete %s", cmdName, c.path)
|
|
}
|
|
expectedErr = c.error
|
|
default:
|
|
t.Fatalf("unrecognized command %s", cmdName)
|
|
}
|
|
|
|
if expectedErr != nil && err != nil {
|
|
if expectedErr.instance != nil {
|
|
require.ErrorIs(t, err, expectedErr.instance)
|
|
}
|
|
|
|
if expectedErr.message != "" {
|
|
require.Errorf(t, err, expectedErr.message, expectedErr.args...)
|
|
}
|
|
}
|
|
}
|
|
|
|
func runChecks(t *testing.T, stepName string, path string, output interface{}, checks []interface{}) {
|
|
if len(checks) == 0 {
|
|
return
|
|
}
|
|
|
|
runFileMetadataCheck := func(file FileMetadata, check interface{}, checkName string) {
|
|
switch c := check.(type) {
|
|
case filePropertiesCheck:
|
|
require.Equal(t, c.v, file.Properties, "%s-%s %s", stepName, checkName, path)
|
|
case fileNameCheck:
|
|
require.Equal(t, c.v, file.Name, "%s-%s %s", stepName, checkName, path)
|
|
case fileSizeCheck:
|
|
require.Equal(t, c.v, file.Size, "%s-%s %s", stepName, checkName, path)
|
|
case fileMimeTypeCheck:
|
|
require.Equal(t, c.v, file.MimeType, "%s-%s %s", stepName, checkName, path)
|
|
case filePathCheck:
|
|
require.Equal(t, c.v, file.FullPath, "%s-%s %s", stepName, checkName, path)
|
|
default:
|
|
t.Fatalf("unrecognized file check %s", checkName)
|
|
}
|
|
}
|
|
|
|
switch o := output.(type) {
|
|
case *File:
|
|
for _, check := range checks {
|
|
checkName := interfaceName(check)
|
|
if fileContentsCheck, ok := check.(fileContentsCheck); ok {
|
|
require.Equal(t, fileContentsCheck.v, o.Contents, "%s-%s %s", stepName, checkName, path)
|
|
} else {
|
|
runFileMetadataCheck(o.FileMetadata, check, checkName)
|
|
}
|
|
}
|
|
case FileMetadata:
|
|
for _, check := range checks {
|
|
runFileMetadataCheck(o, check, interfaceName(check))
|
|
}
|
|
case *ListResponse:
|
|
for _, check := range checks {
|
|
c := check
|
|
checkName := interfaceName(c)
|
|
switch c := check.(type) {
|
|
case listSizeCheck:
|
|
require.Equal(t, c.v, len(o.Files), "%s %s\nReceived %s", stepName, path, o)
|
|
case listHasMoreCheck:
|
|
require.Equal(t, c.v, o.HasMore, "%s %s\nReceived %s", stepName, path, o)
|
|
case listLastPathCheck:
|
|
require.Equal(t, c.v, o.LastPath, "%s %s\nReceived %s", stepName, path, o)
|
|
default:
|
|
t.Fatalf("unrecognized list check %s", checkName)
|
|
}
|
|
}
|
|
|
|
default:
|
|
t.Fatalf("unrecognized output %s", interfaceName(output))
|
|
}
|
|
}
|
|
|
|
func formatPathStructure(files []*File) string {
|
|
if len(files) == 0 {
|
|
return "<<EMPTY>>"
|
|
}
|
|
res := "\n"
|
|
for _, f := range files {
|
|
res = fmt.Sprintf("%s%s\n", res, f.FullPath)
|
|
}
|
|
return res
|
|
}
|
|
|
|
func handleQuery(t *testing.T, ctx context.Context, query interface{}, queryName string, fs FileStorage) {
|
|
t.Helper()
|
|
|
|
switch q := query.(type) {
|
|
case queryGet:
|
|
inputPath := q.input.path
|
|
file, err := fs.Get(ctx, inputPath)
|
|
require.NoError(t, err, "%s: should be able to get file %s", queryName, inputPath)
|
|
|
|
if q.checks != nil && len(q.checks) > 0 {
|
|
require.NotNil(t, file, "%s %s", queryName, inputPath)
|
|
require.Equal(t, strings.ToLower(inputPath), strings.ToLower(file.FullPath), "%s %s", queryName, inputPath)
|
|
runChecks(t, queryName, inputPath, file, q.checks)
|
|
} else {
|
|
require.Nil(t, file, "%s %s", queryName, inputPath)
|
|
}
|
|
case queryListFiles:
|
|
inputPath := q.input.path
|
|
resp, err := fs.List(ctx, inputPath, q.input.paging, q.input.options)
|
|
require.NoError(t, err, "%s: should be able to list files in %s", queryName, inputPath)
|
|
require.NotNil(t, resp)
|
|
if q.list != nil && len(q.list) > 0 {
|
|
runChecks(t, queryName, inputPath, resp, q.list)
|
|
} else {
|
|
require.NotNil(t, resp, "%s %s", queryName, inputPath)
|
|
require.Equal(t, false, resp.HasMore, "%s %s", queryName, inputPath)
|
|
require.Equal(t, 0, len(resp.Files), "%s %s", queryName, inputPath)
|
|
require.Equal(t, "", resp.LastPath, "%s %s", queryName, inputPath)
|
|
}
|
|
|
|
if q.files != nil {
|
|
require.Equal(t, len(resp.Files), len(q.files), "%s expected a check for each actual file at path: \"%s\". actual: %s", queryName, inputPath, formatPathStructure(resp.Files))
|
|
for i, file := range resp.Files {
|
|
runChecks(t, queryName, inputPath, file, q.files[i])
|
|
}
|
|
}
|
|
case queryListFolders:
|
|
inputPath := q.input.path
|
|
opts := q.input.options
|
|
if opts == nil {
|
|
opts = &ListOptions{
|
|
Recursive: true,
|
|
WithFiles: false,
|
|
WithFolders: true,
|
|
WithContents: false,
|
|
Filter: nil,
|
|
}
|
|
} else {
|
|
opts.WithFolders = true
|
|
opts.WithFiles = false
|
|
}
|
|
resp, err := fs.List(ctx, inputPath, &Paging{
|
|
After: "",
|
|
First: 100000,
|
|
}, opts)
|
|
require.NotNil(t, resp)
|
|
require.NoError(t, err, "%s: should be able to list folders in %s", queryName, inputPath)
|
|
|
|
if q.checks != nil {
|
|
require.Equal(t, len(resp.Files), len(q.checks), "%s: expected a check for each actual folder at path: \"%s\". actual: %s", queryName, inputPath, formatPathStructure(resp.Files))
|
|
for i, file := range resp.Files {
|
|
runChecks(t, queryName, inputPath, file, q.checks[i])
|
|
}
|
|
} else {
|
|
require.Equal(t, 0, len(resp.Files), "%s %s", queryName, inputPath)
|
|
}
|
|
default:
|
|
t.Fatalf("unrecognized query %s", queryName)
|
|
}
|
|
}
|
|
|
|
func executeTestStep(t *testing.T, ctx context.Context, step interface{}, stepNumber int, fs FileStorage) {
|
|
name := fmt.Sprintf("[%d]%s", stepNumber, interfaceName(step))
|
|
|
|
switch s := step.(type) {
|
|
case queryGet:
|
|
handleQuery(t, ctx, s, name, fs)
|
|
case queryListFiles:
|
|
handleQuery(t, ctx, s, name, fs)
|
|
case queryListFolders:
|
|
handleQuery(t, ctx, s, name, fs)
|
|
case cmdUpsert:
|
|
handleCommand(t, ctx, s, name, fs)
|
|
case cmdDelete:
|
|
handleCommand(t, ctx, s, name, fs)
|
|
case cmdCreateFolder:
|
|
handleCommand(t, ctx, s, name, fs)
|
|
case cmdDeleteFolder:
|
|
handleCommand(t, ctx, s, name, fs)
|
|
default:
|
|
t.Fatalf("unrecognized step %s", name)
|
|
}
|
|
}
|