aliyun-sdk 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'spec_helper'
4
+
5
+ module Aliyun
6
+ module OSS
7
+
8
+ describe HTTP do
9
+
10
+ context HTTP::StreamWriter do
11
+ it "should read out chunks that are written" do
12
+ s = HTTP::StreamWriter.new do |sr|
13
+ 10.times{ |i| sr << "hello, #{i}" }
14
+ end
15
+
16
+ 10.times do |i|
17
+ bytes, outbuf = "hello, 0".size, ""
18
+ s.read(bytes, outbuf)
19
+ expect(outbuf).to eq("hello, #{i}")
20
+ end
21
+ end
22
+ end # StreamWriter
23
+
24
+ end # HTTP
25
+ end # OSS
26
+ end # Aliyun
@@ -0,0 +1,675 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'spec_helper'
4
+ require 'yaml'
5
+ require 'nokogiri'
6
+
7
+ module Aliyun
8
+ module OSS
9
+
10
+ describe Multipart do
11
+
12
+ before :all do
13
+ @endpoint = 'oss.aliyuncs.com'
14
+ @protocol = Protocol.new(
15
+ Config.new(:endpoint => @endpoint,
16
+ :access_key_id => 'xxx', :access_key_secret => 'yyy'))
17
+
18
+ @bucket = 'rubysdk-bucket'
19
+ @object = 'rubysdk-object'
20
+ end
21
+
22
+ def request_path
23
+ "#{@bucket}.#{@endpoint}/#{@object}"
24
+ end
25
+
26
+ def mock_txn_id(txn_id)
27
+ Nokogiri::XML::Builder.new do |xml|
28
+ xml.InitiateMultipartUploadResult {
29
+ xml.Bucket @bucket
30
+ xml.Key @object
31
+ xml.UploadId txn_id
32
+ }
33
+ end.to_xml
34
+ end
35
+
36
+ def mock_multiparts(multiparts, more = {})
37
+ Nokogiri::XML::Builder.new do |xml|
38
+ xml.ListMultipartUploadsResult {
39
+ {
40
+ :prefix => 'Prefix',
41
+ :delimiter => 'Delimiter',
42
+ :limit => 'MaxUploads',
43
+ :id_marker => 'UploadIdMarker',
44
+ :next_id_marker => 'NextUploadIdMarker',
45
+ :key_marker => 'KeyMarker',
46
+ :next_key_marker => 'NextKeyMarker',
47
+ :truncated => 'IsTruncated',
48
+ :encoding => 'EncodingType'
49
+ }.map do |k, v|
50
+ xml.send(v, more[k]) if more[k]
51
+ end
52
+
53
+ multiparts.each do |m|
54
+ xml.Upload {
55
+ xml.Key m.object
56
+ xml.UploadId m.id
57
+ xml.Initiated m.creation_time.rfc822
58
+ }
59
+ end
60
+ }
61
+ end.to_xml
62
+ end
63
+
64
+ def mock_parts(parts, more = {})
65
+ Nokogiri::XML::Builder.new do |xml|
66
+ xml.ListPartsResult {
67
+ {
68
+ :marker => 'PartNumberMarker',
69
+ :next_marker => 'NextPartNumberMarker',
70
+ :limit => 'MaxParts',
71
+ :truncated => 'IsTruncated',
72
+ :encoding => 'EncodingType'
73
+ }.map do |k, v|
74
+ xml.send(v, more[k]) if more[k]
75
+ end
76
+
77
+ parts.each do |p|
78
+ xml.Part {
79
+ xml.PartNumber p.number
80
+ xml.LastModified p.last_modified.rfc822
81
+ xml.ETag p.etag
82
+ xml.Size p.size
83
+ }
84
+ end
85
+ }
86
+ end.to_xml
87
+ end
88
+
89
+ def mock_error(code, message)
90
+ builder = Nokogiri::XML::Builder.new do |xml|
91
+ xml.Error {
92
+ xml.Code code
93
+ xml.Message message
94
+ xml.RequestId '0000'
95
+ }
96
+ end
97
+
98
+ builder.to_xml
99
+ end
100
+
101
+ context "Initiate multipart upload" do
102
+
103
+ it "should POST to create transaction" do
104
+ query = {'uploads' => ''}
105
+ stub_request(:post, request_path).with(:query => query)
106
+
107
+ @protocol.initiate_multipart_upload(
108
+ @bucket, @object, :metas => {
109
+ 'year' => '2015',
110
+ 'people' => 'mary'
111
+ })
112
+
113
+ expect(WebMock).to have_requested(:post, request_path)
114
+ .with(:body => nil, :query => query,
115
+ :headers => {
116
+ 'x-oss-meta-year' => '2015',
117
+ 'x-oss-meta-people' => 'mary'
118
+ })
119
+ end
120
+
121
+ it "should return transaction id" do
122
+ query = {'uploads' => ''}
123
+ return_txn_id = 'zyx'
124
+ stub_request(:post, request_path).
125
+ with(:query => query).
126
+ to_return(:body => mock_txn_id(return_txn_id))
127
+
128
+ txn_id = @protocol.initiate_multipart_upload(@bucket, @object)
129
+
130
+ expect(WebMock).to have_requested(:post, request_path)
131
+ .with(:body => nil, :query => query)
132
+ expect(txn_id).to eq(return_txn_id)
133
+ end
134
+
135
+ it "should raise Exception on error" do
136
+ query = {'uploads' => ''}
137
+
138
+ code = 'InvalidArgument'
139
+ message = 'Invalid argument.'
140
+ stub_request(:post, request_path)
141
+ .with(:query => query)
142
+ .to_return(:status => 400, :body => mock_error(code, message))
143
+
144
+ expect {
145
+ @protocol.initiate_multipart_upload(@bucket, @object)
146
+ }.to raise_error(Exception, message)
147
+
148
+ expect(WebMock).to have_requested(:post, request_path)
149
+ .with(:body => nil, :query => query)
150
+ end
151
+ end # initiate multipart
152
+
153
+ context "Upload part" do
154
+
155
+ it "should PUT to upload a part" do
156
+ txn_id = 'xxxyyyzzz'
157
+ part_no = 1
158
+ query = {'partNumber' => part_no, 'uploadId' => txn_id}
159
+
160
+ stub_request(:put, request_path).with(:query => query)
161
+
162
+ @protocol.upload_part(@bucket, @object, txn_id, part_no) {}
163
+
164
+ expect(WebMock).to have_requested(:put, request_path)
165
+ .with(:body => nil, :query => query)
166
+ end
167
+
168
+ it "should return part etag" do
169
+ part_no = 1
170
+ txn_id = 'xxxyyyzzz'
171
+ query = {'partNumber' => part_no, 'uploadId' => txn_id}
172
+
173
+ return_etag = 'etag_1'
174
+ stub_request(:put, request_path)
175
+ .with(:query => query)
176
+ .to_return(:headers => {'ETag' => return_etag})
177
+
178
+ body = 'hello world'
179
+ p = @protocol.upload_part(@bucket, @object, txn_id, part_no) do |content|
180
+ content << body
181
+ end
182
+
183
+ expect(WebMock).to have_requested(:put, request_path)
184
+ .with(:body => body, :query => query)
185
+ expect(p.number).to eq(part_no)
186
+ expect(p.etag).to eq(return_etag)
187
+ end
188
+
189
+ it "should raise Exception on error" do
190
+ txn_id = 'xxxyyyzzz'
191
+ part_no = 1
192
+ query = {'partNumber' => part_no, 'uploadId' => txn_id}
193
+
194
+ code = 'InvalidArgument'
195
+ message = 'Invalid argument.'
196
+
197
+ stub_request(:put, request_path)
198
+ .with(:query => query)
199
+ .to_return(:status => 400, :body => mock_error(code, message))
200
+
201
+ expect {
202
+ @protocol.upload_part(@bucket, @object, txn_id, part_no) {}
203
+ }.to raise_error(Exception, message)
204
+
205
+ expect(WebMock).to have_requested(:put, request_path)
206
+ .with(:body => nil, :query => query)
207
+ end
208
+
209
+ end # upload part
210
+
211
+ context "Upload part by copy object" do
212
+
213
+ it "should PUT to upload a part by copy object" do
214
+ txn_id = 'xxxyyyzzz'
215
+ part_no = 1
216
+ copy_source = "/#{@bucket}/src_obj"
217
+
218
+ query = {'partNumber' => part_no, 'uploadId' => txn_id}
219
+ headers = {'x-oss-copy-source' => copy_source}
220
+
221
+ stub_request(:put, request_path)
222
+ .with(:query => query, :headers => headers)
223
+
224
+ @protocol.upload_part_by_copy(@bucket, @object, txn_id, part_no, 'src_obj')
225
+
226
+ expect(WebMock).to have_requested(:put, request_path)
227
+ .with(:body => nil, :query => query, :headers => headers)
228
+ end
229
+
230
+ it "should return part etag" do
231
+ txn_id = 'xxxyyyzzz'
232
+ part_no = 1
233
+ copy_source = "/#{@bucket}/src_obj"
234
+
235
+ query = {'partNumber' => part_no, 'uploadId' => txn_id}
236
+ headers = {'x-oss-copy-source' => copy_source}
237
+ return_etag = 'etag_1'
238
+
239
+ stub_request(:put, request_path)
240
+ .with(:query => query, :headers => headers)
241
+ .to_return(:headers => {'ETag' => return_etag})
242
+
243
+ p = @protocol.upload_part_by_copy(@bucket, @object, txn_id, part_no, 'src_obj')
244
+
245
+ expect(WebMock).to have_requested(:put, request_path)
246
+ .with(:body => nil, :query => query, :headers => headers)
247
+ expect(p.number).to eq(part_no)
248
+ expect(p.etag).to eq(return_etag)
249
+ end
250
+
251
+ it "should set range and conditions when copy" do
252
+ txn_id = 'xxxyyyzzz'
253
+ part_no = 1
254
+ copy_source = "/#{@bucket}/src_obj"
255
+
256
+ query = {'partNumber' => part_no, 'uploadId' => txn_id}
257
+ modified_since = Time.now
258
+ unmodified_since = Time.now
259
+ headers = {
260
+ 'Range' => 'bytes=1-4',
261
+ 'x-oss-copy-source' => copy_source,
262
+ 'x-oss-copy-source-if-modified-since' => modified_since.httpdate,
263
+ 'x-oss-copy-source-if-unmodified-since' => unmodified_since.httpdate,
264
+ 'x-oss-copy-source-if-match' => 'me',
265
+ 'x-oss-copy-source-if-none-match' => 'ume'
266
+ }
267
+ return_etag = 'etag_1'
268
+
269
+ stub_request(:put, request_path)
270
+ .with(:query => query, :headers => headers)
271
+ .to_return(:headers => {'ETag' => return_etag})
272
+
273
+ p = @protocol.upload_part_by_copy(
274
+ @bucket, @object, txn_id, part_no, 'src_obj',
275
+ {:range => [1, 5],
276
+ :condition => {
277
+ :if_modified_since => modified_since,
278
+ :if_unmodified_since => unmodified_since,
279
+ :if_match_etag => 'me',
280
+ :if_unmatch_etag => 'ume'
281
+ }})
282
+
283
+ expect(WebMock).to have_requested(:put, request_path)
284
+ .with(:body => nil, :query => query, :headers => headers)
285
+ expect(p.number).to eq(part_no)
286
+ expect(p.etag).to eq(return_etag)
287
+ end
288
+
289
+ it "should raise Exception on error" do
290
+ txn_id = 'xxxyyyzzz'
291
+ part_no = 1
292
+ copy_source = "/#{@bucket}/src_obj"
293
+
294
+ query = {'partNumber' => part_no, 'uploadId' => txn_id}
295
+ headers = {'x-oss-copy-source' => copy_source}
296
+
297
+ code = 'PreconditionFailed'
298
+ message = 'Precondition check failed.'
299
+ stub_request(:put, request_path)
300
+ .with(:query => query, :headers => headers)
301
+ .to_return(:status => 412, :body => mock_error(code, message))
302
+
303
+ expect {
304
+ @protocol.upload_part_by_copy(@bucket, @object, txn_id, part_no, 'src_obj')
305
+ }.to raise_error(Exception, message)
306
+
307
+ expect(WebMock).to have_requested(:put, request_path)
308
+ .with(:body => nil, :query => query, :headers => headers)
309
+ end
310
+ end # upload part by copy object
311
+
312
+ context "Commit multipart" do
313
+
314
+ it "should POST to complete multipart" do
315
+ txn_id = 'xxxyyyzzz'
316
+
317
+ query = {'uploadId' => txn_id}
318
+ parts = (1..5).map do |i|
319
+ Multipart::Part.new(:number => i, :etag => "etag_#{i}")
320
+ end
321
+
322
+ stub_request(:post, request_path).with(:query => query)
323
+
324
+ @protocol.complete_multipart_upload(@bucket, @object, txn_id, parts)
325
+
326
+ parts_body = Nokogiri::XML::Builder.new do |xml|
327
+ xml.CompleteMultipartUpload {
328
+ parts.each do |p|
329
+ xml.Part {
330
+ xml.PartNumber p.number
331
+ xml.ETag p.etag
332
+ }
333
+ end
334
+ }
335
+ end.to_xml
336
+
337
+ expect(WebMock).to have_requested(:post, request_path)
338
+ .with(:body => parts_body, :query => query)
339
+ end
340
+
341
+ it "should raise Exception on error" do
342
+ txn_id = 'xxxyyyzzz'
343
+ query = {'uploadId' => txn_id}
344
+
345
+ code = 'InvalidDigest'
346
+ message = 'Content md5 does not match.'
347
+
348
+ stub_request(:post, request_path)
349
+ .with(:query => query)
350
+ .to_return(:status => 400, :body => mock_error(code, message))
351
+
352
+ expect {
353
+ @protocol.complete_multipart_upload(@bucket, @object, txn_id, [])
354
+ }.to raise_error(Exception, message)
355
+
356
+ expect(WebMock).to have_requested(:post, request_path)
357
+ .with(:query => query)
358
+ end
359
+ end # commit multipart
360
+
361
+ context "Abort multipart" do
362
+
363
+ it "should DELETE to abort multipart" do
364
+ txn_id = 'xxxyyyzzz'
365
+
366
+ query = {'uploadId' => txn_id}
367
+
368
+ stub_request(:delete, request_path).with(:query => query)
369
+
370
+ @protocol.abort_multipart_upload(@bucket, @object, txn_id)
371
+
372
+ expect(WebMock).to have_requested(:delete, request_path)
373
+ .with(:body => nil, :query => query)
374
+ end
375
+
376
+ it "should raise Exception on error" do
377
+ txn_id = 'xxxyyyzzz'
378
+ query = {'uploadId' => txn_id}
379
+
380
+ code = 'NoSuchUpload'
381
+ message = 'The multipart transaction does not exist.'
382
+
383
+ stub_request(:delete, request_path)
384
+ .with(:query => query)
385
+ .to_return(:status => 404, :body => mock_error(code, message))
386
+
387
+ expect {
388
+ @protocol.abort_multipart_upload(@bucket, @object, txn_id)
389
+ }.to raise_error(Exception, message)
390
+
391
+ expect(WebMock).to have_requested(:delete, request_path)
392
+ .with(:body => nil, :query => query)
393
+ end
394
+ end # abort multipart
395
+
396
+ context "List multiparts" do
397
+
398
+ it "should GET to list multiparts" do
399
+ request_path = "#{@bucket}.#{@endpoint}/"
400
+ query = {'uploads' => ''}
401
+
402
+ stub_request(:get, request_path).with(:query => query)
403
+
404
+ @protocol.list_multipart_uploads(@bucket)
405
+
406
+ expect(WebMock).to have_requested(:get, request_path)
407
+ .with(:body => nil, :query => query)
408
+ end
409
+
410
+ it "should send extra params when list multiparts" do
411
+ request_path = "#{@bucket}.#{@endpoint}/"
412
+ query = {
413
+ 'uploads' => '',
414
+ 'prefix' => 'foo-',
415
+ 'delimiter' => '-',
416
+ 'upload-id-marker' => 'id-marker',
417
+ 'key-marker' => 'key-marker',
418
+ 'max-uploads' => 10,
419
+ 'encoding-type' => KeyEncoding::URL
420
+ }
421
+
422
+ stub_request(:get, request_path).with(:query => query)
423
+
424
+ @protocol.list_multipart_uploads(
425
+ @bucket,
426
+ :prefix => 'foo-',
427
+ :delimiter => '-',
428
+ :id_marker => 'id-marker',
429
+ :key_marker => 'key-marker',
430
+ :limit => 10,
431
+ :encoding => KeyEncoding::URL
432
+ )
433
+
434
+ expect(WebMock).to have_requested(:get, request_path)
435
+ .with(:body => nil, :query => query)
436
+ end
437
+
438
+ it "should get multipart transactions" do
439
+ request_path = "#{@bucket}.#{@endpoint}/"
440
+ query = {
441
+ 'uploads' => '',
442
+ 'prefix' => 'foo-',
443
+ 'delimiter' => '-',
444
+ 'upload-id-marker' => 'id-marker',
445
+ 'key-marker' => 'key-marker',
446
+ 'max-uploads' => 100,
447
+ 'encoding-type' => KeyEncoding::URL
448
+ }
449
+
450
+ return_multiparts = (1..5).map do |i|
451
+ Multipart::Transaction.new(
452
+ :id => "id-#{i}",
453
+ :object => "key-#{i}",
454
+ :bucket => @bucket,
455
+ :creation_time => Time.parse(Time.now.rfc822))
456
+ end
457
+
458
+ return_more = {
459
+ :prefix => 'foo-',
460
+ :delimiter => '-',
461
+ :id_marker => 'id-marker',
462
+ :key_marker => 'key-marker',
463
+ :next_id_marker => 'next-id-marker',
464
+ :next_key_marker => 'next-key-marker',
465
+ :limit => 100,
466
+ :truncated => true
467
+ }
468
+ stub_request(:get, request_path)
469
+ .with(:query => query)
470
+ .to_return(:body => mock_multiparts(return_multiparts, return_more))
471
+
472
+ txns, more = @protocol.list_multipart_uploads(
473
+ @bucket,
474
+ :prefix => 'foo-',
475
+ :delimiter => '-',
476
+ :id_marker => 'id-marker',
477
+ :key_marker => 'key-marker',
478
+ :limit => 100,
479
+ :encoding => KeyEncoding::URL)
480
+
481
+ expect(WebMock).to have_requested(:get, request_path)
482
+ .with(:body => nil, :query => query)
483
+ expect(txns.map {|x| x.to_s}.join(';'))
484
+ .to eq(return_multiparts.map {|x| x.to_s}.join(';'))
485
+ expect(more).to eq(return_more)
486
+ end
487
+
488
+ it "should decode object key" do
489
+ request_path = "#{@bucket}.#{@endpoint}/"
490
+ query = {
491
+ 'uploads' => '',
492
+ 'prefix' => 'foo-',
493
+ 'delimiter' => '-',
494
+ 'upload-id-marker' => 'id-marker',
495
+ 'key-marker' => 'key-marker',
496
+ 'max-uploads' => 100,
497
+ 'encoding-type' => KeyEncoding::URL
498
+ }
499
+
500
+ return_multiparts = (1..5).map do |i|
501
+ Multipart::Transaction.new(
502
+ :id => "id-#{i}",
503
+ :object => "中国-#{i}",
504
+ :bucket => @bucket,
505
+ :creation_time => Time.parse(Time.now.rfc822))
506
+ end
507
+
508
+ es_multiparts = return_multiparts.map do |x|
509
+ Multipart::Transaction.new(
510
+ :id => x.id,
511
+ :object => CGI.escape(x.object),
512
+ :creation_time => x.creation_time)
513
+ end
514
+
515
+ return_more = {
516
+ :prefix => 'foo-',
517
+ :delimiter => '中国のruby',
518
+ :id_marker => 'id-marker',
519
+ :key_marker => '杭州のruby',
520
+ :next_id_marker => 'next-id-marker',
521
+ :next_key_marker => '西湖のruby',
522
+ :limit => 100,
523
+ :truncated => true,
524
+ :encoding => KeyEncoding::URL
525
+ }
526
+
527
+ es_more = {
528
+ :prefix => 'foo-',
529
+ :delimiter => CGI.escape('中国のruby'),
530
+ :id_marker => 'id-marker',
531
+ :key_marker => CGI.escape('杭州のruby'),
532
+ :next_id_marker => 'next-id-marker',
533
+ :next_key_marker => CGI.escape('西湖のruby'),
534
+ :limit => 100,
535
+ :truncated => true,
536
+ :encoding => KeyEncoding::URL
537
+ }
538
+
539
+ stub_request(:get, request_path)
540
+ .with(:query => query)
541
+ .to_return(:body => mock_multiparts(es_multiparts, es_more))
542
+
543
+ txns, more = @protocol.list_multipart_uploads(
544
+ @bucket,
545
+ :prefix => 'foo-',
546
+ :delimiter => '-',
547
+ :id_marker => 'id-marker',
548
+ :key_marker => 'key-marker',
549
+ :limit => 100,
550
+ :encoding => KeyEncoding::URL)
551
+
552
+ expect(WebMock).to have_requested(:get, request_path)
553
+ .with(:body => nil, :query => query)
554
+ expect(txns.map {|x| x.to_s}.join(';'))
555
+ .to eq(return_multiparts.map {|x| x.to_s}.join(';'))
556
+ expect(more).to eq(return_more)
557
+ end
558
+
559
+ it "should raise Exception on error" do
560
+ request_path = "#{@bucket}.#{@endpoint}/"
561
+ query = {'uploads' => ''}
562
+
563
+ code = 'InvalidArgument'
564
+ message = 'Invalid argument.'
565
+ stub_request(:get, request_path)
566
+ .with(:query => query)
567
+ .to_return(:status => 400, :body => mock_error(code, message))
568
+
569
+ expect {
570
+ @protocol.list_multipart_uploads(@bucket)
571
+ }.to raise_error(Exception, message)
572
+
573
+ expect(WebMock).to have_requested(:get, request_path)
574
+ .with(:body => nil, :query => query)
575
+ end
576
+ end # list multiparts
577
+
578
+ context "List parts" do
579
+
580
+ it "should GET to list parts" do
581
+ txn_id = 'yyyxxxzzz'
582
+ query = {'uploadId' => txn_id}
583
+
584
+ stub_request(:get, request_path).with(:query => query)
585
+
586
+ @protocol.list_parts(@bucket, @object, txn_id)
587
+
588
+ expect(WebMock).to have_requested(:get, request_path)
589
+ .with(:body => nil, :query => query)
590
+ end
591
+
592
+ it "should send extra params when list parts" do
593
+ txn_id = 'yyyxxxzzz'
594
+ query = {
595
+ 'uploadId' => txn_id,
596
+ 'part-number-marker' => 'foo-',
597
+ 'max-parts' => 100,
598
+ 'encoding-type' => KeyEncoding::URL
599
+ }
600
+
601
+ stub_request(:get, request_path).with(:query => query)
602
+
603
+ @protocol.list_parts(@bucket, @object, txn_id,
604
+ :marker => 'foo-',
605
+ :limit => 100,
606
+ :encoding => KeyEncoding::URL)
607
+
608
+ expect(WebMock).to have_requested(:get, request_path)
609
+ .with(:body => nil, :query => query)
610
+ end
611
+
612
+ it "should get parts" do
613
+ txn_id = 'yyyxxxzzz'
614
+ query = {
615
+ 'uploadId' => txn_id,
616
+ 'part-number-marker' => 'foo-',
617
+ 'max-parts' => 100,
618
+ 'encoding-type' => KeyEncoding::URL
619
+ }
620
+
621
+ return_parts = (1..5).map do |i|
622
+ Multipart::Part.new(
623
+ :number => i,
624
+ :etag => "etag-#{i}",
625
+ :size => 1024,
626
+ :last_modified => Time.parse(Time.now.rfc822))
627
+ end
628
+
629
+ return_more = {
630
+ :marker => 'foo-',
631
+ :next_marker => 'bar-',
632
+ :limit => 100,
633
+ :truncated => true
634
+ }
635
+
636
+ stub_request(:get, request_path)
637
+ .with(:query => query)
638
+ .to_return(:body => mock_parts(return_parts, return_more))
639
+
640
+ parts, more = @protocol.list_parts(@bucket, @object, txn_id,
641
+ :marker => 'foo-',
642
+ :limit => 100,
643
+ :encoding => KeyEncoding::URL)
644
+
645
+ expect(WebMock).to have_requested(:get, request_path)
646
+ .with(:body => nil, :query => query)
647
+ part_numbers = return_parts.map {|x| x.number}
648
+ expect(parts.map {|x| x.number}).to match_array(part_numbers)
649
+ expect(more).to eq(return_more)
650
+ end
651
+
652
+ it "should raise Exception on error" do
653
+ txn_id = 'yyyxxxzzz'
654
+ query = {'uploadId' => txn_id}
655
+
656
+ code = 'InvalidArgument'
657
+ message = 'Invalid argument.'
658
+
659
+ stub_request(:get, request_path)
660
+ .with(:query => query)
661
+ .to_return(:status => 400, :body => mock_error(code, message))
662
+
663
+ expect {
664
+ @protocol.list_parts(@bucket, @object, txn_id)
665
+ }.to raise_error(Exception, message)
666
+
667
+ expect(WebMock).to have_requested(:get, request_path)
668
+ .with(:body => nil, :query => query)
669
+ end
670
+ end # list parts
671
+
672
+ end # Multipart
673
+
674
+ end # OSS
675
+ end # Aliyun