uploadcare-ruby 1.2.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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