The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/infra/filestorage/test_utils.go

363 lines
8.9 KiB

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
options *GetFileOptions
}
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
options := q.input.options
file, fileFound, err := fs.Get(ctx, inputPath, options)
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.True(t, fileFound, "%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)
require.False(t, fileFound, "%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: "",
Limit: 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)
}
}