paperclip-cloudfiles 2.3.1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. data/LICENSE +26 -0
  2. data/README.rdoc +176 -0
  3. data/Rakefile +105 -0
  4. data/generators/paperclip/USAGE +5 -0
  5. data/generators/paperclip/paperclip_generator.rb +27 -0
  6. data/generators/paperclip/templates/paperclip_migration.rb.erb +19 -0
  7. data/init.rb +1 -0
  8. data/lib/paperclip.rb +356 -0
  9. data/lib/paperclip/attachment.rb +414 -0
  10. data/lib/paperclip/callback_compatability.rb +33 -0
  11. data/lib/paperclip/geometry.rb +115 -0
  12. data/lib/paperclip/interpolations.rb +108 -0
  13. data/lib/paperclip/iostream.rb +59 -0
  14. data/lib/paperclip/matchers.rb +4 -0
  15. data/lib/paperclip/matchers/have_attached_file_matcher.rb +49 -0
  16. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +65 -0
  17. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +48 -0
  18. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +85 -0
  19. data/lib/paperclip/processor.rb +49 -0
  20. data/lib/paperclip/storage.rb +369 -0
  21. data/lib/paperclip/thumbnail.rb +73 -0
  22. data/lib/paperclip/upfile.rb +49 -0
  23. data/shoulda_macros/paperclip.rb +117 -0
  24. data/tasks/paperclip_tasks.rake +79 -0
  25. data/test/attachment_test.rb +780 -0
  26. data/test/cloudfiles.yml +13 -0
  27. data/test/database.yml +4 -0
  28. data/test/fixtures/12k.png +0 -0
  29. data/test/fixtures/50x50.png +0 -0
  30. data/test/fixtures/5k.png +0 -0
  31. data/test/fixtures/bad.png +1 -0
  32. data/test/fixtures/s3.yml +8 -0
  33. data/test/fixtures/text.txt +0 -0
  34. data/test/fixtures/twopage.pdf +0 -0
  35. data/test/geometry_test.rb +177 -0
  36. data/test/helper.rb +111 -0
  37. data/test/integration_test.rb +483 -0
  38. data/test/interpolations_test.rb +124 -0
  39. data/test/iostream_test.rb +78 -0
  40. data/test/matchers/have_attached_file_matcher_test.rb +21 -0
  41. data/test/matchers/validate_attachment_content_type_matcher_test.rb +31 -0
  42. data/test/matchers/validate_attachment_presence_matcher_test.rb +23 -0
  43. data/test/matchers/validate_attachment_size_matcher_test.rb +51 -0
  44. data/test/paperclip_test.rb +319 -0
  45. data/test/processor_test.rb +10 -0
  46. data/test/storage_test.rb +549 -0
  47. data/test/thumbnail_test.rb +227 -0
  48. data/test/upfile_test.rb +28 -0
  49. metadata +175 -0
@@ -0,0 +1,117 @@
1
+ require 'paperclip/matchers'
2
+ require 'action_controller'
3
+
4
+ module Paperclip
5
+ # =Paperclip Shoulda Macros
6
+ #
7
+ # These macros are intended for use with shoulda, and will be included into
8
+ # your tests automatically. All of the macros use the standard shoulda
9
+ # assumption that the name of the test is based on the name of the model
10
+ # you're testing (that is, UserTest is the test for the User model), and
11
+ # will load that class for testing purposes.
12
+ module Shoulda
13
+ include Matchers
14
+ # This will test whether you have defined your attachment correctly by
15
+ # checking for all the required fields exist after the definition of the
16
+ # attachment.
17
+ def should_have_attached_file name
18
+ klass = self.name.gsub(/Test$/, '').constantize
19
+ matcher = have_attached_file name
20
+ should matcher.description do
21
+ assert_accepts(matcher, klass)
22
+ end
23
+ end
24
+
25
+ # Tests for validations on the presence of the attachment.
26
+ def should_validate_attachment_presence name
27
+ klass = self.name.gsub(/Test$/, '').constantize
28
+ matcher = validate_attachment_presence name
29
+ should matcher.description do
30
+ assert_accepts(matcher, klass)
31
+ end
32
+ end
33
+
34
+ # Tests that you have content_type validations specified. There are two
35
+ # options, :valid and :invalid. Both accept an array of strings. The
36
+ # strings should be a list of content types which will pass and fail
37
+ # validation, respectively.
38
+ def should_validate_attachment_content_type name, options = {}
39
+ klass = self.name.gsub(/Test$/, '').constantize
40
+ valid = [options[:valid]].flatten
41
+ invalid = [options[:invalid]].flatten
42
+ matcher = validate_attachment_content_type(name).allowing(valid).rejecting(invalid)
43
+ should matcher.description do
44
+ assert_accepts(matcher, klass)
45
+ end
46
+ end
47
+
48
+ # Tests to ensure that you have file size validations turned on. You
49
+ # can pass the same options to this that you can to
50
+ # validate_attachment_file_size - :less_than, :greater_than, and :in.
51
+ # :less_than checks that a file is less than a certain size, :greater_than
52
+ # checks that a file is more than a certain size, and :in takes a Range or
53
+ # Array which specifies the lower and upper limits of the file size.
54
+ def should_validate_attachment_size name, options = {}
55
+ klass = self.name.gsub(/Test$/, '').constantize
56
+ min = options[:greater_than] || (options[:in] && options[:in].first) || 0
57
+ max = options[:less_than] || (options[:in] && options[:in].last) || (1.0/0)
58
+ range = (min..max)
59
+ matcher = validate_attachment_size(name).in(range)
60
+ should matcher.description do
61
+ assert_accepts(matcher, klass)
62
+ end
63
+ end
64
+
65
+ # Stubs the HTTP PUT for an attachment using S3 storage.
66
+ #
67
+ # @example
68
+ # stub_paperclip_s3('user', 'avatar', 'png')
69
+ def stub_paperclip_s3(model, attachment, extension)
70
+ definition = model.gsub(" ", "_").classify.constantize.
71
+ attachment_definitions[attachment.to_sym]
72
+
73
+ path = "http://s3.amazonaws.com/:id/#{definition[:path]}"
74
+ path.gsub!(/:([^\/\.]+)/) do |match|
75
+ "([^\/\.]+)"
76
+ end
77
+
78
+ begin
79
+ FakeWeb.register_uri(:put, Regexp.new(path), :body => "OK")
80
+ rescue NameError
81
+ raise NameError, "the stub_paperclip_s3 shoulda macro requires the fakeweb gem."
82
+ end
83
+ end
84
+
85
+ # Stub S3 and return a file for attachment. Best with Factory Girl.
86
+ # Uses a strict directory convention:
87
+ #
88
+ # features/support/paperclip
89
+ #
90
+ # This method is used by the Paperclip-provided Cucumber step:
91
+ #
92
+ # When I attach a "demo_tape" "mp3" file to a "band" on S3
93
+ #
94
+ # @example
95
+ # Factory.define :band_with_demo_tape, :parent => :band do |band|
96
+ # band.demo_tape { band.paperclip_fixture("band", "demo_tape", "png") }
97
+ # end
98
+ def paperclip_fixture(model, attachment, extension)
99
+ stub_paperclip_s3(model, attachment, extension)
100
+ base_path = File.join(File.dirname(__FILE__), "..", "..",
101
+ "features", "support", "paperclip")
102
+ File.new(File.join(base_path, model, "#{attachment}.#{extension}"))
103
+ end
104
+ end
105
+ end
106
+
107
+ class ActionController::Integration::Session #:nodoc:
108
+ include Paperclip::Shoulda
109
+ end
110
+
111
+ class Factory
112
+ include Paperclip::Shoulda #:nodoc:
113
+ end
114
+
115
+ class Test::Unit::TestCase #:nodoc:
116
+ extend Paperclip::Shoulda
117
+ end
@@ -0,0 +1,79 @@
1
+ def obtain_class
2
+ class_name = ENV['CLASS'] || ENV['class']
3
+ raise "Must specify CLASS" unless class_name
4
+ @klass = Object.const_get(class_name)
5
+ end
6
+
7
+ def obtain_attachments
8
+ name = ENV['ATTACHMENT'] || ENV['attachment']
9
+ raise "Class #{@klass.name} has no attachments specified" unless @klass.respond_to?(:attachment_definitions)
10
+ if !name.blank? && @klass.attachment_definitions.keys.include?(name)
11
+ [ name ]
12
+ else
13
+ @klass.attachment_definitions.keys
14
+ end
15
+ end
16
+
17
+ def for_all_attachments
18
+ klass = obtain_class
19
+ names = obtain_attachments
20
+ ids = klass.connection.select_values(klass.send(:construct_finder_sql, :select => 'id'))
21
+
22
+ ids.each do |id|
23
+ instance = klass.find(id)
24
+ names.each do |name|
25
+ result = if instance.send("#{ name }?")
26
+ yield(instance, name)
27
+ else
28
+ true
29
+ end
30
+ print result ? "." : "x"; $stdout.flush
31
+ end
32
+ end
33
+ puts " Done."
34
+ end
35
+
36
+ namespace :paperclip do
37
+ desc "Refreshes both metadata and thumbnails."
38
+ task :refresh => ["paperclip:refresh:metadata", "paperclip:refresh:thumbnails"]
39
+
40
+ namespace :refresh do
41
+ desc "Regenerates thumbnails for a given CLASS (and optional ATTACHMENT)."
42
+ task :thumbnails => :environment do
43
+ errors = []
44
+ for_all_attachments do |instance, name|
45
+ result = instance.send(name).reprocess!
46
+ errors << [instance.id, instance.errors] unless instance.errors.blank?
47
+ result
48
+ end
49
+ errors.each{|e| puts "#{e.first}: #{e.last.full_messages.inspect}" }
50
+ end
51
+
52
+ desc "Regenerates content_type/size metadata for a given CLASS (and optional ATTACHMENT)."
53
+ task :metadata => :environment do
54
+ for_all_attachments do |instance, name|
55
+ if file = instance.send(name).to_file
56
+ instance.send("#{name}_file_name=", instance.send("#{name}_file_name").strip)
57
+ instance.send("#{name}_content_type=", file.content_type.strip)
58
+ instance.send("#{name}_file_size=", file.size) if instance.respond_to?("#{name}_file_size")
59
+ instance.save(false)
60
+ else
61
+ true
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ desc "Cleans out invalid attachments. Useful after you've added new validations."
68
+ task :clean => :environment do
69
+ for_all_attachments do |instance, name|
70
+ instance.send(name).send(:validate)
71
+ if instance.send(name).valid?
72
+ true
73
+ else
74
+ instance.send("#{name}=", nil)
75
+ instance.save
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,780 @@
1
+ # encoding: utf-8
2
+ require 'test/helper'
3
+
4
+ class Dummy
5
+ # This is a dummy class
6
+ end
7
+
8
+ class AttachmentTest < Test::Unit::TestCase
9
+ should "return the path based on the url by default" do
10
+ @attachment = attachment :url => "/:class/:id/:basename"
11
+ @model = @attachment.instance
12
+ @model.id = 1234
13
+ @model.avatar_file_name = "fake.jpg"
14
+ assert_equal "#{RAILS_ROOT}/public/fake_models/1234/fake", @attachment.path
15
+ end
16
+
17
+ should "call a proc sent to check_guard" do
18
+ @dummy = Dummy.new
19
+ @dummy.expects(:one).returns(:one)
20
+ assert_equal :one, @dummy.avatar.send(:check_guard, lambda{|x| x.one })
21
+ end
22
+
23
+ should "call a method name sent to check_guard" do
24
+ @dummy = Dummy.new
25
+ @dummy.expects(:one).returns(:one)
26
+ assert_equal :one, @dummy.avatar.send(:check_guard, :one)
27
+ end
28
+
29
+ context "Attachment default_options" do
30
+ setup do
31
+ rebuild_model
32
+ @old_default_options = Paperclip::Attachment.default_options.dup
33
+ @new_default_options = @old_default_options.merge({
34
+ :path => "argle/bargle",
35
+ :url => "fooferon",
36
+ :default_url => "not here.png"
37
+ })
38
+ end
39
+
40
+ teardown do
41
+ Paperclip::Attachment.default_options.merge! @old_default_options
42
+ end
43
+
44
+ should "be overrideable" do
45
+ Paperclip::Attachment.default_options.merge!(@new_default_options)
46
+ @new_default_options.keys.each do |key|
47
+ assert_equal @new_default_options[key],
48
+ Paperclip::Attachment.default_options[key]
49
+ end
50
+ end
51
+
52
+ context "without an Attachment" do
53
+ setup do
54
+ @dummy = Dummy.new
55
+ end
56
+
57
+ should "return false when asked exists?" do
58
+ assert !@dummy.avatar.exists?
59
+ end
60
+ end
61
+
62
+ context "on an Attachment" do
63
+ setup do
64
+ @dummy = Dummy.new
65
+ @attachment = @dummy.avatar
66
+ end
67
+
68
+ Paperclip::Attachment.default_options.keys.each do |key|
69
+ should "be the default_options for #{key}" do
70
+ assert_equal @old_default_options[key],
71
+ @attachment.instance_variable_get("@#{key}"),
72
+ key
73
+ end
74
+ end
75
+
76
+ context "when redefined" do
77
+ setup do
78
+ Paperclip::Attachment.default_options.merge!(@new_default_options)
79
+ @dummy = Dummy.new
80
+ @attachment = @dummy.avatar
81
+ end
82
+
83
+ Paperclip::Attachment.default_options.keys.each do |key|
84
+ should "be the new default_options for #{key}" do
85
+ assert_equal @new_default_options[key],
86
+ @attachment.instance_variable_get("@#{key}"),
87
+ key
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ context "An attachment with similarly named interpolations" do
95
+ setup do
96
+ rebuild_model :path => ":id.omg/:id-bbq/:idwhat/:id_partition.wtf"
97
+ @dummy = Dummy.new
98
+ @dummy.stubs(:id).returns(1024)
99
+ @file = File.new(File.join(File.dirname(__FILE__),
100
+ "fixtures",
101
+ "5k.png"), 'rb')
102
+ @dummy.avatar = @file
103
+ end
104
+
105
+ teardown { @file.close }
106
+
107
+ should "make sure that they are interpolated correctly" do
108
+ assert_equal "1024.omg/1024-bbq/1024what/000/001/024.wtf", @dummy.avatar.path
109
+ end
110
+ end
111
+
112
+ context "An attachment with a :rails_env interpolation" do
113
+ setup do
114
+ @rails_env = "blah"
115
+ @id = 1024
116
+ rebuild_model :path => ":rails_env/:id.png"
117
+ @dummy = Dummy.new
118
+ @dummy.stubs(:id).returns(@id)
119
+ @file = StringIO.new(".")
120
+ @dummy.avatar = @file
121
+ end
122
+
123
+ should "return the proper path" do
124
+ temporary_rails_env(@rails_env) {
125
+ assert_equal "#{@rails_env}/#{@id}.png", @dummy.avatar.path
126
+ }
127
+ end
128
+ end
129
+
130
+ context "An attachment with a default style and an extension interpolation" do
131
+ setup do
132
+ @attachment = attachment :path => ":basename.:extension",
133
+ :styles => { :default => ["100x100", :png] },
134
+ :default_style => :default
135
+ @file = StringIO.new("...")
136
+ @file.stubs(:original_filename).returns("file.jpg")
137
+ end
138
+ should "return the right extension for the path" do
139
+ @attachment.assign(@file)
140
+ assert_equal "file.png", @attachment.path
141
+ end
142
+ end
143
+
144
+ context "An attachment with :convert_options" do
145
+ setup do
146
+ rebuild_model :styles => {
147
+ :thumb => "100x100",
148
+ :large => "400x400"
149
+ },
150
+ :convert_options => {
151
+ :all => "-do_stuff",
152
+ :thumb => "-thumbnailize"
153
+ }
154
+ @dummy = Dummy.new
155
+ @dummy.avatar
156
+ end
157
+
158
+ should "report the correct options when sent #extra_options_for(:thumb)" do
159
+ assert_equal "-thumbnailize -do_stuff", @dummy.avatar.send(:extra_options_for, :thumb), @dummy.avatar.convert_options.inspect
160
+ end
161
+
162
+ should "report the correct options when sent #extra_options_for(:large)" do
163
+ assert_equal "-do_stuff", @dummy.avatar.send(:extra_options_for, :large)
164
+ end
165
+
166
+ before_should "call extra_options_for(:thumb/:large)" do
167
+ Paperclip::Attachment.any_instance.expects(:extra_options_for).with(:thumb)
168
+ Paperclip::Attachment.any_instance.expects(:extra_options_for).with(:large)
169
+ end
170
+ end
171
+
172
+ context "An attachment with :convert_options that is a proc" do
173
+ setup do
174
+ rebuild_model :styles => {
175
+ :thumb => "100x100",
176
+ :large => "400x400"
177
+ },
178
+ :convert_options => {
179
+ :all => lambda{|i| i.all },
180
+ :thumb => lambda{|i| i.thumb }
181
+ }
182
+ Dummy.class_eval do
183
+ def all; "-all"; end
184
+ def thumb; "-thumb"; end
185
+ end
186
+ @dummy = Dummy.new
187
+ @dummy.avatar
188
+ end
189
+
190
+ should "report the correct options when sent #extra_options_for(:thumb)" do
191
+ assert_equal "-thumb -all", @dummy.avatar.send(:extra_options_for, :thumb), @dummy.avatar.convert_options.inspect
192
+ end
193
+
194
+ should "report the correct options when sent #extra_options_for(:large)" do
195
+ assert_equal "-all", @dummy.avatar.send(:extra_options_for, :large)
196
+ end
197
+
198
+ before_should "call extra_options_for(:thumb/:large)" do
199
+ Paperclip::Attachment.any_instance.expects(:extra_options_for).with(:thumb)
200
+ Paperclip::Attachment.any_instance.expects(:extra_options_for).with(:large)
201
+ end
202
+ end
203
+
204
+ context "An attachment with :path that is a proc" do
205
+ setup do
206
+ rebuild_model :path => lambda{ |attachment| "path/#{attachment.instance.other}.:extension" }
207
+
208
+ @file = File.new(File.join(File.dirname(__FILE__),
209
+ "fixtures",
210
+ "5k.png"), 'rb')
211
+ @dummyA = Dummy.new(:other => 'a')
212
+ @dummyA.avatar = @file
213
+ @dummyB = Dummy.new(:other => 'b')
214
+ @dummyB.avatar = @file
215
+ end
216
+
217
+ teardown { @file.close }
218
+
219
+ should "return correct path" do
220
+ assert_equal "path/a.png", @dummyA.avatar.path
221
+ assert_equal "path/b.png", @dummyB.avatar.path
222
+ end
223
+ end
224
+
225
+ context "An attachment with :styles that is a proc" do
226
+ setup do
227
+ rebuild_model :styles => lambda{ |attachment| {:thumb => "50x50#", :large => "400x400"} }
228
+
229
+ @attachment = Dummy.new.avatar
230
+ end
231
+
232
+ should "have the correct geometry" do
233
+ assert_equal "50x50#", @attachment.styles[:thumb][:geometry]
234
+ end
235
+ end
236
+
237
+ context "An attachment with :url that is a proc" do
238
+ setup do
239
+ rebuild_model :url => lambda{ |attachment| "path/#{attachment.instance.other}.:extension" }
240
+
241
+ @file = File.new(File.join(File.dirname(__FILE__),
242
+ "fixtures",
243
+ "5k.png"), 'rb')
244
+ @dummyA = Dummy.new(:other => 'a')
245
+ @dummyA.avatar = @file
246
+ @dummyB = Dummy.new(:other => 'b')
247
+ @dummyB.avatar = @file
248
+ end
249
+
250
+ teardown { @file.close }
251
+
252
+ should "return correct url" do
253
+ assert_equal "path/a.png", @dummyA.avatar.url(:original, false)
254
+ assert_equal "path/b.png", @dummyB.avatar.url(:original, false)
255
+ end
256
+ end
257
+
258
+ geometry_specs = [
259
+ [ lambda{|z| "50x50#" }, :png ],
260
+ lambda{|z| "50x50#" },
261
+ { :geometry => lambda{|z| "50x50#" } }
262
+ ]
263
+ geometry_specs.each do |geometry_spec|
264
+ context "An attachment geometry like #{geometry_spec}" do
265
+ setup do
266
+ rebuild_model :styles => { :normal => geometry_spec }
267
+ @attachment = Dummy.new.avatar
268
+ end
269
+
270
+ should "not run the procs immediately" do
271
+ assert_kind_of Proc, @attachment.styles[:normal][:geometry]
272
+ end
273
+
274
+ context "when assigned" do
275
+ setup do
276
+ @file = StringIO.new(".")
277
+ @attachment.assign(@file)
278
+ end
279
+
280
+ should "have the correct geometry" do
281
+ assert_equal "50x50#", @attachment.styles[:normal][:geometry]
282
+ end
283
+ end
284
+ end
285
+ end
286
+
287
+ context "An attachment with both 'normal' and hash-style styles" do
288
+ setup do
289
+ rebuild_model :styles => {
290
+ :normal => ["50x50#", :png],
291
+ :hash => { :geometry => "50x50#", :format => :png }
292
+ }
293
+ @dummy = Dummy.new
294
+ @attachment = @dummy.avatar
295
+ end
296
+
297
+ [:processors, :whiny, :convert_options, :geometry, :format].each do |field|
298
+ should "have the same #{field} field" do
299
+ assert_equal @attachment.styles[:normal][field], @attachment.styles[:hash][field]
300
+ end
301
+ end
302
+ end
303
+
304
+ context "An attachment with :processors that is a proc" do
305
+ setup do
306
+ rebuild_model :styles => { :normal => '' }, :processors => lambda { |a| [ :test ] }
307
+ @attachment = Dummy.new.avatar
308
+ end
309
+
310
+ should "not run the proc immediately" do
311
+ assert_kind_of Proc, @attachment.styles[:normal][:processors]
312
+ end
313
+
314
+ context "when assigned" do
315
+ setup do
316
+ @attachment.assign(StringIO.new("."))
317
+ end
318
+
319
+ should "have the correct processors" do
320
+ assert_equal [ :test ], @attachment.styles[:normal][:processors]
321
+ end
322
+ end
323
+ end
324
+
325
+ context "An attachment with erroring processor" do
326
+ setup do
327
+ rebuild_model :processor => [:thumbnail], :styles => { :small => '' }, :whiny_thumbnails => true
328
+ @dummy = Dummy.new
329
+ Paperclip::Thumbnail.expects(:make).raises(Paperclip::PaperclipError, "cannot be processed.")
330
+ @file = StringIO.new("...")
331
+ @file.stubs(:to_tempfile).returns(@file)
332
+ @dummy.avatar = @file
333
+ end
334
+
335
+ should "correctly forward processing error message to the instance" do
336
+ @dummy.valid?
337
+ assert_contains @dummy.errors.full_messages, "Avatar cannot be processed."
338
+ end
339
+ end
340
+
341
+ context "An attachment with multiple processors" do
342
+ setup do
343
+ class Paperclip::Test < Paperclip::Processor; end
344
+ @style_params = { :once => {:one => 1, :two => 2} }
345
+ rebuild_model :processors => [:thumbnail, :test], :styles => @style_params
346
+ @dummy = Dummy.new
347
+ @file = StringIO.new("...")
348
+ @file.stubs(:to_tempfile).returns(@file)
349
+ Paperclip::Test.stubs(:make).returns(@file)
350
+ Paperclip::Thumbnail.stubs(:make).returns(@file)
351
+ end
352
+
353
+ context "when assigned" do
354
+ setup { @dummy.avatar = @file }
355
+
356
+ before_should "call #make on all specified processors" do
357
+ expected_params = @style_params[:once].merge({:processors => [:thumbnail, :test], :whiny => true, :convert_options => ""})
358
+ Paperclip::Thumbnail.expects(:make).with(@file, expected_params, @dummy.avatar).returns(@file)
359
+ Paperclip::Test.expects(:make).with(@file, expected_params, @dummy.avatar).returns(@file)
360
+ end
361
+
362
+ before_should "call #make with attachment passed as third argument" do
363
+ expected_params = @style_params[:once].merge({:processors => [:thumbnail, :test], :whiny => true, :convert_options => ""})
364
+ Paperclip::Test.expects(:make).with(@file, expected_params, @dummy.avatar).returns(@file)
365
+ end
366
+ end
367
+ end
368
+
369
+ context "An attachment with no processors defined" do
370
+ setup do
371
+ rebuild_model :processors => [], :styles => {:something => 1}
372
+ @dummy = Dummy.new
373
+ @file = StringIO.new("...")
374
+ end
375
+ should "raise when assigned to" do
376
+ assert_raises(RuntimeError){ @dummy.avatar = @file }
377
+ end
378
+ end
379
+
380
+ context "Assigning an attachment with post_process hooks" do
381
+ setup do
382
+ rebuild_model :styles => { :something => "100x100#" }
383
+ Dummy.class_eval do
384
+ before_avatar_post_process :do_before_avatar
385
+ after_avatar_post_process :do_after_avatar
386
+ before_post_process :do_before_all
387
+ after_post_process :do_after_all
388
+ def do_before_avatar; end
389
+ def do_after_avatar; end
390
+ def do_before_all; end
391
+ def do_after_all; end
392
+ end
393
+ @file = StringIO.new(".")
394
+ @file.stubs(:to_tempfile).returns(@file)
395
+ @dummy = Dummy.new
396
+ Paperclip::Thumbnail.stubs(:make).returns(@file)
397
+ @attachment = @dummy.avatar
398
+ end
399
+
400
+ should "call the defined callbacks when assigned" do
401
+ @dummy.expects(:do_before_avatar).with()
402
+ @dummy.expects(:do_after_avatar).with()
403
+ @dummy.expects(:do_before_all).with()
404
+ @dummy.expects(:do_after_all).with()
405
+ Paperclip::Thumbnail.expects(:make).returns(@file)
406
+ @dummy.avatar = @file
407
+ end
408
+
409
+ should "not cancel the processing if a before_post_process returns nil" do
410
+ @dummy.expects(:do_before_avatar).with().returns(nil)
411
+ @dummy.expects(:do_after_avatar).with()
412
+ @dummy.expects(:do_before_all).with().returns(nil)
413
+ @dummy.expects(:do_after_all).with()
414
+ Paperclip::Thumbnail.expects(:make).returns(@file)
415
+ @dummy.avatar = @file
416
+ end
417
+
418
+ should "cancel the processing if a before_post_process returns false" do
419
+ @dummy.expects(:do_before_avatar).never
420
+ @dummy.expects(:do_after_avatar).never
421
+ @dummy.expects(:do_before_all).with().returns(false)
422
+ @dummy.expects(:do_after_all).never
423
+ Paperclip::Thumbnail.expects(:make).never
424
+ @dummy.avatar = @file
425
+ end
426
+
427
+ should "cancel the processing if a before_avatar_post_process returns false" do
428
+ @dummy.expects(:do_before_avatar).with().returns(false)
429
+ @dummy.expects(:do_after_avatar).never
430
+ @dummy.expects(:do_before_all).with().returns(true)
431
+ @dummy.expects(:do_after_all).never
432
+ Paperclip::Thumbnail.expects(:make).never
433
+ @dummy.avatar = @file
434
+ end
435
+ end
436
+
437
+ context "Assigning an attachment" do
438
+ setup do
439
+ rebuild_model :styles => { :something => "100x100#" }
440
+ @file = StringIO.new(".")
441
+ @file.expects(:original_filename).returns("5k.png\n\n")
442
+ @file.expects(:content_type).returns("image/png\n\n")
443
+ @file.stubs(:to_tempfile).returns(@file)
444
+ @dummy = Dummy.new
445
+ Paperclip::Thumbnail.expects(:make).returns(@file)
446
+ @dummy.expects(:run_callbacks).with(:before_avatar_post_process, {:original => @file})
447
+ @dummy.expects(:run_callbacks).with(:before_post_process, {:original => @file})
448
+ @dummy.expects(:run_callbacks).with(:after_avatar_post_process, {:original => @file, :something => @file})
449
+ @dummy.expects(:run_callbacks).with(:after_post_process, {:original => @file, :something => @file})
450
+ @attachment = @dummy.avatar
451
+ @dummy.avatar = @file
452
+ end
453
+
454
+ should "strip whitespace from original_filename field" do
455
+ assert_equal "5k.png", @dummy.avatar.original_filename
456
+ end
457
+
458
+ should "strip whitespace from content_type field" do
459
+ assert_equal "image/png", @dummy.avatar.instance.avatar_content_type
460
+ end
461
+ end
462
+
463
+ context "Attachment with strange letters" do
464
+ setup do
465
+ rebuild_model
466
+
467
+ @not_file = mock
468
+ @tempfile = mock
469
+ @not_file.stubs(:nil?).returns(false)
470
+ @not_file.expects(:size).returns(10)
471
+ @tempfile.expects(:size).returns(10)
472
+ @not_file.expects(:to_tempfile).returns(@tempfile)
473
+ @not_file.expects(:original_filename).returns("sheep_say_bæ.png\r\n")
474
+ @not_file.expects(:content_type).returns("image/png\r\n")
475
+
476
+ @dummy = Dummy.new
477
+ @attachment = @dummy.avatar
478
+ @attachment.expects(:valid_assignment?).with(@not_file).returns(true)
479
+ @attachment.expects(:queue_existing_for_delete)
480
+ @attachment.expects(:post_process)
481
+ @attachment.expects(:valid?).returns(true)
482
+ @attachment.expects(:validate)
483
+ @dummy.avatar = @not_file
484
+ end
485
+
486
+ should "remove strange letters and replace with underscore (_)" do
487
+ assert_equal "sheep_say_b_.png", @dummy.avatar.original_filename
488
+ end
489
+
490
+ end
491
+
492
+ context "An attachment" do
493
+ setup do
494
+ @old_defaults = Paperclip::Attachment.default_options.dup
495
+ Paperclip::Attachment.default_options.merge!({
496
+ :path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
497
+ })
498
+ FileUtils.rm_rf("tmp")
499
+ rebuild_model
500
+ @instance = Dummy.new
501
+ @attachment = Paperclip::Attachment.new(:avatar, @instance)
502
+ @file = File.new(File.join(File.dirname(__FILE__), "fixtures", "5k.png"), 'rb')
503
+ end
504
+
505
+ teardown do
506
+ @file.close
507
+ Paperclip::Attachment.default_options.merge!(@old_defaults)
508
+ end
509
+
510
+ should "raise if there are not the correct columns when you try to assign" do
511
+ @other_attachment = Paperclip::Attachment.new(:not_here, @instance)
512
+ assert_raises(Paperclip::PaperclipError) do
513
+ @other_attachment.assign(@file)
514
+ end
515
+ end
516
+
517
+ should "return its default_url when no file assigned" do
518
+ assert @attachment.to_file.nil?
519
+ assert_equal "/avatars/original/missing.png", @attachment.url
520
+ assert_equal "/avatars/blah/missing.png", @attachment.url(:blah)
521
+ end
522
+
523
+ should "return nil as path when no file assigned" do
524
+ assert @attachment.to_file.nil?
525
+ assert_equal nil, @attachment.path
526
+ assert_equal nil, @attachment.path(:blah)
527
+ end
528
+
529
+ context "with a file assigned in the database" do
530
+ setup do
531
+ @attachment.stubs(:instance_read).with(:file_name).returns("5k.png")
532
+ @attachment.stubs(:instance_read).with(:content_type).returns("image/png")
533
+ @attachment.stubs(:instance_read).with(:file_size).returns(12345)
534
+ dtnow = DateTime.now
535
+ @now = Time.now
536
+ Time.stubs(:now).returns(@now)
537
+ @attachment.stubs(:instance_read).with(:updated_at).returns(dtnow)
538
+ end
539
+
540
+ should "return a correct url even if the file does not exist" do
541
+ assert_nil @attachment.to_file
542
+ assert_match %r{^/system/avatars/#{@instance.id}/blah/5k\.png}, @attachment.url(:blah)
543
+ end
544
+
545
+ should "make sure the updated_at mtime is in the url if it is defined" do
546
+ assert_match %r{#{@now.to_i}$}, @attachment.url(:blah)
547
+ end
548
+
549
+ should "make sure the updated_at mtime is NOT in the url if false is passed to the url method" do
550
+ assert_no_match %r{#{@now.to_i}$}, @attachment.url(:blah, false)
551
+ end
552
+
553
+ context "with the updated_at field removed" do
554
+ setup do
555
+ @attachment.stubs(:instance_read).with(:updated_at).returns(nil)
556
+ end
557
+
558
+ should "only return the url without the updated_at when sent #url" do
559
+ assert_match "/avatars/#{@instance.id}/blah/5k.png", @attachment.url(:blah)
560
+ end
561
+ end
562
+
563
+ should "return the proper path when filename has a single .'s" do
564
+ assert_equal File.expand_path("./test/../tmp/avatars/dummies/original/#{@instance.id}/5k.png"), File.expand_path(@attachment.path)
565
+ end
566
+
567
+ should "return the proper path when filename has multiple .'s" do
568
+ @attachment.stubs(:instance_read).with(:file_name).returns("5k.old.png")
569
+ assert_equal File.expand_path("./test/../tmp/avatars/dummies/original/#{@instance.id}/5k.old.png"), File.expand_path(@attachment.path)
570
+ end
571
+
572
+ context "when expecting three styles" do
573
+ setup do
574
+ styles = {:styles => { :large => ["400x400", :png],
575
+ :medium => ["100x100", :gif],
576
+ :small => ["32x32#", :jpg]}}
577
+ @attachment = Paperclip::Attachment.new(:avatar,
578
+ @instance,
579
+ styles)
580
+ end
581
+
582
+ context "and assigned a file" do
583
+ setup do
584
+ now = Time.now
585
+ Time.stubs(:now).returns(now)
586
+ @attachment.assign(@file)
587
+ end
588
+
589
+ should "be dirty" do
590
+ assert @attachment.dirty?
591
+ end
592
+
593
+ context "and saved" do
594
+ setup do
595
+ @attachment.save
596
+ end
597
+
598
+ should "return the real url" do
599
+ file = @attachment.to_file
600
+ assert file
601
+ assert_match %r{^/system/avatars/#{@instance.id}/original/5k\.png}, @attachment.url
602
+ assert_match %r{^/system/avatars/#{@instance.id}/small/5k\.jpg}, @attachment.url(:small)
603
+ file.close
604
+ end
605
+
606
+ should "commit the files to disk" do
607
+ [:large, :medium, :small].each do |style|
608
+ io = @attachment.to_file(style)
609
+ assert File.exists?(io)
610
+ assert ! io.is_a?(::Tempfile)
611
+ io.close
612
+ end
613
+ end
614
+
615
+ should "save the files as the right formats and sizes" do
616
+ [[:large, 400, 61, "PNG"],
617
+ [:medium, 100, 15, "GIF"],
618
+ [:small, 32, 32, "JPEG"]].each do |style|
619
+ cmd = %Q[identify -format "%w %h %b %m" "#{@attachment.path(style.first)}"]
620
+ out = `#{cmd}`
621
+ width, height, size, format = out.split(" ")
622
+ assert_equal style[1].to_s, width.to_s
623
+ assert_equal style[2].to_s, height.to_s
624
+ assert_equal style[3].to_s, format.to_s
625
+ end
626
+ end
627
+
628
+ should "still have its #file attribute not be nil" do
629
+ assert ! (file = @attachment.to_file).nil?
630
+ file.close
631
+ end
632
+
633
+ context "and trying to delete" do
634
+ setup do
635
+ @existing_names = @attachment.styles.keys.collect do |style|
636
+ @attachment.path(style)
637
+ end
638
+ end
639
+
640
+ should "delete the files after assigning nil" do
641
+ @attachment.expects(:instance_write).with(:file_name, nil)
642
+ @attachment.expects(:instance_write).with(:content_type, nil)
643
+ @attachment.expects(:instance_write).with(:file_size, nil)
644
+ @attachment.expects(:instance_write).with(:updated_at, nil)
645
+ @attachment.assign nil
646
+ @attachment.save
647
+ @existing_names.each{|f| assert ! File.exists?(f) }
648
+ end
649
+
650
+ should "delete the files when you call #clear and #save" do
651
+ @attachment.expects(:instance_write).with(:file_name, nil)
652
+ @attachment.expects(:instance_write).with(:content_type, nil)
653
+ @attachment.expects(:instance_write).with(:file_size, nil)
654
+ @attachment.expects(:instance_write).with(:updated_at, nil)
655
+ @attachment.clear
656
+ @attachment.save
657
+ @existing_names.each{|f| assert ! File.exists?(f) }
658
+ end
659
+
660
+ should "delete the files when you call #delete" do
661
+ @attachment.expects(:instance_write).with(:file_name, nil)
662
+ @attachment.expects(:instance_write).with(:content_type, nil)
663
+ @attachment.expects(:instance_write).with(:file_size, nil)
664
+ @attachment.expects(:instance_write).with(:updated_at, nil)
665
+ @attachment.destroy
666
+ @existing_names.each{|f| assert ! File.exists?(f) }
667
+ end
668
+ end
669
+ end
670
+ end
671
+ end
672
+
673
+ end
674
+
675
+ context "when trying a nonexistant storage type" do
676
+ setup do
677
+ rebuild_model :storage => :not_here
678
+ end
679
+
680
+ should "not be able to find the module" do
681
+ assert_raise(NameError){ Dummy.new.avatar }
682
+ end
683
+ end
684
+ end
685
+
686
+ context "An attachment with only a avatar_file_name column" do
687
+ setup do
688
+ ActiveRecord::Base.connection.create_table :dummies, :force => true do |table|
689
+ table.column :avatar_file_name, :string
690
+ end
691
+ rebuild_class
692
+ @dummy = Dummy.new
693
+ @file = File.new(File.join(File.dirname(__FILE__), "fixtures", "5k.png"), 'rb')
694
+ end
695
+
696
+ teardown { @file.close }
697
+
698
+ should "not error when assigned an attachment" do
699
+ assert_nothing_raised { @dummy.avatar = @file }
700
+ end
701
+
702
+ should "return the time when sent #avatar_updated_at" do
703
+ now = Time.now
704
+ Time.stubs(:now).returns(now)
705
+ @dummy.avatar = @file
706
+ assert now, @dummy.avatar.updated_at
707
+ end
708
+
709
+ should "return nil when reloaded and sent #avatar_updated_at" do
710
+ @dummy.save
711
+ @dummy.reload
712
+ assert_nil @dummy.avatar.updated_at
713
+ end
714
+
715
+ should "return the right value when sent #avatar_file_size" do
716
+ @dummy.avatar = @file
717
+ assert_equal @file.size, @dummy.avatar.size
718
+ end
719
+
720
+ context "and avatar_updated_at column" do
721
+ setup do
722
+ ActiveRecord::Base.connection.add_column :dummies, :avatar_updated_at, :timestamp
723
+ rebuild_class
724
+ @dummy = Dummy.new
725
+ end
726
+
727
+ should "not error when assigned an attachment" do
728
+ assert_nothing_raised { @dummy.avatar = @file }
729
+ end
730
+
731
+ should "return the right value when sent #avatar_updated_at" do
732
+ now = Time.now
733
+ Time.stubs(:now).returns(now)
734
+ @dummy.avatar = @file
735
+ assert_equal now.to_i, @dummy.avatar.updated_at
736
+ end
737
+ end
738
+
739
+ context "and avatar_content_type column" do
740
+ setup do
741
+ ActiveRecord::Base.connection.add_column :dummies, :avatar_content_type, :string
742
+ rebuild_class
743
+ @dummy = Dummy.new
744
+ end
745
+
746
+ should "not error when assigned an attachment" do
747
+ assert_nothing_raised { @dummy.avatar = @file }
748
+ end
749
+
750
+ should "return the right value when sent #avatar_content_type" do
751
+ @dummy.avatar = @file
752
+ assert_equal "image/png", @dummy.avatar.content_type
753
+ end
754
+ end
755
+
756
+ context "and avatar_file_size column" do
757
+ setup do
758
+ ActiveRecord::Base.connection.add_column :dummies, :avatar_file_size, :integer
759
+ rebuild_class
760
+ @dummy = Dummy.new
761
+ end
762
+
763
+ should "not error when assigned an attachment" do
764
+ assert_nothing_raised { @dummy.avatar = @file }
765
+ end
766
+
767
+ should "return the right value when sent #avatar_file_size" do
768
+ @dummy.avatar = @file
769
+ assert_equal @file.size, @dummy.avatar.size
770
+ end
771
+
772
+ should "return the right value when saved, reloaded, and sent #avatar_file_size" do
773
+ @dummy.avatar = @file
774
+ @dummy.save
775
+ @dummy = Dummy.find(@dummy.id)
776
+ assert_equal @file.size, @dummy.avatar.size
777
+ end
778
+ end
779
+ end
780
+ end