##// END OF EJS Templates
Merged r14159 (#19558)....
Jean-Philippe Lang -
r13827:214e737ee02c
parent child
Show More
@@ -1,551 +1,550
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2015 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.deep_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_account_notice] = (@@handler_options[:no_account_notice].to_s == '1')
42 42 @@handler_options[:no_notification] = (@@handler_options[:no_notification].to_s == '1')
43 43 @@handler_options[:no_permission_check] = (@@handler_options[:no_permission_check].to_s == '1')
44 44
45 45 email.force_encoding('ASCII-8BIT')
46 46 super(email)
47 47 end
48 48
49 49 # Receives an email and rescues any exception
50 50 def self.safe_receive(*args)
51 51 receive(*args)
52 52 rescue Exception => e
53 53 logger.error "An unexpected error occurred when receiving email: #{e.message}" if logger
54 54 return false
55 55 end
56 56
57 57 # Extracts MailHandler options from environment variables
58 58 # Use when receiving emails with rake tasks
59 59 def self.extract_options_from_env(env)
60 60 options = {:issue => {}}
61 61 %w(project status tracker category priority).each do |option|
62 62 options[:issue][option.to_sym] = env[option] if env[option]
63 63 end
64 64 %w(allow_override unknown_user no_permission_check no_account_notice default_group).each do |option|
65 65 options[option.to_sym] = env[option] if env[option]
66 66 end
67 67 options
68 68 end
69 69
70 70 def logger
71 71 Rails.logger
72 72 end
73 73
74 74 cattr_accessor :ignored_emails_headers
75 75 @@ignored_emails_headers = {
76 'X-Auto-Response-Suppress' => /(oof|all)/,
77 76 'Auto-Submitted' => /\Aauto-(replied|generated)/,
78 77 'X-Autoreply' => 'yes'
79 78 }
80 79
81 80 # Processes incoming emails
82 81 # Returns the created object (eg. an issue, a message) or false
83 82 def receive(email)
84 83 @email = email
85 84 sender_email = email.from.to_a.first.to_s.strip
86 85 # Ignore emails received from the application emission address to avoid hell cycles
87 86 if sender_email.downcase == Setting.mail_from.to_s.strip.downcase
88 87 if logger
89 88 logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]"
90 89 end
91 90 return false
92 91 end
93 92 # Ignore auto generated emails
94 93 self.class.ignored_emails_headers.each do |key, ignored_value|
95 94 value = email.header[key]
96 95 if value
97 96 value = value.to_s.downcase
98 97 if (ignored_value.is_a?(Regexp) && value.match(ignored_value)) || value == ignored_value
99 98 if logger
100 99 logger.info "MailHandler: ignoring email with #{key}:#{value} header"
101 100 end
102 101 return false
103 102 end
104 103 end
105 104 end
106 105 @user = User.find_by_mail(sender_email) if sender_email.present?
107 106 if @user && !@user.active?
108 107 if logger
109 108 logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]"
110 109 end
111 110 return false
112 111 end
113 112 if @user.nil?
114 113 # Email was submitted by an unknown user
115 114 case @@handler_options[:unknown_user]
116 115 when 'accept'
117 116 @user = User.anonymous
118 117 when 'create'
119 118 @user = create_user_from_email
120 119 if @user
121 120 if logger
122 121 logger.info "MailHandler: [#{@user.login}] account created"
123 122 end
124 123 add_user_to_group(@@handler_options[:default_group])
125 124 unless @@handler_options[:no_account_notice]
126 125 Mailer.account_information(@user, @user.password).deliver
127 126 end
128 127 else
129 128 if logger
130 129 logger.error "MailHandler: could not create account for [#{sender_email}]"
131 130 end
132 131 return false
133 132 end
134 133 else
135 134 # Default behaviour, emails from unknown users are ignored
136 135 if logger
137 136 logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]"
138 137 end
139 138 return false
140 139 end
141 140 end
142 141 User.current = @user
143 142 dispatch
144 143 end
145 144
146 145 private
147 146
148 147 MESSAGE_ID_RE = %r{^<?redmine\.([a-z0-9_]+)\-(\d+)\.\d+(\.[a-f0-9]+)?@}
149 148 ISSUE_REPLY_SUBJECT_RE = %r{\[(?:[^\]]*\s+)?#(\d+)\]}
150 149 MESSAGE_REPLY_SUBJECT_RE = %r{\[[^\]]*msg(\d+)\]}
151 150
152 151 def dispatch
153 152 headers = [email.in_reply_to, email.references].flatten.compact
154 153 subject = email.subject.to_s
155 154 if headers.detect {|h| h.to_s =~ MESSAGE_ID_RE}
156 155 klass, object_id = $1, $2.to_i
157 156 method_name = "receive_#{klass}_reply"
158 157 if self.class.private_instance_methods.collect(&:to_s).include?(method_name)
159 158 send method_name, object_id
160 159 else
161 160 # ignoring it
162 161 end
163 162 elsif m = subject.match(ISSUE_REPLY_SUBJECT_RE)
164 163 receive_issue_reply(m[1].to_i)
165 164 elsif m = subject.match(MESSAGE_REPLY_SUBJECT_RE)
166 165 receive_message_reply(m[1].to_i)
167 166 else
168 167 dispatch_to_default
169 168 end
170 169 rescue ActiveRecord::RecordInvalid => e
171 170 # TODO: send a email to the user
172 171 logger.error e.message if logger
173 172 false
174 173 rescue MissingInformation => e
175 174 logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
176 175 false
177 176 rescue UnauthorizedAction => e
178 177 logger.error "MailHandler: unauthorized attempt from #{user}" if logger
179 178 false
180 179 end
181 180
182 181 def dispatch_to_default
183 182 receive_issue
184 183 end
185 184
186 185 # Creates a new issue
187 186 def receive_issue
188 187 project = target_project
189 188 # check permission
190 189 unless @@handler_options[:no_permission_check]
191 190 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
192 191 end
193 192
194 193 issue = Issue.new(:author => user, :project => project)
195 194 issue.safe_attributes = issue_attributes_from_keywords(issue)
196 195 issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
197 196 issue.subject = cleaned_up_subject
198 197 if issue.subject.blank?
199 198 issue.subject = '(no subject)'
200 199 end
201 200 issue.description = cleaned_up_text_body
202 201 issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
203 202
204 203 # add To and Cc as watchers before saving so the watchers can reply to Redmine
205 204 add_watchers(issue)
206 205 issue.save!
207 206 add_attachments(issue)
208 207 logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger
209 208 issue
210 209 end
211 210
212 211 # Adds a note to an existing issue
213 212 def receive_issue_reply(issue_id, from_journal=nil)
214 213 issue = Issue.find_by_id(issue_id)
215 214 return unless issue
216 215 # check permission
217 216 unless @@handler_options[:no_permission_check]
218 217 unless user.allowed_to?(:add_issue_notes, issue.project) ||
219 218 user.allowed_to?(:edit_issues, issue.project)
220 219 raise UnauthorizedAction
221 220 end
222 221 end
223 222
224 223 # ignore CLI-supplied defaults for new issues
225 224 @@handler_options[:issue].clear
226 225
227 226 journal = issue.init_journal(user)
228 227 if from_journal && from_journal.private_notes?
229 228 # If the received email was a reply to a private note, make the added note private
230 229 issue.private_notes = true
231 230 end
232 231 issue.safe_attributes = issue_attributes_from_keywords(issue)
233 232 issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
234 233 journal.notes = cleaned_up_text_body
235 234 add_attachments(issue)
236 235 issue.save!
237 236 if logger
238 237 logger.info "MailHandler: issue ##{issue.id} updated by #{user}"
239 238 end
240 239 journal
241 240 end
242 241
243 242 # Reply will be added to the issue
244 243 def receive_journal_reply(journal_id)
245 244 journal = Journal.find_by_id(journal_id)
246 245 if journal && journal.journalized_type == 'Issue'
247 246 receive_issue_reply(journal.journalized_id, journal)
248 247 end
249 248 end
250 249
251 250 # Receives a reply to a forum message
252 251 def receive_message_reply(message_id)
253 252 message = Message.find_by_id(message_id)
254 253 if message
255 254 message = message.root
256 255
257 256 unless @@handler_options[:no_permission_check]
258 257 raise UnauthorizedAction unless user.allowed_to?(:add_messages, message.project)
259 258 end
260 259
261 260 if !message.locked?
262 261 reply = Message.new(:subject => cleaned_up_subject.gsub(%r{^.*msg\d+\]}, '').strip,
263 262 :content => cleaned_up_text_body)
264 263 reply.author = user
265 264 reply.board = message.board
266 265 message.children << reply
267 266 add_attachments(reply)
268 267 reply
269 268 else
270 269 if logger
271 270 logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic"
272 271 end
273 272 end
274 273 end
275 274 end
276 275
277 276 def add_attachments(obj)
278 277 if email.attachments && email.attachments.any?
279 278 email.attachments.each do |attachment|
280 279 next unless accept_attachment?(attachment)
281 280 obj.attachments << Attachment.create(:container => obj,
282 281 :file => attachment.decoded,
283 282 :filename => attachment.filename,
284 283 :author => user,
285 284 :content_type => attachment.mime_type)
286 285 end
287 286 end
288 287 end
289 288
290 289 # Returns false if the +attachment+ of the incoming email should be ignored
291 290 def accept_attachment?(attachment)
292 291 @excluded ||= Setting.mail_handler_excluded_filenames.to_s.split(',').map(&:strip).reject(&:blank?)
293 292 @excluded.each do |pattern|
294 293 regexp = %r{\A#{Regexp.escape(pattern).gsub("\\*", ".*")}\z}i
295 294 if attachment.filename.to_s =~ regexp
296 295 logger.info "MailHandler: ignoring attachment #{attachment.filename} matching #{pattern}"
297 296 return false
298 297 end
299 298 end
300 299 true
301 300 end
302 301
303 302 # Adds To and Cc as watchers of the given object if the sender has the
304 303 # appropriate permission
305 304 def add_watchers(obj)
306 305 if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
307 306 addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
308 307 unless addresses.empty?
309 308 User.active.having_mail(addresses).each do |w|
310 309 obj.add_watcher(w)
311 310 end
312 311 end
313 312 end
314 313 end
315 314
316 315 def get_keyword(attr, options={})
317 316 @keywords ||= {}
318 317 if @keywords.has_key?(attr)
319 318 @keywords[attr]
320 319 else
321 320 @keywords[attr] = begin
322 321 if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) &&
323 322 (v = extract_keyword!(cleaned_up_text_body, attr, options[:format]))
324 323 v
325 324 elsif !@@handler_options[:issue][attr].blank?
326 325 @@handler_options[:issue][attr]
327 326 end
328 327 end
329 328 end
330 329 end
331 330
332 331 # Destructively extracts the value for +attr+ in +text+
333 332 # Returns nil if no matching keyword found
334 333 def extract_keyword!(text, attr, format=nil)
335 334 keys = [attr.to_s.humanize]
336 335 if attr.is_a?(Symbol)
337 336 if user && user.language.present?
338 337 keys << l("field_#{attr}", :default => '', :locale => user.language)
339 338 end
340 339 if Setting.default_language.present?
341 340 keys << l("field_#{attr}", :default => '', :locale => Setting.default_language)
342 341 end
343 342 end
344 343 keys.reject! {|k| k.blank?}
345 344 keys.collect! {|k| Regexp.escape(k)}
346 345 format ||= '.+'
347 346 keyword = nil
348 347 regexp = /^(#{keys.join('|')})[ \t]*:[ \t]*(#{format})\s*$/i
349 348 if m = text.match(regexp)
350 349 keyword = m[2].strip
351 350 text.sub!(regexp, '')
352 351 end
353 352 keyword
354 353 end
355 354
356 355 def target_project
357 356 # TODO: other ways to specify project:
358 357 # * parse the email To field
359 358 # * specific project (eg. Setting.mail_handler_target_project)
360 359 target = Project.find_by_identifier(get_keyword(:project))
361 360 if target.nil?
362 361 # Invalid project keyword, use the project specified as the default one
363 362 default_project = @@handler_options[:issue][:project]
364 363 if default_project.present?
365 364 target = Project.find_by_identifier(default_project)
366 365 end
367 366 end
368 367 raise MissingInformation.new('Unable to determine target project') if target.nil?
369 368 target
370 369 end
371 370
372 371 # Returns a Hash of issue attributes extracted from keywords in the email body
373 372 def issue_attributes_from_keywords(issue)
374 373 assigned_to = (k = get_keyword(:assigned_to, :override => true)) && find_assignee_from_keyword(k, issue)
375 374
376 375 attrs = {
377 376 'tracker_id' => (k = get_keyword(:tracker)) && issue.project.trackers.named(k).first.try(:id),
378 377 'status_id' => (k = get_keyword(:status)) && IssueStatus.named(k).first.try(:id),
379 378 'priority_id' => (k = get_keyword(:priority)) && IssuePriority.named(k).first.try(:id),
380 379 'category_id' => (k = get_keyword(:category)) && issue.project.issue_categories.named(k).first.try(:id),
381 380 'assigned_to_id' => assigned_to.try(:id),
382 381 'fixed_version_id' => (k = get_keyword(:fixed_version, :override => true)) &&
383 382 issue.project.shared_versions.named(k).first.try(:id),
384 383 'start_date' => get_keyword(:start_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
385 384 'due_date' => get_keyword(:due_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
386 385 'estimated_hours' => get_keyword(:estimated_hours, :override => true),
387 386 'done_ratio' => get_keyword(:done_ratio, :override => true, :format => '(\d|10)?0')
388 387 }.delete_if {|k, v| v.blank? }
389 388
390 389 if issue.new_record? && attrs['tracker_id'].nil?
391 390 attrs['tracker_id'] = issue.project.trackers.first.try(:id)
392 391 end
393 392
394 393 attrs
395 394 end
396 395
397 396 # Returns a Hash of issue custom field values extracted from keywords in the email body
398 397 def custom_field_values_from_keywords(customized)
399 398 customized.custom_field_values.inject({}) do |h, v|
400 399 if keyword = get_keyword(v.custom_field.name, :override => true)
401 400 h[v.custom_field.id.to_s] = v.custom_field.value_from_keyword(keyword, customized)
402 401 end
403 402 h
404 403 end
405 404 end
406 405
407 406 # Returns the text/plain part of the email
408 407 # If not found (eg. HTML-only email), returns the body with tags removed
409 408 def plain_text_body
410 409 return @plain_text_body unless @plain_text_body.nil?
411 410
412 411 parts = if (text_parts = email.all_parts.select {|p| p.mime_type == 'text/plain'}).present?
413 412 text_parts
414 413 elsif (html_parts = email.all_parts.select {|p| p.mime_type == 'text/html'}).present?
415 414 html_parts
416 415 else
417 416 [email]
418 417 end
419 418
420 419 parts.reject! do |part|
421 420 part.attachment?
422 421 end
423 422
424 423 @plain_text_body = parts.map do |p|
425 424 body_charset = Mail::RubyVer.respond_to?(:pick_encoding) ?
426 425 Mail::RubyVer.pick_encoding(p.charset).to_s : p.charset
427 426 Redmine::CodesetUtil.to_utf8(p.body.decoded, body_charset)
428 427 end.join("\r\n")
429 428
430 429 # strip html tags and remove doctype directive
431 430 if parts.any? {|p| p.mime_type == 'text/html'}
432 431 @plain_text_body = strip_tags(@plain_text_body.strip)
433 432 @plain_text_body.sub! %r{^<!DOCTYPE .*$}, ''
434 433 end
435 434
436 435 @plain_text_body
437 436 end
438 437
439 438 def cleaned_up_text_body
440 439 @cleaned_up_text_body ||= cleanup_body(plain_text_body)
441 440 end
442 441
443 442 def cleaned_up_subject
444 443 subject = email.subject.to_s
445 444 subject.strip[0,255]
446 445 end
447 446
448 447 def self.full_sanitizer
449 448 @full_sanitizer ||= HTML::FullSanitizer.new
450 449 end
451 450
452 451 def self.assign_string_attribute_with_limit(object, attribute, value, limit=nil)
453 452 limit ||= object.class.columns_hash[attribute.to_s].limit || 255
454 453 value = value.to_s.slice(0, limit)
455 454 object.send("#{attribute}=", value)
456 455 end
457 456
458 457 # Returns a User from an email address and a full name
459 458 def self.new_user_from_attributes(email_address, fullname=nil)
460 459 user = User.new
461 460
462 461 # Truncating the email address would result in an invalid format
463 462 user.mail = email_address
464 463 assign_string_attribute_with_limit(user, 'login', email_address, User::LOGIN_LENGTH_LIMIT)
465 464
466 465 names = fullname.blank? ? email_address.gsub(/@.*$/, '').split('.') : fullname.split
467 466 assign_string_attribute_with_limit(user, 'firstname', names.shift, 30)
468 467 assign_string_attribute_with_limit(user, 'lastname', names.join(' '), 30)
469 468 user.lastname = '-' if user.lastname.blank?
470 469 user.language = Setting.default_language
471 470 user.generate_password = true
472 471 user.mail_notification = 'only_my_events'
473 472
474 473 unless user.valid?
475 474 user.login = "user#{Redmine::Utils.random_hex(6)}" unless user.errors[:login].blank?
476 475 user.firstname = "-" unless user.errors[:firstname].blank?
477 476 (puts user.errors[:lastname];user.lastname = "-") unless user.errors[:lastname].blank?
478 477 end
479 478
480 479 user
481 480 end
482 481
483 482 # Creates a User for the +email+ sender
484 483 # Returns the user or nil if it could not be created
485 484 def create_user_from_email
486 485 from = email.header['from'].to_s
487 486 addr, name = from, nil
488 487 if m = from.match(/^"?(.+?)"?\s+<(.+@.+)>$/)
489 488 addr, name = m[2], m[1]
490 489 end
491 490 if addr.present?
492 491 user = self.class.new_user_from_attributes(addr, name)
493 492 if @@handler_options[:no_notification]
494 493 user.mail_notification = 'none'
495 494 end
496 495 if user.save
497 496 user
498 497 else
499 498 logger.error "MailHandler: failed to create User: #{user.errors.full_messages}" if logger
500 499 nil
501 500 end
502 501 else
503 502 logger.error "MailHandler: failed to create User: no FROM address found" if logger
504 503 nil
505 504 end
506 505 end
507 506
508 507 # Adds the newly created user to default group
509 508 def add_user_to_group(default_group)
510 509 if default_group.present?
511 510 default_group.split(',').each do |group_name|
512 511 if group = Group.named(group_name).first
513 512 group.users << @user
514 513 elsif logger
515 514 logger.warn "MailHandler: could not add user to [#{group_name}], group not found"
516 515 end
517 516 end
518 517 end
519 518 end
520 519
521 520 # Removes the email body of text after the truncation configurations.
522 521 def cleanup_body(body)
523 522 delimiters = Setting.mail_handler_body_delimiters.to_s.split(/[\r\n]+/).reject(&:blank?).map {|s| Regexp.escape(s)}
524 523 unless delimiters.empty?
525 524 regex = Regexp.new("^[> ]*(#{ delimiters.join('|') })\s*[\r\n].*", Regexp::MULTILINE)
526 525 body = body.gsub(regex, '')
527 526 end
528 527 body.strip
529 528 end
530 529
531 530 def find_assignee_from_keyword(keyword, issue)
532 531 keyword = keyword.to_s.downcase
533 532 assignable = issue.assignable_users
534 533 assignee = nil
535 534 assignee ||= assignable.detect {|a|
536 535 a.mail.to_s.downcase == keyword ||
537 536 a.login.to_s.downcase == keyword
538 537 }
539 538 if assignee.nil? && keyword.match(/ /)
540 539 firstname, lastname = *(keyword.split) # "First Last Throwaway"
541 540 assignee ||= assignable.detect {|a|
542 541 a.is_a?(User) && a.firstname.to_s.downcase == firstname &&
543 542 a.lastname.to_s.downcase == lastname
544 543 }
545 544 end
546 545 if assignee.nil?
547 546 assignee ||= assignable.detect {|a| a.name.downcase == keyword}
548 547 end
549 548 assignee
550 549 end
551 550 end
@@ -1,538 +1,538
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2015 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 Mailer < ActionMailer::Base
19 19 layout 'mailer'
20 20 helper :application
21 21 helper :issues
22 22 helper :custom_fields
23 23
24 24 include Redmine::I18n
25 25
26 26 def self.default_url_options
27 27 options = {:protocol => Setting.protocol}
28 28 if Setting.host_name.to_s =~ /\A(https?\:\/\/)?(.+?)(\:(\d+))?(\/.+)?\z/i
29 29 host, port, prefix = $2, $4, $5
30 30 options.merge!({
31 31 :host => host, :port => port, :script_name => prefix
32 32 })
33 33 else
34 34 options[:host] = Setting.host_name
35 35 end
36 36 options
37 37 end
38 38
39 39 # Builds a mail for notifying to_users and cc_users about a new issue
40 40 def issue_add(issue, to_users, cc_users)
41 41 redmine_headers 'Project' => issue.project.identifier,
42 42 'Issue-Id' => issue.id,
43 43 'Issue-Author' => issue.author.login
44 44 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
45 45 message_id issue
46 46 references issue
47 47 @author = issue.author
48 48 @issue = issue
49 49 @users = to_users + cc_users
50 50 @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue)
51 51 mail :to => to_users,
52 52 :cc => cc_users,
53 53 :subject => "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}"
54 54 end
55 55
56 56 # Notifies users about a new issue
57 57 def self.deliver_issue_add(issue)
58 58 to = issue.notified_users
59 59 cc = issue.notified_watchers - to
60 60 issue.each_notification(to + cc) do |users|
61 61 Mailer.issue_add(issue, to & users, cc & users).deliver
62 62 end
63 63 end
64 64
65 65 # Builds a mail for notifying to_users and cc_users about an issue update
66 66 def issue_edit(journal, to_users, cc_users)
67 67 issue = journal.journalized
68 68 redmine_headers 'Project' => issue.project.identifier,
69 69 'Issue-Id' => issue.id,
70 70 'Issue-Author' => issue.author.login
71 71 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
72 72 message_id journal
73 73 references issue
74 74 @author = journal.user
75 75 s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] "
76 76 s << "(#{issue.status.name}) " if journal.new_value_for('status_id')
77 77 s << issue.subject
78 78 @issue = issue
79 79 @users = to_users + cc_users
80 80 @journal = journal
81 81 @journal_details = journal.visible_details(@users.first)
82 82 @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue, :anchor => "change-#{journal.id}")
83 83 mail :to => to_users,
84 84 :cc => cc_users,
85 85 :subject => s
86 86 end
87 87
88 88 # Notifies users about an issue update
89 89 def self.deliver_issue_edit(journal)
90 90 issue = journal.journalized.reload
91 91 to = journal.notified_users
92 92 cc = journal.notified_watchers - to
93 93 journal.each_notification(to + cc) do |users|
94 94 issue.each_notification(users) do |users2|
95 95 Mailer.issue_edit(journal, to & users2, cc & users2).deliver
96 96 end
97 97 end
98 98 end
99 99
100 100 def reminder(user, issues, days)
101 101 set_language_if_valid user.language
102 102 @issues = issues
103 103 @days = days
104 104 @issues_url = url_for(:controller => 'issues', :action => 'index',
105 105 :set_filter => 1, :assigned_to_id => user.id,
106 106 :sort => 'due_date:asc')
107 107 mail :to => user,
108 108 :subject => l(:mail_subject_reminder, :count => issues.size, :days => days)
109 109 end
110 110
111 111 # Builds a Mail::Message object used to email users belonging to the added document's project.
112 112 #
113 113 # Example:
114 114 # document_added(document) => Mail::Message object
115 115 # Mailer.document_added(document).deliver => sends an email to the document's project recipients
116 116 def document_added(document)
117 117 redmine_headers 'Project' => document.project.identifier
118 118 @author = User.current
119 119 @document = document
120 120 @document_url = url_for(:controller => 'documents', :action => 'show', :id => document)
121 121 mail :to => document.notified_users,
122 122 :subject => "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}"
123 123 end
124 124
125 125 # Builds a Mail::Message object used to email recipients of a project when an attachements are added.
126 126 #
127 127 # Example:
128 128 # attachments_added(attachments) => Mail::Message object
129 129 # Mailer.attachments_added(attachments).deliver => sends an email to the project's recipients
130 130 def attachments_added(attachments)
131 131 container = attachments.first.container
132 132 added_to = ''
133 133 added_to_url = ''
134 134 @author = attachments.first.author
135 135 case container.class.name
136 136 when 'Project'
137 137 added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container)
138 138 added_to = "#{l(:label_project)}: #{container}"
139 139 recipients = container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}
140 140 when 'Version'
141 141 added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container.project)
142 142 added_to = "#{l(:label_version)}: #{container.name}"
143 143 recipients = container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}
144 144 when 'Document'
145 145 added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id)
146 146 added_to = "#{l(:label_document)}: #{container.title}"
147 147 recipients = container.notified_users
148 148 end
149 149 redmine_headers 'Project' => container.project.identifier
150 150 @attachments = attachments
151 151 @added_to = added_to
152 152 @added_to_url = added_to_url
153 153 mail :to => recipients,
154 154 :subject => "[#{container.project.name}] #{l(:label_attachment_new)}"
155 155 end
156 156
157 157 # Builds a Mail::Message object used to email recipients of a news' project when a news item is added.
158 158 #
159 159 # Example:
160 160 # news_added(news) => Mail::Message object
161 161 # Mailer.news_added(news).deliver => sends an email to the news' project recipients
162 162 def news_added(news)
163 163 redmine_headers 'Project' => news.project.identifier
164 164 @author = news.author
165 165 message_id news
166 166 references news
167 167 @news = news
168 168 @news_url = url_for(:controller => 'news', :action => 'show', :id => news)
169 169 mail :to => news.notified_users,
170 170 :cc => news.notified_watchers_for_added_news,
171 171 :subject => "[#{news.project.name}] #{l(:label_news)}: #{news.title}"
172 172 end
173 173
174 174 # Builds a Mail::Message object used to email recipients of a news' project when a news comment is added.
175 175 #
176 176 # Example:
177 177 # news_comment_added(comment) => Mail::Message object
178 178 # Mailer.news_comment_added(comment) => sends an email to the news' project recipients
179 179 def news_comment_added(comment)
180 180 news = comment.commented
181 181 redmine_headers 'Project' => news.project.identifier
182 182 @author = comment.author
183 183 message_id comment
184 184 references news
185 185 @news = news
186 186 @comment = comment
187 187 @news_url = url_for(:controller => 'news', :action => 'show', :id => news)
188 188 mail :to => news.notified_users,
189 189 :cc => news.notified_watchers,
190 190 :subject => "Re: [#{news.project.name}] #{l(:label_news)}: #{news.title}"
191 191 end
192 192
193 193 # Builds a Mail::Message object used to email the recipients of the specified message that was posted.
194 194 #
195 195 # Example:
196 196 # message_posted(message) => Mail::Message object
197 197 # Mailer.message_posted(message).deliver => sends an email to the recipients
198 198 def message_posted(message)
199 199 redmine_headers 'Project' => message.project.identifier,
200 200 'Topic-Id' => (message.parent_id || message.id)
201 201 @author = message.author
202 202 message_id message
203 203 references message.root
204 204 recipients = message.notified_users
205 205 cc = ((message.root.notified_watchers + message.board.notified_watchers).uniq - recipients)
206 206 @message = message
207 207 @message_url = url_for(message.event_url)
208 208 mail :to => recipients,
209 209 :cc => cc,
210 210 :subject => "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}"
211 211 end
212 212
213 213 # Builds a Mail::Message object used to email the recipients of a project of the specified wiki content was added.
214 214 #
215 215 # Example:
216 216 # wiki_content_added(wiki_content) => Mail::Message object
217 217 # Mailer.wiki_content_added(wiki_content).deliver => sends an email to the project's recipients
218 218 def wiki_content_added(wiki_content)
219 219 redmine_headers 'Project' => wiki_content.project.identifier,
220 220 'Wiki-Page-Id' => wiki_content.page.id
221 221 @author = wiki_content.author
222 222 message_id wiki_content
223 223 recipients = wiki_content.notified_users
224 224 cc = wiki_content.page.wiki.notified_watchers - recipients
225 225 @wiki_content = wiki_content
226 226 @wiki_content_url = url_for(:controller => 'wiki', :action => 'show',
227 227 :project_id => wiki_content.project,
228 228 :id => wiki_content.page.title)
229 229 mail :to => recipients,
230 230 :cc => cc,
231 231 :subject => "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_added, :id => wiki_content.page.pretty_title)}"
232 232 end
233 233
234 234 # Builds a Mail::Message object used to email the recipients of a project of the specified wiki content was updated.
235 235 #
236 236 # Example:
237 237 # wiki_content_updated(wiki_content) => Mail::Message object
238 238 # Mailer.wiki_content_updated(wiki_content).deliver => sends an email to the project's recipients
239 239 def wiki_content_updated(wiki_content)
240 240 redmine_headers 'Project' => wiki_content.project.identifier,
241 241 'Wiki-Page-Id' => wiki_content.page.id
242 242 @author = wiki_content.author
243 243 message_id wiki_content
244 244 recipients = wiki_content.notified_users
245 245 cc = wiki_content.page.wiki.notified_watchers + wiki_content.page.notified_watchers - recipients
246 246 @wiki_content = wiki_content
247 247 @wiki_content_url = url_for(:controller => 'wiki', :action => 'show',
248 248 :project_id => wiki_content.project,
249 249 :id => wiki_content.page.title)
250 250 @wiki_diff_url = url_for(:controller => 'wiki', :action => 'diff',
251 251 :project_id => wiki_content.project, :id => wiki_content.page.title,
252 252 :version => wiki_content.version)
253 253 mail :to => recipients,
254 254 :cc => cc,
255 255 :subject => "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_updated, :id => wiki_content.page.pretty_title)}"
256 256 end
257 257
258 258 # Builds a Mail::Message object used to email the specified user their account information.
259 259 #
260 260 # Example:
261 261 # account_information(user, password) => Mail::Message object
262 262 # Mailer.account_information(user, password).deliver => sends account information to the user
263 263 def account_information(user, password)
264 264 set_language_if_valid user.language
265 265 @user = user
266 266 @password = password
267 267 @login_url = url_for(:controller => 'account', :action => 'login')
268 268 mail :to => user.mail,
269 269 :subject => l(:mail_subject_register, Setting.app_title)
270 270 end
271 271
272 272 # Builds a Mail::Message object used to email all active administrators of an account activation request.
273 273 #
274 274 # Example:
275 275 # account_activation_request(user) => Mail::Message object
276 276 # Mailer.account_activation_request(user).deliver => sends an email to all active administrators
277 277 def account_activation_request(user)
278 278 # Send the email to all active administrators
279 279 recipients = User.active.where(:admin => true)
280 280 @user = user
281 281 @url = url_for(:controller => 'users', :action => 'index',
282 282 :status => User::STATUS_REGISTERED,
283 283 :sort_key => 'created_on', :sort_order => 'desc')
284 284 mail :to => recipients,
285 285 :subject => l(:mail_subject_account_activation_request, Setting.app_title)
286 286 end
287 287
288 288 # Builds a Mail::Message object used to email the specified user that their account was activated by an administrator.
289 289 #
290 290 # Example:
291 291 # account_activated(user) => Mail::Message object
292 292 # Mailer.account_activated(user).deliver => sends an email to the registered user
293 293 def account_activated(user)
294 294 set_language_if_valid user.language
295 295 @user = user
296 296 @login_url = url_for(:controller => 'account', :action => 'login')
297 297 mail :to => user.mail,
298 298 :subject => l(:mail_subject_register, Setting.app_title)
299 299 end
300 300
301 301 def lost_password(token, recipient=nil)
302 302 set_language_if_valid(token.user.language)
303 303 recipient ||= token.user.mail
304 304 @token = token
305 305 @url = url_for(:controller => 'account', :action => 'lost_password', :token => token.value)
306 306 mail :to => recipient,
307 307 :subject => l(:mail_subject_lost_password, Setting.app_title)
308 308 end
309 309
310 310 def register(token)
311 311 set_language_if_valid(token.user.language)
312 312 @token = token
313 313 @url = url_for(:controller => 'account', :action => 'activate', :token => token.value)
314 314 mail :to => token.user.mail,
315 315 :subject => l(:mail_subject_register, Setting.app_title)
316 316 end
317 317
318 318 def test_email(user)
319 319 set_language_if_valid(user.language)
320 320 @url = url_for(:controller => 'welcome')
321 321 mail :to => user.mail,
322 322 :subject => 'Redmine test'
323 323 end
324 324
325 325 # Sends reminders to issue assignees
326 326 # Available options:
327 327 # * :days => how many days in the future to remind about (defaults to 7)
328 328 # * :tracker => id of tracker for filtering issues (defaults to all trackers)
329 329 # * :project => id or identifier of project to process (defaults to all projects)
330 330 # * :users => array of user/group ids who should be reminded
331 331 # * :version => name of target version for filtering issues (defaults to none)
332 332 def self.reminders(options={})
333 333 days = options[:days] || 7
334 334 project = options[:project] ? Project.find(options[:project]) : nil
335 335 tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil
336 336 target_version_id = options[:version] ? Version.named(options[:version]).pluck(:id) : nil
337 337 if options[:version] && target_version_id.blank?
338 338 raise ActiveRecord::RecordNotFound.new("Couldn't find Version with named #{options[:version]}")
339 339 end
340 340 user_ids = options[:users]
341 341
342 342 scope = Issue.open.where("#{Issue.table_name}.assigned_to_id IS NOT NULL" +
343 343 " AND #{Project.table_name}.status = #{Project::STATUS_ACTIVE}" +
344 344 " AND #{Issue.table_name}.due_date <= ?", days.day.from_now.to_date
345 345 )
346 346 scope = scope.where(:assigned_to_id => user_ids) if user_ids.present?
347 347 scope = scope.where(:project_id => project.id) if project
348 348 scope = scope.where(:fixed_version_id => target_version_id) if target_version_id.present?
349 349 scope = scope.where(:tracker_id => tracker.id) if tracker
350 350 issues_by_assignee = scope.includes(:status, :assigned_to, :project, :tracker).
351 351 group_by(&:assigned_to)
352 352 issues_by_assignee.keys.each do |assignee|
353 353 if assignee.is_a?(Group)
354 354 assignee.users.each do |user|
355 355 issues_by_assignee[user] ||= []
356 356 issues_by_assignee[user] += issues_by_assignee[assignee]
357 357 end
358 358 end
359 359 end
360 360
361 361 issues_by_assignee.each do |assignee, issues|
362 362 reminder(assignee, issues, days).deliver if assignee.is_a?(User) && assignee.active?
363 363 end
364 364 end
365 365
366 366 # Activates/desactivates email deliveries during +block+
367 367 def self.with_deliveries(enabled = true, &block)
368 368 was_enabled = ActionMailer::Base.perform_deliveries
369 369 ActionMailer::Base.perform_deliveries = !!enabled
370 370 yield
371 371 ensure
372 372 ActionMailer::Base.perform_deliveries = was_enabled
373 373 end
374 374
375 375 # Sends emails synchronously in the given block
376 376 def self.with_synched_deliveries(&block)
377 377 saved_method = ActionMailer::Base.delivery_method
378 378 if m = saved_method.to_s.match(%r{^async_(.+)$})
379 379 synched_method = m[1]
380 380 ActionMailer::Base.delivery_method = synched_method.to_sym
381 381 ActionMailer::Base.send "#{synched_method}_settings=", ActionMailer::Base.send("async_#{synched_method}_settings")
382 382 end
383 383 yield
384 384 ensure
385 385 ActionMailer::Base.delivery_method = saved_method
386 386 end
387 387
388 388 def mail(headers={}, &block)
389 389 headers.reverse_merge! 'X-Mailer' => 'Redmine',
390 390 'X-Redmine-Host' => Setting.host_name,
391 391 'X-Redmine-Site' => Setting.app_title,
392 'X-Auto-Response-Suppress' => 'OOF',
392 'X-Auto-Response-Suppress' => 'All',
393 393 'Auto-Submitted' => 'auto-generated',
394 394 'From' => Setting.mail_from,
395 395 'List-Id' => "<#{Setting.mail_from.to_s.gsub('@', '.')}>"
396 396
397 397 # Replaces users with their email addresses
398 398 [:to, :cc, :bcc].each do |key|
399 399 if headers[key].present?
400 400 headers[key] = self.class.email_addresses(headers[key])
401 401 end
402 402 end
403 403
404 404 # Removes the author from the recipients and cc
405 405 # if the author does not want to receive notifications
406 406 # about what the author do
407 407 if @author && @author.logged? && @author.pref.no_self_notified
408 408 addresses = @author.mails
409 409 headers[:to] -= addresses if headers[:to].is_a?(Array)
410 410 headers[:cc] -= addresses if headers[:cc].is_a?(Array)
411 411 end
412 412
413 413 if @author && @author.logged?
414 414 redmine_headers 'Sender' => @author.login
415 415 end
416 416
417 417 # Blind carbon copy recipients
418 418 if Setting.bcc_recipients?
419 419 headers[:bcc] = [headers[:to], headers[:cc]].flatten.uniq.reject(&:blank?)
420 420 headers[:to] = nil
421 421 headers[:cc] = nil
422 422 end
423 423
424 424 if @message_id_object
425 425 headers[:message_id] = "<#{self.class.message_id_for(@message_id_object)}>"
426 426 end
427 427 if @references_objects
428 428 headers[:references] = @references_objects.collect {|o| "<#{self.class.references_for(o)}>"}.join(' ')
429 429 end
430 430
431 431 m = if block_given?
432 432 super headers, &block
433 433 else
434 434 super headers do |format|
435 435 format.text
436 436 format.html unless Setting.plain_text_mail?
437 437 end
438 438 end
439 439 set_language_if_valid @initial_language
440 440
441 441 m
442 442 end
443 443
444 444 def initialize(*args)
445 445 @initial_language = current_language
446 446 set_language_if_valid Setting.default_language
447 447 super
448 448 end
449 449
450 450 def self.deliver_mail(mail)
451 451 return false if mail.to.blank? && mail.cc.blank? && mail.bcc.blank?
452 452 begin
453 453 # Log errors when raise_delivery_errors is set to false, Rails does not
454 454 mail.raise_delivery_errors = true
455 455 super
456 456 rescue Exception => e
457 457 if ActionMailer::Base.raise_delivery_errors
458 458 raise e
459 459 else
460 460 Rails.logger.error "Email delivery error: #{e.message}"
461 461 end
462 462 end
463 463 end
464 464
465 465 def self.method_missing(method, *args, &block)
466 466 if m = method.to_s.match(%r{^deliver_(.+)$})
467 467 ActiveSupport::Deprecation.warn "Mailer.deliver_#{m[1]}(*args) is deprecated. Use Mailer.#{m[1]}(*args).deliver instead."
468 468 send(m[1], *args).deliver
469 469 else
470 470 super
471 471 end
472 472 end
473 473
474 474 # Returns an array of email addresses to notify by
475 475 # replacing users in arg with their notified email addresses
476 476 #
477 477 # Example:
478 478 # Mailer.email_addresses(users)
479 479 # => ["foo@example.net", "bar@example.net"]
480 480 def self.email_addresses(arg)
481 481 arr = Array.wrap(arg)
482 482 mails = arr.reject {|a| a.is_a? Principal}
483 483 users = arr - mails
484 484 if users.any?
485 485 mails += EmailAddress.
486 486 where(:user_id => users.map(&:id)).
487 487 where("is_default = ? OR notify = ?", true, true).
488 488 pluck(:address)
489 489 end
490 490 mails
491 491 end
492 492
493 493 private
494 494
495 495 # Appends a Redmine header field (name is prepended with 'X-Redmine-')
496 496 def redmine_headers(h)
497 497 h.each { |k,v| headers["X-Redmine-#{k}"] = v.to_s }
498 498 end
499 499
500 500 def self.token_for(object, rand=true)
501 501 timestamp = object.send(object.respond_to?(:created_on) ? :created_on : :updated_on)
502 502 hash = [
503 503 "redmine",
504 504 "#{object.class.name.demodulize.underscore}-#{object.id}",
505 505 timestamp.strftime("%Y%m%d%H%M%S")
506 506 ]
507 507 if rand
508 508 hash << Redmine::Utils.random_hex(8)
509 509 end
510 510 host = Setting.mail_from.to_s.strip.gsub(%r{^.*@|>}, '')
511 511 host = "#{::Socket.gethostname}.redmine" if host.empty?
512 512 "#{hash.join('.')}@#{host}"
513 513 end
514 514
515 515 # Returns a Message-Id for the given object
516 516 def self.message_id_for(object)
517 517 token_for(object, true)
518 518 end
519 519
520 520 # Returns a uniq token for a given object referenced by all notifications
521 521 # related to this object
522 522 def self.references_for(object)
523 523 token_for(object, false)
524 524 end
525 525
526 526 def message_id(object)
527 527 @message_id_object = object
528 528 end
529 529
530 530 def references(object)
531 531 @references_objects ||= []
532 532 @references_objects << object
533 533 end
534 534
535 535 def mylogger
536 536 Rails.logger
537 537 end
538 538 end
@@ -1,971 +1,970
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2015 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 :email_addresses,
26 26 :issues, :issue_statuses,
27 27 :workflows, :trackers, :projects_trackers,
28 28 :versions, :enumerations, :issue_categories,
29 29 :custom_fields, :custom_fields_trackers, :custom_fields_projects,
30 30 :boards, :messages
31 31
32 32 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
33 33
34 34 def setup
35 35 ActionMailer::Base.deliveries.clear
36 36 Setting.notified_events = Redmine::Notifiable.all.collect(&:name)
37 37 end
38 38
39 39 def teardown
40 40 Setting.clear_cache
41 41 end
42 42
43 43 def test_add_issue
44 44 ActionMailer::Base.deliveries.clear
45 45 lft1 = new_issue_lft
46 46 # This email contains: 'Project: onlinestore'
47 47 issue = submit_email('ticket_on_given_project.eml')
48 48 assert issue.is_a?(Issue)
49 49 assert !issue.new_record?
50 50 issue.reload
51 51 assert_equal Project.find(2), issue.project
52 52 assert_equal issue.project.trackers.first, issue.tracker
53 53 assert_equal 'New ticket on a given project', issue.subject
54 54 assert_equal User.find_by_login('jsmith'), issue.author
55 55 assert_equal IssueStatus.find_by_name('Resolved'), issue.status
56 56 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
57 57 assert_equal '2010-01-01', issue.start_date.to_s
58 58 assert_equal '2010-12-31', issue.due_date.to_s
59 59 assert_equal User.find_by_login('jsmith'), issue.assigned_to
60 60 assert_equal Version.find_by_name('Alpha'), issue.fixed_version
61 61 assert_equal 2.5, issue.estimated_hours
62 62 assert_equal 30, issue.done_ratio
63 63 assert_equal [issue.id, lft1, lft1 + 1], [issue.root_id, issue.lft, issue.rgt]
64 64 # keywords should be removed from the email body
65 65 assert !issue.description.match(/^Project:/i)
66 66 assert !issue.description.match(/^Status:/i)
67 67 assert !issue.description.match(/^Start Date:/i)
68 68 # Email notification should be sent
69 69 mail = ActionMailer::Base.deliveries.last
70 70 assert_not_nil mail
71 71 assert mail.subject.include?("##{issue.id}")
72 72 assert mail.subject.include?('New ticket on a given project')
73 73 end
74 74
75 75 def test_add_issue_with_default_tracker
76 76 # This email contains: 'Project: onlinestore'
77 77 issue = submit_email(
78 78 'ticket_on_given_project.eml',
79 79 :issue => {:tracker => 'Support request'}
80 80 )
81 81 assert issue.is_a?(Issue)
82 82 assert !issue.new_record?
83 83 issue.reload
84 84 assert_equal 'Support request', issue.tracker.name
85 85 end
86 86
87 87 def test_add_issue_with_status
88 88 # This email contains: 'Project: onlinestore' and 'Status: Resolved'
89 89 issue = submit_email('ticket_on_given_project.eml')
90 90 assert issue.is_a?(Issue)
91 91 assert !issue.new_record?
92 92 issue.reload
93 93 assert_equal Project.find(2), issue.project
94 94 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
95 95 end
96 96
97 97 def test_add_issue_with_attributes_override
98 98 issue = submit_email(
99 99 'ticket_with_attributes.eml',
100 100 :allow_override => 'tracker,category,priority'
101 101 )
102 102 assert issue.is_a?(Issue)
103 103 assert !issue.new_record?
104 104 issue.reload
105 105 assert_equal 'New ticket on a given project', issue.subject
106 106 assert_equal User.find_by_login('jsmith'), issue.author
107 107 assert_equal Project.find(2), issue.project
108 108 assert_equal 'Feature request', issue.tracker.to_s
109 109 assert_equal 'Stock management', issue.category.to_s
110 110 assert_equal 'Urgent', issue.priority.to_s
111 111 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
112 112 end
113 113
114 114 def test_add_issue_with_group_assignment
115 115 with_settings :issue_group_assignment => '1' do
116 116 issue = submit_email('ticket_on_given_project.eml') do |email|
117 117 email.gsub!('Assigned to: John Smith', 'Assigned to: B Team')
118 118 end
119 119 assert issue.is_a?(Issue)
120 120 assert !issue.new_record?
121 121 issue.reload
122 122 assert_equal Group.find(11), issue.assigned_to
123 123 end
124 124 end
125 125
126 126 def test_add_issue_with_partial_attributes_override
127 127 issue = submit_email(
128 128 'ticket_with_attributes.eml',
129 129 :issue => {:priority => 'High'},
130 130 :allow_override => ['tracker']
131 131 )
132 132 assert issue.is_a?(Issue)
133 133 assert !issue.new_record?
134 134 issue.reload
135 135 assert_equal 'New ticket on a given project', issue.subject
136 136 assert_equal User.find_by_login('jsmith'), issue.author
137 137 assert_equal Project.find(2), issue.project
138 138 assert_equal 'Feature request', issue.tracker.to_s
139 139 assert_nil issue.category
140 140 assert_equal 'High', issue.priority.to_s
141 141 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
142 142 end
143 143
144 144 def test_add_issue_with_spaces_between_attribute_and_separator
145 145 issue = submit_email(
146 146 'ticket_with_spaces_between_attribute_and_separator.eml',
147 147 :allow_override => 'tracker,category,priority'
148 148 )
149 149 assert issue.is_a?(Issue)
150 150 assert !issue.new_record?
151 151 issue.reload
152 152 assert_equal 'New ticket on a given project', issue.subject
153 153 assert_equal User.find_by_login('jsmith'), issue.author
154 154 assert_equal Project.find(2), issue.project
155 155 assert_equal 'Feature request', issue.tracker.to_s
156 156 assert_equal 'Stock management', issue.category.to_s
157 157 assert_equal 'Urgent', issue.priority.to_s
158 158 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
159 159 end
160 160
161 161 def test_add_issue_with_attachment_to_specific_project
162 162 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
163 163 assert issue.is_a?(Issue)
164 164 assert !issue.new_record?
165 165 issue.reload
166 166 assert_equal 'Ticket created by email with attachment', issue.subject
167 167 assert_equal User.find_by_login('jsmith'), issue.author
168 168 assert_equal Project.find(2), issue.project
169 169 assert_equal 'This is a new ticket with attachments', issue.description
170 170 # Attachment properties
171 171 assert_equal 1, issue.attachments.size
172 172 assert_equal 'Paella.jpg', issue.attachments.first.filename
173 173 assert_equal 'image/jpeg', issue.attachments.first.content_type
174 174 assert_equal 10790, issue.attachments.first.filesize
175 175 end
176 176
177 177 def test_add_issue_with_custom_fields
178 178 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'onlinestore'})
179 179 assert issue.is_a?(Issue)
180 180 assert !issue.new_record?
181 181 issue.reload
182 182 assert_equal 'New ticket with custom field values', issue.subject
183 183 assert_equal 'PostgreSQL', issue.custom_field_value(1)
184 184 assert_equal 'Value for a custom field', issue.custom_field_value(2)
185 185 assert !issue.description.match(/^searchable field:/i)
186 186 end
187 187
188 188 def test_add_issue_with_version_custom_fields
189 189 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true, :tracker_ids => [1,2,3])
190 190
191 191 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'ecookbook'}) do |email|
192 192 email << "Affected version: 1.0\n"
193 193 end
194 194 assert issue.is_a?(Issue)
195 195 assert !issue.new_record?
196 196 issue.reload
197 197 assert_equal '2', issue.custom_field_value(field)
198 198 end
199 199
200 200 def test_add_issue_should_match_assignee_on_display_name
201 201 user = User.generate!(:firstname => 'Foo Bar', :lastname => 'Foo Baz')
202 202 User.add_to_project(user, Project.find(2))
203 203 issue = submit_email('ticket_on_given_project.eml') do |email|
204 204 email.sub!(/^Assigned to.*$/, 'Assigned to: Foo Bar Foo baz')
205 205 end
206 206 assert issue.is_a?(Issue)
207 207 assert_equal user, issue.assigned_to
208 208 end
209 209
210 210 def test_add_issue_should_set_default_start_date
211 211 with_settings :default_issue_start_date_to_creation_date => '1' do
212 212 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
213 213 assert issue.is_a?(Issue)
214 214 assert_equal Date.today, issue.start_date
215 215 end
216 216 end
217 217
218 218 def test_add_issue_with_cc
219 219 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
220 220 assert issue.is_a?(Issue)
221 221 assert !issue.new_record?
222 222 issue.reload
223 223 assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
224 224 assert_equal 1, issue.watcher_user_ids.size
225 225 end
226 226
227 227 def test_add_issue_from_additional_email_address
228 228 user = User.find(2)
229 229 user.mail = 'mainaddress@somenet.foo'
230 230 user.save!
231 231 EmailAddress.create!(:user => user, :address => 'jsmith@somenet.foo')
232 232
233 233 issue = submit_email('ticket_on_given_project.eml')
234 234 assert issue
235 235 assert_equal user, issue.author
236 236 end
237 237
238 238 def test_add_issue_by_unknown_user
239 239 assert_no_difference 'User.count' do
240 240 assert_equal false,
241 241 submit_email(
242 242 'ticket_by_unknown_user.eml',
243 243 :issue => {:project => 'ecookbook'}
244 244 )
245 245 end
246 246 end
247 247
248 248 def test_add_issue_by_anonymous_user
249 249 Role.anonymous.add_permission!(:add_issues)
250 250 assert_no_difference 'User.count' do
251 251 issue = submit_email(
252 252 'ticket_by_unknown_user.eml',
253 253 :issue => {:project => 'ecookbook'},
254 254 :unknown_user => 'accept'
255 255 )
256 256 assert issue.is_a?(Issue)
257 257 assert issue.author.anonymous?
258 258 end
259 259 end
260 260
261 261 def test_add_issue_by_anonymous_user_with_no_from_address
262 262 Role.anonymous.add_permission!(:add_issues)
263 263 assert_no_difference 'User.count' do
264 264 issue = submit_email(
265 265 'ticket_by_empty_user.eml',
266 266 :issue => {:project => 'ecookbook'},
267 267 :unknown_user => 'accept'
268 268 )
269 269 assert issue.is_a?(Issue)
270 270 assert issue.author.anonymous?
271 271 end
272 272 end
273 273
274 274 def test_add_issue_by_anonymous_user_on_private_project
275 275 Role.anonymous.add_permission!(:add_issues)
276 276 assert_no_difference 'User.count' do
277 277 assert_no_difference 'Issue.count' do
278 278 assert_equal false,
279 279 submit_email(
280 280 'ticket_by_unknown_user.eml',
281 281 :issue => {:project => 'onlinestore'},
282 282 :unknown_user => 'accept'
283 283 )
284 284 end
285 285 end
286 286 end
287 287
288 288 def test_add_issue_by_anonymous_user_on_private_project_without_permission_check
289 289 lft1 = new_issue_lft
290 290 assert_no_difference 'User.count' do
291 291 assert_difference 'Issue.count' do
292 292 issue = submit_email(
293 293 'ticket_by_unknown_user.eml',
294 294 :issue => {:project => 'onlinestore'},
295 295 :no_permission_check => '1',
296 296 :unknown_user => 'accept'
297 297 )
298 298 assert issue.is_a?(Issue)
299 299 assert issue.author.anonymous?
300 300 assert !issue.project.is_public?
301 301 assert_equal [issue.id, lft1, lft1 + 1], [issue.root_id, issue.lft, issue.rgt]
302 302 end
303 303 end
304 304 end
305 305
306 306 def test_add_issue_by_created_user
307 307 Setting.default_language = 'en'
308 308 assert_difference 'User.count' do
309 309 issue = submit_email(
310 310 'ticket_by_unknown_user.eml',
311 311 :issue => {:project => 'ecookbook'},
312 312 :unknown_user => 'create'
313 313 )
314 314 assert issue.is_a?(Issue)
315 315 assert issue.author.active?
316 316 assert_equal 'john.doe@somenet.foo', issue.author.mail
317 317 assert_equal 'John', issue.author.firstname
318 318 assert_equal 'Doe', issue.author.lastname
319 319
320 320 # account information
321 321 email = ActionMailer::Base.deliveries.first
322 322 assert_not_nil email
323 323 assert email.subject.include?('account activation')
324 324 login = mail_body(email).match(/\* Login: (.*)$/)[1].strip
325 325 password = mail_body(email).match(/\* Password: (.*)$/)[1].strip
326 326 assert_equal issue.author, User.try_to_login(login, password)
327 327 end
328 328 end
329 329
330 330 def test_created_user_should_be_added_to_groups
331 331 group1 = Group.generate!
332 332 group2 = Group.generate!
333 333
334 334 assert_difference 'User.count' do
335 335 submit_email(
336 336 'ticket_by_unknown_user.eml',
337 337 :issue => {:project => 'ecookbook'},
338 338 :unknown_user => 'create',
339 339 :default_group => "#{group1.name},#{group2.name}"
340 340 )
341 341 end
342 342 user = User.order('id DESC').first
343 343 assert_equal [group1, group2].sort, user.groups.sort
344 344 end
345 345
346 346 def test_created_user_should_not_receive_account_information_with_no_account_info_option
347 347 assert_difference 'User.count' do
348 348 submit_email(
349 349 'ticket_by_unknown_user.eml',
350 350 :issue => {:project => 'ecookbook'},
351 351 :unknown_user => 'create',
352 352 :no_account_notice => '1'
353 353 )
354 354 end
355 355
356 356 # only 1 email for the new issue notification
357 357 assert_equal 1, ActionMailer::Base.deliveries.size
358 358 email = ActionMailer::Base.deliveries.first
359 359 assert_include 'Ticket by unknown user', email.subject
360 360 end
361 361
362 362 def test_created_user_should_have_mail_notification_to_none_with_no_notification_option
363 363 assert_difference 'User.count' do
364 364 submit_email(
365 365 'ticket_by_unknown_user.eml',
366 366 :issue => {:project => 'ecookbook'},
367 367 :unknown_user => 'create',
368 368 :no_notification => '1'
369 369 )
370 370 end
371 371 user = User.order('id DESC').first
372 372 assert_equal 'none', user.mail_notification
373 373 end
374 374
375 375 def test_add_issue_without_from_header
376 376 Role.anonymous.add_permission!(:add_issues)
377 377 assert_equal false, submit_email('ticket_without_from_header.eml')
378 378 end
379 379
380 380 def test_add_issue_with_invalid_attributes
381 381 with_settings :default_issue_start_date_to_creation_date => '0' do
382 382 issue = submit_email(
383 383 'ticket_with_invalid_attributes.eml',
384 384 :allow_override => 'tracker,category,priority'
385 385 )
386 386 assert issue.is_a?(Issue)
387 387 assert !issue.new_record?
388 388 issue.reload
389 389 assert_nil issue.assigned_to
390 390 assert_nil issue.start_date
391 391 assert_nil issue.due_date
392 392 assert_equal 0, issue.done_ratio
393 393 assert_equal 'Normal', issue.priority.to_s
394 394 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
395 395 end
396 396 end
397 397
398 398 def test_add_issue_with_invalid_project_should_be_assigned_to_default_project
399 399 issue = submit_email('ticket_on_given_project.eml', :issue => {:project => 'ecookbook'}, :allow_override => 'project') do |email|
400 400 email.gsub!(/^Project:.+$/, 'Project: invalid')
401 401 end
402 402 assert issue.is_a?(Issue)
403 403 assert !issue.new_record?
404 404 assert_equal 'ecookbook', issue.project.identifier
405 405 end
406 406
407 407 def test_add_issue_with_localized_attributes
408 408 User.find_by_mail('jsmith@somenet.foo').update_attribute 'language', 'fr'
409 409 issue = submit_email(
410 410 'ticket_with_localized_attributes.eml',
411 411 :allow_override => 'tracker,category,priority'
412 412 )
413 413 assert issue.is_a?(Issue)
414 414 assert !issue.new_record?
415 415 issue.reload
416 416 assert_equal 'New ticket on a given project', issue.subject
417 417 assert_equal User.find_by_login('jsmith'), issue.author
418 418 assert_equal Project.find(2), issue.project
419 419 assert_equal 'Feature request', issue.tracker.to_s
420 420 assert_equal 'Stock management', issue.category.to_s
421 421 assert_equal 'Urgent', issue.priority.to_s
422 422 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
423 423 end
424 424
425 425 def test_add_issue_with_japanese_keywords
426 426 ja_dev = "\xe9\x96\x8b\xe7\x99\xba".force_encoding('UTF-8')
427 427 tracker = Tracker.generate!(:name => ja_dev)
428 428 Project.find(1).trackers << tracker
429 429 issue = submit_email(
430 430 'japanese_keywords_iso_2022_jp.eml',
431 431 :issue => {:project => 'ecookbook'},
432 432 :allow_override => 'tracker'
433 433 )
434 434 assert_kind_of Issue, issue
435 435 assert_equal tracker, issue.tracker
436 436 end
437 437
438 438 def test_add_issue_from_apple_mail
439 439 issue = submit_email(
440 440 'apple_mail_with_attachment.eml',
441 441 :issue => {:project => 'ecookbook'}
442 442 )
443 443 assert_kind_of Issue, issue
444 444 assert_equal 1, issue.attachments.size
445 445
446 446 attachment = issue.attachments.first
447 447 assert_equal 'paella.jpg', attachment.filename
448 448 assert_equal 10790, attachment.filesize
449 449 assert File.exist?(attachment.diskfile)
450 450 assert_equal 10790, File.size(attachment.diskfile)
451 451 assert_equal 'caaf384198bcbc9563ab5c058acd73cd', attachment.digest
452 452 end
453 453
454 454 def test_thunderbird_with_attachment_ja
455 455 issue = submit_email(
456 456 'thunderbird_with_attachment_ja.eml',
457 457 :issue => {:project => 'ecookbook'}
458 458 )
459 459 assert_kind_of Issue, issue
460 460 assert_equal 1, issue.attachments.size
461 461 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt".force_encoding('UTF-8')
462 462 attachment = issue.attachments.first
463 463 assert_equal ja, attachment.filename
464 464 assert_equal 5, attachment.filesize
465 465 assert File.exist?(attachment.diskfile)
466 466 assert_equal 5, File.size(attachment.diskfile)
467 467 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
468 468 end
469 469
470 470 def test_gmail_with_attachment_ja
471 471 issue = submit_email(
472 472 'gmail_with_attachment_ja.eml',
473 473 :issue => {:project => 'ecookbook'}
474 474 )
475 475 assert_kind_of Issue, issue
476 476 assert_equal 1, issue.attachments.size
477 477 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt".force_encoding('UTF-8')
478 478 attachment = issue.attachments.first
479 479 assert_equal ja, attachment.filename
480 480 assert_equal 5, attachment.filesize
481 481 assert File.exist?(attachment.diskfile)
482 482 assert_equal 5, File.size(attachment.diskfile)
483 483 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
484 484 end
485 485
486 486 def test_thunderbird_with_attachment_latin1
487 487 issue = submit_email(
488 488 'thunderbird_with_attachment_iso-8859-1.eml',
489 489 :issue => {:project => 'ecookbook'}
490 490 )
491 491 assert_kind_of Issue, issue
492 492 assert_equal 1, issue.attachments.size
493 493 u = "".force_encoding('UTF-8')
494 494 u1 = "\xc3\x84\xc3\xa4\xc3\x96\xc3\xb6\xc3\x9c\xc3\xbc".force_encoding('UTF-8')
495 495 11.times { u << u1 }
496 496 attachment = issue.attachments.first
497 497 assert_equal "#{u}.png", attachment.filename
498 498 assert_equal 130, attachment.filesize
499 499 assert File.exist?(attachment.diskfile)
500 500 assert_equal 130, File.size(attachment.diskfile)
501 501 assert_equal '4d80e667ac37dddfe05502530f152abb', attachment.digest
502 502 end
503 503
504 504 def test_gmail_with_attachment_latin1
505 505 issue = submit_email(
506 506 'gmail_with_attachment_iso-8859-1.eml',
507 507 :issue => {:project => 'ecookbook'}
508 508 )
509 509 assert_kind_of Issue, issue
510 510 assert_equal 1, issue.attachments.size
511 511 u = "".force_encoding('UTF-8')
512 512 u1 = "\xc3\x84\xc3\xa4\xc3\x96\xc3\xb6\xc3\x9c\xc3\xbc".force_encoding('UTF-8')
513 513 11.times { u << u1 }
514 514 attachment = issue.attachments.first
515 515 assert_equal "#{u}.txt", attachment.filename
516 516 assert_equal 5, attachment.filesize
517 517 assert File.exist?(attachment.diskfile)
518 518 assert_equal 5, File.size(attachment.diskfile)
519 519 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
520 520 end
521 521
522 522 def test_multiple_inline_text_parts_should_be_appended_to_issue_description
523 523 issue = submit_email('multiple_text_parts.eml', :issue => {:project => 'ecookbook'})
524 524 assert_include 'first', issue.description
525 525 assert_include 'second', issue.description
526 526 assert_include 'third', issue.description
527 527 end
528 528
529 529 def test_attachment_text_part_should_be_added_as_issue_attachment
530 530 issue = submit_email('multiple_text_parts.eml', :issue => {:project => 'ecookbook'})
531 531 assert_not_include 'Plain text attachment', issue.description
532 532 attachment = issue.attachments.detect {|a| a.filename == 'textfile.txt'}
533 533 assert_not_nil attachment
534 534 assert_include 'Plain text attachment', File.read(attachment.diskfile)
535 535 end
536 536
537 537 def test_add_issue_with_iso_8859_1_subject
538 538 issue = submit_email(
539 539 'subject_as_iso-8859-1.eml',
540 540 :issue => {:project => 'ecookbook'}
541 541 )
542 542 str = "Testmail from Webmail: \xc3\xa4 \xc3\xb6 \xc3\xbc...".force_encoding('UTF-8')
543 543 assert_kind_of Issue, issue
544 544 assert_equal str, issue.subject
545 545 end
546 546
547 547 def test_quoted_printable_utf8
548 548 issue = submit_email(
549 549 'quoted_printable_utf8.eml',
550 550 :issue => {:project => 'ecookbook'}
551 551 )
552 552 assert_kind_of Issue, issue
553 553 str = "Freundliche Gr\xc3\xbcsse".force_encoding('UTF-8')
554 554 assert_equal str, issue.description
555 555 end
556 556
557 557 def test_gmail_iso8859_2
558 558 issue = submit_email(
559 559 'gmail-iso8859-2.eml',
560 560 :issue => {:project => 'ecookbook'}
561 561 )
562 562 assert_kind_of Issue, issue
563 563 str = "Na \xc5\xa1triku se su\xc5\xa1i \xc5\xa1osi\xc4\x87.".force_encoding('UTF-8')
564 564 assert issue.description.include?(str)
565 565 end
566 566
567 567 def test_add_issue_with_japanese_subject
568 568 issue = submit_email(
569 569 'subject_japanese_1.eml',
570 570 :issue => {:project => 'ecookbook'}
571 571 )
572 572 assert_kind_of Issue, issue
573 573 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88".force_encoding('UTF-8')
574 574 assert_equal ja, issue.subject
575 575 end
576 576
577 577 def test_add_issue_with_korean_body
578 578 # Make sure mail bodies with a charset unknown to Ruby
579 579 # but known to the Mail gem 2.5.4 are handled correctly
580 580 kr = "\xEA\xB3\xA0\xEB\xA7\x99\xEC\x8A\xB5\xEB\x8B\x88\xEB\x8B\xA4.".force_encoding('UTF-8')
581 581 issue = submit_email(
582 582 'body_ks_c_5601-1987.eml',
583 583 :issue => {:project => 'ecookbook'}
584 584 )
585 585 assert_kind_of Issue, issue
586 586 assert_equal kr, issue.description
587 587 end
588 588
589 589 def test_add_issue_with_no_subject_header
590 590 issue = submit_email(
591 591 'no_subject_header.eml',
592 592 :issue => {:project => 'ecookbook'}
593 593 )
594 594 assert_kind_of Issue, issue
595 595 assert_equal '(no subject)', issue.subject
596 596 end
597 597
598 598 def test_add_issue_with_mixed_japanese_subject
599 599 issue = submit_email(
600 600 'subject_japanese_2.eml',
601 601 :issue => {:project => 'ecookbook'}
602 602 )
603 603 assert_kind_of Issue, issue
604 604 ja = "Re: \xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88".force_encoding('UTF-8')
605 605 assert_equal ja, issue.subject
606 606 end
607 607
608 608 def test_should_ignore_emails_from_locked_users
609 609 User.find(2).lock!
610 610
611 611 MailHandler.any_instance.expects(:dispatch).never
612 612 assert_no_difference 'Issue.count' do
613 613 assert_equal false, submit_email('ticket_on_given_project.eml')
614 614 end
615 615 end
616 616
617 617 def test_should_ignore_emails_from_emission_address
618 618 Role.anonymous.add_permission!(:add_issues)
619 619 assert_no_difference 'User.count' do
620 620 assert_equal false,
621 621 submit_email(
622 622 'ticket_from_emission_address.eml',
623 623 :issue => {:project => 'ecookbook'},
624 624 :unknown_user => 'create'
625 625 )
626 626 end
627 627 end
628 628
629 629 def test_should_ignore_auto_replied_emails
630 630 MailHandler.any_instance.expects(:dispatch).never
631 631 [
632 "X-Auto-Response-Suppress: OOF",
633 632 "Auto-Submitted: auto-replied",
634 633 "Auto-Submitted: Auto-Replied",
635 634 "Auto-Submitted: auto-generated",
636 635 'X-Autoreply: yes'
637 636 ].each do |header|
638 637 raw = IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
639 638 raw = header + "\n" + raw
640 639
641 640 assert_no_difference 'Issue.count' do
642 641 assert_equal false, MailHandler.receive(raw), "email with #{header} header was not ignored"
643 642 end
644 643 end
645 644 end
646 645
647 646 test "should not ignore Auto-Submitted headers not defined in RFC3834" do
648 647 [
649 648 "Auto-Submitted: auto-forwarded"
650 649 ].each do |header|
651 650 raw = IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
652 651 raw = header + "\n" + raw
653 652
654 653 assert_difference 'Issue.count', 1 do
655 654 assert_not_nil MailHandler.receive(raw), "email with #{header} header was ignored"
656 655 end
657 656 end
658 657 end
659 658
660 659 def test_add_issue_should_send_email_notification
661 660 Setting.notified_events = ['issue_added']
662 661 ActionMailer::Base.deliveries.clear
663 662 # This email contains: 'Project: onlinestore'
664 663 issue = submit_email('ticket_on_given_project.eml')
665 664 assert issue.is_a?(Issue)
666 665 assert_equal 1, ActionMailer::Base.deliveries.size
667 666 end
668 667
669 668 def test_update_issue
670 669 journal = submit_email('ticket_reply.eml')
671 670 assert journal.is_a?(Journal)
672 671 assert_equal User.find_by_login('jsmith'), journal.user
673 672 assert_equal Issue.find(2), journal.journalized
674 673 assert_match /This is reply/, journal.notes
675 674 assert_equal false, journal.private_notes
676 675 assert_equal 'Feature request', journal.issue.tracker.name
677 676 end
678 677
679 678 def test_update_issue_should_accept_issue_id_after_space_inside_brackets
680 679 journal = submit_email('ticket_reply_with_status.eml') do |email|
681 680 assert email.sub!(/^Subject:.*$/, "Subject: Re: [Feature request #2] Add ingredients categories")
682 681 end
683 682 assert journal.is_a?(Journal)
684 683 assert_equal Issue.find(2), journal.journalized
685 684 end
686 685
687 686 def test_update_issue_should_accept_issue_id_inside_brackets
688 687 journal = submit_email('ticket_reply_with_status.eml') do |email|
689 688 assert email.sub!(/^Subject:.*$/, "Subject: Re: [#2] Add ingredients categories")
690 689 end
691 690 assert journal.is_a?(Journal)
692 691 assert_equal Issue.find(2), journal.journalized
693 692 end
694 693
695 694 def test_update_issue_should_ignore_bogus_issue_ids_in_subject
696 695 journal = submit_email('ticket_reply_with_status.eml') do |email|
697 696 assert email.sub!(/^Subject:.*$/, "Subject: Re: [12345#1][bogus#1][Feature request #2] Add ingredients categories")
698 697 end
699 698 assert journal.is_a?(Journal)
700 699 assert_equal Issue.find(2), journal.journalized
701 700 end
702 701
703 702 def test_update_issue_with_attribute_changes
704 703 # This email contains: 'Status: Resolved'
705 704 journal = submit_email('ticket_reply_with_status.eml')
706 705 assert journal.is_a?(Journal)
707 706 issue = Issue.find(journal.issue.id)
708 707 assert_equal User.find_by_login('jsmith'), journal.user
709 708 assert_equal Issue.find(2), journal.journalized
710 709 assert_match /This is reply/, journal.notes
711 710 assert_equal 'Feature request', journal.issue.tracker.name
712 711 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
713 712 assert_equal '2010-01-01', issue.start_date.to_s
714 713 assert_equal '2010-12-31', issue.due_date.to_s
715 714 assert_equal User.find_by_login('jsmith'), issue.assigned_to
716 715 assert_equal "52.6", issue.custom_value_for(CustomField.find_by_name('Float field')).value
717 716 # keywords should be removed from the email body
718 717 assert !journal.notes.match(/^Status:/i)
719 718 assert !journal.notes.match(/^Start Date:/i)
720 719 end
721 720
722 721 def test_update_issue_with_attachment
723 722 assert_difference 'Journal.count' do
724 723 assert_difference 'JournalDetail.count' do
725 724 assert_difference 'Attachment.count' do
726 725 assert_no_difference 'Issue.count' do
727 726 journal = submit_email('ticket_with_attachment.eml') do |raw|
728 727 raw.gsub! /^Subject: .*$/, 'Subject: Re: [Cookbook - Feature #2] (New) Add ingredients categories'
729 728 end
730 729 end
731 730 end
732 731 end
733 732 end
734 733 journal = Journal.order('id DESC').first
735 734 assert_equal Issue.find(2), journal.journalized
736 735 assert_equal 1, journal.details.size
737 736
738 737 detail = journal.details.first
739 738 assert_equal 'attachment', detail.property
740 739 assert_equal 'Paella.jpg', detail.value
741 740 end
742 741
743 742 def test_update_issue_should_send_email_notification
744 743 ActionMailer::Base.deliveries.clear
745 744 journal = submit_email('ticket_reply.eml')
746 745 assert journal.is_a?(Journal)
747 746 assert_equal 1, ActionMailer::Base.deliveries.size
748 747 end
749 748
750 749 def test_update_issue_should_not_set_defaults
751 750 journal = submit_email(
752 751 'ticket_reply.eml',
753 752 :issue => {:tracker => 'Support request', :priority => 'High'}
754 753 )
755 754 assert journal.is_a?(Journal)
756 755 assert_match /This is reply/, journal.notes
757 756 assert_equal 'Feature request', journal.issue.tracker.name
758 757 assert_equal 'Normal', journal.issue.priority.name
759 758 end
760 759
761 760 def test_replying_to_a_private_note_should_add_reply_as_private
762 761 private_journal = Journal.create!(:notes => 'Private notes', :journalized => Issue.find(1), :private_notes => true, :user_id => 2)
763 762
764 763 assert_difference 'Journal.count' do
765 764 journal = submit_email('ticket_reply.eml') do |email|
766 765 email.sub! %r{^In-Reply-To:.*$}, "In-Reply-To: <redmine.journal-#{private_journal.id}.20060719210421@osiris>"
767 766 end
768 767
769 768 assert_kind_of Journal, journal
770 769 assert_match /This is reply/, journal.notes
771 770 assert_equal true, journal.private_notes
772 771 end
773 772 end
774 773
775 774 def test_reply_to_a_message
776 775 m = submit_email('message_reply.eml')
777 776 assert m.is_a?(Message)
778 777 assert !m.new_record?
779 778 m.reload
780 779 assert_equal 'Reply via email', m.subject
781 780 # The email replies to message #2 which is part of the thread of message #1
782 781 assert_equal Message.find(1), m.parent
783 782 end
784 783
785 784 def test_reply_to_a_message_by_subject
786 785 m = submit_email('message_reply_by_subject.eml')
787 786 assert m.is_a?(Message)
788 787 assert !m.new_record?
789 788 m.reload
790 789 assert_equal 'Reply to the first post', m.subject
791 790 assert_equal Message.find(1), m.parent
792 791 end
793 792
794 793 def test_should_strip_tags_of_html_only_emails
795 794 issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
796 795 assert issue.is_a?(Issue)
797 796 assert !issue.new_record?
798 797 issue.reload
799 798 assert_equal 'HTML email', issue.subject
800 799 assert_equal 'This is a html-only email.', issue.description
801 800 end
802 801
803 802 test "truncate emails with no setting should add the entire email into the issue" do
804 803 with_settings :mail_handler_body_delimiters => '' do
805 804 issue = submit_email('ticket_on_given_project.eml')
806 805 assert_issue_created(issue)
807 806 assert issue.description.include?('---')
808 807 assert issue.description.include?('This paragraph is after the delimiter')
809 808 end
810 809 end
811 810
812 811 test "truncate emails with a single string should truncate the email at the delimiter for the issue" do
813 812 with_settings :mail_handler_body_delimiters => '---' do
814 813 issue = submit_email('ticket_on_given_project.eml')
815 814 assert_issue_created(issue)
816 815 assert issue.description.include?('This paragraph is before delimiters')
817 816 assert issue.description.include?('--- This line starts with a delimiter')
818 817 assert !issue.description.match(/^---$/)
819 818 assert !issue.description.include?('This paragraph is after the delimiter')
820 819 end
821 820 end
822 821
823 822 test "truncate emails with a single quoted reply should truncate the email at the delimiter with the quoted reply symbols (>)" do
824 823 with_settings :mail_handler_body_delimiters => '--- Reply above. Do not remove this line. ---' do
825 824 journal = submit_email('issue_update_with_quoted_reply_above.eml')
826 825 assert journal.is_a?(Journal)
827 826 assert journal.notes.include?('An update to the issue by the sender.')
828 827 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
829 828 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
830 829 end
831 830 end
832 831
833 832 test "truncate emails with multiple quoted replies should truncate the email at the delimiter with the quoted reply symbols (>)" do
834 833 with_settings :mail_handler_body_delimiters => '--- Reply above. Do not remove this line. ---' do
835 834 journal = submit_email('issue_update_with_multiple_quoted_reply_above.eml')
836 835 assert journal.is_a?(Journal)
837 836 assert journal.notes.include?('An update to the issue by the sender.')
838 837 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
839 838 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
840 839 end
841 840 end
842 841
843 842 test "truncate emails with multiple strings should truncate the email at the first delimiter found (BREAK)" do
844 843 with_settings :mail_handler_body_delimiters => "---\nBREAK" do
845 844 issue = submit_email('ticket_on_given_project.eml')
846 845 assert_issue_created(issue)
847 846 assert issue.description.include?('This paragraph is before delimiters')
848 847 assert !issue.description.include?('BREAK')
849 848 assert !issue.description.include?('This paragraph is between delimiters')
850 849 assert !issue.description.match(/^---$/)
851 850 assert !issue.description.include?('This paragraph is after the delimiter')
852 851 end
853 852 end
854 853
855 854 def test_attachments_that_match_mail_handler_excluded_filenames_should_be_ignored
856 855 with_settings :mail_handler_excluded_filenames => '*.vcf, *.jpg' do
857 856 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
858 857 assert issue.is_a?(Issue)
859 858 assert !issue.new_record?
860 859 assert_equal 0, issue.reload.attachments.size
861 860 end
862 861 end
863 862
864 863 def test_attachments_that_do_not_match_mail_handler_excluded_filenames_should_be_attached
865 864 with_settings :mail_handler_excluded_filenames => '*.vcf, *.gif' do
866 865 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
867 866 assert issue.is_a?(Issue)
868 867 assert !issue.new_record?
869 868 assert_equal 1, issue.reload.attachments.size
870 869 end
871 870 end
872 871
873 872 def test_email_with_long_subject_line
874 873 issue = submit_email('ticket_with_long_subject.eml')
875 874 assert issue.is_a?(Issue)
876 875 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]
877 876 end
878 877
879 878 def test_first_keyword_should_be_matched
880 879 issue = submit_email('ticket_with_duplicate_keyword.eml', :allow_override => 'priority')
881 880 assert issue.is_a?(Issue)
882 881 assert_equal 'High', issue.priority.name
883 882 end
884 883
885 884 def test_keyword_after_delimiter_should_be_ignored
886 885 with_settings :mail_handler_body_delimiters => "== DELIMITER ==" do
887 886 issue = submit_email('ticket_with_keyword_after_delimiter.eml', :allow_override => 'priority')
888 887 assert issue.is_a?(Issue)
889 888 assert_equal 'Normal', issue.priority.name
890 889 end
891 890 end
892 891
893 892 def test_new_user_from_attributes_should_return_valid_user
894 893 to_test = {
895 894 # [address, name] => [login, firstname, lastname]
896 895 ['jsmith@example.net', nil] => ['jsmith@example.net', 'jsmith', '-'],
897 896 ['jsmith@example.net', 'John'] => ['jsmith@example.net', 'John', '-'],
898 897 ['jsmith@example.net', 'John Smith'] => ['jsmith@example.net', 'John', 'Smith'],
899 898 ['jsmith@example.net', 'John Paul Smith'] => ['jsmith@example.net', 'John', 'Paul Smith'],
900 899 ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsTheMaximumLength Smith'] => ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsT', 'Smith'],
901 900 ['jsmith@example.net', 'John AVeryLongLastnameThatExceedsTheMaximumLength'] => ['jsmith@example.net', 'John', 'AVeryLongLastnameThatExceedsTh']
902 901 }
903 902
904 903 to_test.each do |attrs, expected|
905 904 user = MailHandler.new_user_from_attributes(attrs.first, attrs.last)
906 905
907 906 assert user.valid?, user.errors.full_messages.to_s
908 907 assert_equal attrs.first, user.mail
909 908 assert_equal expected[0], user.login
910 909 assert_equal expected[1], user.firstname
911 910 assert_equal expected[2], user.lastname
912 911 assert_equal 'only_my_events', user.mail_notification
913 912 end
914 913 end
915 914
916 915 def test_new_user_from_attributes_should_use_default_login_if_invalid
917 916 user = MailHandler.new_user_from_attributes('foo+bar@example.net')
918 917 assert user.valid?
919 918 assert user.login =~ /^user[a-f0-9]+$/
920 919 assert_equal 'foo+bar@example.net', user.mail
921 920 end
922 921
923 922 def test_new_user_with_utf8_encoded_fullname_should_be_decoded
924 923 assert_difference 'User.count' do
925 924 issue = submit_email(
926 925 'fullname_of_sender_as_utf8_encoded.eml',
927 926 :issue => {:project => 'ecookbook'},
928 927 :unknown_user => 'create'
929 928 )
930 929 end
931 930 user = User.order('id DESC').first
932 931 assert_equal "foo@example.org", user.mail
933 932 str1 = "\xc3\x84\xc3\xa4".force_encoding('UTF-8')
934 933 str2 = "\xc3\x96\xc3\xb6".force_encoding('UTF-8')
935 934 assert_equal str1, user.firstname
936 935 assert_equal str2, user.lastname
937 936 end
938 937
939 938 def test_extract_options_from_env_should_return_options
940 939 options = MailHandler.extract_options_from_env({
941 940 'tracker' => 'defect',
942 941 'project' => 'foo',
943 942 'unknown_user' => 'create'
944 943 })
945 944
946 945 assert_equal({
947 946 :issue => {:tracker => 'defect', :project => 'foo'},
948 947 :unknown_user => 'create'
949 948 }, options)
950 949 end
951 950
952 951 def test_safe_receive_should_rescue_exceptions_and_return_false
953 952 MailHandler.stubs(:receive).raises(Exception.new "Something went wrong")
954 953
955 954 assert_equal false, MailHandler.safe_receive
956 955 end
957 956
958 957 private
959 958
960 959 def submit_email(filename, options={})
961 960 raw = IO.read(File.join(FIXTURES_PATH, filename))
962 961 yield raw if block_given?
963 962 MailHandler.receive(raw, options)
964 963 end
965 964
966 965 def assert_issue_created(issue)
967 966 assert issue.is_a?(Issue)
968 967 assert !issue.new_record?
969 968 issue.reload
970 969 end
971 970 end
@@ -1,853 +1,853
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class MailerTest < ActiveSupport::TestCase
21 21 include Redmine::I18n
22 22 include Rails::Dom::Testing::Assertions
23 23 fixtures :projects, :enabled_modules, :issues, :users, :email_addresses, :members,
24 24 :member_roles, :roles, :documents, :attachments, :news,
25 25 :tokens, :journals, :journal_details, :changesets,
26 26 :trackers, :projects_trackers,
27 27 :issue_statuses, :enumerations, :messages, :boards, :repositories,
28 28 :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions,
29 29 :versions,
30 30 :comments
31 31
32 32 def setup
33 33 ActionMailer::Base.deliveries.clear
34 34 Setting.host_name = 'mydomain.foo'
35 35 Setting.protocol = 'http'
36 36 Setting.plain_text_mail = '0'
37 37 Setting.default_language = 'en'
38 38 User.current = nil
39 39 end
40 40
41 41 def test_generated_links_in_emails
42 42 Setting.host_name = 'mydomain.foo'
43 43 Setting.protocol = 'https'
44 44
45 45 journal = Journal.find(3)
46 46 assert Mailer.deliver_issue_edit(journal)
47 47
48 48 mail = last_email
49 49 assert_not_nil mail
50 50
51 51 assert_select_email do
52 52 # link to the main ticket
53 53 assert_select 'a[href=?]',
54 54 'https://mydomain.foo/issues/2#change-3',
55 55 :text => 'Feature request #2: Add ingredients categories'
56 56 # link to a referenced ticket
57 57 assert_select 'a[href=?][title=?]',
58 58 'https://mydomain.foo/issues/1',
59 59 "Cannot print recipes (New)",
60 60 :text => '#1'
61 61 # link to a changeset
62 62 assert_select 'a[href=?][title=?]',
63 63 'https://mydomain.foo/projects/ecookbook/repository/revisions/2',
64 64 'This commit fixes #1, #2 and references #1 & #3',
65 65 :text => 'r2'
66 66 # link to a description diff
67 67 assert_select 'a[href^=?][title=?]',
68 68 # should be https://mydomain.foo/journals/diff/3?detail_id=4
69 69 # but the Rails 4.2 DOM assertion doesn't handle the ? in the
70 70 # attribute value
71 71 'https://mydomain.foo/journals/diff/3',
72 72 'View differences',
73 73 :text => 'diff'
74 74 # link to an attachment
75 75 assert_select 'a[href=?]',
76 76 'https://mydomain.foo/attachments/download/4/source.rb',
77 77 :text => 'source.rb'
78 78 end
79 79 end
80 80
81 81 def test_generated_links_with_prefix
82 82 relative_url_root = Redmine::Utils.relative_url_root
83 83 Setting.host_name = 'mydomain.foo/rdm'
84 84 Setting.protocol = 'http'
85 85
86 86 journal = Journal.find(3)
87 87 assert Mailer.deliver_issue_edit(journal)
88 88
89 89 mail = last_email
90 90 assert_not_nil mail
91 91
92 92 assert_select_email do
93 93 # link to the main ticket
94 94 assert_select 'a[href=?]',
95 95 'http://mydomain.foo/rdm/issues/2#change-3',
96 96 :text => 'Feature request #2: Add ingredients categories'
97 97 # link to a referenced ticket
98 98 assert_select 'a[href=?][title=?]',
99 99 'http://mydomain.foo/rdm/issues/1',
100 100 "Cannot print recipes (New)",
101 101 :text => '#1'
102 102 # link to a changeset
103 103 assert_select 'a[href=?][title=?]',
104 104 'http://mydomain.foo/rdm/projects/ecookbook/repository/revisions/2',
105 105 'This commit fixes #1, #2 and references #1 & #3',
106 106 :text => 'r2'
107 107 # link to a description diff
108 108 assert_select 'a[href^=?][title=?]',
109 109 # should be http://mydomain.foo/rdm/journals/diff/3?detail_id=4
110 110 # but the Rails 4.2 DOM assertion doesn't handle the ? in the
111 111 # attribute value
112 112 'http://mydomain.foo/rdm/journals/diff/3',
113 113 'View differences',
114 114 :text => 'diff'
115 115 # link to an attachment
116 116 assert_select 'a[href=?]',
117 117 'http://mydomain.foo/rdm/attachments/download/4/source.rb',
118 118 :text => 'source.rb'
119 119 end
120 120 end
121 121
122 122 def test_generated_links_with_port_and_prefix
123 123 with_settings :host_name => '10.0.0.1:81/redmine', :protocol => 'http' do
124 124 Mailer.test_email(User.find(1)).deliver
125 125 mail = last_email
126 126 assert_not_nil mail
127 127 assert_include 'http://10.0.0.1:81/redmine', mail_body(mail)
128 128 end
129 129 end
130 130
131 131 def test_generated_links_with_port
132 132 with_settings :host_name => '10.0.0.1:81', :protocol => 'http' do
133 133 Mailer.test_email(User.find(1)).deliver
134 134 mail = last_email
135 135 assert_not_nil mail
136 136 assert_include 'http://10.0.0.1:81', mail_body(mail)
137 137 end
138 138 end
139 139
140 140 def test_issue_edit_should_generate_url_with_hostname_for_relations
141 141 journal = Journal.new(:journalized => Issue.find(1), :user => User.find(1), :created_on => Time.now)
142 142 journal.details << JournalDetail.new(:property => 'relation', :prop_key => 'label_relates_to', :value => 2)
143 143 Mailer.deliver_issue_edit(journal)
144 144 assert_not_nil last_email
145 145 assert_select_email do
146 146 assert_select 'a[href=?]', 'http://mydomain.foo/issues/2', :text => 'Feature request #2'
147 147 end
148 148 end
149 149
150 150 def test_generated_links_with_prefix_and_no_relative_url_root
151 151 relative_url_root = Redmine::Utils.relative_url_root
152 152 Setting.host_name = 'mydomain.foo/rdm'
153 153 Setting.protocol = 'http'
154 154 Redmine::Utils.relative_url_root = nil
155 155
156 156 journal = Journal.find(3)
157 157 assert Mailer.deliver_issue_edit(journal)
158 158
159 159 mail = last_email
160 160 assert_not_nil mail
161 161
162 162 assert_select_email do
163 163 # link to the main ticket
164 164 assert_select 'a[href=?]',
165 165 'http://mydomain.foo/rdm/issues/2#change-3',
166 166 :text => 'Feature request #2: Add ingredients categories'
167 167 # link to a referenced ticket
168 168 assert_select 'a[href=?][title=?]',
169 169 'http://mydomain.foo/rdm/issues/1',
170 170 "Cannot print recipes (New)",
171 171 :text => '#1'
172 172 # link to a changeset
173 173 assert_select 'a[href=?][title=?]',
174 174 'http://mydomain.foo/rdm/projects/ecookbook/repository/revisions/2',
175 175 'This commit fixes #1, #2 and references #1 & #3',
176 176 :text => 'r2'
177 177 # link to a description diff
178 178 assert_select 'a[href^=?][title=?]',
179 179 # should be http://mydomain.foo/rdm/journals/diff/3?detail_id=4
180 180 # but the Rails 4.2 DOM assertion doesn't handle the ? in the
181 181 # attribute value
182 182 'http://mydomain.foo/rdm/journals/diff/3',
183 183 'View differences',
184 184 :text => 'diff'
185 185 # link to an attachment
186 186 assert_select 'a[href=?]',
187 187 'http://mydomain.foo/rdm/attachments/download/4/source.rb',
188 188 :text => 'source.rb'
189 189 end
190 190 ensure
191 191 # restore it
192 192 Redmine::Utils.relative_url_root = relative_url_root
193 193 end
194 194
195 195 def test_email_headers
196 196 issue = Issue.find(1)
197 197 Mailer.deliver_issue_add(issue)
198 198 mail = last_email
199 199 assert_not_nil mail
200 assert_equal 'OOF', mail.header['X-Auto-Response-Suppress'].to_s
200 assert_equal 'All', mail.header['X-Auto-Response-Suppress'].to_s
201 201 assert_equal 'auto-generated', mail.header['Auto-Submitted'].to_s
202 202 assert_equal '<redmine.example.net>', mail.header['List-Id'].to_s
203 203 end
204 204
205 205 def test_email_headers_should_include_sender
206 206 issue = Issue.find(1)
207 207 Mailer.deliver_issue_add(issue)
208 208 mail = last_email
209 209 assert_equal issue.author.login, mail.header['X-Redmine-Sender'].to_s
210 210 end
211 211
212 212 def test_plain_text_mail
213 213 Setting.plain_text_mail = 1
214 214 journal = Journal.find(2)
215 215 Mailer.deliver_issue_edit(journal)
216 216 mail = last_email
217 217 assert_equal "text/plain; charset=UTF-8", mail.content_type
218 218 assert_equal 0, mail.parts.size
219 219 assert !mail.encoded.include?('href')
220 220 end
221 221
222 222 def test_html_mail
223 223 Setting.plain_text_mail = 0
224 224 journal = Journal.find(2)
225 225 Mailer.deliver_issue_edit(journal)
226 226 mail = last_email
227 227 assert_equal 2, mail.parts.size
228 228 assert mail.encoded.include?('href')
229 229 end
230 230
231 231 def test_from_header
232 232 with_settings :mail_from => 'redmine@example.net' do
233 233 Mailer.test_email(User.find(1)).deliver
234 234 end
235 235 mail = last_email
236 236 assert_equal 'redmine@example.net', mail.from_addrs.first
237 237 end
238 238
239 239 def test_from_header_with_phrase
240 240 with_settings :mail_from => 'Redmine app <redmine@example.net>' do
241 241 Mailer.test_email(User.find(1)).deliver
242 242 end
243 243 mail = last_email
244 244 assert_equal 'redmine@example.net', mail.from_addrs.first
245 245 assert_equal 'Redmine app <redmine@example.net>', mail.header['From'].to_s
246 246 end
247 247
248 248 def test_should_not_send_email_without_recipient
249 249 news = News.first
250 250 user = news.author
251 251 # Remove members except news author
252 252 news.project.memberships.each {|m| m.destroy unless m.user == user}
253 253
254 254 user.pref.no_self_notified = false
255 255 user.pref.save
256 256 User.current = user
257 257 Mailer.news_added(news.reload).deliver
258 258 assert_equal 1, last_email.bcc.size
259 259
260 260 # nobody to notify
261 261 user.pref.no_self_notified = true
262 262 user.pref.save
263 263 User.current = user
264 264 ActionMailer::Base.deliveries.clear
265 265 Mailer.news_added(news.reload).deliver
266 266 assert ActionMailer::Base.deliveries.empty?
267 267 end
268 268
269 269 def test_issue_add_message_id
270 270 issue = Issue.find(2)
271 271 Mailer.deliver_issue_add(issue)
272 272 mail = last_email
273 273 assert_match /^redmine\.issue-2\.20060719190421\.[a-f0-9]+@example\.net/, mail.message_id
274 274 assert_include "redmine.issue-2.20060719190421@example.net", mail.references
275 275 end
276 276
277 277 def test_issue_edit_message_id
278 278 journal = Journal.find(3)
279 279 journal.issue = Issue.find(2)
280 280
281 281 Mailer.deliver_issue_edit(journal)
282 282 mail = last_email
283 283 assert_match /^redmine\.journal-3\.\d+\.[a-f0-9]+@example\.net/, mail.message_id
284 284 assert_include "redmine.issue-2.20060719190421@example.net", mail.references
285 285 assert_select_email do
286 286 # link to the update
287 287 assert_select "a[href=?]",
288 288 "http://mydomain.foo/issues/#{journal.journalized_id}#change-#{journal.id}"
289 289 end
290 290 end
291 291
292 292 def test_message_posted_message_id
293 293 message = Message.find(1)
294 294 Mailer.message_posted(message).deliver
295 295 mail = last_email
296 296 assert_match /^redmine\.message-1\.\d+\.[a-f0-9]+@example\.net/, mail.message_id
297 297 assert_include "redmine.message-1.20070512151532@example.net", mail.references
298 298 assert_select_email do
299 299 # link to the message
300 300 assert_select "a[href=?]",
301 301 "http://mydomain.foo/boards/#{message.board.id}/topics/#{message.id}",
302 302 :text => message.subject
303 303 end
304 304 end
305 305
306 306 def test_reply_posted_message_id
307 307 message = Message.find(3)
308 308 Mailer.message_posted(message).deliver
309 309 mail = last_email
310 310 assert_match /^redmine\.message-3\.\d+\.[a-f0-9]+@example\.net/, mail.message_id
311 311 assert_include "redmine.message-1.20070512151532@example.net", mail.references
312 312 assert_select_email do
313 313 # link to the reply
314 314 assert_select "a[href=?]",
315 315 "http://mydomain.foo/boards/#{message.board.id}/topics/#{message.root.id}?r=#{message.id}#message-#{message.id}",
316 316 :text => message.subject
317 317 end
318 318 end
319 319
320 320 test "#issue_add should notify project members" do
321 321 issue = Issue.find(1)
322 322 assert Mailer.deliver_issue_add(issue)
323 323 assert last_email.bcc.include?('dlopper@somenet.foo')
324 324 end
325 325
326 326 def test_issue_add_should_send_mail_to_all_user_email_address
327 327 EmailAddress.create!(:user_id => 3, :address => 'otheremail@somenet.foo')
328 328 issue = Issue.find(1)
329 329 assert Mailer.deliver_issue_add(issue)
330 330 assert last_email.bcc.include?('dlopper@somenet.foo')
331 331 assert last_email.bcc.include?('otheremail@somenet.foo')
332 332 end
333 333
334 334 test "#issue_add should not notify project members that are not allow to view the issue" do
335 335 issue = Issue.find(1)
336 336 Role.find(2).remove_permission!(:view_issues)
337 337 assert Mailer.deliver_issue_add(issue)
338 338 assert !last_email.bcc.include?('dlopper@somenet.foo')
339 339 end
340 340
341 341 test "#issue_add should notify issue watchers" do
342 342 issue = Issue.find(1)
343 343 user = User.find(9)
344 344 # minimal email notification options
345 345 user.pref.no_self_notified = '1'
346 346 user.pref.save
347 347 user.mail_notification = false
348 348 user.save
349 349
350 350 Watcher.create!(:watchable => issue, :user => user)
351 351 assert Mailer.deliver_issue_add(issue)
352 352 assert last_email.bcc.include?(user.mail)
353 353 end
354 354
355 355 test "#issue_add should not notify watchers not allowed to view the issue" do
356 356 issue = Issue.find(1)
357 357 user = User.find(9)
358 358 Watcher.create!(:watchable => issue, :user => user)
359 359 Role.non_member.remove_permission!(:view_issues)
360 360 assert Mailer.deliver_issue_add(issue)
361 361 assert !last_email.bcc.include?(user.mail)
362 362 end
363 363
364 364 def test_issue_add_should_include_enabled_fields
365 365 issue = Issue.find(2)
366 366 assert Mailer.deliver_issue_add(issue)
367 367 assert_mail_body_match '* Target version: 1.0', last_email
368 368 assert_select_email do
369 369 assert_select 'li', :text => 'Target version: 1.0'
370 370 end
371 371 end
372 372
373 373 def test_issue_add_should_not_include_disabled_fields
374 374 issue = Issue.find(2)
375 375 tracker = issue.tracker
376 376 tracker.core_fields -= ['fixed_version_id']
377 377 tracker.save!
378 378 assert Mailer.deliver_issue_add(issue)
379 379 assert_mail_body_no_match 'Target version', last_email
380 380 assert_select_email do
381 381 assert_select 'li', :text => /Target version/, :count => 0
382 382 end
383 383 end
384 384
385 385 # test mailer methods for each language
386 386 def test_issue_add
387 387 issue = Issue.find(1)
388 388 with_each_language_as_default do
389 389 assert Mailer.deliver_issue_add(issue)
390 390 end
391 391 end
392 392
393 393 def test_issue_edit
394 394 journal = Journal.find(1)
395 395 with_each_language_as_default do
396 396 assert Mailer.deliver_issue_edit(journal)
397 397 end
398 398 end
399 399
400 400 def test_issue_edit_should_send_private_notes_to_users_with_permission_only
401 401 journal = Journal.find(1)
402 402 journal.private_notes = true
403 403 journal.save!
404 404
405 405 Role.find(2).add_permission! :view_private_notes
406 406 Mailer.deliver_issue_edit(journal)
407 407 assert_equal %w(dlopper@somenet.foo jsmith@somenet.foo), ActionMailer::Base.deliveries.last.bcc.sort
408 408
409 409 Role.find(2).remove_permission! :view_private_notes
410 410 Mailer.deliver_issue_edit(journal)
411 411 assert_equal %w(jsmith@somenet.foo), ActionMailer::Base.deliveries.last.bcc.sort
412 412 end
413 413
414 414 def test_issue_edit_should_send_private_notes_to_watchers_with_permission_only
415 415 Issue.find(1).set_watcher(User.find_by_login('someone'))
416 416 journal = Journal.find(1)
417 417 journal.private_notes = true
418 418 journal.save!
419 419
420 420 Role.non_member.add_permission! :view_private_notes
421 421 Mailer.deliver_issue_edit(journal)
422 422 assert_include 'someone@foo.bar', ActionMailer::Base.deliveries.last.bcc.sort
423 423
424 424 Role.non_member.remove_permission! :view_private_notes
425 425 Mailer.deliver_issue_edit(journal)
426 426 assert_not_include 'someone@foo.bar', ActionMailer::Base.deliveries.last.bcc.sort
427 427 end
428 428
429 429 def test_issue_edit_should_mark_private_notes
430 430 journal = Journal.find(2)
431 431 journal.private_notes = true
432 432 journal.save!
433 433
434 434 with_settings :default_language => 'en' do
435 435 Mailer.deliver_issue_edit(journal)
436 436 end
437 437 assert_mail_body_match '(Private notes)', last_email
438 438 end
439 439
440 440 def test_issue_edit_with_relation_should_notify_users_who_can_see_the_related_issue
441 441 issue = Issue.generate!
442 442 issue.init_journal(User.find(1))
443 443 private_issue = Issue.generate!(:is_private => true)
444 444 IssueRelation.create!(:issue_from => issue, :issue_to => private_issue, :relation_type => 'relates')
445 445 issue.reload
446 446 assert_equal 1, issue.journals.size
447 447 journal = issue.journals.first
448 448 ActionMailer::Base.deliveries.clear
449 449
450 450 Mailer.deliver_issue_edit(journal)
451 451 last_email.bcc.each do |email|
452 452 user = User.find_by_mail(email)
453 453 assert private_issue.visible?(user), "Issue was not visible to #{user}"
454 454 end
455 455 end
456 456
457 457 def test_document_added
458 458 document = Document.find(1)
459 459 with_each_language_as_default do
460 460 assert Mailer.document_added(document).deliver
461 461 end
462 462 end
463 463
464 464 def test_attachments_added
465 465 attachements = [ Attachment.find_by_container_type('Document') ]
466 466 with_each_language_as_default do
467 467 assert Mailer.attachments_added(attachements).deliver
468 468 end
469 469 end
470 470
471 471 def test_version_file_added
472 472 attachements = [ Attachment.find_by_container_type('Version') ]
473 473 assert Mailer.attachments_added(attachements).deliver
474 474 assert_not_nil last_email.bcc
475 475 assert last_email.bcc.any?
476 476 assert_select_email do
477 477 assert_select "a[href=?]", "http://mydomain.foo/projects/ecookbook/files"
478 478 end
479 479 end
480 480
481 481 def test_project_file_added
482 482 attachements = [ Attachment.find_by_container_type('Project') ]
483 483 assert Mailer.attachments_added(attachements).deliver
484 484 assert_not_nil last_email.bcc
485 485 assert last_email.bcc.any?
486 486 assert_select_email do
487 487 assert_select "a[href=?]", "http://mydomain.foo/projects/ecookbook/files"
488 488 end
489 489 end
490 490
491 491 def test_news_added
492 492 news = News.first
493 493 with_each_language_as_default do
494 494 assert Mailer.news_added(news).deliver
495 495 end
496 496 end
497 497
498 498 def test_news_added_should_notify_project_news_watchers
499 499 user1 = User.generate!
500 500 user2 = User.generate!
501 501 news = News.find(1)
502 502 news.project.enabled_module('news').add_watcher(user1)
503 503
504 504 Mailer.news_added(news).deliver
505 505 assert_include user1.mail, last_email.bcc
506 506 assert_not_include user2.mail, last_email.bcc
507 507 end
508 508
509 509 def test_news_comment_added
510 510 comment = Comment.find(2)
511 511 with_each_language_as_default do
512 512 assert Mailer.news_comment_added(comment).deliver
513 513 end
514 514 end
515 515
516 516 def test_message_posted
517 517 message = Message.first
518 518 recipients = ([message.root] + message.root.children).collect {|m| m.author.mail if m.author}
519 519 recipients = recipients.compact.uniq
520 520 with_each_language_as_default do
521 521 assert Mailer.message_posted(message).deliver
522 522 end
523 523 end
524 524
525 525 def test_wiki_content_added
526 526 content = WikiContent.find(1)
527 527 with_each_language_as_default do
528 528 assert_difference 'ActionMailer::Base.deliveries.size' do
529 529 assert Mailer.wiki_content_added(content).deliver
530 530 assert_select_email do
531 531 assert_select 'a[href=?]',
532 532 'http://mydomain.foo/projects/ecookbook/wiki/CookBook_documentation',
533 533 :text => 'CookBook documentation'
534 534 end
535 535 end
536 536 end
537 537 end
538 538
539 539 def test_wiki_content_updated
540 540 content = WikiContent.find(1)
541 541 with_each_language_as_default do
542 542 assert_difference 'ActionMailer::Base.deliveries.size' do
543 543 assert Mailer.wiki_content_updated(content).deliver
544 544 assert_select_email do
545 545 assert_select 'a[href=?]',
546 546 'http://mydomain.foo/projects/ecookbook/wiki/CookBook_documentation',
547 547 :text => 'CookBook documentation'
548 548 end
549 549 end
550 550 end
551 551 end
552 552
553 553 def test_account_information
554 554 user = User.find(2)
555 555 valid_languages.each do |lang|
556 556 user.update_attribute :language, lang.to_s
557 557 user.reload
558 558 assert Mailer.account_information(user, 'pAsswORd').deliver
559 559 end
560 560 end
561 561
562 562 def test_lost_password
563 563 token = Token.find(2)
564 564 valid_languages.each do |lang|
565 565 token.user.update_attribute :language, lang.to_s
566 566 token.reload
567 567 assert Mailer.lost_password(token).deliver
568 568 end
569 569 end
570 570
571 571 def test_register
572 572 token = Token.find(1)
573 573 Setting.host_name = 'redmine.foo'
574 574 Setting.protocol = 'https'
575 575
576 576 valid_languages.each do |lang|
577 577 token.user.update_attribute :language, lang.to_s
578 578 token.reload
579 579 ActionMailer::Base.deliveries.clear
580 580 assert Mailer.register(token).deliver
581 581 mail = last_email
582 582 assert_select_email do
583 583 assert_select "a[href=?]",
584 584 "https://redmine.foo/account/activate?token=#{token.value}",
585 585 :text => "https://redmine.foo/account/activate?token=#{token.value}"
586 586 end
587 587 end
588 588 end
589 589
590 590 def test_test
591 591 user = User.find(1)
592 592 valid_languages.each do |lang|
593 593 user.update_attribute :language, lang.to_s
594 594 assert Mailer.test_email(user).deliver
595 595 end
596 596 end
597 597
598 598 def test_reminders
599 599 Mailer.reminders(:days => 42)
600 600 assert_equal 1, ActionMailer::Base.deliveries.size
601 601 mail = last_email
602 602 assert mail.bcc.include?('dlopper@somenet.foo')
603 603 assert_mail_body_match 'Bug #3: Error 281 when updating a recipe', mail
604 604 assert_equal '1 issue(s) due in the next 42 days', mail.subject
605 605 end
606 606
607 607 def test_reminders_should_not_include_closed_issues
608 608 with_settings :default_language => 'en' do
609 609 Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 5,
610 610 :subject => 'Closed issue', :assigned_to_id => 3,
611 611 :due_date => 5.days.from_now,
612 612 :author_id => 2)
613 613 ActionMailer::Base.deliveries.clear
614 614
615 615 Mailer.reminders(:days => 42)
616 616 assert_equal 1, ActionMailer::Base.deliveries.size
617 617 mail = last_email
618 618 assert mail.bcc.include?('dlopper@somenet.foo')
619 619 assert_mail_body_no_match 'Closed issue', mail
620 620 end
621 621 end
622 622
623 623 def test_reminders_for_users
624 624 Mailer.reminders(:days => 42, :users => ['5'])
625 625 assert_equal 0, ActionMailer::Base.deliveries.size # No mail for dlopper
626 626 Mailer.reminders(:days => 42, :users => ['3'])
627 627 assert_equal 1, ActionMailer::Base.deliveries.size # No mail for dlopper
628 628 mail = last_email
629 629 assert mail.bcc.include?('dlopper@somenet.foo')
630 630 assert_mail_body_match 'Bug #3: Error 281 when updating a recipe', mail
631 631 end
632 632
633 633 def test_reminder_should_include_issues_assigned_to_groups
634 634 with_settings :default_language => 'en' do
635 635 group = Group.generate!
636 636 group.users << User.find(2)
637 637 group.users << User.find(3)
638 638
639 639 Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 1,
640 640 :subject => 'Assigned to group', :assigned_to => group,
641 641 :due_date => 5.days.from_now,
642 642 :author_id => 2)
643 643 ActionMailer::Base.deliveries.clear
644 644
645 645 Mailer.reminders(:days => 7)
646 646 assert_equal 2, ActionMailer::Base.deliveries.size
647 647 assert_equal %w(dlopper@somenet.foo jsmith@somenet.foo), ActionMailer::Base.deliveries.map(&:bcc).flatten.sort
648 648 ActionMailer::Base.deliveries.each do |mail|
649 649 assert_mail_body_match 'Assigned to group', mail
650 650 end
651 651 end
652 652 end
653 653
654 654 def test_reminders_with_version_option
655 655 with_settings :default_language => 'en' do
656 656 version = Version.generate!(:name => 'Acme', :project_id => 1)
657 657 Issue.generate!(:assigned_to => User.find(2), :due_date => 5.days.from_now)
658 658 Issue.generate!(:assigned_to => User.find(3), :due_date => 5.days.from_now, :fixed_version => version)
659 659 ActionMailer::Base.deliveries.clear
660 660
661 661 Mailer.reminders(:days => 42, :version => 'acme')
662 662 assert_equal 1, ActionMailer::Base.deliveries.size
663 663
664 664 mail = last_email
665 665 assert mail.bcc.include?('dlopper@somenet.foo')
666 666 end
667 667 end
668 668
669 669 def test_mailer_should_not_change_locale
670 670 # Set current language to italian
671 671 set_language_if_valid 'it'
672 672 # Send an email to a french user
673 673 user = User.find(1)
674 674 user.language = 'fr'
675 675 Mailer.account_activated(user).deliver
676 676 mail = last_email
677 677 assert_mail_body_match 'Votre compte', mail
678 678
679 679 assert_equal :it, current_language
680 680 end
681 681
682 682 def test_with_deliveries_off
683 683 Mailer.with_deliveries false do
684 684 Mailer.test_email(User.find(1)).deliver
685 685 end
686 686 assert ActionMailer::Base.deliveries.empty?
687 687 # should restore perform_deliveries
688 688 assert ActionMailer::Base.perform_deliveries
689 689 end
690 690
691 691 def test_token_for_should_strip_trailing_gt_from_address_with_full_name
692 692 with_settings :mail_from => "Redmine Mailer<no-reply@redmine.org>" do
693 693 assert_match /\Aredmine.issue-\d+\.\d+\.[0-9a-f]+@redmine.org\z/, Mailer.token_for(Issue.generate!)
694 694 end
695 695 end
696 696
697 697 def test_layout_should_include_the_emails_header
698 698 with_settings :emails_header => "*Header content*" do
699 699 with_settings :plain_text_mail => 0 do
700 700 assert Mailer.test_email(User.find(1)).deliver
701 701 assert_select_email do
702 702 assert_select ".header" do
703 703 assert_select "strong", :text => "Header content"
704 704 end
705 705 end
706 706 end
707 707 with_settings :plain_text_mail => 1 do
708 708 assert Mailer.test_email(User.find(1)).deliver
709 709 mail = last_email
710 710 assert_not_nil mail
711 711 assert_include "*Header content*", mail.body.decoded
712 712 end
713 713 end
714 714 end
715 715
716 716 def test_layout_should_not_include_empty_emails_header
717 717 with_settings :emails_header => "", :plain_text_mail => 0 do
718 718 assert Mailer.test_email(User.find(1)).deliver
719 719 assert_select_email do
720 720 assert_select ".header", false
721 721 end
722 722 end
723 723 end
724 724
725 725 def test_layout_should_include_the_emails_footer
726 726 with_settings :emails_footer => "*Footer content*" do
727 727 with_settings :plain_text_mail => 0 do
728 728 assert Mailer.test_email(User.find(1)).deliver
729 729 assert_select_email do
730 730 assert_select ".footer" do
731 731 assert_select "strong", :text => "Footer content"
732 732 end
733 733 end
734 734 end
735 735 with_settings :plain_text_mail => 1 do
736 736 assert Mailer.test_email(User.find(1)).deliver
737 737 mail = last_email
738 738 assert_not_nil mail
739 739 assert_include "\n-- \n", mail.body.decoded
740 740 assert_include "*Footer content*", mail.body.decoded
741 741 end
742 742 end
743 743 end
744 744
745 745 def test_layout_should_not_include_empty_emails_footer
746 746 with_settings :emails_footer => "" do
747 747 with_settings :plain_text_mail => 0 do
748 748 assert Mailer.test_email(User.find(1)).deliver
749 749 assert_select_email do
750 750 assert_select ".footer", false
751 751 end
752 752 end
753 753 with_settings :plain_text_mail => 1 do
754 754 assert Mailer.test_email(User.find(1)).deliver
755 755 mail = last_email
756 756 assert_not_nil mail
757 757 assert_not_include "\n-- \n", mail.body.decoded
758 758 end
759 759 end
760 760 end
761 761
762 762 def test_should_escape_html_templates_only
763 763 Issue.generate!(:project_id => 1, :tracker_id => 1, :subject => 'Subject with a <tag>')
764 764 mail = last_email
765 765 assert_equal 2, mail.parts.size
766 766 assert_include '<tag>', text_part.body.encoded
767 767 assert_include '&lt;tag&gt;', html_part.body.encoded
768 768 end
769 769
770 770 def test_should_raise_delivery_errors_when_raise_delivery_errors_is_true
771 771 mail = Mailer.test_email(User.find(1))
772 772 mail.delivery_method.stubs(:deliver!).raises(Exception.new("delivery error"))
773 773
774 774 ActionMailer::Base.raise_delivery_errors = true
775 775 assert_raise Exception, "delivery error" do
776 776 mail.deliver
777 777 end
778 778 ensure
779 779 ActionMailer::Base.raise_delivery_errors = false
780 780 end
781 781
782 782 def test_should_log_delivery_errors_when_raise_delivery_errors_is_false
783 783 mail = Mailer.test_email(User.find(1))
784 784 mail.delivery_method.stubs(:deliver!).raises(Exception.new("delivery error"))
785 785
786 786 Rails.logger.expects(:error).with("Email delivery error: delivery error")
787 787 ActionMailer::Base.raise_delivery_errors = false
788 788 assert_nothing_raised do
789 789 mail.deliver
790 790 end
791 791 end
792 792
793 793 def test_with_synched_deliveries_should_yield_with_synced_deliveries
794 794 ActionMailer::Base.delivery_method = :async_smtp
795 795 ActionMailer::Base.async_smtp_settings = {:foo => 'bar'}
796 796
797 797 Mailer.with_synched_deliveries do
798 798 assert_equal :smtp, ActionMailer::Base.delivery_method
799 799 assert_equal({:foo => 'bar'}, ActionMailer::Base.smtp_settings)
800 800 end
801 801 assert_equal :async_smtp, ActionMailer::Base.delivery_method
802 802 ensure
803 803 ActionMailer::Base.delivery_method = :test
804 804 end
805 805
806 806 def test_email_addresses_should_keep_addresses
807 807 assert_equal ["foo@example.net"],
808 808 Mailer.email_addresses("foo@example.net")
809 809
810 810 assert_equal ["foo@example.net", "bar@example.net"],
811 811 Mailer.email_addresses(["foo@example.net", "bar@example.net"])
812 812 end
813 813
814 814 def test_email_addresses_should_replace_users_with_their_email_addresses
815 815 assert_equal ["admin@somenet.foo"],
816 816 Mailer.email_addresses(User.find(1))
817 817
818 818 assert_equal ["admin@somenet.foo", "jsmith@somenet.foo"],
819 819 Mailer.email_addresses(User.where(:id => [1,2])).sort
820 820 end
821 821
822 822 def test_email_addresses_should_include_notified_emails_addresses_only
823 823 EmailAddress.create!(:user_id => 2, :address => "another@somenet.foo", :notify => false)
824 824 EmailAddress.create!(:user_id => 2, :address => "another2@somenet.foo")
825 825
826 826 assert_equal ["another2@somenet.foo", "jsmith@somenet.foo"],
827 827 Mailer.email_addresses(User.find(2)).sort
828 828 end
829 829
830 830 private
831 831
832 832 def last_email
833 833 mail = ActionMailer::Base.deliveries.last
834 834 assert_not_nil mail
835 835 mail
836 836 end
837 837
838 838 def text_part
839 839 last_email.parts.detect {|part| part.content_type.include?('text/plain')}
840 840 end
841 841
842 842 def html_part
843 843 last_email.parts.detect {|part| part.content_type.include?('text/html')}
844 844 end
845 845
846 846 def with_each_language_as_default(&block)
847 847 valid_languages.each do |lang|
848 848 with_settings :default_language => lang.to_s do
849 849 yield lang
850 850 end
851 851 end
852 852 end
853 853 end
General Comments 0
You need to be logged in to leave comments. Login now