##// END OF EJS Templates
MailHandler: Match assignee on the full display name (#11552)....
Jean-Philippe Lang -
r10053:5969df814249
parent child
Show More
@@ -1,475 +1,475
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2012 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 class MailHandler < ActionMailer::Base
19 19 include ActionView::Helpers::SanitizeHelper
20 20 include Redmine::I18n
21 21
22 22 class UnauthorizedAction < StandardError; end
23 23 class MissingInformation < StandardError; end
24 24
25 25 attr_reader :email, :user
26 26
27 27 def self.receive(email, options={})
28 28 @@handler_options = options.dup
29 29
30 30 @@handler_options[:issue] ||= {}
31 31
32 32 if @@handler_options[:allow_override].is_a?(String)
33 33 @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip)
34 34 end
35 35 @@handler_options[:allow_override] ||= []
36 36 # Project needs to be overridable if not specified
37 37 @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project)
38 38 # Status overridable by default
39 39 @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status)
40 40
41 41 @@handler_options[:no_permission_check] = (@@handler_options[:no_permission_check].to_s == '1' ? true : false)
42 42
43 43 email.force_encoding('ASCII-8BIT') if email.respond_to?(:force_encoding)
44 44 super(email)
45 45 end
46 46
47 47 def logger
48 48 Rails.logger
49 49 end
50 50
51 51 cattr_accessor :ignored_emails_headers
52 52 @@ignored_emails_headers = {
53 53 'X-Auto-Response-Suppress' => 'oof',
54 54 'Auto-Submitted' => /^auto-/
55 55 }
56 56
57 57 # Processes incoming emails
58 58 # Returns the created object (eg. an issue, a message) or false
59 59 def receive(email)
60 60 @email = email
61 61 sender_email = email.from.to_a.first.to_s.strip
62 62 # Ignore emails received from the application emission address to avoid hell cycles
63 63 if sender_email.downcase == Setting.mail_from.to_s.strip.downcase
64 64 if logger && logger.info
65 65 logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]"
66 66 end
67 67 return false
68 68 end
69 69 # Ignore auto generated emails
70 70 self.class.ignored_emails_headers.each do |key, ignored_value|
71 71 value = email.header[key]
72 72 if value
73 73 value = value.to_s.downcase
74 74 if (ignored_value.is_a?(Regexp) && value.match(ignored_value)) || value == ignored_value
75 75 if logger && logger.info
76 76 logger.info "MailHandler: ignoring email with #{key}:#{value} header"
77 77 end
78 78 return false
79 79 end
80 80 end
81 81 end
82 82 @user = User.find_by_mail(sender_email) if sender_email.present?
83 83 if @user && !@user.active?
84 84 if logger && logger.info
85 85 logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]"
86 86 end
87 87 return false
88 88 end
89 89 if @user.nil?
90 90 # Email was submitted by an unknown user
91 91 case @@handler_options[:unknown_user]
92 92 when 'accept'
93 93 @user = User.anonymous
94 94 when 'create'
95 95 @user = create_user_from_email
96 96 if @user
97 97 if logger && logger.info
98 98 logger.info "MailHandler: [#{@user.login}] account created"
99 99 end
100 100 Mailer.account_information(@user, @user.password).deliver
101 101 else
102 102 if logger && logger.error
103 103 logger.error "MailHandler: could not create account for [#{sender_email}]"
104 104 end
105 105 return false
106 106 end
107 107 else
108 108 # Default behaviour, emails from unknown users are ignored
109 109 if logger && logger.info
110 110 logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]"
111 111 end
112 112 return false
113 113 end
114 114 end
115 115 User.current = @user
116 116 dispatch
117 117 end
118 118
119 119 private
120 120
121 121 MESSAGE_ID_RE = %r{^<?redmine\.([a-z0-9_]+)\-(\d+)\.\d+@}
122 122 ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]*#(\d+)\]}
123 123 MESSAGE_REPLY_SUBJECT_RE = %r{\[[^\]]*msg(\d+)\]}
124 124
125 125 def dispatch
126 126 headers = [email.in_reply_to, email.references].flatten.compact
127 127 if headers.detect {|h| h.to_s =~ MESSAGE_ID_RE}
128 128 klass, object_id = $1, $2.to_i
129 129 method_name = "receive_#{klass}_reply"
130 130 if self.class.private_instance_methods.collect(&:to_s).include?(method_name)
131 131 send method_name, object_id
132 132 else
133 133 # ignoring it
134 134 end
135 135 elsif m = email.subject.match(ISSUE_REPLY_SUBJECT_RE)
136 136 receive_issue_reply(m[1].to_i)
137 137 elsif m = email.subject.match(MESSAGE_REPLY_SUBJECT_RE)
138 138 receive_message_reply(m[1].to_i)
139 139 else
140 140 dispatch_to_default
141 141 end
142 142 rescue ActiveRecord::RecordInvalid => e
143 143 # TODO: send a email to the user
144 144 logger.error e.message if logger
145 145 false
146 146 rescue MissingInformation => e
147 147 logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
148 148 false
149 149 rescue UnauthorizedAction => e
150 150 logger.error "MailHandler: unauthorized attempt from #{user}" if logger
151 151 false
152 152 end
153 153
154 154 def dispatch_to_default
155 155 receive_issue
156 156 end
157 157
158 158 # Creates a new issue
159 159 def receive_issue
160 160 project = target_project
161 161 # check permission
162 162 unless @@handler_options[:no_permission_check]
163 163 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
164 164 end
165 165
166 166 issue = Issue.new(:author => user, :project => project)
167 167 issue.safe_attributes = issue_attributes_from_keywords(issue)
168 168 issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
169 169 issue.subject = cleaned_up_subject
170 170 if issue.subject.blank?
171 171 issue.subject = '(no subject)'
172 172 end
173 173 issue.description = cleaned_up_text_body
174 174
175 175 # add To and Cc as watchers before saving so the watchers can reply to Redmine
176 176 add_watchers(issue)
177 177 issue.save!
178 178 add_attachments(issue)
179 179 logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info
180 180 issue
181 181 end
182 182
183 183 # Adds a note to an existing issue
184 184 def receive_issue_reply(issue_id)
185 185 issue = Issue.find_by_id(issue_id)
186 186 return unless issue
187 187 # check permission
188 188 unless @@handler_options[:no_permission_check]
189 189 unless user.allowed_to?(:add_issue_notes, issue.project) ||
190 190 user.allowed_to?(:edit_issues, issue.project)
191 191 raise UnauthorizedAction
192 192 end
193 193 end
194 194
195 195 # ignore CLI-supplied defaults for new issues
196 196 @@handler_options[:issue].clear
197 197
198 198 journal = issue.init_journal(user)
199 199 issue.safe_attributes = issue_attributes_from_keywords(issue)
200 200 issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
201 201 journal.notes = cleaned_up_text_body
202 202 add_attachments(issue)
203 203 issue.save!
204 204 if logger && logger.info
205 205 logger.info "MailHandler: issue ##{issue.id} updated by #{user}"
206 206 end
207 207 journal
208 208 end
209 209
210 210 # Reply will be added to the issue
211 211 def receive_journal_reply(journal_id)
212 212 journal = Journal.find_by_id(journal_id)
213 213 if journal && journal.journalized_type == 'Issue'
214 214 receive_issue_reply(journal.journalized_id)
215 215 end
216 216 end
217 217
218 218 # Receives a reply to a forum message
219 219 def receive_message_reply(message_id)
220 220 message = Message.find_by_id(message_id)
221 221 if message
222 222 message = message.root
223 223
224 224 unless @@handler_options[:no_permission_check]
225 225 raise UnauthorizedAction unless user.allowed_to?(:add_messages, message.project)
226 226 end
227 227
228 228 if !message.locked?
229 229 reply = Message.new(:subject => cleaned_up_subject.gsub(%r{^.*msg\d+\]}, '').strip,
230 230 :content => cleaned_up_text_body)
231 231 reply.author = user
232 232 reply.board = message.board
233 233 message.children << reply
234 234 add_attachments(reply)
235 235 reply
236 236 else
237 237 if logger && logger.info
238 238 logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic"
239 239 end
240 240 end
241 241 end
242 242 end
243 243
244 244 def add_attachments(obj)
245 245 if email.attachments && email.attachments.any?
246 246 email.attachments.each do |attachment|
247 247 obj.attachments << Attachment.create(:container => obj,
248 248 :file => attachment.decoded,
249 249 :filename => attachment.filename,
250 250 :author => user,
251 251 :content_type => attachment.mime_type)
252 252 end
253 253 end
254 254 end
255 255
256 256 # Adds To and Cc as watchers of the given object if the sender has the
257 257 # appropriate permission
258 258 def add_watchers(obj)
259 259 if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
260 260 addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
261 261 unless addresses.empty?
262 262 watchers = User.active.find(:all, :conditions => ['LOWER(mail) IN (?)', addresses])
263 263 watchers.each {|w| obj.add_watcher(w)}
264 264 end
265 265 end
266 266 end
267 267
268 268 def get_keyword(attr, options={})
269 269 @keywords ||= {}
270 270 if @keywords.has_key?(attr)
271 271 @keywords[attr]
272 272 else
273 273 @keywords[attr] = begin
274 274 if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) &&
275 275 (v = extract_keyword!(plain_text_body, attr, options[:format]))
276 276 v
277 277 elsif !@@handler_options[:issue][attr].blank?
278 278 @@handler_options[:issue][attr]
279 279 end
280 280 end
281 281 end
282 282 end
283 283
284 284 # Destructively extracts the value for +attr+ in +text+
285 285 # Returns nil if no matching keyword found
286 286 def extract_keyword!(text, attr, format=nil)
287 287 keys = [attr.to_s.humanize]
288 288 if attr.is_a?(Symbol)
289 289 if user && user.language.present?
290 290 keys << l("field_#{attr}", :default => '', :locale => user.language)
291 291 end
292 292 if Setting.default_language.present?
293 293 keys << l("field_#{attr}", :default => '', :locale => Setting.default_language)
294 294 end
295 295 end
296 296 keys.reject! {|k| k.blank?}
297 297 keys.collect! {|k| Regexp.escape(k)}
298 298 format ||= '.+'
299 299 keyword = nil
300 300 regexp = /^(#{keys.join('|')})[ \t]*:[ \t]*(#{format})\s*$/i
301 301 if m = text.match(regexp)
302 302 keyword = m[2].strip
303 303 text.gsub!(regexp, '')
304 304 end
305 305 keyword
306 306 end
307 307
308 308 def target_project
309 309 # TODO: other ways to specify project:
310 310 # * parse the email To field
311 311 # * specific project (eg. Setting.mail_handler_target_project)
312 312 target = Project.find_by_identifier(get_keyword(:project))
313 313 raise MissingInformation.new('Unable to determine target project') if target.nil?
314 314 target
315 315 end
316 316
317 317 # Returns a Hash of issue attributes extracted from keywords in the email body
318 318 def issue_attributes_from_keywords(issue)
319 319 assigned_to = (k = get_keyword(:assigned_to, :override => true)) && find_assignee_from_keyword(k, issue)
320 320
321 321 attrs = {
322 322 'tracker_id' => (k = get_keyword(:tracker)) && issue.project.trackers.named(k).first.try(:id),
323 323 'status_id' => (k = get_keyword(:status)) && IssueStatus.named(k).first.try(:id),
324 324 'priority_id' => (k = get_keyword(:priority)) && IssuePriority.named(k).first.try(:id),
325 325 'category_id' => (k = get_keyword(:category)) && issue.project.issue_categories.named(k).first.try(:id),
326 326 'assigned_to_id' => assigned_to.try(:id),
327 327 'fixed_version_id' => (k = get_keyword(:fixed_version, :override => true)) &&
328 328 issue.project.shared_versions.named(k).first.try(:id),
329 329 'start_date' => get_keyword(:start_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
330 330 'due_date' => get_keyword(:due_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
331 331 'estimated_hours' => get_keyword(:estimated_hours, :override => true),
332 332 'done_ratio' => get_keyword(:done_ratio, :override => true, :format => '(\d|10)?0')
333 333 }.delete_if {|k, v| v.blank? }
334 334
335 335 if issue.new_record? && attrs['tracker_id'].nil?
336 336 attrs['tracker_id'] = issue.project.trackers.find(:first).try(:id)
337 337 end
338 338
339 339 attrs
340 340 end
341 341
342 342 # Returns a Hash of issue custom field values extracted from keywords in the email body
343 343 def custom_field_values_from_keywords(customized)
344 344 customized.custom_field_values.inject({}) do |h, v|
345 345 if keyword = get_keyword(v.custom_field.name, :override => true)
346 346 h[v.custom_field.id.to_s] = v.custom_field.value_from_keyword(keyword, customized)
347 347 end
348 348 h
349 349 end
350 350 end
351 351
352 352 # Returns the text/plain part of the email
353 353 # If not found (eg. HTML-only email), returns the body with tags removed
354 354 def plain_text_body
355 355 return @plain_text_body unless @plain_text_body.nil?
356 356
357 357 part = email.text_part || email.html_part || email
358 358 @plain_text_body = Redmine::CodesetUtil.to_utf8(part.body.decoded, part.charset)
359 359
360 360 # strip html tags and remove doctype directive
361 361 @plain_text_body = strip_tags(@plain_text_body.strip)
362 362 @plain_text_body.sub! %r{^<!DOCTYPE .*$}, ''
363 363 @plain_text_body
364 364 end
365 365
366 366 def cleaned_up_text_body
367 367 cleanup_body(plain_text_body)
368 368 end
369 369
370 370 def cleaned_up_subject
371 371 subject = email.subject.to_s
372 372 unless subject.respond_to?(:encoding)
373 373 # try to reencode to utf8 manually with ruby1.8
374 374 begin
375 375 if h = email.header[:subject]
376 376 if m = h.value.match(/^=\?([^\?]+)\?/)
377 377 subject = Redmine::CodesetUtil.to_utf8(subject, m[1])
378 378 end
379 379 end
380 380 rescue
381 381 # nop
382 382 end
383 383 end
384 384 subject.strip[0,255]
385 385 end
386 386
387 387 def self.full_sanitizer
388 388 @full_sanitizer ||= HTML::FullSanitizer.new
389 389 end
390 390
391 391 def self.assign_string_attribute_with_limit(object, attribute, value, limit=nil)
392 392 limit ||= object.class.columns_hash[attribute.to_s].limit || 255
393 393 value = value.to_s.slice(0, limit)
394 394 object.send("#{attribute}=", value)
395 395 end
396 396
397 397 # Returns a User from an email address and a full name
398 398 def self.new_user_from_attributes(email_address, fullname=nil)
399 399 user = User.new
400 400
401 401 # Truncating the email address would result in an invalid format
402 402 user.mail = email_address
403 403 assign_string_attribute_with_limit(user, 'login', email_address, User::LOGIN_LENGTH_LIMIT)
404 404
405 405 names = fullname.blank? ? email_address.gsub(/@.*$/, '').split('.') : fullname.split
406 406 assign_string_attribute_with_limit(user, 'firstname', names.shift)
407 407 assign_string_attribute_with_limit(user, 'lastname', names.join(' '))
408 408 user.lastname = '-' if user.lastname.blank?
409 409
410 410 password_length = [Setting.password_min_length.to_i, 10].max
411 411 user.password = Redmine::Utils.random_hex(password_length / 2 + 1)
412 412 user.language = Setting.default_language
413 413
414 414 unless user.valid?
415 415 user.login = "user#{Redmine::Utils.random_hex(6)}" unless user.errors[:login].blank?
416 416 user.firstname = "-" unless user.errors[:firstname].blank?
417 417 user.lastname = "-" unless user.errors[:lastname].blank?
418 418 end
419 419
420 420 user
421 421 end
422 422
423 423 # Creates a User for the +email+ sender
424 424 # Returns the user or nil if it could not be created
425 425 def create_user_from_email
426 426 from = email.header['from'].to_s
427 427 addr, name = from, nil
428 428 if m = from.match(/^"?(.+?)"?\s+<(.+@.+)>$/)
429 429 addr, name = m[2], m[1]
430 430 end
431 431 if addr.present?
432 432 user = self.class.new_user_from_attributes(addr, name)
433 433 if user.save
434 434 user
435 435 else
436 436 logger.error "MailHandler: failed to create User: #{user.errors.full_messages}" if logger
437 437 nil
438 438 end
439 439 else
440 440 logger.error "MailHandler: failed to create User: no FROM address found" if logger
441 441 nil
442 442 end
443 443 end
444 444
445 445 # Removes the email body of text after the truncation configurations.
446 446 def cleanup_body(body)
447 447 delimiters = Setting.mail_handler_body_delimiters.to_s.split(/[\r\n]+/).reject(&:blank?).map {|s| Regexp.escape(s)}
448 448 unless delimiters.empty?
449 449 regex = Regexp.new("^[> ]*(#{ delimiters.join('|') })\s*[\r\n].*", Regexp::MULTILINE)
450 450 body = body.gsub(regex, '')
451 451 end
452 452 body.strip
453 453 end
454 454
455 455 def find_assignee_from_keyword(keyword, issue)
456 456 keyword = keyword.to_s.downcase
457 457 assignable = issue.assignable_users
458 458 assignee = nil
459 459 assignee ||= assignable.detect {|a|
460 460 a.mail.to_s.downcase == keyword ||
461 461 a.login.to_s.downcase == keyword
462 462 }
463 463 if assignee.nil? && keyword.match(/ /)
464 464 firstname, lastname = *(keyword.split) # "First Last Throwaway"
465 465 assignee ||= assignable.detect {|a|
466 466 a.is_a?(User) && a.firstname.to_s.downcase == firstname &&
467 467 a.lastname.to_s.downcase == lastname
468 468 }
469 469 end
470 470 if assignee.nil?
471 assignee ||= assignable.detect {|a| a.is_a?(Group) && a.name.downcase == keyword}
471 assignee ||= assignable.detect {|a| a.name.downcase == keyword}
472 472 end
473 473 assignee
474 474 end
475 475 end
@@ -1,113 +1,114
1 1 module ObjectHelpers
2 2 def User.generate!(attributes={})
3 3 @generated_user_login ||= 'user0'
4 4 @generated_user_login.succ!
5 5 user = User.new(attributes)
6 6 user.login = @generated_user_login if user.login.blank?
7 7 user.mail = "#{@generated_user_login}@example.com" if user.mail.blank?
8 8 user.firstname = "Bob" if user.firstname.blank?
9 9 user.lastname = "Doe" if user.lastname.blank?
10 10 yield user if block_given?
11 11 user.save!
12 12 user
13 13 end
14 14
15 def User.add_to_project(user, project, roles)
15 def User.add_to_project(user, project, roles=nil)
16 roles = Role.find(1) if roles.nil?
16 17 roles = [roles] unless roles.is_a?(Array)
17 18 Member.create!(:principal => user, :project => project, :roles => roles)
18 19 end
19 20
20 21 def Group.generate!(attributes={})
21 22 @generated_group_name ||= 'Group 0'
22 23 @generated_group_name.succ!
23 24 group = Group.new(attributes)
24 25 group.name = @generated_group_name if group.name.blank?
25 26 yield group if block_given?
26 27 group.save!
27 28 group
28 29 end
29 30
30 31 def Project.generate!(attributes={})
31 32 @generated_project_identifier ||= 'project-0000'
32 33 @generated_project_identifier.succ!
33 34 project = Project.new(attributes)
34 35 project.name = @generated_project_identifier if project.name.blank?
35 36 project.identifier = @generated_project_identifier if project.identifier.blank?
36 37 yield project if block_given?
37 38 project.save!
38 39 project
39 40 end
40 41
41 42 def Tracker.generate!(attributes={})
42 43 @generated_tracker_name ||= 'Tracker 0'
43 44 @generated_tracker_name.succ!
44 45 tracker = Tracker.new(attributes)
45 46 tracker.name = @generated_tracker_name if tracker.name.blank?
46 47 yield tracker if block_given?
47 48 tracker.save!
48 49 tracker
49 50 end
50 51
51 52 def Role.generate!(attributes={})
52 53 @generated_role_name ||= 'Role 0'
53 54 @generated_role_name.succ!
54 55 role = Role.new(attributes)
55 56 role.name = @generated_role_name if role.name.blank?
56 57 yield role if block_given?
57 58 role.save!
58 59 role
59 60 end
60 61
61 62 def Issue.generate!(attributes={})
62 63 issue = Issue.new(attributes)
63 64 issue.subject = 'Generated' if issue.subject.blank?
64 65 issue.author ||= User.find(2)
65 66 yield issue if block_given?
66 67 issue.save!
67 68 issue
68 69 end
69 70
70 71 # Generate an issue for a project, using its trackers
71 72 def Issue.generate_for_project!(project, attributes={})
72 73 issue = Issue.new(attributes) do |issue|
73 74 issue.project = project
74 75 issue.tracker = project.trackers.first unless project.trackers.empty?
75 76 issue.subject = 'Generated' if issue.subject.blank?
76 77 issue.author ||= User.find(2)
77 78 yield issue if block_given?
78 79 end
79 80 issue.save!
80 81 issue
81 82 end
82 83
83 84 def Version.generate!(attributes={})
84 85 @generated_version_name ||= 'Version 0'
85 86 @generated_version_name.succ!
86 87 version = Version.new(attributes)
87 88 version.name = @generated_version_name if version.name.blank?
88 89 yield version if block_given?
89 90 version.save!
90 91 version
91 92 end
92 93
93 94 def AuthSource.generate!(attributes={})
94 95 @generated_auth_source_name ||= 'Auth 0'
95 96 @generated_auth_source_name.succ!
96 97 source = AuthSource.new(attributes)
97 98 source.name = @generated_auth_source_name if source.name.blank?
98 99 yield source if block_given?
99 100 source.save!
100 101 source
101 102 end
102 103
103 104 def Board.generate!(attributes={})
104 105 @generated_board_name ||= 'Forum 0'
105 106 @generated_board_name.succ!
106 107 board = Board.new(attributes)
107 108 board.name = @generated_board_name if board.name.blank?
108 109 board.description = @generated_board_name if board.description.blank?
109 110 yield board if block_given?
110 111 board.save!
111 112 board
112 113 end
113 114 end
@@ -1,662 +1,672
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2012 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 MailHandlerTest < ActiveSupport::TestCase
23 23 fixtures :users, :projects, :enabled_modules, :roles,
24 24 :members, :member_roles, :users,
25 25 :issues, :issue_statuses,
26 26 :workflows, :trackers, :projects_trackers,
27 27 :versions, :enumerations, :issue_categories,
28 28 :custom_fields, :custom_fields_trackers, :custom_fields_projects,
29 29 :boards, :messages
30 30
31 31 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
32 32
33 33 def setup
34 34 ActionMailer::Base.deliveries.clear
35 35 Setting.notified_events = Redmine::Notifiable.all.collect(&:name)
36 36 end
37 37
38 38 def teardown
39 39 Setting.clear_cache
40 40 end
41 41
42 42 def test_add_issue
43 43 ActionMailer::Base.deliveries.clear
44 44 # This email contains: 'Project: onlinestore'
45 45 issue = submit_email('ticket_on_given_project.eml')
46 46 assert issue.is_a?(Issue)
47 47 assert !issue.new_record?
48 48 issue.reload
49 49 assert_equal Project.find(2), issue.project
50 50 assert_equal issue.project.trackers.first, issue.tracker
51 51 assert_equal 'New ticket on a given project', issue.subject
52 52 assert_equal User.find_by_login('jsmith'), issue.author
53 53 assert_equal IssueStatus.find_by_name('Resolved'), issue.status
54 54 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
55 55 assert_equal '2010-01-01', issue.start_date.to_s
56 56 assert_equal '2010-12-31', issue.due_date.to_s
57 57 assert_equal User.find_by_login('jsmith'), issue.assigned_to
58 58 assert_equal Version.find_by_name('Alpha'), issue.fixed_version
59 59 assert_equal 2.5, issue.estimated_hours
60 60 assert_equal 30, issue.done_ratio
61 61 assert_equal [issue.id, 1, 2], [issue.root_id, issue.lft, issue.rgt]
62 62 # keywords should be removed from the email body
63 63 assert !issue.description.match(/^Project:/i)
64 64 assert !issue.description.match(/^Status:/i)
65 65 assert !issue.description.match(/^Start Date:/i)
66 66 # Email notification should be sent
67 67 mail = ActionMailer::Base.deliveries.last
68 68 assert_not_nil mail
69 69 assert mail.subject.include?('New ticket on a given project')
70 70 end
71 71
72 72 def test_add_issue_with_default_tracker
73 73 # This email contains: 'Project: onlinestore'
74 74 issue = submit_email(
75 75 'ticket_on_given_project.eml',
76 76 :issue => {:tracker => 'Support request'}
77 77 )
78 78 assert issue.is_a?(Issue)
79 79 assert !issue.new_record?
80 80 issue.reload
81 81 assert_equal 'Support request', issue.tracker.name
82 82 end
83 83
84 84 def test_add_issue_with_status
85 85 # This email contains: 'Project: onlinestore' and 'Status: Resolved'
86 86 issue = submit_email('ticket_on_given_project.eml')
87 87 assert issue.is_a?(Issue)
88 88 assert !issue.new_record?
89 89 issue.reload
90 90 assert_equal Project.find(2), issue.project
91 91 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
92 92 end
93 93
94 94 def test_add_issue_with_attributes_override
95 95 issue = submit_email(
96 96 'ticket_with_attributes.eml',
97 97 :allow_override => 'tracker,category,priority'
98 98 )
99 99 assert issue.is_a?(Issue)
100 100 assert !issue.new_record?
101 101 issue.reload
102 102 assert_equal 'New ticket on a given project', issue.subject
103 103 assert_equal User.find_by_login('jsmith'), issue.author
104 104 assert_equal Project.find(2), issue.project
105 105 assert_equal 'Feature request', issue.tracker.to_s
106 106 assert_equal 'Stock management', issue.category.to_s
107 107 assert_equal 'Urgent', issue.priority.to_s
108 108 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
109 109 end
110 110
111 111 def test_add_issue_with_group_assignment
112 112 with_settings :issue_group_assignment => '1' do
113 113 issue = submit_email('ticket_on_given_project.eml') do |email|
114 114 email.gsub!('Assigned to: John Smith', 'Assigned to: B Team')
115 115 end
116 116 assert issue.is_a?(Issue)
117 117 assert !issue.new_record?
118 118 issue.reload
119 119 assert_equal Group.find(11), issue.assigned_to
120 120 end
121 121 end
122 122
123 123 def test_add_issue_with_partial_attributes_override
124 124 issue = submit_email(
125 125 'ticket_with_attributes.eml',
126 126 :issue => {:priority => 'High'},
127 127 :allow_override => ['tracker']
128 128 )
129 129 assert issue.is_a?(Issue)
130 130 assert !issue.new_record?
131 131 issue.reload
132 132 assert_equal 'New ticket on a given project', issue.subject
133 133 assert_equal User.find_by_login('jsmith'), issue.author
134 134 assert_equal Project.find(2), issue.project
135 135 assert_equal 'Feature request', issue.tracker.to_s
136 136 assert_nil issue.category
137 137 assert_equal 'High', issue.priority.to_s
138 138 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
139 139 end
140 140
141 141 def test_add_issue_with_spaces_between_attribute_and_separator
142 142 issue = submit_email(
143 143 'ticket_with_spaces_between_attribute_and_separator.eml',
144 144 :allow_override => 'tracker,category,priority'
145 145 )
146 146 assert issue.is_a?(Issue)
147 147 assert !issue.new_record?
148 148 issue.reload
149 149 assert_equal 'New ticket on a given project', issue.subject
150 150 assert_equal User.find_by_login('jsmith'), issue.author
151 151 assert_equal Project.find(2), issue.project
152 152 assert_equal 'Feature request', issue.tracker.to_s
153 153 assert_equal 'Stock management', issue.category.to_s
154 154 assert_equal 'Urgent', issue.priority.to_s
155 155 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
156 156 end
157 157
158 158 def test_add_issue_with_attachment_to_specific_project
159 159 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
160 160 assert issue.is_a?(Issue)
161 161 assert !issue.new_record?
162 162 issue.reload
163 163 assert_equal 'Ticket created by email with attachment', issue.subject
164 164 assert_equal User.find_by_login('jsmith'), issue.author
165 165 assert_equal Project.find(2), issue.project
166 166 assert_equal 'This is a new ticket with attachments', issue.description
167 167 # Attachment properties
168 168 assert_equal 1, issue.attachments.size
169 169 assert_equal 'Paella.jpg', issue.attachments.first.filename
170 170 assert_equal 'image/jpeg', issue.attachments.first.content_type
171 171 assert_equal 10790, issue.attachments.first.filesize
172 172 end
173 173
174 174 def test_add_issue_with_custom_fields
175 175 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'onlinestore'})
176 176 assert issue.is_a?(Issue)
177 177 assert !issue.new_record?
178 178 issue.reload
179 179 assert_equal 'New ticket with custom field values', issue.subject
180 180 assert_equal 'Value for a custom field',
181 181 issue.custom_value_for(CustomField.find_by_name('Searchable field')).value
182 182 assert !issue.description.match(/^searchable field:/i)
183 183 end
184 184
185 185 def test_add_issue_with_version_custom_fields
186 186 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true, :tracker_ids => [1,2,3])
187 187
188 188 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'ecookbook'}) do |email|
189 189 email << "Affected version: 1.0\n"
190 190 end
191 191 assert issue.is_a?(Issue)
192 192 assert !issue.new_record?
193 193 issue.reload
194 194 assert_equal '2', issue.custom_field_value(field)
195 195 end
196 196
197 def test_add_issue_should_match_assignee_on_display_name
198 user = User.generate!(:firstname => 'Foo Bar', :lastname => 'Foo Baz')
199 User.add_to_project(user, Project.find(2))
200 issue = submit_email('ticket_on_given_project.eml') do |email|
201 email.sub!(/^Assigned to.*$/, 'Assigned to: Foo Bar Foo baz')
202 end
203 assert issue.is_a?(Issue)
204 assert_equal user, issue.assigned_to
205 end
206
197 207 def test_add_issue_with_cc
198 208 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
199 209 assert issue.is_a?(Issue)
200 210 assert !issue.new_record?
201 211 issue.reload
202 212 assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
203 213 assert_equal 1, issue.watcher_user_ids.size
204 214 end
205 215
206 216 def test_add_issue_by_unknown_user
207 217 assert_no_difference 'User.count' do
208 218 assert_equal false,
209 219 submit_email(
210 220 'ticket_by_unknown_user.eml',
211 221 :issue => {:project => 'ecookbook'}
212 222 )
213 223 end
214 224 end
215 225
216 226 def test_add_issue_by_anonymous_user
217 227 Role.anonymous.add_permission!(:add_issues)
218 228 assert_no_difference 'User.count' do
219 229 issue = submit_email(
220 230 'ticket_by_unknown_user.eml',
221 231 :issue => {:project => 'ecookbook'},
222 232 :unknown_user => 'accept'
223 233 )
224 234 assert issue.is_a?(Issue)
225 235 assert issue.author.anonymous?
226 236 end
227 237 end
228 238
229 239 def test_add_issue_by_anonymous_user_with_no_from_address
230 240 Role.anonymous.add_permission!(:add_issues)
231 241 assert_no_difference 'User.count' do
232 242 issue = submit_email(
233 243 'ticket_by_empty_user.eml',
234 244 :issue => {:project => 'ecookbook'},
235 245 :unknown_user => 'accept'
236 246 )
237 247 assert issue.is_a?(Issue)
238 248 assert issue.author.anonymous?
239 249 end
240 250 end
241 251
242 252 def test_add_issue_by_anonymous_user_on_private_project
243 253 Role.anonymous.add_permission!(:add_issues)
244 254 assert_no_difference 'User.count' do
245 255 assert_no_difference 'Issue.count' do
246 256 assert_equal false,
247 257 submit_email(
248 258 'ticket_by_unknown_user.eml',
249 259 :issue => {:project => 'onlinestore'},
250 260 :unknown_user => 'accept'
251 261 )
252 262 end
253 263 end
254 264 end
255 265
256 266 def test_add_issue_by_anonymous_user_on_private_project_without_permission_check
257 267 assert_no_difference 'User.count' do
258 268 assert_difference 'Issue.count' do
259 269 issue = submit_email(
260 270 'ticket_by_unknown_user.eml',
261 271 :issue => {:project => 'onlinestore'},
262 272 :no_permission_check => '1',
263 273 :unknown_user => 'accept'
264 274 )
265 275 assert issue.is_a?(Issue)
266 276 assert issue.author.anonymous?
267 277 assert !issue.project.is_public?
268 278 assert_equal [issue.id, 1, 2], [issue.root_id, issue.lft, issue.rgt]
269 279 end
270 280 end
271 281 end
272 282
273 283 def test_add_issue_by_created_user
274 284 Setting.default_language = 'en'
275 285 assert_difference 'User.count' do
276 286 issue = submit_email(
277 287 'ticket_by_unknown_user.eml',
278 288 :issue => {:project => 'ecookbook'},
279 289 :unknown_user => 'create'
280 290 )
281 291 assert issue.is_a?(Issue)
282 292 assert issue.author.active?
283 293 assert_equal 'john.doe@somenet.foo', issue.author.mail
284 294 assert_equal 'John', issue.author.firstname
285 295 assert_equal 'Doe', issue.author.lastname
286 296
287 297 # account information
288 298 email = ActionMailer::Base.deliveries.first
289 299 assert_not_nil email
290 300 assert email.subject.include?('account activation')
291 301 login = mail_body(email).match(/\* Login: (.*)$/)[1].strip
292 302 password = mail_body(email).match(/\* Password: (.*)$/)[1].strip
293 303 assert_equal issue.author, User.try_to_login(login, password)
294 304 end
295 305 end
296 306
297 307 def test_add_issue_without_from_header
298 308 Role.anonymous.add_permission!(:add_issues)
299 309 assert_equal false, submit_email('ticket_without_from_header.eml')
300 310 end
301 311
302 312 def test_add_issue_with_invalid_attributes
303 313 issue = submit_email(
304 314 'ticket_with_invalid_attributes.eml',
305 315 :allow_override => 'tracker,category,priority'
306 316 )
307 317 assert issue.is_a?(Issue)
308 318 assert !issue.new_record?
309 319 issue.reload
310 320 assert_nil issue.assigned_to
311 321 assert_nil issue.start_date
312 322 assert_nil issue.due_date
313 323 assert_equal 0, issue.done_ratio
314 324 assert_equal 'Normal', issue.priority.to_s
315 325 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
316 326 end
317 327
318 328 def test_add_issue_with_localized_attributes
319 329 User.find_by_mail('jsmith@somenet.foo').update_attribute 'language', 'fr'
320 330 issue = submit_email(
321 331 'ticket_with_localized_attributes.eml',
322 332 :allow_override => 'tracker,category,priority'
323 333 )
324 334 assert issue.is_a?(Issue)
325 335 assert !issue.new_record?
326 336 issue.reload
327 337 assert_equal 'New ticket on a given project', issue.subject
328 338 assert_equal User.find_by_login('jsmith'), issue.author
329 339 assert_equal Project.find(2), issue.project
330 340 assert_equal 'Feature request', issue.tracker.to_s
331 341 assert_equal 'Stock management', issue.category.to_s
332 342 assert_equal 'Urgent', issue.priority.to_s
333 343 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
334 344 end
335 345
336 346 def test_add_issue_with_japanese_keywords
337 347 ja_dev = "\xe9\x96\x8b\xe7\x99\xba"
338 348 ja_dev.force_encoding('UTF-8') if ja_dev.respond_to?(:force_encoding)
339 349 tracker = Tracker.create!(:name => ja_dev)
340 350 Project.find(1).trackers << tracker
341 351 issue = submit_email(
342 352 'japanese_keywords_iso_2022_jp.eml',
343 353 :issue => {:project => 'ecookbook'},
344 354 :allow_override => 'tracker'
345 355 )
346 356 assert_kind_of Issue, issue
347 357 assert_equal tracker, issue.tracker
348 358 end
349 359
350 360 def test_add_issue_from_apple_mail
351 361 issue = submit_email(
352 362 'apple_mail_with_attachment.eml',
353 363 :issue => {:project => 'ecookbook'}
354 364 )
355 365 assert_kind_of Issue, issue
356 366 assert_equal 1, issue.attachments.size
357 367
358 368 attachment = issue.attachments.first
359 369 assert_equal 'paella.jpg', attachment.filename
360 370 assert_equal 10790, attachment.filesize
361 371 assert File.exist?(attachment.diskfile)
362 372 assert_equal 10790, File.size(attachment.diskfile)
363 373 assert_equal 'caaf384198bcbc9563ab5c058acd73cd', attachment.digest
364 374 end
365 375
366 376 def test_add_issue_with_iso_8859_1_subject
367 377 issue = submit_email(
368 378 'subject_as_iso-8859-1.eml',
369 379 :issue => {:project => 'ecookbook'}
370 380 )
371 381 assert_kind_of Issue, issue
372 382 assert_equal 'Testmail from Webmail: Γ€ ΓΆ ΓΌ...', issue.subject
373 383 end
374 384
375 385 def test_should_ignore_emails_from_locked_users
376 386 User.find(2).lock!
377 387
378 388 MailHandler.any_instance.expects(:dispatch).never
379 389 assert_no_difference 'Issue.count' do
380 390 assert_equal false, submit_email('ticket_on_given_project.eml')
381 391 end
382 392 end
383 393
384 394 def test_should_ignore_emails_from_emission_address
385 395 Role.anonymous.add_permission!(:add_issues)
386 396 assert_no_difference 'User.count' do
387 397 assert_equal false,
388 398 submit_email(
389 399 'ticket_from_emission_address.eml',
390 400 :issue => {:project => 'ecookbook'},
391 401 :unknown_user => 'create'
392 402 )
393 403 end
394 404 end
395 405
396 406 def test_should_ignore_auto_replied_emails
397 407 MailHandler.any_instance.expects(:dispatch).never
398 408 [
399 409 "X-Auto-Response-Suppress: OOF",
400 410 "Auto-Submitted: auto-replied",
401 411 "Auto-Submitted: Auto-Replied",
402 412 "Auto-Submitted: auto-generated"
403 413 ].each do |header|
404 414 raw = IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
405 415 raw = header + "\n" + raw
406 416
407 417 assert_no_difference 'Issue.count' do
408 418 assert_equal false, MailHandler.receive(raw), "email with #{header} header was not ignored"
409 419 end
410 420 end
411 421 end
412 422
413 423 def test_add_issue_should_send_email_notification
414 424 Setting.notified_events = ['issue_added']
415 425 ActionMailer::Base.deliveries.clear
416 426 # This email contains: 'Project: onlinestore'
417 427 issue = submit_email('ticket_on_given_project.eml')
418 428 assert issue.is_a?(Issue)
419 429 assert_equal 1, ActionMailer::Base.deliveries.size
420 430 end
421 431
422 432 def test_update_issue
423 433 journal = submit_email('ticket_reply.eml')
424 434 assert journal.is_a?(Journal)
425 435 assert_equal User.find_by_login('jsmith'), journal.user
426 436 assert_equal Issue.find(2), journal.journalized
427 437 assert_match /This is reply/, journal.notes
428 438 assert_equal 'Feature request', journal.issue.tracker.name
429 439 end
430 440
431 441 def test_update_issue_with_attribute_changes
432 442 # This email contains: 'Status: Resolved'
433 443 journal = submit_email('ticket_reply_with_status.eml')
434 444 assert journal.is_a?(Journal)
435 445 issue = Issue.find(journal.issue.id)
436 446 assert_equal User.find_by_login('jsmith'), journal.user
437 447 assert_equal Issue.find(2), journal.journalized
438 448 assert_match /This is reply/, journal.notes
439 449 assert_equal 'Feature request', journal.issue.tracker.name
440 450 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
441 451 assert_equal '2010-01-01', issue.start_date.to_s
442 452 assert_equal '2010-12-31', issue.due_date.to_s
443 453 assert_equal User.find_by_login('jsmith'), issue.assigned_to
444 454 assert_equal "52.6", issue.custom_value_for(CustomField.find_by_name('Float field')).value
445 455 # keywords should be removed from the email body
446 456 assert !journal.notes.match(/^Status:/i)
447 457 assert !journal.notes.match(/^Start Date:/i)
448 458 end
449 459
450 460 def test_update_issue_with_attachment
451 461 assert_difference 'Journal.count' do
452 462 assert_difference 'JournalDetail.count' do
453 463 assert_difference 'Attachment.count' do
454 464 assert_no_difference 'Issue.count' do
455 465 journal = submit_email('ticket_with_attachment.eml') do |raw|
456 466 raw.gsub! /^Subject: .*$/, 'Subject: Re: [Cookbook - Feature #2] (New) Add ingredients categories'
457 467 end
458 468 end
459 469 end
460 470 end
461 471 end
462 472 journal = Journal.first(:order => 'id DESC')
463 473 assert_equal Issue.find(2), journal.journalized
464 474 assert_equal 1, journal.details.size
465 475
466 476 detail = journal.details.first
467 477 assert_equal 'attachment', detail.property
468 478 assert_equal 'Paella.jpg', detail.value
469 479 end
470 480
471 481 def test_update_issue_should_send_email_notification
472 482 ActionMailer::Base.deliveries.clear
473 483 journal = submit_email('ticket_reply.eml')
474 484 assert journal.is_a?(Journal)
475 485 assert_equal 1, ActionMailer::Base.deliveries.size
476 486 end
477 487
478 488 def test_update_issue_should_not_set_defaults
479 489 journal = submit_email(
480 490 'ticket_reply.eml',
481 491 :issue => {:tracker => 'Support request', :priority => 'High'}
482 492 )
483 493 assert journal.is_a?(Journal)
484 494 assert_match /This is reply/, journal.notes
485 495 assert_equal 'Feature request', journal.issue.tracker.name
486 496 assert_equal 'Normal', journal.issue.priority.name
487 497 end
488 498
489 499 def test_reply_to_a_message
490 500 m = submit_email('message_reply.eml')
491 501 assert m.is_a?(Message)
492 502 assert !m.new_record?
493 503 m.reload
494 504 assert_equal 'Reply via email', m.subject
495 505 # The email replies to message #2 which is part of the thread of message #1
496 506 assert_equal Message.find(1), m.parent
497 507 end
498 508
499 509 def test_reply_to_a_message_by_subject
500 510 m = submit_email('message_reply_by_subject.eml')
501 511 assert m.is_a?(Message)
502 512 assert !m.new_record?
503 513 m.reload
504 514 assert_equal 'Reply to the first post', m.subject
505 515 assert_equal Message.find(1), m.parent
506 516 end
507 517
508 518 def test_should_strip_tags_of_html_only_emails
509 519 issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
510 520 assert issue.is_a?(Issue)
511 521 assert !issue.new_record?
512 522 issue.reload
513 523 assert_equal 'HTML email', issue.subject
514 524 assert_equal 'This is a html-only email.', issue.description
515 525 end
516 526
517 527 context "truncate emails based on the Setting" do
518 528 context "with no setting" do
519 529 setup do
520 530 Setting.mail_handler_body_delimiters = ''
521 531 end
522 532
523 533 should "add the entire email into the issue" do
524 534 issue = submit_email('ticket_on_given_project.eml')
525 535 assert_issue_created(issue)
526 536 assert issue.description.include?('---')
527 537 assert issue.description.include?('This paragraph is after the delimiter')
528 538 end
529 539 end
530 540
531 541 context "with a single string" do
532 542 setup do
533 543 Setting.mail_handler_body_delimiters = '---'
534 544 end
535 545 should "truncate the email at the delimiter for the issue" do
536 546 issue = submit_email('ticket_on_given_project.eml')
537 547 assert_issue_created(issue)
538 548 assert issue.description.include?('This paragraph is before delimiters')
539 549 assert issue.description.include?('--- This line starts with a delimiter')
540 550 assert !issue.description.match(/^---$/)
541 551 assert !issue.description.include?('This paragraph is after the delimiter')
542 552 end
543 553 end
544 554
545 555 context "with a single quoted reply (e.g. reply to a Redmine email notification)" do
546 556 setup do
547 557 Setting.mail_handler_body_delimiters = '--- Reply above. Do not remove this line. ---'
548 558 end
549 559 should "truncate the email at the delimiter with the quoted reply symbols (>)" do
550 560 journal = submit_email('issue_update_with_quoted_reply_above.eml')
551 561 assert journal.is_a?(Journal)
552 562 assert journal.notes.include?('An update to the issue by the sender.')
553 563 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
554 564 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
555 565 end
556 566 end
557 567
558 568 context "with multiple quoted replies (e.g. reply to a reply of a Redmine email notification)" do
559 569 setup do
560 570 Setting.mail_handler_body_delimiters = '--- Reply above. Do not remove this line. ---'
561 571 end
562 572 should "truncate the email at the delimiter with the quoted reply symbols (>)" do
563 573 journal = submit_email('issue_update_with_multiple_quoted_reply_above.eml')
564 574 assert journal.is_a?(Journal)
565 575 assert journal.notes.include?('An update to the issue by the sender.')
566 576 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
567 577 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
568 578 end
569 579 end
570 580
571 581 context "with multiple strings" do
572 582 setup do
573 583 Setting.mail_handler_body_delimiters = "---\nBREAK"
574 584 end
575 585 should "truncate the email at the first delimiter found (BREAK)" do
576 586 issue = submit_email('ticket_on_given_project.eml')
577 587 assert_issue_created(issue)
578 588 assert issue.description.include?('This paragraph is before delimiters')
579 589 assert !issue.description.include?('BREAK')
580 590 assert !issue.description.include?('This paragraph is between delimiters')
581 591 assert !issue.description.match(/^---$/)
582 592 assert !issue.description.include?('This paragraph is after the delimiter')
583 593 end
584 594 end
585 595 end
586 596
587 597 def test_email_with_long_subject_line
588 598 issue = submit_email('ticket_with_long_subject.eml')
589 599 assert issue.is_a?(Issue)
590 600 assert_equal issue.subject, 'New ticket on a given project with a very long subject line which exceeds 255 chars and should not be ignored but chopped off. And if the subject line is still not long enough, we just add more text. And more text. Wow, this is really annoying. Especially, if you have nothing to say...'[0,255]
591 601 end
592 602
593 603 def test_new_user_from_attributes_should_return_valid_user
594 604 to_test = {
595 605 # [address, name] => [login, firstname, lastname]
596 606 ['jsmith@example.net', nil] => ['jsmith@example.net', 'jsmith', '-'],
597 607 ['jsmith@example.net', 'John'] => ['jsmith@example.net', 'John', '-'],
598 608 ['jsmith@example.net', 'John Smith'] => ['jsmith@example.net', 'John', 'Smith'],
599 609 ['jsmith@example.net', 'John Paul Smith'] => ['jsmith@example.net', 'John', 'Paul Smith'],
600 610 ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsTheMaximumLength Smith'] => ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsT', 'Smith'],
601 611 ['jsmith@example.net', 'John AVeryLongLastnameThatExceedsTheMaximumLength'] => ['jsmith@example.net', 'John', 'AVeryLongLastnameThatExceedsTh']
602 612 }
603 613
604 614 to_test.each do |attrs, expected|
605 615 user = MailHandler.new_user_from_attributes(attrs.first, attrs.last)
606 616
607 617 assert user.valid?, user.errors.full_messages.to_s
608 618 assert_equal attrs.first, user.mail
609 619 assert_equal expected[0], user.login
610 620 assert_equal expected[1], user.firstname
611 621 assert_equal expected[2], user.lastname
612 622 end
613 623 end
614 624
615 625 def test_new_user_from_attributes_should_respect_minimum_password_length
616 626 with_settings :password_min_length => 15 do
617 627 user = MailHandler.new_user_from_attributes('jsmith@example.net')
618 628 assert user.valid?
619 629 assert user.password.length >= 15
620 630 end
621 631 end
622 632
623 633 def test_new_user_from_attributes_should_use_default_login_if_invalid
624 634 user = MailHandler.new_user_from_attributes('foo+bar@example.net')
625 635 assert user.valid?
626 636 assert user.login =~ /^user[a-f0-9]+$/
627 637 assert_equal 'foo+bar@example.net', user.mail
628 638 end
629 639
630 640 def test_new_user_with_utf8_encoded_fullname_should_be_decoded
631 641 assert_difference 'User.count' do
632 642 issue = submit_email(
633 643 'fullname_of_sender_as_utf8_encoded.eml',
634 644 :issue => {:project => 'ecookbook'},
635 645 :unknown_user => 'create'
636 646 )
637 647 end
638 648
639 649 user = User.first(:order => 'id DESC')
640 650 assert_equal "foo@example.org", user.mail
641 651 str1 = "\xc3\x84\xc3\xa4"
642 652 str2 = "\xc3\x96\xc3\xb6"
643 653 str1.force_encoding('UTF-8') if str1.respond_to?(:force_encoding)
644 654 str2.force_encoding('UTF-8') if str2.respond_to?(:force_encoding)
645 655 assert_equal str1, user.firstname
646 656 assert_equal str2, user.lastname
647 657 end
648 658
649 659 private
650 660
651 661 def submit_email(filename, options={})
652 662 raw = IO.read(File.join(FIXTURES_PATH, filename))
653 663 yield raw if block_given?
654 664 MailHandler.receive(raw, options)
655 665 end
656 666
657 667 def assert_issue_created(issue)
658 668 assert issue.is_a?(Issue)
659 669 assert !issue.new_record?
660 670 issue.reload
661 671 end
662 672 end
General Comments 0
You need to be logged in to leave comments. Login now