github.com/uber/kraken@v0.1.4/lib/backend/s3backend/client_test.go (about)

     1  // Copyright (c) 2016-2019 Uber Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  package s3backend
    15  
    16  import (
    17  	"bytes"
    18  	"testing"
    19  
    20  	"github.com/uber/kraken/core"
    21  	"github.com/uber/kraken/lib/backend"
    22  	"github.com/uber/kraken/mocks/lib/backend/s3backend"
    23  	"github.com/uber/kraken/utils/mockutil"
    24  	"github.com/uber/kraken/utils/randutil"
    25  	"github.com/uber/kraken/utils/rwutil"
    26  
    27  	"github.com/aws/aws-sdk-go/aws"
    28  	"github.com/aws/aws-sdk-go/service/s3"
    29  	"github.com/aws/aws-sdk-go/service/s3/s3manager"
    30  	"github.com/golang/mock/gomock"
    31  	"github.com/stretchr/testify/require"
    32  )
    33  
    34  type clientMocks struct {
    35  	config   Config
    36  	userAuth UserAuthConfig
    37  	s3       *mocks3backend.MockS3
    38  }
    39  
    40  func newClientMocks(t *testing.T) (*clientMocks, func()) {
    41  	ctrl := gomock.NewController(t)
    42  
    43  	var auth AuthConfig
    44  	auth.S3.AccessKeyID = "accesskey"
    45  	auth.S3.AccessSecretKey = "secret"
    46  
    47  	return &clientMocks{
    48  		config: Config{
    49  			Username:      "test-user",
    50  			Region:        "test-region",
    51  			Bucket:        "test-bucket",
    52  			NamePath:      "identity",
    53  			RootDirectory: "/root",
    54  		},
    55  		userAuth: UserAuthConfig{"test-user": auth},
    56  		s3:       mocks3backend.NewMockS3(ctrl),
    57  	}, ctrl.Finish
    58  }
    59  
    60  func (m *clientMocks) new() *Client {
    61  	c, err := NewClient(m.config, m.userAuth, WithS3(m.s3))
    62  	if err != nil {
    63  		panic(err)
    64  	}
    65  	return c
    66  }
    67  
    68  func TestClientFactory(t *testing.T) {
    69  	require := require.New(t)
    70  
    71  	config := Config{
    72  		Username:      "test-user",
    73  		Region:        "test-region",
    74  		Bucket:        "test-bucket",
    75  		NamePath:      "identity",
    76  		RootDirectory: "/root",
    77  	}
    78  	var auth AuthConfig
    79  	auth.S3.AccessKeyID = "accesskey"
    80  	auth.S3.AccessSecretKey = "secret"
    81  	userAuth := UserAuthConfig{"test-user": auth}
    82  	f := factory{}
    83  	_, err := f.Create(config, userAuth)
    84  	require.NoError(err)
    85  }
    86  
    87  func TestClientStat(t *testing.T) {
    88  	require := require.New(t)
    89  
    90  	mocks, cleanup := newClientMocks(t)
    91  	defer cleanup()
    92  
    93  	client := mocks.new()
    94  
    95  	var length int64 = 100
    96  
    97  	mocks.s3.EXPECT().HeadObject(&s3.HeadObjectInput{
    98  		Bucket: aws.String("test-bucket"),
    99  		Key:    aws.String("/root/test"),
   100  	}).Return(&s3.HeadObjectOutput{ContentLength: &length}, nil)
   101  
   102  	info, err := client.Stat(core.NamespaceFixture(), "test")
   103  	require.NoError(err)
   104  	require.Equal(core.NewBlobInfo(100), info)
   105  }
   106  
   107  func TestClientDownload(t *testing.T) {
   108  	require := require.New(t)
   109  
   110  	mocks, cleanup := newClientMocks(t)
   111  	defer cleanup()
   112  
   113  	client := mocks.new()
   114  
   115  	data := randutil.Text(32)
   116  
   117  	mocks.s3.EXPECT().Download(
   118  		mockutil.MatchWriterAt(data),
   119  		&s3.GetObjectInput{
   120  			Bucket: aws.String("test-bucket"),
   121  			Key:    aws.String("/root/test"),
   122  		},
   123  	).Return(int64(len(data)), nil)
   124  
   125  	var b bytes.Buffer
   126  	require.NoError(client.Download(core.NamespaceFixture(), "test", &b))
   127  	require.Equal(data, b.Bytes())
   128  }
   129  
   130  func TestClientDownloadWithBuffer(t *testing.T) {
   131  	require := require.New(t)
   132  
   133  	mocks, cleanup := newClientMocks(t)
   134  	defer cleanup()
   135  
   136  	client := mocks.new()
   137  
   138  	data := randutil.Text(32)
   139  
   140  	mocks.s3.EXPECT().Download(
   141  		mockutil.MatchWriterAt(data),
   142  		&s3.GetObjectInput{
   143  			Bucket: aws.String("test-bucket"),
   144  			Key:    aws.String("/root/test"),
   145  		},
   146  	).Return(int64(len(data)), nil)
   147  
   148  	// A plain io.Writer will require a buffer to download.
   149  	w := make(rwutil.PlainWriter, len(data))
   150  	require.NoError(client.Download(core.NamespaceFixture(), "test", w))
   151  	require.Equal(data, []byte(w))
   152  }
   153  
   154  func TestClientUpload(t *testing.T) {
   155  	require := require.New(t)
   156  
   157  	mocks, cleanup := newClientMocks(t)
   158  	defer cleanup()
   159  
   160  	client := mocks.new()
   161  
   162  	data := bytes.NewReader(randutil.Text(32))
   163  
   164  	mocks.s3.EXPECT().Upload(
   165  		&s3manager.UploadInput{
   166  			Bucket: aws.String("test-bucket"),
   167  			Key:    aws.String("/root/test"),
   168  			Body:   data,
   169  		},
   170  		gomock.Any(),
   171  	).Return(nil, nil)
   172  
   173  	require.NoError(client.Upload(core.NamespaceFixture(), "test", data))
   174  }
   175  
   176  func TestClientList(t *testing.T) {
   177  	require := require.New(t)
   178  
   179  	mocks, cleanup := newClientMocks(t)
   180  	defer cleanup()
   181  
   182  	client := mocks.new()
   183  
   184  	mocks.s3.EXPECT().ListObjectsV2Pages(
   185  		&s3.ListObjectsV2Input{
   186  			Bucket:            aws.String("test-bucket"),
   187  			MaxKeys:           aws.Int64(250),
   188  			Prefix:            aws.String("root/test"),
   189  		},
   190  		gomock.Any(),
   191  	).DoAndReturn(func(
   192  		input *s3.ListObjectsV2Input,
   193  		f func(page *s3.ListObjectsV2Output, last bool) bool) error {
   194  
   195  		shouldContinue := f(&s3.ListObjectsV2Output{
   196  			Contents: []*s3.Object{
   197  				{Key: aws.String("root/test/a")},
   198  				{Key: aws.String("root/test/b")},
   199  			},
   200  		}, false)
   201  
   202  		if shouldContinue {
   203  			f(&s3.ListObjectsV2Output{
   204  				Contents: []*s3.Object{
   205  					{Key: aws.String("root/test/c")},
   206  					{Key: aws.String("root/test/d")},
   207  				},
   208  			}, true)
   209  		}
   210  
   211  		return nil
   212  	})
   213  
   214  	result, err := client.List("test")
   215  	require.NoError(err)
   216  	require.Equal([]string{"test/a", "test/b", "test/c", "test/d"}, result.Names)
   217  }
   218  
   219  func TestClientListPaginated(t *testing.T) {
   220  	require := require.New(t)
   221  
   222  	mocks, cleanup := newClientMocks(t)
   223  	defer cleanup()
   224  
   225  	client := mocks.new()
   226  
   227  	mocks.s3.EXPECT().ListObjectsV2Pages(
   228  		&s3.ListObjectsV2Input{
   229  			Bucket:            aws.String("test-bucket"),
   230  			MaxKeys:           aws.Int64(2),
   231  			Prefix:            aws.String("root/test"),
   232  		},
   233  		gomock.Any(),
   234  	).DoAndReturn(func(
   235  		input *s3.ListObjectsV2Input,
   236  		f func(page *s3.ListObjectsV2Output, last bool) bool) error {
   237  
   238  		f(&s3.ListObjectsV2Output{
   239  			Contents: []*s3.Object{
   240  				{Key: aws.String("root/test/a")},
   241  				{Key: aws.String("root/test/b")},
   242  			},
   243  			IsTruncated:           aws.Bool(true),
   244  			NextContinuationToken: aws.String("test-continuation-token"),
   245  		}, false)
   246  
   247  		return nil
   248  	})
   249  
   250  	mocks.s3.EXPECT().ListObjectsV2Pages(
   251  		&s3.ListObjectsV2Input{
   252  			Bucket:            aws.String("test-bucket"),
   253  			MaxKeys:           aws.Int64(2),
   254  			Prefix:            aws.String("root/test"),
   255  			ContinuationToken: aws.String("test-continuation-token"),
   256  		},
   257  		gomock.Any(),
   258  	).DoAndReturn(func(
   259  		input *s3.ListObjectsV2Input,
   260  		f func(page *s3.ListObjectsV2Output, last bool) bool) error {
   261  
   262  		f(&s3.ListObjectsV2Output{
   263  			Contents: []*s3.Object{
   264  				{Key: aws.String("root/test/c")},
   265  				{Key: aws.String("root/test/d")},
   266  			},
   267  			IsTruncated: aws.Bool(false),
   268  		}, true)
   269  
   270  		return nil
   271  	})
   272  
   273  	result, err := client.List("test",
   274  		backend.ListWithPagination(),
   275  		backend.ListWithMaxKeys(2),
   276  	)
   277  	require.NoError(err)
   278  	require.Equal([]string{"test/a", "test/b"}, result.Names)
   279  	require.Equal("test-continuation-token", result.ContinuationToken)
   280  
   281  	result, err = client.List("test",
   282  		backend.ListWithPagination(),
   283  		backend.ListWithMaxKeys(2),
   284  		backend.ListWithContinuationToken(result.ContinuationToken),
   285  	)
   286  	require.NoError(err)
   287  	require.Equal([]string{"test/c", "test/d"}, result.Names)
   288  	require.Equal("", result.ContinuationToken)
   289  }