##// END OF EJS Templates
Adds a rake test that removes old unattached uploads (#10253)....
Jean-Philippe Lang -
r8773:b455ac2a27ae
parent child
Show More
@@ -0,0 +1,26
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 desc 'Removes uploaded files left unattached after one day.'
19
20 namespace :redmine do
21 namespace :attachments do
22 task :prune => :environment do
23 Attachment.prune
24 end
25 end
26 end
@@ -1,232 +1,232
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require "digest/md5"
18 require "digest/md5"
19
19
20 class Attachment < ActiveRecord::Base
20 class Attachment < ActiveRecord::Base
21 belongs_to :container, :polymorphic => true
21 belongs_to :container, :polymorphic => true
22 belongs_to :author, :class_name => "User", :foreign_key => "author_id"
22 belongs_to :author, :class_name => "User", :foreign_key => "author_id"
23
23
24 validates_presence_of :filename, :author
24 validates_presence_of :filename, :author
25 validates_length_of :filename, :maximum => 255
25 validates_length_of :filename, :maximum => 255
26 validates_length_of :disk_filename, :maximum => 255
26 validates_length_of :disk_filename, :maximum => 255
27 validate :validate_max_file_size
27 validate :validate_max_file_size
28
28
29 acts_as_event :title => :filename,
29 acts_as_event :title => :filename,
30 :url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id, :filename => o.filename}}
30 :url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id, :filename => o.filename}}
31
31
32 acts_as_activity_provider :type => 'files',
32 acts_as_activity_provider :type => 'files',
33 :permission => :view_files,
33 :permission => :view_files,
34 :author_key => :author_id,
34 :author_key => :author_id,
35 :find_options => {:select => "#{Attachment.table_name}.*",
35 :find_options => {:select => "#{Attachment.table_name}.*",
36 :joins => "LEFT JOIN #{Version.table_name} ON #{Attachment.table_name}.container_type='Version' AND #{Version.table_name}.id = #{Attachment.table_name}.container_id " +
36 :joins => "LEFT JOIN #{Version.table_name} ON #{Attachment.table_name}.container_type='Version' AND #{Version.table_name}.id = #{Attachment.table_name}.container_id " +
37 "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 )"}
37 "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 )"}
38
38
39 acts_as_activity_provider :type => 'documents',
39 acts_as_activity_provider :type => 'documents',
40 :permission => :view_documents,
40 :permission => :view_documents,
41 :author_key => :author_id,
41 :author_key => :author_id,
42 :find_options => {:select => "#{Attachment.table_name}.*",
42 :find_options => {:select => "#{Attachment.table_name}.*",
43 :joins => "LEFT JOIN #{Document.table_name} ON #{Attachment.table_name}.container_type='Document' AND #{Document.table_name}.id = #{Attachment.table_name}.container_id " +
43 :joins => "LEFT JOIN #{Document.table_name} ON #{Attachment.table_name}.container_type='Document' AND #{Document.table_name}.id = #{Attachment.table_name}.container_id " +
44 "LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"}
44 "LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"}
45
45
46 cattr_accessor :storage_path
46 cattr_accessor :storage_path
47 @@storage_path = Redmine::Configuration['attachments_storage_path'] || "#{Rails.root}/files"
47 @@storage_path = Redmine::Configuration['attachments_storage_path'] || "#{Rails.root}/files"
48
48
49 before_save :files_to_final_location
49 before_save :files_to_final_location
50 after_destroy :delete_from_disk
50 after_destroy :delete_from_disk
51
51
52 def container_with_blank_type_check
52 def container_with_blank_type_check
53 if container_type.blank?
53 if container_type.blank?
54 nil
54 nil
55 else
55 else
56 container_without_blank_type_check
56 container_without_blank_type_check
57 end
57 end
58 end
58 end
59 alias_method_chain :container, :blank_type_check unless method_defined?(:container_without_blank_type_check)
59 alias_method_chain :container, :blank_type_check unless method_defined?(:container_without_blank_type_check)
60
60
61 # Returns an unsaved copy of the attachment
61 # Returns an unsaved copy of the attachment
62 def copy(attributes=nil)
62 def copy(attributes=nil)
63 copy = self.class.new
63 copy = self.class.new
64 copy.attributes = self.attributes.dup.except("id", "downloads")
64 copy.attributes = self.attributes.dup.except("id", "downloads")
65 copy.attributes = attributes if attributes
65 copy.attributes = attributes if attributes
66 copy
66 copy
67 end
67 end
68
68
69 def validate_max_file_size
69 def validate_max_file_size
70 if @temp_file && self.filesize > Setting.attachment_max_size.to_i.kilobytes
70 if @temp_file && self.filesize > Setting.attachment_max_size.to_i.kilobytes
71 errors.add(:base, :too_long, :count => Setting.attachment_max_size.to_i.kilobytes)
71 errors.add(:base, :too_long, :count => Setting.attachment_max_size.to_i.kilobytes)
72 end
72 end
73 end
73 end
74
74
75 def file=(incoming_file)
75 def file=(incoming_file)
76 unless incoming_file.nil?
76 unless incoming_file.nil?
77 @temp_file = incoming_file
77 @temp_file = incoming_file
78 if @temp_file.size > 0
78 if @temp_file.size > 0
79 self.filename = sanitize_filename(@temp_file.original_filename)
79 self.filename = sanitize_filename(@temp_file.original_filename)
80 self.disk_filename = Attachment.disk_filename(filename)
80 self.disk_filename = Attachment.disk_filename(filename)
81 self.content_type = @temp_file.content_type.to_s.chomp
81 self.content_type = @temp_file.content_type.to_s.chomp
82 if content_type.blank?
82 if content_type.blank?
83 self.content_type = Redmine::MimeType.of(filename)
83 self.content_type = Redmine::MimeType.of(filename)
84 end
84 end
85 self.filesize = @temp_file.size
85 self.filesize = @temp_file.size
86 end
86 end
87 end
87 end
88 end
88 end
89
89
90 def file
90 def file
91 nil
91 nil
92 end
92 end
93
93
94 # Copies the temporary file to its final location
94 # Copies the temporary file to its final location
95 # and computes its MD5 hash
95 # and computes its MD5 hash
96 def files_to_final_location
96 def files_to_final_location
97 if @temp_file && (@temp_file.size > 0)
97 if @temp_file && (@temp_file.size > 0)
98 logger.info("Saving attachment '#{self.diskfile}' (#{@temp_file.size} bytes)")
98 logger.info("Saving attachment '#{self.diskfile}' (#{@temp_file.size} bytes)")
99 md5 = Digest::MD5.new
99 md5 = Digest::MD5.new
100 File.open(diskfile, "wb") do |f|
100 File.open(diskfile, "wb") do |f|
101 buffer = ""
101 buffer = ""
102 while (buffer = @temp_file.read(8192))
102 while (buffer = @temp_file.read(8192))
103 f.write(buffer)
103 f.write(buffer)
104 md5.update(buffer)
104 md5.update(buffer)
105 end
105 end
106 end
106 end
107 self.digest = md5.hexdigest
107 self.digest = md5.hexdigest
108 end
108 end
109 @temp_file = nil
109 @temp_file = nil
110 # Don't save the content type if it's longer than the authorized length
110 # Don't save the content type if it's longer than the authorized length
111 if self.content_type && self.content_type.length > 255
111 if self.content_type && self.content_type.length > 255
112 self.content_type = nil
112 self.content_type = nil
113 end
113 end
114 end
114 end
115
115
116 # Deletes the file from the file system if it's not referenced by other attachments
116 # Deletes the file from the file system if it's not referenced by other attachments
117 def delete_from_disk
117 def delete_from_disk
118 if Attachment.first(:conditions => ["disk_filename = ? AND id <> ?", disk_filename, id]).nil?
118 if Attachment.first(:conditions => ["disk_filename = ? AND id <> ?", disk_filename, id]).nil?
119 delete_from_disk!
119 delete_from_disk!
120 end
120 end
121 end
121 end
122
122
123 # Returns file's location on disk
123 # Returns file's location on disk
124 def diskfile
124 def diskfile
125 "#{@@storage_path}/#{self.disk_filename}"
125 "#{@@storage_path}/#{self.disk_filename}"
126 end
126 end
127
127
128 def increment_download
128 def increment_download
129 increment!(:downloads)
129 increment!(:downloads)
130 end
130 end
131
131
132 def project
132 def project
133 container.try(:project)
133 container.try(:project)
134 end
134 end
135
135
136 def visible?(user=User.current)
136 def visible?(user=User.current)
137 container && container.attachments_visible?(user)
137 container && container.attachments_visible?(user)
138 end
138 end
139
139
140 def deletable?(user=User.current)
140 def deletable?(user=User.current)
141 container && container.attachments_deletable?(user)
141 container && container.attachments_deletable?(user)
142 end
142 end
143
143
144 def image?
144 def image?
145 self.filename =~ /\.(bmp|gif|jpg|jpe|jpeg|png)$/i
145 self.filename =~ /\.(bmp|gif|jpg|jpe|jpeg|png)$/i
146 end
146 end
147
147
148 def is_text?
148 def is_text?
149 Redmine::MimeType.is_type?('text', filename)
149 Redmine::MimeType.is_type?('text', filename)
150 end
150 end
151
151
152 def is_diff?
152 def is_diff?
153 self.filename =~ /\.(patch|diff)$/i
153 self.filename =~ /\.(patch|diff)$/i
154 end
154 end
155
155
156 # Returns true if the file is readable
156 # Returns true if the file is readable
157 def readable?
157 def readable?
158 File.readable?(diskfile)
158 File.readable?(diskfile)
159 end
159 end
160
160
161 # Returns the attachment token
161 # Returns the attachment token
162 def token
162 def token
163 "#{id}.#{digest}"
163 "#{id}.#{digest}"
164 end
164 end
165
165
166 # Finds an attachment that matches the given token and that has no container
166 # Finds an attachment that matches the given token and that has no container
167 def self.find_by_token(token)
167 def self.find_by_token(token)
168 if token.to_s =~ /^(\d+)\.([0-9a-f]+)$/
168 if token.to_s =~ /^(\d+)\.([0-9a-f]+)$/
169 attachment_id, attachment_digest = $1, $2
169 attachment_id, attachment_digest = $1, $2
170 attachment = Attachment.first(:conditions => {:id => attachment_id, :digest => attachment_digest})
170 attachment = Attachment.first(:conditions => {:id => attachment_id, :digest => attachment_digest})
171 if attachment && attachment.container.nil?
171 if attachment && attachment.container.nil?
172 attachment
172 attachment
173 end
173 end
174 end
174 end
175 end
175 end
176
176
177 # Bulk attaches a set of files to an object
177 # Bulk attaches a set of files to an object
178 #
178 #
179 # Returns a Hash of the results:
179 # Returns a Hash of the results:
180 # :files => array of the attached files
180 # :files => array of the attached files
181 # :unsaved => array of the files that could not be attached
181 # :unsaved => array of the files that could not be attached
182 def self.attach_files(obj, attachments)
182 def self.attach_files(obj, attachments)
183 result = obj.save_attachments(attachments, User.current)
183 result = obj.save_attachments(attachments, User.current)
184 obj.attach_saved_attachments
184 obj.attach_saved_attachments
185 result
185 result
186 end
186 end
187
187
188 def self.latest_attach(attachments, filename)
188 def self.latest_attach(attachments, filename)
189 attachments.sort_by(&:created_on).reverse.detect {
189 attachments.sort_by(&:created_on).reverse.detect {
190 |att| att.filename.downcase == filename.downcase
190 |att| att.filename.downcase == filename.downcase
191 }
191 }
192 end
192 end
193
193
194 def self.prune(age=1.day)
194 def self.prune(age=1.day)
195 attachments = Attachment.all(:conditions => ["created_on < ? AND (container_type IS NULL OR container_type = ''", Time.now - age])
195 attachments = Attachment.all(:conditions => ["created_on < ? AND (container_type IS NULL OR container_type = '')", Time.now - age])
196 attachments.each(&:destroy)
196 attachments.each(&:destroy)
197 end
197 end
198
198
199 private
199 private
200
200
201 # Physically deletes the file from the file system
201 # Physically deletes the file from the file system
202 def delete_from_disk!
202 def delete_from_disk!
203 if disk_filename.present? && File.exist?(diskfile)
203 if disk_filename.present? && File.exist?(diskfile)
204 File.delete(diskfile)
204 File.delete(diskfile)
205 end
205 end
206 end
206 end
207
207
208 def sanitize_filename(value)
208 def sanitize_filename(value)
209 # get only the filename, not the whole path
209 # get only the filename, not the whole path
210 just_filename = value.gsub(/^.*(\\|\/)/, '')
210 just_filename = value.gsub(/^.*(\\|\/)/, '')
211
211
212 # Finally, replace invalid characters with underscore
212 # Finally, replace invalid characters with underscore
213 @filename = just_filename.gsub(/[\/\?\%\*\:\|\"\'<>]+/, '_')
213 @filename = just_filename.gsub(/[\/\?\%\*\:\|\"\'<>]+/, '_')
214 end
214 end
215
215
216 # Returns an ASCII or hashed filename
216 # Returns an ASCII or hashed filename
217 def self.disk_filename(filename)
217 def self.disk_filename(filename)
218 timestamp = DateTime.now.strftime("%y%m%d%H%M%S")
218 timestamp = DateTime.now.strftime("%y%m%d%H%M%S")
219 ascii = ''
219 ascii = ''
220 if filename =~ %r{^[a-zA-Z0-9_\.\-]*$}
220 if filename =~ %r{^[a-zA-Z0-9_\.\-]*$}
221 ascii = filename
221 ascii = filename
222 else
222 else
223 ascii = Digest::MD5.hexdigest(filename)
223 ascii = Digest::MD5.hexdigest(filename)
224 # keep the extension if any
224 # keep the extension if any
225 ascii << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$}
225 ascii << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$}
226 end
226 end
227 while File.exist?(File.join(@@storage_path, "#{timestamp}_#{ascii}"))
227 while File.exist?(File.join(@@storage_path, "#{timestamp}_#{ascii}"))
228 timestamp.succ!
228 timestamp.succ!
229 end
229 end
230 "#{timestamp}_#{ascii}"
230 "#{timestamp}_#{ascii}"
231 end
231 end
232 end
232 end
@@ -1,207 +1,217
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2011 Jean-Philippe Lang
4 # Copyright (C) 2006-2011 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 require File.expand_path('../../test_helper', __FILE__)
20 require File.expand_path('../../test_helper', __FILE__)
21
21
22 class AttachmentTest < ActiveSupport::TestCase
22 class AttachmentTest < ActiveSupport::TestCase
23 fixtures :users, :projects, :roles, :members, :member_roles,
23 fixtures :users, :projects, :roles, :members, :member_roles,
24 :enabled_modules, :issues, :trackers, :attachments
24 :enabled_modules, :issues, :trackers, :attachments
25
25
26 class MockFile
26 class MockFile
27 attr_reader :original_filename, :content_type, :content, :size
27 attr_reader :original_filename, :content_type, :content, :size
28
28
29 def initialize(attributes)
29 def initialize(attributes)
30 @original_filename = attributes[:original_filename]
30 @original_filename = attributes[:original_filename]
31 @content_type = attributes[:content_type]
31 @content_type = attributes[:content_type]
32 @content = attributes[:content] || "Content"
32 @content = attributes[:content] || "Content"
33 @size = content.size
33 @size = content.size
34 end
34 end
35 end
35 end
36
36
37 def setup
37 def setup
38 set_tmp_attachments_directory
38 set_tmp_attachments_directory
39 end
39 end
40
40
41 def test_container_for_new_attachment_should_be_nil
41 def test_container_for_new_attachment_should_be_nil
42 assert_nil Attachment.new.container
42 assert_nil Attachment.new.container
43 end
43 end
44
44
45 def test_create
45 def test_create
46 a = Attachment.new(:container => Issue.find(1),
46 a = Attachment.new(:container => Issue.find(1),
47 :file => uploaded_test_file("testfile.txt", "text/plain"),
47 :file => uploaded_test_file("testfile.txt", "text/plain"),
48 :author => User.find(1))
48 :author => User.find(1))
49 assert a.save
49 assert a.save
50 assert_equal 'testfile.txt', a.filename
50 assert_equal 'testfile.txt', a.filename
51 assert_equal 59, a.filesize
51 assert_equal 59, a.filesize
52 assert_equal 'text/plain', a.content_type
52 assert_equal 'text/plain', a.content_type
53 assert_equal 0, a.downloads
53 assert_equal 0, a.downloads
54 assert_equal '1478adae0d4eb06d35897518540e25d6', a.digest
54 assert_equal '1478adae0d4eb06d35897518540e25d6', a.digest
55 assert File.exist?(a.diskfile)
55 assert File.exist?(a.diskfile)
56 assert_equal 59, File.size(a.diskfile)
56 assert_equal 59, File.size(a.diskfile)
57 end
57 end
58
58
59 def test_size_should_be_validated_for_new_file
59 def test_size_should_be_validated_for_new_file
60 with_settings :attachment_max_size => 0 do
60 with_settings :attachment_max_size => 0 do
61 a = Attachment.new(:container => Issue.find(1),
61 a = Attachment.new(:container => Issue.find(1),
62 :file => uploaded_test_file("testfile.txt", "text/plain"),
62 :file => uploaded_test_file("testfile.txt", "text/plain"),
63 :author => User.find(1))
63 :author => User.find(1))
64 assert !a.save
64 assert !a.save
65 end
65 end
66 end
66 end
67
67
68 def test_size_should_not_be_validated_when_copying
68 def test_size_should_not_be_validated_when_copying
69 a = Attachment.create!(:container => Issue.find(1),
69 a = Attachment.create!(:container => Issue.find(1),
70 :file => uploaded_test_file("testfile.txt", "text/plain"),
70 :file => uploaded_test_file("testfile.txt", "text/plain"),
71 :author => User.find(1))
71 :author => User.find(1))
72 with_settings :attachment_max_size => 0 do
72 with_settings :attachment_max_size => 0 do
73 copy = a.copy
73 copy = a.copy
74 assert copy.save
74 assert copy.save
75 end
75 end
76 end
76 end
77
77
78 def test_destroy
78 def test_destroy
79 a = Attachment.new(:container => Issue.find(1),
79 a = Attachment.new(:container => Issue.find(1),
80 :file => uploaded_test_file("testfile.txt", "text/plain"),
80 :file => uploaded_test_file("testfile.txt", "text/plain"),
81 :author => User.find(1))
81 :author => User.find(1))
82 assert a.save
82 assert a.save
83 assert_equal 'testfile.txt', a.filename
83 assert_equal 'testfile.txt', a.filename
84 assert_equal 59, a.filesize
84 assert_equal 59, a.filesize
85 assert_equal 'text/plain', a.content_type
85 assert_equal 'text/plain', a.content_type
86 assert_equal 0, a.downloads
86 assert_equal 0, a.downloads
87 assert_equal '1478adae0d4eb06d35897518540e25d6', a.digest
87 assert_equal '1478adae0d4eb06d35897518540e25d6', a.digest
88 diskfile = a.diskfile
88 diskfile = a.diskfile
89 assert File.exist?(diskfile)
89 assert File.exist?(diskfile)
90 assert_equal 59, File.size(a.diskfile)
90 assert_equal 59, File.size(a.diskfile)
91 assert a.destroy
91 assert a.destroy
92 assert !File.exist?(diskfile)
92 assert !File.exist?(diskfile)
93 end
93 end
94
94
95 def test_destroy_should_not_delete_file_referenced_by_other_attachment
95 def test_destroy_should_not_delete_file_referenced_by_other_attachment
96 a = Attachment.create!(:container => Issue.find(1),
96 a = Attachment.create!(:container => Issue.find(1),
97 :file => uploaded_test_file("testfile.txt", "text/plain"),
97 :file => uploaded_test_file("testfile.txt", "text/plain"),
98 :author => User.find(1))
98 :author => User.find(1))
99 diskfile = a.diskfile
99 diskfile = a.diskfile
100
100
101 copy = a.copy
101 copy = a.copy
102 copy.save!
102 copy.save!
103
103
104 assert File.exists?(diskfile)
104 assert File.exists?(diskfile)
105 a.destroy
105 a.destroy
106 assert File.exists?(diskfile)
106 assert File.exists?(diskfile)
107 copy.destroy
107 copy.destroy
108 assert !File.exists?(diskfile)
108 assert !File.exists?(diskfile)
109 end
109 end
110
110
111 def test_create_should_auto_assign_content_type
111 def test_create_should_auto_assign_content_type
112 a = Attachment.new(:container => Issue.find(1),
112 a = Attachment.new(:container => Issue.find(1),
113 :file => uploaded_test_file("testfile.txt", ""),
113 :file => uploaded_test_file("testfile.txt", ""),
114 :author => User.find(1))
114 :author => User.find(1))
115 assert a.save
115 assert a.save
116 assert_equal 'text/plain', a.content_type
116 assert_equal 'text/plain', a.content_type
117 end
117 end
118
118
119 def test_identical_attachments_at_the_same_time_should_not_overwrite
119 def test_identical_attachments_at_the_same_time_should_not_overwrite
120 a1 = Attachment.create!(:container => Issue.find(1),
120 a1 = Attachment.create!(:container => Issue.find(1),
121 :file => uploaded_test_file("testfile.txt", ""),
121 :file => uploaded_test_file("testfile.txt", ""),
122 :author => User.find(1))
122 :author => User.find(1))
123 a2 = Attachment.create!(:container => Issue.find(1),
123 a2 = Attachment.create!(:container => Issue.find(1),
124 :file => uploaded_test_file("testfile.txt", ""),
124 :file => uploaded_test_file("testfile.txt", ""),
125 :author => User.find(1))
125 :author => User.find(1))
126 assert a1.disk_filename != a2.disk_filename
126 assert a1.disk_filename != a2.disk_filename
127 end
127 end
128
128
129 def test_filename_should_be_basenamed
129 def test_filename_should_be_basenamed
130 a = Attachment.new(:file => MockFile.new(:original_filename => "path/to/the/file"))
130 a = Attachment.new(:file => MockFile.new(:original_filename => "path/to/the/file"))
131 assert_equal 'file', a.filename
131 assert_equal 'file', a.filename
132 end
132 end
133
133
134 def test_filename_should_be_sanitized
134 def test_filename_should_be_sanitized
135 a = Attachment.new(:file => MockFile.new(:original_filename => "valid:[] invalid:?%*|\"'<>chars"))
135 a = Attachment.new(:file => MockFile.new(:original_filename => "valid:[] invalid:?%*|\"'<>chars"))
136 assert_equal 'valid_[] invalid_chars', a.filename
136 assert_equal 'valid_[] invalid_chars', a.filename
137 end
137 end
138
138
139 def test_diskfilename
139 def test_diskfilename
140 assert Attachment.disk_filename("test_file.txt") =~ /^\d{12}_test_file.txt$/
140 assert Attachment.disk_filename("test_file.txt") =~ /^\d{12}_test_file.txt$/
141 assert_equal 'test_file.txt', Attachment.disk_filename("test_file.txt")[13..-1]
141 assert_equal 'test_file.txt', Attachment.disk_filename("test_file.txt")[13..-1]
142 assert_equal '770c509475505f37c2b8fb6030434d6b.txt', Attachment.disk_filename("test_accentuΓ©.txt")[13..-1]
142 assert_equal '770c509475505f37c2b8fb6030434d6b.txt', Attachment.disk_filename("test_accentuΓ©.txt")[13..-1]
143 assert_equal 'f8139524ebb8f32e51976982cd20a85d', Attachment.disk_filename("test_accentuΓ©")[13..-1]
143 assert_equal 'f8139524ebb8f32e51976982cd20a85d', Attachment.disk_filename("test_accentuΓ©")[13..-1]
144 assert_equal 'cbb5b0f30978ba03731d61f9f6d10011', Attachment.disk_filename("test_accentuΓ©.Γ§a")[13..-1]
144 assert_equal 'cbb5b0f30978ba03731d61f9f6d10011', Attachment.disk_filename("test_accentuΓ©.Γ§a")[13..-1]
145 end
145 end
146
146
147 def test_prune_should_destroy_old_unattached_attachments
148 Attachment.create!(:file => uploaded_test_file("testfile.txt", ""), :author_id => 1, :created_on => 2.days.ago)
149 Attachment.create!(:file => uploaded_test_file("testfile.txt", ""), :author_id => 1, :created_on => 2.days.ago)
150 Attachment.create!(:file => uploaded_test_file("testfile.txt", ""), :author_id => 1)
151
152 assert_difference 'Attachment.count', -2 do
153 Attachment.prune
154 end
155 end
156
147 context "Attachmnet.attach_files" do
157 context "Attachmnet.attach_files" do
148 should "attach the file" do
158 should "attach the file" do
149 issue = Issue.first
159 issue = Issue.first
150 assert_difference 'Attachment.count' do
160 assert_difference 'Attachment.count' do
151 Attachment.attach_files(issue,
161 Attachment.attach_files(issue,
152 '1' => {
162 '1' => {
153 'file' => uploaded_test_file('testfile.txt', 'text/plain'),
163 'file' => uploaded_test_file('testfile.txt', 'text/plain'),
154 'description' => 'test'
164 'description' => 'test'
155 })
165 })
156 end
166 end
157
167
158 attachment = Attachment.first(:order => 'id DESC')
168 attachment = Attachment.first(:order => 'id DESC')
159 assert_equal issue, attachment.container
169 assert_equal issue, attachment.container
160 assert_equal 'testfile.txt', attachment.filename
170 assert_equal 'testfile.txt', attachment.filename
161 assert_equal 59, attachment.filesize
171 assert_equal 59, attachment.filesize
162 assert_equal 'test', attachment.description
172 assert_equal 'test', attachment.description
163 assert_equal 'text/plain', attachment.content_type
173 assert_equal 'text/plain', attachment.content_type
164 assert File.exists?(attachment.diskfile)
174 assert File.exists?(attachment.diskfile)
165 assert_equal 59, File.size(attachment.diskfile)
175 assert_equal 59, File.size(attachment.diskfile)
166 end
176 end
167
177
168 should "add unsaved files to the object as unsaved attachments" do
178 should "add unsaved files to the object as unsaved attachments" do
169 # Max size of 0 to force Attachment creation failures
179 # Max size of 0 to force Attachment creation failures
170 with_settings(:attachment_max_size => 0) do
180 with_settings(:attachment_max_size => 0) do
171 @project = Project.generate!
181 @project = Project.generate!
172 response = Attachment.attach_files(@project, {
182 response = Attachment.attach_files(@project, {
173 '1' => {'file' => mock_file, 'description' => 'test'},
183 '1' => {'file' => mock_file, 'description' => 'test'},
174 '2' => {'file' => mock_file, 'description' => 'test'}
184 '2' => {'file' => mock_file, 'description' => 'test'}
175 })
185 })
176
186
177 assert response[:unsaved].present?
187 assert response[:unsaved].present?
178 assert_equal 2, response[:unsaved].length
188 assert_equal 2, response[:unsaved].length
179 assert response[:unsaved].first.new_record?
189 assert response[:unsaved].first.new_record?
180 assert response[:unsaved].second.new_record?
190 assert response[:unsaved].second.new_record?
181 assert_equal response[:unsaved], @project.unsaved_attachments
191 assert_equal response[:unsaved], @project.unsaved_attachments
182 end
192 end
183 end
193 end
184 end
194 end
185
195
186 def test_latest_attach
196 def test_latest_attach
187 set_fixtures_attachments_directory
197 set_fixtures_attachments_directory
188 a1 = Attachment.find(16)
198 a1 = Attachment.find(16)
189 assert_equal "testfile.png", a1.filename
199 assert_equal "testfile.png", a1.filename
190 assert a1.readable?
200 assert a1.readable?
191 assert (! a1.visible?(User.anonymous))
201 assert (! a1.visible?(User.anonymous))
192 assert a1.visible?(User.find(2))
202 assert a1.visible?(User.find(2))
193 a2 = Attachment.find(17)
203 a2 = Attachment.find(17)
194 assert_equal "testfile.PNG", a2.filename
204 assert_equal "testfile.PNG", a2.filename
195 assert a2.readable?
205 assert a2.readable?
196 assert (! a2.visible?(User.anonymous))
206 assert (! a2.visible?(User.anonymous))
197 assert a2.visible?(User.find(2))
207 assert a2.visible?(User.find(2))
198 assert a1.created_on < a2.created_on
208 assert a1.created_on < a2.created_on
199
209
200 la1 = Attachment.latest_attach([a1, a2], "testfile.png")
210 la1 = Attachment.latest_attach([a1, a2], "testfile.png")
201 assert_equal 17, la1.id
211 assert_equal 17, la1.id
202 la2 = Attachment.latest_attach([a1, a2], "Testfile.PNG")
212 la2 = Attachment.latest_attach([a1, a2], "Testfile.PNG")
203 assert_equal 17, la2.id
213 assert_equal 17, la2.id
204
214
205 set_tmp_attachments_directory
215 set_tmp_attachments_directory
206 end
216 end
207 end
217 end
General Comments 0
You need to be logged in to leave comments. Login now