##// END OF EJS Templates
Strip eols from file names (#14819)....
Jean-Philippe Lang -
r11898:02fca76c13cd
parent child
Show More
@@ -1,326 +1,326
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require "digest/md5"
19 19 require "fileutils"
20 20
21 21 class Attachment < ActiveRecord::Base
22 22 belongs_to :container, :polymorphic => true
23 23 belongs_to :author, :class_name => "User", :foreign_key => "author_id"
24 24
25 25 validates_presence_of :filename, :author
26 26 validates_length_of :filename, :maximum => 255
27 27 validates_length_of :disk_filename, :maximum => 255
28 28 validates_length_of :description, :maximum => 255
29 29 validate :validate_max_file_size
30 30
31 31 acts_as_event :title => :filename,
32 32 :url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id, :filename => o.filename}}
33 33
34 34 acts_as_activity_provider :type => 'files',
35 35 :permission => :view_files,
36 36 :author_key => :author_id,
37 37 :find_options => {:select => "#{Attachment.table_name}.*",
38 38 :joins => "LEFT JOIN #{Version.table_name} ON #{Attachment.table_name}.container_type='Version' AND #{Version.table_name}.id = #{Attachment.table_name}.container_id " +
39 39 "LEFT JOIN #{Project.table_name} ON #{Version.table_name}.project_id = #{Project.table_name}.id OR ( #{Attachment.table_name}.container_type='Project' AND #{Attachment.table_name}.container_id = #{Project.table_name}.id )"}
40 40
41 41 acts_as_activity_provider :type => 'documents',
42 42 :permission => :view_documents,
43 43 :author_key => :author_id,
44 44 :find_options => {:select => "#{Attachment.table_name}.*",
45 45 :joins => "LEFT JOIN #{Document.table_name} ON #{Attachment.table_name}.container_type='Document' AND #{Document.table_name}.id = #{Attachment.table_name}.container_id " +
46 46 "LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"}
47 47
48 48 cattr_accessor :storage_path
49 49 @@storage_path = Redmine::Configuration['attachments_storage_path'] || File.join(Rails.root, "files")
50 50
51 51 cattr_accessor :thumbnails_storage_path
52 52 @@thumbnails_storage_path = File.join(Rails.root, "tmp", "thumbnails")
53 53
54 54 before_save :files_to_final_location
55 55 after_destroy :delete_from_disk
56 56
57 57 # Returns an unsaved copy of the attachment
58 58 def copy(attributes=nil)
59 59 copy = self.class.new
60 60 copy.attributes = self.attributes.dup.except("id", "downloads")
61 61 copy.attributes = attributes if attributes
62 62 copy
63 63 end
64 64
65 65 def validate_max_file_size
66 66 if @temp_file && self.filesize > Setting.attachment_max_size.to_i.kilobytes
67 67 errors.add(:base, l(:error_attachment_too_big, :max_size => Setting.attachment_max_size.to_i.kilobytes))
68 68 end
69 69 end
70 70
71 71 def file=(incoming_file)
72 72 unless incoming_file.nil?
73 73 @temp_file = incoming_file
74 74 if @temp_file.size > 0
75 75 if @temp_file.respond_to?(:original_filename)
76 76 self.filename = @temp_file.original_filename
77 77 self.filename.force_encoding("UTF-8") if filename.respond_to?(:force_encoding)
78 78 end
79 79 if @temp_file.respond_to?(:content_type)
80 80 self.content_type = @temp_file.content_type.to_s.chomp
81 81 end
82 82 if content_type.blank? && filename.present?
83 83 self.content_type = Redmine::MimeType.of(filename)
84 84 end
85 85 self.filesize = @temp_file.size
86 86 end
87 87 end
88 88 end
89 89
90 90 def file
91 91 nil
92 92 end
93 93
94 94 def filename=(arg)
95 95 write_attribute :filename, sanitize_filename(arg.to_s)
96 96 filename
97 97 end
98 98
99 99 # Copies the temporary file to its final location
100 100 # and computes its MD5 hash
101 101 def files_to_final_location
102 102 if @temp_file && (@temp_file.size > 0)
103 103 self.disk_directory = target_directory
104 104 self.disk_filename = Attachment.disk_filename(filename, disk_directory)
105 105 logger.info("Saving attachment '#{self.diskfile}' (#{@temp_file.size} bytes)")
106 106 path = File.dirname(diskfile)
107 107 unless File.directory?(path)
108 108 FileUtils.mkdir_p(path)
109 109 end
110 110 md5 = Digest::MD5.new
111 111 File.open(diskfile, "wb") do |f|
112 112 if @temp_file.respond_to?(:read)
113 113 buffer = ""
114 114 while (buffer = @temp_file.read(8192))
115 115 f.write(buffer)
116 116 md5.update(buffer)
117 117 end
118 118 else
119 119 f.write(@temp_file)
120 120 md5.update(@temp_file)
121 121 end
122 122 end
123 123 self.digest = md5.hexdigest
124 124 end
125 125 @temp_file = nil
126 126 # Don't save the content type if it's longer than the authorized length
127 127 if self.content_type && self.content_type.length > 255
128 128 self.content_type = nil
129 129 end
130 130 end
131 131
132 132 # Deletes the file from the file system if it's not referenced by other attachments
133 133 def delete_from_disk
134 134 if Attachment.where("disk_filename = ? AND id <> ?", disk_filename, id).empty?
135 135 delete_from_disk!
136 136 end
137 137 end
138 138
139 139 # Returns file's location on disk
140 140 def diskfile
141 141 File.join(self.class.storage_path, disk_directory.to_s, disk_filename.to_s)
142 142 end
143 143
144 144 def title
145 145 title = filename.to_s
146 146 if description.present?
147 147 title << " (#{description})"
148 148 end
149 149 title
150 150 end
151 151
152 152 def increment_download
153 153 increment!(:downloads)
154 154 end
155 155
156 156 def project
157 157 container.try(:project)
158 158 end
159 159
160 160 def visible?(user=User.current)
161 161 if container_id
162 162 container && container.attachments_visible?(user)
163 163 else
164 164 author == user
165 165 end
166 166 end
167 167
168 168 def deletable?(user=User.current)
169 169 if container_id
170 170 container && container.attachments_deletable?(user)
171 171 else
172 172 author == user
173 173 end
174 174 end
175 175
176 176 def image?
177 177 !!(self.filename =~ /\.(bmp|gif|jpg|jpe|jpeg|png)$/i)
178 178 end
179 179
180 180 def thumbnailable?
181 181 image?
182 182 end
183 183
184 184 # Returns the full path the attachment thumbnail, or nil
185 185 # if the thumbnail cannot be generated.
186 186 def thumbnail(options={})
187 187 if thumbnailable? && readable?
188 188 size = options[:size].to_i
189 189 if size > 0
190 190 # Limit the number of thumbnails per image
191 191 size = (size / 50) * 50
192 192 # Maximum thumbnail size
193 193 size = 800 if size > 800
194 194 else
195 195 size = Setting.thumbnails_size.to_i
196 196 end
197 197 size = 100 unless size > 0
198 198 target = File.join(self.class.thumbnails_storage_path, "#{id}_#{digest}_#{size}.thumb")
199 199
200 200 begin
201 201 Redmine::Thumbnail.generate(self.diskfile, target, size)
202 202 rescue => e
203 203 logger.error "An error occured while generating thumbnail for #{disk_filename} to #{target}\nException was: #{e.message}" if logger
204 204 return nil
205 205 end
206 206 end
207 207 end
208 208
209 209 # Deletes all thumbnails
210 210 def self.clear_thumbnails
211 211 Dir.glob(File.join(thumbnails_storage_path, "*.thumb")).each do |file|
212 212 File.delete file
213 213 end
214 214 end
215 215
216 216 def is_text?
217 217 Redmine::MimeType.is_type?('text', filename)
218 218 end
219 219
220 220 def is_diff?
221 221 self.filename =~ /\.(patch|diff)$/i
222 222 end
223 223
224 224 # Returns true if the file is readable
225 225 def readable?
226 226 File.readable?(diskfile)
227 227 end
228 228
229 229 # Returns the attachment token
230 230 def token
231 231 "#{id}.#{digest}"
232 232 end
233 233
234 234 # Finds an attachment that matches the given token and that has no container
235 235 def self.find_by_token(token)
236 236 if token.to_s =~ /^(\d+)\.([0-9a-f]+)$/
237 237 attachment_id, attachment_digest = $1, $2
238 238 attachment = Attachment.where(:id => attachment_id, :digest => attachment_digest).first
239 239 if attachment && attachment.container.nil?
240 240 attachment
241 241 end
242 242 end
243 243 end
244 244
245 245 # Bulk attaches a set of files to an object
246 246 #
247 247 # Returns a Hash of the results:
248 248 # :files => array of the attached files
249 249 # :unsaved => array of the files that could not be attached
250 250 def self.attach_files(obj, attachments)
251 251 result = obj.save_attachments(attachments, User.current)
252 252 obj.attach_saved_attachments
253 253 result
254 254 end
255 255
256 256 def self.latest_attach(attachments, filename)
257 257 attachments.sort_by(&:created_on).reverse.detect {
258 258 |att| att.filename.downcase == filename.downcase
259 259 }
260 260 end
261 261
262 262 def self.prune(age=1.day)
263 263 Attachment.where("created_on < ? AND (container_type IS NULL OR container_type = '')", Time.now - age).destroy_all
264 264 end
265 265
266 266 # Moves an existing attachment to its target directory
267 267 def move_to_target_directory!
268 268 if !new_record? & readable?
269 269 src = diskfile
270 270 self.disk_directory = target_directory
271 271 dest = diskfile
272 272 if src != dest && FileUtils.mkdir_p(File.dirname(dest)) && FileUtils.mv(src, dest)
273 273 update_column :disk_directory, disk_directory
274 274 end
275 275 end
276 276 end
277 277
278 278 # Moves existing attachments that are stored at the root of the files
279 279 # directory (ie. created before Redmine 2.3) to their target subdirectories
280 280 def self.move_from_root_to_target_directory
281 281 Attachment.where("disk_directory IS NULL OR disk_directory = ''").find_each do |attachment|
282 282 attachment.move_to_target_directory!
283 283 end
284 284 end
285 285
286 286 private
287 287
288 288 # Physically deletes the file from the file system
289 289 def delete_from_disk!
290 290 if disk_filename.present? && File.exist?(diskfile)
291 291 File.delete(diskfile)
292 292 end
293 293 end
294 294
295 295 def sanitize_filename(value)
296 296 # get only the filename, not the whole path
297 just_filename = value.gsub(/^.*(\\|\/)/, '')
297 just_filename = value.gsub(/\A.*(\\|\/)/m, '')
298 298
299 299 # Finally, replace invalid characters with underscore
300 @filename = just_filename.gsub(/[\/\?\%\*\:\|\"\'<>]+/, '_')
300 @filename = just_filename.gsub(/[\/\?\%\*\:\|\"\'<>\n\r]+/, '_')
301 301 end
302 302
303 303 # Returns the subdirectory in which the attachment will be saved
304 304 def target_directory
305 305 time = created_on || DateTime.now
306 306 time.strftime("%Y/%m")
307 307 end
308 308
309 309 # Returns an ASCII or hashed filename that do not
310 310 # exists yet in the given subdirectory
311 311 def self.disk_filename(filename, directory=nil)
312 312 timestamp = DateTime.now.strftime("%y%m%d%H%M%S")
313 313 ascii = ''
314 314 if filename =~ %r{^[a-zA-Z0-9_\.\-]*$}
315 315 ascii = filename
316 316 else
317 317 ascii = Digest::MD5.hexdigest(filename)
318 318 # keep the extension if any
319 319 ascii << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$}
320 320 end
321 321 while File.exist?(File.join(storage_path, directory.to_s, "#{timestamp}_#{ascii}"))
322 322 timestamp.succ!
323 323 end
324 324 "#{timestamp}_#{ascii}"
325 325 end
326 326 end
@@ -1,283 +1,290
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2013 Jean-Philippe Lang
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; either version 2
9 9 # of the License, or (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 19
20 20 require File.expand_path('../../test_helper', __FILE__)
21 21
22 22 class AttachmentTest < ActiveSupport::TestCase
23 23 fixtures :users, :projects, :roles, :members, :member_roles,
24 24 :enabled_modules, :issues, :trackers, :attachments
25 25
26 26 class MockFile
27 27 attr_reader :original_filename, :content_type, :content, :size
28 28
29 29 def initialize(attributes)
30 30 @original_filename = attributes[:original_filename]
31 31 @content_type = attributes[:content_type]
32 32 @content = attributes[:content] || "Content"
33 33 @size = content.size
34 34 end
35 35 end
36 36
37 37 def setup
38 38 set_tmp_attachments_directory
39 39 end
40 40
41 41 def test_container_for_new_attachment_should_be_nil
42 42 assert_nil Attachment.new.container
43 43 end
44 44
45 def test_filename_should_remove_eols
46 assert_equal "line_feed", Attachment.new(:filename => "line\nfeed").filename
47 assert_equal "line_feed", Attachment.new(:filename => "some\npath/line\nfeed").filename
48 assert_equal "carriage_return", Attachment.new(:filename => "carriage\rreturn").filename
49 assert_equal "carriage_return", Attachment.new(:filename => "some\rpath/carriage\rreturn").filename
50 end
51
45 52 def test_create
46 53 a = Attachment.new(:container => Issue.find(1),
47 54 :file => uploaded_test_file("testfile.txt", "text/plain"),
48 55 :author => User.find(1))
49 56 assert a.save
50 57 assert_equal 'testfile.txt', a.filename
51 58 assert_equal 59, a.filesize
52 59 assert_equal 'text/plain', a.content_type
53 60 assert_equal 0, a.downloads
54 61 assert_equal '1478adae0d4eb06d35897518540e25d6', a.digest
55 62
56 63 assert a.disk_directory
57 64 assert_match %r{\A\d{4}/\d{2}\z}, a.disk_directory
58 65
59 66 assert File.exist?(a.diskfile)
60 67 assert_equal 59, File.size(a.diskfile)
61 68 end
62 69
63 70 def test_copy_should_preserve_attributes
64 71 a = Attachment.find(1)
65 72 copy = a.copy
66 73
67 74 assert_save copy
68 75 copy = Attachment.order('id DESC').first
69 76 %w(filename filesize content_type author_id created_on description digest disk_filename disk_directory diskfile).each do |attribute|
70 77 assert_equal a.send(attribute), copy.send(attribute), "#{attribute} was different"
71 78 end
72 79 end
73 80
74 81 def test_size_should_be_validated_for_new_file
75 82 with_settings :attachment_max_size => 0 do
76 83 a = Attachment.new(:container => Issue.find(1),
77 84 :file => uploaded_test_file("testfile.txt", "text/plain"),
78 85 :author => User.find(1))
79 86 assert !a.save
80 87 end
81 88 end
82 89
83 90 def test_size_should_not_be_validated_when_copying
84 91 a = Attachment.create!(:container => Issue.find(1),
85 92 :file => uploaded_test_file("testfile.txt", "text/plain"),
86 93 :author => User.find(1))
87 94 with_settings :attachment_max_size => 0 do
88 95 copy = a.copy
89 96 assert copy.save
90 97 end
91 98 end
92 99
93 100 def test_description_length_should_be_validated
94 101 a = Attachment.new(:description => 'a' * 300)
95 102 assert !a.save
96 103 assert_not_equal [], a.errors[:description]
97 104 end
98 105
99 106 def test_destroy
100 107 a = Attachment.new(:container => Issue.find(1),
101 108 :file => uploaded_test_file("testfile.txt", "text/plain"),
102 109 :author => User.find(1))
103 110 assert a.save
104 111 assert_equal 'testfile.txt', a.filename
105 112 assert_equal 59, a.filesize
106 113 assert_equal 'text/plain', a.content_type
107 114 assert_equal 0, a.downloads
108 115 assert_equal '1478adae0d4eb06d35897518540e25d6', a.digest
109 116 diskfile = a.diskfile
110 117 assert File.exist?(diskfile)
111 118 assert_equal 59, File.size(a.diskfile)
112 119 assert a.destroy
113 120 assert !File.exist?(diskfile)
114 121 end
115 122
116 123 def test_destroy_should_not_delete_file_referenced_by_other_attachment
117 124 a = Attachment.create!(:container => Issue.find(1),
118 125 :file => uploaded_test_file("testfile.txt", "text/plain"),
119 126 :author => User.find(1))
120 127 diskfile = a.diskfile
121 128
122 129 copy = a.copy
123 130 copy.save!
124 131
125 132 assert File.exists?(diskfile)
126 133 a.destroy
127 134 assert File.exists?(diskfile)
128 135 copy.destroy
129 136 assert !File.exists?(diskfile)
130 137 end
131 138
132 139 def test_create_should_auto_assign_content_type
133 140 a = Attachment.new(:container => Issue.find(1),
134 141 :file => uploaded_test_file("testfile.txt", ""),
135 142 :author => User.find(1))
136 143 assert a.save
137 144 assert_equal 'text/plain', a.content_type
138 145 end
139 146
140 147 def test_identical_attachments_at_the_same_time_should_not_overwrite
141 148 a1 = Attachment.create!(:container => Issue.find(1),
142 149 :file => uploaded_test_file("testfile.txt", ""),
143 150 :author => User.find(1))
144 151 a2 = Attachment.create!(:container => Issue.find(1),
145 152 :file => uploaded_test_file("testfile.txt", ""),
146 153 :author => User.find(1))
147 154 assert a1.disk_filename != a2.disk_filename
148 155 end
149 156
150 157 def test_filename_should_be_basenamed
151 158 a = Attachment.new(:file => MockFile.new(:original_filename => "path/to/the/file"))
152 159 assert_equal 'file', a.filename
153 160 end
154 161
155 162 def test_filename_should_be_sanitized
156 163 a = Attachment.new(:file => MockFile.new(:original_filename => "valid:[] invalid:?%*|\"'<>chars"))
157 164 assert_equal 'valid_[] invalid_chars', a.filename
158 165 end
159 166
160 167 def test_diskfilename
161 168 assert Attachment.disk_filename("test_file.txt") =~ /^\d{12}_test_file.txt$/
162 169 assert_equal 'test_file.txt', Attachment.disk_filename("test_file.txt")[13..-1]
163 170 assert_equal '770c509475505f37c2b8fb6030434d6b.txt', Attachment.disk_filename("test_accentuΓ©.txt")[13..-1]
164 171 assert_equal 'f8139524ebb8f32e51976982cd20a85d', Attachment.disk_filename("test_accentuΓ©")[13..-1]
165 172 assert_equal 'cbb5b0f30978ba03731d61f9f6d10011', Attachment.disk_filename("test_accentuΓ©.Γ§a")[13..-1]
166 173 end
167 174
168 175 def test_title
169 176 a = Attachment.new(:filename => "test.png")
170 177 assert_equal "test.png", a.title
171 178
172 179 a = Attachment.new(:filename => "test.png", :description => "Cool image")
173 180 assert_equal "test.png (Cool image)", a.title
174 181 end
175 182
176 183 def test_prune_should_destroy_old_unattached_attachments
177 184 Attachment.create!(:file => uploaded_test_file("testfile.txt", ""), :author_id => 1, :created_on => 2.days.ago)
178 185 Attachment.create!(:file => uploaded_test_file("testfile.txt", ""), :author_id => 1, :created_on => 2.days.ago)
179 186 Attachment.create!(:file => uploaded_test_file("testfile.txt", ""), :author_id => 1)
180 187
181 188 assert_difference 'Attachment.count', -2 do
182 189 Attachment.prune
183 190 end
184 191 end
185 192
186 193 def test_move_from_root_to_target_directory_should_move_root_files
187 194 a = Attachment.find(20)
188 195 assert a.disk_directory.blank?
189 196 # Create a real file for this fixture
190 197 File.open(a.diskfile, "w") do |f|
191 198 f.write "test file at the root of files directory"
192 199 end
193 200 assert a.readable?
194 201 Attachment.move_from_root_to_target_directory
195 202
196 203 a.reload
197 204 assert_equal '2012/05', a.disk_directory
198 205 assert a.readable?
199 206 end
200 207
201 208 test "Attachmnet.attach_files should attach the file" do
202 209 issue = Issue.first
203 210 assert_difference 'Attachment.count' do
204 211 Attachment.attach_files(issue,
205 212 '1' => {
206 213 'file' => uploaded_test_file('testfile.txt', 'text/plain'),
207 214 'description' => 'test'
208 215 })
209 216 end
210 217
211 218 attachment = Attachment.first(:order => 'id DESC')
212 219 assert_equal issue, attachment.container
213 220 assert_equal 'testfile.txt', attachment.filename
214 221 assert_equal 59, attachment.filesize
215 222 assert_equal 'test', attachment.description
216 223 assert_equal 'text/plain', attachment.content_type
217 224 assert File.exists?(attachment.diskfile)
218 225 assert_equal 59, File.size(attachment.diskfile)
219 226 end
220 227
221 228 test "Attachmnet.attach_files should add unsaved files to the object as unsaved attachments" do
222 229 # Max size of 0 to force Attachment creation failures
223 230 with_settings(:attachment_max_size => 0) do
224 231 @project = Project.find(1)
225 232 response = Attachment.attach_files(@project, {
226 233 '1' => {'file' => mock_file, 'description' => 'test'},
227 234 '2' => {'file' => mock_file, 'description' => 'test'}
228 235 })
229 236
230 237 assert response[:unsaved].present?
231 238 assert_equal 2, response[:unsaved].length
232 239 assert response[:unsaved].first.new_record?
233 240 assert response[:unsaved].second.new_record?
234 241 assert_equal response[:unsaved], @project.unsaved_attachments
235 242 end
236 243 end
237 244
238 245 def test_latest_attach
239 246 set_fixtures_attachments_directory
240 247 a1 = Attachment.find(16)
241 248 assert_equal "testfile.png", a1.filename
242 249 assert a1.readable?
243 250 assert (! a1.visible?(User.anonymous))
244 251 assert a1.visible?(User.find(2))
245 252 a2 = Attachment.find(17)
246 253 assert_equal "testfile.PNG", a2.filename
247 254 assert a2.readable?
248 255 assert (! a2.visible?(User.anonymous))
249 256 assert a2.visible?(User.find(2))
250 257 assert a1.created_on < a2.created_on
251 258
252 259 la1 = Attachment.latest_attach([a1, a2], "testfile.png")
253 260 assert_equal 17, la1.id
254 261 la2 = Attachment.latest_attach([a1, a2], "Testfile.PNG")
255 262 assert_equal 17, la2.id
256 263
257 264 set_tmp_attachments_directory
258 265 end
259 266
260 267 def test_thumbnailable_should_be_true_for_images
261 268 assert_equal true, Attachment.new(:filename => 'test.jpg').thumbnailable?
262 269 end
263 270
264 271 def test_thumbnailable_should_be_true_for_non_images
265 272 assert_equal false, Attachment.new(:filename => 'test.txt').thumbnailable?
266 273 end
267 274
268 275 if convert_installed?
269 276 def test_thumbnail_should_generate_the_thumbnail
270 277 set_fixtures_attachments_directory
271 278 attachment = Attachment.find(16)
272 279 Attachment.clear_thumbnails
273 280
274 281 assert_difference "Dir.glob(File.join(Attachment.thumbnails_storage_path, '*.thumb')).size" do
275 282 thumbnail = attachment.thumbnail
276 283 assert_equal "16_8e0294de2441577c529f170b6fb8f638_100.thumb", File.basename(thumbnail)
277 284 assert File.exists?(thumbnail)
278 285 end
279 286 end
280 287 else
281 288 puts '(ImageMagick convert not available)'
282 289 end
283 290 end
General Comments 0
You need to be logged in to leave comments. Login now