path-paperclip 2.3.3
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.
- data/LICENSE +26 -0
- data/README.rdoc +198 -0
- data/Rakefile +76 -0
- data/generators/paperclip/USAGE +5 -0
- data/generators/paperclip/paperclip_generator.rb +27 -0
- data/generators/paperclip/templates/paperclip_migration.rb.erb +19 -0
- data/init.rb +1 -0
- data/lib/generators/paperclip/USAGE +8 -0
- data/lib/generators/paperclip/paperclip_generator.rb +31 -0
- data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +19 -0
- data/lib/paperclip.rb +440 -0
- data/lib/paperclip/attachment.rb +401 -0
- data/lib/paperclip/callback_compatability.rb +61 -0
- data/lib/paperclip/geometry.rb +150 -0
- data/lib/paperclip/interpolations.rb +113 -0
- data/lib/paperclip/iostream.rb +59 -0
- data/lib/paperclip/matchers.rb +33 -0
- data/lib/paperclip/matchers/have_attached_file_matcher.rb +57 -0
- data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +75 -0
- data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +54 -0
- data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +95 -0
- data/lib/paperclip/processor.rb +49 -0
- data/lib/paperclip/railtie.rb +20 -0
- data/lib/paperclip/storage.rb +258 -0
- data/lib/paperclip/style.rb +90 -0
- data/lib/paperclip/thumbnail.rb +78 -0
- data/lib/paperclip/upfile.rb +52 -0
- data/lib/paperclip/version.rb +3 -0
- data/lib/tasks/paperclip.rake +95 -0
- data/rails/init.rb +2 -0
- data/shoulda_macros/paperclip.rb +119 -0
- data/test/attachment_test.rb +796 -0
- data/test/database.yml +4 -0
- data/test/fixtures/12k.png +0 -0
- data/test/fixtures/50x50.png +0 -0
- data/test/fixtures/5k.png +0 -0
- data/test/fixtures/bad.png +1 -0
- data/test/fixtures/ceedub.gif +0 -0
- data/test/fixtures/s3.yml +8 -0
- data/test/fixtures/text.txt +1 -0
- data/test/fixtures/twopage.pdf +0 -0
- data/test/geometry_test.rb +177 -0
- data/test/helper.rb +152 -0
- data/test/integration_test.rb +610 -0
- data/test/interpolations_test.rb +135 -0
- data/test/iostream_test.rb +78 -0
- data/test/matchers/have_attached_file_matcher_test.rb +24 -0
- data/test/matchers/validate_attachment_content_type_matcher_test.rb +54 -0
- data/test/matchers/validate_attachment_presence_matcher_test.rb +26 -0
- data/test/matchers/validate_attachment_size_matcher_test.rb +51 -0
- data/test/paperclip_test.rb +389 -0
- data/test/processor_test.rb +10 -0
- data/test/storage_test.rb +407 -0
- data/test/style_test.rb +141 -0
- data/test/thumbnail_test.rb +227 -0
- data/test/upfile_test.rb +36 -0
- metadata +221 -0
data/test/database.yml
ADDED
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
DA∑¨øC‰ (corrupted image)
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
ASCII text.
|
Binary file
|
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'test/helper'
|
2
|
+
|
3
|
+
class GeometryTest < Test::Unit::TestCase
|
4
|
+
context "Paperclip::Geometry" do
|
5
|
+
should "correctly report its given dimensions" do
|
6
|
+
assert @geo = Paperclip::Geometry.new(1024, 768)
|
7
|
+
assert_equal 1024, @geo.width
|
8
|
+
assert_equal 768, @geo.height
|
9
|
+
end
|
10
|
+
|
11
|
+
should "set height to 0 if height dimension is missing" do
|
12
|
+
assert @geo = Paperclip::Geometry.new(1024)
|
13
|
+
assert_equal 1024, @geo.width
|
14
|
+
assert_equal 0, @geo.height
|
15
|
+
end
|
16
|
+
|
17
|
+
should "set width to 0 if width dimension is missing" do
|
18
|
+
assert @geo = Paperclip::Geometry.new(nil, 768)
|
19
|
+
assert_equal 0, @geo.width
|
20
|
+
assert_equal 768, @geo.height
|
21
|
+
end
|
22
|
+
|
23
|
+
should "be generated from a WxH-formatted string" do
|
24
|
+
assert @geo = Paperclip::Geometry.parse("800x600")
|
25
|
+
assert_equal 800, @geo.width
|
26
|
+
assert_equal 600, @geo.height
|
27
|
+
end
|
28
|
+
|
29
|
+
should "be generated from a xH-formatted string" do
|
30
|
+
assert @geo = Paperclip::Geometry.parse("x600")
|
31
|
+
assert_equal 0, @geo.width
|
32
|
+
assert_equal 600, @geo.height
|
33
|
+
end
|
34
|
+
|
35
|
+
should "be generated from a Wx-formatted string" do
|
36
|
+
assert @geo = Paperclip::Geometry.parse("800x")
|
37
|
+
assert_equal 800, @geo.width
|
38
|
+
assert_equal 0, @geo.height
|
39
|
+
end
|
40
|
+
|
41
|
+
should "be generated from a W-formatted string" do
|
42
|
+
assert @geo = Paperclip::Geometry.parse("800")
|
43
|
+
assert_equal 800, @geo.width
|
44
|
+
assert_equal 0, @geo.height
|
45
|
+
end
|
46
|
+
|
47
|
+
should "ensure the modifier is nil if not present" do
|
48
|
+
assert @geo = Paperclip::Geometry.parse("123x456")
|
49
|
+
assert_nil @geo.modifier
|
50
|
+
end
|
51
|
+
|
52
|
+
should "treat x and X the same in geometries" do
|
53
|
+
@lower = Paperclip::Geometry.parse("123x456")
|
54
|
+
@upper = Paperclip::Geometry.parse("123X456")
|
55
|
+
assert_equal 123, @lower.width
|
56
|
+
assert_equal 123, @upper.width
|
57
|
+
assert_equal 456, @lower.height
|
58
|
+
assert_equal 456, @upper.height
|
59
|
+
end
|
60
|
+
|
61
|
+
['>', '<', '#', '@', '%', '^', '!', nil].each do |mod|
|
62
|
+
should "ensure the modifier #{mod.inspect} is preserved" do
|
63
|
+
assert @geo = Paperclip::Geometry.parse("123x456#{mod}")
|
64
|
+
assert_equal mod, @geo.modifier
|
65
|
+
assert_equal "123x456#{mod}", @geo.to_s
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
['>', '<', '#', '@', '%', '^', '!', nil].each do |mod|
|
70
|
+
should "ensure the modifier #{mod.inspect} is preserved with no height" do
|
71
|
+
assert @geo = Paperclip::Geometry.parse("123x#{mod}")
|
72
|
+
assert_equal mod, @geo.modifier
|
73
|
+
assert_equal "123#{mod}", @geo.to_s
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
should "make sure the modifier gets passed during transformation_to" do
|
78
|
+
assert @src = Paperclip::Geometry.parse("123x456")
|
79
|
+
assert @dst = Paperclip::Geometry.parse("123x456>")
|
80
|
+
assert_equal ["123x456>", nil], @src.transformation_to(@dst)
|
81
|
+
end
|
82
|
+
|
83
|
+
should "generate correct ImageMagick formatting string for W-formatted string" do
|
84
|
+
assert @geo = Paperclip::Geometry.parse("800")
|
85
|
+
assert_equal "800", @geo.to_s
|
86
|
+
end
|
87
|
+
|
88
|
+
should "generate correct ImageMagick formatting string for Wx-formatted string" do
|
89
|
+
assert @geo = Paperclip::Geometry.parse("800x")
|
90
|
+
assert_equal "800", @geo.to_s
|
91
|
+
end
|
92
|
+
|
93
|
+
should "generate correct ImageMagick formatting string for xH-formatted string" do
|
94
|
+
assert @geo = Paperclip::Geometry.parse("x600")
|
95
|
+
assert_equal "x600", @geo.to_s
|
96
|
+
end
|
97
|
+
|
98
|
+
should "generate correct ImageMagick formatting string for WxH-formatted string" do
|
99
|
+
assert @geo = Paperclip::Geometry.parse("800x600")
|
100
|
+
assert_equal "800x600", @geo.to_s
|
101
|
+
end
|
102
|
+
|
103
|
+
should "be generated from a file" do
|
104
|
+
file = File.join(File.dirname(__FILE__), "fixtures", "5k.png")
|
105
|
+
file = File.new(file, 'rb')
|
106
|
+
assert_nothing_raised{ @geo = Paperclip::Geometry.from_file(file) }
|
107
|
+
assert @geo.height > 0
|
108
|
+
assert @geo.width > 0
|
109
|
+
end
|
110
|
+
|
111
|
+
should "be generated from a file path" do
|
112
|
+
file = File.join(File.dirname(__FILE__), "fixtures", "5k.png")
|
113
|
+
assert_nothing_raised{ @geo = Paperclip::Geometry.from_file(file) }
|
114
|
+
assert @geo.height > 0
|
115
|
+
assert @geo.width > 0
|
116
|
+
end
|
117
|
+
|
118
|
+
should "not generate from a bad file" do
|
119
|
+
file = "/home/This File Does Not Exist.omg"
|
120
|
+
assert_raise(Paperclip::NotIdentifiedByImageMagickError){ @geo = Paperclip::Geometry.from_file(file) }
|
121
|
+
end
|
122
|
+
|
123
|
+
[['vertical', 900, 1440, true, false, false, 1440, 900, 0.625],
|
124
|
+
['horizontal', 1024, 768, false, true, false, 1024, 768, 1.3333],
|
125
|
+
['square', 100, 100, false, false, true, 100, 100, 1]].each do |args|
|
126
|
+
context "performing calculations on a #{args[0]} viewport" do
|
127
|
+
setup do
|
128
|
+
@geo = Paperclip::Geometry.new(args[1], args[2])
|
129
|
+
end
|
130
|
+
|
131
|
+
should "#{args[3] ? "" : "not"} be vertical" do
|
132
|
+
assert_equal args[3], @geo.vertical?
|
133
|
+
end
|
134
|
+
|
135
|
+
should "#{args[4] ? "" : "not"} be horizontal" do
|
136
|
+
assert_equal args[4], @geo.horizontal?
|
137
|
+
end
|
138
|
+
|
139
|
+
should "#{args[5] ? "" : "not"} be square" do
|
140
|
+
assert_equal args[5], @geo.square?
|
141
|
+
end
|
142
|
+
|
143
|
+
should "report that #{args[6]} is the larger dimension" do
|
144
|
+
assert_equal args[6], @geo.larger
|
145
|
+
end
|
146
|
+
|
147
|
+
should "report that #{args[7]} is the smaller dimension" do
|
148
|
+
assert_equal args[7], @geo.smaller
|
149
|
+
end
|
150
|
+
|
151
|
+
should "have an aspect ratio of #{args[8]}" do
|
152
|
+
assert_in_delta args[8], @geo.aspect, 0.0001
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
[[ [1000, 100], [64, 64], "x64", "64x64+288+0" ],
|
158
|
+
[ [100, 1000], [50, 950], "x950", "50x950+22+0" ],
|
159
|
+
[ [100, 1000], [50, 25], "50x", "50x25+0+237" ]]. each do |args|
|
160
|
+
context "of #{args[0].inspect} and given a Geometry #{args[1].inspect} and sent transform_to" do
|
161
|
+
setup do
|
162
|
+
@geo = Paperclip::Geometry.new(*args[0])
|
163
|
+
@dst = Paperclip::Geometry.new(*args[1])
|
164
|
+
@scale, @crop = @geo.transformation_to @dst, true
|
165
|
+
end
|
166
|
+
|
167
|
+
should "be able to return the correct scaling transformation geometry #{args[2]}" do
|
168
|
+
assert_equal args[2], @scale
|
169
|
+
end
|
170
|
+
|
171
|
+
should "be able to return the correct crop transformation geometry #{args[3]}" do
|
172
|
+
assert_equal args[3], @crop
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'tempfile'
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
require 'shoulda'
|
6
|
+
require 'mocha'
|
7
|
+
|
8
|
+
case ENV['RAILS_VERSION']
|
9
|
+
when '2.1' then
|
10
|
+
gem 'activerecord', '~>2.1.0'
|
11
|
+
gem 'activesupport', '~>2.1.0'
|
12
|
+
gem 'actionpack', '~>2.1.0'
|
13
|
+
when '3.0' then
|
14
|
+
gem 'activerecord', '~>3.0.0'
|
15
|
+
gem 'activesupport', '~>3.0.0'
|
16
|
+
gem 'actionpack', '~>3.0.0'
|
17
|
+
else
|
18
|
+
gem 'activerecord', '~>2.3.0'
|
19
|
+
gem 'activesupport', '~>2.3.0'
|
20
|
+
gem 'actionpack', '~>2.3.0'
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'active_record'
|
24
|
+
require 'active_record/version'
|
25
|
+
require 'active_support'
|
26
|
+
require 'action_pack'
|
27
|
+
|
28
|
+
puts "Testing against version #{ActiveRecord::VERSION::STRING}"
|
29
|
+
|
30
|
+
begin
|
31
|
+
require 'ruby-debug'
|
32
|
+
rescue LoadError => e
|
33
|
+
puts "debugger disabled"
|
34
|
+
end
|
35
|
+
|
36
|
+
ROOT = File.join(File.dirname(__FILE__), '..')
|
37
|
+
|
38
|
+
def silence_warnings
|
39
|
+
old_verbose, $VERBOSE = $VERBOSE, nil
|
40
|
+
yield
|
41
|
+
ensure
|
42
|
+
$VERBOSE = old_verbose
|
43
|
+
end
|
44
|
+
|
45
|
+
class Test::Unit::TestCase
|
46
|
+
def setup
|
47
|
+
silence_warnings do
|
48
|
+
Object.const_set(:Rails, stub('Rails', :root => ROOT, :env => 'test'))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
$LOAD_PATH << File.join(ROOT, 'lib')
|
54
|
+
$LOAD_PATH << File.join(ROOT, 'lib', 'paperclip')
|
55
|
+
|
56
|
+
require File.join(ROOT, 'lib', 'paperclip.rb')
|
57
|
+
|
58
|
+
require 'shoulda_macros/paperclip'
|
59
|
+
|
60
|
+
FIXTURES_DIR = File.join(File.dirname(__FILE__), "fixtures")
|
61
|
+
config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
|
62
|
+
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
|
63
|
+
ActiveRecord::Base.establish_connection(config['test'])
|
64
|
+
|
65
|
+
def reset_class class_name
|
66
|
+
ActiveRecord::Base.send(:include, Paperclip)
|
67
|
+
Object.send(:remove_const, class_name) rescue nil
|
68
|
+
klass = Object.const_set(class_name, Class.new(ActiveRecord::Base))
|
69
|
+
klass.class_eval{ include Paperclip }
|
70
|
+
klass
|
71
|
+
end
|
72
|
+
|
73
|
+
def reset_table table_name, &block
|
74
|
+
block ||= lambda { |table| true }
|
75
|
+
ActiveRecord::Base.connection.create_table :dummies, {:force => true}, &block
|
76
|
+
end
|
77
|
+
|
78
|
+
def modify_table table_name, &block
|
79
|
+
ActiveRecord::Base.connection.change_table :dummies, &block
|
80
|
+
end
|
81
|
+
|
82
|
+
def rebuild_model options = {}
|
83
|
+
ActiveRecord::Base.connection.create_table :dummies, :force => true do |table|
|
84
|
+
table.column :other, :string
|
85
|
+
table.column :avatar_file_name, :string
|
86
|
+
table.column :avatar_content_type, :string
|
87
|
+
table.column :avatar_file_size, :integer
|
88
|
+
table.column :avatar_updated_at, :datetime
|
89
|
+
if options.delete(:with_dimensions)
|
90
|
+
table.column :avatar_width, :integer
|
91
|
+
table.column :avatar_height, :integer
|
92
|
+
end
|
93
|
+
end
|
94
|
+
rebuild_class options
|
95
|
+
end
|
96
|
+
|
97
|
+
def rebuild_class options = {}
|
98
|
+
ActiveRecord::Base.send(:include, Paperclip)
|
99
|
+
Object.send(:remove_const, "Dummy") rescue nil
|
100
|
+
Object.const_set("Dummy", Class.new(ActiveRecord::Base))
|
101
|
+
Dummy.class_eval do
|
102
|
+
include Paperclip
|
103
|
+
has_attached_file :avatar, options
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class FakeModel
|
108
|
+
attr_accessor :avatar_file_name,
|
109
|
+
:avatar_file_size,
|
110
|
+
:avatar_last_updated,
|
111
|
+
:avatar_content_type,
|
112
|
+
:id
|
113
|
+
|
114
|
+
def errors
|
115
|
+
@errors ||= []
|
116
|
+
end
|
117
|
+
|
118
|
+
def run_paperclip_callbacks name, *args
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
def attachment options
|
124
|
+
Paperclip::Attachment.new(:avatar, FakeModel.new, options)
|
125
|
+
end
|
126
|
+
|
127
|
+
def silence_warnings
|
128
|
+
old_verbose, $VERBOSE = $VERBOSE, nil
|
129
|
+
yield
|
130
|
+
ensure
|
131
|
+
$VERBOSE = old_verbose
|
132
|
+
end
|
133
|
+
|
134
|
+
def should_accept_dummy_class
|
135
|
+
should "accept the class" do
|
136
|
+
assert_accepts @matcher, @dummy_class
|
137
|
+
end
|
138
|
+
|
139
|
+
should "accept an instance of that class" do
|
140
|
+
assert_accepts @matcher, @dummy_class.new
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def should_reject_dummy_class
|
145
|
+
should "reject the class" do
|
146
|
+
assert_rejects @matcher, @dummy_class
|
147
|
+
end
|
148
|
+
|
149
|
+
should "reject an instance of that class" do
|
150
|
+
assert_rejects @matcher, @dummy_class.new
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,610 @@
|
|
1
|
+
require 'test/helper'
|
2
|
+
|
3
|
+
class IntegrationTest < Test::Unit::TestCase
|
4
|
+
context "Many models at once" do
|
5
|
+
setup do
|
6
|
+
rebuild_model
|
7
|
+
@file = File.new(File.join(FIXTURES_DIR, "5k.png"), 'rb')
|
8
|
+
300.times do |i|
|
9
|
+
Dummy.create! :avatar => @file
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
should "not exceed the open file limit" do
|
14
|
+
assert_nothing_raised do
|
15
|
+
dummies = Dummy.find(:all)
|
16
|
+
dummies.each { |dummy| dummy.avatar }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "An attachment" do
|
22
|
+
setup do
|
23
|
+
rebuild_model :styles => { :thumb => "50x50#" }
|
24
|
+
@dummy = Dummy.new
|
25
|
+
@file = File.new(File.join(File.dirname(__FILE__),
|
26
|
+
"fixtures",
|
27
|
+
"5k.png"), 'rb')
|
28
|
+
@dummy.avatar = @file
|
29
|
+
assert @dummy.save
|
30
|
+
end
|
31
|
+
|
32
|
+
teardown { @file.close }
|
33
|
+
|
34
|
+
should "create its thumbnails properly" do
|
35
|
+
assert_match /\b50x50\b/, `identify "#{@dummy.avatar.path(:thumb)}"`
|
36
|
+
end
|
37
|
+
|
38
|
+
context "redefining its attachment styles" do
|
39
|
+
setup do
|
40
|
+
Dummy.class_eval do
|
41
|
+
has_attached_file :avatar, :styles => { :thumb => "150x25#" }
|
42
|
+
has_attached_file :avatar, :styles => { :thumb => "150x25#", :dynamic => lambda { |a| '50x50#' } }
|
43
|
+
end
|
44
|
+
@d2 = Dummy.find(@dummy.id)
|
45
|
+
@d2.avatar.reprocess!
|
46
|
+
@d2.save
|
47
|
+
end
|
48
|
+
|
49
|
+
should "create its thumbnails properly" do
|
50
|
+
assert_match /\b150x25\b/, `identify "#{@dummy.avatar.path(:thumb)}"`
|
51
|
+
assert_match /\b50x50\b/, `identify "#{@dummy.avatar.path(:dynamic)}"`
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "A model that modifies its original" do
|
57
|
+
setup do
|
58
|
+
rebuild_model :styles => { :original => "2x2#" }
|
59
|
+
@dummy = Dummy.new
|
60
|
+
@file = File.new(File.join(File.dirname(__FILE__),
|
61
|
+
"fixtures",
|
62
|
+
"5k.png"), 'rb')
|
63
|
+
@dummy.avatar = @file
|
64
|
+
end
|
65
|
+
|
66
|
+
should "report the file size of the processed file and not the original" do
|
67
|
+
assert_not_equal @file.size, @dummy.avatar.size
|
68
|
+
end
|
69
|
+
|
70
|
+
teardown { @file.close }
|
71
|
+
end
|
72
|
+
|
73
|
+
context "A model with attachments scoped under an id" do
|
74
|
+
setup do
|
75
|
+
rebuild_model :styles => { :large => "100x100",
|
76
|
+
:medium => "50x50" },
|
77
|
+
:path => ":rails_root/tmp/:id/:attachments/:style.:extension"
|
78
|
+
@dummy = Dummy.new
|
79
|
+
@file = File.new(File.join(File.dirname(__FILE__),
|
80
|
+
"fixtures",
|
81
|
+
"5k.png"), 'rb')
|
82
|
+
@dummy.avatar = @file
|
83
|
+
end
|
84
|
+
|
85
|
+
teardown { @file.close }
|
86
|
+
|
87
|
+
context "when saved" do
|
88
|
+
setup do
|
89
|
+
@dummy.save
|
90
|
+
@saved_path = @dummy.avatar.path(:large)
|
91
|
+
end
|
92
|
+
|
93
|
+
should "have a large file in the right place" do
|
94
|
+
assert File.exists?(@dummy.avatar.path(:large))
|
95
|
+
end
|
96
|
+
|
97
|
+
context "and deleted" do
|
98
|
+
setup do
|
99
|
+
@dummy.avatar.clear
|
100
|
+
@dummy.save
|
101
|
+
end
|
102
|
+
|
103
|
+
should "not have a large file in the right place anymore" do
|
104
|
+
assert ! File.exists?(@saved_path)
|
105
|
+
end
|
106
|
+
|
107
|
+
should "not have its next two parent directories" do
|
108
|
+
assert ! File.exists?(File.dirname(@saved_path))
|
109
|
+
assert ! File.exists?(File.dirname(File.dirname(@saved_path)))
|
110
|
+
end
|
111
|
+
|
112
|
+
before_should "not die if an unexpected SystemCallError happens" do
|
113
|
+
FileUtils.stubs(:rmdir).raises(Errno::EPIPE)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
context "A model with no attachment validation" do
|
120
|
+
setup do
|
121
|
+
rebuild_model :styles => { :large => "300x300>",
|
122
|
+
:medium => "100x100",
|
123
|
+
:thumb => ["32x32#", :gif] },
|
124
|
+
:default_style => :medium,
|
125
|
+
:url => "/:attachment/:class/:style/:id/:basename.:extension",
|
126
|
+
:path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
|
127
|
+
@dummy = Dummy.new
|
128
|
+
end
|
129
|
+
|
130
|
+
should "have its definition return false when asked about whiny_thumbnails" do
|
131
|
+
assert ! Dummy.attachment_definitions[:avatar][:whiny_thumbnails]
|
132
|
+
end
|
133
|
+
|
134
|
+
context "when validates_attachment_thumbnails is called" do
|
135
|
+
setup do
|
136
|
+
Dummy.validates_attachment_thumbnails :avatar
|
137
|
+
end
|
138
|
+
|
139
|
+
should "have its definition return true when asked about whiny_thumbnails" do
|
140
|
+
assert_equal true, Dummy.attachment_definitions[:avatar][:whiny_thumbnails]
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
context "redefined to have attachment validations" do
|
145
|
+
setup do
|
146
|
+
rebuild_model :styles => { :large => "300x300>",
|
147
|
+
:medium => "100x100",
|
148
|
+
:thumb => ["32x32#", :gif] },
|
149
|
+
:whiny_thumbnails => true,
|
150
|
+
:default_style => :medium,
|
151
|
+
:url => "/:attachment/:class/:style/:id/:basename.:extension",
|
152
|
+
:path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
|
153
|
+
end
|
154
|
+
|
155
|
+
should "have its definition return true when asked about whiny_thumbnails" do
|
156
|
+
assert_equal true, Dummy.attachment_definitions[:avatar][:whiny_thumbnails]
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
context "A model with no convert_options setting" do
|
162
|
+
setup do
|
163
|
+
rebuild_model :styles => { :large => "300x300>",
|
164
|
+
:medium => "100x100",
|
165
|
+
:thumb => ["32x32#", :gif] },
|
166
|
+
:default_style => :medium,
|
167
|
+
:url => "/:attachment/:class/:style/:id/:basename.:extension",
|
168
|
+
:path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
|
169
|
+
@dummy = Dummy.new
|
170
|
+
end
|
171
|
+
|
172
|
+
should "have its definition return nil when asked about convert_options" do
|
173
|
+
assert ! Dummy.attachment_definitions[:avatar][:convert_options]
|
174
|
+
end
|
175
|
+
|
176
|
+
context "redefined to have convert_options setting" do
|
177
|
+
setup do
|
178
|
+
rebuild_model :styles => { :large => "300x300>",
|
179
|
+
:medium => "100x100",
|
180
|
+
:thumb => ["32x32#", :gif] },
|
181
|
+
:convert_options => "-strip -depth 8",
|
182
|
+
:default_style => :medium,
|
183
|
+
:url => "/:attachment/:class/:style/:id/:basename.:extension",
|
184
|
+
:path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
|
185
|
+
end
|
186
|
+
|
187
|
+
should "have its definition return convert_options value when asked about convert_options" do
|
188
|
+
assert_equal "-strip -depth 8", Dummy.attachment_definitions[:avatar][:convert_options]
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
context "A model with no dimension columns" do
|
194
|
+
setup do
|
195
|
+
rebuild_model
|
196
|
+
@dummy = Dummy.new
|
197
|
+
@file = File.new(File.join(FIXTURES_DIR, "5k.png"))
|
198
|
+
end
|
199
|
+
|
200
|
+
should "not break on image uploads" do
|
201
|
+
assert_nothing_raised do
|
202
|
+
assert @dummy.avatar = @file
|
203
|
+
assert @dummy.save
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
should "return nil when asked for the width/height" do
|
208
|
+
@dummy.avatar = @file
|
209
|
+
@dummy.save
|
210
|
+
assert_nil @dummy.avatar.width
|
211
|
+
assert_nil @dummy.avatar.height
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
context "A model with dimension columns" do
|
216
|
+
setup do
|
217
|
+
rebuild_model :with_dimensions => true
|
218
|
+
@dummy = Dummy.new
|
219
|
+
@image_file = File.new(File.join(FIXTURES_DIR, "50x50.png"))
|
220
|
+
@image_file2 = File.new(File.join(FIXTURES_DIR, "5k.png"))
|
221
|
+
@text_file = File.new(File.join(FIXTURES_DIR, "text.txt"))
|
222
|
+
@bad_file = File.new(File.join(FIXTURES_DIR, "bad.png"))
|
223
|
+
end
|
224
|
+
|
225
|
+
should "return nil when asked for the width/height of a non-image upload" do
|
226
|
+
@dummy.avatar = @text_file
|
227
|
+
@dummy.save
|
228
|
+
assert_nil @dummy.avatar.width
|
229
|
+
assert_nil @dummy.avatar.height
|
230
|
+
end
|
231
|
+
|
232
|
+
should "assign dimensions for image uploads" do
|
233
|
+
assert @dummy.avatar = @image_file
|
234
|
+
assert @dummy.save
|
235
|
+
assert_equal 50, @dummy.avatar.width
|
236
|
+
assert_equal 50, @dummy.avatar.height
|
237
|
+
end
|
238
|
+
|
239
|
+
should "not break when a bad image is uploaded" do
|
240
|
+
assert_nothing_raised do
|
241
|
+
@dummy.avatar = @bad_file
|
242
|
+
@dummy.save
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
should "return nil width/height when a bad image is uploaded" do
|
247
|
+
assert_nothing_raised do
|
248
|
+
@dummy.avatar = @bad_file
|
249
|
+
@dummy.save
|
250
|
+
end
|
251
|
+
assert_nil @dummy.avatar.width
|
252
|
+
assert_nil @dummy.avatar.height
|
253
|
+
end
|
254
|
+
|
255
|
+
should "change dimensions if changing image upload" do
|
256
|
+
@dummy.avatar = @image_file
|
257
|
+
@dummy.save
|
258
|
+
old_size = `identify -format "%wx%h" #{@dummy.avatar.to_file(:original).path}`.chomp
|
259
|
+
assert_equal old_size, "#{@dummy.avatar.width}x#{@dummy.avatar.height}"
|
260
|
+
@dummy.avatar = @image_file2
|
261
|
+
@dummy.save
|
262
|
+
new_size = `identify -format "%wx%h" #{@dummy.avatar.to_file(:original).path}`.chomp
|
263
|
+
assert_equal new_size, "#{@dummy.avatar.width}x#{@dummy.avatar.height}"
|
264
|
+
assert_not_equal old_size, new_size # sanity check
|
265
|
+
end
|
266
|
+
|
267
|
+
should "unassign dimensions if changing image upload to non-image" do
|
268
|
+
@dummy.avatar = @image_file
|
269
|
+
@dummy.save
|
270
|
+
@dummy.avatar = @text_file
|
271
|
+
@dummy.save
|
272
|
+
assert_nil @dummy.avatar.width
|
273
|
+
assert_nil @dummy.avatar.height
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
context "A model with dimension columns and custom sizes" do
|
278
|
+
setup do
|
279
|
+
rebuild_model :with_dimensions => true,
|
280
|
+
:styles => { :large => "40x30>",
|
281
|
+
:medium => "20x20",
|
282
|
+
:thumb => ["5x5#", :gif] },
|
283
|
+
:default_style => :medium
|
284
|
+
|
285
|
+
@dummy = Dummy.new
|
286
|
+
@file = File.new(File.join(FIXTURES_DIR, "50x50.png"))
|
287
|
+
end
|
288
|
+
|
289
|
+
should "return the default style dimensions" do
|
290
|
+
@dummy.avatar = @file
|
291
|
+
@dummy.save!
|
292
|
+
assert_equal 20, @dummy.avatar.width
|
293
|
+
assert_equal 20, @dummy.avatar.height
|
294
|
+
end
|
295
|
+
|
296
|
+
should "return other style dimensions when asked" do
|
297
|
+
@dummy.avatar = @file
|
298
|
+
@dummy.save!
|
299
|
+
|
300
|
+
assert_equal 5, @dummy.avatar.width(:thumb)
|
301
|
+
assert_equal 5, @dummy.avatar.height(:thumb)
|
302
|
+
|
303
|
+
assert_equal 30, @dummy.avatar.width(:large)
|
304
|
+
assert_equal 30, @dummy.avatar.height(:large)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
context "A model with a filesystem attachment" do
|
309
|
+
setup do
|
310
|
+
rebuild_model :styles => { :large => "300x300>",
|
311
|
+
:medium => "100x100",
|
312
|
+
:thumb => ["32x32#", :gif] },
|
313
|
+
:whiny_thumbnails => true,
|
314
|
+
:default_style => :medium,
|
315
|
+
:url => "/:attachment/:class/:style/:id/:basename.:extension",
|
316
|
+
:path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
|
317
|
+
@dummy = Dummy.new
|
318
|
+
@file = File.new(File.join(FIXTURES_DIR, "5k.png"), 'rb')
|
319
|
+
@bad_file = File.new(File.join(FIXTURES_DIR, "bad.png"), 'rb')
|
320
|
+
@text_file = File.new(File.join(FIXTURES_DIR, "text.txt"), 'rb')
|
321
|
+
|
322
|
+
assert @dummy.avatar = @file
|
323
|
+
assert @dummy.valid?
|
324
|
+
assert @dummy.save
|
325
|
+
end
|
326
|
+
|
327
|
+
should "write and delete its files" do
|
328
|
+
[["434x66", :original],
|
329
|
+
["300x46", :large],
|
330
|
+
["100x15", :medium],
|
331
|
+
["32x32", :thumb]].each do |geo, style|
|
332
|
+
cmd = %Q[identify -format "%wx%h" "#{@dummy.avatar.path(style)}"]
|
333
|
+
assert_equal geo, `#{cmd}`.chomp, cmd
|
334
|
+
end
|
335
|
+
|
336
|
+
saved_paths = [:thumb, :medium, :large, :original].collect{|s| @dummy.avatar.path(s) }
|
337
|
+
|
338
|
+
@d2 = Dummy.find(@dummy.id)
|
339
|
+
assert_equal "100x15", `identify -format "%wx%h" "#{@d2.avatar.path}"`.chomp
|
340
|
+
assert_equal "434x66", `identify -format "%wx%h" "#{@d2.avatar.path(:original)}"`.chomp
|
341
|
+
assert_equal "300x46", `identify -format "%wx%h" "#{@d2.avatar.path(:large)}"`.chomp
|
342
|
+
assert_equal "100x15", `identify -format "%wx%h" "#{@d2.avatar.path(:medium)}"`.chomp
|
343
|
+
assert_equal "32x32", `identify -format "%wx%h" "#{@d2.avatar.path(:thumb)}"`.chomp
|
344
|
+
|
345
|
+
@dummy.avatar = "not a valid file but not nil"
|
346
|
+
assert_equal File.basename(@file.path), @dummy.avatar_file_name
|
347
|
+
assert @dummy.valid?
|
348
|
+
assert @dummy.save
|
349
|
+
|
350
|
+
saved_paths.each do |p|
|
351
|
+
assert File.exists?(p)
|
352
|
+
end
|
353
|
+
|
354
|
+
@dummy.avatar.clear
|
355
|
+
assert_nil @dummy.avatar_file_name
|
356
|
+
assert @dummy.valid?
|
357
|
+
assert @dummy.save
|
358
|
+
|
359
|
+
saved_paths.each do |p|
|
360
|
+
assert ! File.exists?(p)
|
361
|
+
end
|
362
|
+
|
363
|
+
@d2 = Dummy.find(@dummy.id)
|
364
|
+
assert_nil @d2.avatar_file_name
|
365
|
+
end
|
366
|
+
|
367
|
+
should "work exactly the same when new as when reloaded" do
|
368
|
+
@d2 = Dummy.find(@dummy.id)
|
369
|
+
|
370
|
+
assert_equal @dummy.avatar_file_name, @d2.avatar_file_name
|
371
|
+
[:thumb, :medium, :large, :original].each do |style|
|
372
|
+
assert_equal @dummy.avatar.path(style), @d2.avatar.path(style)
|
373
|
+
end
|
374
|
+
|
375
|
+
saved_paths = [:thumb, :medium, :large, :original].collect{|s| @dummy.avatar.path(s) }
|
376
|
+
|
377
|
+
@d2.avatar.clear
|
378
|
+
assert @d2.save
|
379
|
+
|
380
|
+
saved_paths.each do |p|
|
381
|
+
assert ! File.exists?(p)
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
should "know the difference between good files, bad files, and not files" do
|
386
|
+
expected = @dummy.avatar.to_file
|
387
|
+
@dummy.avatar = "not a file"
|
388
|
+
assert @dummy.valid?
|
389
|
+
assert_equal expected.path, @dummy.avatar.path
|
390
|
+
expected.close
|
391
|
+
|
392
|
+
@dummy.avatar = @bad_file
|
393
|
+
assert ! @dummy.valid?
|
394
|
+
end
|
395
|
+
|
396
|
+
should "properly determine #image?" do
|
397
|
+
@dummy.avatar = @file
|
398
|
+
assert_equal true, @dummy.avatar.image?
|
399
|
+
|
400
|
+
@dummy.avatar = @text_file
|
401
|
+
assert_equal false, @dummy.avatar.image?
|
402
|
+
|
403
|
+
@dummy.avatar = "not a file"
|
404
|
+
assert_equal false, @dummy.avatar.image?
|
405
|
+
end
|
406
|
+
|
407
|
+
should "know the difference between good files, bad files, and not files when validating" do
|
408
|
+
Dummy.validates_attachment_presence :avatar
|
409
|
+
@d2 = Dummy.find(@dummy.id)
|
410
|
+
@d2.avatar = @file
|
411
|
+
assert @d2.valid?, @d2.errors.full_messages.inspect
|
412
|
+
@d2.avatar = @bad_file
|
413
|
+
assert ! @d2.valid?
|
414
|
+
end
|
415
|
+
|
416
|
+
should "be able to reload without saving and not have the file disappear" do
|
417
|
+
@dummy.avatar = @file
|
418
|
+
assert @dummy.save
|
419
|
+
@dummy.avatar.clear
|
420
|
+
assert_nil @dummy.avatar_file_name
|
421
|
+
@dummy.reload
|
422
|
+
assert_equal "5k.png", @dummy.avatar_file_name
|
423
|
+
end
|
424
|
+
|
425
|
+
context "that is assigned its file from another Paperclip attachment" do
|
426
|
+
setup do
|
427
|
+
@dummy2 = Dummy.new
|
428
|
+
@file2 = File.new(File.join(FIXTURES_DIR, "12k.png"), 'rb')
|
429
|
+
assert @dummy2.avatar = @file2
|
430
|
+
@dummy2.save
|
431
|
+
end
|
432
|
+
|
433
|
+
should "work when assigned a file" do
|
434
|
+
assert_not_equal `identify -format "%wx%h" "#{@dummy.avatar.path(:original)}"`,
|
435
|
+
`identify -format "%wx%h" "#{@dummy2.avatar.path(:original)}"`
|
436
|
+
|
437
|
+
assert @dummy.avatar = @dummy2.avatar
|
438
|
+
@dummy.save
|
439
|
+
assert_equal `identify -format "%wx%h" "#{@dummy.avatar.path(:original)}"`,
|
440
|
+
`identify -format "%wx%h" "#{@dummy2.avatar.path(:original)}"`
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
end
|
445
|
+
|
446
|
+
context "A model with an attachments association and a Paperclip attachment" do
|
447
|
+
setup do
|
448
|
+
Dummy.class_eval do
|
449
|
+
has_many :attachments, :class_name => 'Dummy'
|
450
|
+
end
|
451
|
+
|
452
|
+
@dummy = Dummy.new
|
453
|
+
@dummy.avatar = File.new(File.join(File.dirname(__FILE__),
|
454
|
+
"fixtures",
|
455
|
+
"5k.png"), 'rb')
|
456
|
+
end
|
457
|
+
|
458
|
+
should "should not error when saving" do
|
459
|
+
assert_nothing_raised do
|
460
|
+
@dummy.save!
|
461
|
+
end
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
if ENV['S3_TEST_BUCKET']
|
466
|
+
def s3_files_for attachment
|
467
|
+
[:thumb, :medium, :large, :original].inject({}) do |files, style|
|
468
|
+
data = `curl "#{attachment.url(style)}" 2>/dev/null`.chomp
|
469
|
+
t = Tempfile.new("paperclip-test")
|
470
|
+
t.binmode
|
471
|
+
t.write(data)
|
472
|
+
t.rewind
|
473
|
+
files[style] = t
|
474
|
+
files
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
def s3_headers_for attachment, style
|
479
|
+
`curl --head "#{attachment.url(style)}" 2>/dev/null`.split("\n").inject({}) do |h,head|
|
480
|
+
split_head = head.chomp.split(/\s*:\s*/, 2)
|
481
|
+
h[split_head.first.downcase] = split_head.last unless split_head.empty?
|
482
|
+
h
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
context "A model with an S3 attachment" do
|
487
|
+
setup do
|
488
|
+
rebuild_model :styles => { :large => "300x300>",
|
489
|
+
:medium => "100x100",
|
490
|
+
:thumb => ["32x32#", :gif] },
|
491
|
+
:storage => :s3,
|
492
|
+
:whiny_thumbnails => true,
|
493
|
+
# :s3_options => {:logger => Logger.new(StringIO.new)},
|
494
|
+
:s3_credentials => File.new(File.join(File.dirname(__FILE__), "s3.yml")),
|
495
|
+
:default_style => :medium,
|
496
|
+
:bucket => ENV['S3_TEST_BUCKET'],
|
497
|
+
:path => ":class/:attachment/:id/:style/:basename.:extension"
|
498
|
+
@dummy = Dummy.new
|
499
|
+
@file = File.new(File.join(FIXTURES_DIR, "5k.png"), 'rb')
|
500
|
+
@bad_file = File.new(File.join(FIXTURES_DIR, "bad.png"), 'rb')
|
501
|
+
|
502
|
+
assert @dummy.avatar = @file
|
503
|
+
assert @dummy.valid?
|
504
|
+
assert @dummy.save
|
505
|
+
|
506
|
+
@files_on_s3 = s3_files_for @dummy.avatar
|
507
|
+
end
|
508
|
+
|
509
|
+
should "have the same contents as the original" do
|
510
|
+
@file.rewind
|
511
|
+
assert_equal @file.read, @files_on_s3[:original].read
|
512
|
+
end
|
513
|
+
|
514
|
+
should "write and delete its files" do
|
515
|
+
[["434x66", :original],
|
516
|
+
["300x46", :large],
|
517
|
+
["100x15", :medium],
|
518
|
+
["32x32", :thumb]].each do |geo, style|
|
519
|
+
cmd = %Q[identify -format "%wx%h" "#{@files_on_s3[style].path}"]
|
520
|
+
assert_equal geo, `#{cmd}`.chomp, cmd
|
521
|
+
end
|
522
|
+
|
523
|
+
@d2 = Dummy.find(@dummy.id)
|
524
|
+
@d2_files = s3_files_for @d2.avatar
|
525
|
+
[["434x66", :original],
|
526
|
+
["300x46", :large],
|
527
|
+
["100x15", :medium],
|
528
|
+
["32x32", :thumb]].each do |geo, style|
|
529
|
+
cmd = %Q[identify -format "%wx%h" "#{@d2_files[style].path}"]
|
530
|
+
assert_equal geo, `#{cmd}`.chomp, cmd
|
531
|
+
end
|
532
|
+
|
533
|
+
@dummy.avatar = "not a valid file but not nil"
|
534
|
+
assert_equal File.basename(@file.path), @dummy.avatar_file_name
|
535
|
+
assert @dummy.valid?
|
536
|
+
assert @dummy.save
|
537
|
+
|
538
|
+
[:thumb, :medium, :large, :original].each do |style|
|
539
|
+
assert @dummy.avatar.exists?(style)
|
540
|
+
end
|
541
|
+
|
542
|
+
@dummy.avatar.clear
|
543
|
+
assert_nil @dummy.avatar_file_name
|
544
|
+
assert @dummy.valid?
|
545
|
+
assert @dummy.save
|
546
|
+
|
547
|
+
[:thumb, :medium, :large, :original].each do |style|
|
548
|
+
assert ! @dummy.avatar.exists?(style)
|
549
|
+
end
|
550
|
+
|
551
|
+
@d2 = Dummy.find(@dummy.id)
|
552
|
+
assert_nil @d2.avatar_file_name
|
553
|
+
end
|
554
|
+
|
555
|
+
should "work exactly the same when new as when reloaded" do
|
556
|
+
@d2 = Dummy.find(@dummy.id)
|
557
|
+
|
558
|
+
assert_equal @dummy.avatar_file_name, @d2.avatar_file_name
|
559
|
+
[:thumb, :medium, :large, :original].each do |style|
|
560
|
+
assert_equal @dummy.avatar.to_file(style).read, @d2.avatar.to_file(style).read
|
561
|
+
end
|
562
|
+
|
563
|
+
saved_keys = [:thumb, :medium, :large, :original].collect{|s| @dummy.avatar.to_file(s) }
|
564
|
+
|
565
|
+
@d2.avatar.clear
|
566
|
+
assert @d2.save
|
567
|
+
|
568
|
+
[:thumb, :medium, :large, :original].each do |style|
|
569
|
+
assert ! @dummy.avatar.exists?(style)
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
should "know the difference between good files, bad files, not files, and nil" do
|
574
|
+
expected = @dummy.avatar.to_file
|
575
|
+
@dummy.avatar = "not a file"
|
576
|
+
assert @dummy.valid?
|
577
|
+
assert_equal expected.read, @dummy.avatar.to_file.read
|
578
|
+
|
579
|
+
@dummy.avatar = @bad_file
|
580
|
+
assert ! @dummy.valid?
|
581
|
+
@dummy.avatar = nil
|
582
|
+
assert @dummy.valid?
|
583
|
+
|
584
|
+
Dummy.validates_attachment_presence :avatar
|
585
|
+
@d2 = Dummy.find(@dummy.id)
|
586
|
+
@d2.avatar = @file
|
587
|
+
assert @d2.valid?
|
588
|
+
@d2.avatar = @bad_file
|
589
|
+
assert ! @d2.valid?
|
590
|
+
@d2.avatar = nil
|
591
|
+
assert ! @d2.valid?
|
592
|
+
end
|
593
|
+
|
594
|
+
should "be able to reload without saving and not have the file disappear" do
|
595
|
+
@dummy.avatar = @file
|
596
|
+
assert @dummy.save
|
597
|
+
@dummy.avatar = nil
|
598
|
+
assert_nil @dummy.avatar_file_name
|
599
|
+
@dummy.reload
|
600
|
+
assert_equal "5k.png", @dummy.avatar_file_name
|
601
|
+
end
|
602
|
+
|
603
|
+
should "have the right content type" do
|
604
|
+
headers = s3_headers_for(@dummy.avatar, :original)
|
605
|
+
assert_equal 'image/png', headers['content-type']
|
606
|
+
end
|
607
|
+
end
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|