storj/pkg/miniogw/gateway-storj_test.go
Kaloyan Raev 99640225fd
Refactor Path type (#522)
The old paths.Path type is now replaced with the new storj.Path.

storj.Path is simply an alias to the built-in string type. As such it can be used just as any string, which simplifies a lot working with paths. No more conversions paths.New and path.String().

As an alias storj.Path does not define any methods. However, any functions applying to strings (like those from the strings package) gracefully apply to storj.Path too. In addition we have a few more functions defined:

    storj.SplitPath
    storj.JoinPaths
    encryption.EncryptPath
    encryption.DecryptPath
    encryption.DerivePathKey
    encryption.DeriveContentKey

All code in master is migrated to the new storj.Path type.

The Path example is also updated and is good for reference: /pkg/encryption/examples_test.go

This PR also resolve a nonce misuse issue in path encryption: https://storjlabs.atlassian.net/browse/V3-545
2018-10-25 23:28:16 +03:00

558 lines
16 KiB
Go

// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package miniogw
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"testing"
"time"
"github.com/golang/mock/gomock"
minio "github.com/minio/minio/cmd"
"github.com/minio/minio/pkg/hash"
"github.com/stretchr/testify/assert"
"storj.io/storj/pkg/ranger"
"storj.io/storj/pkg/storage/buckets"
mock_buckets "storj.io/storj/pkg/storage/buckets/mocks"
"storj.io/storj/pkg/storage/meta"
"storj.io/storj/pkg/storage/objects"
"storj.io/storj/storage"
)
var (
ctx = context.Background()
)
func TestCopyObject(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockBS := mock_buckets.NewMockStore(ctrl)
b := Storj{bs: mockBS}
mockOS := NewMockStore(ctrl)
storjObj := storjObjects{storj: &b}
for i, example := range []struct {
bucket, srcObject string
destObject string
data string
getErr error
putErr error
errString string
}{
// happy scenario
{"mybucket", "mySrcObj", "myDestObj", "abcdef", nil, nil, ""},
// error returned by the objects.Get()
{"mybucket", "mySrcObj", "myDestObj", "abcdef", errors.New("some Get err"), nil, "some Get err"},
// error returned by the objects.Put()
{"mybucket", "mySrcObj", "myDestObj", "abcdef", nil, errors.New("some Put err"), "some Put err"},
} {
errTag := fmt.Sprintf("Test case #%d", i)
metadata := map[string]string{
"content-type": "media/foo",
"userdef_key1": "userdef_val1",
"userdef_key2": "userdef_val2",
}
serMeta := objects.SerializableMeta{
ContentType: metadata["content-type"],
UserDefined: map[string]string{
"userdef_key1": metadata["userdef_key1"],
"userdef_key2": metadata["userdef_key2"],
},
}
meta := objects.Meta{
SerializableMeta: serMeta,
Modified: time.Time{},
Expiration: time.Time{},
Size: 1234,
Checksum: "test-checksum",
}
srcInfo := minio.ObjectInfo{
Bucket: example.bucket,
Name: example.srcObject,
Size: 1234,
ContentType: serMeta.ContentType,
UserDefined: serMeta.UserDefined,
}
rr := ranger.ByteRanger([]byte(example.data))
r, err := rr.Range(ctx, 0, rr.Size())
if err != nil {
t.Fatal(err)
}
// if o.Get returns an error, only expect GetObjectStore once, do not expect Put
if example.errString != "some Get err" {
mockBS.EXPECT().GetObjectStore(gomock.Any(), example.bucket).Return(mockOS, nil).Times(2)
mockOS.EXPECT().Get(gomock.Any(), example.srcObject).Return(rr, meta, example.getErr)
mockOS.EXPECT().Put(gomock.Any(), example.destObject, r, serMeta, time.Time{}).Return(meta, example.putErr)
} else {
mockBS.EXPECT().GetObjectStore(gomock.Any(), example.bucket).Return(mockOS, nil)
mockOS.EXPECT().Get(gomock.Any(), example.srcObject).Return(rr, meta, example.getErr)
}
objInfo, err := storjObj.CopyObject(ctx, example.bucket, example.srcObject, example.bucket, example.destObject, srcInfo)
if err != nil {
assert.EqualError(t, err, example.errString, errTag)
} else {
assert.NoError(t, err, errTag)
assert.NotNil(t, objInfo, errTag)
assert.Equal(t, example.bucket, objInfo.Bucket, errTag)
assert.Equal(t, example.destObject, objInfo.Name, errTag)
assert.Equal(t, meta.Modified, objInfo.ModTime, errTag)
assert.Equal(t, meta.Size, objInfo.Size, errTag)
assert.Equal(t, meta.Checksum, objInfo.ETag, errTag)
assert.Equal(t, meta.UserDefined, objInfo.UserDefined, errTag)
}
}
}
func TestGetObject(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockBS := mock_buckets.NewMockStore(ctrl)
b := Storj{bs: mockBS}
mockOS := NewMockStore(ctrl)
storjObj := storjObjects{storj: &b}
meta := objects.Meta{}
for i, example := range []struct {
bucket, object string
data string
offset, length int64
substr string
err error
errString string
}{
// happy scenario
{"mybucket", "myobject1", "abcdef", 0, 5, "abcde", nil, ""},
// error returned by the ranger in the code
{"mybucket", "myobject1", "abcdef", -1, 7, "abcde", nil, "ranger error: negative offset"},
{"mybucket", "myobject1", "abcdef", 0, -1, "abcdef", nil, ""},
{"mybucket", "myobject1", "abcdef", 0, -2, "abcde", nil, "ranger error: negative length"},
{"mybucket", "myobject1", "abcdef", 1, 7, "bcde", nil, "ranger error: buffer runoff"},
// error returned by the objects.Get()
{"mybucket", "myobject1", "abcdef", 0, 6, "abcdef", errors.New("some err"), "some err"},
} {
errTag := fmt.Sprintf("Test case #%d", i)
rr := ranger.ByteRanger([]byte(example.data))
mockBS.EXPECT().GetObjectStore(gomock.Any(), example.bucket).Return(mockOS, nil)
mockOS.EXPECT().Get(gomock.Any(), example.object).Return(rr, meta, example.err)
var buf bytes.Buffer
iowriter := io.Writer(&buf)
err := storjObj.GetObject(ctx, example.bucket, example.object, example.offset, example.length, iowriter, "etag")
if err != nil {
assert.EqualError(t, err, example.errString, errTag)
} else {
assert.Equal(t, example.substr, buf.String(), errTag)
assert.NoError(t, err, errTag)
}
}
}
func TestDeleteObject(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockBS := mock_buckets.NewMockStore(ctrl)
b := Storj{bs: mockBS}
mockOS := NewMockStore(ctrl)
storjObj := storjObjects{storj: &b}
for i, example := range []struct {
bucket, object string
err error
errString string
}{
// happy scenario
{"mybucket", "myobject1", nil, ""},
} {
errTag := fmt.Sprintf("Test case #%d", i)
mockBS.EXPECT().GetObjectStore(gomock.Any(), example.bucket).Return(mockOS, nil)
mockOS.EXPECT().Delete(gomock.Any(), example.object).Return(example.err)
err := storjObj.DeleteObject(ctx, example.bucket, example.object)
assert.NoError(t, err, errTag)
}
}
func TestPutObject(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockBS := mock_buckets.NewMockStore(ctrl)
b := Storj{bs: mockBS}
mockOS := NewMockStore(ctrl)
storjObj := storjObjects{storj: &b}
data, err := hash.NewReader(bytes.NewReader([]byte("abcdefgiiuweriiwyrwyiywrywhti")),
int64(len("abcdefgiiuweriiwyrwyiywrywhti")),
"e2fc714c4727ee9395f324cd2e7f331f",
"88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589")
if err != nil {
t.Fatal(err)
}
for i, example := range []struct {
bucket, object string
err error // used by mock function
errString string
}{
// happy scenario
{"mybucket", "myobject1", nil, ""},
// emulating objects.Put() returning err
{"mybucket", "myobject1", Error.New("some non nil error"), "Storj Gateway error: some non nil error"},
} {
errTag := fmt.Sprintf("Test case #%d", i)
metadata := map[string]string{
"content-type": "media/foo",
"userdef_key1": "userdef_val1",
"userdef_key2": "userdef_val2",
}
serMeta := objects.SerializableMeta{
ContentType: metadata["content-type"],
UserDefined: map[string]string{
"userdef_key1": metadata["userdef_key1"],
"userdef_key2": metadata["userdef_key2"],
},
}
meta := objects.Meta{
SerializableMeta: serMeta,
Modified: time.Now(),
Expiration: time.Time{},
Size: 1234,
Checksum: "test-checksum",
}
mockBS.EXPECT().GetObjectStore(gomock.Any(), example.bucket).Return(mockOS, nil)
mockOS.EXPECT().Put(gomock.Any(), example.object, data, serMeta, time.Time{}).Return(meta, example.err)
objInfo, err := storjObj.PutObject(ctx, example.bucket, example.object, data, metadata)
if err != nil {
assert.EqualError(t, err, example.errString, errTag)
} else {
assert.NoError(t, err, errTag)
}
assert.NotNil(t, objInfo, errTag)
assert.Equal(t, example.bucket, objInfo.Bucket, errTag)
assert.Equal(t, example.object, objInfo.Name, errTag)
assert.Equal(t, meta.Modified, objInfo.ModTime, errTag)
assert.Equal(t, meta.Size, objInfo.Size, errTag)
assert.Equal(t, meta.Checksum, objInfo.ETag, errTag)
assert.Equal(t, meta.UserDefined, objInfo.UserDefined, errTag)
}
}
func TestGetObjectInfo(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockBS := mock_buckets.NewMockStore(ctrl)
b := Storj{bs: mockBS}
mockOS := NewMockStore(ctrl)
storjObj := storjObjects{storj: &b}
meta := objects.Meta{
Modified: time.Now(),
Expiration: time.Time{},
Size: 1234,
Checksum: "test-checksum",
SerializableMeta: objects.SerializableMeta{
ContentType: "media/foo",
UserDefined: map[string]string{
"userdef_key1": "userdef_val1",
"userdef_key2": "userdef_val2",
},
},
}
for i, example := range []struct {
bucket, object string
err error
errString string
}{
// happy scenario
{"mybucket", "myobject1", nil, ""},
// mock object.Meta function to return error
{"mybucket", "myobject1", Error.New("mock error"), "Storj Gateway error: mock error"},
} {
errTag := fmt.Sprintf("Test case #%d", i)
mockBS.EXPECT().GetObjectStore(gomock.Any(), example.bucket).Return(mockOS, nil)
mockOS.EXPECT().Meta(gomock.Any(), example.object).Return(meta, example.err)
objInfo, err := storjObj.GetObjectInfo(ctx, example.bucket, example.object)
if err != nil {
assert.EqualError(t, err, example.errString, errTag)
if example.err != nil {
assert.Empty(t, objInfo, errTag)
}
} else {
assert.NoError(t, err, errTag)
assert.NotNil(t, objInfo, errTag)
assert.Equal(t, example.bucket, objInfo.Bucket, errTag)
assert.Equal(t, example.object, objInfo.Name, errTag)
assert.Equal(t, meta.Modified, objInfo.ModTime, errTag)
assert.Equal(t, meta.Size, objInfo.Size, errTag)
assert.Equal(t, meta.Checksum, objInfo.ETag, errTag)
assert.Equal(t, meta.UserDefined, objInfo.UserDefined, errTag)
}
}
}
func TestListObjects(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockBS := mock_buckets.NewMockStore(ctrl)
b := Storj{bs: mockBS}
mockOS := NewMockStore(ctrl)
storjObj := storjObjects{storj: &b}
bucket := "test-bucket"
prefix := "test-prefix"
maxKeys := 123
items := []objects.ListItem{
{Path: "test-file-1.txt"},
{Path: "test-file-2.txt"},
}
for i, example := range []struct {
more bool
startAfter string
nextMarker string
delimiter string
recursive bool
objInfos []minio.ObjectInfo
err error
errString string
}{
{
more: false, startAfter: "", nextMarker: "", delimiter: "", recursive: true,
objInfos: []minio.ObjectInfo{
{Bucket: bucket, Name: "test-prefix/test-file-1.txt"},
{Bucket: bucket, Name: "test-prefix/test-file-2.txt"},
}, err: nil, errString: "",
},
{
more: true, startAfter: "test-start-after", nextMarker: "test-file-2.txt", delimiter: "/", recursive: false,
objInfos: []minio.ObjectInfo{
{Bucket: bucket, Name: "test-file-1.txt"},
{Bucket: bucket, Name: "test-file-2.txt"},
}, err: nil, errString: "",
},
{
more: false, startAfter: "", nextMarker: "", delimiter: "", recursive: true,
objInfos: []minio.ObjectInfo{
{Bucket: bucket, Name: "test-prefix/test-file-1.txt"},
{Bucket: bucket, Name: "test-prefix/test-file-2.txt"},
}, err: Error.New("error"), errString: "Storj Gateway error: error",
},
} {
errTag := fmt.Sprintf("Test case #%d", i)
mockBS.EXPECT().GetObjectStore(gomock.Any(), bucket).Return(mockOS, nil)
mockOS.EXPECT().List(gomock.Any(), prefix, example.startAfter, "", example.recursive, maxKeys, meta.All).Return(items, example.more, example.err)
listInfo, err := storjObj.ListObjects(ctx, bucket, prefix, example.startAfter, example.delimiter, maxKeys)
if err != nil {
assert.EqualError(t, err, example.errString, errTag)
if example.err != nil {
assert.Empty(t, listInfo, errTag)
}
} else {
assert.NoError(t, err, errTag)
assert.NotNil(t, listInfo, errTag)
assert.Equal(t, example.more, listInfo.IsTruncated, errTag)
assert.Equal(t, example.nextMarker, listInfo.NextMarker, errTag)
assert.Equal(t, example.objInfos, listInfo.Objects, errTag)
assert.Nil(t, listInfo.Prefixes, errTag)
}
}
}
func TestDeleteBucket(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockOS := NewMockStore(ctrl)
mockBS := mock_buckets.NewMockStore(ctrl)
b := Storj{bs: mockBS}
storjObj := storjObjects{storj: &b}
itemsInBucket := make([]objects.ListItem, 1)
itemsInBucket[0] = objects.ListItem{Path: "path1", Meta: objects.Meta{}}
exp := time.Unix(0, 0).UTC()
var noItemsInBucket []objects.ListItem
for i, example := range []struct {
bucket string
items []objects.ListItem
bucketStatus error
err error
errString string
}{
{"mybucket", noItemsInBucket, nil, nil, ""},
{"mybucket", noItemsInBucket, storage.ErrKeyNotFound.New("mybucket"), nil, "Bucket not found: mybucket"},
{"mybucket", itemsInBucket, nil, minio.BucketNotEmpty{Bucket: "mybucket"}, "Bucket not empty: mybucket"},
} {
errTag := fmt.Sprintf("Test case #%d", i)
mockBS.EXPECT().Get(gomock.Any(), gomock.Any()).Return(buckets.Meta{Created: exp}, example.bucketStatus)
if !storage.ErrKeyNotFound.Has(example.bucketStatus) {
mockBS.EXPECT().GetObjectStore(gomock.Any(), example.bucket).Return(mockOS, nil)
mockOS.EXPECT().List(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
gomock.Any(), gomock.Any(), gomock.Any()).Return(example.items, false, example.err)
if len(example.items) == 0 {
mockBS.EXPECT().Delete(gomock.Any(), example.bucket).Return(example.err)
}
}
err := storjObj.DeleteBucket(ctx, example.bucket)
if err != nil {
assert.EqualError(t, err, example.errString, errTag)
} else {
assert.NoError(t, err, errTag)
}
}
}
func TestGetBucketInfo(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockBS := mock_buckets.NewMockStore(ctrl)
b := Storj{bs: mockBS}
storjObj := storjObjects{storj: &b}
exp := time.Unix(0, 0).UTC()
for i, example := range []struct {
bucket string
meta time.Time
err error
errString string
}{
// happy scenario
{"mybucket", exp, nil, ""},
} {
errTag := fmt.Sprintf("Test case #%d", i)
mockBS.EXPECT().Get(gomock.Any(), example.bucket).Return(buckets.Meta{Created: example.meta}, example.err)
_, err := storjObj.GetBucketInfo(ctx, example.bucket)
assert.NoError(t, err, errTag)
}
}
func TestMakeBucketWithLocation(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockBS := mock_buckets.NewMockStore(ctrl)
b := Storj{bs: mockBS}
storjObj := storjObjects{storj: &b}
exp := time.Unix(0, 0).UTC()
for i, example := range []struct {
bucket string
meta time.Time
retErr error
bucketStatus error
}{
{"mybucket", exp, minio.BucketAlreadyExists{Bucket: "mybucket"}, nil},
{"mybucket", exp, nil, storage.ErrKeyNotFound.New("mybucket")},
} {
errTag := fmt.Sprintf("Test case #%d", i)
mockBS.EXPECT().Get(gomock.Any(), gomock.Any()).Return(buckets.Meta{Created: exp}, example.bucketStatus)
if storage.ErrKeyNotFound.Has(example.bucketStatus) {
mockBS.EXPECT().Put(gomock.Any(), example.bucket).Return(buckets.Meta{Created: example.meta}, nil)
}
err := storjObj.MakeBucketWithLocation(ctx, example.bucket, "location")
if example.retErr != nil {
assert.NotNil(t, err, errTag)
assert.Equal(t, example.retErr, err, errTag)
} else {
assert.Nil(t, err, errTag)
}
}
}
func TestListBuckets(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockBS := mock_buckets.NewMockStore(ctrl)
b := Storj{bs: mockBS}
storjObj := storjObjects{storj: &b}
exp := time.Unix(0, 0).UTC()
for i, example := range []struct {
bucket string
meta time.Time
more bool
err error
errString string
}{
// happy scenario
{"mybucket", exp, false, nil, ""},
{"mybucket", exp, true, nil, ""},
} {
errTag := fmt.Sprintf("Test case #%d", i)
b := make([]buckets.ListItem, 5)
for i, item := range b {
item.Bucket = fmt.Sprintf("bucket %d", i)
item.Meta = buckets.Meta{Created: exp}
}
mockBS.EXPECT().List(gomock.Any(), gomock.Any(), gomock.Any(), 0).Return(b, example.more, example.err).AnyTimes()
_, err := storjObj.ListBuckets(ctx)
assert.NoError(t, err, errTag)
}
}