438 lines
11 KiB
Go
438 lines
11 KiB
Go
package gcsstore_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"cloud.google.com/go/storage"
|
|
"github.com/golang/mock/gomock"
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/tus/tusd/v2/pkg/gcsstore"
|
|
"github.com/tus/tusd/v2/pkg/handler"
|
|
)
|
|
|
|
//go:generate mockgen -destination=./gcsstore_mock_test.go -package=gcsstore_test github.com/tus/tusd/pkg/gcsstore GCSReader,GCSAPI
|
|
|
|
const mockID = "123456789abcdefghijklmnopqrstuvwxyz"
|
|
const mockBucket = "bucket"
|
|
const mockSize = 1337
|
|
const mockReaderData = "helloworld"
|
|
|
|
var mockTusdInfoJson = fmt.Sprintf(`{"ID":"%s","Size":%d,"MetaData":{"foo":"bar"},"Storage":{"Bucket":"bucket","Key":"%s","Type":"gcsstore"}}`, mockID, mockSize, mockID)
|
|
var mockTusdInfo = handler.FileInfo{
|
|
ID: mockID,
|
|
Size: mockSize,
|
|
MetaData: map[string]string{
|
|
"foo": "bar",
|
|
},
|
|
Storage: map[string]string{
|
|
"Type": "gcsstore",
|
|
"Bucket": mockBucket,
|
|
"Key": mockID,
|
|
},
|
|
}
|
|
|
|
var mockPartial0 = fmt.Sprintf("%s_0", mockID)
|
|
var mockPartial1 = fmt.Sprintf("%s_1", mockID)
|
|
var mockPartial2 = fmt.Sprintf("%s_2", mockID)
|
|
var mockPartials = []string{mockPartial0, mockPartial1, mockPartial2}
|
|
|
|
func TestNewUpload(t *testing.T) {
|
|
mockCtrl := gomock.NewController(t)
|
|
defer mockCtrl.Finish()
|
|
assert := assert.New(t)
|
|
|
|
service := NewMockGCSAPI(mockCtrl)
|
|
store := gcsstore.New(mockBucket, service)
|
|
|
|
assert.Equal(store.Bucket, mockBucket)
|
|
|
|
data, err := json.Marshal(mockTusdInfo)
|
|
assert.Nil(err)
|
|
|
|
r := bytes.NewReader(data)
|
|
|
|
params := gcsstore.GCSObjectParams{
|
|
Bucket: store.Bucket,
|
|
ID: fmt.Sprintf("%s.info", mockID),
|
|
}
|
|
|
|
ctx := context.Background()
|
|
service.EXPECT().WriteObject(ctx, params, r).Return(int64(r.Len()), nil)
|
|
|
|
upload, err := store.NewUpload(context.Background(), mockTusdInfo)
|
|
assert.Nil(err)
|
|
assert.NotNil(upload)
|
|
}
|
|
|
|
func TestNewUploadWithPrefix(t *testing.T) {
|
|
mockCtrl := gomock.NewController(t)
|
|
defer mockCtrl.Finish()
|
|
assert := assert.New(t)
|
|
|
|
service := NewMockGCSAPI(mockCtrl)
|
|
store := gcsstore.New(mockBucket, service)
|
|
store.ObjectPrefix = "/path/to/file"
|
|
|
|
assert.Equal(store.Bucket, mockBucket)
|
|
|
|
info := mockTusdInfo
|
|
info.Storage = map[string]string{
|
|
"Type": "gcsstore",
|
|
"Bucket": mockBucket,
|
|
"Key": "/path/to/file/" + mockID,
|
|
}
|
|
data, err := json.Marshal(info)
|
|
assert.Nil(err)
|
|
|
|
r := bytes.NewReader(data)
|
|
|
|
params := gcsstore.GCSObjectParams{
|
|
Bucket: store.Bucket,
|
|
ID: fmt.Sprintf("%s.info", "/path/to/file/"+mockID),
|
|
}
|
|
|
|
ctx := context.Background()
|
|
service.EXPECT().WriteObject(ctx, params, r).Return(int64(r.Len()), nil)
|
|
|
|
upload, err := store.NewUpload(context.Background(), mockTusdInfo)
|
|
assert.Nil(err)
|
|
assert.NotNil(upload)
|
|
}
|
|
|
|
type MockGetInfoReader struct{}
|
|
|
|
func (r MockGetInfoReader) Close() error {
|
|
return nil
|
|
}
|
|
|
|
func (r MockGetInfoReader) ContentType() string {
|
|
return "text/plain; charset=utf-8"
|
|
}
|
|
|
|
func (r MockGetInfoReader) Read(p []byte) (int, error) {
|
|
copy(p, mockTusdInfoJson)
|
|
return len(p), nil
|
|
}
|
|
|
|
func (r MockGetInfoReader) Remain() int64 {
|
|
return int64(len(mockTusdInfoJson))
|
|
}
|
|
|
|
func (r MockGetInfoReader) Size() int64 {
|
|
return int64(len(mockTusdInfoJson))
|
|
}
|
|
|
|
func TestGetInfo(t *testing.T) {
|
|
mockCtrl := gomock.NewController(t)
|
|
defer mockCtrl.Finish()
|
|
assert := assert.New(t)
|
|
|
|
service := NewMockGCSAPI(mockCtrl)
|
|
store := gcsstore.New(mockBucket, service)
|
|
|
|
assert.Equal(store.Bucket, mockBucket)
|
|
|
|
params := gcsstore.GCSObjectParams{
|
|
Bucket: store.Bucket,
|
|
ID: fmt.Sprintf("%s.info", mockID),
|
|
}
|
|
|
|
r := MockGetInfoReader{}
|
|
|
|
filterParams := gcsstore.GCSFilterParams{
|
|
Bucket: store.Bucket,
|
|
Prefix: mockID,
|
|
}
|
|
|
|
mockObjectParams0 := gcsstore.GCSObjectParams{
|
|
Bucket: store.Bucket,
|
|
ID: mockPartial0,
|
|
}
|
|
|
|
mockObjectParams1 := gcsstore.GCSObjectParams{
|
|
Bucket: store.Bucket,
|
|
ID: mockPartial1,
|
|
}
|
|
|
|
mockObjectParams2 := gcsstore.GCSObjectParams{
|
|
Bucket: store.Bucket,
|
|
ID: mockPartial2,
|
|
}
|
|
|
|
var size int64 = 100
|
|
|
|
mockTusdInfo.Offset = 300
|
|
offsetInfoData, err := json.Marshal(mockTusdInfo)
|
|
assert.Nil(err)
|
|
|
|
infoR := bytes.NewReader(offsetInfoData)
|
|
|
|
ctx := context.Background()
|
|
gomock.InOrder(
|
|
service.EXPECT().ReadObject(ctx, params).Return(r, nil),
|
|
service.EXPECT().FilterObjects(ctx, filterParams).Return(mockPartials, nil),
|
|
)
|
|
|
|
ctxCancel, cancel := context.WithCancel(ctx)
|
|
service.EXPECT().GetObjectSize(ctxCancel, mockObjectParams0).Return(size, nil)
|
|
service.EXPECT().GetObjectSize(ctxCancel, mockObjectParams1).Return(size, nil)
|
|
lastGetObjectSize := service.EXPECT().GetObjectSize(ctxCancel, mockObjectParams2).Return(size, nil)
|
|
|
|
service.EXPECT().WriteObject(ctx, params, infoR).Return(int64(len(offsetInfoData)), nil).After(lastGetObjectSize)
|
|
|
|
upload, err := store.GetUpload(context.Background(), mockID)
|
|
assert.Nil(err)
|
|
|
|
info, err := upload.GetInfo(context.Background())
|
|
assert.Nil(err)
|
|
assert.Equal(mockTusdInfo, info)
|
|
|
|
// Cancel the context to avoid getting an error from `go vet`
|
|
cancel()
|
|
}
|
|
|
|
func TestGetInfoNotFound(t *testing.T) {
|
|
mockCtrl := gomock.NewController(t)
|
|
defer mockCtrl.Finish()
|
|
assert := assert.New(t)
|
|
|
|
service := NewMockGCSAPI(mockCtrl)
|
|
store := gcsstore.New(mockBucket, service)
|
|
|
|
params := gcsstore.GCSObjectParams{
|
|
Bucket: store.Bucket,
|
|
ID: fmt.Sprintf("%s.info", mockID),
|
|
}
|
|
|
|
ctx := context.Background()
|
|
gomock.InOrder(
|
|
service.EXPECT().ReadObject(ctx, params).Return(nil, storage.ErrObjectNotExist),
|
|
)
|
|
|
|
upload, err := store.GetUpload(context.Background(), mockID)
|
|
assert.Nil(err)
|
|
|
|
_, err = upload.GetInfo(context.Background())
|
|
assert.Equal(handler.ErrNotFound, err)
|
|
}
|
|
|
|
type MockGetReader struct{}
|
|
|
|
func (r MockGetReader) Close() error {
|
|
return nil
|
|
}
|
|
|
|
func (r MockGetReader) ContentType() string {
|
|
return "text/plain; charset=utf-8"
|
|
}
|
|
|
|
func (r MockGetReader) Read(p []byte) (int, error) {
|
|
copy(p, mockReaderData)
|
|
return len(p), nil
|
|
}
|
|
|
|
func (r MockGetReader) Remain() int64 {
|
|
return int64(len(mockReaderData))
|
|
}
|
|
|
|
func (r MockGetReader) Size() int64 {
|
|
return int64(len(mockReaderData))
|
|
}
|
|
|
|
func TestGetReader(t *testing.T) {
|
|
mockCtrl := gomock.NewController(t)
|
|
defer mockCtrl.Finish()
|
|
assert := assert.New(t)
|
|
|
|
service := NewMockGCSAPI(mockCtrl)
|
|
store := gcsstore.New(mockBucket, service)
|
|
|
|
assert.Equal(store.Bucket, mockBucket)
|
|
|
|
params := gcsstore.GCSObjectParams{
|
|
Bucket: store.Bucket,
|
|
ID: mockID,
|
|
}
|
|
|
|
r := MockGetReader{}
|
|
|
|
ctx := context.Background()
|
|
service.EXPECT().ReadObject(ctx, params).Return(r, nil)
|
|
|
|
upload, err := store.GetUpload(context.Background(), mockID)
|
|
assert.Nil(err)
|
|
|
|
reader, err := upload.GetReader(context.Background())
|
|
assert.Nil(err)
|
|
|
|
buf := make([]byte, len(mockReaderData))
|
|
_, err = reader.Read(buf)
|
|
|
|
assert.Nil(err)
|
|
assert.Equal(mockReaderData, string(buf[:]))
|
|
}
|
|
|
|
func TestTerminate(t *testing.T) {
|
|
mockCtrl := gomock.NewController(t)
|
|
defer mockCtrl.Finish()
|
|
assert := assert.New(t)
|
|
|
|
service := NewMockGCSAPI(mockCtrl)
|
|
store := gcsstore.New(mockBucket, service)
|
|
|
|
assert.Equal(store.Bucket, mockBucket)
|
|
|
|
filterParams := gcsstore.GCSFilterParams{
|
|
Bucket: store.Bucket,
|
|
Prefix: mockID,
|
|
}
|
|
|
|
ctx := context.Background()
|
|
service.EXPECT().DeleteObjectsWithFilter(ctx, filterParams).Return(nil)
|
|
|
|
upload, err := store.GetUpload(context.Background(), mockID)
|
|
assert.Nil(err)
|
|
|
|
err = store.AsTerminatableUpload(upload).Terminate(context.Background())
|
|
assert.Nil(err)
|
|
}
|
|
|
|
func TestFinishUpload(t *testing.T) {
|
|
mockCtrl := gomock.NewController(t)
|
|
defer mockCtrl.Finish()
|
|
assert := assert.New(t)
|
|
|
|
service := NewMockGCSAPI(mockCtrl)
|
|
store := gcsstore.New(mockBucket, service)
|
|
|
|
assert.Equal(store.Bucket, mockBucket)
|
|
|
|
filterParams := gcsstore.GCSFilterParams{
|
|
Bucket: store.Bucket,
|
|
Prefix: fmt.Sprintf("%s_", mockID),
|
|
}
|
|
|
|
filterParams2 := gcsstore.GCSFilterParams{
|
|
Bucket: store.Bucket,
|
|
Prefix: mockID,
|
|
}
|
|
|
|
composeParams := gcsstore.GCSComposeParams{
|
|
Bucket: store.Bucket,
|
|
Destination: mockID,
|
|
Sources: mockPartials,
|
|
}
|
|
|
|
infoParams := gcsstore.GCSObjectParams{
|
|
Bucket: store.Bucket,
|
|
ID: fmt.Sprintf("%s.info", mockID),
|
|
}
|
|
|
|
r := MockGetInfoReader{}
|
|
|
|
mockObjectParams0 := gcsstore.GCSObjectParams{
|
|
Bucket: store.Bucket,
|
|
ID: mockPartial0,
|
|
}
|
|
|
|
mockObjectParams1 := gcsstore.GCSObjectParams{
|
|
Bucket: store.Bucket,
|
|
ID: mockPartial1,
|
|
}
|
|
|
|
mockObjectParams2 := gcsstore.GCSObjectParams{
|
|
Bucket: store.Bucket,
|
|
ID: mockPartial2,
|
|
}
|
|
|
|
var size int64 = 100
|
|
|
|
mockTusdInfo.Offset = 300
|
|
offsetInfoData, err := json.Marshal(mockTusdInfo)
|
|
assert.Nil(err)
|
|
|
|
infoR := bytes.NewReader(offsetInfoData)
|
|
|
|
objectParams := gcsstore.GCSObjectParams{
|
|
Bucket: store.Bucket,
|
|
ID: mockID,
|
|
}
|
|
|
|
metadata := map[string]string{
|
|
"foo": "bar",
|
|
}
|
|
|
|
ctx := context.Background()
|
|
gomock.InOrder(
|
|
service.EXPECT().FilterObjects(ctx, filterParams).Return(mockPartials, nil),
|
|
service.EXPECT().ComposeObjects(ctx, composeParams).Return(nil),
|
|
service.EXPECT().DeleteObjectsWithFilter(ctx, filterParams).Return(nil),
|
|
service.EXPECT().ReadObject(ctx, infoParams).Return(r, nil),
|
|
service.EXPECT().FilterObjects(ctx, filterParams2).Return(mockPartials, nil),
|
|
)
|
|
|
|
ctxCancel, cancel := context.WithCancel(ctx)
|
|
service.EXPECT().GetObjectSize(ctxCancel, mockObjectParams0).Return(size, nil)
|
|
service.EXPECT().GetObjectSize(ctxCancel, mockObjectParams1).Return(size, nil)
|
|
lastGetObjectSize := service.EXPECT().GetObjectSize(ctxCancel, mockObjectParams2).Return(size, nil)
|
|
|
|
writeObject := service.EXPECT().WriteObject(ctx, infoParams, infoR).Return(int64(len(offsetInfoData)), nil).After(lastGetObjectSize)
|
|
service.EXPECT().SetObjectMetadata(ctx, objectParams, metadata).Return(nil).After(writeObject)
|
|
|
|
upload, err := store.GetUpload(context.Background(), mockID)
|
|
assert.Nil(err)
|
|
|
|
err = upload.FinishUpload(context.Background())
|
|
assert.Nil(err)
|
|
|
|
// Cancel the context to avoid getting an error from `go vet`
|
|
cancel()
|
|
}
|
|
|
|
func TestWriteChunk(t *testing.T) {
|
|
mockCtrl := gomock.NewController(t)
|
|
defer mockCtrl.Finish()
|
|
assert := assert.New(t)
|
|
|
|
service := NewMockGCSAPI(mockCtrl)
|
|
store := gcsstore.New(mockBucket, service)
|
|
|
|
assert.Equal(store.Bucket, mockBucket)
|
|
|
|
// filter objects
|
|
filterParams := gcsstore.GCSFilterParams{
|
|
Bucket: store.Bucket,
|
|
Prefix: fmt.Sprintf("%s_", mockID),
|
|
}
|
|
|
|
var partials = []string{mockPartial0}
|
|
|
|
// write object
|
|
writeObjectParams := gcsstore.GCSObjectParams{
|
|
Bucket: store.Bucket,
|
|
ID: mockPartial1,
|
|
}
|
|
|
|
rGet := bytes.NewReader([]byte(mockReaderData))
|
|
|
|
ctx := context.Background()
|
|
gomock.InOrder(
|
|
service.EXPECT().FilterObjects(ctx, filterParams).Return(partials, nil),
|
|
service.EXPECT().WriteObject(ctx, writeObjectParams, rGet).Return(int64(len(mockReaderData)), nil),
|
|
)
|
|
|
|
upload, err := store.GetUpload(context.Background(), mockID)
|
|
assert.Nil(err)
|
|
|
|
reader := bytes.NewReader([]byte(mockReaderData))
|
|
var offset int64 = mockSize / 3
|
|
|
|
_, err = upload.WriteChunk(context.Background(), offset, reader)
|
|
assert.Nil(err)
|
|
}
|