##// END OF EJS Templates
Merged r13125 (#16700)....
Jean-Philippe Lang -
r12889:a6d62710cd34
parent child
Show More
@@ -1,115 +1,115
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2014 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 module Redmine
19 19 module Acts
20 20 module Attachable
21 21 def self.included(base)
22 22 base.extend ClassMethods
23 23 end
24 24
25 25 module ClassMethods
26 26 def acts_as_attachable(options = {})
27 27 cattr_accessor :attachable_options
28 28 self.attachable_options = {}
29 29 attachable_options[:view_permission] = options.delete(:view_permission) || "view_#{self.name.pluralize.underscore}".to_sym
30 30 attachable_options[:delete_permission] = options.delete(:delete_permission) || "edit_#{self.name.pluralize.underscore}".to_sym
31 31
32 32 has_many :attachments, options.merge(:as => :container,
33 33 :order => "#{Attachment.table_name}.created_on ASC, #{Attachment.table_name}.id ASC",
34 34 :dependent => :destroy)
35 35 send :include, Redmine::Acts::Attachable::InstanceMethods
36 36 before_save :attach_saved_attachments
37 37 end
38 38 end
39 39
40 40 module InstanceMethods
41 41 def self.included(base)
42 42 base.extend ClassMethods
43 43 end
44 44
45 45 def attachments_visible?(user=User.current)
46 46 (respond_to?(:visible?) ? visible?(user) : true) &&
47 47 user.allowed_to?(self.class.attachable_options[:view_permission], self.project)
48 48 end
49 49
50 50 def attachments_deletable?(user=User.current)
51 51 (respond_to?(:visible?) ? visible?(user) : true) &&
52 52 user.allowed_to?(self.class.attachable_options[:delete_permission], self.project)
53 53 end
54 54
55 55 def saved_attachments
56 56 @saved_attachments ||= []
57 57 end
58 58
59 59 def unsaved_attachments
60 60 @unsaved_attachments ||= []
61 61 end
62 62
63 63 def save_attachments(attachments, author=User.current)
64 64 if attachments.is_a?(Hash)
65 65 attachments = attachments.stringify_keys
66 66 attachments = attachments.to_a.sort {|a, b|
67 67 if a.first.to_i > 0 && b.first.to_i > 0
68 68 a.first.to_i <=> b.first.to_i
69 69 elsif a.first.to_i > 0
70 70 1
71 71 elsif b.first.to_i > 0
72 72 -1
73 73 else
74 74 a.first <=> b.first
75 75 end
76 76 }
77 77 attachments = attachments.map(&:last)
78 78 end
79 79 if attachments.is_a?(Array)
80 80 attachments.each do |attachment|
81 81 next unless attachment.is_a?(Hash)
82 82 a = nil
83 83 if file = attachment['file']
84 84 next unless file.size > 0
85 85 a = Attachment.create(:file => file, :author => author)
86 86 elsif token = attachment['token']
87 87 a = Attachment.find_by_token(token)
88 88 next unless a
89 89 a.filename = attachment['filename'] unless attachment['filename'].blank?
90 a.content_type = attachment['content_type']
90 a.content_type = attachment['content_type'] unless attachment['content_type'].blank?
91 91 end
92 92 next unless a
93 93 a.description = attachment['description'].to_s.strip
94 94 if a.new_record?
95 95 unsaved_attachments << a
96 96 else
97 97 saved_attachments << a
98 98 end
99 99 end
100 100 end
101 101 {:files => saved_attachments, :unsaved => unsaved_attachments}
102 102 end
103 103
104 104 def attach_saved_attachments
105 105 saved_attachments.each do |attachment|
106 106 self.attachments << attachment
107 107 end
108 108 end
109 109
110 110 module ClassMethods
111 111 end
112 112 end
113 113 end
114 114 end
115 115 end
@@ -1,289 +1,298
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2014 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 45 def test_filename_should_remove_eols
46 46 assert_equal "line_feed", Attachment.new(:filename => "line\nfeed").filename
47 47 assert_equal "line_feed", Attachment.new(:filename => "some\npath/line\nfeed").filename
48 48 assert_equal "carriage_return", Attachment.new(:filename => "carriage\rreturn").filename
49 49 assert_equal "carriage_return", Attachment.new(:filename => "some\rpath/carriage\rreturn").filename
50 50 end
51 51
52 52 def test_create
53 53 a = Attachment.new(:container => Issue.find(1),
54 54 :file => uploaded_test_file("testfile.txt", "text/plain"),
55 55 :author => User.find(1))
56 56 assert a.save
57 57 assert_equal 'testfile.txt', a.filename
58 58 assert_equal 59, a.filesize
59 59 assert_equal 'text/plain', a.content_type
60 60 assert_equal 0, a.downloads
61 61 assert_equal '1478adae0d4eb06d35897518540e25d6', a.digest
62 62
63 63 assert a.disk_directory
64 64 assert_match %r{\A\d{4}/\d{2}\z}, a.disk_directory
65 65
66 66 assert File.exist?(a.diskfile)
67 67 assert_equal 59, File.size(a.diskfile)
68 68 end
69 69
70 70 def test_copy_should_preserve_attributes
71 71 a = Attachment.find(1)
72 72 copy = a.copy
73 73
74 74 assert_save copy
75 75 copy = Attachment.order('id DESC').first
76 76 %w(filename filesize content_type author_id created_on description digest disk_filename disk_directory diskfile).each do |attribute|
77 77 assert_equal a.send(attribute), copy.send(attribute), "#{attribute} was different"
78 78 end
79 79 end
80 80
81 81 def test_size_should_be_validated_for_new_file
82 82 with_settings :attachment_max_size => 0 do
83 83 a = Attachment.new(:container => Issue.find(1),
84 84 :file => uploaded_test_file("testfile.txt", "text/plain"),
85 85 :author => User.find(1))
86 86 assert !a.save
87 87 end
88 88 end
89 89
90 90 def test_size_should_not_be_validated_when_copying
91 91 a = Attachment.create!(:container => Issue.find(1),
92 92 :file => uploaded_test_file("testfile.txt", "text/plain"),
93 93 :author => User.find(1))
94 94 with_settings :attachment_max_size => 0 do
95 95 copy = a.copy
96 96 assert copy.save
97 97 end
98 98 end
99 99
100 100 def test_description_length_should_be_validated
101 101 a = Attachment.new(:description => 'a' * 300)
102 102 assert !a.save
103 103 assert_not_equal [], a.errors[:description]
104 104 end
105 105
106 106 def test_destroy
107 107 a = Attachment.new(:container => Issue.find(1),
108 108 :file => uploaded_test_file("testfile.txt", "text/plain"),
109 109 :author => User.find(1))
110 110 assert a.save
111 111 assert_equal 'testfile.txt', a.filename
112 112 assert_equal 59, a.filesize
113 113 assert_equal 'text/plain', a.content_type
114 114 assert_equal 0, a.downloads
115 115 assert_equal '1478adae0d4eb06d35897518540e25d6', a.digest
116 116 diskfile = a.diskfile
117 117 assert File.exist?(diskfile)
118 118 assert_equal 59, File.size(a.diskfile)
119 119 assert a.destroy
120 120 assert !File.exist?(diskfile)
121 121 end
122 122
123 123 def test_destroy_should_not_delete_file_referenced_by_other_attachment
124 124 a = Attachment.create!(:container => Issue.find(1),
125 125 :file => uploaded_test_file("testfile.txt", "text/plain"),
126 126 :author => User.find(1))
127 127 diskfile = a.diskfile
128 128
129 129 copy = a.copy
130 130 copy.save!
131 131
132 132 assert File.exists?(diskfile)
133 133 a.destroy
134 134 assert File.exists?(diskfile)
135 135 copy.destroy
136 136 assert !File.exists?(diskfile)
137 137 end
138 138
139 139 def test_create_should_auto_assign_content_type
140 140 a = Attachment.new(:container => Issue.find(1),
141 141 :file => uploaded_test_file("testfile.txt", ""),
142 142 :author => User.find(1))
143 143 assert a.save
144 144 assert_equal 'text/plain', a.content_type
145 145 end
146 146
147 147 def test_identical_attachments_at_the_same_time_should_not_overwrite
148 148 a1 = Attachment.create!(:container => Issue.find(1),
149 149 :file => uploaded_test_file("testfile.txt", ""),
150 150 :author => User.find(1))
151 151 a2 = Attachment.create!(:container => Issue.find(1),
152 152 :file => uploaded_test_file("testfile.txt", ""),
153 153 :author => User.find(1))
154 154 assert a1.disk_filename != a2.disk_filename
155 155 end
156 156
157 157 def test_filename_should_be_basenamed
158 158 a = Attachment.new(:file => MockFile.new(:original_filename => "path/to/the/file"))
159 159 assert_equal 'file', a.filename
160 160 end
161 161
162 162 def test_filename_should_be_sanitized
163 163 a = Attachment.new(:file => MockFile.new(:original_filename => "valid:[] invalid:?%*|\"'<>chars"))
164 164 assert_equal 'valid_[] invalid_chars', a.filename
165 165 end
166 166
167 167 def test_diskfilename
168 168 assert Attachment.disk_filename("test_file.txt") =~ /^\d{12}_test_file.txt$/
169 169 assert_equal 'test_file.txt', Attachment.disk_filename("test_file.txt")[13..-1]
170 170 assert_equal '770c509475505f37c2b8fb6030434d6b.txt', Attachment.disk_filename("test_accentuΓ©.txt")[13..-1]
171 171 assert_equal 'f8139524ebb8f32e51976982cd20a85d', Attachment.disk_filename("test_accentuΓ©")[13..-1]
172 172 assert_equal 'cbb5b0f30978ba03731d61f9f6d10011', Attachment.disk_filename("test_accentuΓ©.Γ§a")[13..-1]
173 173 end
174 174
175 175 def test_title
176 176 a = Attachment.new(:filename => "test.png")
177 177 assert_equal "test.png", a.title
178 178
179 179 a = Attachment.new(:filename => "test.png", :description => "Cool image")
180 180 assert_equal "test.png (Cool image)", a.title
181 181 end
182 182
183 183 def test_prune_should_destroy_old_unattached_attachments
184 184 Attachment.create!(:file => uploaded_test_file("testfile.txt", ""), :author_id => 1, :created_on => 2.days.ago)
185 185 Attachment.create!(:file => uploaded_test_file("testfile.txt", ""), :author_id => 1, :created_on => 2.days.ago)
186 186 Attachment.create!(:file => uploaded_test_file("testfile.txt", ""), :author_id => 1)
187 187
188 188 assert_difference 'Attachment.count', -2 do
189 189 Attachment.prune
190 190 end
191 191 end
192 192
193 193 def test_move_from_root_to_target_directory_should_move_root_files
194 194 a = Attachment.find(20)
195 195 assert a.disk_directory.blank?
196 196 # Create a real file for this fixture
197 197 File.open(a.diskfile, "w") do |f|
198 198 f.write "test file at the root of files directory"
199 199 end
200 200 assert a.readable?
201 201 Attachment.move_from_root_to_target_directory
202 202
203 203 a.reload
204 204 assert_equal '2012/05', a.disk_directory
205 205 assert a.readable?
206 206 end
207 207
208 208 test "Attachmnet.attach_files should attach the file" do
209 209 issue = Issue.first
210 210 assert_difference 'Attachment.count' do
211 211 Attachment.attach_files(issue,
212 212 '1' => {
213 213 'file' => uploaded_test_file('testfile.txt', 'text/plain'),
214 214 'description' => 'test'
215 215 })
216 216 end
217 217 attachment = Attachment.order('id DESC').first
218 218 assert_equal issue, attachment.container
219 219 assert_equal 'testfile.txt', attachment.filename
220 220 assert_equal 59, attachment.filesize
221 221 assert_equal 'test', attachment.description
222 222 assert_equal 'text/plain', attachment.content_type
223 223 assert File.exists?(attachment.diskfile)
224 224 assert_equal 59, File.size(attachment.diskfile)
225 225 end
226 226
227 227 test "Attachmnet.attach_files should add unsaved files to the object as unsaved attachments" do
228 228 # Max size of 0 to force Attachment creation failures
229 229 with_settings(:attachment_max_size => 0) do
230 230 @project = Project.find(1)
231 231 response = Attachment.attach_files(@project, {
232 232 '1' => {'file' => mock_file, 'description' => 'test'},
233 233 '2' => {'file' => mock_file, 'description' => 'test'}
234 234 })
235 235
236 236 assert response[:unsaved].present?
237 237 assert_equal 2, response[:unsaved].length
238 238 assert response[:unsaved].first.new_record?
239 239 assert response[:unsaved].second.new_record?
240 240 assert_equal response[:unsaved], @project.unsaved_attachments
241 241 end
242 242 end
243 243
244 test "Attachment.attach_files should preserve the content_type of attachments added by token" do
245 @project = Project.find(1)
246 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", ""), :author_id => 1, :created_on => 2.days.ago)
247 assert_equal 'text/plain', attachment.content_type
248 Attachment.attach_files(@project, { '1' => {'token' => attachment.token } })
249 attachment.reload
250 assert_equal 'text/plain', attachment.content_type
251 end
252
244 253 def test_latest_attach
245 254 set_fixtures_attachments_directory
246 255 a1 = Attachment.find(16)
247 256 assert_equal "testfile.png", a1.filename
248 257 assert a1.readable?
249 258 assert (! a1.visible?(User.anonymous))
250 259 assert a1.visible?(User.find(2))
251 260 a2 = Attachment.find(17)
252 261 assert_equal "testfile.PNG", a2.filename
253 262 assert a2.readable?
254 263 assert (! a2.visible?(User.anonymous))
255 264 assert a2.visible?(User.find(2))
256 265 assert a1.created_on < a2.created_on
257 266
258 267 la1 = Attachment.latest_attach([a1, a2], "testfile.png")
259 268 assert_equal 17, la1.id
260 269 la2 = Attachment.latest_attach([a1, a2], "Testfile.PNG")
261 270 assert_equal 17, la2.id
262 271
263 272 set_tmp_attachments_directory
264 273 end
265 274
266 275 def test_thumbnailable_should_be_true_for_images
267 276 assert_equal true, Attachment.new(:filename => 'test.jpg').thumbnailable?
268 277 end
269 278
270 279 def test_thumbnailable_should_be_true_for_non_images
271 280 assert_equal false, Attachment.new(:filename => 'test.txt').thumbnailable?
272 281 end
273 282
274 283 if convert_installed?
275 284 def test_thumbnail_should_generate_the_thumbnail
276 285 set_fixtures_attachments_directory
277 286 attachment = Attachment.find(16)
278 287 Attachment.clear_thumbnails
279 288
280 289 assert_difference "Dir.glob(File.join(Attachment.thumbnails_storage_path, '*.thumb')).size" do
281 290 thumbnail = attachment.thumbnail
282 291 assert_equal "16_8e0294de2441577c529f170b6fb8f638_100.thumb", File.basename(thumbnail)
283 292 assert File.exists?(thumbnail)
284 293 end
285 294 end
286 295 else
287 296 puts '(ImageMagick convert not available)'
288 297 end
289 298 end
General Comments 0
You need to be logged in to leave comments. Login now