cloudinary 1.1.1 → 1.1.2

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