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