uploadcare-ruby 1.2.2 → 2.0.0

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.
Files changed (44) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -1
  3. data/.rspec +1 -0
  4. data/.travis.yml +19 -5
  5. data/CHANGELOG.md +12 -30
  6. data/README.md +149 -72
  7. data/Rakefile +1 -1
  8. data/UPGRADE_NOTES.md +36 -0
  9. data/lib/uploadcare.rb +16 -22
  10. data/lib/uploadcare/api.rb +3 -1
  11. data/lib/uploadcare/api/file_list_api.rb +15 -4
  12. data/lib/uploadcare/api/file_storage_api.rb +34 -0
  13. data/lib/uploadcare/api/group_list_api.rb +13 -4
  14. data/lib/uploadcare/api/raw_api.rb +10 -16
  15. data/lib/uploadcare/api/uploading_api.rb +45 -84
  16. data/lib/uploadcare/api/uploading_api/upload_params.rb +72 -0
  17. data/lib/uploadcare/api/validators/file_list_options_validator.rb +73 -0
  18. data/lib/uploadcare/api/validators/group_list_options_validator.rb +49 -0
  19. data/lib/uploadcare/resources/file_list.rb +6 -33
  20. data/lib/uploadcare/resources/group_list.rb +6 -23
  21. data/lib/uploadcare/resources/resource_list.rb +83 -0
  22. data/lib/uploadcare/rest/connections/api_connection.rb +25 -3
  23. data/lib/uploadcare/rest/connections/upload_connection.rb +3 -2
  24. data/lib/uploadcare/version.rb +1 -1
  25. data/spec/api/file_list_api_spec.rb +95 -0
  26. data/spec/api/file_storage_api_spec.rb +88 -0
  27. data/spec/api/group_list_api_spec.rb +59 -0
  28. data/spec/api/raw_api_spec.rb +12 -12
  29. data/spec/api/uploading_api/upload_params_spec.rb +99 -0
  30. data/spec/api/uploading_api_spec.rb +59 -0
  31. data/spec/resources/file_list_spec.rb +13 -52
  32. data/spec/resources/file_spec.rb +6 -3
  33. data/spec/resources/group_list_spec.rb +15 -20
  34. data/spec/rest/api_connection_spec.rb +1 -1
  35. data/spec/shared/resource_list.rb +188 -0
  36. data/spec/spec_helper.rb +11 -1
  37. data/spec/uploadcare_spec.rb +9 -32
  38. data/spec/utils/parser_spec.rb +34 -36
  39. data/uploadcare-ruby.gemspec +7 -6
  40. metadata +52 -37
  41. data/lib/uploadcare/utils/user_agent.rb +0 -44
  42. data/spec/uploading/uploading_multiple_spec.rb +0 -43
  43. data/spec/uploading/uploading_spec.rb +0 -40
  44. data/spec/utils/user_agent_spec.rb +0 -46
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe Uploadcare::GroupListApi do
4
+ let(:api){ API }
5
+ subject{ api.group_list(limit: 1) }
6
+
7
+ before(:each){ allow(api).to receive(:get){ {'results' => []} } }
8
+
9
+ it 'returns a group list' do
10
+ expect( subject ).to be_a(Uploadcare::Api::GroupList)
11
+ end
12
+
13
+ it 'stores options in a group list object' do
14
+ expect( subject.options ).to eq({limit: 1})
15
+ end
16
+
17
+ describe 'validation' do
18
+ it 'passes validation when no options given' do
19
+ expect{ api.group_list }.not_to raise_error
20
+ end
21
+
22
+ it "validates that options don't have unsupported keys" do
23
+ expect{ api.group_list(unknown: 1) }.to raise_error(ArgumentError)
24
+ end
25
+
26
+ it 'validates that :limit is an integer from 1 to 1000' do
27
+ [1, 359, 1000].each do |v|
28
+ expect{ api.group_list(limit: v) }.not_to raise_error
29
+ end
30
+
31
+ [1.0, -1, 0, 1001, false].each do |v|
32
+ expect{ api.group_list(limit: v) }.to raise_error(ArgumentError)
33
+ end
34
+ end
35
+
36
+ valid_ordering = %w{datetime_created -datetime_created}
37
+ it "validates that :ordering is in [#{valid_ordering.join(', ')}]" do
38
+ valid_ordering.each do |valid_value|
39
+ expect{ api.group_list(ordering: valid_value) }.not_to raise_error
40
+ end
41
+
42
+ expect{ api.group_list(ordering: 'yes') }.to raise_error(ArgumentError)
43
+ end
44
+
45
+ describe 'from' do
46
+ it 'validates that :from.to_s is a iso8601 string' do
47
+ valid = [DateTime.now, DateTime.now.iso8601, "2017-01-01T15"]
48
+ valid.each do |value|
49
+ expect{ api.group_list(from: value) }.not_to raise_error
50
+ end
51
+
52
+ invalid = [Date.today, Time.now, DateTime.now.rfc2822, "2017-01-01", 123, false]
53
+ invalid.each do |value|
54
+ expect{ api.group_list(from: value) }.to raise_error(ArgumentError)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -3,23 +3,23 @@ require 'uri'
3
3
  require 'socket'
4
4
 
5
5
  describe Uploadcare::Api do
6
- before :each do
7
- @api = Uploadcare::Api.new(CONFIG)
8
- end
6
+ subject(:api) { Uploadcare::Api.new(CONFIG) }
9
7
 
10
8
  it "should initialize api" do
11
- @api.should be_an_instance_of(Uploadcare::Api)
9
+ is_expected.to be_an_instance_of(Uploadcare::Api)
12
10
  end
13
11
 
14
12
  it 'should respond to request methods' do
15
- @api.should respond_to :request
16
- @api.should respond_to :get
17
- @api.should respond_to :post
18
- @api.should respond_to :put
19
- @api.should respond_to :delete
13
+ is_expected.to respond_to :request
14
+ is_expected.to respond_to :get
15
+ is_expected.to respond_to :post
16
+ is_expected.to respond_to :put
17
+ is_expected.to respond_to :delete
20
18
  end
21
19
 
22
- it 'should perform custom requests' do
23
- expect { @api.request }.to_not raise_error
20
+ context 'when performing requests' do
21
+ subject(:request) { api.request }
22
+
23
+ it { is_expected.to be_a Hash }
24
24
  end
25
- end
25
+ end
@@ -0,0 +1,99 @@
1
+ require 'spec_helper'
2
+
3
+ describe Uploadcare::UploadingApi::UploadParams do
4
+ shared_examples 'handles store flag' do |key_name|
5
+ let(:true_value) { 1 }
6
+ let(:false_value) { 0 }
7
+
8
+ context 'when neither global nor per-request store option is set' do
9
+ it { is_expected.not_to include(key_name) }
10
+ end
11
+
12
+ context 'accepts :auto value in :store option' do
13
+ before { request_options.merge!(store: :auto) }
14
+ it { is_expected.to include(key_name => 'auto') }
15
+ end
16
+
17
+ context 'when only global :autostore option is set' do
18
+ context 'to true' do
19
+ before { global_options.merge!(autostore: true) }
20
+ it { is_expected.to include(key_name => true_value) }
21
+ end
22
+
23
+ context 'to false' do
24
+ before { global_options.merge!(autostore: false) }
25
+ it { is_expected.to include(key_name => false_value) }
26
+ end
27
+ end
28
+
29
+ context 'per-request :store option is set' do
30
+ context 'to true' do
31
+ before { request_options.merge!(store: true) }
32
+ it { is_expected.to include(key_name => true_value) }
33
+ end
34
+
35
+ context 'to false' do
36
+ before { request_options.merge!(store: false) }
37
+ it { is_expected.to include(key_name => false_value) }
38
+ end
39
+ end
40
+
41
+ context 'when both global and per-request store options are set' do
42
+ before do
43
+ global_options.merge!(autostore: false)
44
+ request_options.merge!(store: true)
45
+ end
46
+
47
+ it 'per-request :store option has higher presidence' do
48
+ is_expected.to include(key_name => true_value)
49
+ end
50
+ end
51
+ end
52
+
53
+ let(:global_options) { {public_key: 'test_public_key'} }
54
+ let(:request_options) { {} }
55
+
56
+ describe '#for_url_upload' do
57
+ let(:url) { 'http://example.com/image.jpg' }
58
+ subject(:upload_params) do
59
+ described_class.new(global_options, request_options).for_url_upload(url)
60
+ end
61
+
62
+ it { is_expected.to include(source_url: URI.parse(url)) }
63
+ it { is_expected.to include(pub_key: 'test_public_key') }
64
+ it_behaves_like 'handles store flag', :store
65
+
66
+ context 'works with https URLs' do
67
+ let(:url) { 'https://example.com/image.jpg' }
68
+ it { expect { upload_params }.not_to raise_error }
69
+ end
70
+
71
+ context 'if url is not http/https' do
72
+ let(:url) { 'ftp://example.com/image.jpg' }
73
+ it { expect { upload_params }.to raise_error(ArgumentError) }
74
+ end
75
+ end
76
+
77
+ describe '#for_file_upload' do
78
+ let(:files) { FILES_ARY } # contains 2 files, first is png, second is jpg
79
+ subject(:upload_params) do
80
+ described_class.new(global_options, request_options).for_file_upload(files)
81
+ end
82
+
83
+ it { is_expected.to include(UPLOADCARE_PUB_KEY: 'test_public_key') }
84
+ it_behaves_like 'handles store flag', :UPLOADCARE_STORE
85
+
86
+ context 'file params' do
87
+ subject(:file_params) { upload_params.select { |k, _| k =~ /^file\[\d+\]/ }.values }
88
+
89
+ it { expect(file_params.size).to eq(files.size) }
90
+ it { is_expected.to all(be_a(Faraday::UploadIO)) }
91
+ it { is_expected.to all(satisfy { |file| file.content_type =~ /image\/(png|jpeg)/}) }
92
+ end
93
+
94
+ context 'when any of given objects is not a File' do
95
+ let(:files) { FILES_ARY + ['not a File'] }
96
+ it { expect {upload_params}.to raise_error(ArgumentError) }
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe Uploadcare::Api do
4
+ shared_examples 'respects :store option' do
5
+ context 'respects :store option' do
6
+ let(:upload_options) { {store: store} }
7
+ subject { Array(uploaded).map(&:load_data) }
8
+
9
+ context 'when :store option is true' do
10
+ let(:store) { true }
11
+ it { is_expected.to all(be_stored) }
12
+ end
13
+
14
+ define_negated_matcher :be_not_stored, :be_stored
15
+ context 'when :store option is false' do
16
+ let(:store) { false }
17
+ it { is_expected.to all(be_not_stored) }
18
+ end
19
+ end
20
+ end
21
+
22
+ let(:api) { API }
23
+ let(:upload_options) { {} }
24
+
25
+ context 'when uploading single object' do
26
+ subject(:uploaded) { api.upload(object, upload_options) }
27
+
28
+ context 'when uploading a file' do
29
+ let(:object) { FILE1 }
30
+
31
+ it { is_expected.to be_a(Uploadcare::Api::File) }
32
+ it { is_expected.to have_attributes(uuid: match(UUID_REGEX)) }
33
+ include_examples 'respects :store option'
34
+ end
35
+
36
+ context 'when uploading from url' do
37
+ let(:object) { IMAGE_URL }
38
+
39
+ it { is_expected.to be_a(Uploadcare::Api::File) }
40
+ it { is_expected.to have_attributes(uuid: match(UUID_REGEX)) }
41
+ it { expect { api.upload('invalid.url.') }.to raise_error(ArgumentError) }
42
+ include_examples 'respects :store option'
43
+ end
44
+ end
45
+
46
+ context 'when loading multiple objects' do
47
+ before(:all) { @uploaded_files = API.upload(FILES_ARY) }
48
+ subject(:uploaded) { @uploaded_files.dup }
49
+
50
+ it { is_expected.to be_a(Array) }
51
+ it { is_expected.to all(be_a(Uploadcare::Api::File)) }
52
+ it { is_expected.to all(have_attributes(uuid: match(UUID_REGEX))) }
53
+ it { expect { uploaded.map(&:load_data) }.not_to raise_error }
54
+ end
55
+
56
+ context 'when given object is not a File, Array or String' do
57
+ it { expect { api.upload(12) }.to raise_error(ArgumentError) }
58
+ end
59
+ end
@@ -1,64 +1,25 @@
1
1
  require 'spec_helper'
2
- require 'uri'
3
- require 'socket'
4
2
 
5
- describe Uploadcare::Api::File do
3
+ describe Uploadcare::Api::FileList do
6
4
  before :all do
7
5
  @api = API
8
- @list = @api.file_list 1
9
- end
10
-
11
- it "should return file list" do
12
- @list.should be_kind_of(Uploadcare::Api::FileList)
13
- end
14
-
15
- it "should respont to results method" do
16
- @list.should respond_to :results
17
- end
18
6
 
19
- it "results should be an array" do
20
- @list.results.should be_kind_of(Array)
21
- end
7
+ # ensure that current project has at least three files
8
+ count = @api.get('/files/', limit: 3)['results'].size
9
+ (3 - count).times{ @api.upload(IMAGE_URL) } if count < 3
22
10
 
23
- it "resulst should be UC files" do
24
- @list.results.each do |file|
25
- file.should be_kind_of(Uploadcare::Api::File)
26
- end
11
+ @list = @api.file_list(limit: 1)
27
12
  end
28
13
 
29
- it "results should be not only files, but loaded files" do
30
- @list.results.each do |file|
31
- file.is_loaded?.should be true
32
- end
33
- end
14
+ let(:resource_class){ Uploadcare::Api::File }
15
+ subject{ @list }
34
16
 
35
- it "should load next page" do
36
- next_page = @list.next_page
37
- next_page.should be_kind_of(Uploadcare::Api::FileList)
38
- end
17
+ it_behaves_like 'resource list'
39
18
 
40
- it "should load prev page" do
41
- list = @api.file_list 3
42
- prev_page = list.previous_page
43
- prev_page.should be_kind_of(Uploadcare::Api::FileList)
44
- end
45
-
46
- it "should load custom page" do
47
- page = @list.go_to(@list.pages - 1)
48
- page.should be_kind_of(Uploadcare::Api::FileList)
49
- end
50
-
51
- it "should not load next page if there isn't one" do
52
- page= @list.go_to @list.pages
53
- page.next_page.should be_nil
54
- end
55
-
56
- it "should not load prev page if there isn't one" do
57
- @list.previous_page.should be_nil
58
- end
19
+ describe '#objects' do
20
+ subject{ @list.objects }
59
21
 
60
- it "should not load custom page with index more than there is actually page in project" do
61
- page = @list.go_to @list.pages + 3
62
- page.should be_nil
22
+ it{ is_expected.to all(be_a(resource_class)) }
23
+ it{ is_expected.to all(be_loaded) }
63
24
  end
64
- end
25
+ end
@@ -48,9 +48,10 @@ describe Uploadcare::Api::File do
48
48
 
49
49
  it 'should be able to tell thenever file was stored' do
50
50
  @file.load
51
- @file.is_stored?.should == false
52
- @file.store
53
- @file.is_stored?.should == true
51
+ expect(@file.stored?).to be(true)
52
+ wait_until_ready(@file)
53
+ @file.delete
54
+ expect(@file.stored?).to be(false)
54
55
  end
55
56
 
56
57
  it 'should delete itself' do
@@ -60,6 +61,7 @@ describe Uploadcare::Api::File do
60
61
  it 'should be able to tell thenever file was deleted' do
61
62
  @file.load
62
63
  @file.is_deleted?.should == false
64
+ wait_until_ready(@file)
63
65
  @file.delete
64
66
  @file.is_deleted?.should == true
65
67
  end
@@ -96,6 +98,7 @@ describe Uploadcare::Api::File do
96
98
 
97
99
  it 'should respond to datetime_removed' do
98
100
  @file.load
101
+ wait_until_ready(@file)
99
102
  @file.delete
100
103
  @file.datetime_removed.should be_kind_of(DateTime)
101
104
  @file.datetime_deleted.should be_kind_of(DateTime)
@@ -1,30 +1,25 @@
1
1
  require 'spec_helper'
2
- require 'uri'
3
- require 'socket'
4
2
 
5
- describe Uploadcare::Api::File do
3
+ describe Uploadcare::Api::GroupList do
6
4
  before :all do
7
5
  @api = API
8
- @list = @api.group_list
9
- end
10
6
 
11
- it "basic group list" do
12
- @list.should be_kind_of Uploadcare::Api::GroupList
13
- end
7
+ # ensure that current project has at least three groups
8
+ count = @api.get('/groups/', limit: 3)['results'].size
9
+ (3 - count).times{ @api.create_group([@api.upload(IMAGE_URL)]) } if count < 3
14
10
 
15
- it "should contain groups and results" do
16
- @list.should respond_to(:results)
17
- @list.should respond_to(:groups)
18
- @list.groups.should be_kind_of(Array)
11
+ @list = @api.group_list(limit: 1)
19
12
  end
20
13
 
21
- it "results should contain groups" do
22
- group = @list.groups.sample
23
- group.should be_kind_of(Uploadcare::Api::Group)
24
- end
14
+ let(:resource_class){ Uploadcare::Api::Group }
15
+ subject{ @list }
16
+
17
+ it_behaves_like 'resource list'
18
+
19
+ describe '#objects' do
20
+ subject{ @list.objects }
25
21
 
26
- it "group should no be loaded" do
27
- group = @list.groups.sample
28
- group.is_loaded?.should == false
22
+ it{ is_expected.to all(be_a(resource_class)) }
23
+ it{ is_expected.not_to include(be_loaded) }
29
24
  end
30
- end
25
+ end
@@ -19,7 +19,7 @@ describe Uploadcare::Connections::ApiConnection do
19
19
  end
20
20
 
21
21
  it 'includes correct User-Agent header' do
22
- expected = Uploadcare::UserAgent.new.call(settings)
22
+ expected = Uploadcare::user_agent(settings)
23
23
  expect(subject['User-Agent']).to eq expected
24
24
  end
25
25
  end
@@ -0,0 +1,188 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples 'resource list' do
4
+ describe '#options' do
5
+ subject{ @list.options }
6
+
7
+ it{ is_expected.to be_a(Hash) }
8
+ it{ is_expected.to be_frozen }
9
+
10
+ it 'stores options' do
11
+ expect( subject ).to eq(limit: 1)
12
+ end
13
+ end
14
+
15
+ describe '#meta' do
16
+ subject{ @list.meta }
17
+
18
+ it{ is_expected.to be_a(Hash) }
19
+ it{ is_expected.to be_frozen }
20
+ end
21
+
22
+ describe '#total' do
23
+ subject{ @list.total }
24
+
25
+ it{ is_expected.to be_an(Integer) }
26
+
27
+ it 'returns a "total" value from metadata' do
28
+ expect( subject ).to eq @list.meta["total"]
29
+ end
30
+ end
31
+
32
+ describe '#loaded' do
33
+ subject{ @list.loaded }
34
+
35
+ it{ is_expected.to be_an(Integer) }
36
+
37
+ it 'contains currently loaded objects count' do
38
+ size = rand(2..10)
39
+ allow(@list).to receive(:objects){ Array.new(size) }
40
+
41
+ expect( subject ).to eq(size)
42
+ end
43
+ end
44
+
45
+ describe '#fully_loaded?' do
46
+ subject{ @list.fully_loaded? }
47
+
48
+ context 'if there is no more next pages left' do
49
+ before { allow(@list).to receive(:meta){ {"next" => nil} } }
50
+ it { is_expected.to be_truthy }
51
+ end
52
+
53
+ context 'if there is a next page' do
54
+ before { allow(@list).to receive(:meta){ {"next" => 'example.com'} } }
55
+ it { is_expected.to be_falsey }
56
+ end
57
+ end
58
+
59
+ describe '#each' do
60
+ it 'when called without a block, returns an Enumerator' do
61
+ expect( subject.each ).to be_an(Enumerator)
62
+ end
63
+
64
+ it 'when called with a block, returns self' do
65
+ allow(subject).to receive(:meta){ {"next" => nil} }
66
+ expect( subject.each{|o| nil } ).to eq(subject)
67
+ end
68
+
69
+ it 'when called with a break inside a block, returns nil' do
70
+ expect( subject.each{|o| break } ).to be_nil
71
+ end
72
+ end
73
+
74
+ describe '#[]' do
75
+ subject{ @list.dup }
76
+
77
+ it "returns instances of a resource class" do
78
+ expect( subject[0] ).to be_a(resource_class)
79
+ end
80
+
81
+ it 'loads additional objects when needed' do
82
+ expect(@api).to receive(:get)
83
+ .with(subject.meta["next"]).and_call_original
84
+
85
+ expect { subject[1] }.to change { subject.objects.size }.by(1)
86
+ end
87
+ end
88
+
89
+ describe 'enumerable interface' do
90
+ subject{ @list.dup }
91
+
92
+ it 'is an Enumerable' do
93
+ expect( subject ).to be_an(Enumerable)
94
+ end
95
+
96
+ it 'iterates through objects' do
97
+ i, uuids = 0, []
98
+ subject.each{|object| uuids << object.uuid; i+=1; break if i >= 2 }
99
+
100
+ expect(uuids).to eq([subject[0].uuid, subject[1].uuid])
101
+ end
102
+
103
+ it 'loads additional objects when needed' do
104
+ expect(@api).to receive(:get)
105
+ .with(subject.meta["next"]).and_call_original
106
+
107
+ objects = subject.first(2)
108
+
109
+ expect(objects.size).to eq(2)
110
+ end
111
+
112
+ it 'doesn\'t load more objects than needed' do
113
+ expect(@api).to receive(:get).once.and_call_original
114
+
115
+ objects = subject.first(2)
116
+
117
+ expect(objects.size).to eq(2)
118
+ end
119
+
120
+ it "loads different objects" do
121
+ expect(@api).to receive(:get)
122
+ .with(subject.meta["next"]).and_call_original
123
+
124
+ objects = subject.first(2)
125
+
126
+ expect(objects[0].uuid).not_to eq(objects[1].uuid)
127
+ end
128
+
129
+ it 'stops loading objects when no objects left' do
130
+ allow(@api).to receive(:get).and_wrap_original do |m, *args|
131
+ m.call(*args).tap{|data| data["next"] = nil}
132
+ end
133
+
134
+ uuids = subject.map{|object| object.uuid}
135
+
136
+ expect( uuids.size ).to eq(2)
137
+ end
138
+
139
+ it 'preserves loaded objects' do
140
+ expect( subject.loaded ).to eq(1)
141
+
142
+ subject.first(2)
143
+
144
+ expect( subject.loaded ).to eq(2)
145
+ end
146
+
147
+ it 'updates #meta with data from last api response' do
148
+ new_meta = {}
149
+ allow(@api).to receive(:get).and_wrap_original do |m, *args|
150
+ m.call(*args).tap{|data| new_meta = data.reject{|k,_| k == "results"}}
151
+ end
152
+
153
+ subject.first(2)
154
+
155
+ expect( subject.meta ).to eq(new_meta)
156
+ end
157
+
158
+ if Gem.ruby_version >= Gem::Version.new('2.0.0')
159
+ context 'when lazy enumerator is used' do
160
+ it 'preserves loaded objects' do
161
+ expect(subject.loaded).to eq 1
162
+
163
+ subject.lazy.first(2)
164
+
165
+ expect(subject.loaded).to eq 2
166
+ end
167
+ end
168
+ end
169
+
170
+ context 'when a block passed to an enumerator method contains a break' do
171
+ it 'preserves loaded objects' do
172
+ i = 0
173
+ subject.each{|o| i+= 1; break if i >= 2}
174
+
175
+ expect( subject.loaded ).to eq(2)
176
+ end
177
+ end
178
+
179
+ context 'when a block passed to an enumerator method raises an exception' do
180
+ it 'preserves loaded objects' do
181
+ i = 0
182
+ subject.each{|o| i += 1; raise if i>= 2} rescue nil
183
+
184
+ expect( subject.loaded ).to eq(2)
185
+ end
186
+ end
187
+ end
188
+ end