cloudinary 1.1.1 → 1.1.2

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.
@@ -3,6 +3,8 @@ require 'digest/sha1'
3
3
  require 'zlib'
4
4
  require 'uri'
5
5
  require 'aws_cf_signer'
6
+ require 'json'
7
+ require 'cgi'
6
8
 
7
9
  class Cloudinary::Utils
8
10
  # @deprecated Use Cloudinary::SHARED_CDN
@@ -10,9 +12,9 @@ class Cloudinary::Utils
10
12
  DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION = {:width => :auto, :crop => :limit}
11
13
 
12
14
  # Warning: options are being destructively updated!
13
- def self.generate_transformation_string(options={})
15
+ def self.generate_transformation_string(options={}, allow_implicit_crop_mode = false)
14
16
  if options.is_a?(Array)
15
- return options.map{|base_transformation| generate_transformation_string(base_transformation.clone)}.join("/")
17
+ return options.map{|base_transformation| generate_transformation_string(base_transformation.clone, allow_implicit_crop_mode)}.join("/")
16
18
  end
17
19
 
18
20
  symbolize_keys!(options)
@@ -32,7 +34,7 @@ class Cloudinary::Utils
32
34
  options.delete(:width) if width && (width.to_f < 1 || no_html_sizes || width == "auto" || responsive_width)
33
35
  options.delete(:height) if height && (height.to_f < 1 || no_html_sizes || responsive_width)
34
36
 
35
- width=height=nil if crop.nil? && !has_layer && width != "auto"
37
+ width=height=nil if crop.nil? && !has_layer && width != "auto" && !allow_implicit_crop_mode
36
38
 
37
39
  background = options.delete(:background)
38
40
  background = background.sub(/^#/, 'rgb:') if background
@@ -44,7 +46,7 @@ class Cloudinary::Utils
44
46
  if base_transformations.any?{|base_transformation| base_transformation.is_a?(Hash)}
45
47
  base_transformations = base_transformations.map do
46
48
  |base_transformation|
47
- base_transformation.is_a?(Hash) ? generate_transformation_string(base_transformation.clone) : generate_transformation_string(:transformation=>base_transformation)
49
+ base_transformation.is_a?(Hash) ? generate_transformation_string(base_transformation.clone, allow_implicit_crop_mode) : generate_transformation_string({:transformation=>base_transformation}, allow_implicit_crop_mode)
48
50
  end
49
51
  else
50
52
  named_transformation = base_transformations.join(".")
@@ -87,6 +89,7 @@ class Cloudinary::Utils
87
89
  :w => width
88
90
  }
89
91
  {
92
+ :ar => :aspect_ratio,
90
93
  :ac => :audio_codec,
91
94
  :br => :bit_rate,
92
95
  :cs => :color_space,
@@ -125,7 +128,7 @@ class Cloudinary::Utils
125
128
  transformations = base_transformations << transformation
126
129
  if responsive_width
127
130
  responsive_width_transformation = Cloudinary.config.responsive_width_transformation || DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION
128
- transformations << generate_transformation_string(responsive_width_transformation.clone)
131
+ transformations << generate_transformation_string(responsive_width_transformation.clone, allow_implicit_crop_mode)
129
132
  end
130
133
 
131
134
  if width.to_s == "auto" || responsive_width
@@ -207,6 +210,8 @@ class Cloudinary::Utils
207
210
  end
208
211
  letter_spacing = layer[:letter_spacing]
209
212
  keywords.push("letter_spacing_#{letter_spacing}") unless letter_spacing.blank?
213
+ line_spacing = layer[:line_spacing]
214
+ keywords.push("line_spacing_#{line_spacing}") unless line_spacing.blank?
210
215
  if !font_size.blank? || !font_family.blank? || !keywords.empty?
211
216
  raise(CloudinaryException, "Must supply font_family for text in overlay/underlay") if font_family.blank?
212
217
  raise(CloudinaryException, "Must supply font_size for text in overlay/underlay") if font_size.blank?
@@ -225,6 +230,23 @@ class Cloudinary::Utils
225
230
  Digest::SHA1.hexdigest("#{to_sign}#{api_secret}")
226
231
  end
227
232
 
233
+ def self.generate_responsive_breakpoints_string(breakpoints)
234
+ return nil if breakpoints.nil?
235
+ breakpoints = build_array(breakpoints)
236
+
237
+ breakpoints.map do |breakpoint_settings|
238
+ unless breakpoint_settings.nil?
239
+ breakpoint_settings = breakpoint_settings.clone
240
+ transformation = breakpoint_settings.delete(:transformation) || breakpoint_settings.delete("transformation")
241
+ if transformation
242
+ breakpoint_settings[:transformation] = Cloudinary::Utils.generate_transformation_string(transformation.clone, true)
243
+ end
244
+ end
245
+ breakpoint_settings
246
+
247
+ end.to_json
248
+ end
249
+
228
250
  # Warning: options are being destructively updated!
229
251
  def self.unsigned_download_url(source, options = {})
230
252
 
@@ -354,14 +376,21 @@ class Cloudinary::Utils
354
376
  [resource_type, type]
355
377
  end
356
378
 
379
+ # Creates the URL prefix for the cloudinary resource URL
380
+ #
357
381
  # cdn_subdomain and secure_cdn_subdomain
358
- # 1) Customers in shared distribution (e.g. res.cloudinary.com)
359
- # if cdn_domain is true uses res-[1-5].cloudinary.com for both http and https. Setting secure_cdn_subdomain to false disables this for https.
360
- # 2) Customers with private cdn
361
- # if cdn_domain is true uses cloudname-res-[1-5].cloudinary.com for http
362
- # if secure_cdn_domain is true uses cloudname-res-[1-5].cloudinary.com for https (please contact support if you require this)
363
- # 3) Customers with cname
364
- # if cdn_domain is true uses a[1-5].cname for http. For https, uses the same naming scheme as 1 for shared distribution and as 2 for private distribution.
382
+ # 1. Customers in shared distribution (e.g. res.cloudinary.com)
383
+ #
384
+ # if cdn_domain is true uses res-[1-5 ].cloudinary.com for both http and https. Setting secure_cdn_subdomain to false disables this for https.
385
+ # 2. Customers with private cdn
386
+ #
387
+ # if cdn_domain is true uses cloudname-res-[1-5 ].cloudinary.com for http
388
+ #
389
+ # if secure_cdn_domain is true uses cloudname-res-[1-5 ].cloudinary.com for https (please contact support if you require this)
390
+ # 3. Customers with cname
391
+ #
392
+ # if cdn_domain is true uses a\[1-5\]\.cname for http. For https, uses the same naming scheme as 1 for shared distribution and as 2 for private distribution.
393
+ # @private
365
394
  def self.unsigned_download_url_prefix(source, cloud_name, private_cdn, cdn_subdomain, secure_cdn_subdomain, cname, secure, secure_distribution)
366
395
  return "/res#{cloud_name}" if cloud_name.start_with?("/") # For development
367
396
 
@@ -417,12 +446,53 @@ class Cloudinary::Utils
417
446
  :expires_at=>options[:expires_at] && options[:expires_at].to_i
418
447
  }, options)
419
448
 
420
- return Cloudinary::Utils.cloudinary_api_url("download", options) + "?" + cloudinary_params.to_query
449
+ return Cloudinary::Utils.cloudinary_api_url("download", options) + "?" + hash_query_params(cloudinary_params)
421
450
  end
422
451
 
452
+ # Utility method that uses the deprecated ZIP download API.
453
+ # @deprecated Replaced by {download_zip_url} that uses the more advanced and robust archive generation and download API
423
454
  def self.zip_download_url(tag, options = {})
455
+ warn "zip_download_url is deprecated. Please use download_zip_url instead."
424
456
  cloudinary_params = sign_request({:timestamp=>Time.now.to_i, :tag=>tag, :transformation=>generate_transformation_string(options)}, options)
425
- return Cloudinary::Utils.cloudinary_api_url("download_tag.zip", options) + "?" + cloudinary_params.to_query
457
+ return Cloudinary::Utils.cloudinary_api_url("download_tag.zip", options) + "?" + hash_query_params(cloudinary_params)
458
+ end
459
+
460
+ # Returns a URL that when invokes creates an archive and returns it.
461
+ # @param options [Hash]
462
+ # @option options [String|Symbol] :resource_type The resource type of files to include in the archive. Must be one of :image | :video | :raw
463
+ # @option options [String|Symbol] :type (:upload) The specific file type of resources: :upload|:private|:authenticated
464
+ # @option options [String|Symbol|Array] :tags (nil) list of tags to include in the archive
465
+ # @option options [String|Array<String>] :public_ids (nil) list of public_ids to include in the archive
466
+ # @option options [String|Array<String>] :prefixes (nil) Optional list of prefixes of public IDs (e.g., folders).
467
+ # @option options [String|Array<String>] :transformations Optional list of transformations.
468
+ # The derived images of the given transformations are included in the archive. Using the string representation of
469
+ # multiple chained transformations as we use for the 'eager' upload parameter.
470
+ # @option options [String|Symbol] :mode (:create) return the generated archive file or to store it as a raw resource and
471
+ # return a JSON with URLs for accessing the archive. Possible values: :download, :create
472
+ # @option options [String|Symbol] :target_format (:zip)
473
+ # @option options [String] :target_public_id Optional public ID of the generated raw resource.
474
+ # Relevant only for the create mode. If not specified, random public ID is generated.
475
+ # @option options [boolean] :flatten_folders (false) If true, flatten public IDs with folders to be in the root of the archive.
476
+ # Add numeric counter to the file name in case of a name conflict.
477
+ # @option options [boolean] :flatten_transformations (false) If true, and multiple transformations are given,
478
+ # flatten the folder structure of derived images and store the transformation details on the file name instead.
479
+ # @option options [boolean] :use_original_filename Use the original file name of included images (if available) instead of the public ID.
480
+ # @option options [boolean] :async (false) If true, return immediately and perform the archive creation in the background.
481
+ # Relevant only for the create mode.
482
+ # @option options [String] :notification_url Optional URL to send an HTTP post request (webhook) when the archive creation is completed.
483
+ # @option options [String|Array<String] :target_tags Optional array. Allows assigning one or more tag to the generated archive file (for later housekeeping via the admin API).
484
+ # @option options [String] :keep_derived (false) keep the derived images used for generating the archive
485
+ # @return [String] archive url
486
+ def self.download_archive_url(options = {})
487
+ cloudinary_params = sign_request(Cloudinary::Utils.archive_params(options.merge(:mode => "download")), options)
488
+ return Cloudinary::Utils.cloudinary_api_url("generate_archive", options) + "?" + hash_query_params(cloudinary_params)
489
+ end
490
+
491
+
492
+ # Returns a URL that when invokes creates an zip archive and returns it.
493
+ # @see download_archive_url
494
+ def self.download_zip_url(options = {})
495
+ download_archive_url(options.merge(:target_format => "zip"))
426
496
  end
427
497
 
428
498
  def self.signed_download_url(public_id, options = {})
@@ -591,8 +661,62 @@ class Cloudinary::Utils
591
661
  end
592
662
  end
593
663
 
664
+ # Returns a Hash of parameters used to create an archive
665
+ # @param [Hash] options
666
+ # @private
667
+ def self.archive_params(options = {})
668
+ options = Cloudinary::Utils.symbolize_keys options
669
+ {
670
+ :timestamp=>(options[:timestamp] || Time.now.to_i),
671
+ :type=>options[:type],
672
+ :mode => options[:mode],
673
+ :target_format => options[:target_format],
674
+ :target_public_id=> options[:target_public_id],
675
+ :flatten_folders=>Cloudinary::Utils.as_safe_bool(options[:flatten_folders]),
676
+ :flatten_transformations=>Cloudinary::Utils.as_safe_bool(options[:flatten_transformations]),
677
+ :use_original_filename=>Cloudinary::Utils.as_safe_bool(options[:use_original_filename]),
678
+ :async=>Cloudinary::Utils.as_safe_bool(options[:async]),
679
+ :notification_url=>options[:notification_url],
680
+ :target_tags=>options[:target_tags] && Cloudinary::Utils.build_array(options[:target_tags]),
681
+ :keep_derived=>Cloudinary::Utils.as_safe_bool(options[:keep_derived]),
682
+ :tags=>options[:tags] && Cloudinary::Utils.build_array(options[:tags]),
683
+ :public_ids=>options[:public_ids] && Cloudinary::Utils.build_array(options[:public_ids]),
684
+ :prefixes=>options[:prefixes] && Cloudinary::Utils.build_array(options[:prefixes]),
685
+ :transformations => build_eager(options[:transformations])
686
+ }
687
+ end
688
+
689
+ # @private
690
+ def self.build_eager(eager)
691
+ return nil if eager.nil?
692
+ Cloudinary::Utils.build_array(eager).map do
693
+ |transformation, format|
694
+ transformation = transformation.clone
695
+ format = transformation.delete(:format) || format
696
+ [Cloudinary::Utils.generate_transformation_string(transformation, true), format].compact.join("/")
697
+ end.join("|")
698
+ end
699
+
594
700
  private
595
701
 
702
+ def self.hash_query_params(hash)
703
+ if hash.respond_to?("to_query")
704
+ hash.to_query
705
+ else
706
+ flat_hash_to_query_params(hash)
707
+ end
708
+ end
709
+
710
+ def self.flat_hash_to_query_params(hash)
711
+ hash.collect do |key, value|
712
+ if value.is_a?(Array)
713
+ value.map{|v| "#{CGI.escape(key.to_s)}[]=#{CGI.escape(v.to_s)}"}.join("&")
714
+ else
715
+ "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"
716
+ end
717
+ end.compact.sort!.join('&')
718
+ end
719
+
596
720
  def self.number_pattern
597
721
  "([0-9]*)\\.([0-9]+)|([0-9]+)"
598
722
  end
@@ -1,4 +1,4 @@
1
1
  # Copyright Cloudinary
2
2
  module Cloudinary
3
- VERSION = "1.1.1"
3
+ VERSION = "1.1.2"
4
4
  end
@@ -3,29 +3,25 @@ require 'cloudinary'
3
3
 
4
4
  describe Cloudinary::Api do
5
5
  break puts("Please setup environment for api test to run") if Cloudinary.config.api_secret.blank?
6
+ include_context "cleanup"
6
7
 
8
+ prefix = "api_test_#{Time.now.to_i}"
9
+ test_id_1 = "#{prefix}_1"
10
+ test_id_2 = "#{prefix}_2"
7
11
  before(:all) do
8
12
  @timestamp_tag = "api_test_tag_#{Time.now.to_i}"
9
13
  @api = Cloudinary::Api
10
- Cloudinary::Uploader.destroy("api_test")
11
- Cloudinary::Uploader.destroy("api_test2")
12
- Cloudinary::Uploader.upload("spec/logo.png", :public_id=>"api_test", :tags=> ["api_test_tag", @timestamp_tag], :context => "key=value", :eager=>[:width=>100,:crop=>:scale])
13
- Cloudinary::Uploader.upload("spec/logo.png", :public_id=>"api_test2", :tags=> ["api_test_tag", @timestamp_tag], :context => "key=value", :eager=>[:width=>100,:crop=>:scale])
14
- @api.delete_transformation("api_test_transformation") rescue nil
15
- @api.delete_transformation("api_test_transformation2") rescue nil
16
- @api.delete_transformation("api_test_transformation3") rescue nil
17
- @api.delete_upload_preset("api_test_upload_preset") rescue nil
18
- @api.delete_upload_preset("api_test_upload_preset2") rescue nil
19
- @api.delete_upload_preset("api_test_upload_preset3") rescue nil
20
- @api.delete_upload_preset("api_test_upload_preset4") rescue nil
14
+ Cloudinary::Uploader.upload("spec/logo.png", :public_id => test_id_1, :tags => [TEST_TAG, @timestamp_tag], :context => "key=value", :eager =>[:width =>100, :crop =>:scale])
15
+ Cloudinary::Uploader.upload("spec/logo.png", :public_id => test_id_2, :tags => [TEST_TAG, @timestamp_tag], :context => "key=value", :eager =>[:width =>100, :crop =>:scale])
21
16
  end
22
-
17
+
23
18
  it "should allow listing resource_types" do
24
19
  expect(@api.resource_types()["resource_types"]).to include("image")
25
20
  end
26
21
 
27
22
  it "should allow listing resources" do
28
- resource = @api.resources()["resources"].find{|resource| resource["public_id"] == "api_test"}
23
+ resource = @api.resources()["resources"].find{|resource| resource["public_id"] == test_id_1
24
+ }
29
25
  expect(resource).not_to be_blank
30
26
  expect(resource["type"]).to eq("upload")
31
27
  end
@@ -43,30 +39,33 @@ describe Cloudinary::Api do
43
39
 
44
40
 
45
41
  it "should allow listing resources by type" do
46
- resource = @api.resources(:type=>"upload", :tags=>true)["resources"].find{|resource| resource["public_id"] == "api_test"}
42
+ resource = @api.resources(:type=>"upload", :tags=>true)["resources"].find{|resource| resource["public_id"] == test_id_1
43
+ }
47
44
  expect(resource).not_to be_blank
48
- expect(resource["tags"]).to eq(["api_test_tag", @timestamp_tag])
45
+ expect(resource["tags"]).to match_array([TEST_TAG, @timestamp_tag])
49
46
  end
50
47
 
51
48
  it "should allow listing resources by prefix" do
52
- resources = @api.resources(:type=>"upload", :prefix=>"api_test", :tags => true, :context => true)["resources"]
53
- expect(resources.map{|resource| resource["public_id"]}).to include("api_test", "api_test2")
54
- expect(resources.map{|resource| resource["tags"]}).to include(["api_test_tag", @timestamp_tag])
49
+ resources = @api.resources(:type =>"upload", :prefix => prefix, :tags => true, :context => true)["resources"]
50
+ expect(resources.map{|resource| resource["public_id"]}).to include(test_id_1, test_id_2)
51
+ expect(resources.map{|resource| resource["tags"]}.flatten).to include(TEST_TAG, @timestamp_tag)
55
52
  expect(resources.map{|resource| resource["context"]}).to include({"custom" => {"key" => "value"}})
56
53
  end
57
54
 
58
55
  it "should allow listing resources by tag" do
59
- resources = @api.resources_by_tag("api_test_tag", :tags => true, :context => true)["resources"]
60
- expect(resources.find{|resource| resource["public_id"] == "api_test"}).not_to be_blank
61
- expect(resources.map{|resource| resource["tags"]}).to include(["api_test_tag", @timestamp_tag])
56
+ resources = @api.resources_by_tag(TEST_TAG, :tags => true, :context => true)["resources"]
57
+ expect(resources.find{|resource| resource["public_id"] == test_id_1
58
+ }).not_to be_blank
59
+ expect(resources.map{|resource| resource["tags"]}.flatten).to include(TEST_TAG, @timestamp_tag)
62
60
  expect(resources.map{|resource| resource["context"]}).to include({"custom" => {"key" => "value"}})
63
61
  end
64
62
 
65
63
  it "should allow listing resources by public ids" do
66
- resources = @api.resources_by_ids(["api_test", "api_test2"], :tags => true, :context => true)["resources"]
64
+ resources = @api.resources_by_ids([test_id_1, test_id_2], :tags => true, :context => true)["resources"]
67
65
  expect(resources.length).to eq(2)
68
- expect(resources.find{|resource| resource["public_id"] == "api_test"}).not_to be_blank
69
- expect(resources.map{|resource| resource["tags"]}).to include(["api_test_tag", @timestamp_tag])
66
+ expect(resources.find{|resource| resource["public_id"] == test_id_1
67
+ }).not_to be_blank
68
+ expect(resources.map{|resource| resource["tags"]}.flatten).to include(TEST_TAG, @timestamp_tag)
70
69
  expect(resources.map{|resource| resource["context"]}).to include({"custom" => {"key" => "value"}})
71
70
  end
72
71
 
@@ -93,9 +92,9 @@ describe Cloudinary::Api do
93
92
  end
94
93
 
95
94
  it "should allow get resource metadata" do
96
- resource = @api.resource("api_test")
95
+ resource = @api.resource(test_id_1)
97
96
  expect(resource).not_to be_blank
98
- expect(resource["public_id"]).to eq("api_test")
97
+ expect(resource["public_id"]).to eq(test_id_1)
99
98
  expect(resource["bytes"]).to eq(3381)
100
99
  expect(resource["derived"].length).to eq(1)
101
100
  end
@@ -116,7 +115,7 @@ describe Cloudinary::Api do
116
115
  Cloudinary::Uploader.upload("spec/logo.png", :public_id=>"api_test3")
117
116
  resource = @api.resource("api_test3")
118
117
  expect(resource).not_to be_blank
119
- @api.delete_resources(["apit_test", "api_test2", "api_test3"])
118
+ @api.delete_resources(["apit_test", test_id_2, "api_test3"])
120
119
  expect{@api.resource("api_test3")}.to raise_error(Cloudinary::Api::NotFound)
121
120
  end
122
121
 
@@ -129,7 +128,7 @@ describe Cloudinary::Api do
129
128
  end
130
129
 
131
130
  it "should allow deleting resources by tags" do
132
- Cloudinary::Uploader.upload("spec/logo.png", :public_id=>"api_test4", :tags=>["api_test_tag_for_delete"])
131
+ Cloudinary::Uploader.upload("spec/logo.png", :public_id=>"api_test4", :tags=>[TEST_TAG, "api_test_tag_for_delete"])
133
132
  resource = @api.resource("api_test4")
134
133
  expect(resource).not_to be_blank
135
134
  @api.delete_resources_by_tag("api_test_tag_for_delete")
@@ -138,86 +137,91 @@ describe Cloudinary::Api do
138
137
 
139
138
  it "should allow listing tags" do
140
139
  tags = @api.tags()["tags"]
141
- expect(tags).to include('api_test_tag')
140
+ expect(tags).to include(TEST_TAG)
142
141
  end
143
142
 
144
143
  it "should allow listing tag by prefix" do
145
- tags = @api.tags(:prefix=>"api_test")["tags"]
146
- expect(tags).to include('api_test_tag')
144
+ tags = @api.tags(:prefix=> "api_test_tag")["tags"]
145
+ expect(tags).to include(@timestamp_tag)
147
146
  tags = @api.tags(:prefix=>"api_test_no_such_tag")["tags"]
148
147
  expect(tags).to be_blank
149
148
  end
150
149
 
151
- it "should allow listing transformations" do
152
- transformation = @api.transformations()["transformations"].find{|transformation| transformation["name"] == "c_scale,w_100"}
153
- expect(transformation).not_to be_blank
154
- expect(transformation["used"]).to eq(true)
155
- end
156
-
157
- it "should allow getting transformation metadata" do
158
- transformation = @api.transformation("c_scale,w_100")
159
- expect(transformation).not_to be_blank
160
- expect(transformation["info"]).to eq(["crop"=>"scale", "width"=>100] )
161
- transformation = @api.transformation("crop"=>"scale", "width"=>100)
162
- expect(transformation).not_to be_blank
163
- expect(transformation["info"]).to eq(["crop"=>"scale", "width"=>100] )
164
- end
165
-
166
- it "should allow updating transformation allowed_for_strict" do
167
- @api.update_transformation("c_scale,w_100", :allowed_for_strict=>true)
168
- transformation = @api.transformation("c_scale,w_100")
169
- expect(transformation).not_to be_blank
170
- expect(transformation["allowed_for_strict"]).to eq(true)
171
- @api.update_transformation("c_scale,w_100", :allowed_for_strict=>false)
172
- transformation = @api.transformation("c_scale,w_100")
173
- expect(transformation).not_to be_blank
174
- expect(transformation["allowed_for_strict"]).to eq(false)
175
- end
176
- describe "named transformations" do
177
- it "should allow creating named transformation" do
178
- @api.create_transformation("api_test_transformation", "crop"=>"scale", "width"=>102)
179
- transformation = @api.transformation("api_test_transformation")
150
+ describe 'transformations' do
151
+ it "should allow listing transformations" do
152
+ transformation = @api.transformations()["transformations"].find { |transformation| transformation["name"] == "c_scale,w_100" }
180
153
  expect(transformation).not_to be_blank
181
- expect(transformation["allowed_for_strict"]).to eq(true)
182
- expect(transformation["info"]).to eq(["crop"=>"scale", "width"=>102])
183
- expect(transformation["used"]).to eq(false)
154
+ expect(transformation["used"]).to eq(true)
184
155
  end
185
156
 
186
- it "should allow deleting named transformation" do
187
- @api.create_transformation("api_test_transformation2", "crop"=>"scale", "width"=>103)
188
- @api.transformation("api_test_transformation2")
189
- @api.delete_transformation("api_test_transformation2")
190
- expect{@api.transformation("api_test_transformation2")}.to raise_error(Cloudinary::Api::NotFound)
157
+ it "should allow getting transformation metadata" do
158
+ transformation = @api.transformation("c_scale,w_100")
159
+ expect(transformation).not_to be_blank
160
+ expect(transformation["info"]).to eq(["crop" => "scale", "width" => 100])
161
+ transformation = @api.transformation("crop" => "scale", "width" => 100)
162
+ expect(transformation).not_to be_blank
163
+ expect(transformation["info"]).to eq(["crop" => "scale", "width" => 100])
191
164
  end
192
165
 
193
- it "should allow unsafe update of named transformation" do
194
- @api.create_transformation("api_test_transformation3", "crop"=>"scale", "width"=>102)
195
- @api.update_transformation("api_test_transformation3", :unsafe_update=>{"crop"=>"scale", "width"=>103})
196
- transformation = @api.transformation("api_test_transformation3")
166
+ it "should allow updating transformation allowed_for_strict" do
167
+ @api.update_transformation("c_scale,w_100", :allowed_for_strict => true)
168
+ transformation = @api.transformation("c_scale,w_100")
169
+ expect(transformation).not_to be_blank
170
+ expect(transformation["allowed_for_strict"]).to eq(true)
171
+ @api.update_transformation("c_scale,w_100", :allowed_for_strict => false)
172
+ transformation = @api.transformation("c_scale,w_100")
197
173
  expect(transformation).not_to be_blank
198
- expect(transformation["info"]).to eq(["crop"=>"scale", "width"=>103])
199
- expect(transformation["used"]).to eq(false)
174
+ expect(transformation["allowed_for_strict"]).to eq(false)
200
175
  end
201
176
 
202
- end
203
- it "should allow deleting implicit transformation" do
204
- @api.transformation("c_scale,w_100")
205
- @api.delete_transformation("c_scale,w_100")
206
- expect{@api.transformation("c_scale,w_100")}.to raise_error(Cloudinary::Api::NotFound)
177
+ describe "named transformations" do
178
+ it "should allow creating named transformation" do
179
+ public_id = "api_test_transformation_#{Time.now.to_i}"
180
+ @api.create_transformation(public_id, "crop" => "scale", "width" => 102, :tags => TEST_TAG)
181
+ transformation = @api.transformation(public_id)
182
+ expect(transformation).not_to be_blank
183
+ expect(transformation["allowed_for_strict"]).to eq(true)
184
+ expect(transformation["info"]).to eq(["crop" => "scale", "width" => 102])
185
+ expect(transformation["used"]).to eq(false)
186
+ end
187
+
188
+ it "should allow deleting named transformation" do
189
+ public_id = "api_test_transformation_#{Time.now.to_i}"
190
+ @api.create_transformation(public_id, "crop" => "scale", "width" => 103, :tags => TEST_TAG)
191
+ @api.transformation(public_id)
192
+ @api.delete_transformation(public_id)
193
+ expect { @api.transformation(public_id) }.to raise_error(Cloudinary::Api::NotFound)
194
+ end
195
+
196
+ it "should allow unsafe update of named transformation" do
197
+ public_id = "api_test_transformation_#{Time.now.to_i}"
198
+ @api.create_transformation(public_id, "crop" => "scale", "width" => 102, :tags => TEST_TAG)
199
+ @api.update_transformation(public_id, :unsafe_update => { "crop" => "scale", "width" => 103 })
200
+ transformation = @api.transformation(public_id)
201
+ expect(transformation).not_to be_blank
202
+ expect(transformation["info"]).to eq(["crop" => "scale", "width" => 103])
203
+ expect(transformation["used"]).to eq(false)
204
+ end
205
+
206
+ end
207
+ it "should allow deleting implicit transformation" do
208
+ @api.transformation("c_scale,w_100")
209
+ @api.delete_transformation("c_scale,w_100")
210
+ expect { @api.transformation("c_scale,w_100") }.to raise_error(Cloudinary::Api::NotFound)
211
+ end
207
212
  end
208
213
 
209
214
  it "should allow creating and listing upload_presets", :upload_preset => true do
210
- @api.create_upload_preset(:name => "api_test_upload_preset", :folder => "folder")
211
- @api.create_upload_preset(:name => "api_test_upload_preset2", :folder => "folder2")
212
- @api.create_upload_preset(:name => "api_test_upload_preset3", :folder => "folder3")
213
- expect(@api.upload_presets["presets"].first(3).map{|p| p["name"]}).to eq(["api_test_upload_preset3", "api_test_upload_preset2", "api_test_upload_preset"])
214
- @api.delete_upload_preset("api_test_upload_preset")
215
- @api.delete_upload_preset("api_test_upload_preset2")
216
- @api.delete_upload_preset("api_test_upload_preset3")
215
+ name = []
216
+ 3.times { |i| name.push( "api_test_#{Time.now.to_i}_#{i}")}
217
+ @api.create_upload_preset(:name => name[0], :folder => "folder", :tags => TEST_TAG)
218
+ @api.create_upload_preset(:name => name[1], :folder => "folder2", :tags => TEST_TAG)
219
+ @api.create_upload_preset(:name => name[2], :folder => "folder3", :tags => TEST_TAG)
220
+ expect(@api.upload_presets["presets"].first(3).map{|p| p["name"]}).to match_array(name)
217
221
  end
218
222
 
219
223
  it "should allow getting a single upload_preset", :upload_preset => true do
220
- result = @api.create_upload_preset(:unsigned => true, :folder => "folder", :width => 100, :crop => :scale, :tags => ["a","b","c"], :context => {:a => "b", :c => "d"})
224
+ result = @api.create_upload_preset(:unsigned => true, :folder => "folder", :width => 100, :crop => :scale, :tags => ["a","b","c", TEST_TAG], :context => {:a => "b", :c => "d"})
221
225
  name = result["name"]
222
226
  preset = @api.upload_preset(name)
223
227
  expect(preset["name"]).to eq(name)
@@ -225,31 +229,29 @@ describe Cloudinary::Api do
225
229
  expect(preset["settings"]["folder"]).to eq("folder")
226
230
  expect(preset["settings"]["transformation"]).to eq([{"width" => 100, "crop" => "scale"}])
227
231
  expect(preset["settings"]["context"]).to eq({"a" => "b", "c" => "d"})
228
- expect(preset["settings"]["tags"]).to eq(["a","b","c"])
229
- @api.delete_upload_preset(name)
232
+ expect(preset["settings"]["tags"]).to eq(["a","b","c", TEST_TAG])
230
233
  end
231
234
 
232
235
  it "should allow deleting upload_presets", :upload_preset => true do
233
- @api.create_upload_preset(:name => "api_test_upload_preset4", :folder => "folder")
236
+ @api.create_upload_preset(:name => "api_test_upload_preset4", :folder => "folder", :tags => TEST_TAG)
234
237
  preset = @api.upload_preset("api_test_upload_preset4")
235
238
  @api.delete_upload_preset("api_test_upload_preset4")
236
239
  expect{preset = @api.upload_preset("api_test_upload_preset4")}.to raise_error
237
240
  end
238
241
 
239
242
  it "should allow updating upload_presets", :upload_preset => true do
240
- name = @api.create_upload_preset(:folder => "folder")["name"]
243
+ name = @api.create_upload_preset(:folder => "folder", :tags => TEST_TAG)["name"]
241
244
  preset = @api.upload_preset(name)
242
245
  @api.update_upload_preset(name, preset["settings"].merge(:colors => true, :unsigned => true, :disallow_public_id => true))
243
246
  preset = @api.upload_preset(name)
244
247
  expect(preset["name"]).to eq(name)
245
248
  expect(preset["unsigned"]).to eq(true)
246
- expect(preset["settings"]).to eq({"folder" => "folder", "colors" => true, "disallow_public_id" => true})
247
- @api.delete_upload_preset(name)
249
+ expect(preset["settings"]).to eq({"folder" => "folder", "colors" => true, "disallow_public_id" => true, "tags" => [TEST_TAG]})
248
250
  end
249
251
 
250
252
  # this test must be last because it deletes (potentially) all dependent transformations which some tests rely on. Excluded by default.
251
- it "should allow deleting all resources", :delete_all=>true do
252
- Cloudinary::Uploader.upload("spec/logo.png", :public_id=>"api_test5", :eager=>[:width=>101,:crop=>:scale])
253
+ skip "should allow deleting all resources", :delete_all=>true do
254
+ Cloudinary::Uploader.upload("spec/logo.png", :public_id=>"api_test5", :eager=>[:width=>101,:crop=>:scale], :tags => TEST_TAG)
253
255
  resource = @api.resource("api_test5")
254
256
  expect(resource).not_to be_blank
255
257
  expect(resource["derived"].length).to eq(1)
@@ -260,7 +262,7 @@ describe Cloudinary::Api do
260
262
  end
261
263
 
262
264
  it "should support setting manual moderation status" do
263
- result = Cloudinary::Uploader.upload("spec/logo.png", {:moderation => :manual})
265
+ result = Cloudinary::Uploader.upload("spec/logo.png", {:moderation => :manual, :tags => TEST_TAG})
264
266
  expect(result["moderation"][0]["status"]).to eq("pending")
265
267
  expect(result["moderation"][0]["kind"]).to eq("manual")
266
268
  api_result = Cloudinary::Api.update(result["public_id"], {:moderation_status => :approved})
@@ -269,29 +271,29 @@ describe Cloudinary::Api do
269
271
  end
270
272
 
271
273
  it "should support requesting raw conversion" do
272
- result = Cloudinary::Uploader.upload("spec/docx.docx", :resource_type => :raw)
274
+ result = Cloudinary::Uploader.upload("spec/docx.docx", :resource_type => :raw, :tags => TEST_TAG)
273
275
  expect{Cloudinary::Api.update(result["public_id"], {:resource_type => :raw, :raw_convert => :illegal})}.to raise_error(Cloudinary::Api::BadRequest, /^Illegal value|not a valid/)
274
276
  end
275
277
 
276
278
  it "should support requesting categorization" do
277
- result = Cloudinary::Uploader.upload("spec/logo.png")
279
+ result = Cloudinary::Uploader.upload("spec/logo.png", :tags => TEST_TAG)
278
280
  expect{Cloudinary::Api.update(result["public_id"], {:categorization => :illegal})}.to raise_error(Cloudinary::Api::BadRequest, /^Illegal value/)
279
281
  end
280
282
 
281
283
  it "should support requesting detection" do
282
- result = Cloudinary::Uploader.upload("spec/logo.png")
284
+ result = Cloudinary::Uploader.upload("spec/logo.png", :tags => TEST_TAG)
283
285
  expect{Cloudinary::Api.update(result["public_id"], {:detection => :illegal})}.to raise_error(Cloudinary::Api::BadRequest, /^Illegal value/)
284
286
  end
285
287
 
286
288
  it "should support requesting auto_tagging" do
287
- result = Cloudinary::Uploader.upload("spec/logo.png")
289
+ result = Cloudinary::Uploader.upload("spec/logo.png", :tags => TEST_TAG)
288
290
  expect{Cloudinary::Api.update(result["public_id"], {:auto_tagging => 0.5})}.to raise_error(Cloudinary::Api::BadRequest, /^Must use/)
289
291
  end
290
292
 
291
293
  it "should support listing by moderation kind and value" do
292
- result1 = Cloudinary::Uploader.upload("spec/logo.png", {:moderation => :manual})
293
- result2 = Cloudinary::Uploader.upload("spec/logo.png", {:moderation => :manual})
294
- result3 = Cloudinary::Uploader.upload("spec/logo.png", {:moderation => :manual})
294
+ result1 = Cloudinary::Uploader.upload("spec/logo.png", { :moderation => :manual, :tags => TEST_TAG})
295
+ result2 = Cloudinary::Uploader.upload("spec/logo.png", { :moderation => :manual, :tags => TEST_TAG})
296
+ result3 = Cloudinary::Uploader.upload("spec/logo.png", { :moderation => :manual, :tags => TEST_TAG})
295
297
  Cloudinary::Api.update(result1["public_id"], {:moderation_status => :approved})
296
298
  Cloudinary::Api.update(result2["public_id"], {:moderation_status => :rejected})
297
299
  approved = Cloudinary::Api.resources_by_moderation(:manual, :approved, :max_results => 1000)["resources"].map{|r| r["public_id"]}
@@ -309,20 +311,71 @@ describe Cloudinary::Api do
309
311
  end
310
312
 
311
313
  it "should support listing folders" do
312
- pending("For this test to work, 'Auto-create folders' should be enabled in the Upload Settings, " +
313
- "and the account should be empty of folders. " +
314
- "Comment out this line if you really want to test it.")
315
- Cloudinary::Uploader.upload("spec/logo.png", {:public_id => "test_folder1/item"})
316
- Cloudinary::Uploader.upload("spec/logo.png", {:public_id => "test_folder2/item"})
317
- Cloudinary::Uploader.upload("spec/logo.png", {:public_id => "test_folder1/test_subfolder1/item"})
318
- Cloudinary::Uploader.upload("spec/logo.png", {:public_id => "test_folder1/test_subfolder2/item"})
314
+ # pending("For this test to work, 'Auto-create folders' should be enabled in the Upload Settings, " +
315
+ # "and the account should be empty of folders. " +
316
+ # "Comment out this line if you really want to test it.")
317
+ Cloudinary::Uploader.upload("spec/logo.png", {:public_id => "test_folder1/item", :tags => TEST_TAG})
318
+ Cloudinary::Uploader.upload("spec/logo.png", {:public_id => "test_folder2/item", :tags => TEST_TAG})
319
+ Cloudinary::Uploader.upload("spec/logo.png", {:public_id => "test_folder1/test_subfolder1/item", :tags => TEST_TAG})
320
+ Cloudinary::Uploader.upload("spec/logo.png", {:public_id => "test_folder1/test_subfolder2/item", :tags => TEST_TAG})
319
321
  result = Cloudinary::Api.root_folders
320
- expect(result["folders"][0]["name"]).to eq("test_folder1")
321
- expect(result["folders"][1]["name"]).to eq("test_folder2")
322
+ expect(result["folders"]).not_to be_empty, "Folders should have been created. Make sure that 'Auto-create folders' is enabled for this cloud"
323
+ names = result["folders"].map{|f| f["name"]}
324
+ expect(names).to include("test_folder1")
325
+ expect(names).to include("test_folder2")
322
326
  result = Cloudinary::Api.subfolders("test_folder1")
323
- expect(result["folders"][0]["path"]).to eq("test_folder1/test_subfolder1")
324
- expect(result["folders"][1]["path"]).to eq("test_folder1/test_subfolder2")
327
+
328
+ paths = result["folders"].map{|f| f["path"]}
329
+ expect(paths).to include("test_folder1/test_subfolder1")
330
+ expect(paths).to include("test_folder1/test_subfolder2")
325
331
  expect{Cloudinary::Api.subfolders("test_folder")}.to raise_error(Cloudinary::Api::NotFound)
326
- Cloudinary::Api.delete_resources_by_prefix("test_folder")
332
+ end
333
+
334
+ describe '.restore' do
335
+ before :each do
336
+ Cloudinary::Uploader.upload("spec/logo.png", :public_id => "api_test_restore", :backup => true, :tags => TEST_TAG)
337
+ resource = Cloudinary::Api.resource("api_test_restore")
338
+ expect(resource).not_to be_nil
339
+ expect(resource["bytes"]).to eq(3381)
340
+ Cloudinary::Api.delete_resources("api_test_restore")
341
+ resource = Cloudinary::Api.resource("api_test_restore")
342
+ expect(resource).not_to be_nil
343
+ expect(resource["bytes"]).to eq(0)
344
+ expect(resource["placeholder"]).to eq(true)
345
+ end
346
+ it 'should restore a deleted resource' do
347
+ response = Cloudinary::Api.restore("api_test_restore")
348
+ info = response["api_test_restore"]
349
+ expect(info).not_to be_nil
350
+ expect(info["bytes"]).to eq(3381)
351
+ resource = Cloudinary::Api.resource("api_test_restore")
352
+ expect(resource).not_to be_nil
353
+ expect(resource["bytes"]).to eq(3381)
354
+ end
355
+ end
356
+
357
+ describe 'mapping' do
358
+ mapping = "api_test_upload_mapping#{rand(100000)}"
359
+ after :all do
360
+ begin
361
+ Cloudinary::Api.delete_upload_mapping(mapping)
362
+ rescue
363
+ puts "Could not delete #{mapping}"
364
+ end
365
+ end
366
+
367
+ it 'should create mapping' do
368
+ result = Cloudinary::Api.create_upload_mapping(mapping, :template =>"http://cloudinary.com")
369
+ result = Cloudinary::Api.upload_mapping(mapping)
370
+ expect(result['template']).to eq("http://cloudinary.com")
371
+ Cloudinary::Api.update_upload_mapping(mapping, "template" =>"http://res.cloudinary.com")
372
+ result = Cloudinary::Api.upload_mapping(mapping)
373
+ expect(result["template"]).to eq("http://res.cloudinary.com")
374
+ result = Cloudinary::Api.upload_mappings
375
+ expect(result["mappings"]).to include("folder" => mapping, "template" =>"http://res.cloudinary.com" )
376
+ Cloudinary::Api.delete_upload_mapping(mapping)
377
+ result = Cloudinary::Api.upload_mappings()
378
+ expect(result["mappings"]).not_to include(mapping )
379
+ end
327
380
  end
328
381
  end