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