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