##// END OF EJS Templates
Limit the characters stripped by Attachment#sanitize_filename (#4324)....
Jean-Philippe Lang -
r7797:902b3078d549
parent child
Show More
@@ -1,203 +1,201
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 :container, :filename, :author
24 validates_presence_of :container, :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 validate_max_file_size
52 def validate_max_file_size
53 if self.filesize > Setting.attachment_max_size.to_i.kilobytes
53 if self.filesize > Setting.attachment_max_size.to_i.kilobytes
54 errors.add(:base, :too_long, :count => Setting.attachment_max_size.to_i.kilobytes)
54 errors.add(:base, :too_long, :count => Setting.attachment_max_size.to_i.kilobytes)
55 end
55 end
56 end
56 end
57
57
58 def file=(incoming_file)
58 def file=(incoming_file)
59 unless incoming_file.nil?
59 unless incoming_file.nil?
60 @temp_file = incoming_file
60 @temp_file = incoming_file
61 if @temp_file.size > 0
61 if @temp_file.size > 0
62 self.filename = sanitize_filename(@temp_file.original_filename)
62 self.filename = sanitize_filename(@temp_file.original_filename)
63 self.disk_filename = Attachment.disk_filename(filename)
63 self.disk_filename = Attachment.disk_filename(filename)
64 self.content_type = @temp_file.content_type.to_s.chomp
64 self.content_type = @temp_file.content_type.to_s.chomp
65 if content_type.blank?
65 if content_type.blank?
66 self.content_type = Redmine::MimeType.of(filename)
66 self.content_type = Redmine::MimeType.of(filename)
67 end
67 end
68 self.filesize = @temp_file.size
68 self.filesize = @temp_file.size
69 end
69 end
70 end
70 end
71 end
71 end
72
72
73 def file
73 def file
74 nil
74 nil
75 end
75 end
76
76
77 # Copies the temporary file to its final location
77 # Copies the temporary file to its final location
78 # and computes its MD5 hash
78 # and computes its MD5 hash
79 def files_to_final_location
79 def files_to_final_location
80 if @temp_file && (@temp_file.size > 0)
80 if @temp_file && (@temp_file.size > 0)
81 logger.info("Saving attachment '#{self.diskfile}' (#{@temp_file.size} bytes)")
81 logger.info("Saving attachment '#{self.diskfile}' (#{@temp_file.size} bytes)")
82 md5 = Digest::MD5.new
82 md5 = Digest::MD5.new
83 File.open(diskfile, "wb") do |f|
83 File.open(diskfile, "wb") do |f|
84 buffer = ""
84 buffer = ""
85 while (buffer = @temp_file.read(8192))
85 while (buffer = @temp_file.read(8192))
86 f.write(buffer)
86 f.write(buffer)
87 md5.update(buffer)
87 md5.update(buffer)
88 end
88 end
89 end
89 end
90 self.digest = md5.hexdigest
90 self.digest = md5.hexdigest
91 end
91 end
92 @temp_file = nil
92 @temp_file = nil
93 # Don't save the content type if it's longer than the authorized length
93 # Don't save the content type if it's longer than the authorized length
94 if self.content_type && self.content_type.length > 255
94 if self.content_type && self.content_type.length > 255
95 self.content_type = nil
95 self.content_type = nil
96 end
96 end
97 end
97 end
98
98
99 # Deletes file on the disk
99 # Deletes file on the disk
100 def delete_from_disk
100 def delete_from_disk
101 File.delete(diskfile) if !filename.blank? && File.exist?(diskfile)
101 File.delete(diskfile) if !filename.blank? && File.exist?(diskfile)
102 end
102 end
103
103
104 # Returns file's location on disk
104 # Returns file's location on disk
105 def diskfile
105 def diskfile
106 "#{@@storage_path}/#{self.disk_filename}"
106 "#{@@storage_path}/#{self.disk_filename}"
107 end
107 end
108
108
109 def increment_download
109 def increment_download
110 increment!(:downloads)
110 increment!(:downloads)
111 end
111 end
112
112
113 def project
113 def project
114 container.project
114 container.project
115 end
115 end
116
116
117 def visible?(user=User.current)
117 def visible?(user=User.current)
118 container.attachments_visible?(user)
118 container.attachments_visible?(user)
119 end
119 end
120
120
121 def deletable?(user=User.current)
121 def deletable?(user=User.current)
122 container.attachments_deletable?(user)
122 container.attachments_deletable?(user)
123 end
123 end
124
124
125 def image?
125 def image?
126 self.filename =~ /\.(bmp|gif|jpg|jpe|jpeg|png)$/i
126 self.filename =~ /\.(bmp|gif|jpg|jpe|jpeg|png)$/i
127 end
127 end
128
128
129 def is_text?
129 def is_text?
130 Redmine::MimeType.is_type?('text', filename)
130 Redmine::MimeType.is_type?('text', filename)
131 end
131 end
132
132
133 def is_diff?
133 def is_diff?
134 self.filename =~ /\.(patch|diff)$/i
134 self.filename =~ /\.(patch|diff)$/i
135 end
135 end
136
136
137 # Returns true if the file is readable
137 # Returns true if the file is readable
138 def readable?
138 def readable?
139 File.readable?(diskfile)
139 File.readable?(diskfile)
140 end
140 end
141
141
142 # Bulk attaches a set of files to an object
142 # Bulk attaches a set of files to an object
143 #
143 #
144 # Returns a Hash of the results:
144 # Returns a Hash of the results:
145 # :files => array of the attached files
145 # :files => array of the attached files
146 # :unsaved => array of the files that could not be attached
146 # :unsaved => array of the files that could not be attached
147 def self.attach_files(obj, attachments)
147 def self.attach_files(obj, attachments)
148 attached = []
148 attached = []
149 if attachments && attachments.is_a?(Hash)
149 if attachments && attachments.is_a?(Hash)
150 attachments.each_value do |attachment|
150 attachments.each_value do |attachment|
151 file = attachment['file']
151 file = attachment['file']
152 next unless file && file.size > 0
152 next unless file && file.size > 0
153 a = Attachment.create(:container => obj,
153 a = Attachment.create(:container => obj,
154 :file => file,
154 :file => file,
155 :description => attachment['description'].to_s.strip,
155 :description => attachment['description'].to_s.strip,
156 :author => User.current)
156 :author => User.current)
157 obj.attachments << a
157 obj.attachments << a
158
158
159 if a.new_record?
159 if a.new_record?
160 obj.unsaved_attachments ||= []
160 obj.unsaved_attachments ||= []
161 obj.unsaved_attachments << a
161 obj.unsaved_attachments << a
162 else
162 else
163 attached << a
163 attached << a
164 end
164 end
165 end
165 end
166 end
166 end
167 {:files => attached, :unsaved => obj.unsaved_attachments}
167 {:files => attached, :unsaved => obj.unsaved_attachments}
168 end
168 end
169
169
170 def self.latest_attach(attachments, filename)
170 def self.latest_attach(attachments, filename)
171 attachments.sort_by(&:created_on).reverse.detect {
171 attachments.sort_by(&:created_on).reverse.detect {
172 |att| att.filename.downcase == filename.downcase
172 |att| att.filename.downcase == filename.downcase
173 }
173 }
174 end
174 end
175
175
176 private
176 private
177 def sanitize_filename(value)
177 def sanitize_filename(value)
178 # get only the filename, not the whole path
178 # get only the filename, not the whole path
179 just_filename = value.gsub(/^.*(\\|\/)/, '')
179 just_filename = value.gsub(/^.*(\\|\/)/, '')
180 # NOTE: File.basename doesn't work right with Windows paths on Unix
181 # INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/'))
182
180
183 # Finally, replace all non alphanumeric, hyphens or periods with underscore
181 # Finally, replace invalid characters with underscore
184 @filename = just_filename.gsub(/[^\w\.\-]/,'_')
182 @filename = just_filename.gsub(/[\/\?\%\*\:\|\"\'<>]+/, '_')
185 end
183 end
186
184
187 # Returns an ASCII or hashed filename
185 # Returns an ASCII or hashed filename
188 def self.disk_filename(filename)
186 def self.disk_filename(filename)
189 timestamp = DateTime.now.strftime("%y%m%d%H%M%S")
187 timestamp = DateTime.now.strftime("%y%m%d%H%M%S")
190 ascii = ''
188 ascii = ''
191 if filename =~ %r{^[a-zA-Z0-9_\.\-]*$}
189 if filename =~ %r{^[a-zA-Z0-9_\.\-]*$}
192 ascii = filename
190 ascii = filename
193 else
191 else
194 ascii = Digest::MD5.hexdigest(filename)
192 ascii = Digest::MD5.hexdigest(filename)
195 # keep the extension if any
193 # keep the extension if any
196 ascii << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$}
194 ascii << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$}
197 end
195 end
198 while File.exist?(File.join(@@storage_path, "#{timestamp}_#{ascii}"))
196 while File.exist?(File.join(@@storage_path, "#{timestamp}_#{ascii}"))
199 timestamp.succ!
197 timestamp.succ!
200 end
198 end
201 "#{timestamp}_#{ascii}"
199 "#{timestamp}_#{ascii}"
202 end
200 end
203 end
201 end
@@ -1,147 +1,168
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
26 class MockFile
27 attr_reader :original_filename, :content_type, :content, :size
28
29 def initialize(attributes)
30 @original_filename = attributes[:original_filename]
31 @content_type = attributes[:content_type]
32 @content = attributes[:content] || "Content"
33 @size = content.size
34 end
35 end
25
36
26 def setup
37 def setup
27 set_tmp_attachments_directory
38 set_tmp_attachments_directory
28 end
39 end
29
40
30 def test_create
41 def test_create
31 a = Attachment.new(:container => Issue.find(1),
42 a = Attachment.new(:container => Issue.find(1),
32 :file => uploaded_test_file("testfile.txt", "text/plain"),
43 :file => uploaded_test_file("testfile.txt", "text/plain"),
33 :author => User.find(1))
44 :author => User.find(1))
34 assert a.save
45 assert a.save
35 assert_equal 'testfile.txt', a.filename
46 assert_equal 'testfile.txt', a.filename
36 assert_equal 59, a.filesize
47 assert_equal 59, a.filesize
37 assert_equal 'text/plain', a.content_type
48 assert_equal 'text/plain', a.content_type
38 assert_equal 0, a.downloads
49 assert_equal 0, a.downloads
39 assert_equal '1478adae0d4eb06d35897518540e25d6', a.digest
50 assert_equal '1478adae0d4eb06d35897518540e25d6', a.digest
40 assert File.exist?(a.diskfile)
51 assert File.exist?(a.diskfile)
41 assert_equal 59, File.size(a.diskfile)
52 assert_equal 59, File.size(a.diskfile)
42 end
53 end
43
54
44 def test_destroy
55 def test_destroy
45 a = Attachment.new(:container => Issue.find(1),
56 a = Attachment.new(:container => Issue.find(1),
46 :file => uploaded_test_file("testfile.txt", "text/plain"),
57 :file => uploaded_test_file("testfile.txt", "text/plain"),
47 :author => User.find(1))
58 :author => User.find(1))
48 assert a.save
59 assert a.save
49 assert_equal 'testfile.txt', a.filename
60 assert_equal 'testfile.txt', a.filename
50 assert_equal 59, a.filesize
61 assert_equal 59, a.filesize
51 assert_equal 'text/plain', a.content_type
62 assert_equal 'text/plain', a.content_type
52 assert_equal 0, a.downloads
63 assert_equal 0, a.downloads
53 assert_equal '1478adae0d4eb06d35897518540e25d6', a.digest
64 assert_equal '1478adae0d4eb06d35897518540e25d6', a.digest
54 diskfile = a.diskfile
65 diskfile = a.diskfile
55 assert File.exist?(diskfile)
66 assert File.exist?(diskfile)
56 assert_equal 59, File.size(a.diskfile)
67 assert_equal 59, File.size(a.diskfile)
57 assert a.destroy
68 assert a.destroy
58 assert !File.exist?(diskfile)
69 assert !File.exist?(diskfile)
59 end
70 end
60
71
61 def test_create_should_auto_assign_content_type
72 def test_create_should_auto_assign_content_type
62 a = Attachment.new(:container => Issue.find(1),
73 a = Attachment.new(:container => Issue.find(1),
63 :file => uploaded_test_file("testfile.txt", ""),
74 :file => uploaded_test_file("testfile.txt", ""),
64 :author => User.find(1))
75 :author => User.find(1))
65 assert a.save
76 assert a.save
66 assert_equal 'text/plain', a.content_type
77 assert_equal 'text/plain', a.content_type
67 end
78 end
68
79
69 def test_identical_attachments_at_the_same_time_should_not_overwrite
80 def test_identical_attachments_at_the_same_time_should_not_overwrite
70 a1 = Attachment.create!(:container => Issue.find(1),
81 a1 = Attachment.create!(:container => Issue.find(1),
71 :file => uploaded_test_file("testfile.txt", ""),
82 :file => uploaded_test_file("testfile.txt", ""),
72 :author => User.find(1))
83 :author => User.find(1))
73 a2 = Attachment.create!(:container => Issue.find(1),
84 a2 = Attachment.create!(:container => Issue.find(1),
74 :file => uploaded_test_file("testfile.txt", ""),
85 :file => uploaded_test_file("testfile.txt", ""),
75 :author => User.find(1))
86 :author => User.find(1))
76 assert a1.disk_filename != a2.disk_filename
87 assert a1.disk_filename != a2.disk_filename
77 end
88 end
89
90 def test_filename_should_be_basenamed
91 a = Attachment.new(:file => MockFile.new(:original_filename => "path/to/the/file"))
92 assert_equal 'file', a.filename
93 end
94
95 def test_filename_should_be_sanitized
96 a = Attachment.new(:file => MockFile.new(:original_filename => "valid:[] invalid:?%*|\"'<>chars"))
97 assert_equal 'valid_[] invalid_chars', a.filename
98 end
78
99
79 def test_diskfilename
100 def test_diskfilename
80 assert Attachment.disk_filename("test_file.txt") =~ /^\d{12}_test_file.txt$/
101 assert Attachment.disk_filename("test_file.txt") =~ /^\d{12}_test_file.txt$/
81 assert_equal 'test_file.txt', Attachment.disk_filename("test_file.txt")[13..-1]
102 assert_equal 'test_file.txt', Attachment.disk_filename("test_file.txt")[13..-1]
82 assert_equal '770c509475505f37c2b8fb6030434d6b.txt', Attachment.disk_filename("test_accentuΓ©.txt")[13..-1]
103 assert_equal '770c509475505f37c2b8fb6030434d6b.txt', Attachment.disk_filename("test_accentuΓ©.txt")[13..-1]
83 assert_equal 'f8139524ebb8f32e51976982cd20a85d', Attachment.disk_filename("test_accentuΓ©")[13..-1]
104 assert_equal 'f8139524ebb8f32e51976982cd20a85d', Attachment.disk_filename("test_accentuΓ©")[13..-1]
84 assert_equal 'cbb5b0f30978ba03731d61f9f6d10011', Attachment.disk_filename("test_accentuΓ©.Γ§a")[13..-1]
105 assert_equal 'cbb5b0f30978ba03731d61f9f6d10011', Attachment.disk_filename("test_accentuΓ©.Γ§a")[13..-1]
85 end
106 end
86
107
87 context "Attachmnet.attach_files" do
108 context "Attachmnet.attach_files" do
88 should "attach the file" do
109 should "attach the file" do
89 issue = Issue.first
110 issue = Issue.first
90 assert_difference 'Attachment.count' do
111 assert_difference 'Attachment.count' do
91 Attachment.attach_files(issue,
112 Attachment.attach_files(issue,
92 '1' => {
113 '1' => {
93 'file' => uploaded_test_file('testfile.txt', 'text/plain'),
114 'file' => uploaded_test_file('testfile.txt', 'text/plain'),
94 'description' => 'test'
115 'description' => 'test'
95 })
116 })
96 end
117 end
97
118
98 attachment = Attachment.first(:order => 'id DESC')
119 attachment = Attachment.first(:order => 'id DESC')
99 assert_equal issue, attachment.container
120 assert_equal issue, attachment.container
100 assert_equal 'testfile.txt', attachment.filename
121 assert_equal 'testfile.txt', attachment.filename
101 assert_equal 59, attachment.filesize
122 assert_equal 59, attachment.filesize
102 assert_equal 'test', attachment.description
123 assert_equal 'test', attachment.description
103 assert_equal 'text/plain', attachment.content_type
124 assert_equal 'text/plain', attachment.content_type
104 assert File.exists?(attachment.diskfile)
125 assert File.exists?(attachment.diskfile)
105 assert_equal 59, File.size(attachment.diskfile)
126 assert_equal 59, File.size(attachment.diskfile)
106 end
127 end
107
128
108 should "add unsaved files to the object as unsaved attachments" do
129 should "add unsaved files to the object as unsaved attachments" do
109 # Max size of 0 to force Attachment creation failures
130 # Max size of 0 to force Attachment creation failures
110 with_settings(:attachment_max_size => 0) do
131 with_settings(:attachment_max_size => 0) do
111 @project = Project.generate!
132 @project = Project.generate!
112 response = Attachment.attach_files(@project, {
133 response = Attachment.attach_files(@project, {
113 '1' => {'file' => mock_file, 'description' => 'test'},
134 '1' => {'file' => mock_file, 'description' => 'test'},
114 '2' => {'file' => mock_file, 'description' => 'test'}
135 '2' => {'file' => mock_file, 'description' => 'test'}
115 })
136 })
116
137
117 assert response[:unsaved].present?
138 assert response[:unsaved].present?
118 assert_equal 2, response[:unsaved].length
139 assert_equal 2, response[:unsaved].length
119 assert response[:unsaved].first.new_record?
140 assert response[:unsaved].first.new_record?
120 assert response[:unsaved].second.new_record?
141 assert response[:unsaved].second.new_record?
121 assert_equal response[:unsaved], @project.unsaved_attachments
142 assert_equal response[:unsaved], @project.unsaved_attachments
122 end
143 end
123 end
144 end
124 end
145 end
125
146
126 def test_latest_attach
147 def test_latest_attach
127 Attachment.storage_path = "#{Rails.root}/test/fixtures/files"
148 Attachment.storage_path = "#{Rails.root}/test/fixtures/files"
128 a1 = Attachment.find(16)
149 a1 = Attachment.find(16)
129 assert_equal "testfile.png", a1.filename
150 assert_equal "testfile.png", a1.filename
130 assert a1.readable?
151 assert a1.readable?
131 assert (! a1.visible?(User.anonymous))
152 assert (! a1.visible?(User.anonymous))
132 assert a1.visible?(User.find(2))
153 assert a1.visible?(User.find(2))
133 a2 = Attachment.find(17)
154 a2 = Attachment.find(17)
134 assert_equal "testfile.PNG", a2.filename
155 assert_equal "testfile.PNG", a2.filename
135 assert a2.readable?
156 assert a2.readable?
136 assert (! a2.visible?(User.anonymous))
157 assert (! a2.visible?(User.anonymous))
137 assert a2.visible?(User.find(2))
158 assert a2.visible?(User.find(2))
138 assert a1.created_on < a2.created_on
159 assert a1.created_on < a2.created_on
139
160
140 la1 = Attachment.latest_attach([a1, a2], "testfile.png")
161 la1 = Attachment.latest_attach([a1, a2], "testfile.png")
141 assert_equal 17, la1.id
162 assert_equal 17, la1.id
142 la2 = Attachment.latest_attach([a1, a2], "Testfile.PNG")
163 la2 = Attachment.latest_attach([a1, a2], "Testfile.PNG")
143 assert_equal 17, la2.id
164 assert_equal 17, la2.id
144
165
145 set_tmp_attachments_directory
166 set_tmp_attachments_directory
146 end
167 end
147 end
168 end
General Comments 0
You need to be logged in to leave comments. Login now