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