mirror of https://github.com/grafana/grafana
#45498: refactor tests, fix pagination bug in FS implementation
parent
a2baf378bd
commit
0e20df4ccb
@ -1,338 +0,0 @@ |
|||||||
//go:build integration
|
|
||||||
// +build integration
|
|
||||||
|
|
||||||
package filestorage |
|
||||||
|
|
||||||
import ( |
|
||||||
"context" |
|
||||||
"testing" |
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log" |
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore" |
|
||||||
"github.com/stretchr/testify/require" |
|
||||||
"gocloud.dev/blob" |
|
||||||
) |
|
||||||
|
|
||||||
type NameFullPath struct { |
|
||||||
Name string |
|
||||||
FullPath string |
|
||||||
} |
|
||||||
|
|
||||||
func extractNameFullPath(meta []FileMetadata) []NameFullPath { |
|
||||||
resp := make([]NameFullPath, 0) |
|
||||||
for i := range meta { |
|
||||||
resp = append(resp, NameFullPath{ |
|
||||||
Name: meta[i].Name, |
|
||||||
FullPath: meta[i].FullPath, |
|
||||||
}) |
|
||||||
} |
|
||||||
return resp |
|
||||||
} |
|
||||||
|
|
||||||
func TestSqlStorage(t *testing.T) { |
|
||||||
|
|
||||||
var sqlStore *sqlstore.SQLStore |
|
||||||
var filestorage FileStorage |
|
||||||
var ctx context.Context |
|
||||||
|
|
||||||
setup := func() { |
|
||||||
mode := "mem" |
|
||||||
testLogger := log.New("testStorageLogger") |
|
||||||
if mode == "db" { |
|
||||||
sqlStore = sqlstore.InitTestDB(t) |
|
||||||
filestorage = NewDbStorage(testLogger, sqlStore, nil) |
|
||||||
} else if mode == "mem" { |
|
||||||
bucket, _ := blob.OpenBucket(context.Background(), "mem://") |
|
||||||
filestorage = NewCdkBlobStorage(testLogger, bucket, Delimiter, nil) |
|
||||||
} |
|
||||||
|
|
||||||
ctx = context.Background() |
|
||||||
} |
|
||||||
|
|
||||||
t.Run("Should be able to insert a file", func(t *testing.T) { |
|
||||||
setup() |
|
||||||
err := filestorage.Upsert(ctx, &UpsertFileCommand{ |
|
||||||
Path: "/folder1/folder2/file.jpg", |
|
||||||
Contents: &[]byte{}, |
|
||||||
Properties: map[string]string{"prop1": "val1", "prop2": "val"}, |
|
||||||
}) |
|
||||||
require.NoError(t, err) |
|
||||||
}) |
|
||||||
|
|
||||||
t.Run("Should be able to get a file", func(t *testing.T) { |
|
||||||
setup() |
|
||||||
path := "/folder1/folder2/file.jpg" |
|
||||||
properties := map[string]string{"prop1": "val1", "prop2": "val"} |
|
||||||
err := filestorage.Upsert(ctx, &UpsertFileCommand{ |
|
||||||
Path: path, |
|
||||||
Contents: &[]byte{}, |
|
||||||
Properties: properties, |
|
||||||
}) |
|
||||||
require.NoError(t, err) |
|
||||||
|
|
||||||
file, err := filestorage.Get(ctx, path) |
|
||||||
require.NoError(t, err) |
|
||||||
|
|
||||||
require.Equal(t, path, file.FullPath) |
|
||||||
require.Equal(t, "file.jpg", file.Name) |
|
||||||
require.Equal(t, properties, file.Properties) |
|
||||||
}) |
|
||||||
|
|
||||||
t.Run("Should not be able to get a non-existent file", func(t *testing.T) { |
|
||||||
setup() |
|
||||||
path := "/folder1/folder2/file.jpg" |
|
||||||
|
|
||||||
file, err := filestorage.Get(ctx, path) |
|
||||||
require.NoError(t, err) |
|
||||||
require.Nil(t, file) |
|
||||||
}) |
|
||||||
|
|
||||||
t.Run("Should be able to list files", func(t *testing.T) { |
|
||||||
setup() |
|
||||||
err := filestorage.Upsert(ctx, &UpsertFileCommand{ |
|
||||||
Path: "/folder1/folder2/file.jpg", |
|
||||||
Contents: &[]byte{}, |
|
||||||
Properties: map[string]string{"prop1": "val1", "prop2": "val"}, |
|
||||||
}) |
|
||||||
require.NoError(t, err) |
|
||||||
|
|
||||||
err = filestorage.Upsert(ctx, &UpsertFileCommand{ |
|
||||||
Path: "/folder1/file-inner.jpg", |
|
||||||
Contents: &[]byte{}, |
|
||||||
Properties: map[string]string{"prop1": "val1", "prop2": "val"}, |
|
||||||
}) |
|
||||||
require.NoError(t, err) |
|
||||||
|
|
||||||
resp, err := filestorage.ListFiles(ctx, "/folder1", nil, &ListOptions{Recursive: true}) |
|
||||||
require.NoError(t, err) |
|
||||||
|
|
||||||
require.Equal(t, []NameFullPath{ |
|
||||||
{ |
|
||||||
Name: "file-inner.jpg", |
|
||||||
FullPath: "/folder1/file-inner.jpg", |
|
||||||
}, |
|
||||||
{ |
|
||||||
Name: "file.jpg", |
|
||||||
FullPath: "/folder1/folder2/file.jpg", |
|
||||||
}, |
|
||||||
}, extractNameFullPath(resp.Files)) |
|
||||||
|
|
||||||
resp, err = filestorage.ListFiles(ctx, "/folder1", nil, nil) |
|
||||||
require.NoError(t, err) |
|
||||||
|
|
||||||
require.Equal(t, []NameFullPath{ |
|
||||||
{ |
|
||||||
Name: "file-inner.jpg", |
|
||||||
FullPath: "/folder1/file-inner.jpg", |
|
||||||
}, |
|
||||||
}, extractNameFullPath(resp.Files)) |
|
||||||
|
|
||||||
resp, err = filestorage.ListFiles(ctx, "/folder1/folder2", nil, nil) |
|
||||||
require.NoError(t, err) |
|
||||||
|
|
||||||
require.Equal(t, []NameFullPath{ |
|
||||||
{ |
|
||||||
Name: "file.jpg", |
|
||||||
FullPath: "/folder1/folder2/file.jpg", |
|
||||||
}, |
|
||||||
}, extractNameFullPath(resp.Files)) |
|
||||||
}) |
|
||||||
|
|
||||||
t.Run("Should be able to list files with prefix filtering", func(t *testing.T) { |
|
||||||
setup() |
|
||||||
err := filestorage.Upsert(ctx, &UpsertFileCommand{ |
|
||||||
Path: "/folder1/folder2/file.jpg", |
|
||||||
Contents: &[]byte{}, |
|
||||||
Properties: map[string]string{"prop1": "val1", "prop2": "val"}, |
|
||||||
}) |
|
||||||
require.NoError(t, err) |
|
||||||
|
|
||||||
err = filestorage.Upsert(ctx, &UpsertFileCommand{ |
|
||||||
Path: "/folder1/file-inner.jpg", |
|
||||||
Contents: &[]byte{}, |
|
||||||
Properties: map[string]string{"prop1": "val1", "prop2": "val"}, |
|
||||||
}) |
|
||||||
require.NoError(t, err) |
|
||||||
|
|
||||||
resp, err := filestorage.ListFiles(ctx, "/folder1", nil, &ListOptions{ |
|
||||||
Recursive: true, PathFilters: PathFilters{ |
|
||||||
allowedPrefixes: []string{"/folder2"}, |
|
||||||
}, |
|
||||||
}) |
|
||||||
require.NoError(t, err) |
|
||||||
|
|
||||||
require.Equal(t, []NameFullPath{}, extractNameFullPath(resp.Files)) |
|
||||||
|
|
||||||
resp, err = filestorage.ListFiles(ctx, "/folder1", nil, &ListOptions{ |
|
||||||
Recursive: true, PathFilters: PathFilters{ |
|
||||||
allowedPrefixes: []string{"/folder1/folde"}, |
|
||||||
}, |
|
||||||
}) |
|
||||||
require.NoError(t, err) |
|
||||||
|
|
||||||
require.Equal(t, []NameFullPath{ |
|
||||||
{ |
|
||||||
Name: "file.jpg", |
|
||||||
FullPath: "/folder1/folder2/file.jpg", |
|
||||||
}, |
|
||||||
}, extractNameFullPath(resp.Files)) |
|
||||||
}) |
|
||||||
|
|
||||||
t.Run("Should be able to list files with pagination", func(t *testing.T) { |
|
||||||
setup() |
|
||||||
err := filestorage.Upsert(ctx, &UpsertFileCommand{ |
|
||||||
Path: "/folder1/folder2/file.jpg", |
|
||||||
Contents: &[]byte{}, |
|
||||||
Properties: map[string]string{"prop1": "val1", "prop2": "val"}, |
|
||||||
}) |
|
||||||
require.NoError(t, err) |
|
||||||
|
|
||||||
err = filestorage.Upsert(ctx, &UpsertFileCommand{ |
|
||||||
Path: "/folder1/file-inner.jpg", |
|
||||||
Contents: &[]byte{}, |
|
||||||
Properties: map[string]string{"prop1": "val1", "prop2": "val"}, |
|
||||||
}) |
|
||||||
require.NoError(t, err) |
|
||||||
filestorage.Upsert(ctx, &UpsertFileCommand{ |
|
||||||
Path: "/folderA/folderB/file.txt", |
|
||||||
Contents: &[]byte{}, |
|
||||||
Properties: map[string]string{"prop1": "val1", "prop2": "val"}, |
|
||||||
}) |
|
||||||
|
|
||||||
resp, err := filestorage.ListFiles(ctx, "/", &Paging{ |
|
||||||
After: "/folder1/file-inner.jpg", |
|
||||||
First: 1, |
|
||||||
}, &ListOptions{Recursive: true}) |
|
||||||
require.NoError(t, err) |
|
||||||
|
|
||||||
require.Equal(t, []NameFullPath{ |
|
||||||
{Name: "file.jpg", FullPath: "/folder1/folder2/file.jpg"}, |
|
||||||
}, extractNameFullPath(resp.Files)) |
|
||||||
}) |
|
||||||
|
|
||||||
t.Run("Should be able to list folders", func(t *testing.T) { |
|
||||||
setup() |
|
||||||
filestorage.Upsert(ctx, &UpsertFileCommand{ |
|
||||||
Path: "/folder1/folder2/file.jpg", |
|
||||||
Contents: &[]byte{}, |
|
||||||
Properties: map[string]string{"prop1": "val1", "prop2": "val"}, |
|
||||||
}) |
|
||||||
filestorage.Upsert(ctx, &UpsertFileCommand{ |
|
||||||
Path: "/folder1/file-inner.jpg", |
|
||||||
Contents: &[]byte{}, |
|
||||||
Properties: map[string]string{"prop1": "val1", "prop2": "val"}, |
|
||||||
}) |
|
||||||
filestorage.Upsert(ctx, &UpsertFileCommand{ |
|
||||||
Path: "/folderX/folderZ/file.txt", |
|
||||||
Contents: &[]byte{}, |
|
||||||
Properties: map[string]string{"prop1": "val1", "prop2": "val"}, |
|
||||||
}) |
|
||||||
filestorage.Upsert(ctx, &UpsertFileCommand{ |
|
||||||
Path: "/folderA/folderB/file.txt", |
|
||||||
Contents: &[]byte{}, |
|
||||||
Properties: map[string]string{"prop1": "val1", "prop2": "val"}, |
|
||||||
}) |
|
||||||
|
|
||||||
resp, err := filestorage.ListFolders(ctx, "/", nil) |
|
||||||
require.NoError(t, err) |
|
||||||
|
|
||||||
require.Equal(t, []FileMetadata{ |
|
||||||
{ |
|
||||||
Name: "folder1", |
|
||||||
FullPath: "/folder1", |
|
||||||
}, |
|
||||||
{ |
|
||||||
Name: "folder2", |
|
||||||
FullPath: "/folder1/folder2", |
|
||||||
}, |
|
||||||
{ |
|
||||||
Name: "folderA", |
|
||||||
FullPath: "/folderA", |
|
||||||
}, { |
|
||||||
Name: "folderB", |
|
||||||
FullPath: "/folderA/folderB", |
|
||||||
}, |
|
||||||
{ |
|
||||||
Name: "folderX", |
|
||||||
FullPath: "/folderX", |
|
||||||
}, { |
|
||||||
Name: "folderZ", |
|
||||||
FullPath: "/folderX/folderZ", |
|
||||||
}, |
|
||||||
}, resp) |
|
||||||
}) |
|
||||||
|
|
||||||
t.Run("Should be able to create and delete folders", func(t *testing.T) { |
|
||||||
setup() |
|
||||||
filestorage.Upsert(ctx, &UpsertFileCommand{ |
|
||||||
Path: "/folder1/folder2/file.jpg", |
|
||||||
Contents: &[]byte{}, |
|
||||||
Properties: map[string]string{"prop1": "val1", "prop2": "val"}, |
|
||||||
}) |
|
||||||
filestorage.CreateFolder(ctx, "/folder/dashboards", "myNewFolder") |
|
||||||
filestorage.CreateFolder(ctx, "/folder/icons", "emojis") |
|
||||||
err := filestorage.DeleteFolder(ctx, "/folder/dashboards/myNewFolder") |
|
||||||
require.NoError(t, err) |
|
||||||
|
|
||||||
resp, err := filestorage.ListFolders(ctx, "/", nil) |
|
||||||
require.NoError(t, err) |
|
||||||
|
|
||||||
require.Equal(t, []FileMetadata{ |
|
||||||
{ |
|
||||||
Name: "folder", |
|
||||||
FullPath: "/folder", |
|
||||||
}, { |
|
||||||
Name: "icons", |
|
||||||
FullPath: "/folder/icons", |
|
||||||
}, |
|
||||||
{ |
|
||||||
Name: "emojis", |
|
||||||
FullPath: "/folder/icons/emojis", |
|
||||||
}, { |
|
||||||
Name: "folder1", |
|
||||||
FullPath: "/folder1", |
|
||||||
}, |
|
||||||
{ |
|
||||||
Name: "folder2", |
|
||||||
FullPath: "/folder1/folder2", |
|
||||||
}, |
|
||||||
}, resp) |
|
||||||
}) |
|
||||||
|
|
||||||
t.Run("Should not be able to delete folders with files", func(t *testing.T) { |
|
||||||
setup() |
|
||||||
filestorage.CreateFolder(ctx, "/folder/dashboards", "myNewFolder") |
|
||||||
filestorage.Upsert(ctx, &UpsertFileCommand{ |
|
||||||
Path: "/folder/dashboards/myNewFolder/file.jpg", |
|
||||||
Contents: &[]byte{}, |
|
||||||
Properties: map[string]string{"prop1": "val1", "prop2": "val"}, |
|
||||||
}) |
|
||||||
filestorage.DeleteFolder(ctx, "/folder/dashboards/myNewFolder") |
|
||||||
|
|
||||||
resp, err := filestorage.ListFolders(ctx, "/", nil) |
|
||||||
require.NoError(t, err) |
|
||||||
|
|
||||||
require.Equal(t, []FileMetadata{ |
|
||||||
{ |
|
||||||
Name: "folder", |
|
||||||
FullPath: "/folder", |
|
||||||
}, { |
|
||||||
Name: "dashboards", |
|
||||||
FullPath: "/folder/dashboards", |
|
||||||
}, |
|
||||||
{ |
|
||||||
Name: "myNewFolder", |
|
||||||
FullPath: "/folder/dashboards/myNewFolder", |
|
||||||
}, |
|
||||||
}, resp) |
|
||||||
|
|
||||||
files, err := filestorage.ListFiles(ctx, "/", nil, &ListOptions{Recursive: true}) |
|
||||||
require.NoError(t, err) |
|
||||||
require.Equal(t, []NameFullPath{ |
|
||||||
{ |
|
||||||
Name: "file.jpg", |
|
||||||
FullPath: "/folder/dashboards/myNewFolder/file.jpg", |
|
||||||
}, |
|
||||||
}, extractNameFullPath(files.Files)) |
|
||||||
}) |
|
||||||
} |
|
@ -0,0 +1,387 @@ |
|||||||
|
//go:build integration
|
||||||
|
// +build integration
|
||||||
|
|
||||||
|
package filestorage |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"encoding/base64" |
||||||
|
"fmt" |
||||||
|
"github.com/grafana/grafana/pkg/infra/log" |
||||||
|
"github.com/grafana/grafana/pkg/services/sqlstore" |
||||||
|
"gocloud.dev/blob" |
||||||
|
"io/ioutil" |
||||||
|
"os" |
||||||
|
"testing" |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
pngImageBase64 = "iVBORw0KGgoAAAANSUhEUgAAAC4AAAAmCAYAAAC76qlaAAAABHNCSVQICAgIfAhkiAAAABl0RVh0U29mdHdhcmUAZ25vbWUtc2NyZWVuc2hvdO8Dvz4AAABFSURBVFiF7c5BDQAhEACx4/x7XjzwGELSKuiamfke9N8OnBKvidfEa+I18Zp4TbwmXhOvidfEa+I18Zp4TbwmXhOvidc2lcsESD1LGnUAAAAASUVORK5CYII=" |
||||||
|
) |
||||||
|
|
||||||
|
func TestFsStorage(t *testing.T) { |
||||||
|
|
||||||
|
var testLogger log.Logger |
||||||
|
var sqlStore *sqlstore.SQLStore |
||||||
|
var filestorage FileStorage |
||||||
|
var ctx context.Context |
||||||
|
var tempDir string |
||||||
|
pngImage, _ := base64.StdEncoding.DecodeString(pngImageBase64) |
||||||
|
pngImageSize := int64(len(pngImage)) |
||||||
|
|
||||||
|
commonSetup := func() { |
||||||
|
testLogger = log.New("testStorageLogger") |
||||||
|
ctx = context.Background() |
||||||
|
} |
||||||
|
|
||||||
|
cleanUp := func() { |
||||||
|
testLogger = nil |
||||||
|
sqlStore = nil |
||||||
|
if filestorage != nil { |
||||||
|
_ = filestorage.close() |
||||||
|
filestorage = nil |
||||||
|
} |
||||||
|
|
||||||
|
ctx = nil |
||||||
|
_ = os.RemoveAll(tempDir) |
||||||
|
} |
||||||
|
|
||||||
|
setupInMemFS := func() { |
||||||
|
commonSetup() |
||||||
|
bucket, _ := blob.OpenBucket(context.Background(), "mem://") |
||||||
|
filestorage = NewCdkBlobStorage(testLogger, bucket, Delimiter, nil) |
||||||
|
} |
||||||
|
|
||||||
|
setupSqlFS := func() { |
||||||
|
commonSetup() |
||||||
|
sqlStore = sqlstore.InitTestDB(t) |
||||||
|
filestorage = NewDbStorage(testLogger, sqlStore, nil) |
||||||
|
} |
||||||
|
|
||||||
|
setupLocalFs := func() { |
||||||
|
commonSetup() |
||||||
|
tmpDir, err := ioutil.TempDir("", "") |
||||||
|
tempDir = tmpDir |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
|
||||||
|
bucket, err := blob.OpenBucket(context.Background(), "file://"+tmpDir) |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
filestorage = NewCdkBlobStorage(testLogger, bucket, "", nil) |
||||||
|
} |
||||||
|
|
||||||
|
tests := []struct { |
||||||
|
name string |
||||||
|
steps []interface{} |
||||||
|
}{ |
||||||
|
{ |
||||||
|
name: "inserting a file", |
||||||
|
steps: []interface{}{ |
||||||
|
cmdUpsert{ |
||||||
|
cmd: UpsertFileCommand{ |
||||||
|
Path: "/folder1/file.png", |
||||||
|
Contents: &pngImage, |
||||||
|
Properties: map[string]string{"prop1": "val1", "prop2": "val"}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
queryGet{ |
||||||
|
input: queryGetInput{ |
||||||
|
path: "/folder1/file.png", |
||||||
|
}, |
||||||
|
checks: checks( |
||||||
|
fName("file.png"), |
||||||
|
fMimeType("image/png"), |
||||||
|
fProperties(map[string]string{"prop1": "val1", "prop2": "val"}), |
||||||
|
fSize(pngImageSize), |
||||||
|
fContents(pngImage), |
||||||
|
), |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "getting a non-existent file", |
||||||
|
steps: []interface{}{ |
||||||
|
queryGet{ |
||||||
|
input: queryGetInput{ |
||||||
|
path: "/folder1/file12412.png", |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "listing files", |
||||||
|
steps: []interface{}{ |
||||||
|
cmdUpsert{ |
||||||
|
cmd: UpsertFileCommand{ |
||||||
|
Path: "/folder1/folder2/file.jpg", |
||||||
|
Contents: &[]byte{}, |
||||||
|
Properties: map[string]string{"prop1": "val1", "prop2": "val"}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
cmdUpsert{ |
||||||
|
cmd: UpsertFileCommand{ |
||||||
|
Path: "/folder1/file-inner.jpg", |
||||||
|
Contents: &[]byte{}, |
||||||
|
Properties: map[string]string{"prop1": "val1", "prop2": "val"}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
queryListFiles{ |
||||||
|
input: queryListFilesInput{path: "/folder1", options: &ListOptions{Recursive: true}}, |
||||||
|
list: checks(listSize(2), listHasMore(false), listLastPath("/folder1/folder2/file.jpg")), |
||||||
|
files: [][]interface{}{ |
||||||
|
checks(fPath("/folder1/file-inner.jpg")), |
||||||
|
checks(fPath("/folder1/folder2/file.jpg")), |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "listing files with prefix filter", |
||||||
|
steps: []interface{}{ |
||||||
|
cmdUpsert{ |
||||||
|
cmd: UpsertFileCommand{ |
||||||
|
Path: "/folder1/folder2/file.jpg", |
||||||
|
Contents: &[]byte{}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
cmdUpsert{ |
||||||
|
cmd: UpsertFileCommand{ |
||||||
|
Path: "/folder1/file-inner.jpg", |
||||||
|
Contents: &[]byte{}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
queryListFiles{ |
||||||
|
input: queryListFilesInput{path: "/folder1", options: &ListOptions{Recursive: true, PathFilters: PathFilters{allowedPrefixes: []string{"/folder2"}}}}, |
||||||
|
list: checks(listSize(0), listHasMore(false), listLastPath("")), |
||||||
|
}, |
||||||
|
queryListFiles{ |
||||||
|
input: queryListFilesInput{path: "/folder1", options: &ListOptions{Recursive: true, PathFilters: PathFilters{allowedPrefixes: []string{"/folder1/folder"}}}}, |
||||||
|
list: checks(listSize(1), listHasMore(false)), |
||||||
|
files: [][]interface{}{ |
||||||
|
checks(fPath("/folder1/folder2/file.jpg")), |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "listing files with pagination", |
||||||
|
steps: []interface{}{ |
||||||
|
cmdUpsert{ |
||||||
|
cmd: UpsertFileCommand{ |
||||||
|
Path: "/folder1/a", |
||||||
|
Contents: &[]byte{}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
cmdUpsert{ |
||||||
|
cmd: UpsertFileCommand{ |
||||||
|
Path: "/folder1/b", |
||||||
|
Contents: &[]byte{}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
cmdUpsert{ |
||||||
|
cmd: UpsertFileCommand{ |
||||||
|
Path: "/folder2/c", |
||||||
|
Contents: &[]byte{}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
queryListFiles{ |
||||||
|
input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true}, paging: &Paging{First: 1, After: ""}}, |
||||||
|
list: checks(listSize(1), listHasMore(true), listLastPath("/folder1/a")), |
||||||
|
files: [][]interface{}{ |
||||||
|
checks(fPath("/folder1/a")), |
||||||
|
}, |
||||||
|
}, |
||||||
|
queryListFiles{ |
||||||
|
input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true}, paging: &Paging{First: 1, After: "/folder1/a"}}, |
||||||
|
list: checks(listSize(1), listHasMore(true), listLastPath("/folder1/b")), |
||||||
|
files: [][]interface{}{ |
||||||
|
checks(fPath("/folder1/b")), |
||||||
|
}, |
||||||
|
}, |
||||||
|
queryListFiles{ |
||||||
|
input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true}, paging: &Paging{First: 1, After: "/folder1/b"}}, |
||||||
|
list: checks(listSize(1), listHasMore(false), listLastPath("/folder2/c")), |
||||||
|
files: [][]interface{}{ |
||||||
|
checks(fPath("/folder2/c")), |
||||||
|
}, |
||||||
|
}, |
||||||
|
queryListFiles{ |
||||||
|
input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true}, paging: &Paging{First: 5, After: ""}}, |
||||||
|
list: checks(listSize(3), listHasMore(false), listLastPath("/folder2/c")), |
||||||
|
files: [][]interface{}{ |
||||||
|
checks(fPath("/folder1/a")), |
||||||
|
checks(fPath("/folder1/b")), |
||||||
|
checks(fPath("/folder2/c")), |
||||||
|
}, |
||||||
|
}, |
||||||
|
queryListFiles{ |
||||||
|
input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true}, paging: &Paging{First: 5, After: "/folder2"}}, |
||||||
|
list: checks(listSize(1), listHasMore(false)), |
||||||
|
}, |
||||||
|
queryListFiles{ |
||||||
|
input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true}, paging: &Paging{First: 5, After: "/folder2/c"}}, |
||||||
|
list: checks(listSize(0), listHasMore(false)), |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "listing folders", |
||||||
|
steps: []interface{}{ |
||||||
|
cmdUpsert{ |
||||||
|
cmd: UpsertFileCommand{ |
||||||
|
Path: "/folder1/folder2/file.jpg", |
||||||
|
Contents: &[]byte{}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
cmdUpsert{ |
||||||
|
cmd: UpsertFileCommand{ |
||||||
|
Path: "/folder1/file-inner.jpg", |
||||||
|
Contents: &[]byte{}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
cmdUpsert{ |
||||||
|
cmd: UpsertFileCommand{ |
||||||
|
Path: "/folderX/folderZ/file.txt", |
||||||
|
Contents: &[]byte{}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
cmdUpsert{ |
||||||
|
cmd: UpsertFileCommand{ |
||||||
|
Path: "/folderA/folderB/file.txt", |
||||||
|
Contents: &[]byte{}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
queryListFolders{ |
||||||
|
input: queryListFoldersInput{path: "/", options: &ListOptions{Recursive: true}}, |
||||||
|
checks: [][]interface{}{ |
||||||
|
checks(fPath("/folder1")), |
||||||
|
checks(fPath("/folder1/folder2")), |
||||||
|
checks(fPath("/folderA")), |
||||||
|
checks(fPath("/folderA/folderB")), |
||||||
|
checks(fPath("/folderX")), |
||||||
|
checks(fPath("/folderX/folderZ")), |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "creating and deleting folders", |
||||||
|
steps: []interface{}{ |
||||||
|
cmdUpsert{ |
||||||
|
cmd: UpsertFileCommand{ |
||||||
|
Path: "/folder1/folder2/file.jpg", |
||||||
|
Contents: &[]byte{}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
cmdCreateFolder{ |
||||||
|
path: "/folder/dashboards", |
||||||
|
name: "myNewFolder", |
||||||
|
}, |
||||||
|
cmdCreateFolder{ |
||||||
|
path: "/folder/icons", |
||||||
|
name: "emojis", |
||||||
|
}, |
||||||
|
queryListFolders{ |
||||||
|
input: queryListFoldersInput{path: "/", options: &ListOptions{Recursive: true}}, |
||||||
|
checks: [][]interface{}{ |
||||||
|
checks(fPath("/folder")), |
||||||
|
checks(fPath("/folder/dashboards")), |
||||||
|
checks(fPath("/folder/dashboards/myNewFolder")), |
||||||
|
checks(fPath("/folder/icons")), |
||||||
|
checks(fPath("/folder/icons/emojis")), |
||||||
|
checks(fPath("/folder1")), |
||||||
|
checks(fPath("/folder1/folder2")), |
||||||
|
}, |
||||||
|
}, |
||||||
|
cmdDeleteFolder{ |
||||||
|
path: "/folder/dashboards/myNewFolder", |
||||||
|
}, |
||||||
|
queryListFolders{ |
||||||
|
input: queryListFoldersInput{path: "/", options: &ListOptions{Recursive: true}}, |
||||||
|
checks: [][]interface{}{ |
||||||
|
checks(fPath("/folder")), |
||||||
|
checks(fPath("/folder/icons")), |
||||||
|
checks(fPath("/folder/icons/emojis")), |
||||||
|
checks(fPath("/folder1")), |
||||||
|
checks(fPath("/folder1/folder2")), |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "should not be able to delete folders with files", |
||||||
|
steps: []interface{}{ |
||||||
|
cmdCreateFolder{ |
||||||
|
path: "/folder/dashboards", |
||||||
|
name: "myNewFolder", |
||||||
|
}, |
||||||
|
cmdUpsert{ |
||||||
|
cmd: UpsertFileCommand{ |
||||||
|
Path: "/folder/dashboards/myNewFolder/file.jpg", |
||||||
|
Contents: &[]byte{}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
cmdDeleteFolder{ |
||||||
|
path: "/folder/dashboards/myNewFolder", |
||||||
|
error: &cmdErrorOutput{ |
||||||
|
message: "folder %s is not empty - cant remove it", |
||||||
|
args: []interface{}{"/folder/dashboards/myNewFolder"}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
queryListFolders{ |
||||||
|
input: queryListFoldersInput{path: "/", options: &ListOptions{Recursive: true}}, |
||||||
|
checks: [][]interface{}{ |
||||||
|
checks(fPath("/folder")), |
||||||
|
checks(fPath("/folder/dashboards")), |
||||||
|
checks(fPath("/folder/dashboards/myNewFolder")), |
||||||
|
}, |
||||||
|
}, |
||||||
|
queryGet{ |
||||||
|
input: queryGetInput{ |
||||||
|
path: "/folder/dashboards/myNewFolder/file.jpg", |
||||||
|
}, |
||||||
|
checks: checks( |
||||||
|
fName("file.jpg"), |
||||||
|
), |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
for _, tt := range tests { |
||||||
|
t.Run(fmt.Sprintf("%s: %s", "IN MEM FS", tt.name), func(t *testing.T) { |
||||||
|
setupInMemFS() |
||||||
|
defer cleanUp() |
||||||
|
for i, step := range tt.steps { |
||||||
|
executeTestStep(t, ctx, step, i, filestorage) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
for _, tt := range tests { |
||||||
|
t.Run(fmt.Sprintf("%s: %s", "SQL FS", tt.name), func(t *testing.T) { |
||||||
|
setupSqlFS() |
||||||
|
defer cleanUp() |
||||||
|
for i, step := range tt.steps { |
||||||
|
executeTestStep(t, ctx, step, i, filestorage) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
for _, tt := range tests { |
||||||
|
t.Run(fmt.Sprintf("%s: %s", "Local FS", tt.name), func(t *testing.T) { |
||||||
|
if tt.name == "listing files with pagination" { |
||||||
|
// bug in cdk fileblob
|
||||||
|
return |
||||||
|
} |
||||||
|
setupLocalFs() |
||||||
|
defer cleanUp() |
||||||
|
for i, step := range tt.steps { |
||||||
|
executeTestStep(t, ctx, step, i, filestorage) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
@ -1,74 +0,0 @@ |
|||||||
//go:build integration
|
|
||||||
// +build integration
|
|
||||||
|
|
||||||
package filestorage |
|
||||||
|
|
||||||
import ( |
|
||||||
"context" |
|
||||||
"testing" |
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log" |
|
||||||
"github.com/stretchr/testify/require" |
|
||||||
"gocloud.dev/blob" |
|
||||||
) |
|
||||||
|
|
||||||
func TestLocalFsCdkBlobStorage(t *testing.T) { |
|
||||||
|
|
||||||
var filestorage FileStorage |
|
||||||
var ctx context.Context |
|
||||||
|
|
||||||
setup := func() { |
|
||||||
bucket, _ := blob.OpenBucket(context.Background(), "file://./test_fs") |
|
||||||
testLogger := log.New("testFsStorageLogger") |
|
||||||
filestorage = NewCdkBlobStorage(testLogger, bucket, "", nil) |
|
||||||
ctx = context.Background() |
|
||||||
} |
|
||||||
|
|
||||||
t.Run("Should be able to list folders", func(t *testing.T) { |
|
||||||
setup() |
|
||||||
folders, err := filestorage.ListFolders(ctx, "/", nil) |
|
||||||
require.NoError(t, err) |
|
||||||
require.Equal(t, []FileMetadata{ |
|
||||||
{ |
|
||||||
Name: "folderA", |
|
||||||
FullPath: "/folderA", |
|
||||||
}, |
|
||||||
{ |
|
||||||
Name: "folderAnestedA", |
|
||||||
FullPath: "/folderA/folderAnestedA", |
|
||||||
}, |
|
||||||
}, folders) |
|
||||||
|
|
||||||
folders, err = filestorage.ListFolders(ctx, "/folderA", nil) |
|
||||||
require.NoError(t, err) |
|
||||||
require.Equal(t, []FileMetadata{ |
|
||||||
{ |
|
||||||
Name: "folderAnestedA", |
|
||||||
FullPath: "/folderA/folderAnestedA", |
|
||||||
}, |
|
||||||
}, folders) |
|
||||||
|
|
||||||
}) |
|
||||||
|
|
||||||
t.Run("Should be able to list files", func(t *testing.T) { |
|
||||||
setup() |
|
||||||
res, err := filestorage.ListFiles(ctx, "/", nil, &ListOptions{Recursive: true}) |
|
||||||
require.NoError(t, err) |
|
||||||
|
|
||||||
require.Equal(t, []NameFullPath{ |
|
||||||
{Name: "file.txt", FullPath: "/folderA/folderAnestedA/file.txt"}, |
|
||||||
{Name: "rootFile.txt", FullPath: "/rootFile.txt"}, |
|
||||||
}, extractNameFullPath(res.Files)) |
|
||||||
}) |
|
||||||
|
|
||||||
t.Run("should be able to read file", func(t *testing.T) { |
|
||||||
setup() |
|
||||||
path := "/folderA/folderAnestedA/file.txt" |
|
||||||
res, err := filestorage.Get(ctx, path) |
|
||||||
require.NoError(t, err) |
|
||||||
|
|
||||||
require.Equal(t, res.FullPath, path) |
|
||||||
require.Equal(t, res.Name, "file.txt") |
|
||||||
require.Equal(t, "content\n", string(res.Contents)) |
|
||||||
}) |
|
||||||
} |
|
@ -0,0 +1,335 @@ |
|||||||
|
//go:build integration
|
||||||
|
// +build integration
|
||||||
|
|
||||||
|
package filestorage |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"fmt" |
||||||
|
"reflect" |
||||||
|
"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 |
||||||
|
name string |
||||||
|
error *cmdErrorOutput |
||||||
|
} |
||||||
|
|
||||||
|
type cmdDeleteFolder struct { |
||||||
|
path string |
||||||
|
error *cmdErrorOutput |
||||||
|
} |
||||||
|
|
||||||
|
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, c.name) |
||||||
|
if c.error == nil { |
||||||
|
require.NoError(t, err, "%s: should be able to create folder %s in %s", cmdName, c.name, c.path) |
||||||
|
} |
||||||
|
expectedErr = c.error |
||||||
|
case cmdDeleteFolder: |
||||||
|
err = fs.DeleteFolder(ctx, c.path) |
||||||
|
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 checks == nil || 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 ListFilesResponse: |
||||||
|
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", stepName, path) |
||||||
|
case listHasMoreCheck: |
||||||
|
require.Equal(t, c.v, o.HasMore, "%s %s", stepName, path) |
||||||
|
case listLastPathCheck: |
||||||
|
require.Equal(t, c.v, o.LastPath, "%s %s", stepName, path) |
||||||
|
default: |
||||||
|
t.Fatalf("unrecognized list check %s", checkName) |
||||||
|
} |
||||||
|
} |
||||||
|
default: |
||||||
|
t.Fatalf("unrecognized output %s", interfaceName(output)) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
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, inputPath, 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.ListFiles(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 %s", queryName, inputPath) |
||||||
|
for i, file := range resp.Files { |
||||||
|
runChecks(t, queryName, inputPath, file, q.files[i]) |
||||||
|
} |
||||||
|
} |
||||||
|
case queryListFolders: |
||||||
|
inputPath := q.input.path |
||||||
|
resp, err := fs.ListFolders(ctx, inputPath, q.input.options) |
||||||
|
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), len(q.checks), "%s %s", queryName, inputPath) |
||||||
|
for i, file := range resp { |
||||||
|
runChecks(t, queryName, inputPath, file, q.checks[i]) |
||||||
|
} |
||||||
|
} else { |
||||||
|
require.Equal(t, 0, len(resp), "%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) |
||||||
|
} |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue