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