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 }