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