##// END OF EJS Templates
Mail Handler: add support for allow_override=all (#20543)....
Jean-Philippe Lang -
r14299:1ccdf38fdde1
parent child
Show More
@@ -1,544 +1,547
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, :handler_options
26 26
27 27 def self.receive(raw_mail, options={})
28 28 options = options.deep_dup
29 29
30 30 options[:issue] ||= {}
31 31
32 options[:allow_override] ||= []
32 33 if options[:allow_override].is_a?(String)
33 options[:allow_override] = options[:allow_override].split(',').collect(&:strip)
34 options[:allow_override] = options[:allow_override].split(',')
34 35 end
35 options[:allow_override] ||= []
36 options[:allow_override].map!(&:downcase)
36 options[:allow_override].map! {|s| s.strip.downcase.gsub(/\s+/, '_')}
37 37 # Project needs to be overridable if not specified
38 38 options[:allow_override] << 'project' unless options[:issue].has_key?(:project)
39 39
40 40 options[:no_account_notice] = (options[:no_account_notice].to_s == '1')
41 41 options[:no_notification] = (options[:no_notification].to_s == '1')
42 42 options[:no_permission_check] = (options[:no_permission_check].to_s == '1')
43 43
44 44 raw_mail.force_encoding('ASCII-8BIT')
45 45
46 46 ActiveSupport::Notifications.instrument("receive.action_mailer") do |payload|
47 47 mail = Mail.new(raw_mail)
48 48 set_payload_for_mail(payload, mail)
49 49 new.receive(mail, options)
50 50 end
51 51 end
52 52
53 53 # Receives an email and rescues any exception
54 54 def self.safe_receive(*args)
55 55 receive(*args)
56 56 rescue Exception => e
57 57 logger.error "An unexpected error occurred when receiving email: #{e.message}" if logger
58 58 return false
59 59 end
60 60
61 61 # Extracts MailHandler options from environment variables
62 62 # Use when receiving emails with rake tasks
63 63 def self.extract_options_from_env(env)
64 64 options = {:issue => {}}
65 65 %w(project status tracker category priority).each do |option|
66 66 options[:issue][option.to_sym] = env[option] if env[option]
67 67 end
68 68 %w(allow_override unknown_user no_permission_check no_account_notice default_group).each do |option|
69 69 options[option.to_sym] = env[option] if env[option]
70 70 end
71 71 if env['private']
72 72 options[:issue][:is_private] = '1'
73 73 end
74 74 options
75 75 end
76 76
77 77 def logger
78 78 Rails.logger
79 79 end
80 80
81 81 cattr_accessor :ignored_emails_headers
82 82 self.ignored_emails_headers = {
83 83 'Auto-Submitted' => /\Aauto-(replied|generated)/,
84 84 'X-Autoreply' => 'yes'
85 85 }
86 86
87 87 # Processes incoming emails
88 88 # Returns the created object (eg. an issue, a message) or false
89 89 def receive(email, options={})
90 90 @email = email
91 91 @handler_options = options
92 92 sender_email = email.from.to_a.first.to_s.strip
93 93 # Ignore emails received from the application emission address to avoid hell cycles
94 94 if sender_email.casecmp(Setting.mail_from.to_s.strip) == 0
95 95 if logger
96 96 logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]"
97 97 end
98 98 return false
99 99 end
100 100 # Ignore auto generated emails
101 101 self.class.ignored_emails_headers.each do |key, ignored_value|
102 102 value = email.header[key]
103 103 if value
104 104 value = value.to_s.downcase
105 105 if (ignored_value.is_a?(Regexp) && value.match(ignored_value)) || value == ignored_value
106 106 if logger
107 107 logger.info "MailHandler: ignoring email with #{key}:#{value} header"
108 108 end
109 109 return false
110 110 end
111 111 end
112 112 end
113 113 @user = User.find_by_mail(sender_email) if sender_email.present?
114 114 if @user && !@user.active?
115 115 if logger
116 116 logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]"
117 117 end
118 118 return false
119 119 end
120 120 if @user.nil?
121 121 # Email was submitted by an unknown user
122 122 case handler_options[:unknown_user]
123 123 when 'accept'
124 124 @user = User.anonymous
125 125 when 'create'
126 126 @user = create_user_from_email
127 127 if @user
128 128 if logger
129 129 logger.info "MailHandler: [#{@user.login}] account created"
130 130 end
131 131 add_user_to_group(handler_options[:default_group])
132 132 unless handler_options[:no_account_notice]
133 133 Mailer.account_information(@user, @user.password).deliver
134 134 end
135 135 else
136 136 if logger
137 137 logger.error "MailHandler: could not create account for [#{sender_email}]"
138 138 end
139 139 return false
140 140 end
141 141 else
142 142 # Default behaviour, emails from unknown users are ignored
143 143 if logger
144 144 logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]"
145 145 end
146 146 return false
147 147 end
148 148 end
149 149 User.current = @user
150 150 dispatch
151 151 end
152 152
153 153 private
154 154
155 155 MESSAGE_ID_RE = %r{^<?redmine\.([a-z0-9_]+)\-(\d+)\.\d+(\.[a-f0-9]+)?@}
156 156 ISSUE_REPLY_SUBJECT_RE = %r{\[(?:[^\]]*\s+)?#(\d+)\]}
157 157 MESSAGE_REPLY_SUBJECT_RE = %r{\[[^\]]*msg(\d+)\]}
158 158
159 159 def dispatch
160 160 headers = [email.in_reply_to, email.references].flatten.compact
161 161 subject = email.subject.to_s
162 162 if headers.detect {|h| h.to_s =~ MESSAGE_ID_RE}
163 163 klass, object_id = $1, $2.to_i
164 164 method_name = "receive_#{klass}_reply"
165 165 if self.class.private_instance_methods.collect(&:to_s).include?(method_name)
166 166 send method_name, object_id
167 167 else
168 168 # ignoring it
169 169 end
170 170 elsif m = subject.match(ISSUE_REPLY_SUBJECT_RE)
171 171 receive_issue_reply(m[1].to_i)
172 172 elsif m = subject.match(MESSAGE_REPLY_SUBJECT_RE)
173 173 receive_message_reply(m[1].to_i)
174 174 else
175 175 dispatch_to_default
176 176 end
177 177 rescue ActiveRecord::RecordInvalid => e
178 178 # TODO: send a email to the user
179 179 logger.error e.message if logger
180 180 false
181 181 rescue MissingInformation => e
182 182 logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
183 183 false
184 184 rescue UnauthorizedAction => e
185 185 logger.error "MailHandler: unauthorized attempt from #{user}" if logger
186 186 false
187 187 end
188 188
189 189 def dispatch_to_default
190 190 receive_issue
191 191 end
192 192
193 193 # Creates a new issue
194 194 def receive_issue
195 195 project = target_project
196 196 # check permission
197 197 unless handler_options[:no_permission_check]
198 198 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
199 199 end
200 200
201 201 issue = Issue.new(:author => user, :project => project)
202 202 issue.safe_attributes = issue_attributes_from_keywords(issue)
203 203 issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
204 204 issue.subject = cleaned_up_subject
205 205 if issue.subject.blank?
206 206 issue.subject = '(no subject)'
207 207 end
208 208 issue.description = cleaned_up_text_body
209 209 issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
210 210 issue.is_private = (handler_options[:issue][:is_private] == '1')
211 211
212 212 # add To and Cc as watchers before saving so the watchers can reply to Redmine
213 213 add_watchers(issue)
214 214 issue.save!
215 215 add_attachments(issue)
216 216 logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger
217 217 issue
218 218 end
219 219
220 220 # Adds a note to an existing issue
221 221 def receive_issue_reply(issue_id, from_journal=nil)
222 222 issue = Issue.find_by_id(issue_id)
223 223 return unless issue
224 224 # check permission
225 225 unless handler_options[:no_permission_check]
226 226 unless user.allowed_to?(:add_issue_notes, issue.project) ||
227 227 user.allowed_to?(:edit_issues, issue.project)
228 228 raise UnauthorizedAction
229 229 end
230 230 end
231 231
232 232 # ignore CLI-supplied defaults for new issues
233 233 handler_options[:issue].clear
234 234
235 235 journal = issue.init_journal(user)
236 236 if from_journal && from_journal.private_notes?
237 237 # If the received email was a reply to a private note, make the added note private
238 238 issue.private_notes = true
239 239 end
240 240 issue.safe_attributes = issue_attributes_from_keywords(issue)
241 241 issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
242 242 journal.notes = cleaned_up_text_body
243 243 add_attachments(issue)
244 244 issue.save!
245 245 if logger
246 246 logger.info "MailHandler: issue ##{issue.id} updated by #{user}"
247 247 end
248 248 journal
249 249 end
250 250
251 251 # Reply will be added to the issue
252 252 def receive_journal_reply(journal_id)
253 253 journal = Journal.find_by_id(journal_id)
254 254 if journal && journal.journalized_type == 'Issue'
255 255 receive_issue_reply(journal.journalized_id, journal)
256 256 end
257 257 end
258 258
259 259 # Receives a reply to a forum message
260 260 def receive_message_reply(message_id)
261 261 message = Message.find_by_id(message_id)
262 262 if message
263 263 message = message.root
264 264
265 265 unless handler_options[:no_permission_check]
266 266 raise UnauthorizedAction unless user.allowed_to?(:add_messages, message.project)
267 267 end
268 268
269 269 if !message.locked?
270 270 reply = Message.new(:subject => cleaned_up_subject.gsub(%r{^.*msg\d+\]}, '').strip,
271 271 :content => cleaned_up_text_body)
272 272 reply.author = user
273 273 reply.board = message.board
274 274 message.children << reply
275 275 add_attachments(reply)
276 276 reply
277 277 else
278 278 if logger
279 279 logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic"
280 280 end
281 281 end
282 282 end
283 283 end
284 284
285 285 def add_attachments(obj)
286 286 if email.attachments && email.attachments.any?
287 287 email.attachments.each do |attachment|
288 288 next unless accept_attachment?(attachment)
289 289 obj.attachments << Attachment.create(:container => obj,
290 290 :file => attachment.decoded,
291 291 :filename => attachment.filename,
292 292 :author => user,
293 293 :content_type => attachment.mime_type)
294 294 end
295 295 end
296 296 end
297 297
298 298 # Returns false if the +attachment+ of the incoming email should be ignored
299 299 def accept_attachment?(attachment)
300 300 @excluded ||= Setting.mail_handler_excluded_filenames.to_s.split(',').map(&:strip).reject(&:blank?)
301 301 @excluded.each do |pattern|
302 302 regexp = %r{\A#{Regexp.escape(pattern).gsub("\\*", ".*")}\z}i
303 303 if attachment.filename.to_s =~ regexp
304 304 logger.info "MailHandler: ignoring attachment #{attachment.filename} matching #{pattern}"
305 305 return false
306 306 end
307 307 end
308 308 true
309 309 end
310 310
311 311 # Adds To and Cc as watchers of the given object if the sender has the
312 312 # appropriate permission
313 313 def add_watchers(obj)
314 314 if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
315 315 addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
316 316 unless addresses.empty?
317 317 User.active.having_mail(addresses).each do |w|
318 318 obj.add_watcher(w)
319 319 end
320 320 end
321 321 end
322 322 end
323 323
324 324 def get_keyword(attr, options={})
325 325 @keywords ||= {}
326 326 if @keywords.has_key?(attr)
327 327 @keywords[attr]
328 328 else
329 329 @keywords[attr] = begin
330 if (options[:override] || handler_options[:allow_override].include?(attr.to_s.downcase)) &&
331 (v = extract_keyword!(cleaned_up_text_body, attr, options[:format]))
330 override = options.key?(:override) ?
331 options[:override] :
332 (handler_options[:allow_override] & [attr.to_s.downcase.gsub(/\s+/, '_'), 'all']).present?
333
334 if override && (v = extract_keyword!(cleaned_up_text_body, attr, options[:format]))
332 335 v
333 336 elsif !handler_options[:issue][attr].blank?
334 337 handler_options[:issue][attr]
335 338 end
336 339 end
337 340 end
338 341 end
339 342
340 343 # Destructively extracts the value for +attr+ in +text+
341 344 # Returns nil if no matching keyword found
342 345 def extract_keyword!(text, attr, format=nil)
343 346 keys = [attr.to_s.humanize]
344 347 if attr.is_a?(Symbol)
345 348 if user && user.language.present?
346 349 keys << l("field_#{attr}", :default => '', :locale => user.language)
347 350 end
348 351 if Setting.default_language.present?
349 352 keys << l("field_#{attr}", :default => '', :locale => Setting.default_language)
350 353 end
351 354 end
352 355 keys.reject! {|k| k.blank?}
353 356 keys.collect! {|k| Regexp.escape(k)}
354 357 format ||= '.+'
355 358 keyword = nil
356 359 regexp = /^(#{keys.join('|')})[ \t]*:[ \t]*(#{format})\s*$/i
357 360 if m = text.match(regexp)
358 361 keyword = m[2].strip
359 362 text.sub!(regexp, '')
360 363 end
361 364 keyword
362 365 end
363 366
364 367 def target_project
365 368 # TODO: other ways to specify project:
366 369 # * parse the email To field
367 370 # * specific project (eg. Setting.mail_handler_target_project)
368 371 target = Project.find_by_identifier(get_keyword(:project))
369 372 if target.nil?
370 373 # Invalid project keyword, use the project specified as the default one
371 374 default_project = handler_options[:issue][:project]
372 375 if default_project.present?
373 376 target = Project.find_by_identifier(default_project)
374 377 end
375 378 end
376 379 raise MissingInformation.new('Unable to determine target project') if target.nil?
377 380 target
378 381 end
379 382
380 383 # Returns a Hash of issue attributes extracted from keywords in the email body
381 384 def issue_attributes_from_keywords(issue)
382 385 attrs = {
383 386 'tracker_id' => (k = get_keyword(:tracker)) && issue.project.trackers.named(k).first.try(:id),
384 387 'status_id' => (k = get_keyword(:status)) && IssueStatus.named(k).first.try(:id),
385 388 'priority_id' => (k = get_keyword(:priority)) && IssuePriority.named(k).first.try(:id),
386 389 'category_id' => (k = get_keyword(:category)) && issue.project.issue_categories.named(k).first.try(:id),
387 390 'assigned_to_id' => (k = get_keyword(:assigned_to)) && find_assignee_from_keyword(k, issue).try(:id),
388 391 'fixed_version_id' => (k = get_keyword(:fixed_version)) && issue.project.shared_versions.named(k).first.try(:id),
389 392 'start_date' => get_keyword(:start_date, :format => '\d{4}-\d{2}-\d{2}'),
390 393 'due_date' => get_keyword(:due_date, :format => '\d{4}-\d{2}-\d{2}'),
391 394 'estimated_hours' => get_keyword(:estimated_hours),
392 395 'done_ratio' => get_keyword(:done_ratio, :format => '(\d|10)?0')
393 396 }.delete_if {|k, v| v.blank? }
394 397
395 398 if issue.new_record? && attrs['tracker_id'].nil?
396 399 attrs['tracker_id'] = issue.project.trackers.first.try(:id)
397 400 end
398 401
399 402 attrs
400 403 end
401 404
402 405 # Returns a Hash of issue custom field values extracted from keywords in the email body
403 406 def custom_field_values_from_keywords(customized)
404 407 customized.custom_field_values.inject({}) do |h, v|
405 408 if keyword = get_keyword(v.custom_field.name)
406 409 h[v.custom_field.id.to_s] = v.custom_field.value_from_keyword(keyword, customized)
407 410 end
408 411 h
409 412 end
410 413 end
411 414
412 415 # Returns the text/plain part of the email
413 416 # If not found (eg. HTML-only email), returns the body with tags removed
414 417 def plain_text_body
415 418 return @plain_text_body unless @plain_text_body.nil?
416 419
417 420 parts = if (text_parts = email.all_parts.select {|p| p.mime_type == 'text/plain'}).present?
418 421 text_parts
419 422 elsif (html_parts = email.all_parts.select {|p| p.mime_type == 'text/html'}).present?
420 423 html_parts
421 424 else
422 425 [email]
423 426 end
424 427
425 428 parts.reject! do |part|
426 429 part.attachment?
427 430 end
428 431
429 432 @plain_text_body = parts.map do |p|
430 433 body_charset = Mail::RubyVer.respond_to?(:pick_encoding) ?
431 434 Mail::RubyVer.pick_encoding(p.charset).to_s : p.charset
432 435
433 436 body = Redmine::CodesetUtil.to_utf8(p.body.decoded, body_charset)
434 437 # convert html parts to text
435 438 p.mime_type == 'text/html' ? self.class.html_body_to_text(body) : self.class.plain_text_body_to_text(body)
436 439 end.join("\r\n")
437 440
438 441 @plain_text_body
439 442 end
440 443
441 444 def cleaned_up_text_body
442 445 @cleaned_up_text_body ||= cleanup_body(plain_text_body)
443 446 end
444 447
445 448 def cleaned_up_subject
446 449 subject = email.subject.to_s
447 450 subject.strip[0,255]
448 451 end
449 452
450 453 # Converts a HTML email body to text
451 454 def self.html_body_to_text(html)
452 455 Redmine::WikiFormatting.html_parser.to_text(html)
453 456 end
454 457
455 458 # Converts a plain/text email body to text
456 459 def self.plain_text_body_to_text(text)
457 460 # Removes leading spaces that would cause the line to be rendered as
458 461 # preformatted text with textile
459 462 text.gsub(/^ +(?![*#])/, '')
460 463 end
461 464
462 465 def self.assign_string_attribute_with_limit(object, attribute, value, limit=nil)
463 466 limit ||= object.class.columns_hash[attribute.to_s].limit || 255
464 467 value = value.to_s.slice(0, limit)
465 468 object.send("#{attribute}=", value)
466 469 end
467 470
468 471 # Returns a User from an email address and a full name
469 472 def self.new_user_from_attributes(email_address, fullname=nil)
470 473 user = User.new
471 474
472 475 # Truncating the email address would result in an invalid format
473 476 user.mail = email_address
474 477 assign_string_attribute_with_limit(user, 'login', email_address, User::LOGIN_LENGTH_LIMIT)
475 478
476 479 names = fullname.blank? ? email_address.gsub(/@.*$/, '').split('.') : fullname.split
477 480 assign_string_attribute_with_limit(user, 'firstname', names.shift, 30)
478 481 assign_string_attribute_with_limit(user, 'lastname', names.join(' '), 30)
479 482 user.lastname = '-' if user.lastname.blank?
480 483 user.language = Setting.default_language
481 484 user.generate_password = true
482 485 user.mail_notification = 'only_my_events'
483 486
484 487 unless user.valid?
485 488 user.login = "user#{Redmine::Utils.random_hex(6)}" unless user.errors[:login].blank?
486 489 user.firstname = "-" unless user.errors[:firstname].blank?
487 490 (puts user.errors[:lastname];user.lastname = "-") unless user.errors[:lastname].blank?
488 491 end
489 492
490 493 user
491 494 end
492 495
493 496 # Creates a User for the +email+ sender
494 497 # Returns the user or nil if it could not be created
495 498 def create_user_from_email
496 499 from = email.header['from'].to_s
497 500 addr, name = from, nil
498 501 if m = from.match(/^"?(.+?)"?\s+<(.+@.+)>$/)
499 502 addr, name = m[2], m[1]
500 503 end
501 504 if addr.present?
502 505 user = self.class.new_user_from_attributes(addr, name)
503 506 if handler_options[:no_notification]
504 507 user.mail_notification = 'none'
505 508 end
506 509 if user.save
507 510 user
508 511 else
509 512 logger.error "MailHandler: failed to create User: #{user.errors.full_messages}" if logger
510 513 nil
511 514 end
512 515 else
513 516 logger.error "MailHandler: failed to create User: no FROM address found" if logger
514 517 nil
515 518 end
516 519 end
517 520
518 521 # Adds the newly created user to default group
519 522 def add_user_to_group(default_group)
520 523 if default_group.present?
521 524 default_group.split(',').each do |group_name|
522 525 if group = Group.named(group_name).first
523 526 group.users << @user
524 527 elsif logger
525 528 logger.warn "MailHandler: could not add user to [#{group_name}], group not found"
526 529 end
527 530 end
528 531 end
529 532 end
530 533
531 534 # Removes the email body of text after the truncation configurations.
532 535 def cleanup_body(body)
533 536 delimiters = Setting.mail_handler_body_delimiters.to_s.split(/[\r\n]+/).reject(&:blank?).map {|s| Regexp.escape(s)}
534 537 unless delimiters.empty?
535 538 regex = Regexp.new("^[> ]*(#{ delimiters.join('|') })\s*[\r\n].*", Regexp::MULTILINE)
536 539 body = body.gsub(regex, '')
537 540 end
538 541 body.strip
539 542 end
540 543
541 544 def find_assignee_from_keyword(keyword, issue)
542 545 Principal.detect_by_keyword(issue.assignable_users, keyword)
543 546 end
544 547 end
@@ -1,185 +1,169
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 namespace :redmine do
19 19 namespace :email do
20 20
21 21 desc <<-END_DESC
22 22 Read an email from standard input.
23 23
24 General options:
25 unknown_user=ACTION how to handle emails from an unknown user
26 ACTION can be one of the following values:
27 ignore: email is ignored (default)
28 accept: accept as anonymous user
29 create: create a user account
30 no_permission_check=1 disable permission checking when receiving
31 the email
32 no_account_notice=1 disable new user account notification
33 default_group=foo,bar adds created user to foo and bar groups
34
35 Issue attributes control options:
36 project=PROJECT identifier of the target project
37 status=STATUS name of the target status
38 tracker=TRACKER name of the target tracker
39 category=CATEGORY name of the target category
40 priority=PRIORITY name of the target priority
41 allow_override=ATTRS allow email content to override attributes
42 specified by previous options
43 ATTRS is a comma separated list of attributes
44
45 Examples:
46 # No project specified. Emails MUST contain the 'Project' keyword:
47 rake redmine:email:read RAILS_ENV="production" < raw_email
48
49 # Fixed project and default tracker specified, but emails can override
50 # both tracker and priority attributes:
51 rake redmine:email:read RAILS_ENV="production" \\
52 project=foo \\
53 tracker=bug \\
54 allow_override=tracker,priority < raw_email
24 See redmine:email:receive_imap for more options and examples.
55 25 END_DESC
56 26
57 27 task :read => :environment do
58 28 Mailer.with_synched_deliveries do
59 29 MailHandler.receive(STDIN.read, MailHandler.extract_options_from_env(ENV))
60 30 end
61 31 end
62 32
63 33 desc <<-END_DESC
64 34 Read emails from an IMAP server.
65 35
66 General options:
36 Available IMAP options:
37 host=HOST IMAP server host (default: 127.0.0.1)
38 port=PORT IMAP server port (default: 143)
39 ssl=SSL Use SSL/TLS? (default: false)
40 starttls=STARTTLS Use STARTTLS? (default: false)
41 username=USERNAME IMAP account
42 password=PASSWORD IMAP password
43 folder=FOLDER IMAP folder to read (default: INBOX)
44
45 Processed emails control options:
46 move_on_success=MAILBOX move emails that were successfully received
47 to MAILBOX instead of deleting them
48 move_on_failure=MAILBOX move emails that were ignored to MAILBOX
49
50 User and permissions options:
67 51 unknown_user=ACTION how to handle emails from an unknown user
68 52 ACTION can be one of the following values:
69 53 ignore: email is ignored (default)
70 54 accept: accept as anonymous user
71 55 create: create a user account
72 56 no_permission_check=1 disable permission checking when receiving
73 57 the email
74 58 no_account_notice=1 disable new user account notification
75 59 default_group=foo,bar adds created user to foo and bar groups
76 60
77 Available IMAP options:
78 host=HOST IMAP server host (default: 127.0.0.1)
79 port=PORT IMAP server port (default: 143)
80 ssl=SSL Use SSL/TLS? (default: false)
81 starttls=STARTTLS Use STARTTLS? (default: false)
82 username=USERNAME IMAP account
83 password=PASSWORD IMAP password
84 folder=FOLDER IMAP folder to read (default: INBOX)
85
86 61 Issue attributes control options:
87 62 project=PROJECT identifier of the target project
88 63 status=STATUS name of the target status
89 64 tracker=TRACKER name of the target tracker
90 65 category=CATEGORY name of the target category
91 66 priority=PRIORITY name of the target priority
92 67 private create new issues as private
93 allow_override=ATTRS allow email content to override attributes
94 specified by previous options
68 allow_override=ATTRS allow email content to set attributes values
95 69 ATTRS is a comma separated list of attributes
70 or 'all' to allow all attributes to be overridable
71 (see below for details)
96 72
97 Processed emails control options:
98 move_on_success=MAILBOX move emails that were successfully received
99 to MAILBOX instead of deleting them
100 move_on_failure=MAILBOX move emails that were ignored to MAILBOX
73 Overrides:
74 ATTRS is a comma separated list of attributes among:
75 * project, tracker, status, priority, category, assigned_to, fixed_version,
76 start_date, due_date, estimated_hours, done_ratio
77 * custom fields names with underscores instead of spaces (case insensitive)
78
79 Example: allow_override=project,priority,my_custom_field
80
81 If the project option is not set, project is overridable by default for
82 emails that create new issues.
83
84 You can use allow_override=all to allow all attributes to be overridable.
101 85
102 86 Examples:
103 87 # No project specified. Emails MUST contain the 'Project' keyword:
104 88
105 89 rake redmine:email:receive_imap RAILS_ENV="production" \\
106 90 host=imap.foo.bar username=redmine@example.net password=xxx
107 91
108 92
109 93 # Fixed project and default tracker specified, but emails can override
110 94 # both tracker and priority attributes:
111 95
112 96 rake redmine:email:receive_imap RAILS_ENV="production" \\
113 97 host=imap.foo.bar username=redmine@example.net password=xxx ssl=1 \\
114 98 project=foo \\
115 99 tracker=bug \\
116 100 allow_override=tracker,priority
117 101 END_DESC
118 102
119 103 task :receive_imap => :environment do
120 104 imap_options = {:host => ENV['host'],
121 105 :port => ENV['port'],
122 106 :ssl => ENV['ssl'],
123 107 :starttls => ENV['starttls'],
124 108 :username => ENV['username'],
125 109 :password => ENV['password'],
126 110 :folder => ENV['folder'],
127 111 :move_on_success => ENV['move_on_success'],
128 112 :move_on_failure => ENV['move_on_failure']}
129 113
130 114 Mailer.with_synched_deliveries do
131 115 Redmine::IMAP.check(imap_options, MailHandler.extract_options_from_env(ENV))
132 116 end
133 117 end
134 118
135 119 desc <<-END_DESC
136 120 Read emails from an POP3 server.
137 121
138 122 Available POP3 options:
139 123 host=HOST POP3 server host (default: 127.0.0.1)
140 124 port=PORT POP3 server port (default: 110)
141 125 username=USERNAME POP3 account
142 126 password=PASSWORD POP3 password
143 127 apop=1 use APOP authentication (default: false)
144 128 ssl=SSL Use SSL? (default: false)
145 129 delete_unprocessed=1 delete messages that could not be processed
146 130 successfully from the server (default
147 131 behaviour is to leave them on the server)
148 132
149 133 See redmine:email:receive_imap for more options and examples.
150 134 END_DESC
151 135
152 136 task :receive_pop3 => :environment do
153 137 pop_options = {:host => ENV['host'],
154 138 :port => ENV['port'],
155 139 :apop => ENV['apop'],
156 140 :ssl => ENV['ssl'],
157 141 :username => ENV['username'],
158 142 :password => ENV['password'],
159 143 :delete_unprocessed => ENV['delete_unprocessed']}
160 144
161 145 Mailer.with_synched_deliveries do
162 146 Redmine::POP3.check(pop_options, MailHandler.extract_options_from_env(ENV))
163 147 end
164 148 end
165 149
166 150 desc "Send a test email to the user with the provided login name"
167 151 task :test, [:login] => :environment do |task, args|
168 152 include Redmine::I18n
169 153 abort l(:notice_email_error, "Please include the user login to test with. Example: rake redmine:email:test[login]") if args[:login].blank?
170 154
171 155 user = User.find_by_login(args[:login])
172 156 abort l(:notice_email_error, "User #{args[:login]} not found") unless user && user.logged?
173 157
174 158 ActionMailer::Base.raise_delivery_errors = true
175 159 begin
176 160 Mailer.with_synched_deliveries do
177 161 Mailer.test_email(user).deliver
178 162 end
179 163 puts l(:notice_email_sent, user.mail)
180 164 rescue Exception => e
181 165 abort l(:notice_email_error, e.message)
182 166 end
183 167 end
184 168 end
185 169 end
@@ -1,1002 +1,1041
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 def test_add_issue
43 def test_add_issue_with_specific_overrides
44 44 ActionMailer::Base.deliveries.clear
45 45 lft1 = new_issue_lft
46 # This email contains: 'Project: onlinestore'
47 46 issue = submit_email('ticket_on_given_project.eml',
48 47 :allow_override => ['status', 'start_date', 'due_date', 'assigned_to', 'fixed_version', 'estimated_hours', 'done_ratio']
49 48 )
50 49 assert issue.is_a?(Issue)
51 50 assert !issue.new_record?
52 51 issue.reload
53 52 assert_equal Project.find(2), issue.project
54 53 assert_equal issue.project.trackers.first, issue.tracker
55 54 assert_equal 'New ticket on a given project', issue.subject
56 55 assert_equal User.find_by_login('jsmith'), issue.author
57 56 assert_equal IssueStatus.find_by_name('Resolved'), issue.status
58 57 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
59 58 assert_equal '2010-01-01', issue.start_date.to_s
60 59 assert_equal '2010-12-31', issue.due_date.to_s
61 60 assert_equal User.find_by_login('jsmith'), issue.assigned_to
62 61 assert_equal Version.find_by_name('Alpha'), issue.fixed_version
63 62 assert_equal 2.5, issue.estimated_hours
64 63 assert_equal 30, issue.done_ratio
65 64 assert_equal [issue.id, lft1, lft1 + 1], [issue.root_id, issue.lft, issue.rgt]
66 65 # keywords should be removed from the email body
67 66 assert !issue.description.match(/^Project:/i)
68 67 assert !issue.description.match(/^Status:/i)
69 68 assert !issue.description.match(/^Start Date:/i)
70 69 # Email notification should be sent
71 70 mail = ActionMailer::Base.deliveries.last
72 71 assert_not_nil mail
73 72 assert mail.subject.include?("##{issue.id}")
74 73 assert mail.subject.include?('New ticket on a given project')
75 74 end
76 75
76 def test_add_issue_with_all_overrides
77 ActionMailer::Base.deliveries.clear
78 lft1 = new_issue_lft
79 issue = submit_email('ticket_on_given_project.eml', :allow_override => 'all')
80 assert issue.is_a?(Issue)
81 assert !issue.new_record?
82 issue.reload
83 assert_equal Project.find(2), issue.project
84 assert_equal issue.project.trackers.first, issue.tracker
85 assert_equal IssueStatus.find_by_name('Resolved'), issue.status
86 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
87 assert_equal '2010-01-01', issue.start_date.to_s
88 assert_equal '2010-12-31', issue.due_date.to_s
89 assert_equal User.find_by_login('jsmith'), issue.assigned_to
90 assert_equal Version.find_by_name('Alpha'), issue.fixed_version
91 assert_equal 2.5, issue.estimated_hours
92 assert_equal 30, issue.done_ratio
93 end
94
95 def test_add_issue_without_overrides_should_ignore_attributes
96 WorkflowRule.delete_all
97 issue = submit_email('ticket_on_given_project.eml')
98 assert issue.is_a?(Issue)
99 assert !issue.new_record?
100 issue.reload
101 assert_equal Project.find(2), issue.project
102 assert_equal 'New ticket on a given project', issue.subject
103 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
104 assert_equal User.find_by_login('jsmith'), issue.author
105
106 assert_equal issue.project.trackers.first, issue.tracker
107 assert_equal 'New', issue.status.name
108 assert_not_equal '2010-01-01', issue.start_date.to_s
109 assert_nil issue.due_date
110 assert_nil issue.assigned_to
111 assert_nil issue.fixed_version
112 assert_nil issue.estimated_hours
113 assert_equal 0, issue.done_ratio
114 end
115
77 116 def test_add_issue_with_default_tracker
78 117 # This email contains: 'Project: onlinestore'
79 118 issue = submit_email(
80 119 'ticket_on_given_project.eml',
81 120 :issue => {:tracker => 'Support request'}
82 121 )
83 122 assert issue.is_a?(Issue)
84 123 assert !issue.new_record?
85 124 issue.reload
86 125 assert_equal 'Support request', issue.tracker.name
87 126 end
88 127
89 def test_add_issue_with_status
128 def test_add_issue_with_status_override
90 129 # This email contains: 'Project: onlinestore' and 'Status: Resolved'
91 issue = submit_email('ticket_on_given_project.eml')
130 issue = submit_email('ticket_on_given_project.eml', :allow_override => ['status'])
92 131 assert issue.is_a?(Issue)
93 132 assert !issue.new_record?
94 133 issue.reload
95 134 assert_equal Project.find(2), issue.project
96 135 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
97 136 end
98 137
99 138 def test_add_issue_should_accept_is_private_attribute
100 139 issue = submit_email('ticket_on_given_project.eml', :issue => {:is_private => '1'})
101 140 assert issue.is_a?(Issue)
102 141 assert !issue.new_record?
103 142 assert_equal true, issue.reload.is_private
104 143 end
105 144
106 145 def test_add_issue_with_attributes_override
107 146 issue = submit_email(
108 147 'ticket_with_attributes.eml',
109 148 :allow_override => 'tracker,category,priority'
110 149 )
111 150 assert issue.is_a?(Issue)
112 151 assert !issue.new_record?
113 152 issue.reload
114 153 assert_equal 'New ticket on a given project', issue.subject
115 154 assert_equal User.find_by_login('jsmith'), issue.author
116 155 assert_equal Project.find(2), issue.project
117 156 assert_equal 'Feature request', issue.tracker.to_s
118 157 assert_equal 'Stock management', issue.category.to_s
119 158 assert_equal 'Urgent', issue.priority.to_s
120 159 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
121 160 end
122 161
123 162 def test_add_issue_with_group_assignment
124 163 with_settings :issue_group_assignment => '1' do
125 164 issue = submit_email('ticket_on_given_project.eml', :allow_override => ['assigned_to']) do |email|
126 165 email.gsub!('Assigned to: John Smith', 'Assigned to: B Team')
127 166 end
128 167 assert issue.is_a?(Issue)
129 168 assert !issue.new_record?
130 169 issue.reload
131 170 assert_equal Group.find(11), issue.assigned_to
132 171 end
133 172 end
134 173
135 174 def test_add_issue_with_partial_attributes_override
136 175 issue = submit_email(
137 176 'ticket_with_attributes.eml',
138 177 :issue => {:priority => 'High'},
139 178 :allow_override => ['tracker']
140 179 )
141 180 assert issue.is_a?(Issue)
142 181 assert !issue.new_record?
143 182 issue.reload
144 183 assert_equal 'New ticket on a given project', issue.subject
145 184 assert_equal User.find_by_login('jsmith'), issue.author
146 185 assert_equal Project.find(2), issue.project
147 186 assert_equal 'Feature request', issue.tracker.to_s
148 187 assert_nil issue.category
149 188 assert_equal 'High', issue.priority.to_s
150 189 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
151 190 end
152 191
153 192 def test_add_issue_with_spaces_between_attribute_and_separator
154 193 issue = submit_email(
155 194 'ticket_with_spaces_between_attribute_and_separator.eml',
156 195 :allow_override => 'tracker,category,priority'
157 196 )
158 197 assert issue.is_a?(Issue)
159 198 assert !issue.new_record?
160 199 issue.reload
161 200 assert_equal 'New ticket on a given project', issue.subject
162 201 assert_equal User.find_by_login('jsmith'), issue.author
163 202 assert_equal Project.find(2), issue.project
164 203 assert_equal 'Feature request', issue.tracker.to_s
165 204 assert_equal 'Stock management', issue.category.to_s
166 205 assert_equal 'Urgent', issue.priority.to_s
167 206 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
168 207 end
169 208
170 209 def test_add_issue_with_attachment_to_specific_project
171 210 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
172 211 assert issue.is_a?(Issue)
173 212 assert !issue.new_record?
174 213 issue.reload
175 214 assert_equal 'Ticket created by email with attachment', issue.subject
176 215 assert_equal User.find_by_login('jsmith'), issue.author
177 216 assert_equal Project.find(2), issue.project
178 217 assert_equal 'This is a new ticket with attachments', issue.description
179 218 # Attachment properties
180 219 assert_equal 1, issue.attachments.size
181 220 assert_equal 'Paella.jpg', issue.attachments.first.filename
182 221 assert_equal 'image/jpeg', issue.attachments.first.content_type
183 222 assert_equal 10790, issue.attachments.first.filesize
184 223 end
185 224
186 225 def test_add_issue_with_custom_fields
187 226 issue = submit_email('ticket_with_custom_fields.eml',
188 :issue => {:project => 'onlinestore'}, :allow_override => ['database', 'Searchable field']
227 :issue => {:project => 'onlinestore'}, :allow_override => ['database', 'Searchable_field']
189 228 )
190 229 assert issue.is_a?(Issue)
191 230 assert !issue.new_record?
192 231 issue.reload
193 232 assert_equal 'New ticket with custom field values', issue.subject
194 233 assert_equal 'PostgreSQL', issue.custom_field_value(1)
195 234 assert_equal 'Value for a custom field', issue.custom_field_value(2)
196 235 assert !issue.description.match(/^searchable field:/i)
197 236 end
198 237
199 238 def test_add_issue_with_version_custom_fields
200 239 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true, :tracker_ids => [1,2,3])
201 240
202 241 issue = submit_email('ticket_with_custom_fields.eml',
203 242 :issue => {:project => 'ecookbook'}, :allow_override => ['affected version']
204 243 ) do |email|
205 244 email << "Affected version: 1.0\n"
206 245 end
207 246 assert issue.is_a?(Issue)
208 247 assert !issue.new_record?
209 248 issue.reload
210 249 assert_equal '2', issue.custom_field_value(field)
211 250 end
212 251
213 252 def test_add_issue_should_match_assignee_on_display_name
214 253 user = User.generate!(:firstname => 'Foo Bar', :lastname => 'Foo Baz')
215 254 User.add_to_project(user, Project.find(2))
216 255 issue = submit_email('ticket_on_given_project.eml', :allow_override => ['assigned_to']) do |email|
217 256 email.sub!(/^Assigned to.*$/, 'Assigned to: Foo Bar Foo baz')
218 257 end
219 258 assert issue.is_a?(Issue)
220 259 assert_equal user, issue.assigned_to
221 260 end
222 261
223 262 def test_add_issue_should_set_default_start_date
224 263 with_settings :default_issue_start_date_to_creation_date => '1' do
225 264 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
226 265 assert issue.is_a?(Issue)
227 266 assert_equal Date.today, issue.start_date
228 267 end
229 268 end
230 269
231 270 def test_add_issue_with_cc
232 271 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
233 272 assert issue.is_a?(Issue)
234 273 assert !issue.new_record?
235 274 issue.reload
236 275 assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
237 276 assert_equal 1, issue.watcher_user_ids.size
238 277 end
239 278
240 279 def test_add_issue_from_additional_email_address
241 280 user = User.find(2)
242 281 user.mail = 'mainaddress@somenet.foo'
243 282 user.save!
244 283 EmailAddress.create!(:user => user, :address => 'jsmith@somenet.foo')
245 284
246 285 issue = submit_email('ticket_on_given_project.eml')
247 286 assert issue
248 287 assert_equal user, issue.author
249 288 end
250 289
251 290 def test_add_issue_by_unknown_user
252 291 assert_no_difference 'User.count' do
253 292 assert_equal false,
254 293 submit_email(
255 294 'ticket_by_unknown_user.eml',
256 295 :issue => {:project => 'ecookbook'}
257 296 )
258 297 end
259 298 end
260 299
261 300 def test_add_issue_by_anonymous_user
262 301 Role.anonymous.add_permission!(:add_issues)
263 302 assert_no_difference 'User.count' do
264 303 issue = submit_email(
265 304 'ticket_by_unknown_user.eml',
266 305 :issue => {:project => 'ecookbook'},
267 306 :unknown_user => 'accept'
268 307 )
269 308 assert issue.is_a?(Issue)
270 309 assert issue.author.anonymous?
271 310 end
272 311 end
273 312
274 313 def test_add_issue_by_anonymous_user_with_no_from_address
275 314 Role.anonymous.add_permission!(:add_issues)
276 315 assert_no_difference 'User.count' do
277 316 issue = submit_email(
278 317 'ticket_by_empty_user.eml',
279 318 :issue => {:project => 'ecookbook'},
280 319 :unknown_user => 'accept'
281 320 )
282 321 assert issue.is_a?(Issue)
283 322 assert issue.author.anonymous?
284 323 end
285 324 end
286 325
287 326 def test_add_issue_by_anonymous_user_on_private_project
288 327 Role.anonymous.add_permission!(:add_issues)
289 328 assert_no_difference 'User.count' do
290 329 assert_no_difference 'Issue.count' do
291 330 assert_equal false,
292 331 submit_email(
293 332 'ticket_by_unknown_user.eml',
294 333 :issue => {:project => 'onlinestore'},
295 334 :unknown_user => 'accept'
296 335 )
297 336 end
298 337 end
299 338 end
300 339
301 340 def test_add_issue_by_anonymous_user_on_private_project_without_permission_check
302 341 lft1 = new_issue_lft
303 342 assert_no_difference 'User.count' do
304 343 assert_difference 'Issue.count' do
305 344 issue = submit_email(
306 345 'ticket_by_unknown_user.eml',
307 346 :issue => {:project => 'onlinestore'},
308 347 :no_permission_check => '1',
309 348 :unknown_user => 'accept'
310 349 )
311 350 assert issue.is_a?(Issue)
312 351 assert issue.author.anonymous?
313 352 assert !issue.project.is_public?
314 353 assert_equal [issue.id, lft1, lft1 + 1], [issue.root_id, issue.lft, issue.rgt]
315 354 end
316 355 end
317 356 end
318 357
319 358 def test_add_issue_by_created_user
320 359 Setting.default_language = 'en'
321 360 assert_difference 'User.count' do
322 361 issue = submit_email(
323 362 'ticket_by_unknown_user.eml',
324 363 :issue => {:project => 'ecookbook'},
325 364 :unknown_user => 'create'
326 365 )
327 366 assert issue.is_a?(Issue)
328 367 assert issue.author.active?
329 368 assert_equal 'john.doe@somenet.foo', issue.author.mail
330 369 assert_equal 'John', issue.author.firstname
331 370 assert_equal 'Doe', issue.author.lastname
332 371
333 372 # account information
334 373 email = ActionMailer::Base.deliveries.first
335 374 assert_not_nil email
336 375 assert email.subject.include?('account activation')
337 376 login = mail_body(email).match(/\* Login: (.*)$/)[1].strip
338 377 password = mail_body(email).match(/\* Password: (.*)$/)[1].strip
339 378 assert_equal issue.author, User.try_to_login(login, password)
340 379 end
341 380 end
342 381
343 382 def test_created_user_should_be_added_to_groups
344 383 group1 = Group.generate!
345 384 group2 = Group.generate!
346 385
347 386 assert_difference 'User.count' do
348 387 submit_email(
349 388 'ticket_by_unknown_user.eml',
350 389 :issue => {:project => 'ecookbook'},
351 390 :unknown_user => 'create',
352 391 :default_group => "#{group1.name},#{group2.name}"
353 392 )
354 393 end
355 394 user = User.order('id DESC').first
356 395 assert_equal [group1, group2].sort, user.groups.sort
357 396 end
358 397
359 398 def test_created_user_should_not_receive_account_information_with_no_account_info_option
360 399 assert_difference 'User.count' do
361 400 submit_email(
362 401 'ticket_by_unknown_user.eml',
363 402 :issue => {:project => 'ecookbook'},
364 403 :unknown_user => 'create',
365 404 :no_account_notice => '1'
366 405 )
367 406 end
368 407
369 408 # only 1 email for the new issue notification
370 409 assert_equal 1, ActionMailer::Base.deliveries.size
371 410 email = ActionMailer::Base.deliveries.first
372 411 assert_include 'Ticket by unknown user', email.subject
373 412 end
374 413
375 414 def test_created_user_should_have_mail_notification_to_none_with_no_notification_option
376 415 assert_difference 'User.count' do
377 416 submit_email(
378 417 'ticket_by_unknown_user.eml',
379 418 :issue => {:project => 'ecookbook'},
380 419 :unknown_user => 'create',
381 420 :no_notification => '1'
382 421 )
383 422 end
384 423 user = User.order('id DESC').first
385 424 assert_equal 'none', user.mail_notification
386 425 end
387 426
388 427 def test_add_issue_without_from_header
389 428 Role.anonymous.add_permission!(:add_issues)
390 429 assert_equal false, submit_email('ticket_without_from_header.eml')
391 430 end
392 431
393 432 def test_add_issue_with_invalid_attributes
394 433 with_settings :default_issue_start_date_to_creation_date => '0' do
395 434 issue = submit_email(
396 435 'ticket_with_invalid_attributes.eml',
397 436 :allow_override => 'tracker,category,priority'
398 437 )
399 438 assert issue.is_a?(Issue)
400 439 assert !issue.new_record?
401 440 issue.reload
402 441 assert_nil issue.assigned_to
403 442 assert_nil issue.start_date
404 443 assert_nil issue.due_date
405 444 assert_equal 0, issue.done_ratio
406 445 assert_equal 'Normal', issue.priority.to_s
407 446 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
408 447 end
409 448 end
410 449
411 450 def test_add_issue_with_invalid_project_should_be_assigned_to_default_project
412 451 issue = submit_email('ticket_on_given_project.eml', :issue => {:project => 'ecookbook'}, :allow_override => 'project') do |email|
413 452 email.gsub!(/^Project:.+$/, 'Project: invalid')
414 453 end
415 454 assert issue.is_a?(Issue)
416 455 assert !issue.new_record?
417 456 assert_equal 'ecookbook', issue.project.identifier
418 457 end
419 458
420 459 def test_add_issue_with_localized_attributes
421 460 User.find_by_mail('jsmith@somenet.foo').update_attribute 'language', 'fr'
422 461 issue = submit_email(
423 462 'ticket_with_localized_attributes.eml',
424 463 :allow_override => 'tracker,category,priority'
425 464 )
426 465 assert issue.is_a?(Issue)
427 466 assert !issue.new_record?
428 467 issue.reload
429 468 assert_equal 'New ticket on a given project', issue.subject
430 469 assert_equal User.find_by_login('jsmith'), issue.author
431 470 assert_equal Project.find(2), issue.project
432 471 assert_equal 'Feature request', issue.tracker.to_s
433 472 assert_equal 'Stock management', issue.category.to_s
434 473 assert_equal 'Urgent', issue.priority.to_s
435 474 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
436 475 end
437 476
438 477 def test_add_issue_with_japanese_keywords
439 478 ja_dev = "\xe9\x96\x8b\xe7\x99\xba".force_encoding('UTF-8')
440 479 tracker = Tracker.generate!(:name => ja_dev)
441 480 Project.find(1).trackers << tracker
442 481 issue = submit_email(
443 482 'japanese_keywords_iso_2022_jp.eml',
444 483 :issue => {:project => 'ecookbook'},
445 484 :allow_override => 'tracker'
446 485 )
447 486 assert_kind_of Issue, issue
448 487 assert_equal tracker, issue.tracker
449 488 end
450 489
451 490 def test_add_issue_from_apple_mail
452 491 issue = submit_email(
453 492 'apple_mail_with_attachment.eml',
454 493 :issue => {:project => 'ecookbook'}
455 494 )
456 495 assert_kind_of Issue, issue
457 496 assert_equal 1, issue.attachments.size
458 497
459 498 attachment = issue.attachments.first
460 499 assert_equal 'paella.jpg', attachment.filename
461 500 assert_equal 10790, attachment.filesize
462 501 assert File.exist?(attachment.diskfile)
463 502 assert_equal 10790, File.size(attachment.diskfile)
464 503 assert_equal 'caaf384198bcbc9563ab5c058acd73cd', attachment.digest
465 504 end
466 505
467 506 def test_thunderbird_with_attachment_ja
468 507 issue = submit_email(
469 508 'thunderbird_with_attachment_ja.eml',
470 509 :issue => {:project => 'ecookbook'}
471 510 )
472 511 assert_kind_of Issue, issue
473 512 assert_equal 1, issue.attachments.size
474 513 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt".force_encoding('UTF-8')
475 514 attachment = issue.attachments.first
476 515 assert_equal ja, attachment.filename
477 516 assert_equal 5, attachment.filesize
478 517 assert File.exist?(attachment.diskfile)
479 518 assert_equal 5, File.size(attachment.diskfile)
480 519 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
481 520 end
482 521
483 522 def test_gmail_with_attachment_ja
484 523 issue = submit_email(
485 524 'gmail_with_attachment_ja.eml',
486 525 :issue => {:project => 'ecookbook'}
487 526 )
488 527 assert_kind_of Issue, issue
489 528 assert_equal 1, issue.attachments.size
490 529 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt".force_encoding('UTF-8')
491 530 attachment = issue.attachments.first
492 531 assert_equal ja, attachment.filename
493 532 assert_equal 5, attachment.filesize
494 533 assert File.exist?(attachment.diskfile)
495 534 assert_equal 5, File.size(attachment.diskfile)
496 535 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
497 536 end
498 537
499 538 def test_thunderbird_with_attachment_latin1
500 539 issue = submit_email(
501 540 'thunderbird_with_attachment_iso-8859-1.eml',
502 541 :issue => {:project => 'ecookbook'}
503 542 )
504 543 assert_kind_of Issue, issue
505 544 assert_equal 1, issue.attachments.size
506 545 u = "".force_encoding('UTF-8')
507 546 u1 = "\xc3\x84\xc3\xa4\xc3\x96\xc3\xb6\xc3\x9c\xc3\xbc".force_encoding('UTF-8')
508 547 11.times { u << u1 }
509 548 attachment = issue.attachments.first
510 549 assert_equal "#{u}.png", attachment.filename
511 550 assert_equal 130, attachment.filesize
512 551 assert File.exist?(attachment.diskfile)
513 552 assert_equal 130, File.size(attachment.diskfile)
514 553 assert_equal '4d80e667ac37dddfe05502530f152abb', attachment.digest
515 554 end
516 555
517 556 def test_gmail_with_attachment_latin1
518 557 issue = submit_email(
519 558 'gmail_with_attachment_iso-8859-1.eml',
520 559 :issue => {:project => 'ecookbook'}
521 560 )
522 561 assert_kind_of Issue, issue
523 562 assert_equal 1, issue.attachments.size
524 563 u = "".force_encoding('UTF-8')
525 564 u1 = "\xc3\x84\xc3\xa4\xc3\x96\xc3\xb6\xc3\x9c\xc3\xbc".force_encoding('UTF-8')
526 565 11.times { u << u1 }
527 566 attachment = issue.attachments.first
528 567 assert_equal "#{u}.txt", attachment.filename
529 568 assert_equal 5, attachment.filesize
530 569 assert File.exist?(attachment.diskfile)
531 570 assert_equal 5, File.size(attachment.diskfile)
532 571 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
533 572 end
534 573
535 574 def test_multiple_inline_text_parts_should_be_appended_to_issue_description
536 575 issue = submit_email('multiple_text_parts.eml', :issue => {:project => 'ecookbook'})
537 576 assert_include 'first', issue.description
538 577 assert_include 'second', issue.description
539 578 assert_include 'third', issue.description
540 579 end
541 580
542 581 def test_attachment_text_part_should_be_added_as_issue_attachment
543 582 issue = submit_email('multiple_text_parts.eml', :issue => {:project => 'ecookbook'})
544 583 assert_not_include 'Plain text attachment', issue.description
545 584 attachment = issue.attachments.detect {|a| a.filename == 'textfile.txt'}
546 585 assert_not_nil attachment
547 586 assert_include 'Plain text attachment', File.read(attachment.diskfile)
548 587 end
549 588
550 589 def test_add_issue_with_iso_8859_1_subject
551 590 issue = submit_email(
552 591 'subject_as_iso-8859-1.eml',
553 592 :issue => {:project => 'ecookbook'}
554 593 )
555 594 str = "Testmail from Webmail: \xc3\xa4 \xc3\xb6 \xc3\xbc...".force_encoding('UTF-8')
556 595 assert_kind_of Issue, issue
557 596 assert_equal str, issue.subject
558 597 end
559 598
560 599 def test_quoted_printable_utf8
561 600 issue = submit_email(
562 601 'quoted_printable_utf8.eml',
563 602 :issue => {:project => 'ecookbook'}
564 603 )
565 604 assert_kind_of Issue, issue
566 605 str = "Freundliche Gr\xc3\xbcsse".force_encoding('UTF-8')
567 606 assert_equal str, issue.description
568 607 end
569 608
570 609 def test_gmail_iso8859_2
571 610 issue = submit_email(
572 611 'gmail-iso8859-2.eml',
573 612 :issue => {:project => 'ecookbook'}
574 613 )
575 614 assert_kind_of Issue, issue
576 615 str = "Na \xc5\xa1triku se su\xc5\xa1i \xc5\xa1osi\xc4\x87.".force_encoding('UTF-8')
577 616 assert issue.description.include?(str)
578 617 end
579 618
580 619 def test_add_issue_with_japanese_subject
581 620 issue = submit_email(
582 621 'subject_japanese_1.eml',
583 622 :issue => {:project => 'ecookbook'}
584 623 )
585 624 assert_kind_of Issue, issue
586 625 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88".force_encoding('UTF-8')
587 626 assert_equal ja, issue.subject
588 627 end
589 628
590 629 def test_add_issue_with_korean_body
591 630 # Make sure mail bodies with a charset unknown to Ruby
592 631 # but known to the Mail gem 2.5.4 are handled correctly
593 632 kr = "\xEA\xB3\xA0\xEB\xA7\x99\xEC\x8A\xB5\xEB\x8B\x88\xEB\x8B\xA4.".force_encoding('UTF-8')
594 633 issue = submit_email(
595 634 'body_ks_c_5601-1987.eml',
596 635 :issue => {:project => 'ecookbook'}
597 636 )
598 637 assert_kind_of Issue, issue
599 638 assert_equal kr, issue.description
600 639 end
601 640
602 641 def test_add_issue_with_no_subject_header
603 642 issue = submit_email(
604 643 'no_subject_header.eml',
605 644 :issue => {:project => 'ecookbook'}
606 645 )
607 646 assert_kind_of Issue, issue
608 647 assert_equal '(no subject)', issue.subject
609 648 end
610 649
611 650 def test_add_issue_with_mixed_japanese_subject
612 651 issue = submit_email(
613 652 'subject_japanese_2.eml',
614 653 :issue => {:project => 'ecookbook'}
615 654 )
616 655 assert_kind_of Issue, issue
617 656 ja = "Re: \xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88".force_encoding('UTF-8')
618 657 assert_equal ja, issue.subject
619 658 end
620 659
621 660 def test_should_ignore_emails_from_locked_users
622 661 User.find(2).lock!
623 662
624 663 MailHandler.any_instance.expects(:dispatch).never
625 664 assert_no_difference 'Issue.count' do
626 665 assert_equal false, submit_email('ticket_on_given_project.eml')
627 666 end
628 667 end
629 668
630 669 def test_should_ignore_emails_from_emission_address
631 670 Role.anonymous.add_permission!(:add_issues)
632 671 assert_no_difference 'User.count' do
633 672 assert_equal false,
634 673 submit_email(
635 674 'ticket_from_emission_address.eml',
636 675 :issue => {:project => 'ecookbook'},
637 676 :unknown_user => 'create'
638 677 )
639 678 end
640 679 end
641 680
642 681 def test_should_ignore_auto_replied_emails
643 682 MailHandler.any_instance.expects(:dispatch).never
644 683 [
645 684 "Auto-Submitted: auto-replied",
646 685 "Auto-Submitted: Auto-Replied",
647 686 "Auto-Submitted: auto-generated",
648 687 'X-Autoreply: yes'
649 688 ].each do |header|
650 689 raw = IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
651 690 raw = header + "\n" + raw
652 691
653 692 assert_no_difference 'Issue.count' do
654 693 assert_equal false, MailHandler.receive(raw), "email with #{header} header was not ignored"
655 694 end
656 695 end
657 696 end
658 697
659 698 test "should not ignore Auto-Submitted headers not defined in RFC3834" do
660 699 [
661 700 "Auto-Submitted: auto-forwarded"
662 701 ].each do |header|
663 702 raw = IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
664 703 raw = header + "\n" + raw
665 704
666 705 assert_difference 'Issue.count', 1 do
667 706 assert_not_nil MailHandler.receive(raw), "email with #{header} header was ignored"
668 707 end
669 708 end
670 709 end
671 710
672 711 def test_add_issue_should_send_email_notification
673 712 Setting.notified_events = ['issue_added']
674 713 ActionMailer::Base.deliveries.clear
675 714 # This email contains: 'Project: onlinestore'
676 715 issue = submit_email('ticket_on_given_project.eml')
677 716 assert issue.is_a?(Issue)
678 717 assert_equal 1, ActionMailer::Base.deliveries.size
679 718 end
680 719
681 720 def test_update_issue
682 721 journal = submit_email('ticket_reply.eml')
683 722 assert journal.is_a?(Journal)
684 723 assert_equal User.find_by_login('jsmith'), journal.user
685 724 assert_equal Issue.find(2), journal.journalized
686 725 assert_match /This is reply/, journal.notes
687 726 assert_equal false, journal.private_notes
688 727 assert_equal 'Feature request', journal.issue.tracker.name
689 728 end
690 729
691 730 def test_update_issue_should_accept_issue_id_after_space_inside_brackets
692 731 journal = submit_email('ticket_reply_with_status.eml') do |email|
693 732 assert email.sub!(/^Subject:.*$/, "Subject: Re: [Feature request #2] Add ingredients categories")
694 733 end
695 734 assert journal.is_a?(Journal)
696 735 assert_equal Issue.find(2), journal.journalized
697 736 end
698 737
699 738 def test_update_issue_should_accept_issue_id_inside_brackets
700 739 journal = submit_email('ticket_reply_with_status.eml') do |email|
701 740 assert email.sub!(/^Subject:.*$/, "Subject: Re: [#2] Add ingredients categories")
702 741 end
703 742 assert journal.is_a?(Journal)
704 743 assert_equal Issue.find(2), journal.journalized
705 744 end
706 745
707 746 def test_update_issue_should_ignore_bogus_issue_ids_in_subject
708 747 journal = submit_email('ticket_reply_with_status.eml') do |email|
709 748 assert email.sub!(/^Subject:.*$/, "Subject: Re: [12345#1][bogus#1][Feature request #2] Add ingredients categories")
710 749 end
711 750 assert journal.is_a?(Journal)
712 751 assert_equal Issue.find(2), journal.journalized
713 752 end
714 753
715 754 def test_update_issue_with_attribute_changes
716 755 journal = submit_email('ticket_reply_with_status.eml', :allow_override => ['status','assigned_to','start_date','due_date', 'float field'])
717 756 assert journal.is_a?(Journal)
718 757 issue = Issue.find(journal.issue.id)
719 758 assert_equal User.find_by_login('jsmith'), journal.user
720 759 assert_equal Issue.find(2), journal.journalized
721 760 assert_match /This is reply/, journal.notes
722 761 assert_equal 'Feature request', journal.issue.tracker.name
723 762 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
724 763 assert_equal '2010-01-01', issue.start_date.to_s
725 764 assert_equal '2010-12-31', issue.due_date.to_s
726 765 assert_equal User.find_by_login('jsmith'), issue.assigned_to
727 766 assert_equal "52.6", issue.custom_value_for(CustomField.find_by_name('Float field')).value
728 767 # keywords should be removed from the email body
729 768 assert !journal.notes.match(/^Status:/i)
730 769 assert !journal.notes.match(/^Start Date:/i)
731 770 end
732 771
733 772 def test_update_issue_with_attachment
734 773 assert_difference 'Journal.count' do
735 774 assert_difference 'JournalDetail.count' do
736 775 assert_difference 'Attachment.count' do
737 776 assert_no_difference 'Issue.count' do
738 777 journal = submit_email('ticket_with_attachment.eml') do |raw|
739 778 raw.gsub! /^Subject: .*$/, 'Subject: Re: [Cookbook - Feature #2] (New) Add ingredients categories'
740 779 end
741 780 end
742 781 end
743 782 end
744 783 end
745 784 journal = Journal.order('id DESC').first
746 785 assert_equal Issue.find(2), journal.journalized
747 786 assert_equal 1, journal.details.size
748 787
749 788 detail = journal.details.first
750 789 assert_equal 'attachment', detail.property
751 790 assert_equal 'Paella.jpg', detail.value
752 791 end
753 792
754 793 def test_update_issue_should_send_email_notification
755 794 ActionMailer::Base.deliveries.clear
756 795 journal = submit_email('ticket_reply.eml')
757 796 assert journal.is_a?(Journal)
758 797 assert_equal 1, ActionMailer::Base.deliveries.size
759 798 end
760 799
761 800 def test_update_issue_should_not_set_defaults
762 801 journal = submit_email(
763 802 'ticket_reply.eml',
764 803 :issue => {:tracker => 'Support request', :priority => 'High'}
765 804 )
766 805 assert journal.is_a?(Journal)
767 806 assert_match /This is reply/, journal.notes
768 807 assert_equal 'Feature request', journal.issue.tracker.name
769 808 assert_equal 'Normal', journal.issue.priority.name
770 809 end
771 810
772 811 def test_replying_to_a_private_note_should_add_reply_as_private
773 812 private_journal = Journal.create!(:notes => 'Private notes', :journalized => Issue.find(1), :private_notes => true, :user_id => 2)
774 813
775 814 assert_difference 'Journal.count' do
776 815 journal = submit_email('ticket_reply.eml') do |email|
777 816 email.sub! %r{^In-Reply-To:.*$}, "In-Reply-To: <redmine.journal-#{private_journal.id}.20060719210421@osiris>"
778 817 end
779 818
780 819 assert_kind_of Journal, journal
781 820 assert_match /This is reply/, journal.notes
782 821 assert_equal true, journal.private_notes
783 822 end
784 823 end
785 824
786 825 def test_reply_to_a_message
787 826 m = submit_email('message_reply.eml')
788 827 assert m.is_a?(Message)
789 828 assert !m.new_record?
790 829 m.reload
791 830 assert_equal 'Reply via email', m.subject
792 831 # The email replies to message #2 which is part of the thread of message #1
793 832 assert_equal Message.find(1), m.parent
794 833 end
795 834
796 835 def test_reply_to_a_message_by_subject
797 836 m = submit_email('message_reply_by_subject.eml')
798 837 assert m.is_a?(Message)
799 838 assert !m.new_record?
800 839 m.reload
801 840 assert_equal 'Reply to the first post', m.subject
802 841 assert_equal Message.find(1), m.parent
803 842 end
804 843
805 844 def test_should_convert_tags_of_html_only_emails
806 845 with_settings :text_formatting => 'textile' do
807 846 issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
808 847 assert issue.is_a?(Issue)
809 848 assert !issue.new_record?
810 849 issue.reload
811 850 assert_equal 'HTML email', issue.subject
812 851 assert_equal "This is a *html-only* email.\r\n\r\nh1. With a title\r\n\r\nand a paragraph.", issue.description
813 852 end
814 853 end
815 854
816 855 def test_should_handle_outlook_web_access_2010_html_only
817 856 issue = submit_email('outlook_web_access_2010_html_only.eml', :issue => {:project => 'ecookbook'})
818 857 assert issue.is_a?(Issue)
819 858 issue.reload
820 859 assert_equal 'Upgrade Redmine to 3.0.x', issue.subject
821 860 assert_equal "A mess.\r\n\r\n--Geoff Maciolek\r\nMYCOMPANYNAME, LLC", issue.description
822 861 end
823 862
824 863 def test_should_handle_outlook_2010_html_only
825 864 issue = submit_email('outlook_2010_html_only.eml', :issue => {:project => 'ecookbook'})
826 865 assert issue.is_a?(Issue)
827 866 issue.reload
828 867 assert_equal 'Test email', issue.subject
829 868 assert_equal "Simple, unadorned test email generated by Outlook 2010. It is in HTML format, but" +
830 869 " no special formatting has been chosen. I’m going to save this as a draft and then manually" +
831 870 " drop it into the Inbox for scraping by Redmine 3.0.2.", issue.description
832 871 end
833 872
834 873 test "truncate emails with no setting should add the entire email into the issue" do
835 874 with_settings :mail_handler_body_delimiters => '' do
836 875 issue = submit_email('ticket_on_given_project.eml')
837 876 assert_issue_created(issue)
838 877 assert issue.description.include?('---')
839 878 assert issue.description.include?('This paragraph is after the delimiter')
840 879 end
841 880 end
842 881
843 882 test "truncate emails with a single string should truncate the email at the delimiter for the issue" do
844 883 with_settings :mail_handler_body_delimiters => '---' do
845 884 issue = submit_email('ticket_on_given_project.eml')
846 885 assert_issue_created(issue)
847 886 assert issue.description.include?('This paragraph is before delimiters')
848 887 assert issue.description.include?('--- This line starts with a delimiter')
849 888 assert !issue.description.match(/^---$/)
850 889 assert !issue.description.include?('This paragraph is after the delimiter')
851 890 end
852 891 end
853 892
854 893 test "truncate emails with a single quoted reply should truncate the email at the delimiter with the quoted reply symbols (>)" do
855 894 with_settings :mail_handler_body_delimiters => '--- Reply above. Do not remove this line. ---' do
856 895 journal = submit_email('issue_update_with_quoted_reply_above.eml')
857 896 assert journal.is_a?(Journal)
858 897 assert journal.notes.include?('An update to the issue by the sender.')
859 898 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
860 899 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
861 900 end
862 901 end
863 902
864 903 test "truncate emails with multiple quoted replies should truncate the email at the delimiter with the quoted reply symbols (>)" do
865 904 with_settings :mail_handler_body_delimiters => '--- Reply above. Do not remove this line. ---' do
866 905 journal = submit_email('issue_update_with_multiple_quoted_reply_above.eml')
867 906 assert journal.is_a?(Journal)
868 907 assert journal.notes.include?('An update to the issue by the sender.')
869 908 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
870 909 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
871 910 end
872 911 end
873 912
874 913 test "truncate emails with multiple strings should truncate the email at the first delimiter found (BREAK)" do
875 914 with_settings :mail_handler_body_delimiters => "---\nBREAK" do
876 915 issue = submit_email('ticket_on_given_project.eml')
877 916 assert_issue_created(issue)
878 917 assert issue.description.include?('This paragraph is before delimiters')
879 918 assert !issue.description.include?('BREAK')
880 919 assert !issue.description.include?('This paragraph is between delimiters')
881 920 assert !issue.description.match(/^---$/)
882 921 assert !issue.description.include?('This paragraph is after the delimiter')
883 922 end
884 923 end
885 924
886 925 def test_attachments_that_match_mail_handler_excluded_filenames_should_be_ignored
887 926 with_settings :mail_handler_excluded_filenames => '*.vcf, *.jpg' do
888 927 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
889 928 assert issue.is_a?(Issue)
890 929 assert !issue.new_record?
891 930 assert_equal 0, issue.reload.attachments.size
892 931 end
893 932 end
894 933
895 934 def test_attachments_that_do_not_match_mail_handler_excluded_filenames_should_be_attached
896 935 with_settings :mail_handler_excluded_filenames => '*.vcf, *.gif' do
897 936 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
898 937 assert issue.is_a?(Issue)
899 938 assert !issue.new_record?
900 939 assert_equal 1, issue.reload.attachments.size
901 940 end
902 941 end
903 942
904 943 def test_email_with_long_subject_line
905 944 issue = submit_email('ticket_with_long_subject.eml')
906 945 assert issue.is_a?(Issue)
907 946 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]
908 947 end
909 948
910 949 def test_first_keyword_should_be_matched
911 950 issue = submit_email('ticket_with_duplicate_keyword.eml', :allow_override => 'priority')
912 951 assert issue.is_a?(Issue)
913 952 assert_equal 'High', issue.priority.name
914 953 end
915 954
916 955 def test_keyword_after_delimiter_should_be_ignored
917 956 with_settings :mail_handler_body_delimiters => "== DELIMITER ==" do
918 957 issue = submit_email('ticket_with_keyword_after_delimiter.eml', :allow_override => 'priority')
919 958 assert issue.is_a?(Issue)
920 959 assert_equal 'Normal', issue.priority.name
921 960 end
922 961 end
923 962
924 963 def test_new_user_from_attributes_should_return_valid_user
925 964 to_test = {
926 965 # [address, name] => [login, firstname, lastname]
927 966 ['jsmith@example.net', nil] => ['jsmith@example.net', 'jsmith', '-'],
928 967 ['jsmith@example.net', 'John'] => ['jsmith@example.net', 'John', '-'],
929 968 ['jsmith@example.net', 'John Smith'] => ['jsmith@example.net', 'John', 'Smith'],
930 969 ['jsmith@example.net', 'John Paul Smith'] => ['jsmith@example.net', 'John', 'Paul Smith'],
931 970 ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsTheMaximumLength Smith'] => ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsT', 'Smith'],
932 971 ['jsmith@example.net', 'John AVeryLongLastnameThatExceedsTheMaximumLength'] => ['jsmith@example.net', 'John', 'AVeryLongLastnameThatExceedsTh']
933 972 }
934 973
935 974 to_test.each do |attrs, expected|
936 975 user = MailHandler.new_user_from_attributes(attrs.first, attrs.last)
937 976
938 977 assert user.valid?, user.errors.full_messages.to_s
939 978 assert_equal attrs.first, user.mail
940 979 assert_equal expected[0], user.login
941 980 assert_equal expected[1], user.firstname
942 981 assert_equal expected[2], user.lastname
943 982 assert_equal 'only_my_events', user.mail_notification
944 983 end
945 984 end
946 985
947 986 def test_new_user_from_attributes_should_use_default_login_if_invalid
948 987 user = MailHandler.new_user_from_attributes('foo+bar@example.net')
949 988 assert user.valid?
950 989 assert user.login =~ /^user[a-f0-9]+$/
951 990 assert_equal 'foo+bar@example.net', user.mail
952 991 end
953 992
954 993 def test_new_user_with_utf8_encoded_fullname_should_be_decoded
955 994 assert_difference 'User.count' do
956 995 issue = submit_email(
957 996 'fullname_of_sender_as_utf8_encoded.eml',
958 997 :issue => {:project => 'ecookbook'},
959 998 :unknown_user => 'create'
960 999 )
961 1000 end
962 1001 user = User.order('id DESC').first
963 1002 assert_equal "foo@example.org", user.mail
964 1003 str1 = "\xc3\x84\xc3\xa4".force_encoding('UTF-8')
965 1004 str2 = "\xc3\x96\xc3\xb6".force_encoding('UTF-8')
966 1005 assert_equal str1, user.firstname
967 1006 assert_equal str2, user.lastname
968 1007 end
969 1008
970 1009 def test_extract_options_from_env_should_return_options
971 1010 options = MailHandler.extract_options_from_env({
972 1011 'tracker' => 'defect',
973 1012 'project' => 'foo',
974 1013 'unknown_user' => 'create'
975 1014 })
976 1015
977 1016 assert_equal({
978 1017 :issue => {:tracker => 'defect', :project => 'foo'},
979 1018 :unknown_user => 'create'
980 1019 }, options)
981 1020 end
982 1021
983 1022 def test_safe_receive_should_rescue_exceptions_and_return_false
984 1023 MailHandler.stubs(:receive).raises(Exception.new "Something went wrong")
985 1024
986 1025 assert_equal false, MailHandler.safe_receive
987 1026 end
988 1027
989 1028 private
990 1029
991 1030 def submit_email(filename, options={})
992 1031 raw = IO.read(File.join(FIXTURES_PATH, filename))
993 1032 yield raw if block_given?
994 1033 MailHandler.receive(raw, options)
995 1034 end
996 1035
997 1036 def assert_issue_created(issue)
998 1037 assert issue.is_a?(Issue)
999 1038 assert !issue.new_record?
1000 1039 issue.reload
1001 1040 end
1002 1041 end
General Comments 0
You need to be logged in to leave comments. Login now