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