aliyun-oss-ruby-sdk 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +95 -0
  3. data/README.md +423 -0
  4. data/examples/aliyun/oss/bucket.rb +144 -0
  5. data/examples/aliyun/oss/callback.rb +61 -0
  6. data/examples/aliyun/oss/object.rb +182 -0
  7. data/examples/aliyun/oss/resumable_download.rb +42 -0
  8. data/examples/aliyun/oss/resumable_upload.rb +49 -0
  9. data/examples/aliyun/oss/streaming.rb +124 -0
  10. data/examples/aliyun/oss/using_sts.rb +48 -0
  11. data/examples/aliyun/sts/assume_role.rb +59 -0
  12. data/lib/aliyun_sdk/common.rb +6 -0
  13. data/lib/aliyun_sdk/common/exception.rb +18 -0
  14. data/lib/aliyun_sdk/common/logging.rb +46 -0
  15. data/lib/aliyun_sdk/common/struct.rb +56 -0
  16. data/lib/aliyun_sdk/oss.rb +16 -0
  17. data/lib/aliyun_sdk/oss/bucket.rb +661 -0
  18. data/lib/aliyun_sdk/oss/client.rb +106 -0
  19. data/lib/aliyun_sdk/oss/config.rb +39 -0
  20. data/lib/aliyun_sdk/oss/download.rb +255 -0
  21. data/lib/aliyun_sdk/oss/exception.rb +108 -0
  22. data/lib/aliyun_sdk/oss/http.rb +338 -0
  23. data/lib/aliyun_sdk/oss/iterator.rb +92 -0
  24. data/lib/aliyun_sdk/oss/multipart.rb +74 -0
  25. data/lib/aliyun_sdk/oss/object.rb +15 -0
  26. data/lib/aliyun_sdk/oss/protocol.rb +1499 -0
  27. data/lib/aliyun_sdk/oss/struct.rb +208 -0
  28. data/lib/aliyun_sdk/oss/upload.rb +238 -0
  29. data/lib/aliyun_sdk/oss/util.rb +89 -0
  30. data/lib/aliyun_sdk/sts.rb +9 -0
  31. data/lib/aliyun_sdk/sts/client.rb +38 -0
  32. data/lib/aliyun_sdk/sts/config.rb +22 -0
  33. data/lib/aliyun_sdk/sts/exception.rb +53 -0
  34. data/lib/aliyun_sdk/sts/protocol.rb +130 -0
  35. data/lib/aliyun_sdk/sts/struct.rb +64 -0
  36. data/lib/aliyun_sdk/sts/util.rb +48 -0
  37. data/lib/aliyun_sdk/version.rb +7 -0
  38. data/spec/aliyun/oss/bucket_spec.rb +597 -0
  39. data/spec/aliyun/oss/client/bucket_spec.rb +554 -0
  40. data/spec/aliyun/oss/client/client_spec.rb +297 -0
  41. data/spec/aliyun/oss/client/resumable_download_spec.rb +220 -0
  42. data/spec/aliyun/oss/client/resumable_upload_spec.rb +413 -0
  43. data/spec/aliyun/oss/http_spec.rb +83 -0
  44. data/spec/aliyun/oss/multipart_spec.rb +686 -0
  45. data/spec/aliyun/oss/object_spec.rb +785 -0
  46. data/spec/aliyun/oss/service_spec.rb +142 -0
  47. data/spec/aliyun/oss/util_spec.rb +50 -0
  48. data/spec/aliyun/sts/client_spec.rb +150 -0
  49. data/spec/aliyun/sts/util_spec.rb +39 -0
  50. data/tests/config.rb +31 -0
  51. data/tests/test_content_encoding.rb +54 -0
  52. data/tests/test_content_type.rb +95 -0
  53. data/tests/test_custom_headers.rb +70 -0
  54. data/tests/test_encoding.rb +77 -0
  55. data/tests/test_large_file.rb +66 -0
  56. data/tests/test_multipart.rb +97 -0
  57. data/tests/test_object_acl.rb +49 -0
  58. data/tests/test_object_key.rb +68 -0
  59. data/tests/test_object_url.rb +69 -0
  60. data/tests/test_resumable.rb +40 -0
  61. metadata +240 -0
@@ -0,0 +1,554 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'spec_helper'
4
+ require 'yaml'
5
+ require 'nokogiri'
6
+
7
+ module AliyunSDK
8
+ module OSS
9
+
10
+ describe Bucket do
11
+
12
+ before :all do
13
+ @endpoint = 'oss-cn-hangzhou.aliyuncs.com'
14
+ @bucket_name = 'rubysdk-bucket'
15
+ @bucket = Client.new(
16
+ :endpoint => @endpoint,
17
+ :access_key_id => 'xxx',
18
+ :access_key_secret => 'yyy').get_bucket(@bucket_name)
19
+ end
20
+
21
+ def bucket_url
22
+ "#{@bucket_name}.#{@endpoint}"
23
+ end
24
+
25
+ def object_url(object)
26
+ "#{@bucket_name}.#{@endpoint}/#{object}"
27
+ end
28
+
29
+ def resource_path(object)
30
+ "/#{@bucket_name}/#{object}"
31
+ end
32
+
33
+ def mock_objects(objects, more = {})
34
+ Nokogiri::XML::Builder.new do |xml|
35
+ xml.ListBucketResult {
36
+ {
37
+ :prefix => 'Prefix',
38
+ :delimiter => 'Delimiter',
39
+ :limit => 'MaxKeys',
40
+ :marker => 'Marker',
41
+ :next_marker => 'NextMarker',
42
+ :truncated => 'IsTruncated',
43
+ :encoding => 'EncodingType'
44
+ }.map do |k, v|
45
+ xml.send(v, more[k]) if more[k] != nil
46
+ end
47
+
48
+ objects.each do |o|
49
+ xml.Contents {
50
+ xml.Key o.key
51
+ xml.Size o.size
52
+ xml.ETag o.etag
53
+ }
54
+ end
55
+
56
+ (more[:common_prefixes] || []).each do |p|
57
+ xml.CommonPrefixes {
58
+ xml.Prefix p
59
+ }
60
+ end
61
+ }
62
+ end.to_xml
63
+ end
64
+
65
+ def mock_uploads(txns, more = {})
66
+ Nokogiri::XML::Builder.new do |xml|
67
+ xml.ListMultipartUploadsResult {
68
+ {
69
+ :prefix => 'Prefix',
70
+ :delimiter => 'Delimiter',
71
+ :limit => 'MaxUploads',
72
+ :key_marker => 'KeyMarker',
73
+ :id_marker => 'UploadIdMarker',
74
+ :next_key_marker => 'NextKeyMarker',
75
+ :next_id_marker => 'NextUploadIdMarker',
76
+ :truncated => 'IsTruncated',
77
+ :encoding => 'EncodingType'
78
+ }.map do |k, v|
79
+ xml.send(v, more[k]) if more[k] != nil
80
+ end
81
+
82
+ txns.each do |t|
83
+ xml.Upload {
84
+ xml.Key t.object
85
+ xml.UploadId t.id
86
+ }
87
+ end
88
+ }
89
+ end.to_xml
90
+ end
91
+
92
+ def mock_acl(acl)
93
+ Nokogiri::XML::Builder.new do |xml|
94
+ xml.AccessControlPolicy {
95
+ xml.Owner {
96
+ xml.ID 'owner_id'
97
+ xml.DisplayName 'owner_name'
98
+ }
99
+
100
+ xml.AccessControlList {
101
+ xml.Grant acl
102
+ }
103
+ }
104
+ end.to_xml
105
+ end
106
+
107
+ def mock_error(code, message)
108
+ Nokogiri::XML::Builder.new do |xml|
109
+ xml.Error {
110
+ xml.Code code
111
+ xml.Message message
112
+ xml.RequestId '0000'
113
+ }
114
+ end.to_xml
115
+ end
116
+
117
+ def err(msg, reqid = '0000')
118
+ "#{msg} RequestId: #{reqid}"
119
+ end
120
+
121
+ context "bucket operations" do
122
+ it "should get acl" do
123
+ query = {'acl' => ''}
124
+ return_acl = ACL::PUBLIC_READ
125
+
126
+ stub_request(:get, bucket_url)
127
+ .with(:query => query)
128
+ .to_return(:body => mock_acl(return_acl))
129
+
130
+ acl = @bucket.acl
131
+
132
+ expect(WebMock).to have_requested(:get, bucket_url)
133
+ .with(:query => query, :body => nil)
134
+ expect(acl).to eq(return_acl)
135
+ end
136
+
137
+ it "should set acl" do
138
+ query = {'acl' => ''}
139
+
140
+ stub_request(:put, bucket_url).with(:query => query)
141
+
142
+ @bucket.acl = ACL::PUBLIC_READ
143
+
144
+ expect(WebMock).to have_requested(:put, bucket_url)
145
+ .with(:query => query, :body => nil)
146
+ end
147
+
148
+ it "should delete logging setting" do
149
+ query = {'logging' => ''}
150
+
151
+ stub_request(:delete, bucket_url).with(:query => query)
152
+
153
+ @bucket.logging = BucketLogging.new(:enable => false)
154
+
155
+ expect(WebMock).to have_requested(:delete, bucket_url)
156
+ .with(:query => query, :body => nil)
157
+ end
158
+
159
+ it "should get bucket url" do
160
+ expect(@bucket.bucket_url)
161
+ .to eq('http://rubysdk-bucket.oss-cn-hangzhou.aliyuncs.com/')
162
+ end
163
+
164
+ it "should get access key id" do
165
+ expect(@bucket.access_key_id).to eq('xxx')
166
+ end
167
+ end # bucket operations
168
+
169
+ context "object operations" do
170
+ it "should list objects" do
171
+ query_1 = {
172
+ :prefix => 'list-',
173
+ :delimiter => '-',
174
+ 'encoding-type' => 'url'
175
+ }
176
+ return_obj_1 = (1..5).map{ |i| Object.new(
177
+ :key => "obj-#{i}",
178
+ :size => 1024 * i,
179
+ :etag => "etag-#{i}")}
180
+ return_more_1 = {
181
+ :next_marker => 'foo',
182
+ :truncated => true,
183
+ :common_prefixes => ['hello', 'world']
184
+ }
185
+
186
+ query_2 = {
187
+ :prefix => 'list-',
188
+ :delimiter => '-',
189
+ :marker => 'foo',
190
+ 'encoding-type' => 'url'
191
+ }
192
+ return_obj_2 = (6..8).map{ |i| Object.new(
193
+ :key => "obj-#{i}",
194
+ :size => 1024 * i,
195
+ :etag => "etag-#{i}")}
196
+ return_more_2 = {
197
+ :next_marker => 'bar',
198
+ :truncated => false,
199
+ :common_prefixes => ['rock', 'suck']
200
+ }
201
+
202
+ stub_request(:get, bucket_url)
203
+ .with(:query => query_1)
204
+ .to_return(:body => mock_objects(return_obj_1, return_more_1))
205
+
206
+ stub_request(:get, bucket_url)
207
+ .with(:query => query_2)
208
+ .to_return(:body => mock_objects(return_obj_2, return_more_2))
209
+
210
+ list = @bucket.list_objects :prefix => 'list-', :delimiter => '-'
211
+ all = list.to_a
212
+
213
+ expect(WebMock).to have_requested(:get, bucket_url)
214
+ .with(:query => query_1).times(1)
215
+ expect(WebMock).to have_requested(:get, bucket_url)
216
+ .with(:query => query_2).times(1)
217
+
218
+ objs = all.select{ |x| x.is_a?(Object) }
219
+ common_prefixes = all.select{ |x| x.is_a?(String) }
220
+ all_objs = (1..8).map{ |i| Object.new(
221
+ :key => "obj-#{i}",
222
+ :size => 1024 * i,
223
+ :etag => "etag-#{i}")}
224
+ expect(objs.map{ |o| o.to_s }).to match_array(all_objs.map{ |o| o.to_s })
225
+ all_prefixes = ['hello', 'world', 'rock', 'suck']
226
+ expect(common_prefixes).to match_array(all_prefixes)
227
+ end
228
+
229
+ it "should put object from file" do
230
+ key = 'ruby'
231
+ stub_request(:put, object_url(key))
232
+
233
+ content = (1..10).map{ |i| i.to_s.rjust(9, '0') }.join("\n")
234
+ File.open('/tmp/x', 'w'){ |f| f.write(content) }
235
+
236
+ @bucket.put_object(key, :file => '/tmp/x')
237
+
238
+ expect(WebMock).to have_requested(:put, object_url(key))
239
+ .with(:body => content, :query => {})
240
+ end
241
+
242
+ it "should put object with acl" do
243
+ key = 'ruby'
244
+ stub_request(:put, object_url(key))
245
+
246
+ @bucket.put_object(key, :acl => ACL::PUBLIC_READ)
247
+
248
+ expect(WebMock)
249
+ .to have_requested(:put, object_url(key))
250
+ .with(:headers => {'X-Oss-Object-Acl' => ACL::PUBLIC_READ})
251
+ end
252
+
253
+ it "should put object with callback" do
254
+ key = 'ruby'
255
+ stub_request(:put, object_url(key))
256
+
257
+ callback = Callback.new(
258
+ url: 'http://app.server.com/callback',
259
+ query: {'id' => 1, 'name' => '杭州'},
260
+ body: 'hello world',
261
+ host: 'server.com'
262
+ )
263
+ @bucket.put_object(key, callback: callback)
264
+
265
+ expect(WebMock).to have_requested(:put, object_url(key))
266
+ .with { |req| req.headers.key?('X-Oss-Callback') }
267
+ end
268
+
269
+ it "should raise CallbackError when callback failed" do
270
+ key = 'ruby'
271
+ code = 'CallbackFailed'
272
+ message = 'Error status: 502.'
273
+ stub_request(:put, object_url(key))
274
+ .to_return(:status => 203, :body => mock_error(code, message))
275
+
276
+ callback = Callback.new(
277
+ url: 'http://app.server.com/callback',
278
+ query: {'id' => 1, 'name' => '杭州'},
279
+ body: 'hello world',
280
+ host: 'server.com'
281
+ )
282
+ expect {
283
+ @bucket.put_object(key, callback: callback)
284
+ }.to raise_error(CallbackError, err(message))
285
+
286
+ expect(WebMock).to have_requested(:put, object_url(key))
287
+ .with { |req| req.headers.key?('X-Oss-Callback') }
288
+ end
289
+
290
+ it "should set custom headers when put object" do
291
+ key = 'ruby'
292
+ stub_request(:put, object_url(key))
293
+
294
+ @bucket.put_object(
295
+ key, headers: {'cache-control' => 'xxx', 'expires' => 'yyy'})
296
+
297
+ headers = {}
298
+ expect(WebMock).to have_requested(:put, object_url(key))
299
+ .with { |req| headers = req.headers }
300
+ expect(headers['Cache-Control']).to eq('xxx')
301
+ expect(headers['Expires']).to eq('yyy')
302
+ end
303
+
304
+ it "should set custom headers when append object" do
305
+ key = 'ruby'
306
+ query = {'append' => '', 'position' => 11}
307
+ stub_request(:post, object_url(key)).with(:query => query)
308
+
309
+ @bucket.append_object(
310
+ key, 11,
311
+ headers: {'CACHE-CONTROL' => 'nocache', 'EXPIRES' => 'seripxe'})
312
+
313
+ headers = {}
314
+ expect(WebMock).to have_requested(:post, object_url(key))
315
+ .with(:query => query)
316
+ .with { |req| headers = req.headers }
317
+ expect(headers['Cache-Control']).to eq('nocache')
318
+ expect(headers['Expires']).to eq('seripxe')
319
+ end
320
+
321
+ it "should get object to file" do
322
+ key = 'ruby'
323
+ # 100 KB
324
+ content = (1..1024).map{ |i| i.to_s.rjust(99, '0') }.join(",")
325
+
326
+ stub_request(:get, object_url(key)).to_return(:body => content)
327
+
328
+ @bucket.get_object(key, :file => '/tmp/x')
329
+
330
+ expect(WebMock).to have_requested(:get, object_url(key))
331
+ .with(:body => nil, :query => {})
332
+ expect(File.read('/tmp/x')).to eq(content)
333
+ end
334
+
335
+ it "should only get meta when get object without :file or block" do
336
+ key = 'ruby'
337
+
338
+ last_modified = Time.now.rfc822
339
+ return_headers = {
340
+ 'x-oss-object-type' => 'Normal',
341
+ 'ETag' => 'xxxyyyzzz',
342
+ 'Content-Length' => 1024,
343
+ 'Last-Modified' => last_modified,
344
+ 'x-oss-meta-year' => '2015',
345
+ 'x-oss-meta-people' => 'mary'
346
+ }
347
+ stub_request(:head, object_url(key))
348
+ .to_return(:headers => return_headers)
349
+
350
+ obj = @bucket.get_object(key)
351
+
352
+ expect(WebMock).to have_requested(:head, object_url(key))
353
+ .with(:body => nil, :query => {})
354
+
355
+ expect(obj.key).to eq(key)
356
+ expect(obj.type).to eq('Normal')
357
+ expect(obj.etag).to eq('xxxyyyzzz')
358
+ expect(obj.size).to eq(1024)
359
+ expect(obj.last_modified.rfc822).to eq(last_modified)
360
+ expect(obj.metas).to eq({'year' => '2015', 'people' => 'mary'})
361
+ end
362
+
363
+ it "should append object from file" do
364
+ key = 'ruby'
365
+ query = {'append' => '', 'position' => 11}
366
+ stub_request(:post, object_url(key)).with(:query => query)
367
+
368
+ content = (1..10).map{ |i| i.to_s.rjust(9, '0') }.join("\n")
369
+ content = "<html>" + content + "</html>"
370
+ File.open('/tmp/x.html', 'w'){ |f| f.write(content) }
371
+
372
+ @bucket.append_object(key, 11, :file => '/tmp/x.html')
373
+
374
+ expect(WebMock).to have_requested(:post, object_url(key))
375
+ .with(:query => query, :body => content,
376
+ :headers => {'Content-Type' => 'text/html'})
377
+ end
378
+
379
+ it "should append object with acl" do
380
+ key = 'ruby'
381
+ query = {'append' => '', 'position' => 11}
382
+ stub_request(:post, object_url(key)).with(:query => query)
383
+
384
+ @bucket.append_object(key, 11, :acl => ACL::PUBLIC_READ_WRITE)
385
+
386
+ expect(WebMock)
387
+ .to have_requested(:post, object_url(key))
388
+ .with(:query => query,
389
+ :headers => {'X-Oss-Object-Acl' => ACL::PUBLIC_READ_WRITE})
390
+ end
391
+
392
+ it "should answer object exists?" do
393
+ key = 'ruby'
394
+
395
+ stub_request(:head, object_url(key))
396
+ .to_return(:status => 404).times(3)
397
+
398
+ expect {
399
+ @bucket.get_object(key)
400
+ }.to raise_error(ServerError, err("UnknownError[404].", ''))
401
+
402
+ expect(@bucket.object_exists?(key)).to be false
403
+ expect(@bucket.object_exist?(key)).to be false
404
+
405
+ last_modified = Time.now.rfc822
406
+ return_headers = {
407
+ 'x-oss-object-type' => 'Normal',
408
+ 'ETag' => 'xxxyyyzzz',
409
+ 'Content-Length' => 1024,
410
+ 'Last-Modified' => last_modified,
411
+ 'x-oss-meta-year' => '2015',
412
+ 'x-oss-meta-people' => 'mary'
413
+ }
414
+
415
+ stub_request(:head, object_url(key))
416
+ .to_return(:headers => return_headers).times(2)
417
+
418
+ expect(@bucket.object_exists?(key)).to be true
419
+ expect(@bucket.object_exist?(key)).to be true
420
+
421
+ stub_request(:head, object_url(key))
422
+ .to_return(:status => 500)
423
+
424
+ expect {
425
+ @bucket.object_exists?(key)
426
+ }.to raise_error(ServerError, err("UnknownError[500].", ''))
427
+ end
428
+
429
+ it "should update object metas" do
430
+ key = 'ruby'
431
+
432
+ stub_request(:put, object_url(key))
433
+
434
+ @bucket.update_object_metas(
435
+ key, {'people' => 'mary', 'year' => '2016'})
436
+
437
+ expect(WebMock).to have_requested(:put, object_url(key))
438
+ .with(:body => nil,
439
+ :headers => {
440
+ 'x-oss-copy-source' => resource_path(key),
441
+ 'x-oss-metadata-directive' => 'REPLACE',
442
+ 'x-oss-meta-year' => '2016',
443
+ 'x-oss-meta-people' => 'mary'})
444
+ end
445
+
446
+ it "should get object url" do
447
+ url = @bucket.object_url('yeah', false)
448
+ object_url = 'http://rubysdk-bucket.oss-cn-hangzhou.aliyuncs.com/yeah'
449
+ expect(url).to eq(object_url)
450
+
451
+ url = @bucket.object_url('yeah')
452
+ path = url[0, url.index('?')]
453
+ expect(path).to eq(object_url)
454
+
455
+ query = {}
456
+ url[url.index('?') + 1, url.size].split('&')
457
+ .each { |s| k, v = s.split('='); query[k] = v }
458
+
459
+ expect(query.key?('Expires')).to be true
460
+ expect(query['OSSAccessKeyId']).to eq('xxx')
461
+ expires = query['Expires']
462
+ signature = CGI.unescape(query['Signature'])
463
+
464
+ string_to_sign =
465
+ "GET\n" + "\n\n" + "#{expires}\n" + "/rubysdk-bucket/yeah"
466
+ sig = Util.sign('yyy', string_to_sign)
467
+ expect(signature).to eq(sig)
468
+ end
469
+
470
+ it "should get object url with STS" do
471
+ sts_bucket = Client.new(
472
+ :endpoint => @endpoint,
473
+ :access_key_id => 'xxx',
474
+ :access_key_secret => 'yyy',
475
+ :sts_token => 'zzz').get_bucket(@bucket_name)
476
+
477
+ object_url = 'http://rubysdk-bucket.oss-cn-hangzhou.aliyuncs.com/yeah'
478
+
479
+ url = sts_bucket.object_url('yeah')
480
+ path = url[0, url.index('?')]
481
+ expect(path).to eq(object_url)
482
+
483
+ query = {}
484
+ url[url.index('?') + 1, url.size].split('&')
485
+ .each { |s| k, v = s.split('='); query[k] = v }
486
+
487
+ expect(query.key?('Expires')).to be true
488
+ expect(query.key?('Signature')).to be true
489
+ expect(query['OSSAccessKeyId']).to eq('xxx')
490
+ expect(query['security-token']).to eq('zzz')
491
+ end
492
+
493
+ end # object operations
494
+
495
+ context "multipart operations" do
496
+ it "should list uploads" do
497
+ query_1 = {
498
+ :prefix => 'list-',
499
+ 'encoding-type' => 'url',
500
+ 'uploads' => ''
501
+ }
502
+ return_up_1 = (1..5).map{ |i| Multipart::Transaction.new(
503
+ :id => "txn-#{i}",
504
+ :object => "my-object",
505
+ :bucket => @bucket_name
506
+ )}
507
+ return_more_1 = {
508
+ :next_id_marker => "txn-5",
509
+ :truncated => true
510
+ }
511
+
512
+ query_2 = {
513
+ :prefix => 'list-',
514
+ 'upload-id-marker' => 'txn-5',
515
+ 'encoding-type' => 'url',
516
+ 'uploads' => ''
517
+ }
518
+ return_up_2 = (6..8).map{ |i| Multipart::Transaction.new(
519
+ :id => "txn-#{i}",
520
+ :object => "my-object",
521
+ :bucket => @bucket_name
522
+ )}
523
+ return_more_2 = {
524
+ :next_id_marker => 'txn-8',
525
+ :truncated => false,
526
+ }
527
+
528
+ stub_request(:get, bucket_url)
529
+ .with(:query => query_1)
530
+ .to_return(:body => mock_uploads(return_up_1, return_more_1))
531
+
532
+ stub_request(:get, bucket_url)
533
+ .with(:query => query_2)
534
+ .to_return(:body => mock_uploads(return_up_2, return_more_2))
535
+
536
+ txns = @bucket.list_uploads(prefix: 'list-').to_a
537
+
538
+ expect(WebMock).to have_requested(:get, bucket_url)
539
+ .with(:query => query_1).times(1)
540
+ expect(WebMock).to have_requested(:get, bucket_url)
541
+ .with(:query => query_2).times(1)
542
+
543
+ all_txns = (1..8).map{ |i| Multipart::Transaction.new(
544
+ :id => "txn-#{i}",
545
+ :object => "my-object",
546
+ :bucket => @bucket_name
547
+ )}
548
+ expect(txns.map(&:to_s)).to match_array(all_txns.map(&:to_s))
549
+ end
550
+ end # multipart operations
551
+
552
+ end # Bucket
553
+ end # OSS
554
+ end # Aliyun